ここでは Arduino ボードを使ったプログラミング練習キット「Trainer」を使ってプログラミングについて教える.手元に一台用意しておいて欲しい
一応一人でもできるように書いているつもりだが,分からないところも出てくると思うので,Issue に「〇〇がわからない」など残しておいてくれるとありがたい
もちろんそれらについて自分で調べてみたり,それでもわからければ先輩等に聞くのも全然問題ない
Trainer には,入力として 2 つの押しボタン式スイッチが,出力として 2 つの LED と 1 つの 7 セグメント LED が用意されている.それらをマイコンを積んだボード「Arduino Nano」を使って制御するものである
開発は以下のリポジトリで行っている.サンプルコードもここにおいてあるのでダウンロードし,D:/Arduino/
に展開しておこう
https://github.com/I-Sys-UF/Trainer
サンプルコードはこちら
https://github.com/I-Sys-UF/Trainer/releases
これ以降の作業を進めるにあたって,自分がどこまで進めたかを記録するのも兼ねて Issue というものを立ててみよう.タブの左から 2 番目に Issues というのがある.「Trainer-A Getting Started」というテンプレートを用意してあるので,Submit new Issue から新しい Issue を発行しよう.その際,わかりやすい名前を設定してほしい
作業が終わり次第,終わった項目にチェックを入れていくと,自分がどこまでやったのか,残りはどれだけ残っているのかが可視化できる
基本的には Issue 通り進めていけばいいが,一部コツがいるところがあるので,それについては適宜解説する
部品を実装する際は,基本的に背の低い部品から実装していく.今回の場合は抵抗である
抵抗は基本的に特に考えることはなく,ただ浮かないようにはんだ付けすればよい.向きもないので,気にしないのであれば適当でよい
もちろん揃えると見た目はいいので,揃えてもいい
終わったら Issue にチェックを入れていこう.以下同じ
次に背が低いのは IC ソケットである
逆になっても問題はないのだが,IC ソケットには一応向きがある.よく見ると一方の妻手にくぼみがあるだろう.それを基板上のシルク印刷と合わせて欲しい
はんだ付けの際,少々やり方に気をつけないと面倒なことになるのでここは注意.具体的には以下の通りにやると良い
こうすることで,ソケットが浮くのを防ぐことができる
ちなみに,ソケットを挟む理由だが,IC がはんだ付けの際の熱で壊れるのを防ぐためというのが大きな理由である
スイッチは特に考えることはない.基板にはめ込むと割とガッチリ保持されるので,普通にはんだ付けすればよい
基板上の D1 と D2 をはんだ付けする
LED には向きがあるので,注意しよう
片方だけはんだ付けして傾かないようにし,問題なければ残った方もはんだ付けするようにするとうまく行く
42 ピンの分割ロングピンソケットを,必要な長さに合わせて割って使用する.5,5,15,15 に分割させよう.2 ピン分残るが,捨ててしまって良い
ピンソケットも IC ソケットと同様,両端 2 ピンだけ先にはんだ付けし,浮き.傾きがないことを確認して残りもはんだ付けするときれいにはんだ付けできる
Arduino Nano を用意し,開封すると,中から本体とピンヘッダ 3 個が出てくる.このうち,1 列のものを使用する
2 x 3 のものは使わないので捨ててしまって良い
これも IC ソケット,ピンソケットと同様のやり方ではんだ付けをする.向きは,書かなくても分かる,よね?
Arduino Nano,7 セグメント LED,IC 2 つをそれぞれ向きに気を付けてはめ込む.基本的に上から押し込むだけ
Arduino Nano は USB コネクタが上になるように,7 セグメント LED は正立するように,IC はシルク印刷の通りにはめ込もう
ここからはダウンロードしたサンプルコードを使用して進めよう.Releases からダウンロードしたサンプルコードを使って欲しい
まずはデモプログラムを走らせてみよう.これは,問題なく組み立てができてちゃんと動くかを確認する目的もある
ただ,Arduino IDE をインストールした直後はコンパイルが通らないと思うので,ある手順を踏む必要がある.それは以下の通り
[File] -> [Preferences] を開き,設定を変更する
結論は以下の通りとして欲しい
変更点は以下の通り
d:\Arduino
に日本語
に「追加のボードマネージャのURL」は気にしなくて良いが,以下のリンクを追記しておくと後々便利
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
ツール
-> ボード
-> ボードマネージャ
と辿るか,[Ctrl] + [Shift] + [B] でボードマネージャを開くArduino AVR Boards
をインストールする
demo では MsTimer2
というライブラリを使用している.これをインストールしないとコンパイルができないので以下の手順でやってほしい
Arduino Nano
となっていることを確認するツール
タブの中の Processor
が ATmega328P (Old Bootloader)
になっていることを確認する書き込みをする際は,IDE 左上の,右向き矢印が書かれた丸いボタンを押すと書き込みができる.その左隣のチェックマークはコンパイルだけを実行したいときに使うものとなっている
このコードは,2 箇所に分かれたマクロ定義の文と,マイコンが起動したときに最初に実行される setup
関数,setup
関数が実行されたあとに無限に実行され続ける loop
関数で構成されている
ただし,loop
には何も書かれていないため,実質的に setup
関数が一度呼び出され,実行されるのみである
ここでは #define
というマクロを使用し,定数を設定している
#define
は,#define A B
のように記述して使用する
こうすると,プログラムがコンパイルされる際,プリプロセッサと呼ばれるプログラムにより,コード内の A
という文字列(または文字)を,B
という文字列(文字)に置き換えてくれる
何かしらの処理(コード)をまとめたものを関数と呼ぶ
Arduino で用意されている setup
や loop
も関数であり,名前の後ろに丸カッコ ()
が付く.引数(ひきすう)を渡して関数を呼ぶ場合,このカッコ内に値を書いて呼び出すことになる
Arduino では,便利な関数やよく使う機能をまとめた関数が様々用意されており,プログラミングが始めやすくなっている.もちろん,自分で必要な処理をまとめた関数を作ることもできる
変数とは,プログラムの中で使用する「値」つまるところデータを保存したり使ったりするためのもので,さまざまな「型」が用意されている.一例を以下に挙げる
int
char
float
int
型は整数を保存することができる
char
型は文字または -128
~ 127
の整数を保存することができる.文字はそれに対応する数字として扱われる
float
型は小数を保存することができる
他にも様々な型があり,場合によって使い分けることが望ましい
変数には,必要に応じて修飾子を付けることができる.修飾子には以下のものがある
static
unsigned
volatile
const
それぞれで役割が異なるが,そこについてはまだ触れることはないので興味がある方は各自調べて欲しい
グローバル変数とローカル変数について簡単に触れておく
グローバル変数は名の通り Global に使用することができる.つまり,プログラム内のどこからでも参照でき,どこからでも変更することができる(例外あり)
対して,ある一部分の範囲でしか利用できない変数をローカル変数と呼ぶ.関数内部で新たに定義したものはローカル変数になり,その関数の中でしか使用することはできない
呼び出せる範囲が被っていないローカル変数は,それぞれ同じ名前を使用することができる.以下の i
はそれぞれ異なるローカル変数である
for(int i = 0; i < 16; i++) {
}
for(int i = 0; i < 24; i++) {
}
ここで,関数に 2 つの数字を渡し,その和を計算させる関数を考える
その際に関数に渡す数字が,引数(実引数)である
この場合の関数の書き方は以下のとおり.なお,「型」についてはいずれも int
型で,オーバーフローは無視する
int sum(int a, int b) {
int number;
number = a + b;
return number;
}
一番前の int
が返り値(関数が返す値)の型になる.sum
がこの関数の名前で,丸カッコ ()
の中の int a
と int b
が仮引数と呼ばれるものであり,この関数内で自由に使える変数として振る舞う
関数の中を見ると,3 つの文で構成されていることがわかる.それらの解説については以下の通り
int number;
ここで,この関数の中で使用するローカル変数として,int
型の number
という名前の変数を定義している
この変数はローカル変数なので,この関数の中でしか使うことができない
number = a + b;
ここで,この関数の中で最も重要な部分である,計算をしている.やってることはただの足し算だが,立派な計算である
先ほど定義した number
という関数に,仮引数 a
と b
の和を代入している
return number;
ここで,関数の内部で計算した値を返している.この return
という命令が実行されると,その関数の処理は終わる
この関数を使用するときは,一般に,以下のようなコードを書くことになる
int y = sum(3, 5);
ここで,3
と 5
を実引数として直接指定しているが,呼ばれた関数から見ると,a
に 3
が,b
に 5
が代入されているように見える
また,この場合,この関数は 8
という値を返すので,8
と同じように振る舞う
プログラム内で説明を残しておきたいときや,一時的に特定のコードを無効にしたいときに,コメントアウトという処理をする.それには二通りのやり方がある
※これは私がこう呼んでいるだけであり,この名称が正しいかは分からない
以下のような書き方をし,スラッシュ 2 つ //
以降のすべての文字列をコンパイラに無視させることができる
// コメント
以下のような書き方をし,/*
と */
に挟まれた部分すべてがコメントとして扱われる
/* コメント */
/*
こんな書き方でも
問題ない
複数行にわたってコメントアウトできる
*/
Train-1
と比べると格段にコードの量が増えた.ある程度実用的なコードを書こうとすると,どうしても長くならざるを得ない
それはさておき,このコードは,マクロ定義,setup
関数,loop
関数から成るが,loop
関数にもコードが書かれている.つまり,起動したあとに setup
が実行されたあと,電源が切れるまで無限に loop
文が呼ばれ続けるということになるという点に注意して欲しい
さて,ここで使用している関数の説明をこれからしていく.わからないことがあれば先輩などに聞いてみてほしい
setup
関数では,以下の 2 つの関数が使用されている.いづれも Arduino で用意されている関数である
pinMode
digitalWrite
また,以下の 2 つの制御が使用されている
この関数は,第一引数に設定したいピンの番号を,第二引数に設定したい状態を指定して使用する.ピンの番号はほとんどの場合ボード上にかかれている番号そのままである
状態は以下の 3 通り指定することができる
INPUT
INPUT_PULLUP
OUTPUT
INPUT
OUTPUT
については文字通り入力・出力で,INPUT_PULLUP
は,指定したピンを入力にしつつ,内蔵のプルアップ抵抗を有効にするもので,スイッチなどを接続する際に使用する.これを使うことで外付けのプルアップ抵抗を省くことができる
この関数は,第一引数に操作したいピンの番号を,第二引数に設定したい状態を指定して使用する.ピンの番号については先述の通りで,状態には以下の 2 通りある
なお,この関数を使用する前に,pinMode
関数で出力に設定しておく必要がある
HIGH
LOW
HIGH
はボードにより変わるが概ね 5V ないし 3.3V を,LOW
は 0V を出力させることを意味する
関数と似たような見た目をしているが,関数ではない.言うなれば制御文である
if
文は,条件分岐に使う制御文で,カッコの中には条件式が入る.条件式には,以下のようなものがある(登場する変数 a
b
は int
型で定義されているものとする)
(a < b)
(a == b)
(a != b)
これら条件式は評価された後,bool
型を返す.そのため,以下のような書き方も(意味はないが)問題ない
if(true) {
// こっちが実行される
}else {
// こっちは実行されない
}
if(false) {
// ここは実行されない
}else if(true) {
// ここが実行される
}else {
// ここも実行されない
}
なお,ここでは複文(波カッコ {}
により囲まれた文の集合)で記述しているが,条件分岐したあとに実行する処理が一行で済む場合は波カッコを省略しても構わない.が,あまり推奨はしない
if(true)
function();
if
文は条件分岐に使ったが,while
文は繰り返し処理に使う.丸カッコの条件式の評価結果が true
の間ずっと中の処理を繰り返す
例えば,以下のように書けば永遠に中の処理が繰り返される
while(true) {
loop();
}
どこかで見覚えがないだろうか.そう.Arduino の loop
関数は,中でこのように実行されているのである
また,意図的にコードを特定のところでストップさせたいときに,以下のようにすることがある.Train-2
でも使われている手法である
while(true);
loop
関数では,同じ型の 2 つの変数を用意し,それぞれ 2 つのスイッチの状態を保存するのに使用している
新たに digitalRead
関数が登場しているので紹介しておく
引数は一つだけで,そこで指定したピンの状態をデジタルで読み取り,その結果を返す.返り値は HIGH
か LOW
である
その結果をそれぞれ対応する変数に保存し,その後の条件分岐で使用している
f
の中身を書き換えてみるTrain-3
ではオリジナルの関数を実装するということをやってみる
今回実装する関数は,数式 f(x) = 4x^2 + 6x - 10
を解く関数である
関数の書き方は Train-1
の説明でも触れている
結論をいうと,今回実装する関数は以下の通り.コード本文に書いてあるものそのものである
float f(float x) {
float y;
y = 4 * x * x + 6 * x - 10;
return y;
}
このように書いてもいい
float f(float x) {
return 4 * x * x + 6 * x - 10;
}
なお,一般的に C 言語で本コードのような書き方をするとエラーがでる.C 言語では,変数であれ関数であれ,呼び出されるよりも前の段階で定義されている必要があるからである.そのため,関数をコールする箇所よりも前で実装してしまうか,プロトタイプ宣言というものを使用する.ただし,Arduino はその限りでなく,勝手にプロトタイプ宣言を作ってくれるので,あまり気にしなくてもいい
本題に戻ると,こうして実装された関数を setup
関数内で呼び出して実行するわけだが,このように書かれている
float x = 2.5;
float y = f(x);
まず,float
型の変数 x
を 2.5
という値で初期化している.これを,関数 f
に渡しながらコールしている
そして,計算された値が帰ってきて,変数 y
に代入される.という流れである
その結果を,シリアルモニタに表示するように文字列を送信する関数である Serial.print
を用いて PC に送っている
シリアルモニタは,IDE の画面右上の虫メガネのようなアイコンをクリックするか,[Ctrl] + [Shift] + [M] で開くことができる
文字化けする場合は,シリアルモニタ右側にある「〇〇baud」の数字を 115200
に変更しよう
本コード内の Serial.begin
関数に渡している数字と同じであればいいので,コードの方を変えても構わない
ここで,float x = 2.5;
の行で初期化される値を色々変えて実行してみて欲しい
計算された値も当然変わるはずで,その変化を見て欲しい
uint8_t data = 0b11111111;
のところを自由に変えて遊んでみる
0
か 1
かつ合計 8 桁までじゃないとコンパイルエラーになるNUM0
~ NUM9
に当てはまる数字を考えてみるここでは基板上の大部分を占めている,7 セグメント LED を操作するということをやる
操作するのに使用する関数を作れというのは流石に酷なので,用意したものを使ってもらう.LED_OUT
という関数がそれ.ここに 8bit の数字を渡すと,その数字をシフトレジスタに送信し,LED を駆動するトランジスタアレイをオンオフさせる.なので,7 セグメント LED を駆動できる形式のデータが必要になる.つまり,単純に LED_OUT(7)
とすれば 7
が表示されるわけではない.これは実際にやってみるとわかると思う
そこで,まずはそのまま実行し,すべての LED が光ることを確認して欲しい.その後,8 桁のうちどれか一桁だけを 1 にして(ビットを立てて,と表現します)実行する.というのを,場所を変えて 8 回やってみよう.そうすると,どのビットがどのセグメントに対応するかがわかるはず
それがわかったら,0
~ 9
の数字を表現するにはどこを光らせるといいか,そのためにはどのようなデータを送ればいいかを考え,用意された変数に保存してみよう.そうすれば,その変数を使うことで,LED_OUT(NUM7)
とすれば 7
と表示されるということができるようになる
次の Train-5
で配列を学習し,その次 Train-6
でその配列を使って使いやすくするということを行う
Train-6
に手を付けるまでに NUM0
~ NUM9
に当てはまる数字を探して欲しい
この数字は,ある意味 7 セグメント LED のフォントデータとなる
頭についている 0b
は二進数であることを示すもので,十六進数の場合は 0x
を,八進数は 0
を付ける
何も付けなければ十進数のままである
INDEX
の値を任意に変えて実行してみるここでは配列を取り扱う.配列とは,同じ型の値を,メモリの許す限り並べてひとあつめにしたもので,任意の型に適応できる.やり方は簡単.変数名の後ろに角カッコ []
を付けるだけ.この角カッコは任意数付けることができ,1 つの場合を一次元配列,2 つの場合を二次元配列...と呼ぶ
一般には二次元配列くらいまでは使うが,三次元以上を使うのは稀であろう
本プログラムでは,uint8_t
型の配列 array
を,値とともに初期化している.本来,角カッコの中には配列の要素の数(要素数)を記述する必要があるが,値とともに初期化する場合はコンパイラがその要素数を数えて補完してくれる.なお,要素数以上の数であれば併記しても差し支えない
以下のような宣言はすべて有効である
uint8_t array_1[10];
uint8_t array_2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint8_t array_3[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint8_t array_4[20] = {0, 1, 2, 3};
char
型の配列のみ別名があり,文字の配列ということで文字列と呼ぶ.以下のような宣言方法がある.なお,文字列は終わりを表すためにヌル文字 \0
を挿入する必要があるため,配列のサイズは要素数 + 1 となる
初期化時にヌル文字を挿入する必要は特になく,コンパイラが補完してくれる.もちろん明示的に記述して宣言してもいい
char string_1[] = "Arduino";
char string_2[10] = "Arduino";
char string_3[10] = {'A', 'r', 'd', 'u', 'i', 'n', 'o'};
char string_4[10];
ただし,このようなことはできない
char string[10];
string = "Arduino";
このようなことをしたい場合は,sprintf
関数が便利
char string[10];
sprintf(string, "%s", "Arduino");
注意点として,「文字」はシングルクォーテーション '
で囲み,「文字列」はダブルクォーテーション "
で囲む必要がある.一文字しかなかったとしてもダブルクォーテーション "
で囲めばそれは文字列であり,末尾にヌル文字 \0
が付加される
ここに,一つの配列を宣言する.
uint8_t array_1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
これは本コードでも使用されている配列である
配列として宣言された値は,インデックスと呼ばれる値を使って呼び出すことができる.インデックスとなりうる値は 0
~ 要素数 - 1
の範囲で,0
スタートなので最大値は 要素数 - 1
になる.なお,この範囲を超えた値をインデックスに指定してしまうと,メモリ上に展開されているデータの,配列に関係ないところを読み出してしまい,バグの原因となるので注意.C 言語ではその辺のケアはしてくれない
以下の条件式は true
である
(array_1[5] == 5)
この配列 array
は,インデックスと,インデックスに対応する要素の値が一致するように作られているので本質的には意味がないが,インデックスとの対応がわかりやすいものである
ここで問題.以下の配列を宣言する
int array_2[10] = {83, 287, 65, 492, 836, 567, 0, 445, 31, 768};
問①:以下の値は何になるか
array_2[3]
問②:array_2[x]
の値は 445
であった.x
に当てはまる数字は何か
このプログラムはただ実行するだけでいい
ここで,Train-4
と Train-5
の答え合わせをしていく.
結論を言うと以下の通り.必ずしも全ビットが一致している必要はなく,光らせた結果意図した文字に見えるように光ればそれで正解
uint8_t NUM0 = 0b11111100;
uint8_t NUM1 = 0b01100000;
uint8_t NUM2 = 0b11011010;
uint8_t NUM3 = 0b11110010;
uint8_t NUM4 = 0b01100110;
uint8_t NUM5 = 0b10110110;
uint8_t NUM6 = 0b10111110;
uint8_t NUM7 = 0b11100100;
uint8_t NUM8 = 0b11111110;
uint8_t NUM9 = 0b11110110;
int array_2[10] = {83, 287, 65, 492, 836, 567, 0, 445, 31, 768};
問①
array_2[3]
この配列のインデックスは 3
ということで,左から 4 つ目の値が選択される.よって,492
が正解
問②
445
が出てくるのは 8 番目なのでインデックスの値は 1 を引いて 7
が正解
配列のインデックスは 0
スタートであることに注意しよう
本コードでは,uint8_t
型で num_to_bin
という配列を作り,値とともに初期化している
なお,16 進数に対応できるよう,ついでに A
~ F
も作っている
setup
関数内では,2 つの初期化用関数 GPIO_init
と LED_init
がコールされている.これらはその下で定義されているもので,元々 setup
関数内で直接実行されていたもののラッパーである.初期化処理は関数化しておくとどこで何のための処理をしているのかわかりやすくなる
loop
関数内で実行されている for
文について,おそらく初出なので詳しめに解説しておく
for
文は,while
文と同じように,繰り返し処理をするための制御文だが,「決まった回数繰り返す」ということに向いている.一方の while
は「特定の条件を満たしている間」というように,やや性質がことなる(が,工夫すれば全く同じことができる)
while
文は,丸カッコ ()
の中に入るのは条件式一つだけだったが,for
文はセミコロン ;
区切りで 3 つ記述する必要がある.順に説明していく
なお,便宜上,第一・第二・第三記述欄と呼称する
for
文では,カウンタ変数(ほとんどの場合 i
)を用いて繰り返し処理を行う.その変数 i
の初期化を行うのが第一記述欄である.ANSI C 系の場合は途中で変数を新たに宣言することができないが,Arduino は C++ ベースなので第一記述欄で直接変数を宣言できるので,size_t i = 0
のように記述することが多い.なお,型は size_t
が最適とされる.size_t
はアーキテクチャによってビット数が変わる整数型である
ここで何回繰り返すかを決める.条件式を用いて回数を指定し,i < N
(N
は回数)とすると N 回繰り返すことになる.条件式を見ればわかるが,i < N
なので i
は N - 1
まで増加する.ここで,i
は 0
から始まるので,結果として N 回のループとなるのである
ところが本コードでは,見慣れない書き方をしている.sizeof
とはなんぞや
話は少し脇にそれるが,for
文と配列はとても相性が良いことが知られている.なぜなら,配列を用意して,その配列のサイズだけ繰り返すという処理をすれば,その配列全体について所望の処理を行うことができるからである
たとえば,配列の要素すべてを二倍する・総和や平均を求めるなど.そんな単純な処理をするシーンがあるとは思えないが,まぁ一例として
話をもとに戻すと,sizeof
は C 言語に用意されている演算子で,引数として渡した変数(配列を含む)の大きさが何バイトかを返してくれる便利なものである.配列 num_to_bin
の型は uint8_t
つまり 8bit 幅の非負整数であり,要素数がそのままバイト数になるので,配列をそのまま sizeof
に渡すだけで要素数を返してくれる.なんと便利なことか
なお,一般に配列 array
の要素数を sizeof
で調べたい場合,以下のようにするのが良い
size_t size = sizeof(array) / sizeof(array[0]);
今回の場合,配列 num_to_bin
は 16 個の要素を持っており,かつ uint8_t
型なので,返ってくる値は 16
となる.つまり,この for
文は 16 回繰り返されるということになる
ここはカウンタ変数をどう扱うかを記述する.ほとんどの場合 i++
や i += 1
のように単純に 1 インクリメント(加算)させるだけだが,稀にデクリメント(減算)させたり,偶数だけ扱いたいときのようなシーンでは i += 2
とすることもある.ここはケースバイケースだが,単純に決まった回数繰り返したいのであれば i++
とおぼえておけばよい
このようにして for
文を使うわけだが,本コードではどのような挙動をするか,考えてみよう
先述の通り,sizeof(num_to_bin)
は 16
を返すので,16 回繰り返されることになる.その際,繰り返されるごとに i
の値は 1 づつ増えていく
このことを頭に入れておけば,その下で何が起きるか,ある程度想像できるだろう
まず最初のループでは, i
は 0
なので,num_to_bin[i]
は 0b11111100
を返す.これを直接 LED_OUT()
に渡すことで,0 に見える文字が光る
次のループでは,i
はインクリメント演算子により 1
となるのでnum_to_bin[i]
は 0b01100000
を返す.これを LED_OUT
に渡すことで,今度は 1 に見える文字が光る
あとはこれの繰り返しである.このように,配列と for
文はかなり相性がいいので覚えておくと便利
その下の delay(500)
は 500 ミリ秒待つという処理である.これにより,大体 2Hz で数字が更新される.精度は必要でないので delay
関数で十分
このプログラムはただ実行するだけでいい
一気にコードの量が増えたので面食らうと思うが,頑張ってついてきて欲しい
マクロ定義と配列の初期化についてはもうわかっていると思うので説明は省略する.そろそろ説明を読むのも疲れてきた頃合いであろう.書くのも疲れてきた
setup
関数では,LED に接続されている GPIO の初期化と,シフトレジスタ 74HC595 に接続されている GPIO の初期化を行っている.関数化はせず,そのまま書いてある形である
loop
関数では,static uint8_t
と uint8_t
型でそれぞれ 2 つの変数を,static int8_t
型で 1 つの変数を宣言している.uint8_t
, int8_t
は良いとして,見慣れない static
という修飾子がついているが,これを付けることにより,関数が実行されたあとも変数と値が保持され,再度同じ関数がコールされたときに以前コールされたときのデータを引き続き使うことができるようになる
これを付けずに実行すると,関数が実行され終わったあとに変数が破棄されてしまう.詳しくは各自調べて欲しい
ここで用意された変数は,今の段階でのスイッチの状態と,一つ前の段階でのスイッチの状態を,それぞれ保持するために使われている.ビット演算を使えば一つにまとめることができるが,可読性が落ちるので採用していない
SW1 を例に挙げると,SW1_Status_Old
という変数に一つ前の段階でのスイッチの状態が,SW1_Status_New
という変数に現時点でのスイッチの状態が保存される.これにより,一つ前と今で比較することができ,「押された瞬間」を検知することができるようになる
もちろん,「離された瞬間」も検知できる
その下の if
文のところで,押された瞬間を検知し,LED を光らせつつ,カウンタ変数 counter
を操作している.その下はオーバーフロー対策のコードである
そして 7 セグメント LED を光らせ,少し待つ.このような流れで動いている
waitTime
と TimerInt
の二箇所マクロ定義と配列まではこれまでの通りだが,新たに #include
というものが出てきている
これは,ライブラリという,便利な関数たちをまとめた辞書のようなものを,文字通りインクルード,コードに含ませることをしてくれる機能である.ここでは,MsTimer2
というライブラリを読み込んでいる
新たな修飾子が付いた変数も用意されている.これは,タイマー割り込みなどにより,予期せぬ方法で値が変更される可能性があるということをコンパイラに教えるために使用する
setup
関数では,諸々の初期化を行っている.それぞれ何をやっているのかはそれぞれの関数の実装を眺めてみて欲しい
loop
関数では,LED_VRAM
という変数に LED を光らせるためのデータを配列から呼び出し,代入するという作業を行っている
普通はこれだけだとただ値が代入されるだけだが,このプログラムでは「タイマー割り込み」により一定時間ごとにある関数が実行されている.その関数が LED_Flash()
関数である
この関数の中で,LED_VRAM
から値を取り出し,シフトレジスタに送信するということをやっている.これにより,データの送信は勝手にやってくれるので,ただ変数を更新するだけで,表示を変更することができるようになっている.便利
ここまで学習を進めれば,C 言語によるプログラミングについて,多少は理解できたのではないだろうか
理解出来なかったところ,わからなかったところがあるなら,調べるなり聞くなりして欲しい
ある程度理解できたら次のステップ.自分でプログラムを書くということをやってみよう
以下に例を置いておくので,やってみたいのを選ぶなり,自分で考えてみるなりして取り組んで欲しい
その際,やったことは Issue に書き残しておくとよい
rand
を使ってサイコロを作る
オリジナルのプログラムを作るときは,GitHub に新しいリポジトリを立ててそこで管理をしてみよう.他の人に試してもらってデバッグするなんてこともできるようになる