AVRtiny13Aでつくるシリーズ3 TCXOを使って時計を作る
- 2016/03/31 12:04
- カテゴリー:make:
さて,今回はCPUのクロックに外部のTCXOで作った10MHzを突っ込み,これをもとに時計を作るという話です。少し前に,わざわざSi5351で32.678kHzを生成して時計を作ったという話を書きましたが,そんなことをしなくてもTCXOの時計を作る事は出来るんじゃないかという発想を,実際にやってみたという話です。
そのTCXOの周波数ですが,別に10MHzでなくてもいいんじゃないのと思われると思いますが,そうは問屋が卸しません。
なにせtiny13Aです,最終的に1秒を作る事の出来る外部クロックは,限られます。ここでは手持ちの関係もあり,10MHzのTCXOを使う事にします。
原理は簡単で,以前の32.768kHzの時計と全く同じです。ただ,CPUクロックを使いますので,プリスケーラを通せます。
まず10MHzをプリスケーラで1/64します。そうすると156250Hzになるのですが,これをカウンタで数えます。一定の数になったら割り込みを発生させますが,割り込みの発生が整数回数でなければ,1秒を作れません。
そこで,数える数を250とします。すると1秒間に625回の割り込みが発生します。割り込み処理で変数をインクリメントし,625になったら1秒です。あとは普通に作ればいいだけです。
変数はint型になりますし,16ビットの加算になるのでコードも大きくなりますが,処理時間としてはこのくらいの内容なら,まあ大丈夫でしょう。
LCDは,引き続きデジットの8桁14セグのものです。
//
// TCXO Clock for tiny13
// LCD : Digit 14seg type
// Clock : 10MHz external
//
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#define F_CPU 10000000UL
#define LE _BV(PB0) // _LE
#define DIN _BV(PB2) // DIN
#define SCK _BV(PB3) // SCK
#define MIN_SW PINB4
#define HOUR_SW PINB5
unsigned char tx_buf[18];
unsigned char digit_buf[8];
const PROGMEM int seg_pat[] = {
// 0 1 2 3 4 5 6 7 8 9
0x003f, 0x1200, 0x00db, 0x00cf, 0x00e6, 0x00ed, 0x00fd, 0x1401, 0x00ff, 0x00ef,
// SPC .
0x0000, 0x1000};
const PROGMEM unsigned char bit_pos0[14] = {
86, 85, 84, 83, 82, 80, 81, 1, 0, 6, 5, 4, 3, 2};
void LCD_Disp(void);
void LCD_Clear(void);
void LCD_Convert(void);
volatile unsigned int clk = 0;
volatile unsigned char sec = 0;
volatile unsigned char min = 0;
volatile unsigned char hour = 0;
unsigned char mode = 0;
// Timer0 interrupt CTC mode(COMPA Interrupt)
ISR(TIM0_COMPA_vect)
{
clk++;
if (clk == 625){
clk = 0;
sec ++;
}
if (sec == 60){
sec =0;
min++;
}
if (min == 60){
min = 0;
hour++;
}
if (hour == 24){
hour = 0;
}
}
int main(void)
{
DDRB = 0b11001111; // PB4 = Input
PORTB = 0b00110000; // PB4 = pull-up
TCCR0A = 0b00000010; // CTC mode
TCCR0B = 0b00000011; // input = Internal /64
OCR0A = 249; // COMPA = 250 - 1
TIMSK0 = 0b00000100; // COMPA interrupt Enable
digit_buf[2] = 11;
digit_buf[5] = 11;
sei();
while(1){
if (bit_is_clear (PINB, MIN_SW) && bit_is_clear (PINB, HOUR_SW)){
_delay_ms(20);
if (bit_is_clear (PINB, MIN_SW) && bit_is_clear (PINB, HOUR_SW)){
sec = 0;
clk = 0;
}
}
else if (bit_is_clear (PINB, MIN_SW)){
_delay_ms(20);
if (bit_is_clear (PINB, MIN_SW)){
min++;
}
}
else if (bit_is_clear (PINB, HOUR_SW)){
_delay_ms(20);
if (bit_is_clear (PINB, HOUR_SW)){
hour++;
}
}
digit_buf[6] = sec /10;
digit_buf[7] = sec %10;
digit_buf[3] = min /10;
digit_buf[4] = min %10;
digit_buf[0] = hour /10;
digit_buf[1] = hour %10;
LCD_Clear();
LCD_Convert();
LCD_Disp();
}
}
void LCD_Disp(void)
{
int i, j;
unsigned char k;
for (i=142 ; i>=0 ; i--){
PORTB &= ~SCK; //SCK = Low
j = i/8;
k = i%8;
if ((tx_buf[j] & (1<<k)) != 0)
{
PORTB |= DIN;
}
else
{
PORTB &= ~DIN;
}
_delay_us(1);
PORTB |= SCK; //SCK =High
_delay_us(1);
}
_delay_us(1);
PORTB |= LE;
_delay_us(1);
PORTB &= ~LE;
}
void LCD_Clear(void)
{
unsigned char i;
for (i=0 ; i<=17 ; i++) {
tx_buf[i] = 0;
}
}
void LCD_Convert(void)
{
int p, r, s;
int pattern, tx_bit_pos;
unsigned char digit_num, seg_num;
for (digit_num=0 ; digit_num<=7 ; digit_num++){
pattern = pgm_read_word(&seg_pat[digit_buf[digit_num]]);
for (seg_num=0 ; seg_num<=13 ; seg_num++){
p = pattern & 1; // LSB of pattern
pattern /= 2; // shift right
tx_bit_pos = pgm_read_byte(&bit_pos0[seg_num]);
tx_bit_pos += digit_num * 7;
r = tx_bit_pos / 8;
s = (1<<(tx_bit_pos % 8)) * p;
tx_buf[r] = ((tx_buf[r])& ~s) | s;
}
}
}
今回はなにも面白い事はないのですが,OCR0Aに設定する値を最初250にしていて,1時間で数秒遅れるという事態が発生してから,ここは1つ少ない249にしないといけないと気が付きました。0から始まるカウンタなので,249でクリアしないとまずいんですね。
それと,後々LMT01と組み合わせること考えて,コンパレータ入力端子を開けておきます。クロック入力に1本,LCDに3本必要ですので,RESETを潰さないとスイッチを2つ用意出来ません。
当初1ボタンで進める事も考えたのですが,操作がややこしいので2ボタンにしました。その結果RESETを潰すことにしたので,書き込み後にヒューズビットで,RESETを向こうにする設定を書き込みます。
そうすると,次からISPでプログラム出来なくなるので,もしプログラムにバグが見つかったら,高圧書き込みを行うか,ヒューズリセッタを使うか,あるいは捨てるかという選択を迫られます。
なので,バグはきちんと潰しておきましょう。
そうそう,もう1つ注意があります。10MHzのTCXOに秋月で売っているTG-5021CEを使ったのですが,これに限らずTCXOの多くは,出力が1Vp-p程度のなんちゃって正弦波です。
これをCMOSレベルに変換しないと動かないばかりか,中途半端な電位が印加され続けるので,もしかすると壊れるかも知れません。
変換の仕方はいろいろありますが,私の場合は簡単に,トランジスタ1個を使って変換しました。本当はシュミットトリガで綺麗な矩形波にしないといけないのですが,それは面倒だったのでレベル変換をしただけで突っ込みます。
さて,次はですね,I2CでキャラクタLCDを動かしてみる,です。簡単に思ったのですが,ドツボにはまり,胃に穴が開くほど悩みました。