前の記事で若干無理やりながらも、gcc の導入に完了したので、続いてC言語を用いたプログラムをしていきたいと思います。
前回導入したヘッダを用いながら、用いるペリフェラルは、PM と GPIO のみです。便宜上、説明にはそれ以外のペリフェラルを用いることはご了承ください。
Atmel公式のレジスタマクロの記法
相変わらず癖があります。さすが Atmel(?)。
まずペリフェラル名からレジスタに分岐していくと思うのですが、このマイコンもそうです。しかし、接頭辞 AVR32_
が付くのが特徴です。
例えば GPIO を用いたい時は、AVR32_GPIO
となります。LPC マイコンのように、無駄な AVR32_
が付いてしまうのです。個人的には、STM32 の HAL_
のような、こういうつまらない接頭辞をつけるのなら、いっそ C++ のみの対応としてしまえ!と思います。名前空間(namespace
)なら libstdc++ 要りませんからね。
そういう非現実的な嘆きはさておき、実際の例を示しながら記法を学んでいきましょう。
「これで終わり」は小文字
STM32 等、いわゆる普通の ARM マイコンを使ってきた人には慣れ親しんでいるであろう記法です(大文字小文字の違いはさておき)。すなわち、このレジスタに直接アクセスしたい!というものには小文字で記述します。例えば TC
(16bit タイマ) のチャネル0であるところの SR
にアクセスしたい時は、以下のように記述します。
AVR32_TC.channel[0].sr;
channel
の部分はさておき、sr
が小文字であることはお気づきでしょうか。つまりレジスタをそのまま 32bit 値としてアクセスする場合は小文字で記述します。
ビットアクセスはレジスタ名が大文字、ビット名が小文字
しかしながら、ペリフェラルの設定でレジスタに直接アクセスするのはビットを立てる等が面倒と思う方が居るでしょう。そこでこの記法です。H8 や SH 、RX のように、ルネサス製のマイコンをいじったことがある人は .BIT.HOGE
というようにビットごとのアクセスをしたことがあるでしょう。その方法です。
同様に TC(16bit カウンタ)のチャネル0にある SR レジスタの CPCS ビットを例に見てみましょう。小見出しにあるように、ビット名を小文字にします。しかし、このアクセスの時はレジスタ名を大文字にします。
AVR32_TC.channel[0].SR.cpcs;
これでシフト等しなくてもビットの値そのものを得ることができるのです。
ポート・チャネルはそのまま小文字の英単語が現れる
各ペリフェラルには内部構造として、それぞれのチャネルやポート番号があるはずです。その際は、チャネルやポート番号に関連する部分はとにかく小文字で、配列として記すという法則性があります。なので先程の TC のチャネル0はそういうふうに記述しているわけです。他にも、GPIO のポート0にある OVR
には
AVR32_GPIO.port[0].ovr;
と記すわけです。port[0]
以降の記法に関しては上をご参照ください。
これまでのアセンブリ記述をC言語で書いてみる
以上の法則性を利用して、単純なLチカプログラムをC言語で書いてみます。なお、C言語にしたことによる無駄ループウェイトの低速化を考慮して、ループ終了条件を適当に調節しています。
#include <avr32/io.h>
void clock_osc0_start(void) {
AVR32_PM.OSCCTRL0.startup = AVR32_PM_OSCCTRL0_STARTUP_64_RCOSC;
AVR32_PM.OSCCTRL0.mode = AVR32_PM_OSCCTRL0_MODE_CRYSTAL_G3;
AVR32_PM.MCCTRL.osc0en = 1;
while( !AVR32_PM.POSCSR.osc0rdy ); // wait for OSC0 to start
}
void clock_pll_start(void) {
// (OSC0)12MHz * 10 / 1 / 2 == 60MHz
// set MUL & DIV & PLL divider and then start PLL0
AVR32_PM.PLL[0].pllmul = 10 - 1;
AVR32_PM.PLL[0].plldiv = 1;
AVR32_PM.PLL[0].pllopt = 1 << 1;
AVR32_PM.PLL[0].pllen = 1;
while( !AVR32_PM.POSCSR.lock0 ); // wait for PLL0 to be locked
}
typedef enum {
SLOW, OSC, PLL
} ClockType;
void set_clock(ClockType clk) {
switch(clk) {
case SLOW:
AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_SLOW;
break;
case OSC:
AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_OSC0;
break;
case PLL:
AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_PLL0;
break;
default:
break;
}
}
void led_init(void) {
AVR32_GPIO.port[0].gper |= 1 << 14;
AVR32_GPIO.port[0].oder |= 1 << 14;
}
int main(void) {
clock_osc0_start();
clock_pll_start();
set_clock(PLL);
led_init();
while(1) {
for(volatile int i = 0; i < 0xFFFFF; i++);
AVR32_GPIO.port[0].ovrt = 1 << 14;
}
return 0;
}
〆
思いの外サクサク進んでいてとてもいいですねえ(自画自賛)。
次回はタイマーを動かして、割り込みを入れたいと思います。そのために準備が必要なんですが、そこからやっていきましょう。