フタなしカンヅメ

徒然なるままに @happytar0

AVRでLEDを点滅させるプログラム

前回の記事で無事にマイコンを経由してLEDを点灯させる事ができた。
その時に利用したプログラムはサンプルをコピペしただけだったので、2つ接続したLEDを交互にランダム点灯させるプログラムに変更したいと思う。

最初にサンプルコードで意味が分からない変数や関数があったので、それを調べることにしてみた。サンプルコードは以下のような感じだ。

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRD = 1 << 4;           /* make the LED pin an output */
    for(;;){
        char i;
        for(i = 0; i < 10; i++){
            _delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
        }
        PORTD ^= 1 << 4;    /* toggle the LED */
    }
    return 0;               /* never reached */
}

定義済みグローバル変数

DDRDとPORTDにビット演算の結果を代入しているようだけど、まずここが謎だった。

DDRD = 1 << 4;
PORTD ^= 1 << 4;

DDRD変数の中身が気になったので、宣言元である「avr/io.h」を覗いてみたところ、マイコンの種類判定後に「iotn2313.h」をインクルードしているようだ。

#elif defined (__AVR_ATtiny2313__)
#  include <avr/iotn2313.h>

「iotn2313.h」の中にDDRDの宣言があったけどマクロを呼んでいるようだ…。

#define DDRD    _SFR_IO8(0x11)

_SFR_IO8マクロの宣言元は「sfr_defs.h」にあった。どうやらアセンブラとCからの呼び出しでマクロの内容も変化するようだ。

// アセンブラ
#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)
// C
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

uint8_t型でアクセスできるようで、__SFR_OFFSETは、0x20のアドレスになっているようだ。

AVRのSFRは基本的にメモリ空間にマッピングされているが、低位の64バイトはI/O空間にもマッピングされていて、どちらでもアクセスできる。 しかし、メモリ空間の最初の32バイトには汎用レジスタがマッピングされているのに対し、I/O空間にはそれがないのでアドレスが32バイトずれている。 つまり、メモリ空間で0x20~0x5Fのレジスタが、I/O空間で0x00~0x3Fに見える。 これらのレジスタはIN命令やOUT命令などでアクセスが可能だが、io.h ではレジスタ名がメモリ空間のアドレスで定義されいているので変換が必要になる。 このズレ(0x20)を示すマクロが__SFR_OFFSETで、メモリ空間からI/O空間に変換するためのマクロが_SFR_IO_ADDR。 _SFR_MEM_ADDRというのもあり、こちらは何も変換しない。

このサイトで詳しく解説されていた。
AVR libcを使ってみる you/junkbox

0x11をデータシートで確認してみると、DDRDレジスタ(0x31)となっていた。DDD0〜6とビット位置は同じになっているようだ。
そもそもDDRDレジスタってなんだろって事なんだけど、実は先生に概ね教わっていた。
DDR○は入力・出力を決定するレジスタらしい。この場合、ポートDを入力にする場合は「0」、出力にする場合は「1」に設定するようだ。
同じようにPORTDは、ポートDの出力をHで出す場合は「1」、Lで出す場合は「0」にするらしい。HはHigh、LはLowを表すようで、Hにした場合にプルアップになり、電流が流れるようだ。

これで変数の意味はだいたい分かった。「iotn2313.h」にはこの他にも定義されている変数があるようだ。

関数

続いて関数について調べてみた。今回使用されているのは一つで、_delay_msという関数だ。
調べなくともだいたい分かるが、指定ミリ秒遅延させるものだろう。
気になるのはコメントの「max is 262.14 ms / F_CPU in MHz」というもの。
今回クロック周波数は8MHzに設定していたので、262.14ms / 8Mhz = 32.7675ms になる。最大32msまで設定可能という事だと思う。
32ms以上遅延させたい場合は複数回呼び出す必要があるようで、forループなどで指定回数呼び出す。

for(i = 0; i < 10; i++){
  _delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}

30ms * 10 = 300ms(0.3秒遅延させている)

ランダム点滅させるプログラムを組む

お勉強はこのくらいで実際にプログラムを組んでみた。LEDは2つ接続していて、PD3とPD4に接続している。
DDRDのビット3と4を「1(出力)」に設定した。ランダム点滅は、rand関数を呼び出す事で遅延時間を調整する。
定義済み定数・マクロをフル活用するために、指定ビットを立てる_BVマクロ、PD3・PD4定数を利用した。

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h> 
#include <stdbool.h>

int main(void) {
  DDRD = 0b00011000;
  bool flag = false;

  for (;;) {
    char i;
    int r = rand() % 10 + 1;

    for(i = 0; i < r; i++) {
      _delay_ms(30);
    }

    PORTD = flag ? _BV(PD3) : _BV(PD4);
    flag = !flag;
  }

  return 0;
}

今回のコードとMakefileはGitHubにも上げてみた。
pontago/avr-LedTest · GitHub

アセンブラで書いてみるのもいいかな。。。
次に作るものは温度計・湿度計になると思うけど、一気にハードルも上るから苦労しそうだ。