自動水やり器の製作:ソフトウェア

全ソースコード

autofeeder.ino

開発環境

 ソフトウェア開発は、Raspberry Pi 上の Arduino IDE (1.8.3) 上で行った。Arduino 上で ATtiny の開発を行う設定方法については「ラズパイで AVR プログラミング」を参照。

仕様

ヒューズの設定

ヒューズの設定は下の通りとする。8 MHz のクロックを8分周して、1 MHz クロックで動作させる。

Low: 01101111
     ||||++++-- CKSEL[3:0] システムクロック選択 (111: 外部クリスタル、>=8MHz)
     ||++-- SUT[1:0] 起動時間
     |+-- CKOUT (1:PB0にシステムクロックを出力しない)
     +-- CKDIV8 クロック分周初期値 (1:1/1, 0:1/8)
$ sudo avrdude -c linuxgpio -p attiny2313 -U lfuse:w:0x6f:m

設計

1. 7セグメント LED 2桁のダイナミック点灯

 Timer0 を CTC モードで使って、10 msec ごとに割り込みをかける。割込みが起きたら、表示する桁を1桁目・2桁目で交互に設定し、表示する桁のコモンアノードを "1" にして、光らせるセグメントのポートを "0" にする。

 割り込み設定部分(抜粋)。

#define TIMER0_MAX 159      //  1usec * 64* 160 = 10.2 msec

void setup()
{
  ...
  //  Set timer0
  //  WGM02..WGM00 = 0b010 (CTC mode with OCR0A)
  TCCR0A = (1 << WGM01);
  TCCR0B = (1 << CS01) | (1 << CS00);  //  CTC mode, clock source = clk/64
  TCNT0 = 0;                 //  Reset timer
  OCR0A = TIMER0_MAX;        //  Interrupt every 10 msec
  ...
}

 割り込み処理部分(抜粋)。

unsigned char timer0Count;
unsigned char timerInvoked;

unsigned char digits;   //  Bits 7:4 and 3:0 for LED2/1
unsigned char dpoints;  //  Bits 1 and 0 for LED2/1

unsigned char patTable[12] = {
 A | B | C | D | E | F,     /* 0: ABCDEF- */
 B | C,                     /* 1: -BC---- */
 A | B | D | E | G,         /* 2: AB-DE-G */
 A | B | C | D | G,         /* 3: ABCD--G */
 B | C | F | G,             /* 4: -BC--FG */
 A | C | D | F | G,         /* 5: A-CD-FG */
 A | C | D | E | F | G,     /* 6: A-CDEFG */
 A | B | C | F,             /* 7: ABC--F- */
 A | B | C | D | E | F | G, /* 8: ABCDEFG */
 A | B | C | D | F | G,     /* 9: ABCD-FG */
 G,                         /* -: ------G */
 0                          /* blank */
};

//  Interrupt handler (timer0 CTC)
ISR(TIMER0_COMPA_vect)
{
  timerInvoked |= 4;
  timer0Count++;
}

   ...
   if (timerInvoked & 4) {
      unsigned char m, dp;
      timerInvoked &= ~4;
      n = timer0Count % 2;
      PORTD |= 0b01111100;
      PORTB |= 0b00000111;
      if (n == 0) {
        PORTB &= ~ANODE2;
        PORTB |= ANODE1 | 0b00000111;
        m = (digits >> 4) & 0x0f;
        dp = (dpoints >> 1) & 1;
      } else {
        PORTB &= ~ANODE1;
        PORTB |= ANODE2 | 0b00000111;
        m = digits & 0x0f;
        dp = dpoints & 1;
      }
      m = patTable[m];
      dp = (dp ? DP : 0);
      PORTD &= ~(((m >> 1) & 0b01111100) | dp);
      PORTB &= ~(m & 0b00000111);
    }
    ...

2. タイマー部分の実装

 Timer1 を CTC モードで使って 1 sec をカウントする。「生きている」ことを示すため、LED を「ON: 0.4 秒、OFF: 0.6 秒」のタイミングで点滅させる。これは、同じ Timer1 の OCR1B 割込みで実現する。

 割込み設定部分(抜粋)。

void setup()
{
  ...
  //  Set timer1
  //  WGM13..WGM10 = 0b0100 (CTC mode with OCR1A)
  TCCR1A = 0;     
  TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10);  //  CTC mode, clock source = clk/64
  TCNT1 = TIMER1_MAX - 1;    //  Reset timer
  OCR1A = TIMER1_MAX;        //  Interrupt every 1 second
  OCR1B = TIMER1_INT;
  
  //  Enable OCR0A, OCR1A and OCR1B interrupt
  TIMSK = (1 << OCIE0A) | (1 << OCIE1A) | (1 << OCIE1B);
  ...
}

 1秒ごとの割込み処理。時刻が timerAMotor より小さければ、リレーを ON にして、モーターを動作させる。また、残り時間によって表示内容を設定する。digit の上位・下位4ビットがそれぞれ LED の1桁目・2桁目に対応している。0〜9はその数字を表示、0xA なら "-" を表示、0xB なら無表示

      unsigned int rem_time;
      PORTD |= 0b00000010;  //  LED on
      //  Wrap the timerA if necessary
      if (timerACount >= timerALimit)
        timerACount -= timerALimit;
      //  Motor on?
      if (timerACount < timerAMotor)
        PORTD |= 0b00000001;
      else
        PORTD &= 0b11111110;;
      rem_time = timerALimit - timerACount;
      if (rem_time >= timerASwitch1) {
        //  Show the remaining hours (4 secs), minutes (1 sec), seconds (1 sec)
        dpoints = 0;
        switch (rem_time % 6) {
          case 0: case 5: case 4: case 3:
            set_digits(rem_time / 3600);
            break;
           case 2:
             set_digits((rem_time % 3600) / 60);
             break;
           case 1:
            set_digits(rem_time % 60);
            break;
        }
      } else if (rem_time >= timerASwitch2) {
        //  Show the remaining minutes (3 sec), seconds (1 sec)
        switch (rem_time % 4) {
          case 0: case 3: case 2:
            set_digits(rem_time / 60);
            break;
           case 1:
            set_digits(rem_time % 60);
            break;
        }
        dpoints = (rem_time % 2);
      } else {
        dpoints = 1;
        set_digits(rem_time);
      }

 OCR1B の割込み処理。上の処理で LED が点灯しているので、それを消す。ただし、モーターが ON の間は、点灯しっぱなしにする。

      dpoints = 0;
      if (timerACount >= timerAMotor)
        PORTD &= 0b11111101;  //  LED off

3. スリープモードの設定

set_sleep_mode(), sleep_enable(), sleep_cpu(), sleep_disable() を使う。「電流電圧ロガーの製作:(2) 割り込み処理」と全く同じなので、そちらをご参照ください。