昨年末にクリスマスプレゼントとして子供にUVレジン系のプレゼントをあげたのだが、これがなかなか面白く筆者自身もレジン系クラフトにはまりつつある。
シリコンの型にレジン液を流し込み好みのデコレーションパーツを入れ、太陽光もしくはUVライトで照射し固めると綺麗なクリア系のアクセサリーの出来上がりと言うここ数年人気のクラフトである。
電子工作や3Dプリンター系の工作にも応用が効き、NeoPixelやOLEDディスプレイのコーティングに試してみたりしている。
レジンを硬化させるには太陽光に照らすかUVライトを使うのだが、太陽光は非常に時間がかかるためUVライトを購入した。UVライトであれば数分~十分程度で硬化する。
筆者が購入したのはDivaの36W UVライトである。この系統の中華製品は安価で多数Amazonに出ているのだが、Amazonレビューを見ると粗悪品も多いようだ。幸い筆者が購入した製品は状態も良く好調に動作している。
Diva(ディーヴァ) UVライト36W(UVランプ) 本体 ホワイト
さて、このUVランプであるが、スイッチにより常時点灯もしくは120秒で自動オフするタイマー機能がついている。だが、UVレジンを硬化させる際には大きさ等により数分~7、8分程度まで照射させたい時間が異なる。照射させすぎてもよろしくないので別途アラームをセットし、時間が来たら手動でオフする必要がある。
が、いちいち面倒である。なんとかならないかと分解して基板を調べてみた所、2分タイマーを制御しているICの電源ピンに5VとGNDが来ており、オンオフはこのICから出ている信号1本でリレー制御している様だ。これを利用する事によりArduinoで好きなタイマー設定が出来そうである。
製品により異なる可能性があるが、筆者が購入した製品は下の写真で追加しているオレンジ線が5v、黄色線がGND、緑線がリレー制御の信号線となっている。これを外部に引っ張り出しArduino制御する。
上記写真の線を本体左上に引き出す。必要なのは5V/GND電源線2本とリレー制御信号線1本の計3本のみだ。
過去に中華から購入したLCD4884のシールドがあり、これに液晶画面とジョイスティック、信号ピンが付いているのでこれを利用する事にした。
電源はシールド上のピンのどの5V/GNDに供給してもArduino本体に電源供給されるのでUVライトから引き出した5V/GNDを接続する。リレー制御信号ピンはD12に接続した。また、音でも終了を通知出来る様ブザーをD10ピンに接続。後はArduino+シールドを両面テープでUVライトにペタリ。
UIとタイマーによるリレー制御のコードを組んで完成。UVライト本体のスイッチをタイマー側に入れるとArduinoの電源が入り、ジョイスティックで好きなタイマーを選択しジョイスティック押し込みでタイマーカウントダウンが始まり指定時間照射が始まる。終了時音と画面でお知らせ(画面の画像は適当に拾ってきたもの)
タイマーの数字表示はLCD4884のライブラリの大きい数字フォントがイマイチだったのでデザインをカスタマイズし、「:」とスペース文字を追加している。修正したファイルはこちら。lcd4884.cppは:やスペース文字追加の為LCD_write_string_bigやLCD_write_char_big関数を修正している。
ブザー音は千本桜のメロディを参考にしたもの。トーン音の定義ファイルはこちら。
bmp画像のカスタマイズは下記サイトを参考に。
Arduinoの制御コードは下記の通り。面倒なのでコメント等打っていない。ブザーを鳴らす際LCD4884のキー入力確認用タイマーが邪魔をしていたので、ブザー鳴動時はタイマーを一時切っている。
何故か途中整形が崩れるのでファイルはこちら
uv_timer_control_withlcd4884.ino
#include "LCD4884.h" #include "LCD4884_bmp.h" #include "LCD4884_chinese.h" #include <avr/power.h> #include "tone.h" //keypad debounce parameter #define DEBOUNCE_MAX 15 #define DEBOUNCE_ON 10 #define DEBOUNCE_OFF 3 #define NUM_KEYS 5 #define RELAYPIN 12 #define BUZZ 10 // joystick number #define LEFT_KEY 0 #define CENTER_KEY 1 #define DOWN_KEY 2 #define RIGHT_KEY 3 #define UP_KEY 4 // menu starting points #define MENU_X 10 #define MENU_Y 0 int adc_key_val[5] ={50, 200, 400, 600, 800 }; boolean btn_inter = false; // debounce counters byte button_count[NUM_KEYS]; // button status - pressed/released byte button_status[NUM_KEYS]; // button on flags for user program byte button_flag[NUM_KEYS]; // menu definition #define NUM_MENU_ITEM 6 char menu_items[NUM_MENU_ITEM][12]={ "1 minute", "2 minute", "3 minute", "5 minute", "7 minute", "10 minute" }; int timer_list[NUM_MENU_ITEM] = { 1,2,3,5,7,10 }; char current_menu_item; void relay(bool sw){ pinMode(RELAYPIN,OUTPUT); digitalWrite(RELAYPIN,sw?LOW:HIGH); //reversed for UV control } /* menu functions */ void init_MENU(void){ byte i; lcd.LCD_clear(); for (i=0; i<NUM_MENU_ITEM; i++){ if (i == current_menu_item) lcd.LCD_write_string(MENU_X, MENU_Y+i, menu_items[i], MENU_HIGHLIGHT ); else lcd.LCD_write_string(MENU_X, MENU_Y+i, menu_items[i], MENU_NORMAL); } } // waiting for center key press void waitfor_OKkey(){ byte i; byte key = 0xFF; while (key!= CENTER_KEY){ for(i=0; i<NUM_KEYS; i++){ if(button_flag[i] !=0){ button_flag[i]=0; // reset button flag if(i== CENTER_KEY) key=CENTER_KEY; } } } } // The followinging are interrupt-driven keypad reading functions // which includes DEBOUNCE ON/OFF mechanism, and continuous pressing detection // Convert ADC value to key number char get_key(unsigned int input) { char k; for (k = 0; k < NUM_KEYS; k++){ if (input < adc_key_val[k]){ return k; } } if (k >= NUM_KEYS) k = -1; // No valid key pressed return k; } void update_adc_key(){ int adc_key_in; char key_in; byte i; adc_key_in = analogRead(0); key_in = get_key(adc_key_in); for(i=0; i<NUM_KEYS; i++) { if(key_in==i){ //one key is pressed if(button_count[i]<DEBOUNCE_MAX){ button_count[i]++; if(button_count[i]>DEBOUNCE_ON){ if(button_status[i] == 0){ button_flag[i] = 1; button_status[i] = 1; //button debounced to 'pressed' status btn_inter = true; } } } }else{ // no button pressed if (button_count[i] >0){ button_flag[i] = 0; button_count[i]--; if(button_count[i]<DEBOUNCE_OFF){ button_status[i]=0; //button debounced to 'released' status } } } } } // Timer2 interrupt routine - // 1/(160000000/256/(256-6)) = 4ms interval ISR(TIMER2_OVF_vect) { TCNT2 = 6; update_adc_key(); } void Set_Timer(){ // Setup timer2 -- Prescaler/256 TCCR2A &= ~((1<<WGM21) | (1<<WGM20)); TCCR2B &= ~(1<<WGM22); TCCR2B = (1<<CS22)|(1<<CS21); ASSR |=(0<<AS2); // Use normal mode TCCR2A =0; //Timer2 Overflow Interrupt Enable TIMSK2 |= (0<<OCIE2A); TCNT2=0x6; // counting starts from 6; TIMSK2 = (1<<TOIE2); SREG|=1<<SREG_I; } void Stop_Timer(){ TIMSK2 &= ~(1<<TOIE2); } void Sakura(){ //レファシラソファ ソラミドレ Stop_Timer(); tone(BUZZ,NOTE_D7,150); delay(150); tone(BUZZ,NOTE_F7,150); delay(150); tone(BUZZ,NOTE_AS7,300); delay(300); tone(BUZZ,NOTE_A7,300); delay(300); tone(BUZZ,NOTE_G7,300); delay(300); tone(BUZZ,NOTE_F7,300); delay(300); tone(BUZZ,NOTE_G7,150); delay(150); tone(BUZZ,NOTE_A7,150); delay(150); tone(BUZZ,NOTE_E7,150); delay(150); tone(BUZZ,NOTE_C7,150); delay(150); tone(BUZZ,NOTE_D7,150); delay(150); noTone(BUZZ); Set_Timer(); } void f_timer_update(int tremain){ static unsigned long lastupdate = millis(); if (millis() - lastupdate >= 500) //update every 500ms { char buff[12]; if (millis() % 1000 > 500) sprintf(buff,"%02d:%02d",tremain/60,tremain%60); else sprintf(buff,"%02d %02d",tremain/60,tremain%60); lcd.LCD_write_string_big(5, 1, buff, MENU_NORMAL); } } void f_timer_countdown(int mincount){ relay(true); unsigned long millend; millend = millis() + mincount * 60000; byte i; byte key = 0xFF; while (key!= CENTER_KEY){ for(i=0; i<NUM_KEYS; i++){ if(button_flag[i] !=0){ button_flag[i]=0; // reset button flag if(i== CENTER_KEY) key=CENTER_KEY; } } if (millis() >= millend) key = CENTER_KEY; f_timer_update((millend-millis())/1000); } relay(false); } void f_timer(int mincount){ char buff[12]; sprintf(buff,"%02d:%02d",mincount,0); lcd.LCD_write_string_big(5, 1, buff, MENU_NORMAL); lcd.LCD_write_string(25, 5, "Timer", MENU_HIGHLIGHT ); f_timer_countdown(mincount); lcd.LCD_draw_bmp_pixel(0,0, DFrobot_bmp, 84,38); lcd.LCD_write_string(20, 5, "Complete", MENU_HIGHLIGHT ); Sakura(); waitfor_OKkey(); } void f_temperature() { lcd.LCD_write_string_big(10, 1, "+12.30", MENU_NORMAL); lcd.LCD_write_string(78, 2, "C", MENU_NORMAL); lcd.LCD_write_string(38, 5, "OK", MENU_HIGHLIGHT ); waitfor_OKkey(); } void f_charmap(){ char i,j; for(i=0; i<5; i++){ for(j=0; j<14; j++){ lcd.LCD_set_XY(j*6,i); lcd.LCD_write_char(i*14+j+32, MENU_NORMAL); } } lcd.LCD_write_string(38, 5, "OK", MENU_HIGHLIGHT ); waitfor_OKkey(); } void f_bitmap(){ lcd.LCD_draw_bmp_pixel(0,0, DFrobot_bmp, 84,24); lcd.LCD_write_chinese(6,3, DFrobot_chinese,12,6,0,0); lcd.LCD_write_string(38, 5, "OK", MENU_HIGHLIGHT ); waitfor_OKkey(); } void f_about(){ lcd.LCD_write_string( 0, 1, "LCD4884 Shield", MENU_NORMAL); lcd.LCD_write_string( 0, 3, "www.DFrobot.cn", MENU_NORMAL); lcd.LCD_write_string(38, 5, "OK", MENU_HIGHLIGHT ); waitfor_OKkey(); } void (*menu_funcs[NUM_MENU_ITEM])(void) = { f_temperature, f_charmap, f_bitmap, f_about }; void setup() { pinMode(BUZZ, OUTPUT); pinMode(RELAYPIN,OUTPUT); digitalWrite(RELAYPIN,HIGH); // setup interrupt-driven keypad arrays // reset button arrays for(byte i=0; i<NUM_KEYS; i++){ button_count[i]=0; button_status[i]=0; button_flag[i]=0; } Set_Timer(); lcd.LCD_init(); lcd.LCD_clear(); //menu initialization init_MENU(); current_menu_item = 0; lcd.backlight(ON);//Turn on the backlight } /* loop */ void loop() { byte i; for(i=0; i<NUM_KEYS; i++){ if(button_flag[i] !=0){ button_flag[i]=0; // reset button flag switch(i){ case UP_KEY: // current item to normal display lcd.LCD_write_string(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_NORMAL ); current_menu_item -=1; if(current_menu_item <0) current_menu_item = NUM_MENU_ITEM -1; // next item to highlight display lcd.LCD_write_string(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_HIGHLIGHT ); break; case DOWN_KEY: // current item to normal display lcd.LCD_write_string(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_NORMAL ); current_menu_item +=1; if(current_menu_item >(NUM_MENU_ITEM-1)) current_menu_item = 0; // next item to highlight display lcd.LCD_write_string(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_HIGHLIGHT ); break; case LEFT_KEY: init_MENU(); current_menu_item = 0; break; case RIGHT_KEY: break; case CENTER_KEY: lcd.LCD_clear(); //(*menu_funcs[current_menu_item])(); f_timer(timer_list[current_menu_item]); lcd.LCD_clear(); init_MENU(); //current_menu_item = 0; break; } btn_inter = false; } } }