(1) キッチンタイマー
30秒刻みで、31分30秒まで設定可能。
時間は、半固定抵抗で設定し、スイッチ0を押すことで起動する。
(2) 汎用カウンタ
スイッチを押すとカウントアップする手動カウンタ
(3) ストップウォッチ
秒単位で計測し、59分まで計測可能
初期画面から、スイッチ0を押すとキッチンタイマーの時間設定に移行する。
機能は、スイッチ1を押すことで
汎用カウンタ→ストップウォッチ→キッチンタイマー
の順で切りかわる。
AVRマイコン ATmega168P
ICソケット 28PIN, 狭幅タイプ
I2C接続薄型16文字×2行液晶 AQM1602XA-RN-GBW
ピンソケット 4ピン
カーボン皮膜抵抗器 1/4W, 470Ω, 1kΩ, 10kΩ×3個
半固定抵抗器 100kΩ ツマミ付
LED φ5mm
積層セラミックコンデンサ 0.1uF 3個
タクトスイッチ 2個
スライドスイッチ
圧電サウンダ PKM13EPYH4000-A0
ユニバーサル基板 Sunhayato ICB-93SG
バッテリースナップ
電池ボックス バッテリースナップタイプ 単3型3本用
スペーサ類
ニッケル水素電池(単3型)
(写真の回路とは一部異なります)
スイッチ1を押すと、モードが切り替わる。
スイッチ0を押すと、モード内での機能を切り替える。
I2Cや液晶ディスプレイの基本制御ライブラリは別ファイルで、下記のコードから構成される。
OS X (Mac) やLinuxの開発環境を使用する場合のMakefileの例は、ソース中にある。
//
// 3mode-timer-lcd-atmega168.c
//
// Created by kaimunantai on 2015/11/16.
// modified on 2015/11/21 : i2c master , define LCD parameters
// modified on 2015/11/22 : add Timer, sw2
// modified on 2015/11/23 : 3-mode add universal counter, stop-watch
/*
* Target : ATmega168P
* OUTPUT :
* LED : PD0 (PIN 2)
* Buzzer : PB1 (PIN15)
* LCD (AQM1602XA-RN-GBW) : I2C (TWI) SCL(PIN27) , SDA (PIN28)
* INPUT :
* tact switch sw0 : PD2 / INT0 (PIN 4)
* tact switch sw1 : PD3 / INT1 (PIN 5)
* VR controler : ADC1 (PIN24)
*
*/
/*
# AVR-GCC Makefile
PROJECT=i2c-timer-lcd-led-buz-sw-adc-atmega168
SOURCES=$(PROJECT).c twimaster.c i2c_lcd.c adc.c
CC=avr-gcc
OBJCOPY=avr-objcopy
MMCU=atmega168
TARGETDEV=m168p
CFLAGS=-g -O2 -mmcu=$(MMCU) -Wall
$(PROJECT).hex: $(PROJECT).out
$(OBJCOPY) -j .text -j .data -O ihex $(PROJECT).out $(PROJECT).hex
$(PROJECT).out: $(SOURCES)
$(CC) $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)
program: $(PROJECT).hex
sudo avrdude -p $(TARGETDEV) -P usb -c avrispmkII -e -U flash:w:$(PROJECT).hex
clean:
rm -f $(PROJECT).out
rm -f $(PROJECT).hex
*
*/
#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/twi.h>
#include <string.h>
#include "i2cmaster.h"
#include "i2c_lcd.h"
#include "adc.h"
/*
* i2c-master interface for avrgcc ((C) 2015 Peter Fleury, GNU General Public License Version 3)
*http://homepage.hispeed.ch/peterfleury/avr-software.html#libs
*
*/
/*
* I/O Control MACROs
*/
#define led() ( PORTD ^= (1<<PD0) )
#define ledOn() ( PORTD |= (1<<PD0) )
#define ledOff() ( PORTD &= ~(1<<PD0) )
#define isSw0On() ( !( PIND & (1<<PD2) ) )
#define isSw0Off() ( PIND & (1<<PD2) )
#define isSw1On() ( !( PIND & (1<<PD3) ) )
#define isSw1Off() ( PIND & (1<<PD3) )
#define buz() ( PORTB ^= (1<<PB1) )
/*
* stage definition
*/
enum {
STAGE_START,
STAGE_TIMER_READY,
STAGE_TIMER_SET,
STAGE_TIMER_COUNTDOWN,
STAGE_ALARM,
STAGE_END,
STAGE_COUNTER_RESET,
STAGE_COUNTER_UP,
STAGE_STOPWATCH_RESET,
STAGE_STOPWATCH_UP,
STAGE_STOPWATCH_STOP
};
static
volatile uint8_t STAGE = STAGE_START;
/*
* Mode definition
*/
enum {
MODE_TIMER,
MODE_COUNTER,
MODE_STOPWATCH
};
static
volatile uint8_t MODE = MODE_TIMER;
static
inline
void wait(volatile
unsigned
long i)
{
while (i-- > 0);
}
static
inline
void beep(unsigned
int height, unsigned
int length)
{
volatile
unsigned
int i, j;
for (i = 0; i < length; i++) {
buz();
for (j = 0; j < height; j++);
}
}
/*
* initialize
*/
static
inline
void io_init(void)
{
PORTD = (1<<PD2) | (1<<PD3); // weak pull up resistor enagle on PD2,3
DDRD = 0b11110011; // PD2,3 as Input, others Output
DDRB = 0b11111111; // PORTB All Output
}
static
inline
void int0_init(void)
{
EICRA |= (1<<ISC01) | (1<<ISC11); // INT0 Falling Edge, INT1 Falling edge
EIMSK |= (1<<INT0) | (1<<INT1); // Enable Interrupt 0, 1
}
static
inline
void timer0_init(void)
// Initialize TIMER0
{
// CTC mode , TOP is OCR0A
TCCR0A = (1 << WGM01) | (0 << WGM00) ;
// I/O clock prescaler N = 8
TCCR0B = (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00);
// Timer0 on OCR0A compare match interrupt enable
TIMSK0 |= ( 1<< OCIE0A );
// set TOP value . Interrupt every 1 ms
OCR0A = 125; // OCR0A = interrupt period [s] * f_clkio [Hz] / I/O clock prescaler N
}
/*
* Interrupt
*/
volatile
unsigned
int T0count; // Timer0 interrupt counter
#define TIMER0_LIMIT 65535
#define TIMER0_10SEC 10000
#define TIMER0_1SEC 1000
#define TIMER0_500MSEC 500
ISR(TIMER0_COMPA_vect)
// Timer0 Timer counter Compair match Interrupt
{
if ( T0count > TIMER0_LIMIT ) {
T0count = 0;
} else {
T0count++;
}
}
volatile
unsigned
int UNIVERSAL_COUNTER = 0;
ISR(INT0_vect)
// INT0 Interrupt
{
switch ( MODE ) {
case MODE_TIMER :
switch ( STAGE ) {
case STAGE_START :
STAGE = STAGE_TIMER_SET;
break;
case STAGE_TIMER_SET :
STAGE = STAGE_TIMER_READY;
break;
case STAGE_TIMER_READY :
STAGE = STAGE_TIMER_COUNTDOWN;
break;
case STAGE_TIMER_COUNTDOWN :
STAGE = STAGE_END;
break;
case STAGE_ALARM :
STAGE = STAGE_TIMER_SET;
break;
default :
STAGE = STAGE_START;
}
break;
case MODE_COUNTER :
switch (STAGE) {
case STAGE_START :
STAGE = STAGE_COUNTER_RESET;
break;
case STAGE_COUNTER_RESET :
UNIVERSAL_COUNTER++;
STAGE = STAGE_COUNTER_UP;
break;
case STAGE_COUNTER_UP :
UNIVERSAL_COUNTER++;
break;
default :
STAGE = STAGE_START;
}
break;
case MODE_STOPWATCH :
switch ( STAGE ) {
case STAGE_STOPWATCH_RESET :
STAGE = STAGE_STOPWATCH_UP;
break;
case STAGE_STOPWATCH_UP :
STAGE = STAGE_STOPWATCH_STOP;
break;
case STAGE_STOPWATCH_STOP :
STAGE = STAGE_STOPWATCH_RESET;
break;
default :
STAGE = STAGE_STOPWATCH_RESET;
}
break;
default:
STAGE = STAGE_START;
}
beep(100, 20);
while(isSw0On());
}
ISR(INT1_vect)
// INT1 Interrupt
{
switch ( MODE )
{
case MODE_TIMER :
MODE = MODE_COUNTER;
STAGE = STAGE_COUNTER_RESET;
break;
case MODE_COUNTER :
MODE = MODE_STOPWATCH;
STAGE = STAGE_STOPWATCH_RESET;
break;
case MODE_STOPWATCH :
MODE = MODE_TIMER;
STAGE = STAGE_TIMER_SET;
}
beep(10, 200);
while(isSw1On());
}
/*
* main
*/
int main(void)
{
unsigned
int adata = 0; // data from ADC
int minutes = 0, seconds = 0; // minutes , seconds for timer
io_init();
int0_init();
timer0_init();
adc_init();
i2c_lcd_init();
ledOn();
while(isSw0On() && isSw1On() ); // wait until switch released
ledOff();
sei(); // Enable Interrupt
i2c_lcd_setContrast(20);
i2c_lcd_clearDisplay();
while (1) {
switch ( STAGE ) {
case STAGE_START :
i2c_lcd_putStringRC(1, 1, "< LCD-MEGA168 >");
i2c_lcd_putStringRC(2, 1, " by KeinSoft");
break;
case STAGE_TIMER_SET :
i2c_lcd_putStringRC(1, 1, "< TIMER M168 >");
i2c_lcd_putStringRC(2, 1,"SET TIME>");
adata = adc_get_data(1); // get Analog signal from ADC1
minutes = adata >> 5 ;
seconds = (((adata & 0x1f)>>4) % 2) * 30;
i2c_lcd_putNumberRC(2, 10, (unsigned)minutes, 2);
i2c_lcd_putStringRC(2, 12,":");
i2c_lcd_putNumberRC(2, 13, (unsigned)seconds, 2);
i2c_lcd_putStringRC(2, 15," ");
T0count = 0;
break;
case STAGE_TIMER_READY :
i2c_lcd_putStringRC(2, 1, "**START* ");
STAGE = STAGE_TIMER_COUNTDOWN;
T0count = 0;
break;
case STAGE_TIMER_COUNTDOWN :
if (T0count >= TIMER0_1SEC) {
led();
seconds--;
if (minutes <= 0 && seconds <= 0) {
STAGE = STAGE_ALARM;
ledOff();
i2c_lcd_putStringRC(2, 1, "!!! ALARM !!!! ");
} else {
if ( seconds <= 0) {
seconds = 59;
minutes--;
}
i2c_lcd_putNumberRC(2, 10, (unsigned)minutes, 2);
i2c_lcd_putStringRC(2, 12, ":");
i2c_lcd_putNumberRC(2, 13, (unsigned)seconds, 2);
}
T0count = 0;
}
break;
case STAGE_ALARM :
led();
beep(5,100);
wait(5000);
break;
case STAGE_COUNTER_RESET :
UNIVERSAL_COUNTER = 0;
i2c_lcd_putStringRC(1, 1, "< COUNTER M168 >");
i2c_lcd_putStringRC(2, 1, "COUNTUP:");
i2c_lcd_putNumberRC(2, 9, UNIVERSAL_COUNTER, 8);
break;
case STAGE_COUNTER_UP :
i2c_lcd_putNumberRC(2, 9, UNIVERSAL_COUNTER, 8);
break;
case STAGE_STOPWATCH_RESET :
i2c_lcd_putStringRC(1, 1, "< STOP WATCH >");
i2c_lcd_putStringRC(2, 1, " TIME >");
minutes = 0;
seconds = 0;
i2c_lcd_putNumberRC(2, 10, (unsigned)minutes, 2);
i2c_lcd_putStringRC(2, 12,":");
i2c_lcd_putNumberRC(2, 13, (unsigned)seconds, 2);
i2c_lcd_putStringRC(2, 15," ");
T0count = 0;
break;
case STAGE_STOPWATCH_UP:
i2c_lcd_putStringRC(2, 1, " START >");
if (T0count >= TIMER0_1SEC) {
seconds++;
if (seconds >= 60) {
seconds = 0;
minutes++;
if ( minutes >= 60 ) {
minutes = 0;
}
i2c_lcd_putNumberRC(2, 10, (unsigned)minutes, 2);
}
i2c_lcd_putNumberRC(2, 13, (unsigned)seconds, 2);
i2c_lcd_putStringRC(2, 15," ");
T0count = 0;
}
break;
case STAGE_STOPWATCH_STOP :
i2c_lcd_putStringRC(2, 1, " TIME :");
break;
case STAGE_END :
i2c_lcd_putStringRC(1, 1, "< LCD-TIMER168 >");
i2c_lcd_putStringRC(2, 1, " BYE-BYE ");
break;
default :
ledOff();
}
}
return
0;
}
ATmega168P のTWI機能によるI2Cのインターフェース制御コード。
Peter Fleury氏のサイト(http://homepage.hispeed.ch/peterfleury/avr-software.html#libs)のコード(GPL3ライセンスによるオープンソース)をそのまま利用させていただいた。ありがとうございます。
使用しているファイルは、次の二つ。
i2cmaster.h
twimaster.c
参考にさせていただいたのは、over80氏のコード。
(http://d.hatena.ne.jp/over80/20141013/1413174967#seemore)
秋月のデータシートと合わせて読むと、制御の手順がよくわかる素晴らしいコード。ありがとうございます。
元のコードの可読性を高め(低くなっているかも)、命名ルールをi2c masterに合わせ、機能を追加したものとなっている。
ヘッダファイル i2c_lcd.h
//
// i2c_lcd.h
//
//
// Created by kaimunantai on 2015/11/21.
//
//
#ifndef i2c_lcd_h
#define i2c_lcd_h
#include <stdio.h>
/*
* I2C LCD Commands, etc
*/
#define I2C_LCD_ADDRESS 0x7c
// Akizuki AQM1602XA-RN-GBW ( Controller : ST7032i )
#define LCD_CMD_CONTINUOUS 0x00 // Co:0, RS:0 continuous command
#define LCD_CMD_ONLYONE 0x80 // Co:1, RS:0 only One command
#define LCD_CMD_FUNCTION 0x38 // Normal mode command
#define LCD_CMD_FUNCTION_EXT 0x39
// Extended mode command
#define LCD_CMD_OSCFREQ 0x14
// Internal OSC frequency
#define LCD_CMD_CONTRAST0 0b01110100
// Contrast set ( 0b0111 | C3 C2 C1 C0 )
#define LCD_CMD_NOICON_3V_CONTRAST1 0b01010101
//0b0101 | ICON OFF(0) | Booster Output ON(1) | Contrast set ( C5 C4 )
#define LCD_CMD_FOLLOWER_CONTROL 0x6c
#define LCD_CMD_CLEAR_DISPLAY 0x01
#define LCD_CMD_DISPLAY_ON 0x0c
#define LCD_CMD_DDRAM_ADDRESS 0x80
// set DDRAM Address Instruction
#define LCD_CMD_WRITE_RAM 0x40
// Write Data into internal RAM
#define LCD_DDRAM_R1C1 0x00 // DDRAM Row 1, Col 1
#define LCD_DDRAM_R2C1 0x40 // DDRAM Row 2, Col 1
uint8_t i2c_lcd_init(void);
uint8_t i2c_lcd_sendControls(uint8_t *data, uint8_t num);
uint8_t i2c_lcd_sendString(uint8_t addr, char *data, uint8_t num);
uint8_t i2c_lcd_clearDisplay(void);
uint8_t i2c_lcd_setContrast(uint8_t contrast);
uint8_t i2c_lcd_putStringRC(uint8_t row, uint8_t col, char *string);
uint8_t i2c_lcd_putNumberRC(uint8_t row, uint8_t col, unsigned
int value, int digit);
#endif /* i2c_lcd_h */
モジュール本体 i2c
//
// i2c_lcd.c
//
//
// Created by kaimunantai on 2015/11/21.
//
//
#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <util/twi.h>
#include <string.h>
#include "i2cmaster.h"
#include "i2c_lcd.h"
/* I2C_LCD : ST7032i(I2C charactor LCD) */
// Refer to
// http://d.hatena.ne.jp/over80/20141013/1413174967
// START
uint8_t i2c_lcd_init(void)
{
uint8_t init_data1[] = {
LCD_CMD_FUNCTION, LCD_CMD_FUNCTION_EXT,
LCD_CMD_OSCFREQ, LCD_CMD_CONTRAST0,
LCD_CMD_NOICON_3V_CONTRAST1,
LCD_CMD_FOLLOWER_CONTROL
};
uint8_t init_data2[] = {
LCD_CMD_FUNCTION,
LCD_CMD_DISPLAY_ON,
LCD_CMD_CLEAR_DISPLAY
};
uint8_t st;
_delay_ms(40);
i2c_init();
st = i2c_lcd_sendControls(init_data1, sizeof(init_data1));
if (st != 0) {
return st;
}
_delay_ms(200);
st = i2c_lcd_sendControls(init_data2, sizeof(init_data2));
if (st != 0) {
return st;
}
_delay_ms(2);
return
0;
}
uint8_t i2c_lcd_sendControls(uint8_t *data, uint8_t num)
{
uint8_t i;
i2c_start_wait(I2C_LCD_ADDRESS | I2C_WRITE);
i2c_write(LCD_CMD_CONTINUOUS); // Co:0(continuous) RS:0
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
for (i = 0; i < num; i++) {
i2c_write(*(data + i));
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
_delay_us(27);
}
i2c_stop();
return
0;
}
uint8_t i2c_lcd_sendString(uint8_t addr, char *data, uint8_t num)
{
uint8_t i;
i2c_start_wait(I2C_LCD_ADDRESS | I2C_WRITE);
i2c_write(LCD_CMD_ONLYONE); // Co:1(one shot) RS:0
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
i2c_write(LCD_CMD_DDRAM_ADDRESS | addr); // Set DDRAM address
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
i2c_write(LCD_CMD_WRITE_RAM); //Co:0 RS:1 (Write data to RAM)
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
for (i = 0; i < num; i++) {
i2c_write( *(data + i) );
if (TW_STATUS != TW_MT_DATA_ACK) {
return -1;
}
_delay_us(27);
}
i2c_stop();
return
0;
}
// Refer to
// http://d.hatena.ne.jp/over80/20141013/1413174967
// END
uint8_t i2c_lcd_clearDisplay(void)
/*
* Clear LCD Screen
*/
{
uint8_t st;
uint8_t command[] = {
LCD_CMD_FUNCTION,
LCD_CMD_CLEAR_DISPLAY
};
st = i2c_lcd_sendControls(command, sizeof(command));
if (st != 0) {
return st;
}
_delay_ms(2);
return
0;
}
uint8_t i2c_lcd_setContrast(uint8_t contrast)
/*
* set LCD Contrast
*
* arguments
* contrast : contrust value 0-63
*
*/
{
uint8_t st;
uint8_t contrast_command[] = {
LCD_CMD_FUNCTION,
LCD_CMD_FUNCTION_EXT,
0x70,
0x55,
LCD_CMD_FUNCTION};
contrast_command[2] = 0x70 | (contrast & 0b00001111);
contrast_command[3] = 0x50 | 0b0100 | ((contrast & 0b00110000) >> 4);
st = i2c_lcd_sendControls(contrast_command, sizeof(contrast_command));
if (st != 0) {
return st;
}
_delay_ms(2);
return
0;
}
uint8_t i2c_lcd_putStringRC(uint8_t row, uint8_t col, char *string)
/*
* ARGUMENTS
*
* row : String start position , row of LCD
* col : String start position , column of LCD
*
* col ->->->
* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* row | 1
* ^ 2
*
* string : pointer of string data
*/
{
uint8_t address;
uint8_t st;
address = LCD_DDRAM_R1C1 + ( row - 1 ) * LCD_DDRAM_R2C1 + col - 1;
st = i2c_lcd_sendString(address, string, strlen(string));
if (st != 0 ) return st;
return
0;
}
uint8_t i2c_lcd_putNumberRC(uint8_t row, uint8_t col, unsigned
int value, int digit)
/*
* ARGUMENTS
*
* row : String start position , row of LCD
* col : String start position , column of LCD
*
* col ->->->
* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* row | 1
* ^ 2
*
* value : output value
* digit : number of digit
*/
{
char string[17]; // buffer of strings
uint8_t st;
int i;
if (digit >= 17) {
// Over flow
return -1;
}
string[digit] = 0x00;
for (i = digit - 1 ; i >= 0; i--) {
string[i] = '0' + value % 10;
value /= 10;
}
st = i2c_lcd_putStringRC(row, col, string);
if (st != 0 ) return st;
return
0;
}