幼児って ふみきり 好きですよね。
そんな息子ためにPIC12F683+SG90サーボモーターを使って、ふみきりのオモチャを自作してみました。
MAINループでスイッチ入力を監視し、入力があったらサーボモーター動作とLED点滅、ブザーによる警報音鳴動を行います。
CCPの割り込みとブザー鳴動の割り込みを同時に行うと、CCPのパルス幅が安定しないため、サーボモーターの角度が定まらず、遮断棒がプルプルしてしまいます。
よって、警報音が鳴動しているときはサーボモーターの制御を諦めました。
そのため、遮断棒の上昇/下降が間欠動作になってしまいましたが気にしない方向で。
(もう一つマイコンを追加すれば容易に解決可能ですが、基板に空きスペースがなかった。)
ソースはこちら。(gist)
/* | |
* ふみきりのオモチャ | |
* 2018/02/28 7M4MON | |
* Memory usage: ROM=19% RAM=16% - 19% | |
*/ | |
#include <12f683.h> // サーボを使うのでCCPモジュールがある683を選定 | |
#fuses INTRC_IO, NOWDT, PUT, NOPROTECT, NOMCLR, NOBROWNOUT | |
#use Delay(CLOCK=4000000) // 1cycle = 1us | |
#use fast_io(a) | |
#define PIN_LED1 PIN_A0 | |
#define PIN_LED2 PIN_A1 | |
#define PIN_SERVO PIN_A2 | |
#define PIN_EXE PIN_A3 | |
#define PIN_CANCEL PIN_A4 | |
#define PIN_BUZZ PIN_A5 | |
/* タイマの定数は実機での実験結果により10% 早めた値にする */ | |
#define BUZZ_TIME_523 (239) // 523Hz -> 1912us周期、半周期は 938us、タイマの都合で8bit,DIV_4 → 938 ÷ 4 (タイマ2は減算カウンタ) | |
#define BUZZ_TIME_659 (190) // 659Hz -> 1517us周期、半周期は 759us、タイマの都合で8bit,DIV_4 → 759 ÷ 4 (タイマ2は減算カウンタ) | |
#define INTRPT_50MS (255 - 160) /* 約50ms毎*/ | |
#define POST_500MS_DIV 5 /* 250ms ÷ 50ms */ | |
#define EXE_TIMEOUT (20*4) /* 20秒 */ | |
#define SERVO_CYCLE (65535-20000) // 20ms → サーボモーターのサイクル | |
#define SERVO_H_DEG (SERVO_CYCLE + 1100) // 1ms = 0° | |
#define SERVO_V_DEG (SERVO_CYCLE + 2000) // 2ms = 90° | |
#define SERVO_POS_DIV 5 // サーボの動かすポジションの分割数 | |
#define SERVO_MOVE_DEG ((SERVO_V_DEG - SERVO_H_DEG) / SERVO_POS_DIV) | |
#define SERVO_MOVE_ADD 1 // サーボモーターを角度が一致してから何回動作させるか | |
/* グローバル変数 */ | |
unsigned int1 exe = 0; /* 実行中フラグ */ | |
unsigned int8 exe_time = 0; /* 0:L点灯_BUZZ_OFF, 1:0:L点灯_BUZZ_ON, 2:R点灯_BUZZ_OFF, 3:R点灯_BUZZ_ON */ | |
unsigned int8 postscale = POST_500MS_DIV; | |
unsigned int8 buzz_state = 0; | |
signed int8 servo_move = 0; | |
unsigned int16 deg = SERVO_V_DEG; | |
/* TIMER1はサーボモーター制御のため、コンペアモードで使う*/ | |
// 20ms毎にパルスを出力する。 | |
#INT_TIMER1 | |
void timer1_isr(){ | |
set_timer1(SERVO_CYCLE); | |
if(servo_move > 0){ | |
output_high(PIN_SERVO); | |
} | |
} | |
#INT_CCP1 //コンペアマッチ割り込み | |
void ccp1_isr(){ //出力をLowにする | |
output_low(PIN_SERVO); | |
} | |
/* カンカン音の周期での割り込み */ | |
#INT_TIMER2 | |
void set_buzzer(void){ | |
unsigned int8 buzz_time; | |
if(exe && (exe_time & 1) ){ /* 実行中かつexe_timeが奇数だったら */ | |
output_toggle(PIN_BUZZ); | |
buzz_state++; | |
buzz_state &= 3; // 4→0 | |
/* 523Hzと659Hzを交互に鳴らす*/ | |
buzz_time = (buzz_state & 2) ? BUZZ_TIME_523 : BUZZ_TIME_659; | |
set_timer2(buzz_time); | |
}else{ | |
output_low(PIN_BUZZ); | |
} | |
} | |
/* サーボモーターの角度を決める。実行中なら角度上げ、停止中なら角度下げ */ | |
void set_servo(void){ | |
unsigned int16 deg_work; | |
signed int8 servo_move_work; | |
deg_work = exe ? (deg - SERVO_MOVE_DEG) : (deg + SERVO_MOVE_DEG); | |
if((deg_work > SERVO_V_DEG)){ | |
deg_work = SERVO_V_DEG; | |
}else if(deg_work < SERVO_H_DEG){ | |
deg_work = SERVO_H_DEG; | |
} | |
/* 角度が一致して動かす必要がない場合は止める */ | |
servo_move_work = (deg == deg_work) ? (servo_move - 1) : SERVO_MOVE_ADD; | |
servo_move = (servo_move_work < 0) ? 0 : servo_move_work; | |
CCP_1 = deg_work; | |
deg = deg_work; | |
} | |
/* ブザー用の割り込み許可(サーボと排他利用) */ | |
void enable_interrupt_buzz(void){ | |
disable_interrupts(INT_TIMER1); //タイマ1(サーボ)割込み禁止 | |
disable_interrupts(INT_CCP1); //CCP(サーボ)割り込み禁止 | |
set_timer2(BUZZ_TIME_523); | |
enable_interrupts(INT_TIMER2); //タイマ2(ブザー)割込み許可 | |
enable_interrupts(INT_TIMER0); //タイマ0割込み許可 | |
} | |
/* サーボ用の割り込み許可(ブザーと排他利用) */ | |
void enable_interrupt_servo(void){ | |
disable_interrupts(INT_TIMER2); //タイマ2(ブザー)割込み禁止 | |
set_servo(); | |
set_timer1(SERVO_CYCLE); | |
enable_interrupts(INT_TIMER1); //タイマ1(サーボ)割込み許可 | |
enable_interrupts(INT_CCP1); //CCP(サーボ)割り込み許可 | |
enable_interrupts(INT_TIMER0); //タイマ0割込み許可 | |
} | |
/* 50msに一度のタイマ割り込み */ | |
#INT_RTCC | |
void set_cross_state(void){ | |
disable_interrupts(GLOBAL); //グローバル割込み禁止 | |
if(postscale == 0){ /* 250ms */ | |
postscale = POST_500MS_DIV; | |
if(exe){ | |
exe_time++; | |
exe = (exe_time > EXE_TIMEOUT) ? 0 : 1; | |
if(exe_time & 2){ | |
output_low(PIN_LED2); | |
output_high(PIN_LED1); | |
}else{ | |
output_low(PIN_LED1); | |
output_high(PIN_LED2); | |
} | |
if(exe_time & 1){ | |
enable_interrupt_buzz(); | |
}else{ | |
enable_interrupt_servo(); | |
} | |
}else{ // 停止中 | |
output_low(PIN_LED1); | |
output_low(PIN_LED2); | |
exe_time = 0; | |
enable_interrupt_servo(); | |
} | |
}else{ | |
postscale--; | |
} | |
set_timer0(INTRPT_50MS); | |
enable_interrupts(GLOBAL); //グローバル割込み許可 | |
} | |
/* ボタンをチェックして動作状態を決定する。 */ | |
void check_button(void){ | |
/* 三項演算子だとうまく動かないのでベタに書くことにする。 */ | |
//exe = input(PIN_EXE) ? exe : 1; // 実行ボタンが押されたら実行にする | |
//exe = input(PIN_CANCEL) ? exe : 0; // 停止ボタンが押されたら停止にする | |
if(!input(PIN_EXE)){ | |
exe = 1; | |
}else if (!input(PIN_CANCEL)){ | |
exe = 0; | |
} | |
} | |
void main(void){ | |
/*ピンの設定*/ | |
setup_adc_ports(NO_ANALOGS); | |
port_a_pullups(0b00010000); | |
set_tris_a(0b00011000); // 3,4が入力 | |
/* 割り込み設定 */ | |
setup_counters(RTCC_INTERNAL , RTCC_DIV_256); // 256us毎 | |
set_timer0(INTRPT_50MS); // 50ms毎 | |
setup_timer_2(T2_DIV_BY_4,BUZZ_TIME_523,1); // 4us毎 | |
set_timer2(BUZZ_TIME_523); | |
//CCPの設定 | |
CCP_1 = SERVO_H_DEG; //初期化 | |
setup_ccp1(CCP_COMPARE_INT); //CCP1コンペアマッチした時の動作 | |
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1); //内部クロック使用 プリスケーラ1倍 | |
set_timer1(SERVO_CYCLE); | |
enable_interrupts(INT_TIMER0); //タイマ0割込み許可 | |
disable_interrupts(INT_TIMER1); //タイマ1割込み許可 | |
disable_interrupts(INT_TIMER2); //タイマ2割込み許可 | |
disable_interrupts(INT_CCP1); //CCP割り込み許可 | |
enable_interrupts(GLOBAL); //グローバル割込み許可 | |
while(true){ | |
check_button(); /* ボタンの状態をチェック */ | |
} | |
} | |