LEDを点滅するプログラムを完成させます。
今回の説明
回路を完成させるために以下の順序で説明しています。このエントリの説明は(3)「プログラムを作る」の部分の最後の部分になります。
- LEDを電池と抵抗のみで光らせる回路を組み立てる
PICマイコンの回路を組み立てる前に、まずはブレッドボードに慣れておくことにします。電池、抵抗、LEDのみを使って、ブレッドポード上に回路を組んでLEDを光らせてみます。ここでは電池、抵抗、発光ダイオードの回路記号と回路図の説明をして、回路図からブレッドボードに組む方法を説明します。まずはブレッドボードに慣れましょう! - PICマイコンのベース回路を組む
はじめの一歩の回路は、LEDを1秒に1回光らせるだけの回路です。この回路をブレッドボードに組み立てます。 - プログラムを作る
LEDを1秒に1回光らせるプログラムを作成します。 - PICマイコンに書き込んで動作させる
作成したプログラムをPICマイコンに書き込んで動作させてみます。 - ベース回路にスイッチを追加
LEDの点滅をスイッチで開始させるために、ベース回路にスイッチを追加します。これまではLEDを光らせる、という出力制御をしましたが、今度はPICマイコンで外部から信号を入力する方法を確認します。 - ベース回路にブザーを追加
スタートスイッチ付きの、1秒に1回光らせる回路を作りましたので、ブザーを追加してタイマーとして完成させます。
PIC12F1822データシートの入手
これからプログラムのメイン部分を作成していきます。メイン部分については、PIC12F1822のデータシートの読み方も説明します。
ただ、最初からデータシートを使って説明するとかなり難しくなってしまいますので、最初は要点のみ説明して、そのあとにデータシートを読んでみる、という説明方法で進めます。もしデータシートを読むことが難しかったら、とりあえず要点の説明のみを理解して先に進めてください。
最初はデータシートを読むのは辛いですが、その内容構成(章立てなど)はどのPICマイコンでも大体同じです。最初は読み解くのに時間がかかりますが、読めるようになると、違うPICマイコンで何か設計する場合でも、必要な設定などすぐに情報を得られるようになります。
また、できれば英語版のデータシートを見るようにして、どうしてもわからなかったら日本語版を参照するようにしてみてください。というのは、PIC12F1822のように日本語版が用意されているマイコンもありますが、多くのPICマイコンではデータシートは英語版のみ提供されているためです。
それでは必要なデータシートをダウンロードして手元に置いておきましょう!
PIC12F1822データシート(PDFファイル/日本語版)
これから説明する部分
前回までに、「コメント」「ヘッダファイルインクルード」「PICマイコンコンフィグレーション設定」の各部分について説明しました。
特にコンフィグレーション部分は眠かったですよね…。それに基礎という割にはちょっと難しかったような…。でも大丈夫です。ここまでのところは、今回製作するタイマーのように、電池で動作させ、内部クロックを使用するのであれば、「典型的な設定」で特に問題ありません。
ただ今回説明する以下の図の部分、「その他設定」と「メイン関数」については、作るものによって大きく異なりますので、内容をできるだけ理解するようにしてみてください。
なお、「その他設定」部分は、主に#define文になります。#define文はメイン関数で必要なものを設定することになりますので、「メイン関数」から説明を始めて、必要になったときに「その他設定」を説明します。
メイン関数内の構成概略
MPLABXのFileメニューからmain.cファイルを作成すると、新規ファイルが作成されて自動的に以下のコードが記述されている状態になります。
void main(void) {
return;
}
このメイン関数内にプログラムを記述していきます。今回製作するタイマーに限らず、他の処理でもメイン関数はほぼ以下のような構成となります。
まず内部クロック設定部分です。PICマイコンコンフィグレーション設定では、「内部クロックを使用する」という設定にしましたが、あくまで「内部クロックを使用する」という設定だけで、具体的な周波数は設定しませんでした。内部クロックの周波数は、メイン関数の中のプログラムで設定します。
次のピン設定部分では、PICマイコンの各ピンの機能を設定します。具体的には、デジタルにするかアナログにするかの設定と、入力ピンにするか出力ピンにするかの設定です。
この基礎編では、LEDのON/OFFを制御、つまりデジタル制御をしますので、LEDを接続しているピンに対して「デジタル」の「出力ピン」という設定を行います。また、この後スイッチを追加しますが、スイッチはOFF/ONの2つの状態、つまりデジタル値を読み取ります。スイッチを接続しているピンに対しては「デジタル」の「入力ピン」という設定を行います。
ここで「デジタル」と「アナログ」についてもう少し詳しく説明しておきます。以下の説明ではPICマイコンの電源電圧を5Vとし、ピンの入出力設定は「入力ピン」とします。
ピンの機能を「デジタル」にした場合、ピンの入力はデジタル、つまり0Vか5Vのどちらかになります。
一方、ピンの機能を「アナログ」にした場合は、入力は0V〜5Vの間、例えば1.25V、などと途中の電圧をを読み取る(入力)することができます。
「そうなんだ」と、なんとなく納得してしまいそうですが、よく考えてみると、ちょっと、あれっ?、って思いませんか? 「アナログ」のときは0V〜5Vまで読み取るできるのだから、デジタルで0Vと5Vの2つを使用したい場合も、「アナログ」設定で0Vと5Vのみを使えばいいのではないか、という疑問です。
いちいち「デジタル」か「アナログ」かという設定はやめて、すべて「アナログ」設定にしてしまい、デジタルで使いたい人は、0Vか5Vのみを使用、アナログで使いたい人は0V〜5Vの可変電圧を使用、ということにしてしまえばよさそうですよね。そうすれば、いちいち「デジタル」か「アナログ」かなんて面倒な設定をひとつ減らすことができます。
ではなぜ「デジタル」と「アナログ」をわざわざ設定するのでしょうか。
理由は、「デジタル」か「アナログ」かの設定に応じて、PICマイコン内部の回路をデジタル用、アナログ用に切り替えるためです。内部回路が異なると、ピンを制御する動作速度や消費電力が異なります。具体的には「デジタル」の方が「アナログ」よりもずっと早く処理できて、消費電力も少なくなります。
もし「アナログ」設定のみにして、デジタルで使いたい人は0Vと5Vのみを使用する、という場合、デジタルとしてしか(0Vと5Vしか)使わないのに「アナログ」の遅い速度(といっても早いことは早いですが)に付き合わなくてはならない、というデメリットがでてきてしまいます。また処理に時間がかかる分、電池(電力)も多く消費してしまいます。
マイコンのように何かの装置に組み込んで電池で動作させる場合、電力消費を抑えることは重要です。内部回路を変える(=動作速度や消費電力を適切にする)ために「デジタル」か「アナログ」か設定するようになっています。
ここまでの設定で、クロック動作と各ピンの機能を設定しましたので、いよいよプログラムでピンを制御することが可能となります。
最初に、ピン出力の初期設定部分で、使用するピンを初期状態を設定します。LEDは1秒に1回光らせる制御をしますので、電源投入直後はLEDは消しておきます。またプログラム内で使用する変数なども何か初期設定が必要でしたらここで設定します。初期設定ができたら、いよいよ動作処理となります。
メイン動作処理の部分はどのような制御をするかでかなり異なってきます。この基礎編では、最初にLEDの点滅制御を行い、その次はLEDの点滅開始させるスイッチを付けて、最後はブザーをつけて鳴らしてみます。この動作処理の部分がどのように変わっていくか確認してみてください。
それではメイン関数の詳細を説明します。
内部クロック設定
内部クロックを使用する場合、その周波数は「OSCCON」という変数(レジスタと呼びます)に値を代入することにより設定します。この変数は “xc.h”ファイル(#includeでインクルードしています)に定義されています。
「OSCCON」は「Oscillator Configuration」(発振モジュール設定)の略です。
これから2進数と16進数がでてきますので、もし忘れてしまっていたら復習しておきましょう。
基礎編と応用編では内部クロックの周波数を1MHzに設定します。1MHzに設定するには2進数表記では
OSCCON = 0b01011000;
または16進数表記では
OSCCON = 0x58;
と設定すればよいのですが、なぜこのような値を設定するのか、他の周波数に設定するときはどうすればよいか説明します。
[補足] ANSI Cでは上記の2進数表記(0bで始まる1と0の記述)は規格化されていませんが、XCコンパイラでは使用できます。
OSCCONは1バイト、つまり8ビットのレジスタで、以下のように構成されています。
この表はPIC12F1822データシートの内容を簡略化したものです。
表の見方ですが、1行目はビット位置、2行目は設定項目、3行目はその設定項目の意味です。また4行目は電源投入時やリセット時のデフォルト値です。それでは、具体的な設定内容を説明します。
まず第7ビットは、PLLを使用するかどうかの設定です。早速、あれっ?って感じですよね。PLLの設定はコンフィグレーション設定にありました。ここにも使用するかどうかの設定があるのは不思議ですよね。
このSPLLENは「Software PLL Enable」の略で、このビットでPLLを使用する/しないの設定ができます。ただし、コンフィグレーション設定でPLLをONにした場合はこのビットの設定は無効で、常にPLLが使用されます(つまり周波数は常に4倍される)。コンフィグレーション設定でPLLをOFFにした場合、プログラム動作中にPLLをONにしたい場合は、このビットを1にするとPLLが有効になります。0を設定した場合はPLLは無効です。
基礎編と応用編では、PLLは使用しませんので0を設定します。
第6ビットから第3ビットは、この4ビットでひとつの意味を表していて、内部クロックの周波数を表します。設定名称は「IRCF」ですが、この意味はデータシートでは「Internal Oscillator Frequency Select(内部発振周波数選択)」と説明されています(後でデータシートを確認します)。なんか設定名称と英語があってないですよね。「IRCF」は、「Internal RC oscillator Frequency」の略で、日本語に無理やり訳すと、「内部の抵抗とコンデンサを使用した発振モジュールの周波数」という感じでしょうか。設定名称と英語は合わせた方がいいような…
この4ビットにどのような設定をするとどの周波数に設定できるか以下にまとめておきます。
第6ビット | 第5ビット | 第4ビット | 第3ビット | 周波数 |
---|---|---|---|---|
0 | 0 | 0 | 0か1 | 31kHz (LF) |
0 | 0 | 1 | 0 | 31.25kHz (MF) |
0 | 0 | 1 | 1 | 31.25kHz (HF) |
0 | 1 | 0 | 0 | 62.5kHz (MF) |
0 | 1 | 0 | 1 | 125kHz (MF) |
0 | 1 | 1 | 0 | 250kHz (MF) |
0 | 1 | 1 | 1 | 500kHz (MF) (デフォルト) |
1 | 0 | 0 | 0 | 125kHz (HF) |
1 | 0 | 0 | 1 | 250kHz (HF) |
1 | 0 | 1 | 0 | 500kHz (HF) |
1 | 0 | 1 | 1 | 1MHz (HF) |
1 | 1 | 0 | 0 | 2MHz (HF) |
1 | 1 | 0 | 1 | 4MHz (HF) |
1 | 1 | 1 | 0 | 8MHz (HF) |
1 | 1 | 1 | 1 | 16MHz (HF) |
1MHzの場合は、第6ビットから第3ビットは「1011」という設定にすればよいことがわかります。
ところで、すぐにお気づきかもしれませんが、31.25kHzとか、125kHzとか、他の周波数もそうですが、2回出てきてますよね。例えば250kHzに設定したい場合、「0110の250kHz(MF)」と「1001の250kHz(HF)」の設定があります。これって、どちらに設定すればよいのでしょうか。
結論としては、「(MF)」と「(HF)」の両方がある周波数では、基本的には「(MF)」を使用するようにします。ただし、プログラムによっては「(HF)」を選択した方が良いケースもあります。
この部分は基礎編の範囲を超えるかもしれませんが、周波数設定はPICマイコンの基本ですので、詳しく説明します。
周波数の右側の文字を見ると、「(MF)」「(HF)」の他に一番最初の31kHzのところに「(LF)」がありますよね。
最初に「(MF)」、「(HF)」、「(LF)」は何を意味しているか、ここから解明していきましょう。
今作っているプログラムでは、PICマイコンのコンフィグレーション設定部分で、クロック設定を「内部クロックを使用する」としました。「内部クロック」に設定した場合、当然PICマイコンの内部にあるクロック発生モジュールで生成したクロックを使用するわけですが、実は内部のクロック発生モジュールは3個あります(ハードウエア的に3個あります)。その3個は以下のモジュールです。
- 低周波モジュール(Low Frequency = LF)
31kHzの周波数を発生させるモジュールです。あまり精度は良くありません。ウォッチドッグタイマーなどに使用されます。 - 中周波モジュール(Middle Frequency = MF)
500kHzの周波数を発生させるモジュールです。工場出荷時に調整されますので、ある程度の精度は期待できます。プログラム実行に使用します。この周波数を元に、この半分の250kHz、さらに半分の125kHz、という感じで31.25kHzまでの周波数を生成します。 - 高周波モジュール(High Frequency = HF)
16MHzの周波数を発生させるモジュールです。こちらも工場出荷時に調整されますので、ある程度の精度は期待できます。プログラム実行に使用します。この周波数を元に、この半分の周波数である8MHz、さらに半分の4MHz、という感じで125kHzまでと、その半分の半分の31.25kHzの周波数を生成します。
周波数の右側の文字、「(MF)」などは、どの周波数モジュールを使用して生成しているか、を意味しています。例えば、250kHz(MF)であれば、中周波モジュールの500kHzのクロックを元に生成している250kHz、という意味です。250kHz(HF)であれば、高周波モジュールの16MHzのクロックを元に生成している、という意味になります。
クロックの発生元モジュールが違うとはいっても、結局は250kHzの矩形波です。同じ250kHzだったら何が違うのでしょうか。250kHzだったら片方だけでいいような気もします。
もしかしたらお気づきかもしれませんが、周波数が高いと消費電力が多くなります。つまり、同じ250kHzを指定するにしても「250kHz(HF)」は「250kHz(MF)」よりも消費電力が多くなります。ということは、250kHzにしたい場合、消費電力の少ない「250kHz(MF)」を使用した方がよいことになります。となると、ますます「250kHz(HF)」は必要なさそうですよね。
ではなぜ「250kHz(HF)」があるでしょうか。
今回製作するタイマーのように、プログラム動作中は周波数を変更しない場合、「(MF)」と「(HF)」の2種類があれば、消費電力の少ない「(MF)」を選択します。
一方でスリープモードなどを設けた場合、スリープ中は周波数は少ない方が消費電力は低くなります。当然、プログラム動作中はキビキビ動く必要があるので、ある程度高い周波数が必要です。このようにプログラム実行中に周波数を切り替えたい場合があります。
周波数の切り替えは、同じクロック発生モジュールであれば、一瞬で周波数が変更されます。例えば、「1MHz(HF)」から「500kHz(HF)」というように、高周波モジュールを使用する周波数同士の切り替えは一瞬です。しかし、例えば「1MHz(HF)」から「500kHz(MF)」というように周波数切り替えに伴い、モジュールが変更される場合は、切り替えの時間がかかります。
つまりクロック周波数を切り替える時に、なるべくクロック発生モジュールの切り替えがないように、同じ周波数でもMFとHFが用意されているわけです。
なお、スリープモードにする場合、一般的に一番低い周波数(31.25kHz)にします。(HF)の周波数は125kHzより低い周波数は途切れていますが、31.25kHzはスリープモードで使用する重要な周波数ですので、(MF)も(HF)も両方用意されています。
かなり込み入った説明になってしまいましたが、結論は「(MF)」があれば「(MF)」を使う、ということです。(長い説明の割には結論はこれだけ、って感じですが…)
これで第6ビットから第3ビットまでの説明は終わりです。
第2ビットは使用されていません。
第1ビットと第0ビットは、クロック元の指定です。「SCS」は「System Clock Select」です。なんだかクロック元の指定って散々出てきたような。。。
第1ビット | 第0ビット | 意味 |
---|---|---|
1 | 0か1 | 内部クロックを使用 |
0 | 1 | Timer1を使用 |
0 | 0 | コンフィグレーション設定のFOSCに従う |
「11」「10」の「内部クロックを使用」と、「00」の「コンフィグレーション設定のFOSCに従う」はすぐにわかると思います。「01」の「Timer1を使用」ですが、これは基礎編、応用編では出てこない内容になりますので詳しい説明は省略します。
コンフィグレーション設定のクロック指定(FOSC)で「内部クロックを使用する」という設定をしましたので、「00」のコンフィグレーション設定に従う、の設定でもいいですし、「10」または「11」の内部クロックを使用、の設定でも大丈夫です。通常はクロックの設定はコンフィグレーション設定で行いますので、「00」にします。(すみません。第17回でサンプルとして動作させたプログラムでは、この設定を「10」にしていましたが、普通は「00」ですので、「00」に設定することにします)
ということで、内部クロックの周波数を1MHzに設定する場合、各ビットは以下のように設定することになります。なお、使われていないビットにも0か1を入れておく必要がありますので、あまり深い意味はありませんが0を入れておきます。
プログラムにすると、以下のようになります。
OSCCON = 0b01011000;
または16進数で
OSCCON = 0x58;
になります。なんかこれだけの設定をするのに、大変でしたよね。お疲れのところすみませんが、実際のデータシートを見てみます。
OSCCONの説明は、英語版データシートは65ページ、日本語版では69ページにあります。以下は英語版データシートの抜粋です。
(Microchip Technology社 PIC12F1822データシートより引用・加工)
それでは、今まで説明した内容を念頭に置いて、このデータシートを読んで見ます。
最初のレジスタ構成の部分は、3行で構成さています。一番上の行は、OSCCONレジスタの各ビットの「読み書き属性」と「デフォルト値」の説明です。読み書き属性の行の意味は、次のレジスタ構成の記号の意味に説明されています。この記号の意味を以下にまとめます。
記号 | 意味 | 解説 |
---|---|---|
R | Readable bit | 読取り可能ビット (「R」だけのビットは書き込みしても反映されない) |
W | Writable bit | 書込み可能ビット |
U | Unimplemented bit | 無効ビット (読み出すと常にゼロ、書き込みしても反映されない) |
u | Unchanged | 値変更不可 |
x | Unknown | 値不定 |
-n/n | Value at POR&BOR / Other Resets | R/Wなどのあとに書かれている「-0/0」などの数字は、スラッシュの左側の数字は電源投入時またはブラウンアウトリセット時のデフォルト値、スラッシュの右側の数字はその他のリセット時のデフォルト値 |
1 | Bit is set | 1がセットされる |
0 | Bit is clearted | 0がセットされる |
2行目は設定項目で、1ビットから構成されている項目もありますし(SPLLEN)、複数ビットから構成されているものもあります(IRCF)。一番下の行は、一番左が第7ビット(bit7)、一番右が第0ビット(bit0)であることを示しています。
各項目の設定値では、それぞれの設定項目の簡単な意味の説明と、設定値が説明されています。なお、設定項目の詳細な説明については、データシートの他のところでくわ欲しく説明されています。
すでに各設定項目の内容は説明しましたので、ここではデータシートを読むポイントのみ説明します。
2番目の設定項目「IRCF」を見てみましょう。最初に「bit 6-3」とありますが、これはこの設定項目は第6ビットから第3ビットの4ビットで指定する、という意味です。
次に「IRCF<3:0>>」というのは、IRCF単独で見ると、第3ビットから第0ビットの4ビット構成になっている、という意味です。そのあとは、設定項目の意味と、設定値に対する意味が書かれています。
あとは、今まで説明した内容と突き合わせると、データシートのOSCCONのページがだいたいわかると思います。このOSCCONですが、PICマイコンによって微妙に異なりますので、他のPICマイコンを使用する場合、データシートのOSCCONのページを探して、このように読み解くようにしてください。
ピンの機能設定
次に、PICマイコンのピンの機能設定を行います。
かなり前の回になりますが、PICマイコンのピンはそれぞれたくさんの機能があって、プログラムでその機能を指定する、と説明しました。以下は、PIC12F1822の各ピンの割り当てを説明したものです。
なお、この図で説明している機能は、基礎編で出てくる内容のみに絞っています。実際には各ピンにはもっと多くの機能が割り当てられています。実際に各ピンにどのような機能が割り当てられているかは、最後にデータシートで確認します。
それでは、上の図についてもう少し詳しく説明します。
入出力制御ができるピンは、2番ピンから7番ピンです。つまり電源ピン(1番ピンのVDDと8番ピンのVSS)以外は基本的に入出力制御ができます。ただし、これらのピンはすべて同じ性質ではなく、ちょっとずつ異なります。
最初はアナログかデジタルか、という観点です。
「RA」という文字のついたピンは、デジタル制御ができることを示しています。ピンの機能を見ると、電源ピンを除くすべてのピン(2番ピン〜7番ピン)に対して、「RA0」から「RA5」が割り当てられていることがわかります。
「AN」という文字のついたピンは、アナログ制御ができることを示しています。アナログ制御ができるピンは、デジタルピンよりも少なく、「AN0」から「AN3」までの4ピンです。またデジタルピンと番号がずれています。
このように、デジタル/アナログ指定は全てのピンで自由に設定できるわけではなく、例えば2番ピンは「RA5」しかありませんので、デジタル制御しかできない、ということになります。
次に、入力か出力か、という観点です。
まず、デジタルで使用する「RA」ピンは、入力か出力かの設定が可能です。ただし例外があります。4番ピンの「RA3」は入力しか設定できません。この例外はほぼ全てのPICマイコンで共通で、ほとんどのPICマイコンでは4番ピンは「RA3」が割り当てられていて、入力しか設定できません。
この基礎編では、2番ピンにLEDを接続して点滅制御しますので、2番ピンを「デジタル」に設定して、さらに「出力」に設定することになります。
また、アナログで使用する「AN」ピンは、全て入力になります。この「AN」という機能は、A/Dコンバータ(Analog-to-Digital Converter)と呼ばれている機能で、ピンのアナログ電圧値(Analog)をデジタル値(Digital)に変換する(Convert)機能です。
応用編ではこのA/Dコンバータの機能を使ってピンの電圧値を読んでみることにします。その場合はピンの機能を「アナログ」に設定にして、さらにそのピンを「入力」に設定することになります。
ちょっと疑問があるかもしれません。デジタル制御する場合は例外はあるものの入力と出力の指定ができます。一方でアナログ制御する場合は入力しか設定できません。ではアナログの出力制御はどうするのか、という疑問です。
実際、PIC12F1822にはアナログの出力制御ができるピンが1ピンあります(7番ピン)。このピンをアナログ出力制御するには、他のレジスタを使って設定します。基礎編、応用編とも、この設定は使用しませんが、アナログ出力する場合は、これから説明するデジタル/アナログ設定と入力/出力設定を組み合わせて設定できるわけではない、ということを頭の片隅に置いておいてください。
それではこれから、各ピンのデジタル/アナログ設定、入力/出力設定を行います。
デジタル/アナログ制御の設定は「ANSELA」レジスタで行います。ANSELAは「Analog Select (PortA)」の略です。直訳すると「アナログ選択」ですが、意味的には「アナログ/デジタルの選択」という感じです。
入出力設定は「TRISA」レジスタで行います。TRISAは「Tri-state (PortA)」の略です。「Tri-state」を直訳すると「3状態」という意味ですが、、、とりあえず入出力設定をするんだ、と覚えておきましょう。(興味のある方は、「3ステートバッファ」という電子部品がありますので、ネットなどで調べてみてください。TRISAはこの3ステートバッファの設定になります)
ところで、ANSELAにしても、TRISAにしても、最後に文字「A」がついています。これは「PortA」を意味しているのですが、これってなんなんでしょうか。
この「Port」は、PICマイコンに限らず、一般的なマイコンでも同じ言葉、意味で使われています(最後のAはあとで説明します)。「Port」は日本語では「港」という意味ですが、マイコンの「Port」も「港」の意味合いで使われています。
ちょっと話が逸れますが、大きめな港に行くと、どこからきたかわからないけど、なんとなく遠くの知らない国から来たような大型船が停泊してたりしますよね。そんなのを見ると、この港ってなんだか自分の知らない外界と繋がってるんだなぁ、なんて思いを馳せたりします。
マイコンの「Port」もそんな感じです。マイコンでピンの制御をするには、プログラムで特定の変数(レジスタ)に値を設定したり、特定の変数(レジスタ)を読み取ることにより実現しています。このレジスタはマイコン内部で直接ピンに接続されているのですが、このレジスタ部分を「Port」と呼んでいます。マイコンから見ると、ピンの外側は外界です。その外界と繋がる部分は港みたいな感じ、というところでしょうか。
PICマイコンでは、この「Port」は8ビット(=1バイト)構成で、「PortA」「PortB」、、、とアルファベットがついています。
PIC12F1822の場合、外部制御できるピンの数は6本ですので、ピンを制御する「Port」はひとつ(8ビット)あれば十分です。ということで、PIC12F1822はPortは一つしかありません。「PortA」というように「A」をつけて区別する必要もありませんが、慣習的にPortが一つしか必要なくても「PortA」と呼んでいます。
例えばもっと制御できるピンが多いPICマイコンでは、「PortA」「PortB」「PortC」と3つのレジスタがあったりします。この場合、「PortC」のアナログ/デジタル設定は「ANSELC」レジスタで設定します。
前置きが長くなってしまいましたが、これから「ANSELA」と「TRISA」を詳細に説明します。
各ピンのデジタル/アナログ制御の設定を行うには「ANSELA」変数に値を代入します。「ANSELAは1バイト、8ビットの変数でそれぞれのビットは以下のような設定になっています。
アナログかデジタルかの設定は、アナログ/デジタルの設定ができるピンのみ必要ですので、RAポートでいうと、「RA0(7番ピン)」「RA1(6番ピン)」「RA2(5番ピン)」「RA4(3番ピン)」の設定のみになります。「RA5(2番ピン)」「RA3(4番ピン)」はデジタル制御しかできませんので、これらのピンに対する設定はありません。
基礎編では以下のように設定することにします。
結局ゼロを指定することになりますので、ANSELの設定は
ANSELA = 0b00000000;
あるいは
ANSELA = 0x00;
ということになります。
次に各ピンの入出力設定です。先ほど説明しましたが、こちらは「TRISA」レジスタで行います。この変数も1バイト、8ビットでそれぞれのビットは以下のような設定になっています。
各ビット、0を設定すると出力、1を設定すると入力となります。なお第3ビット、つまりRA3の設定は入力のみとなり、このビットは読み出し専用となっています。なお、TRISAレジスタを設定するときにRA3に設定する値は0、1どちらを設定しても1のままです。(0を設定すると大変なことが起きる、とかではないです)
今回はとりあえずRA3ピン以外は出力設定とします。
TRISAの設定は、
TRISA = 0b00001000;
あるいは
TRISA = 0x08;
ということになります。
では、ANSELAとTRISAについてデータシートを確認します。
ANSELAレジスタは英語版データシートのp.118、日本語版のp.127です。以下は英語版のANSELAレジスタの説明です。
(Microchip Technology社 PIC12F1822データシートより引用)
読み方は先ほどのOSCCONレジスタとほぼ同じです。ほとんど説明するところはないですが、最後の「Note1」(注記)に書いてあることを説明します。これは、「アナログ設定にした場合、TRISAレジスタの対応するポートを入力設定にすること」という意味です。
先ほど説明したように、アナログ設定にするとA/Dコンバータの機能のピンになり、ピンの電圧を読み取る、という「入力」の機能になりますので、TRISAレジスタで対応するピンを「入力」に指定する必要がある、ということになります。
TRISAレジスタは英語版データシートのp.117、日本語版のp.126です。以下は英語版のTRISAレジスタの説明です。
(Microchip Technology社 PIC12F1822データシートより引用)
こちらもほとんど説明するところはないと思います。もしわからなところがありましたらコメント欄でご質問いただければと思います。
これでクロック設定とピン機能設定が終わりました。かなり難しかったですよね。次はいよいよピンの制御です。
ピンの初期設定
これまでのコードで一通りPICマイコンの動作設定が完了しましたので、いよいよピンを制御してLEDを点滅制御します。LEDはPICマイコンの2番ピンに接続しました。これからこのピンを0Vにしたり5VにしたりしてLEDを点滅制御するのですが、その方法は簡単です。
このピンはデジタル出力の「RA5」として使用する設定をしましたので、「RA5」という変数に0を代入すると、RA5ピンは0V、「RA5」に1を代入するとRA5ピンは5V出力となります。
このように「RA5」に0や1を代入するとピンのデジタル制御ができるのですが、実は、、、この変数を使用すると問題が発生するケースがあります。この問題についてはかなり込み入った話になりますので、一通り回路を作成した後に、実際に実験をして、その問題を確認してみます。
ここで説明もなく突然出てきて申し訳ないのですが、ピンのデジタル出力制御をする場合は、例えば「RA5」ピンであれば、「LATA5」という変数を使用します。
今回製作した回路で、LEDを点灯したい場合は、
LATA5 = 1;
消灯したい場合は、
LATA5 = 0;
と書きます。
デジタル出力制御ピンは「RA5」ですので、「RA5 = 1;」という書き方もできるのに(実際に動作します)、なんでこの人は「LATA5 = 1;」と書いてください、なんて言ってるんだろう、とかなり悶々としてしまうと思いますが、この「RA5」と「LATA5」については、基礎編の最後の方で詳しく説明します。今は、「デジタル出力制御」する場合は「LATA」を使う、と覚えておくことにします。
参考ですが、あとでブザーをRA4ピン(3番ピン)に接続します。ブザーをONにする場合は、「LATA4 = 1;」、OFFにする場合は「LATA4 = 0;」と書くことになります。
それでは、プログラムを書きましょう。
まず動作処理が始まる前に、初期設定としてLEDを消灯しておきますので、メイン関数の「ピンの初期設定」のところで、
LATA5 = 0;
と記述しておきます。これでRA5ピンは0Vとなり、LEDは消灯状態となります。
動作処理
さて、いよいよ1秒に1回、LEDをピカッ、ピカッと光らせる処理です。実は、どのような時間間隔でLEDを光らせれば「ピカッ、ピカッ」と見えるか検討してみました。結果、950ms(ミリ秒)消灯状態にして、50ms点灯させると、ピカッ、という感じになりました。ということで、以下のようにRA5を制御すればそのように光らせることができます。
ということで、動作処理部分は言葉で書くと、
- 950ms消灯状態にする
- 「LATA5 = 1;」でLEDを点灯させる
- そのまま50ms点灯状態にしておく
- 「LATA5 = 0;」でLEDを消灯させる
- (1)に戻る
ということになります。まず、永久に処理を繰り替える場合、手っ取り早いのがwhile文を使う方法です。
while(1){
処理
}
と書くと、while文の中の処理を永遠繰り返します。ということで、あとは処理の部分に(a)〜(d)を書けばOKです。(b)と(d)については、それぞれLATA5変数の代入でOKです。(a)と(c)はどのように書けばいいのでしょうか。
時間待ちをすればいいので適当な命令を必要回数for文などで繰り返せばいいのですが、C言語の場合これが意外に難しいんです。ある一定の時間待ちをするには、まずC言語がどのPICマイコンの命令にコンパイルされて、その命令にどの程度時間がかかるか調べる必要があります。できないことではありませんが、ちょー面倒です。そんなこともあってか、時間待ちの関数が用意されています。
__delay_ms();
という関数です。「delay」の前にはアンダースコア2個、後には1個つきます。これは、ms(ミリ秒)指定でどのくらい時間待ちをするか、という関数です。例えば950ms待つには、
__delay_ms(950);
とすれば、950ms時間待ちしてくれます。ということで、(a)〜(e)の処理をC言語にまとめると、
while(1){
__delay_ms(950);
LATA5 = 1;
__delay_ms(50);
LATA5 = 0;
}
ということになります。これでプログラムが完成、といきたいところですが、__delay_ms()を使用する場合、この関数はクロック周波数が何Hzに設定されているかの情報を必要とするため教えてあげる必要があります。教えるには、_XTAL_FREQにクロック周波数(単位はHz)を#defineします。今回は1MHZ = 1,000,000Hzにしましたので、
#define _XTAL_FREQ 1000000
と定義してあげればOKです。この#defineはメイン関数の前に置きました。
プログラム完成ですが
ようやく回路とプログラムが完成しました。完成したところで、何ですが、実はこの回路もプログラムも正確な時間は計測できていません。数十分程度のタイマーでしたら実用的に使えると思いますが、1日とか計測するタイマーとしては改良が必要です。
具体的には以下の点で正確に時間が計測できていません。
- 内部クロックの誤差
今回はPICマイコンの内部クロックを使用しましたが、内部クロックはある程度、誤差があります。正確に時間を計測する、つまり正確なクロック信号を得るには、時計用の32.768kHzの水晶発振子を使用したりする必要があります。ただ、タイマー程度でしたら内部クロックでも十分実用になると思います。 - プログラムの誤差
これはほとんど誤差の要因にはなりませんが、上のプログラムは正確には1秒の処理を繰り返すようにはなっていません。while(1){}、LATA5=1, LATA5=0、という処理も時間がかかります。__delay_ms()でぴったり1秒時間待ちしていますので、それにプラスして、while()、LATA5代入の処理分時間がかかっていることになります。ただ、これらの処理にかかる時間は非常に短いのでタイマー程度でしたらこのような実装で問題ありません。
なお、完成したプログラムは一度「第17回 ブレッドボードの回路を動作させてみる」で書き込み、動作させる方法を説明していますので、まだ動作させていない方はこの記事を参考にして動作させてみてください。
次回、PICマイコンに書き込んで動作させる、というテーマにしていますが、書き込む方法について、PICKitから電源供給する方法と、回路の電源を使う方法の説明をしたいと思います。
更新履歴
日付 | 内容 |
---|---|
日付 | 内容 |
2016.10.15 | 新規投稿 |
2018.11.24 | プログラムテンプレートをMPLABX IDE v5.10版に更新 |
2019.6.20 | 誤記訂正(OSCCONのクロックソースのtimer1使用設定の誤記訂正) |
初めまして。
大変わかりやすい解説で参考にさせていただいています。
今回初めてのPICマイコンでLチカを試しているのですが、どうもうまく動きません。
行き詰ってしまったため、お助けいただけると幸いです。
使用しているマイコンは「PIC18F14K22」で
MPLAB X IDEのMCCで初期化コードを生成しています。
アルゴリズムは下記のようにしています。
・タイマ0を使って1msecの周期割込みを発生
・割込み処理内で1msecカウンタgMSecTimeをインクリメント
・mainループで1msecカウンタgMSecTimeを監視
・1000msec経過したら、LED(IOポートRA2)をトグル
発生している事象
・1000msec毎のトグルが、ときどき800msecくらいになってしまう。
—以下、ソースコード—————
uint16_t gMSecTime;
// タイマー割込み関数
void Timer0IntFunc(void)
{
gMSecTime++;
}
/*
Main application
*/
void main(void)
{
uint16_t t_led_tgl = 0; // time scale for LED
uint16_t diff;
// Initialize the device
SYSTEM_Initialize();
TMR0_SetInterruptHandler(Timer0IntFunc); // タイマー0割込み関数の登録
// Enable the Global Interrupts
INTERRUPT_GlobalInterruptEnable();
// Enable the Peripheral Interrupts
INTERRUPT_PeripheralInterruptEnable();
t_led_tgl = gMSecTime;
while (1)
{
diff = gMSecTime – t_led_tgl; // 経過時間計算
if( diff >= 1000 ){ // 1000msec経過?
// ログ出力
sprintf(line_buf, “%u,%u,%u\r\n”, gMSecTime, t_led_tgl, diff );
TxString(line_buf,strlen(line_buf));
IO_RA2_Toggle(); // LEDトグル
t_led_tgl = gMSecTime; // 時間再計測
}
}
}
—以下、ログ出力—————————–
11195,10195,1000
12216,11216,1000
13056,12237,1074 ★この時、時間が短くなる。
14077,13077,1000
15098,14098,1000
16119,15119,1000
★の箇所で
gMSecTime:13056
t_led_tgl:12237
なので、
diff:819
となり、ifの条件に引っかからないはずなのですが
diff:1074
となっておりifの条件に引っかかってトグル処理を実行しています。
引き算の結果が時々おかしくなる!?
気になっているのは、
PIC18Fは8bitマイコンですがgMSecTimeが16bitなので
何か弊害があるのでは・・・
と思っています。
長々と申し訳ありません。
先人たちの考えをお聞きしたいので
よろしくお願い致します。
はじめまして!
ご質問どうもありがとうございます。
原因の特定が難しい現象ですね。
ロジックは問題ないですし、ログを見ると問題の場所以外はプログラム通りに動作していますね。
プログラム上は問題ないと思います。
とはいっても問題のところは、13056-11216=816なのにdiffは1074になっている、という謎現状が起きていますので、実行コードで何か起きているのかもしれません。
きちんとした原因がわかっていない中、手探りの状況で申し訳ないのですが、各変数にvolatileをつけてご確認いただければと思います。コードの変更ですが、現在のプログラムで、各変数宣言のところを次のように変更してみていただければと思います。
グローバル変数
volatile uint16_t gMSecTime;
main関数のローカル変数
volatile uint16_t t_led_tgl = 0; // time scale for LED
volatile uint16_t diff;
すぐに原因判明&解決、とならず申し訳ございません。
お手数ですが、まずはどうなるかご確認いただければと思います。
返信ありがとうございます。
ログまで見ていただいて嬉しい限りです。
結果ですがvolatileを付けていても症状は変わりませんでした。
不躾ですがteratailにも同じ質問をしてみました。
https://teratail.com/questions/p4m6rtstnmgcuv
解決策ですが、下記の処理を割込み禁止にすることで正常動作しました。
diff = gMSecTime – t_led_tgl; // 経過時間計算
簡単にいうとgMSecTimeは16bit変数ですがマイコンは8bitなので
下位バイトと上位バイトを読み込む間に割込みが入ると計算が異常になる
という事象が発生していたと思われます。
これまで組込みで32bitCPUしか扱った経験が有りませんでしたので
8bitや16bitCPUはこんなケアもしなきゃならないのか・・・
と少し戸惑いました。
たかがLチカでも侮れないと痛感・・・
これからも参考にさせていただきます。
ありがとうございました。
ご連絡どうもありがとうございます!
今回はお力になれず申し訳ございませんでした。
また、別サイトでの質問状況も教えていただいて本当にどうもありがとうございます。解決された、とのことでよかったです!
確かに、16ビットデータを8ビットで処理すると、その間で割り込みが発生するというのは十分あり得ますよね。
私もとても勉強になりました。
また、このようなコメントをいただくことによって、弱小サイトながら参考になる方もいらっしゃるかもしれませんので、情報をいただきまして感謝いたします。
マイコンの世界はかなりハード色が強く、扱いが難しいところもありますが、このような知識をつけていくのもなんだか楽しいですよね。
どうもありがとうございました。
1~5ではなくてa~dではないでしょうか?
ご指摘どうもありがとうございます。
申し訳ございません、HTML記述の関係からこのようになってしまっています。適宜読み替えていただけると幸いです。
内部クロックOSCCONの設定で「10」の「Timer1を使用」の表記がありますが、「10」とは1ビット目と0ビット目と言う意味で良いでしょうか、前の説明は設定値なので「01」では?と勘違いしそうな・・・
でも、とっても素人に優しく説明して頂き非常に感謝しています。
これからも是非サポート宜しくお願いします。
ご指摘どうもありがとうございました!
すみません、「timer1を使用」は「01」(第1ビットは0、第0ビットは1)になります。本文訂正しておきました。自分でも記事見直しをしているのですが、気づきませんでした。どうもありがとうございました。
このシリーズは、自分がPICマイコンを手がけた時にいろいろと苦労したので、過去の自分に対して書いている感じです。なるべく分かりやすく書いているつもりですが、分かりづらいところは多々あると思います。そのような時はまたご質問いただければと思います。
>>今回は1MHZ = 10000000Hzにしましたので、
この部分、桁が1つ多いのではないでしょうか。
musashiさま、
ご指摘どうもありがとうございます!
すみません、間違っていました。カンマで区切って修正しました。
自分では見直しても気づかないと思いますので、ご指摘いただいて助かります。どうもありがとうございました。