トップ 新規 編集 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

サンプル-基本

最終更新時間:2009年08月11日 05時02分39秒

スイッチ入力、LED点灯など基本

 入力

スイッチ入力

内蔵プルアップを利用する場合

環境や配線によってはこれではうまくないこともあります。その場合は適切な入力回路を設計する必要があります。

プログラム例:PortB7(PINB7)にスイッチをつなげ、他は出力にする場合
/*初期設定*/
DDRB = 0x7F; /* DDRB 出力ピン対応ビットはセット、入力ピン対応ビットはクリア */
PORTB= 0xFF; /* 入力ピンに対応するビットに1を書き込み、内蔵プルアップ指定 */
/* スイッチの読み取り */
if (bit_is_clear(PINB,PINB7)) /* スイッチが閉じると成立 */
    PORTB = PORTB & ~_BV(PB0);       /* ここではPB0をクリアしてLED点灯 */

ただしこれはスイッチのチャタリングに対応していません。実際には10msec毎にPINBを読み取り、連続して同じ値が得られたときはじめて採用するようにするとよい。

※バグや暴走でポートが出力になっても大丈夫なようにしたければ、スイッチに直列に適当な抵抗を入れれば安心? 内蔵プルアップは20kΩ〜100kΩなので、数百Ω抵抗ならスイッチオン時確実にLowLevelとなり、出力になった場合でも電流は10mA程度に収まります。

チャタリング対策の例

オーバーフロー割り込みは10msec毎程度にする

#define TCNT0_BASE (100)   /* 例です。割り込み周期の調整のため */
static  uint8_t pin_b=0;   /* チャタリング除去後のpinb出力*/
ISR(TIMER0_OVF_vect)
{
    TCNT0 = (TCNT0_BASE);  /* 割り込み周期の調整のため */
    static uint8_t pin_b1=0; /* 前回入力値 */
    uint8_t pin_b2,mask;
    pin_b2  = PINB & ~DDRB; /*入力用portのみ取り出す*/
    if (pin_b1==pin_b2) 
        pin_b = pin_b1;
    pin_b1 = pin_b2;
}

スイッチの値読み取りにのんびり時間を掛けてもいい場合は、こんなもんで充分かな?

//makefile内でF_CPUにクロック周波数を定義してください
#include <util/delay.h> 
  :
uint8_t check_switch(void)
{
    static PINB_value=0xFF;
    uint8_t v;
    
    v=PINB;
    _delay_ms(10);
    if (v!=PINB) 
        v = PINB_value;
    else
        PINB_value = v;
    return(v);
}

:ハードウェア対応ローパスフィルタを入れる。時定数(C*R)が1-2msec程度のもの(1-2k*1uF)を入れればいける?

  • ↑【コメント】 AVRの入力ってシュミットだっけ?立ち上がりが遅い波形を入れると動作が不定になる ってなハイスピードCMOSみたいな事はないんだっけ?
  • ↑そうです。入力がシュミットトリガなAVRだからできるのです。データシートのポート回路構成図をご覧ください。そうでないデバイスの場合はローパスフィルタの後にシュミットトリガゲートを挟まないとおっしゃるような事態になると思います。
  • ↑スイッチと直列の抵抗(20k)はもっと小さい方が良いのでは?1k位が妥当と思います。
  • ↑んだな。内蔵プルアップが最悪20kしかないかもしれない可能性を忘れてたわ。動いたのはたまたまか。

外部の電圧を入力する:

確実にAVRと同じ電圧の信号でGNDレベルが共通だとわかっているなら、そのままポートにつなげばよいです。

相手が12VなどVccを越える電圧の場合

安易な方法:100kΩ程度の抵抗を入れる

他人には使わせない趣味工作限定?AVRのVccが5Vで入力が12Vの場合、電位差7Vに応じて0.07mA程度の電流が流れますが、この程度ならAVRの保護ダイオードがどうにかしてくれ・・ると思う。

ダイオードを使う

ダイオードを介してプルアップした入力に接続します。負電圧がかかるとまずいのが欠点

ツェナーダイオードを使う
制限抵抗とVcc付近の電圧のツェナーで電圧を落とす。負電圧時の保護にもなります。
トランジスタを噛ませる
論理反転する。実際のプルアップ・ベース制限抵抗の値は要計算。
フォトカプラを噛ませる
いちばん安全?欠点:相手回路から数mAの電流を取る
分圧して入力
相手電圧が決まっていればOKなはずだけど、予定外電圧には辛い

ADC入力

ノイズに注意。適切な配線についてAVRのデータシートにいろいろ書かれています。

 出力

  • 例えばATMega8の電気的特性の「絶対最大定格」を見ると、入出力ピン出力電流40.0mA、消費電流200.0mAとある。これは「絶対最大」の定格なので、多くてもこれの70%くらいで使うのが望ましい(よね?)
  • そのはずだけど、データシートのグラフはそんなのぶっちぎって60mAとか70mAとか流してる〜。流れるけど、流すのは止めましょうってか?

LED点灯

  • 旧いAVR(AT90Sシリーズ)の場合シンク(Vcc→制限抵抗→LED→portとつなぎ、出力=LoでONとする)の方が電流を流しやすい。たとえばAT90S2313では Vcc=5V時、ソース(port→制限抵抗→LED→GNDとつなぎ、出力=H でONとする)は1Vの電圧降下で7mA程度しか流せないのに対し、シンクは(1Vの電位上昇で)45mA流せた。
  • 今のAVR(mega/tinyシリーズ)はソースシンクがほぼ対称で、Vcc=5V時に1Vの電圧降下(上昇)で40〜45mAを流すことができる。ATmega48/88/168のデータシートから:
  • LEDの制限抵抗値 = (Vcc-LEDのVf)/(LEDに流したい電流)。Vfはおおむね白・青で3.4V、緑で2.5V、赤で1.9Vですけど、ちゃんとお使いのLEDのデータシートで確認しましょう。
  • ソースで使った場合、旧いAVRでは内部の電圧降下が大きく、上記より低めの電流になる。
  • これを逆手にとって、AT90Sシリーズ限定であるが、低めVccでポート→LED→GNDと接続して、制限抵抗なしで(ポートの内部抵抗を制限抵抗代わりにして)LED点灯している人もいるらしい・・あまりお勧めできないし、現行のAVR(mega/tiny)ではまったく勧められない)が、AVR-Libcドキュメント内の "A simple project"にその辺の記述があります。
  • よくわからん時は、5V駆動の場合はとりあえず470Ωくらいの抵抗をLEDに付けてみる。ここから輝度を見ながら抵抗の値を変えてみる。くれぐれも素子の定格を超えないように。

7segLED・ダイナミック点灯

7segLEDなど多数のLEDを点滅させる場合は、ピン1本に1つのLEDをつないでいてはピン数が足りないので、ダイナミック点灯を行う。

  • PB4がLoでトランジスタQ0がONになり、そこにつながっている左側4個のLEDとVccが接続される。LED4個を点灯する電流はポート1個ではまかないきれないのでこのようにトランジスタを介する。
  • PB0-PB3がLoになると、対応するLEDが点灯する。
  • 左側4個と右側4個を同時に点灯するとPB0-PB3を共用しているためお互いに影響し合うので×。そこで、左側4個を点灯(Q0のみON)と右側4個を点灯(Q1のみON)のパターンを繰り返す。
uint8_t led_data[2];

ISR(TIMER0_OVF_vect)
{
    static uint8_t led_bank;
    uint8_t pbdata,led_pattern;
    led_bank++;
    if (led_bank>1) led_bank=0;
    //led_bankは0〜1を繰り返す
    led_pattern = (_BV(4)<<led_bank) | (led_data[led_bank]&0x0F);
    pbdata = PORTB & 0b11000000;  //LED点灯に関係ない部分を保存
    PORTB  = pbdata | 0b00111111; //いったん全消灯
    PORTB  = pbdata | ~led_pattern;
    //点灯/トランジスタONはLo(0)なので、ビット反転して書き込む
}

led_bankは今回点灯したいトランジスタを表します。Q0を点灯したければ0,Q1なら1。タイマオーバーフロー割り込みで、適切な時間間隔でled_bankを切り替えていきます。ここでは2つしかないですので、0→1→0→1・・・・となりますが、たとえば8組あるなら0→1→2→3→4→5→6→7→0→・・・・となります。

_BV(4)<<led_bank は、今回点灯したいトランジスタにつながっているPORTにあたるbitを1にします。led_bank=1ならQ1にあたるPB5が1となる値を返します。下位4bitにはledの点灯パターンを入れます。Q0にあたるパターンはled_data[0],Q1のパターンはled_data[1]に入れてあります。これを呼び出してled_patternの下位4bitにセットします。実際にはONにするトランジスタ/LEDは0(Lo)になるので、あとでビット反転(~)します。

最後に、今回は使っていないPORTBの上位2bitと、led_pattern(ビット反転)を合成してPORTBに送ります。

これをタイマ割り込みなどで数msec毎に繰り返せば、8個のLEDを6本のピンで制御できます。LED点灯データled_data[]は、割り込みの外、メインルーチンから書き込みます。LED点灯pinとトランジスタ駆動pinの数が等しいとき最も少ないピンで多くのLEDを制御できるようになりますが、使いやすい値を選ぶのが便利だと思われます。

カソードコモン、アノードコモン

7segmentLEDなどは、最初からLEDの片方を束ねた形でパッケージングされたものもあります。LEDのアノード側(+側)を束ねたものをアノードコモン、カソード側(-側)を束ねたものをカソードコモンといいます。先の例はアノードコモンになります。カソードコモンとなる場合は、以下のようになります。トランジスタにNPNを使うこと、PORTに与える値1は、LED点灯/トランジスタになることに注意してください。(先のプログラムでPORTB出力前にbit反転させなくてもいい)

uint8_t led_data[2];

ISR(TIMER0_OVF_vect)
{
    static uint8_t led_bank;
    uint8_t pbdata,led_pattern;
    led_bank++;
    if (led_bank>1) led_bank=0;
    //led_bankは0〜1を繰り返す
    led_pattern = (_BV(4)<<led_bank) | (led_data[led_bank]&0x0F);
    pbdata = PORTB & 0b11000000;  //LED点灯に関係ない部分を保存
    PORTB  = pbdata;  //いったん全消灯
    PORTB  = pbdata | led_pattern;  //bit1=点灯なのでそのまま書き込み
}

7segment-LED

7segmentLEDについても同様。カソードコモンの例。LED数が多いので、トランジスタ駆動ポートとLED制御ポートが別々になっています。秋月の2桁LEDのpin配置/LEDデコーダIC(4511)に合わせて、PB0〜PB7には順にセグメントf,g,a,b,c,d,e,pの順に割り当てています。他の並びにする場合はデータを差し替えてください。

      a PB2      74HC4511     秋月2桁LED(A/C-552SR)  秋月3桁LED(C-553SR)
     ----         +----+     +-------------------+   +-------------+
PB0 | PB1|b       |    |f    | f g a b 1 2 f a b |   | 1 A F 2 3 B |
   f|  g | PB3    |    |g    | 1 1 1 1     2 2 2 |   |             |
     ----         |    |a    |                   |   |             |
 PB6|    |c       |    |b    | 1 1 1 1 2 2 2 2 2 |   |             |
   e|    | PB4    |    |c    | e d c p e g d c p |   | E D p C G NC|
     ----         |    |d    +-------------------+   +-------------+
       d PB5      |    |e
          *p PB7  +----+

[PB0..PB6] = [f,g,a,b,c,d,e]なら、
    0x7D,0x18,0x6E,0x3E,0x1B,0x37,0x77,0x1C,0x7F,0x3F
[PB0..PB6] = [a,b,c,d,e,f,g]なら、
    0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7C,0x07,0x7F,0x67 (上と字体が違うかも)

配線の都合により、どのように並べたほうが楽かは変わってくると思うので、
テストプログラムを走らせてセグメント対応を調べてからパターンデータを回路にあわせて作ったほうがいいかも。。。

以下はカソードコモンの場合です。

このケースでは、たとえば"1"を表示したければ、セグメントbとcを点灯するので、PB3とPB4点灯で、PORTBに与えるデータは0b00011000となります。表示したい数字・文字とパターンデータのテーブルを作っておいて、それを使ってLED点灯パターンデータに変換し、これをled_data[]に代入すればよいでしょう。

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

uint8_t led_data[2];

ISR(TIMER0_OVF_vect)
{
    static uint8_t led_bank;
    uint8_t pd_data;
    led_bank++;
    if (led_bank>1) led_bank=0;
    //led_bankは0〜1を繰り返す
    pd_data = PORTD&0b11001111; //トランジスタに接続部以外を保存
    PORTB = 0;    //いったん全消灯
    PORTD = pd_data & (_BV(4)<<led_bank);
    PORTB = led_data[led_bank];
}

prog_char digit_pattern[10] PROGMEM = 
{
    0x7D,0x18,0x6E,0x3E,0x1B,
    0x37,0x77,0x1C,0x7F,0x3F
};

#define get_digit_pattern(n) pgm_read_byte(digit_pattern+n)

int main(void)
{
    TCCR0 = 3; // overflow each 1.6msec @ 10MHz
    TIMSK |= (1<<TOIE0);  //enable Timer0  overflow
    sei();
    led_data[0]=get_digit_pattern(1);
    led_data[1]=get_digit_pattern(2);
    // 7segLEDには"12"が表示される・・・はず
}

(↓の指摘、対応)

あれ?

   a
  ----
 |    |b
f|  g |
  ----
 |    |c  
e|    |
  ----
    d  *

じゃないんすか?

prog_char digit_pattern[10] PROGMEM = 
{
    0x3F,0x0C,0x76,0x5E,0x4D,
    0x5B,0x7B,0x0E,0x7F,0x5F
};

は

   b
  ----
 |    |c
a|  g |
  ----
 |    |d
f|    |
  ----
    e  *

になってる気がするんですが?

↑あたり・・・いつもfabcdegpの順に信号線並べていたので(専用デコーダIC互換)間違えた・・・・orz と思ったら、gの位置が違っていた,0のデータが抜けてた・・・・・・○| ̄|_

私はいつも(AVRでCを使ったことは無いんですけど)こんな風にプリプロセッサ任せにしてます。

#define PB7     7
#define PB6     6
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

#define SEGA    (1<<PB2)
#define SEGB    (1<<PB3)
#define SEGC    (1<<PB4)
#define SEGD    (1<<PB5)
#define SEGE    (1<<PB6)
#define SEGF    (1<<PB0)
#define SEGG    (1<<PB1)
#define SEGP    (1<<PB7)

prog_char digit_pattern[10] PROGMEM = {
/*  SEGA|SEGB|SEGC|SEGD|SEGE|SEGF|SEGG|SEGP */
    SEGA|SEGB|SEGC|SEGD|SEGE|SEGF          ,    /*  0   */
         SEGB|SEGC                         ,    /*  1   */
    SEGA|SEGB|     SEGD|SEGE|     SEGG     ,    /*  2   */
    SEGA|SEGB|SEGC|SEGD|          SEGG     ,    /*  3   */
         SEGB|SEGC|          SEGF|SEGG     ,    /*  4   */
    SEGA|     SEGC|SEGD|     SEGF|SEGG     ,    /*  5   */
    SEGA|     SEGC|SEGD|SEGE|SEGF|SEGG     ,    /*  6   */
    SEGA|SEGB|SEGC|          SEGF          ,    /*  7   */
    SEGA|SEGB|SEGC|SEGD|SEGE|SEGF|SEGG     ,    /*  8   */
    SEGA|SEGB|SEGC|SEGD|     SEGF|SEGG     ,    /*  9   */
};

ポートを変える場合でも、字体を変える(7のaセグメントとか)場合でも楽チンです。

逆に、2進数で指定しておいて、マクロでビット入れ替えする手もあるね。
#define BITSORT(x)  ( \
    ((x& 64)?  4:0)+  \
    ((x& 32)?  8:0)+  \
    ((x& 16)? 16:0)+  \
    ((x&  8)? 32:0)+  \
    ((x&  4)? 64:0)+  \
    ((x&  2)?  1:0)+  \
    ((x&  1)?  2:0)   \
    )

prog_char digit_pattern[10] PROGMEM = {
    /*        ABCDEFG */
    BITSORT(0b1111110)    ,    /*  0   */
    BITSORT(0b0110000)    ,    /*  1   */
    BITSORT(0b0110000)    ,    /*  2   */
    BITSORT(0b1111001)    ,    /*  3   */
    BITSORT(0b0110011)    ,    /*  4   */
    BITSORT(0b1011011)    ,    /*  5   */
    BITSORT(0b1011111)    ,    /*  6   */
    BITSORT(0b1110010)    ,    /*  7   */
    BITSORT(0b1111111)    ,    /*  8   */
    BITSORT(0b1111011)    ,    /*  9   */
};

ピンが足りない場合

シフトレジスタなどを上手く使うとよいです。ELMさんとこの下記参照。

//実機未検証
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#define  p|=_BV(b);
#define  P&=~_BV(b);
#define SCK  (7)
#define MISO (6)
#define MOSI (5)

uint8_t led_data[8];

ISR(TIMER0_OVF_vect)
{
    // 比較的時間が掛かるので多重割り込み可としています。
    // ただしsbi/cbiマクロの途中に割り込まれると問題がありますので
    // ここだけ割り込み禁止にしています。
    static uint8_t led_bank;
    uint8_t data,i;
    led_bank++;
    if (led_bank>8) 
        led_bank=0;
    //led_bankは0〜7を繰り返す
    data = led_data[led_bank];
    for (i=0;i<8;i++)
    {
        cli(); cbi(PORTB,SCK); sei();
        if (data&0x80) 
        {
            cli(); sbi(PORTB,MISO); sei();
        }
        else 
        {
            cli(); cbi(PORTB,MISO); sei();
        }
        data = data<<1;
        cli(); sbi(PORTB,SCK); sei();
    }
    if (led_bank==0)         // ラッチ時ここがLoだと74HC164にLoが入力される
    {
        cli(); sbi(PORTB,MISO); sei();   // 164のpin=11111110でbank0だけがOnになる
    }
    else                     // HiだとHiが入力される。例えばled_bank=1なら、
    {
        cli(); cbi(PORTB,MISO); sei();   // 164のpin=11111101となり、bank1がON
    }
    cli(); sbi(PORTB,MOSI); sei();       // 595 Latch / 164 shift
    cli(); cbi(PORTB,MOSI); sei();
}

prog_char digit_pattern[] PROGMEM = 
{
    0x7D,0x18,0x6E,0x3E,0x1B,
    0x37,0x77,0x1C,0x7F,0x3F
};//0123456789
#define minus_pattern (0x01)

void putc_7seg(uint8_t i , char c)
{
    if (c=='-') 
        c = minus_pattern;
    else if (c>='0' && c<='9') 
        c = pgm_read_byte(digit_pattern+(c&15));
    else
        c = 0;
    led_data[i] = c;
}
void clr_7seg(void)
{
    uint8_t i;
    for (i=0;i<8;i++) led_data[i]=0;
}
void putstr_7seg(char *s)
{
    uint8_t i;
    for (i=0;i<8;i++)
        putc_7seg(i,*s++);
}
#define set_dp(i)  led_data[i] |= 0x80;

int main(void)
{
    TCCR0 = 3; // overflow each 1.6msec @ 10MHz
    TIMSK |= (1<<TOIE0);  //enable Timer0  overflow
    sei();
    putstr_7seg("1234 -567");
    set_dp(1);
    // 7segLEDには"1234 -56.7"が表示される・・・はず.
    for (;;);
}

ダイナミック点灯用のトランジスタ

詳しくはトランジスタの使い方の解説本、解説サイトを参照(なひたふ新聞の「電子回路の豆知識」→「個別半導体の豆知識」→「スイッチング1・スイッチング2」などおすすめ)

ベース抵抗を内蔵している便利なトランジスタがあります。上記のELMさんのISPコネクタ利用の7segLED点灯回路にも使われていますが、RN1221(NPN)/RN2221(PNP)など。サトー電気などで通販されています。データシートはググれば出てきます。普通のトランジスタと異なり、ベースに掛ける電圧とコレクタ電流の関係の特性図も出ていますのでわかりやすかったりします(笑)

  • 1201/2201シリーズは、ベースに5V掛けたときのVce=0.2V条件でのコレクタ電流が30mA程度なので、LEDダイナミック点灯には向かない模様。1221/2221だと同条件で300mA以上OKです。3.3Vの時はベース抵抗が小さい1225/2225が適当?
  • 一般に「抵抗内蔵型トランジスタ」「デジタルトランジスタ」と呼ばれています。今知ったのですが、これはロームの特許らしいです。

モーターなど大電流機器