電子工作

ニキシー管時計のAVR-gccソースコード

本体は内部RCで1 MHzで動作し、時計用タイマは外部クロック端子につながっている時計用水晶振動子の32.768 kHzで動作させています。24時間制です。

時計合わせ用にボタン2つ(←と↑)がついています。←を押すと順に1分の位、10分の位、1時間の位、10時間の位が点滅して、その位の数字が調整できます。↑を押すと、点滅している桁の数字が1つ増えます。10秒なにもボタンを押さないと元にもどります。


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

#define TIMEOUT 10//config timeout
#define S_IDOL 0 //idol state
#define S_ONEMIN 1
#define S_TENMIN 2
#define S_ONEHOUR 3
#define S_TENHOUR 4

volatile uint8_t sec = 0;//0-60
volatile uint16_t min = 0;//0-60*(24 + 1)
volatile uint8_t confsec = 0;//0-10+alpha
volatile uint8_t state = S_IDOL;//state
volatile uint8_t red_last = 1;
volatile uint8_t white_last = 1;

int8_t display(uint16_t num);

ISR(TIMER2_OVF_vect){ //int. per 1 sec
  //count and set time
  sec++;
  if (sec >= 60){
    min++;
    sec = 0;
    if (min >= 60*24){
        min = 0;
    }
  }

  display((min / 60)*100 + min % 60);

  //config timeout
  confsec++;
  if(confsec > TIMEOUT){
    state = S_IDOL;
    confsec = 0;
  }

}


ISR(TIMER2_COMPA_vect){ //int. per 1 sec + 1/2
    //turn off -> unavailable 74141
    if(state == S_ONEMIN){
        //PB0,PD7
        PORTB |= (1 << PORTB0);
        PORTD |= (1 << PORTD7); //| (1 << PORTD6) | (1 << PORTD5));
    }else if(state == S_TENMIN){
        //PB1,2
        PORTB |= ((1 << PORTB1) | (1 << PORTB2));
    }else if(state == S_ONEHOUR){
        //PD0,1
        PORTD |= ((1 << PORTD0) | (1 << PORTD1));
    }else if(state == S_TENHOUR){
        //PC0,1
        PORTC |= ((1 << PORTC0) | (1 << PORTC1));
    }

}


ISR(PCINT1_vect){
  _delay_ms(10);
  uint8_t red_now = PINC & (1 << PINC4);
  uint8_t white_now = PINC & (1 << PINC5);

  if ((white_last != 0) & (white_now == 0)){//white pull down
    //shift state
    state++;
    if(state > S_TENHOUR){
        state = S_IDOL;
    }
    confsec = 0;
  }
  white_last = white_now;

  if ((red_last != 0) & (red_now == 0)){//red pull down
    confsec = 0;
    if(state == S_ONEMIN){
        min++;
        if(min % 10 == 0){
            min -= 10;
        }
    }else if(state == S_TENMIN){
        min += 10;
        if(min % 60 < 10){
            min -= 60;
        }
    }else if(state == S_ONEHOUR){
        min += 60;
        if((min / 60) / 10 < 2){
            if((min / 60) % 10 == 0){
                min -= 60*10;
            }
        }else{
            if((min / 60) % 10 == 4){
                min -= 60*4;
            }
        }
    }else if(state == S_TENHOUR){
        min += 60*10;
        if((min / 60) % 10 >= 4){
            if((min / 60) / 10 == 2){
                min -= 60*20;
            }
        }else{
            if((min / 60) / 10 == 3){
                min -= 60*30;
            }
        }
    }

    display((min / 60)*100 + min % 60);
  }
  red_last = red_now;
}

int8_t display(uint16_t num){
  uint8_t one = num % 10;
  uint8_t ten = (num / 10) % 10;
  uint8_t hund = (num / 100) % 10;
  uint8_t thou = (num / 1000) % 10;

  //4one DCBA -> PB0,PD7,6,5
  //3ten DCBA -> PB1,2,3,4
  //2hundDCBA -> PD0,1,2,3
  //1thouDCBA -> PC0,1,2,3
  PORTB = (((ten & 0b0001)?1:0) << 4)
    | (((ten & 0b0010)?1:0) << 3)
    | (((ten & 0b0100)?1:0) << 2)
    | (((ten & 0b1000)?1:0) << 1)
    | (((one & 0b1000)?1:0) << 0);

  PORTC = (((thou & 0b0001)?1:0) << 3)
    | (((thou & 0b0010)?1:0) << 2)
    | (((thou & 0b0100)?1:0) << 1)
    | (((thou & 0b1000)?1:0) << 0)
    | (1 << PORTC4)
    | (1 << PORTC5);

  PORTD = (((hund & 0b0001)?1:0) << 3)
    | (((hund & 0b0010)?1:0) << 2)
    | (((hund & 0b0100)?1:0) << 1)
    | (((hund & 0b1000)?1:0) << 0)
    | (((one & 0b0001)?1:0) << 5)
    | (((one & 0b0010)?1:0) << 6)
    | (((one & 0b0100)?1:0) << 7);

  return 0;
}

int8_t init(void){

//pc4(PCINT12) red, pc5(PCINT13) white
  DDRB = 0xFF;
  DDRC = 0b11001111;
  DDRD = 0xFF;
  PORTB = 0x00;
  PORTC = 0b00110000;
  PORTD = 0x00;


  ASSR   = 0b00100000; // use crystal.
  TCCR2B = 0b00000101; // 128 prescaler, timer on; 1 sec
  //TCCR2B = 0b00000100; // 64 prescaler, timer on; 1/2 sec
  //TCCR2B = 0b00000011; // 32 prescaler, timer on; 1/4 sec

  TIMSK2 |= (1 << TOIE2) | (1 << OCIE2A); // overflow and A interrupts on
  OCR2A = 0x80;

  //interrupt.
  PCICR |= (1 << PCIE1);
  PCMSK1 |= (1 << PCINT12) | (1 << PCINT13);//PCMSK1 = 0x30

  GTCCR  = 0b00000010; //timer reset
  sei(); // allow interrupts

  return 0;
}

int main(void){
  init();

  while(1){

  }
  return 0;
}

AVRのIOピンに押しボタンスイッチで入力する

スイッチが押されると、LEDがON/OFFとトグルする簡単なコードを書きました。

スイッチはPC5, LEDはPD4に接続してあります。
PC5はプルアップしてあり、スイッチによりGNDに短絡されると、割り込み(PCINT1_vect)が入り、PD4の出力がXOR演算でトグルされます。
初期化では、ポートのIN/OUT、1/0の設定、割り込み関係のレジスタの設定がなされます。

ポイントは、スイッチのチャタリングをソフトウェアで吸収することです。
スイッチを押したり/離したりするときのバタつきが納まるまで10ms待ちます。さらに、押している最中のバタつきを無視するために、「前は離されていて、今は押されている」というときのみボタンが押されたと判定します。押している最中のバタつきは、「前は押されていて、今は押されている」という状況になり、排除されます。
今回のように「押したらLEDをトグルする」という処理をするときにはこのような判定が必要ですが、「押している間だけLEDをON」という処理ならば、チャタリングで一瞬LEDがOFFになっても気づかないので、必要ありません。


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

volatile int8_t last;

SIGNAL(PCINT1_vect){
  _delay_ms(10);
  int8_t now = PINC & (1 << PINC5);
  if ((last != 0) & (now == 0)){
    PORTD ^= (1 << PORTD4);
  }
  last = now;
}


int main(void){
  //button: pc5(PCINT13)
  //led: pd4
  DDRB = 0xFF;//direction 1 out, 0 in
  DDRC = 0b11011111;//in: pc5
  DDRD = 0xFF;
  PORTB = 0x00;//off
  PORTC = (1 << PORTC5);//pullup: pc5
  PORTD = 0b00010000;

  last = 1;

  ///INT.
  PCICR |= 1 << PCIE1;//allow int. for pin group 1(8-14)
  PCMSK1 |= (1 << PCINT13);//PCMSK1 = 0x30

  sei(); // allow interrupts

  while(1){
  }
  return 0;
}

割り込みを有効にする前にlast = 1;としないと、ボタンを初めて押した時にボタンが効きません。初期化と同時に代入するのでも良いです。

AVRライタ usbaspをツェナーダイオードなしで、atmega328pと青色LEDでつくる

f:id:beiz23:20140113221601j:image:w360
久しぶりにAVRに書き込みをすることに。winavrを入れたら開発環境が全部入っていてすぐ書き込みができるようになりました。(昔秋月で買ったatmega88vが220円の袋に入っていましたが、今ググったら150円に値下がりしていました。)

しかし、この写真の、昔作ったAVRライタはFT232RL(USBシリアル変換)+ponyser(シリアルAVRライタ)USBaspを作りました。

USBasp以外にもいろいろ書き込みハードやソフトがあるようですが、個人で作成されているためそちらは触る気がおきませんでした。オープンソースでだれでも開発に参加できる、というふうではなくてオリジナルの開発者が自分のホームページで公開しているというだけだと、その人の仕事が忙しくなったとかで保守ができなくなることはよくありますので。

そういう意味では、USBaspはGNUGPLv2のもとファームソースが公開されているUSB AVRライタですし、デファクトスタンダード(と私は勝手思っている)でこちらもGNUGPLv2な書き込みソフトavrdudeから使えるというのは良いです。avrdudeはwindowsからでも使えますし、ubuntu gnu/linuxならapt-getでインストールできるのも楽です。さらにavrdudeは、WinAVRgoogle:winavrに標準で入っていて、programmers notepadという開発環境からAVR studio風にボタンひとつでコンパイル・ダウンロード(orプログラム)ができます。

さて、手元の部品で作成するにあたり3.3Vツェナー(Zener)ダイオードがありませんので、代わりに青色発光ダイオードの順方向電圧を使うことにしました。そんな電圧が不安定そうな(I-V特性が悪そうな)ものでよいのか、と思いましたが(定電圧)ツェナーダイオードのデータシートを読むと発光ダイオードの順方向とたいして不安定さは変わらず、十分代用できるように思えます。
その話題はここでも議論されています。http://www-ice.yamagata-cit.ac.jp/ken/senshu/sitedev/index.php?AVR%2Fnews25a#nc0d3993
実際に青色ダイオード(型番不明2種類)のI-V特性を測定してみますと、こんな感じでした。(なお緑色ダイオードは多くが~2.0 Vでしたが、一種類だけ青色ダイオード相当の3.0 Vのものがありました。)

@100 Ohm
Vin Vout
テープ青led
5.30 3.355
5.0 3.319
4.7 3.263
4.48 3.235
4.24 3.173
4.03 3.143
3.77 3.084
3.50 3.013
3.21 2.919
3.02 2.840
2.87 2.762
2.44 2.42
0.916 0.88

3mm青
2.796 2.731
3.036 2.866
3.280 2.989
3.562 3.088
3.979 3.206
4.365 3.295
4.548 3.320
4.761 3.359
5.020 3.401
5.377 3.459

f:id:beiz23:20140120161834p:image:w360
例えばルネサスの定電圧ダイオードのデータシートはこちらにありましたが、3.0 Vのダイオードで比較的リニアーなI-V特性のところを読むと、2 mA@3.0 V, 10 mA@3.5 V -> dV/dI = 0.063 mA で、LEDとくらべてたいして変わりません。
http://japan.renesas.com/products/discrete/diode/constant/Documentation.jsp

USBスレーブコントローラとしてAVRのatmega88vを12MHzにオーバークロックして使うつもりでいましたが、ヒューズビット(Hfuse)0xddをGUI経由で書き込みしたところ、GUIソフトが十六進表記を理解せず、Hfuse最上位ビットがプログラム(0)されてしまい、RESETピンがIOピンになってしまいました。高電圧パラレルライタを持っていないので、つまりatmega88vは死んでしまいました。
他にはatmega328pという大容量版があったのでこれを使うことにしました。88用のファームを書いたところ、動作しませんでした。そこで、ファームウェアをソースコードから384p用にコンパイルしなおしました。Makefileのターゲットをatmega328にしてmakeしただけです。

TARGET=atmega328
HFUSE=0xdd
LFUSE=0xff

そしてhfuse, lfuseは88と同じはずなので、これも0xdd, 0xffと書き込みました。
作成されたmain.hexを書き込んだところ、無事windowsに認識されました。
青色ダイオードの電圧を測ると、5 VをAVRがかけた時も、PCから3.3 Vが入った時も、~3.0 Vとなっており、ツェナーダイオードの代わりとして無事使えることが分かりました。

なお抵抗は68Ω ->100Ωとしています。2.2 kオームプルアップ抵抗は、1.5 kΩが正しいのでは?と思いますが、どちらもないので、1 kΩを差しています。3.3 kΩを並列するのが良いかもしれません。プルアップが強すぎると、データラインをGNDに落とすときに負荷が大きくなりますので。
クロックについては、USBはNRZIを情報の符号方法に使っていて、わりと正確なクロックが必要なので、10年前にデジットで買った水晶をさしています。
RESETピンのプルアップは、データシートを見ると実は内部プルアップされているので、外部でわざわざつけなくてもよいと思う。
f:id:beiz23:20140118112425j:image:w360
プルアップされている方のデータラインの青色ダイオードが強く光っています。
f:id:beiz23:20140118113331p:image:w360

 

基板に載せました。
f:id:beiz23:20140118204306j:image:w360
余っている蛇の目基板がなかったので、すでに使っている基板の端を割って使いました。

これをPCに差すと認識します。
しかし、avrdude 5.1で使おうとすると、このようなエラーが出て動いてくれません。

error: could not find USB device "USBasp" with vid=0x16c0 pid=0x5dc

どうもwindows の64 bit系のOSだと、署名されていないドライバはそのままでは使えないようです。そこでここで書かれているように、PCの再起動をしてwindows起動中にF8を押して出てくるメニューのところで、「ドライバの署名を強制しない」を選びました。
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=788666
そしたら無事使えるようになりました。
しかし、再起動すると無効になるので、起動のたびにこれをしないといけないのがめんどい。ということで、ドライバを作りなおしました。
最新のlibusb-win32をダウンロードして(ver. 1.2.6) usbaspを接続した状態で、bin/inf-wizard.exeを実行します。usbaspの装置情報を読み取って、勝手にinfファイルを作ってインストールしてくれます。これだと署名が無効といわれず、無事使うことができます。
f:id:beiz23:20140118205628p:image:w360

 trying to connect to device...

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.04s

avrdude: Device signature = 0x1e950f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed

         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "C:\data\ele\avr\tmp\led.hex"
avrdude: writing flash (934 bytes):

Writing | ################################################## | 100% 4.48s



avrdude: 934 bytes of flash written
avrdude: verifying flash memory against C:\data\ele\avr\tmp\led.hex:
avrdude: load data flash data from input file C:\data\ele\avr\tmp\led.hex:
avrdude: input file C:\data\ele\avr\tmp\led.hex contains 934 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 4.05s



avrdude: verifying ...
avrdude: 934 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.

ターゲットのCLKが1MHzなので、slowモードで書き込みしていますが1 kB を、5 sec程度で読み書きできています。この速度だったら、いつもslowモードでも良さそうな気もする…

ニキシー管とAVRをつなげました。


AVRにシチズン製の時計用水晶をつなげて、時間をカウントするコードを書き込みました。AVRとニキシー管をつなげて表示させてみました。無事光っています。家にある部品だけを新古問わず切り貼りして作ったので大変でした。表示のテストが終わったらとりあえず時計か温湿度計にでもしようかと思いますが、数字が頻繁に変わるほうが面白いと思うので他になにか応用はないかなあと考えています。

配線など。

11本足 x 4個 = 全44ピンなので、ごちゃごちゃです。

基板はこの三層構造+電源がさらにこの下につきます。

12Vin, 200 V 8mAout and 5 V ?mAout 電源

スタティックにニキシー管IN-17をドライブしています。ニキシー管ドライバK155ID(74141互換)を4つ差しています。このICの耐電圧は仕様書によると60 Vです。ということは、2SA1015, 2SC1815の耐電圧と同程度ですので、このドライバが無くても汎用トランジスタを使えば良さそうです。60 Vを超えた電圧のときに、このICの内部トランジスタorツェナーダイオードは一次降伏しますが、電源が200 Vだとすると、ICで60 V消費され、残り140Vとなって、無事ニキシー管は消灯する、ということのようです。