※Arduinoやmbedの記事ではありません。
AVRをCとアセンブラで開発、JTAGICEでデバッグ。
CPUはTiny2313・ATmega328・ATmega1284など。
AVRはArduinoに使われてますが、ここではJTAGICEと共にC言語やアセンブラで直接コーディングしています。
AVR の分類
8ピン
I/O ピン数が僅少で済むような特殊用途
20ピン
AT90S1200, AT90S2313, ATtiny2313
8ビットポートが取れる
28ピン
AT90S4433, ATmega8, ATmega88, ATmega168, ATmega328 など
ADC + PWM 等、連続で8ビットポートは実質取れない
40ピン
AT90S8535, ATmega164, ATmega644(秋月で\450) など
ADC + PWM 等、8ビットポートが取れる
40ピン(8051互換)
AT90S8515, ATmega8515, ATmega161, ATmega162 など
ADC無し、外部メモリが搭載できる
64ピン
ATmega64 ATmega128 など
機能豊富だが表面実装しかない模様
USB内蔵タイプ
AT90USB82 AT90USB162 ATmega32U2 など
世代間の差異
後になって開発されたCPUでは・・・
・電源電圧範囲が拡大されていたり、
・動作可能なクロック周波数が改善されていたり、
・ピン変化検出による割り込みが追加されていたり、
・特殊機能専用ピンが汎用ポートピンの代替機能に一般化されていたりする
秋月通販で買えるAVRの比較
tiny2313V | mega88 | mega164P | mega644P | |
秋月価格 | 100 | 250 | 400 | 550 |
ピン数 | 20 | 28 | 40 | 40 |
フラッシュメモリ | 2k | 8k | 16k | 64k |
EEPROM | 128 | 512 | 512 | 2048(2k) |
SRAM | 128 | 1024(1k) | 1024(1k) | 4096(4k) |
タイマー | 8*1,16*1 | 8*2,16*1 | 8*2,16*1 | ← |
RTC | – | ○ | ○ | ← |
PWM | 4 | 6 | 6 | ← |
ADC | – | 6 | 8 | ← |
AC | 1 | 8 | 8 | ← |
2線シリアル | USI | TWI | TWI | ← |
USART | 1 | 1 | 2 | ← |
SPI | USI | SPI | SPI | ← |
Tiny2313
ここで使用してるCPUはTiny2313です。
ブレッドボードに電源とLEDを載せただけ・・・ICE と as7 を試すだけだからこれで十分だよね。
アセンブラで
/*
* AssemblerApplication1.asm
*
* Created: ?? 24/5/16 14:21:30
* Author: oj3
*/
.INCLUDE <tn2313def.inc>
RJMP reset
;ポートBを全ビット出力に設定
reset:
LDI R18, 1<<4 ;LEDポート(PD4)のみ出力ポートに設定
OUT DDRD, R18
SBI PORTD, 4 ;LED1 ON
CBI PORTD, 4 ;LED1 OFF
main: RJMP main
こりゃすごい。
CPUの初期設定も何も要らずに動いちまう。
ルネサスのSHなんかを動かそうと思ったら初期設定だけで気が狂いそうなのに、AVRはなんもせんでもまず動いてしまう。素晴らしいっす。
良く見ればコメントがC形式だ(^^;
なんか良い感じ~
ちゅうことで1本目は終わり。
Cで
次はCで書いてみた。
ハードは同じ物。
せっかくだから内蔵のクロックやらWDTやらを試してみた(WDTの実例はここにもあり)。
//---------------------------------------------------------------------------------------------
//
// ■I/Oφの分周比変更と動作確認
//
// ■WDTの動作確認
//
// Atmel studio 6 & AVRDragon & ATtiny2313
//
//---------------------------------------------------------------------------------------------
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <util/delay.h>
#define nop() __asm__ __volatile__ ("nop")
volatile uint32_t _timer;
//---------------------------------------------------------------------------------------------
// I/Oクロック・プリスケーラ変更方法 2012/05/25 oj3
// http://awawa.hariko.com/chira-ura/atmega168-chapter8-jp.html#chapter8-12-2
// CKDIV8ヒューズがプログラムされていない場合、CLKPSビットは"0000"にリセットされます。
// CKDIV8がプログラムされている場合、CLKPSビットは"0011"にリセットされ、スタートアップ時の
// 分周比は8=1MHzになります。
//
// ・CKDIV8ヒューズに「レ」が入ってるとI/Oクロックは1Mになる。外すと8Mになる。
// 「レ」が入ってても以下の方法でI/Oクロックを自由に変更可能だけど、まず使わないな。
//
//---------------------------------------------------------------------------------------------
void ioclk_test(void)
{
// φ確認用のTIMER0の設定
TIMSK = (1<<TOIE0); // タイマ0のオーバーフロー割り込み許可
TCCR0B = 0x4; // 8M動作時 0x4=256 分周だと実測で約8ms周期(timer=131で1秒)
sei();
// 分周無し(=8MHz動作)でTimer動作時間を見る
CLKPR = 0b10000000; // クロック・プリスケーラ変更許可ビットをセットしておいて
CLKPR = 0b00000000; // プリスケーラ値を変える:0=分周無し、1=2分周、3=4分周...
_timer = 1310L; // BP3 10秒待ち
while (_timer);
nop(); // BP4
// 分周値1でTimer動作時間を見る
CLKPR = 0b10000000; // クロック・プリスケーラ変更許可ビットをセットしておいて
CLKPR = 0b00000001; // プリスケーラ値を変える:0=分周無し、1=2分周...8=256分周...
_timer = 1310L; // BP1 20秒待ち
while (_timer);
nop(); // BP2
}
// TIMER0オーバーフロー割込みハンドラ
ISR(TIMER0_OVF_vect)
{
if (_timer) --_timer; // I/Oφ8M時、_timer=131で約1秒待ちになる(8mS周期)
}
//---------------------------------------------------------------------------------------------
// WDTの動作チェック 2012/05/25 oj3
// http://cega.jp/avr-libc-jp/group__avr__watchdog.html
//
// ・FuseはデフォルトのままでOK(ベクタ割込を発生させる場合は変更の必要が有るようだ)。
// ・wdt_enableとするだけでWDTは使用可能であり、WDT発生時はmain()に戻ってくるのを確認した。
// ・WDT発生までの時間は以下のマクロで指定する(wdt.h内にある)
//
//#include <avr/wdt.h>
//#define wdt_reset() __asm__ __volatile__ ("wdr")
//#define wdt_enable(value)
//#define wdt_disable()
//#define WDTO_15MS 0
//#define WDTO_30MS 1
//#define WDTO_60MS 2
//#define WDTO_120MS 3
//#define WDTO_250MS 4
//#define WDTO_500MS 5
//#define WDTO_1S 6
//#define WDTO_2S 7
//#define WDTO_4S 8
//#define WDTO_8S 9
//---------------------------------------------------------------------------------------------
ISR(WDT_OVERFLOW_vect) // ベクタ割込みはテストしてない
{
if (_timer) --_timer;
}
void WDT_off(void)
{
cli();
wdt_reset(); // ウォッチドッグ タイマ リセット
MCUSR &= ~(1<<WDRF); // ウォッチドッグ リセット フラグ(WDRF)解除
WDTCSR |= (1<<WDCE)|(1<<WDE); // WDCEとWDEに論理1書き込み
WDTCSR = 0x00; // ウォッチドッグ禁止
sei();
}
void wdt_test(void)
{
volatile long i;
PORTD = 0xff;
DDRD = 0xff;
for (i = 100000L; i > 0L; i--) wdt_reset();
sei();
wdt_enable(WDTO_1S); // 約0.12sでタイマアウトする
nop();
while (1) {
PIND |= _BV(PD6);
for (i = 1000L; i > 0L; i--);// wdt_reset();
nop();
}
}
//---------------------------------------------------------------------------------------------
// Tiny2313動作テスト用
//---------------------------------------------------------------------------------------------
void test2313(void)
{
volatile long i;
PORTD = 0xff;
DDRD = 0xff;
while(1) {
PIND |= _BV(PD6);
for (i = 100000L; i > 0L; i--) nop();
nop();
}
}
//---------------------------------------------------------------------------------------------
//
//---------------------------------------------------------------------------------------------
int main (void)
{
nop();
WDT_off();
wdt_disable();
// LED点灯で基礎動作チェック
test2313();
// I/Oクロックの分周比のテスト
//ioclk_test();
// WDTのテスト(テスト終了後はmain先頭でWDTを止めること)
nop(); // ここにBPをセッとして実行させるとWDTの発生を見られる
wdt_test();
}
良いね~
ステップ実行もブレークも綺麗にかかる。
調子が悪いときはライターでFlashをクリアするとか、ISPの通信速度を125Kにしたりリセットしたりして回復させる。
ISPケーブルの接触不良やハンダの外れかけとかで調子を崩したらCPUを交換しちまうのが手っ取り早い。100円だし(笑)
この時点で徹底的にいじっておかないと、怖くて先に進めないよ。
ICEが100%とは言わなくても90%は確実に動く事、Flash上でプログラムが間違いなく安定して動くこと、WDTが効くこととか調べておかないと組み込みには使えないもん。
ATmega328P
tiny2313で色々作ってたんだけど、ちょいI/Oとメモリが不足してきた(tiny2313は浮動小数点とか使うとメモリが足りなくなるよ・・・当たり前だ)。
で、一回り大きいmega328pをメインに使うことにした。確か150円だったかな?
足の数が28本とちょっと多いけど、メモリ量やI/O点数からしてやっぱ便利そうなんだもん・・・これ以上の能力が必要だとR8やSH2を使うかも。
まずはXtalを外付けした。
高速通信やPWMを使うとなると、やっぱり安定した周波数が欲しい。
16MのXtalの足を無理矢理曲げて9Pと10Pに突っ込んだ・・・16Mにした理由は特になし。
両方の足に22pFのコンデンサを挿して実験。
メインクロックがまともに動いてるか調べるのに一番簡単なのはUARTか?
ざっとプログラムを組んでターミナルと通信できるか調べてみよう。
Xtalを繋いだだけじゃダメよ。
Fuseを書き換えるのだ。
あまり深く考えずにデータシートを斜め読みして安全パイそうなやつを設定。
ここに詳しい説明有り。
ちなみにFuseビットはこんな感じ。
エコーバックするプログラムはこんな。
受信は割込みで取ってる。
そのうち本気で使うときにリングバッファ化しよう。
ちゅうことでXtal外付け成功。
#include "_common.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/sfr_defs.h>
#include <string.h>
#include <stdlib.h>
#define F_CPU 16000000
#define BAUD 9600
#define MYUBRR (F_CPU/16/BAUD)-1
// 初期化
void uart_init(void)
{
// ボーレート設定
unsigned short tmp = MYUBRR;
UBRR0H = (tmp >> 8) & 0xff;
UBRR0L = tmp & 0xff;
// 受信完了割込み、送信機能、受信機能を利用する
UCSR0B = (1 << RXCIE0) | (1 << RXEN0) | (1 << TXEN0) ;
UCSR0C = (3 << UCSZ00) ; // データ長8bit
}
// 受信完了割込み
ISR(USART_RX_vect)
{
volatile unsigned char ch ;
ch = UDR0 ;
UDR0 = ch;
}
Fuseビットの詳細(328Pの場合)
- BODLEVEL
低電圧監視機能
Vcc電源レベルを常に監視し、設定された値より下回ったときに、RESET信号をアクティブにする(2V7は2.7Vの意味)。
どうせ必要だから「2V7」を必ず指定。
- RSTDISBL
RESETピンをIOピンとして使う
ピン数の少ないデバイスでは1ピンでも多くのピンを利用したいじゃん。
AVRはパワーオンリセット回路が搭載されてるんで、外部リセットが不要ならIOとして使えるよってこと。
でもISPを使うんだからチェック禁止。
- DWEN
debugWIREイネーブル
DragonでISPデバッグするんだから必ずチェックする。
これをチェックしたらターゲットを再起動するのを忘れずに(再起動しないとデバッグモードに入れない)。
- SPIEN
SPIで使うんだから必ずチェックする。
- WDTON
ウオッチドッグタイマの使用・不使用
デバッグ中にこれをチェックしちゃうとデバッグがやりにくいぞ。
開発が終わりに近づいた頃にチェックを入れるって感じにしてる。
ま、最終的にはチェックするだな。
- EESAVE
EEPROMの消去防止
プログラムを書き換えるときデバイスをEraseするとEEPROMのデータも消去されちまう。
それを防ぎたいときはこいつをチェックする。
オラはEEPROMを使ってないので今のところは無関係だな。
- BOOTSZ
ブートブロックサイズを決める。
ブートブロックはメモリの最後部に置かれます・・・だとさ。
- BOOTRST
RESET時に0番地からスタートするか、ブートブロックの先頭からスタートするかを決める。
これをチェックしておくと、BOOTSZビットで設定したブロックの先頭番地からスタートするそうだ。
- CLKDIV8
システムクロックの分周指定
内蔵8Mクロックを8分周する指定だ(デフォルトではAVRは1Mで動いてる)。
CPUの起動が確認出来たら全く不要なんでチェック禁止。
- CLKOUT
システムクロックを外部に出す
そうしたければチェックすれば良い。
- SUT_CKSEL
システムクロックの種類と起動時間の設定
XTAL発振でスリープとか使ってないなら EXTFSXTAL_16KCK_14CK_65MS で良いよな。
↑XTAL外付け、電源ONから16クロック+65ms、RESETから14クロック+65msで起動。
スリープからの起動で電気をケチしたいなら短くするってこと。選択肢の詳細はここ。
<能書き>
AVRはシステムクロックとして非常に多くのクロックソースを選択できます。このFuseビットは・・・
・クロックソースとして何を用いるか?(XTALか内蔵かなど)
・クロック起動 → リセット解除 → 実際にプログラムがスタートするまでの時間はどうする?
ってなことを指定する。
つまり、SUT_CKSELは、クロックソースとスタートアップタイムを選択するビットだ。
クロックソースとしては・・・
・内部RCオシレータ
・外部クリスタルオシレータ/セラミックレゾネーター
・外部クロック
などから選択できます。
外部クリスタルの場合、発振周波数によっても選択が変わりますので注意します。
これらのクロックソースの選択に加えて、スタートアップタイムも選択しなければなりません。
スタートアップタイムの目的ですが、第一にAVRに接続される周辺デバイスと同期を取ることが考えられます。
電源が加わり、システムクロックが安定にスタートしてすぐに実行が開始され、周辺デバイスへ初期化コマンドを送っても
周辺デバイス自体の初期化がすんでいなければコマンドを受け取れません。
そこでAVRではクロックが安定してからAVRがスタートする時間を遅らせることができるスタートアップタイムをプログラム
できるようになっています。
これとは逆に、システムクロックをとめて消費電力を抑えるスリープモードから割り込みなどで立ち上がる際、クロックが安定
したらすぐにプログラムをスタートさせる必要があります。
このときはスタートアップタイムを最小に設定します。また電源の立ち上がり早いもの遅いものによってもこれらの設定を選択
する必要があります。
遅い立ち上がり電源を使用するときはクロックがスタートしてから電源が十分な電位に達するまでの時間を確保すべきです
割込みの初期設定
割込みがらみのレジスタ設定 概要 詳細 マニュアル(P39)
mega328では、INT0~1とPCINT0~20の割込みがあるが、INTはPIN別+優先順位高、PCINTは8PIN分まとめて発生し優先順位が低い。
検索文字列としては INTでは EICRA/EIMSK 、PCINTでは PCICR/PCMSK 、その他 sei() cli() ISR などだ。
レジスタ設定ではビットが定義してあるので sbi(EIMSK, INT0) などとする。
割込み関数は ISR(INT0_vect) で記述する。Symbol名の一覧表は概要のページ下部を参照のこと。
なおPCINTはレベル変化で割り込みが発生するので、ISR関数内でポートを読んで処理を決めてやるこっちゃ。
<例>
cli();
DDRC = 0b11111111; // OUT方向に設定しても割込は発生する(この機能によりソフトウエア割込みを実現)
sbi(PCICR, PCIE1); // PCINT8~14の割込を使用する
sbi(PCMSK1, PCINT8); // PCINT8のマスクを解除
sei();
ISR(PCINT1_vect)
{
bool LimitOn = (PINC & _BV(PCINT8))? TRUE: FALSE; // HレベルならリミットONと判断してる
}
定義済みの変数など
as6(Atmel studio 6)を起動して、ソリューションエクスプローラの”Dipendencies\io.h”にCPU名の一覧がある。
__AVR_ATmega1284P__ とか __AVR_ATmega328P__ __AVR_ATtiny4313__ ・・・などなど。
これらの定義済み#defineは、コンパイル時に参照可能なので以下のような使い方が可能。
// 1284はUART0を使用
#if defined (__AVR_ATmega1284P__)
#define _ISR_RX_NAME USART0_RX_vect // 受信割込みベクタ
#define _ISR_UDRE_NAME USART0_UDRE_vect // 送信 "
#warning "_uart.cは1284p用に作成された"
// 328はUARTは一つだけだ
#elif defined (__AVR_ATmega328P__)
#define _ISR_RX_NAME USART_RX_vect // 受信割込みベクタ
#define _ISR_UDRE_NAME USART_UDRE_vect // 送信 "
#warning "_uart.cは328p用に作成された"
#endif
また各CPUに特有のレジスタ名や割り込みベクタ名などは、iomxxxx.h(xxxxにはCPUの型番が入る)に記述されてる。
コメント