実用品をAVRで

母親が編み物する時に使う段数カウンタを作ってみた。
以前、景品でもらった万歩計を改造して作ったのだが、ついに動作不良を起こして使えなくなってしまったとのこと。
 
ちょうど、AVR練習用に購入して色々遊んでいた AVR-PICO-3S が手元にあったので、これを単純カウンタに改造。
加算・減算(長押しで連続加減算)、リセット(長押しで反応。誤操作対策)ができる2桁のカウンタという、超基本的な仕様。似た様なアイテムは100円ショップに売られているらしいけど、どうも数字が見づらいらしい。
編み物をしている間ずっと電源ON、しかも7セグLEDが2つ搭載されていて電池容量が心配なので、電源を単三2本に変更。
 
バラ組の状態ではとりあえず完成したのだけど、道具として使う以上ケースが必要。
PICO-3S の基盤上のスイッチはあまりにも小さいので、母親でも押しやすいように大型のプッシュボタンにしたい。電源スイッチは、操作しやすくて他の物に引っかかりにくいよう埋め込みのシーソーがいいな。
手持ちがないのでマルツさんで一式発注。今週末には届くだろう。
 
 
後々の参考のため、ソースも掲載しておく。
Web上のソースの切った貼ったがメインだが、チャタリング対策をソフトウェア的に行ったり長押し動作に反応したりするためにボタン入力処理が無駄にややこしくなっているとか、LED制御をタイマーを使ったダイナミック式に変更して消費電力を押さえられそうな気がするとか、そもそもコード自体が最適化されてない(長いこと遊びに使ってきたので、使ってない変数や関数を宣言したりしてメモリを無駄に食ってる気がする)とか、気になる所は色々あるけど・・・まあいずれ改造するさwww
完成後でもソフトウェアの改造で仕様を変更できるのがマイコンの良いところ。

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

#define NDIGIT 2
#define sbi(BYTE,BIT) BYTE|=_BV(BIT) // BYTEの指定BITに1をセット
#define cbi(BYTE,BIT) BYTE&=~_BV(BIT) // BYTEの指定BITをクリア

unsigned char gDL = 0;
unsigned char g7SEG[NDIGIT];
// 7SegPattern
unsigned char gPTN[10] = {
	0b00111111,
	0b00000110,
	0b01011011,
	0b01001111,
	0b01100110,
	0b01101101,
	0b01111101,
	0b00000111,
	0b01111111,
	0b01100111,
};

//グローバル変数
volatile int g_LEDCounter = 0;
volatile int g_Counter = 0;
volatile int g_ButtonACnt = 0;
volatile int g_ButtonBCnt = 0;
volatile int g_ButtonCCnt = 0;

// IO Ports Initialize
void init_io() {
	// PortA = LED Drive
	PORTA = 0b00000000;
	DDRA  = 0b11111111;

	// PortB = Sound & LED Common & SW
	PORTB = 0b11000111;
	DDRB  = 0b00111000;
}

// BEEP Countrol
void init_Sound() {
	/* プリスケーラ停止 消音 */
	TCCR1B = 0x00;
	/* PB3(OC1B)がタイマでトグルするよう設定 */
	TCCR1A = 0x10;
}

void Sound_ON(int f) {
	/* 周波数設定 */
	OCR1B = F_CPU/2/8/f - 1;
	/* プリスケーラ起動 発音 */
	TCCR1B = 0x04;
}

void Sound_OFF() {
	/* プリスケーラ停止 消音 */
	TCCR1B = 0x00;
}

//7segLED
void seg_set(int i, int n) {
	if (n > 9) {
		// Over
		g7SEG[(i & 1)] = 0b00000000;
	} else {
		g7SEG[(i & 1)] = gPTN[n];
		// Column Over
		if (i >= 2) g7SEG[(i & 1)] |= 0b10000000;
	}
}
void seg_update() {
	if (gDL >= NDIGIT) gDL = 0;
	// Common Unactive
	PORTB |=   0b00110000;
	// SEG Data Output
	PORTA = ~g7SEG[gDL];
	// Common Active
	PORTB &= ~(0b00010000 << gDL++);
}

void do_task() {
	// Update Display
	if ( g_LEDCounter >= 20) {
		seg_set(1, g_Counter % 10);
		if (g_Counter < 10) {
			seg_set(0, 10);
		} else {
			seg_set(0, g_Counter / 10);
		}
		g_LEDCounter = 0;
	} else {
		g_LEDCounter += 1;
	}
	// Button Control (+)
	if ((PINB & 0b00000001) == 0) {
		if (g_ButtonACnt < 50) {
			g_ButtonACnt += 1;
		} else {
			if (g_ButtonACnt == 50 || g_ButtonACnt == 500) {
				if (g_Counter < 99) {
					g_Counter += 1;
				}
			}
			if (g_ButtonACnt >= 500) {
				g_ButtonACnt = 400;
			}
			g_ButtonACnt += 1;
		}
	} else {
		g_ButtonACnt = 0;
	}
	// Button Control (-)
	if ((PINB & 0b00000010) == 0) {
		if (g_ButtonBCnt < 50) {
			g_ButtonBCnt += 1;
		} else {
			if (g_ButtonBCnt == 50 || g_ButtonBCnt == 500) {
				if (g_Counter > 0) {
					g_Counter -= 1;
				}
			}
			if (g_ButtonBCnt >= 500) {
				g_ButtonBCnt = 400;
			}
			g_ButtonBCnt += 1;
		}
	} else {
		g_ButtonBCnt = 0;
	}
	// Button Control (Reset)
	if ((PINB & 0b01000000) == 0) {
		if (g_ButtonCCnt < 500) {
			g_ButtonCCnt += 1;
		} else {
			if (g_ButtonCCnt == 500) {
				g_Counter = 0;
				Sound_ON(100);
				_delay_ms(200);
				Sound_OFF();
				g_ButtonCCnt = 501;
			}
		}
	} else {
		g_ButtonCCnt = 0;
	}
}

// MAIN
int main() {
	init_io();
	init_Sound();

	// Startup
	sei();

	Sound_ON(200);
	_delay_ms(500);
	Sound_OFF();

	for (;; _delay_ms(10)) {
		seg_update();
		do_task();
	}
}

最近になってようやく、ヘルプや参考書をガサガサしなくてもCでコードが書ける様になってきた。普段使い慣れた言語とは、宣言や比較記号、基本処理(forとか)のお作法が微妙に違うから時々ゴチャゴチャになるwww
でもやっぱりpascalの文法が一番しっくりくるなぁ・・・。