Hatena::ブログ(Diary)

へにゃぺんて@日々勉強のまとめ

2012-03-24

[] AVRマイコンデジタル時計をLEGOに収めた

前回、ユニバーサル基板に実装したAVRマイコンデジタル時計をLEGOのケースに収めました。

材料

完成写真

f:id:cupnes:20120324191359j:image

とても簡単なものですが、

やっぱりLEGOだと、おもちゃらしい雰囲気が出て良く・・ないですか?

おまけ

f:id:cupnes:20120318233957j:image

せっかくなので、

付属のマニュアルに書いてあったのも作ってみました。

2012-03-20

[] 7セグ時計をATMEGA168Pに対応

前回まででは、ATMEGA168を使用していました。

ですが、今は「ATMEGA168P」のほうが主流な様で、秋月電子でも千石通商でもATMEGA168ではなくATMEGA168Pを取り扱っているようです。


予備として買ってあるのもATMEGA168Pなので、これに対応させておきたいです。


ソースコード

そんなわけで、以下のようにプログラムを修正してみました。

/***********************************
デジタル時計 V3.0 (2012/03/18 11:55)

ATMEGA168P対応
割り込み周波数を1にして、
タイマ割り込み自体が1秒ごとに発生するように設定してみた。
ちょっとみたところうまくいっているようではあるが、
未だ厳密に調べてはいない。

参考:
AVR タイマー実験
http://www9.plala.or.jp/fsson/NewHP_elc/AVR/Avr_Tmr0_OVF.html

ユアネーム・7セグ・12セグフォント大全集
http://www.yourname.jp/soft/digitalfonts-20090306.shtml

WinAVR付属のドキュメント
C:/WinAVR-20100110/doc/avr-libc/avr-libc-user-manual/group__avr__interrupts.html
************************************/

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

#define BTN_TH 20
#define F_CPU 1000000	/* CPUクロック周波数[Hz] */
#define PITCH 1	/* 割り込み周波数[Hz] */
#define PRESC 64	/* プリスケーラ値 (分周比) */
#define INTRCOUNT (0x10000 - ((F_CPU / PRESC) / PITCH))

unsigned int led_seg[4] = {0x00, 0x00, 0x00, 0x00};
unsigned char hour = 0, min = 0, sec = 0, cnt = 0, on_dp = 1;

ISR (TIMER1_OVF_vect)
{
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */

	on_dp = !on_dp;
	sec++;
	if (sec > 59) {
		sec = 0;
		min++;
		if (min > 59) {
			min = 0;
			hour++;
			if (hour > 23)
				hour = 0;
		}
	}

/*	cnt++;
	if (cnt > 99) {
		cnt = 0;
		on_dp = !on_dp;
		sec++;
		if (sec > 59) {
			sec = 0;
			min++;
			if (min > 59) {
				min = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			}
		}
	}*/
}

void timer1_init(void)
{
	TCCR1A = 0;	/* TCCR1A タイマモード */
	TCCR1B = 3;	/* プリスケーラ (64分周) */

	TIFR1 |= (1 << TOV1);	/* オーバーフローフラグをクリア */
	TIMSK1 |= (1 << TOIE1);	/* オーバーフロー割り込み許可 */
}

const unsigned int to_num_pattern[10] = {
	0xfc,
	0x60,
	0xda,
	0xf2,
	0x66,
	0xb6,
	0xbe,
	0xe0,
	0xfe,
	0xf6};

void scan(void)
{
	static unsigned char line = 0;

	PORTD = led_seg[line];

	DDRB = 1 << line;
	line = (line + 1) % 4;
}

void display(unsigned long num)
{
	led_seg[3] = to_num_pattern[(num / 1000) % 10];
	led_seg[2] = to_num_pattern[(num / 100) % 10];
	if (on_dp)
		led_seg[2] |= 0x01;
	led_seg[1] = to_num_pattern[(num / 10) % 10];
	led_seg[0] = to_num_pattern[num % 10];
}

int main(){
	unsigned char btn_cnt[4] = {0};

	/* ポートのデータ方向(入力/出力)設定 */

	DDRB = 0x00;
	DDRC = 0x00;
	DDRD = 0xff;

	/* ポートの初期値設定 */

	PORTB = 0x00;
	PORTC = 0xff;
	PORTD = 0x00;



	timer1_init();

	sec = 0;
	min = 0;
	hour = 0;
	cnt = 0;
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */
	sei();



	while (1) {
		scan();

		/* M-- についてのチェック */
		if ((PINC & 0x01) == 0) {
			if (btn_cnt[0] > BTN_TH) {
				btn_cnt[0] = 0;
				cnt = 0;
				sec = 0;
				min--;
				if (min == 255)
					min = 59;
			} else {
				btn_cnt[0]++;
			}
		} else {
			btn_cnt[0] = 0;
		}

		/* M++ についてのチェック */
		if ((PINC & 0x02) == 0) {
			if (btn_cnt[1] > BTN_TH) {
				btn_cnt[1] = 0;
				cnt = 0;
				sec = 0;
				min++;
				if (min > 59)
					min = 0;
			} else {
				btn_cnt[1]++;
			}
		} else {
			btn_cnt[1] = 0;
		}

		/* H-- についてのチェック */
		if ((PINC & 0x04) == 0) {
			if (btn_cnt[2] > BTN_TH) {
				btn_cnt[2] = 0;
				cnt = 0;
				sec = 0;
				hour--;
				if (hour == 255)
					hour = 23;
			} else {
				btn_cnt[2]++;
			}
		} else {
			btn_cnt[2] = 0;
		}

		/* H++ についてのチェック */
		if ((PINC & 0x08) == 0) {
			if (btn_cnt[3] > BTN_TH) {
				btn_cnt[3] = 0;
				cnt = 0;
				sec = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			} else {
				btn_cnt[3]++;
			}
		} else {
			btn_cnt[3] = 0;
		}

		display((unsigned long)(hour * 100 + min));
	}
}

主な変更点は、タイマオーバーフロー割り込みの箇所です。


33行目あたりが、

ISR (SIG_OVERFLOW1)

から、下のように変更しています。

ISR (TIMER1_OVF_vect)

これは、ATMEGA168Pでの書き方で、「SIG_OVERFLOW1」のままだと警告が出ます。


また、オーバーフロー割り込み自体を1秒ごとに発生させて、割り込み周期は1に設定しています(24〜28行目のdefine)。

もともと割り込み周期を100にしていたのは、ATMEGA168のタイマには8ビットのタイマがあるためでした。

今回は16ビットのタイマ(タイマ1)を使用しているので、タイマ/カウンタレジスタ(TCNT1)に1秒分の値を設定できるため、

このように変更しています。

(実は、以前からタイマ1を使用しているため、割り込み周期100の設定はあまり意味はありませんでした。)


所感

ATMEGA168を使用した時ほどの動作確認はしていないのですが、

少し時間の進みが早いような気がします。


といっても、2・3時間動かした限りでは、1分もずれたりはしていませんでした。

2012-03-18

[] AVRマイコンデジタル時計をユニバーサル基板へ実装

先日のブレッドボードデジタル時計ユニバーサル基板上に実装しました。

材料

前回から新たに増えた素材は以下のものです。

できあがり

f:id:cupnes:20120318212952j:image

f:id:cupnes:20120318213002j:image

比較のものを載せていないですが、大きさは

ユニバーサル基板が「Cタイプ(72x48mm)」で、電池の大きさを含めると、

72x55x14mm ほどです。

使い方

ボタンの使い方は下のようになっています。

  • 右の赤色のボタン
1分増加
1分減らす
  • 左の青色のボタン
1時間増加
1時間減らす

ソースコード

実は、7セグ表示器に間違ってカソードコモンの「OSL40562-LRA」をはんだ付けしてしまいました。

回路としてはどちらでも構わないので、プログラムを以下に示すものに変えました。

また、AVR Studioのプロジェクトファイルはこちらです。

/***********************************
デジタル時計

V1.1をカソードコモン対応

参考:
AVR タイマー実験
http://www9.plala.or.jp/fsson/NewHP_elc/AVR/Avr_Tmr0_OVF.html

ユアネーム・7セグ・12セグフォント大全集
http://www.yourname.jp/soft/digitalfonts-20090306.shtml
************************************/

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

#define BTN_TH 20
#define F_CPU 1000000	/* CPUクロック周波数[Hz] */
#define PITCH 100	/* 割り込み周波数[Hz] */
#define PRESC 64	/* プリスケーラ値 (分周比) */
#define INTRCOUNT (0x10000 - ((F_CPU / PRESC) / PITCH))

/* アノードコモンの場合
unsigned int led_seg[4] = {0xff, 0xff, 0xff, 0xff};
*/
unsigned int led_seg[4] = {0x00, 0x00, 0x00, 0x00};
unsigned char hour = 0, min = 0, sec = 0, cnt = 0, on_dp = 1;

ISR (SIG_OVERFLOW1)
{
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */

	cnt++;
	if (cnt > 99) {
		cnt = 0;
		on_dp = !on_dp;
		sec++;
		if (sec > 59) {
			sec = 0;
			min++;
			if (min > 59) {
				min = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			}
		}
	}
}

void timer1_init(void)
{
	TCCR1A = 0;	/* TCCR1A タイマモード */
	TCCR1B = 3;	/* プリスケーラ (64分周) */

	TIFR1 |= (1 << TOV1);	/* オーバーフローフラグをクリア */
	TIMSK1 |= (1 << TOIE1);	/* オーバーフロー割り込み許可 */
}

/* アノードコモンの場合
const unsigned int to_num_pattern[10] = {
	0xff - 0xfc,
	0xff - 0x60,
	0xff - 0xda,
	0xff - 0xf2,
	0xff - 0x66,
	0xff - 0xb6,
	0xff - 0xbe,
	0xff - 0xe0,
	0xff - 0xfe,
	0xff - 0xf6};
*/
const unsigned int to_num_pattern[10] = {
	0xfc,
	0x60,
	0xda,
	0xf2,
	0x66,
	0xb6,
	0xbe,
	0xe0,
	0xfe,
	0xf6};

void scan(void)
{
	static unsigned char line = 0;

	PORTD = led_seg[line];

	DDRB = 1 << line;
	line = (line + 1) % 4;
}

void display(unsigned long num)
{
	led_seg[3] = to_num_pattern[(num / 1000) % 10];
	led_seg[2] = to_num_pattern[(num / 100) % 10];
	if (on_dp) {
		/* アノードコモンの場合
		led_seg[2] &= 0xfe;
		*/
		led_seg[2] |= 0x01;
	}
	led_seg[1] = to_num_pattern[(num / 10) % 10];
	led_seg[0] = to_num_pattern[num % 10];
}

int main(){
	unsigned char btn_cnt[4] = {0};

	/* ポートのデータ方向(入力/出力)設定 */

	DDRB = 0x00;
	DDRC = 0x00;
	DDRD = 0xff;

	/* ポートの初期値設定 */

	/* アノードコモンの場合
	PORTB = 0xff;
	*/
	PORTB = 0x00;
	PORTC = 0xff;
	PORTD = 0x00;



	timer1_init();

	sec = 0;
	min = 0;
	hour = 0;
	cnt = 0;
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */
	sei();



	while (1) {
		scan();

		/* M-- についてのチェック */
		if ((PINC & 0x01) == 0) {
			if (btn_cnt[0] > BTN_TH) {
				btn_cnt[0] = 0;
				cnt = 0;
				sec = 0;
				min--;
				if (min == 255)
					min = 59;
			} else {
				btn_cnt[0]++;
			}
		} else {
			btn_cnt[0] = 0;
		}

		/* M++ についてのチェック */
		if ((PINC & 0x02) == 0) {
			if (btn_cnt[1] > BTN_TH) {
				btn_cnt[1] = 0;
				cnt = 0;
				sec = 0;
				min++;
				if (min > 59)
					min = 0;
			} else {
				btn_cnt[1]++;
			}
		} else {
			btn_cnt[1] = 0;
		}

		/* H-- についてのチェック */
		if ((PINC & 0x04) == 0) {
			if (btn_cnt[2] > BTN_TH) {
				btn_cnt[2] = 0;
				cnt = 0;
				sec = 0;
				hour--;
				if (hour == 255)
					hour = 23;
			} else {
				btn_cnt[2]++;
			}
		} else {
			btn_cnt[2] = 0;
		}

		/* H++ についてのチェック */
		if ((PINC & 0x08) == 0) {
			if (btn_cnt[3] > BTN_TH) {
				btn_cnt[3] = 0;
				cnt = 0;
				sec = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			} else {
				btn_cnt[3]++;
			}
		} else {
			btn_cnt[3] = 0;
		}

		display((unsigned long)(hour * 100 + min));
	}
}

所感

お昼12時から今(22:18)まで動かしてみた時点で、

2分ほど遅れています。

だいたい半日で2分遅れるので、1日で4分ほど遅れる様子です。

時計としては致命的かもしれません。


ですが、「所さんのDAITAI時計」とかをみると、

5分くらいは余裕を持って生活したほうが健康には良さそうです。

(いいわけですか・・・。)

今後やること

プラスチックのケースに収めたいです。

LEGOブロックでケースを作ろうかなと思っています。


追記

2012年3月31日

AVR Studioのプロジェクトファイルを追加しました。

2012-03-03

[]AVRマイコンと7セグメント表示器でデジタル時計

AVRマイコンでデジタル時計を作ってみました。使ってる技術要素としては

くらいで、入門で初めて作るものとしては最適かと。

開発環境

開発環境はこんな感じです。

マイコンATMEGA168-20PU(リンク先はATMEGA168P-20PUですが)
ライタAVRISP mk?
評価ボードATMEGAマイコンボード・キット(液晶付)

ライタのAVRISPがISPで出力するので、それをマイコンへつなぐために、

今回は転がってたマイコンボードキットを使いました。


ソフトウェアの環境は以下のようになってます。

コンパイラなどのセットWINAVR-20100110
統合開発環境AVRStudio 4(Version 4.18)

WINAVRは http://winavr.sourceforge.net/ からダウンロードしてきたものを、

AVRStudioはAVRISPに付属のCDのを使いました。

(これらは、特にどうでもいいですが。)


材料

材料はこんなところです。

AVRマイコンATMEGA168-20PU1個
7セグメント表示器OSL40562-IRA1個
抵抗10Ω4個
タクトスイッチ青色と赤色それぞれ2個ずつ
電池CR20321個
ボタン電池基板取付用ホルダーCR2032用(小型タイプ)1個
ブレッドボードEIC-108J1枚
ジャンパワイヤ-適量

7セグメント表示器は、今回はアノードコモンのものを使いました。

回路図

f:id:cupnes:20120303113912p:image

回路図の作成には、水魚堂ホームページで公開されているBSch3Vを使いました。

BSch3Vのプロジェクトファイルとかはこちらです。


なお、7セグメントLEDの各セグメントへ流れる際にそのセグメントのLEDをいくつ光らせるかで、

マイコンの出力ポートからの電流が2つや6つに分散します。

ですが、今回はトランジスタを用いた電流の増幅をしていないため、

  • 1を表示するときは比較的明るく(セグメントで光らせるLEDが2つ)
  • 8を表示するときは比較的暗い(セグメントで光らせるLEDが7つ)

なことになっています。


ソースコード

/***********************************
デジタル時計 Ver. 1.1 (2012/03/03 09:03)

参考:
AVR タイマー実験
http://www9.plala.or.jp/fsson/NewHP_elc/AVR/Avr_Tmr0_OVF.html

ユアネーム・7セグ・12セグフォント大全集
http://www.yourname.jp/soft/digitalfonts-20090306.shtml
************************************/

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

#define BTN_TH 20
#define F_CPU 1000000	/* CPUクロック周波数[Hz] */
#define PITCH 100	/* 割り込み周波数[Hz] */
#define PRESC 64	/* プリスケーラ値 (分周比) */
#define INTRCOUNT (0x10000 - ((F_CPU / PRESC) / PITCH))

unsigned int led_seg[4] = {0xff, 0xff, 0xff, 0xff};
unsigned char hour = 0, min = 0, sec = 0, cnt = 0, on_dp = 1;

ISR (SIG_OVERFLOW1)
{
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */

	cnt++;
	if (cnt > 99) {
		cnt = 0;
		on_dp = !on_dp;
		sec++;
		if (sec > 59) {
			sec = 0;
			min++;
			if (min > 59) {
				min = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			}
		}
	}
}

void timer1_init(void)
{
	TCCR1A = 0;	/* TCCR1A タイマモード */
	TCCR1B = 3;	/* プリスケーラ (64分周) */

	TIFR1 |= (1 << TOV1);	/* オーバーフローフラグをクリア */
	TIMSK1 |= (1 << TOIE1);	/* オーバーフロー割り込み許可 */
}

const unsigned int to_num_pattern[10] = {
	0xff - 0xfc,
	0xff - 0x60,
	0xff - 0xda,
	0xff - 0xf2,
	0xff - 0x66,
	0xff - 0xb6,
	0xff - 0xbe,
	0xff - 0xe0,
	0xff - 0xfe,
	0xff - 0xf6};

void scan(void)
{
	static unsigned char line = 0;

	PORTD = led_seg[line];

	DDRB = 1 << line;
	line = (line + 1) % 4;
}

void display(unsigned long num)
{
	led_seg[3] = to_num_pattern[(num / 1000) % 10];
	led_seg[2] = to_num_pattern[(num / 100) % 10];
	if (on_dp)
		led_seg[2] &= 0xfe;
	led_seg[1] = to_num_pattern[(num / 10) % 10];
	led_seg[0] = to_num_pattern[num % 10];
}

int main(){
	unsigned char btn_cnt[4] = {0};

	/* ポートのデータ方向(入力/出力)設定 */

	DDRB = 0x00;
	DDRC = 0x00;
	DDRD = 0xff;

	/* ポートの初期値設定 */

	PORTB = 0xff;
	PORTC = 0xff;
	PORTD = 0x00;



	timer1_init();

	sec = 0;
	min = 0;
	hour = 0;
	cnt = 0;
	TCNT1 = INTRCOUNT;	/* タイマ1の初期値設定 */
	sei();



	while (1) {
		scan();

		/* M-- についてのチェック */
		if ((PINC & 0x01) == 0) {
			if (btn_cnt[0] > BTN_TH) {
				btn_cnt[0] = 0;
				cnt = 0;
				sec = 0;
				min--;
				if (min == 255)
					min = 59;
			} else {
				btn_cnt[0]++;
			}
		} else {
			btn_cnt[0] = 0;
		}

		/* M++ についてのチェック */
		if ((PINC & 0x02) == 0) {
			if (btn_cnt[1] > BTN_TH) {
				btn_cnt[1] = 0;
				cnt = 0;
				sec = 0;
				min++;
				if (min > 59)
					min = 0;
			} else {
				btn_cnt[1]++;
			}
		} else {
			btn_cnt[1] = 0;
		}

		/* H-- についてのチェック */
		if ((PINC & 0x04) == 0) {
			if (btn_cnt[2] > BTN_TH) {
				btn_cnt[2] = 0;
				cnt = 0;
				sec = 0;
				hour--;
				if (hour == 255)
					hour = 23;
			} else {
				btn_cnt[2]++;
			}
		} else {
			btn_cnt[2] = 0;
		}

		/* H++ についてのチェック */
		if ((PINC & 0x08) == 0) {
			if (btn_cnt[3] > BTN_TH) {
				btn_cnt[3] = 0;
				cnt = 0;
				sec = 0;
				hour++;
				if (hour > 23)
					hour = 0;
			} else {
				btn_cnt[3]++;
			}
		} else {
			btn_cnt[3] = 0;
		}

		display((unsigned long)(hour * 100 + min));
	}
}

7セグメントLEDがアノードコモンタイプなので、to_num_pattern配列では

0xffから目的のパターンの差分をとっています。


完成写真

こんな感じで出来上がりました。

f:id:cupnes:20120301232711j:image

左から2つ目のセグメントのDPを時表示と分表示の区切りに使っています。

そして、このDPで1秒おきに明滅を繰り返すことで秒を表現しています。

(あと、「時計らしさ」も表現しています。)


また、回路図作成の際に問題点としていた

各セグメントで光らせるLEDの数に応じた明るさの変化については

実際に見てみるとそれほど気になるものではありませんでした。


また、時刻のずれについては、

電池の出力電圧が2.9Vほどへ落ちた時点でも

2時間ほど使用し続けた際に1分のずれも起こりませんでした。

(まだまだ、実際に使ってみないと分からないですが。)


今後の方針

あとは、ブレッドボード上に実装したものを、

ユニバーサル基盤上に実装します。


参考文献

AVRマイコン活用ブック―わかるマイコン電子工作

AVRマイコン活用ブック―わかるマイコン電子工作


追記

2012年3月31日

BSch3V用ファイルを追加しました。

2011-12-11

[]yasnippetの「yas/snippet-dirs」に関して

(yas/initialize)

の実行時に、

[yas] Check your `yas/snippet-dirs': ~/.emacs.d/snippets is not a directory

なんてエラーが出る場合の対処法。

対処法

snippetsのディレクトリの場所が~/.emacs.d/直下ではない場合に出るので、

(setq yas/snippet-dirs "~/.emacs.d/plugins/yasnippet/snippets")

を(yas/initialize)の前に評価するようにするだけ。

[]yasnippetインストール

ついでに、インストールについてもメモしておこう。

~/.emacs.d/plugins/

以下にインストールするやり方で書いていますので、

適宜読み替えてください。

取得

https://github.com/capitaomorte/yasnippet

こちらのページから持ってくる。

git cloneの場合
$ cd ~/.emacs.d/plugins/
$ git clone https://github.com/capitaomorte/yasnippet.git
ZIPダウンロードの場合

ZIPボタンを押すとZIPで固めた奴をくれるので、

それを「~/.emacs.d/plugins/」へ配置。

その後、

$ cd ~/.emacs.d/plugins/
$ unzip capitaomorte-yasnippet-ec9a75a.zip
$ rm capitaomorte-yasnippet-ec9a75a.zip
$ mv capitaomorte-yasnippet-ec9a75a yasnippet

「ec9a75a」のあたりは変化するかと思うので注意を。

.emacsに設定

;; yasnippet
(add-to-list 'load-path "~/.emacs.d/plugins/yasnippet")
(require 'yasnippet)
(setq yas/snippet-dirs "~/.emacs.d/plugins/yasnippet/snippets")
(yas/initialize)

のように設定を追加して完了。