AVRマイコン入門|第一歩|TinyやATmegaを使って

当サイトにはアフィリエイト広告が表示されます。

※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の比較

tiny2313Vmega88mega164Pmega644P
秋月価格100250400550
ピン数20284040
フラッシュメモリ2k8k16k64k
EEPROM1285125122048(2k)
SRAM1281024(1k)1024(1k)4096(4k)
タイマー8*1,16*18*2,16*18*2,16*1
RTC
PWM466
ADC68
AC188
2線シリアルUSITWITWI
USART112
SPIUSISPISPI

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の場合)

ここの下2/3辺り、ここにはビットの並びが出てる。

  • 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の型番が入る)に記述されてる。

スポンサーリンク

購入リンク

¥34,241 (2023/01/30 04:50時点 | Amazon調べ)
スポンサーリンク
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


興味があるところを読む