![]() | 最新のGCCにおいてATmega32Aをターゲットとした場合のDDRのビット定義名が変わってしまったためソース互換性が保てなくなりました。そのため、PORT・DDR・PINの各レジスタのビット定義名は、PORT7~PORT0・DD7~DD0・PIN7~PIN0に変更しました。 |
マイコンのUSARTを使ったシリアル通信を行う。シリアル通信はGCC Developer Liteで提供される専用のライブラリを直接使用し、文字及び文字列の送受信や書式化文字列の送信を行う。
シリアル通信ライブラリのプロトタイプ宣言はrs.hとして提供され、ソースにインクルードする事で参照される。
#include <rs.h> // USART通信ライブラリ
また、シリアル通信を行う前には必ず初期化処理を必要とし、実際の通信処理を行う内部のサブルーチンは割り込みを使用しているので、割り込みを許可しておく。
// 送受信用バッファ // バッファの大きさは任意(2~255の間)ですが、大きすぎるとRAMエリアを圧迫するだけです。 // 通常の使用においては数バイト程度で十分です。またバッファはグローバル変数としてください。 static char txb[10], rxb[10]; // 通信ポートを初期化 ボーレートは115200[bps] rs_init (br115200, txb, sizeof(txb), rxb, sizeof(rxb)); // 通信は割り込みで処理されます // 初期化後は必ず割り込みを許可状態にしてください sei ();
その他のサブルーチンは以下のものが用意されている。詳細はソースのコメントを参照の事。
・1文字送信 void rs_putc (char); ・文字列送信 void rs_puts (char *); ・指定サイズのバイナリバイト列送信 void rs_putb (char *, short); ・受信バッファデータサイズ取得 short rs_rx_buff (void); ・受信バッファクリア void rs_rx_purge (void); ・1文字受信(受信されるまで待つ) char rs_getc (void); ・文字列受信(入力時のエコーバックあり,CRで入力完了) short rs_gets (char *, short); ・簡易書式化文字列送信 short rs_printf (const char *, ...);
マイコンのUSARTを使ったシリアル通信を行う。avr-libcで提供される標準I/Oルーチンを中継し、シリアル通信はGCC Developer Liteで提供される専用のライブラリを間接的に使用する。
初期化まではUSART1.cとほぼ同様だが、fdevopenで初期化された以後はC言語でよく使われる標準I/Oルーチン(putcharやprintf等)を使用する事ができる。
// 通信ポートを初期化 ボーレートは115200[bps] rs_init (br115200, txb, sizeof(txb), rxb, sizeof(rxb)); // 標準入出力をlibm32.a内のルーチンに割り当て fdevopen(rs_putchar, rs_getchar);
標準I/Oルーチンではまかないきれない機能は、シリアル通信ライブラリで提供されるサブルーチンで補填出来る。
PC4にソース接続されたLEDを点滅させる。
AVRの内部レジスタは avr/io.h をインクルードする事でデータシートに記載されている名称で参照出来る。なお、io.hは複数のデバイスのヘッダを取りまとめており、ATmega32の場合は avr/iom32.h が実体である。
#include <avr/io.h>
PC4を出力ポートに設定するにはDDRCの4ビット目を1に設定する。
DDRC |= _BV(DD4); // PC4を出力に
PC4へ1を書くとLEDは点灯、0を書くと消灯する。ちなみに、_BV() は引数で指定した数値をビットに変換するavr-libcで提供されるマクロである。
PORTC &= ~_BV(PORT4); // 消灯 PORTC |= _BV(PORT4); // 点灯
点滅の周期は util/delay.h で宣言される _delay_ms 関数を使用し、ミリ秒単位で設定している。
_delay_ms (500); // 500ms待つ
PC4~PC7へそれぞれLEDをソース接続し点滅させる。
出力端子の初期化は以下の通り。
DDRC |= _BV(DD4) | _BV(DD5) | _BV(DD6) | _BV(DD7); // PC4..7を出力に
点灯させるビットが増えると一つ一つビットを指定するのが面倒なので、4ビットまとめて点灯ないし消灯させる関数を用意している。
void LED (int d) { PORTC = (PORTC & 0x0f) | ((d << 4) & 0xf0); }
PC2~3にDIPスイッチ、PD2~PD3にプッシュボタンを接続し、逐次それらの状態を取り込んでLED1~4に反映させる。
入力端子の初期化は以下の通り。
// DIP1(PC2,3)を構成 DDRC &= ~(_BV(DD2) | _BV(DD3)); // PC2,3を入力に PORTC |= (_BV(PORT2) | _BV(PORT3)); // PC2,3の内蔵プルアップ抵抗をON // PB1~PB2(PD2,3)を構成 DDRD &= ~(_BV(DD2) | _BV(DD3)); // PD2,3を入力に PORTD |= (_BV(PORT2) | _BV(PORT3)); // PD2,3の内蔵プルアップ抵抗をON
DIPスイッチは2ビットまとめて数値として取り込む関数を用意している。
// DIP1(ディップスイッチ)の状態取り込み unsigned char DIP1_STAT (void) { return (~(PINC >> 2)) & 0x3; }
プッシュボタン1/2の取り込みも同様に各々関数を用意している。
// プッシュボタン1の状態取り込み unsigned char PB1_STAT (void) { return bit_is_clear (PIND, PIN2); } // プッシュボタン2の状態取り込み unsigned char PB2_STAT (void) { return bit_is_clear (PIND, PIN3); }
外部割り込みを使ってプッシュボタンの押下を検出し、各々のボタンで異なる機能を持たせる。さらに、割り込みが発生しない間はマイコンをスリープモードにし、低消費電力モードに移行させる。
プッシュボタン1,2はPD2(INT0)とPD3(INT1)に直結しており、押下されて信号が0になった瞬間に割り込みを励起するよう設定する。
// INT0/1の割り込み条件を立下りエッジに MCUCR |= _BV(ISC11) | _BV(ISC01); // ISC11とISC01を1に MCUCR &= ~(_BV(ISC10) | _BV(ISC00)); // ISC10とISC00を0に // INT0/1割り込みを許可 GICR |= _BV(INT1) | _BV (INT0); // 全割り込み許可 sei ();
マイコンの低消費電力モードはINT0/1でスリープから復帰できるモードを選択する。
set_sleep_mode (SLEEP_MODE_IDLE);
mainの中では sleep_mode () をコールする以外に特に何もしないが、スリープから復帰した事を明示的にわかるようにLED4を点滅する様に仕掛けている。
// 特に仕事はないのでスリープモードに突入(割り込みにて復帰) while (1) { sleep_mode (); PORTC ^= _BV(PORT7); // 起きる度にLED4をブリンク }
INT0に対応した割り込みルーチン名は INT0_vect としてavr-libcにて決められているので、その様に記述。処理はcnt変数の値をインクリメントし、その値をLED1~3に表示する。
ISR(INT0_vect) { LED (++cnt); }
INT1に対応した割り込みルーチン名は INT1_vect としてavr-libcにて決められているので、その様に記述。処理はcnt変数の値をデクリメントし、その値をLED1~3に表示する。
ISR(INT1_vect) { LED (--cnt); }
PA0~PA7の端子に入力されるDC0~5Vの電圧を計測し、シリアル通信で送信する。
ここではA/D変換するにあたり基準となる電圧(DC5V)をAVCC端子に接続した。
A/D変換器の初期化は以下の通り。変換時間等は変換精度等の必要に応じて設定する事。
// A/D変換許可, 変換クロックCK/16 ADCSRA = _BV(ADEN) | _BV(ADPS2);
内部にはA/D変換器は1つしかないため、8つの端子からの入力を同時に行う事はできない。任意のチャネルを選択し、変換を励起し、変換が終了するまでチャネルを切り替える事はできない。
変換開始から終了の待機及び変換されたデータを取得する関数は以下の通り。
// 任意CHのA/D変換 unsigned short a2d (char ch) { ADMUX = _BV(REFS0) | (ch & 7); // リファレンス,CH指定 ADCSRA |= _BV(ADSC); // 変換開始 loop_until_bit_is_clear (ADCSRA, ADSC); // 変換終了待ち return ADC; }
DC0~5V入力された電圧に応じて0~1023の10ビットの精度で値が取得される。
mainのループではプッシュボタン1を押すと0~1023の値として、プッシュボタン2を押すと5.00Vといった電圧値として8ch分の変換値をターミナルに対して文字列として送信する。
・PushButton1を押している間 ADC=[ 338, 609, 617, 802, 639, 887, 670, 495 ] ・PushButton2を押している間 ADC=[ 0.57V,0.32V,1.21V,1.18V,1.55V,1.93V,1.82V,1.78V ]
タイマ/カウンタ0のオーバーフローを使用して一定間隔で割り込みを励起する。
タイマ/カウンタ0のオーバーフロー割り込みを許可するのは以下の通り。
// タイマ/カウンタ0割り込みマスク構成 TIMSK |= _BV(TOIE0); // タイマカウンタ0オーバーフロー割り込み許可
タイマ/カウンタ0をCLK/1024(16MHz/1024=15.625kHz)で標準動作をさせるには以下の通り。クロックを選択した瞬間からタイマとして動作を開始する。
TCCR0 = _BV(CS02) | _BV(CS00); // 標準動作,CLK/1024
15.625kHz毎にTCNT0の値がインクリメントされ、TOP値の255を超えて0にリセットされるとオーバーフロー割り込みが発生(15.625kHz/256=約61Hz)する。タイマ/カウンタ0のオーバフロー割り込みルーチン名は TIMER0_OVF_vect としてavr-libcにて決められているので、その様に記述。ここでは割り込みが発生したらさらにソフト的に分周し、約0.5秒毎にLED1を点滅させている。
ISR (TIMER0_OVF_vect) { static int cnt = 0; // 1/16MHz * 1024 * 256 秒毎に割り込み発生 if (cnt++ > 31) { // 約0.5秒毎にLEDを反転 cnt = 0; rbi (PORTC, PORT4); } }
mainは諸々の初期化を行った後は例のごとくしつこく省電力モードに移行し続ける以外の処理は行っていない。
while (1) { // 無限ループ sleep_mode (); }
タイマ/カウンタ1の高速PWMモードにてPD4(OC1B)端子とPD5(OC1A)端子から個別のデューティー比でPWM信号を出力できる様に構成し、PWMの1周期(コンペアマッチ)毎に割り込みを発生させてLED1の点滅と周期の更新を行う。
PWM信号を五感で把握するにはPD4とPD5端子にオシロスコープ等を接続し、オシロスコープのディスプレイ上に表示される波形にて確認する。
LED1が接続されたPC4とPWM信号を出力するPD4とPD5を出力に設定する。
// LED1(PC4)を構成 DDRC |= _BV(DD4); // PC4を出力に PORTC &= ~_BV(PORT4); // LEDをOFF PC4=0 // PWM出力端子を構成 DDRD |= _BV(DD4) | _BV(DD5); // PD5,PD4を出力に
タイマ/カウンタ1を高速PWMモードに設定する。ICR1がPWMの周期、OCR1AがPD5(OC1A)のデューティー、OCR1BがPD4(OC1B)のデューティーとなり、ここでは周期を8ms(=125Hz=CLK/256 /500)としたので以下の通りに設定する。
// タイマ/カウンタ1を高速PWMモードに設定 (ICR1でTOP) TCNT1 = 0; ICR1 = 500; // TOP値 62.5kHz/500=8ms周期 OCR1A = cnt[0]; // PD5(OC1A)のデューティー OCR1B = cnt[1]; // PD4(OC1B)のデューティー TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11); TCCR1B = _BV(CS12) | _BV(WGM13) | _BV(WGM12); // CLK/256=62.5kHz
TCNT1の値がICR1と一致したらコンペアマッチA割り込みを励起させるには、TIMSKのOCIE1Aビットを1にする。
// タイマ/カウンタ割り込みマスク設定 TIMSK |= _BV(OCIE1A);
タイマ/カウンタ1のコンペアマッチAオーバフロー割り込みルーチン名は TIMER1_COMPA_vect としてavr-libcにて決められているので、その様に記述。割り込み発生毎に各々のPWMのデューティ値をインクリメントし、500以上になったら0にリセットしている。
ISR (TIMER1_COMPA_vect) { int i; // 1周期毎に比較レジスタ更新 for (i = 0; i < 2; i++) if (cnt[i]++ > 500) cnt[i] = 0; OCR1A = cnt[0]; OCR1B = cnt[1]; rbi (PORTC, PORT4); // LEDビット反転 }
マイコンに内蔵されたEEPROMの読み書きを行う。操作及び状況の確認はシリアル通信にて行う。
内蔵EEPROM関連のサブルーチン等は avr/eeprom.h に定義される。
#include <avr/eeprom.h> // EEPROM関連
EEPROMへのアクセスを行う際はそれ以前の内部的なEEPROMへの処理が完了している必要があるため eeprom_busy_wait 関数にて処理完了を待ち、さらにアクセス中は他の処理に影響が及ぶとの事で割り込み等は禁止にする必要がある。
cli (); eeprom_busy_wait (); eeprom_write_byte ((uint8_t *)i, c); sei ();
avr-libcで提供される特殊なメモリアクセス方法の紹介。
AVRはハーバードアーキテクチャーであるため、プログラムコード領域(フラッシュROM)とワーク領域(SRAM)が分離されている。つまり連続・不連続に関わらず同一のメモリマップ上には配置されないため、一般的なC言語の記述ではフラッシュROM上に置かれたデータ類へのアクセスが出来ない。
例えば
const char hoge[] = "TEST DATA\n";
といった宣言を行うとフラッシュROM内にhogeのデータが確保され、プログラムの実行が開始される際にhogeをSRAM上にコピーしたものがアクセス対象となる。つまり、ある程度サイズに余裕のあるフラッシュROM上に不変の固定データを置いたつもりでも、それと同等のサイズのデータがSRAM上に別途確保されるので、非常に小さいSRAMを圧迫する結果となる。
avr-libcにはSRAMにコピーせずにフラッシュROM上に確保したデータへアクセスするために、宣言時と利用時に特殊な命令が用意されている。
const char gyao[27000] PROGMEM = {0,0,0,0,0,0,};
PD7(OC2)端子に接続されたトランジスタを介して他励式のブザーを駆動する。
この回路ではPD7端子がHIGHになるとトランジスタがONしてブザーの+/-両端に電圧が加わる。LOWになるとトランジスタがOFFしてブザーの-端子がオープンになる。これをゆっくり繰り返すとブザーがプチプチとなるだけだが、数百~数kHzの周期で繰り返すとぴ~ぷ~といった音として聞こえる様になる。
周波数はPA7(ADC7)端子に接続したポテンショメータで分圧された電圧を計測する事で設定するものとしている。
PD7はカウンタタイマ2のOC2端子が接続されており、コンペアマッチでトグル動作する様に設定すればOCR2に設定できる範囲の値で周波数が設定出来る。また同時にPD7を出力端子に設定する必要もある。
void ToneOn (int tone) { OCR2 = UD_ToneTable[tone]; TCCR2 = _BV(WGM21) | _BV(COM20) | _BV(CS22) | _BV(CS20); // PD7(OC2)を出力に DDRD |= _BV(DD7); }
大抵ブザーの類は常時一定の電圧を加えておく事は推奨されていないので、音を出さないときはOFFしておく事で劣化を防ぎつつ無駄に電力を消費する事もなくなる。
void ToneOff (void) { // PD7(OC2)を入力(外部のプルダウン抵抗でLOWに) DDRD &= ~_BV(DD7); TCCR2 = 0; }
SPIバスで接続されたATA6831(3ch ハーフブリッジドライバ)と通信を行い、任意のパターンで各チャネルのトランジスタをON/OFFさせる。
SPIは簡単に言えばクロック同期式の双方向シリアル通信で、MISO・MOSI・SCKとチップセレクトの4本の信号でコミュニケーションする。対象によってタイミング等細かい違いはあるが、AVRの場合は大抵のデバイスとと接続する事ができる。
ATA6831は3chのハーフブリッジを内蔵した主にリレーやランプを駆動するドライバICである。ここのうちハーフブリッジを2つ使えばモータの正/逆転を行うのに十分なHブリッジが構成できるので、小型モータの駆動程度であれば十分に使える。
また3chのハーフブリッジに対してモータをうまくつなげば、動作モードの制限はあるものの2台を個別に駆動する事ができない事も無い。