LECTURE 2 LED を点滅させる

○ はじめに

ここではまず手始めとして、LED を ATmega8535 に繋げて点滅させてみます。

○ 入出力ポート

LED を光らせるためには、ATmega8535 の入出力ポートに LED を接続する必要があります。ATmega8535 は A - D の 4 つの入出力ポートを備えており、各ポートには 8 ピンずつの入出力ピンがあります。PDIP パッケージの場合、各ポートに属するピン番号は以下の通りです。

  • PORTA : 40 (PA0) - 33 (PA7)
  • PORTB : 01 (PB0) - 08 (PB7)
  • PORTC : 22 (PC0) - 29 (PC7)
  • PORTD : 14 (PD0) - 21 (PD7)
ATmega8535 -PDIP-

後述の関連レジスタの所で入出力ピンを表すマクロを載せていますが、avr-libc の場合は例えば PORTA0/PA0/PINA0 は全て同じ番号として定義されています。PINA0 の形式は入力ピンということを明示するために使えると思いますが、PORTA0/PA0 の使い分けはどうするのが一番良いのかよく分りません。

本文書では入出力ピンを表す場合は PINA0/PORTA0 の形式を使い、PA0 の形式は上記の図で使われている様にピンそのものを表したい場合に使う様にします。

○ 関連レジスタ

入出力ポートに関連するレジスタは、各ポートに対して出力/方向/入力の 3 つのレジスタがあります。以下に各レジスタの内容を書きます。X を A - D で読み替えてください。マクロについては、avr-libc で使用できる定義名を掲載しています。

  • ポート X 出力レジスタ (PORTX)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ PORTX7 PORTX6 PORTX5 PORTX4 PORTX3 PORTX2 PORTX1 PORTX0
    初期値 0 0 0 0 0 0 0 0


  • ポート X 方向レジスタ (DDRX)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ DDX7 DDX6 DDX5 DDX4 DDX3 DDX2 DDX1 DDX0
    初期値 0 0 0 0 0 0 0 0


  • ポート X 入力レジスタ (PINX)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ PINX7 PINX6 PINX5 PINX4 PINX3 PINX2 PINX1 PINX0
    初期値 不定 不定 不定 不定 不定 不定 不定 不定

入出力ポートを使う場合は、まず DDRX を設定します。ピンを入力として使う場合は、DDRX の対応するビットに 0 を、出力として使う場合は 1 を書き込みます。出力する場合は PORTX の対応するビットに 1/0 を書き込みます。入力の場合は、PINX の対応するビットを参照します。

入力ピンに対して 1 を書き込むと内部プルアップ抵抗が有効となり、未使用時/未接続時の過大電流消費を防ぐことができます。

○ 回路図

LECTURE 1 ATmega8535 および使用環境について に掲載してある全体回路図に対して、点滅させる LED 部分を赤枠で囲んだ図をこちらに置いておきます。

PORTB の PB0 ピンに LED を接続し、保護抵抗を介して GND に接続しています。これにより、PB0 が H の場合に LED が点灯します。

○ サンプルプログラムと説明

LED を点滅させるサンプルプログラムを 02led.tar.gz からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた tar ファイルをダウンロードすることもできます。

中には

  • Makefile
  • util.h
  • util.c
  • main.c

が格納されています。それぞれについて以下に載せます。なお、以下に掲載しているソースコードには修正 BSD ライセンス条項を含めていませんが、ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。

これまでにヒューズビットの設定を行ったことが一度も無い場合は、まず最初に make fuse を実行してください。今後のサンプルにおける Makefile でも fuse ターゲットが用意されています。fuse ターゲットは

uisp -dprog=dapa -dlpt=0x378 --wr_fuse_l=0xff

を実行するためのターゲットです。これにより、ATmega8535 のヒューズビットと呼ばれる設定値の下位バイトを 0xff 0xef 0xff に設定しています。ATMega8535 はデフォルトでは 1MHz の内臓 RC 発振器がクロック元となります。今回の回路では、8MHz の外部水晶振動子を接続しているため、ヒューズビットを設定することで水晶振動子をクロック元として使用する様にしています。

ヒューズビットの下位バイトの設定を 0xff から 0xef に変えました。というのも、LECTURE 1 ATmega8535 および使用環境について のヒューズビットの所に書きましたが、SUT1 - 0 ビットの設定値を誤っていました。自分の環境で動作確認してみた所、動作に影響は無かった様ですが…

上記の修正自体が間違っていました…ヒューズビットの下位バイトの設定は 0xff のままで O.K. です。LECTURE 1 ATmega8535 および使用環境について のヒューズビットの所も参照してください。

本設定は一度実行すれば記憶されますので、今回実行した場合は今後のサンプルコード実行の度に行う必要はありません。ヒューズビットについては LECTURE 1 ATmega8535 および使用環境について を参照してください。

ヒューズビットの設定が終わりましたら

make
make uisp

で、コンパイルおよび ATmega8535 への書き込みを実行します。uisp ターゲットでは

uisp --dprog=dapa -dlpt=0x378 --erase --upload --verify

を実行します。フラッシュメモリの消去、プログラムのアップロードおよび検証を行います。エラーが表示されなければアップロードは成功です。LED が約 250ms 毎に(1 秒間に 4 回程)ON/OFF を繰り返します。

↓赤枠内が PB0 ピンに接続した LED です。
LED 点灯の様子

こちらは LED 点滅の動画です(10秒。YouTube に接続します)。

  • Makefile
        1    CC = avr-gcc
        2    OBJCOPY = avr-objcopy
        3    
        4    UISP = uisp -dprog=dapa -dlpt=0x378 --erase --upload --verify
        5    FUSE = uisp -dprog=dapa -dlpt=0x378 --wr_fuse_l=0xff
        6    
        7    MCU = atmega8535
        8    FORMAT = ihex
        9    F_CPU = 8000000
       10    
       11    CFLAGS = -Wall -std=gnu99 -mmcu=$(MCU) -Os -DF_CPU=$(F_CPU)
       12    
       13    %.o: %.c; $(CC) -c $(CFLAGS) -o $@ $<
       14    %.hex: %.elf; $(OBJCOPY) -O $(FORMAT) $< $@
       15    
       16    OBJS = main.o util.o
       17    ELFS = main.elf
       18    TARGET=main.hex
       19    
       20    all: $(TARGET)
       21    main.hex: main.elf
       22    
       23    main.elf: $(OBJS)
       24        $(CC) $(CFLAGS) -o $@ $^
       25    
       26    main.o: main.c util.h
       27    util.o: util.c util.h
       28    
       29    uisp: main.hex
       30        $(UISP) if=$<
       31    
       32    fuse:
       33        $(FUSE)
       34    
       35    clean:
       36        rm -f $(TARGET) $(ELFS) $(OBJS) *~
    
    
  • util.h
        1    #ifndef ___UTIL_H_INCLUDED_
        2    #define ___UTIL_H_INCLUDED_
        3    
        4    #include <avr/io.h>
        5    
        6    
        7    #define pullup() {\
        8            PORTA = 0xff; \
        9            PORTB = 0xff; \
       10            PORTC = 0xff; \
       11            PORTD = 0xff; \
       12    }
       13    
       14    void delay_250ms(void);
       15    
       16    
       17    #endif
       18    
       19    /* End of util.h */
    
    
    • 7 〜 12 行目 (pullup())

      pullup() マクロでは、ATmega8535 が備える全ての入出力ポートの全てのピンを High に設定しています(各ビットを1に設定)。未接続の入出力ポートをそのままにしておくと 過大な電流が流れる場合があるため、入力方向に設定されていることを前提として、 全てのピンの内部プルアップ抵抗を有効にするために用意しています。

  • util.c
        1    #include <util/delay.h>
        2    #include "util.h"
        3    
        4    
        5    void
        6    delay_250ms(void)
        7    {
        8            int i;
        9    
       10            for (i = 0; i < 25; i ++) {
       11                    _delay_ms(10);
       12            }
       13    }
       14    
       15    /* End of util.c */
    
    
    • 5 〜 13 行目 (delay_250ms())

      250ms を計測するための関数 delay_250ms() です。avr-libc に _delay_ms() が定義されているので、 それを基にしています。

      void _delay_ms(double _ms);
      _ms : 遅延するミリ秒
      

      _delay_ms() を使うためには、F_CPU マクロを定義する必要があります。本サンプルでは Makefile にて

      F_CPU = 8000000
      

      としています(8MHz なので)。avr-libc のドキュメントによると、可能な最大の遅延時間は 262.14ms / F_CPU(MHz) = 約 38ms までとなっています。これにより、_delay_ms(10) を 25 回実行することで 250ms の遅延を実現しています。

  • main.c
        1    #include <avr/io.h>
        2    #include "util.h"
        3    
        4    
        5    int
        6    main(void)
        7    {
        8            /* PB0 ピンのみ出力設定 */
        9            DDRB = _BV(PORTB0);
       10            pullup();
       11    
       12            while (1){
       13                    PORTB ^= _BV(PORTB0);
       14                    delay_250ms();
       15            }
       16    
       17            return 0; /* 警告を避けるため */
       18    }
       19    
       20    /* End of main.c */
    
    
    • 1 行目

      各種レジスタやビット等のマクロ(PORTB0 や DDRB 等) を使うために、avr/io.h をインクルードしています。 ほとんどのプログラムで avr/io.h をインクルードすることになると思います。

    • 5 〜 18 行目 (main())

      LED 点滅処理の main 関数です。

      9 行目で、PORTB0 ピンを出力方向に設定しています。DDRB はポート B の方向レジスタです。 _BV() マクロは、番号を指定するとそのビットのみを 1 にした値を返してくれます。 avr/io.h をインクルードすると使えます。PORTB0 = 0 のため、_BV(PORTB0) によってビット 0 のみが 1 となった値を DDRB に設定することになります。

      10 行目で、PORTB0 以外の内部プルアップを有効にします。PORTB0 は出力ピンのため High が出力されます。これにより、PORTB0 に接続した LED が点灯します。

      12 〜 15 行目のループで点滅処理を行っています。13 行目の

      PORTB ^= _BV(PORTB0);
      

      によって、PORTB0 の状態を反転しています。^= は排他的論理和のため、PORTB0 が 1 であれば結果は 0 に、PORTB0 が 0 であれば結果は 1 になります。LED の状態を反転した後、 delay_250ms() によって 250ms 待った後、状態反転処理に戻ります。

以上で解説は終わりです。プログラムはかなり簡単に感じたのではないでしょうか?もっと速く点滅させたりと、いろいろやってみてください。