/*********************************************/ /* Arduino-UNOで作ったパルスジェネレータ */ /*********************************************/ // 2022-08-05 "P_Gen16a.ino" // ※参考資料 //  ・ラジオペンチさん // http://radiopench.blog96.fc2.com/blog-entry-808.html //  ・vabenecosi さん // http://vabenecosi.blog.fc2.com/blog-entry-37.html // ・トラ技 2010年7月号 ATtiny2313を使ったハンディ方形波発生器 // http://act-ele.c.ooco.jp/toukou/pulse1/pulse1.htm // 16bitタイマー1を使って2~65535周期のPWMパルスを出す。 // PWMのクロック源はタイマー2を使い、1MHz,100kHz,10kHz,1kHzの // 4種類を選択。 (1us 10us 0.1ms 1ms) // 1MHz = 1usだと最大周期が65.535ms。 // 1kHz = 1msだと65.535秒となる。 // デューティは同クロックのパルス幅として16bitで設定。 // 出力極性,正か負を繰り替え。 // 正極性出力ならデューティが100%で出力Hに、デューティ0%でLに。 // 負極性ならデューティが100%で出力Lに、デューティ0%でHに固定。 // スイッチ操作 // SW1 入力モード切り替え。 周期(P)かパルス幅(W) // SW2 入力位置カーソル←  ロータリーエンコーダによる // SW3 入力位置カーソル→   入力設定位置を左右に // SW4 クロック選択 (1us 10us 0.1ms 1ms) // SW1長押し EEPORMにパラメータをセーブ // SW2,3長押し 出力波形デューティを50%に // SW4長押し 出力極性切り替え Pos,Neg // ポート // PB0 IO8 in SW3 (onでL) // PB1 IO9 in SW4 // PB2 IO10 out OC1B PWM出力 // PB3 IO11 out OC2A CLK出力 T1入力へ // PB4 IO12 out (!!!) // PB5 IO13 out LED (!!!) // PC0 AD0 out LCD RS // PC1 AD1 out LCD E // PC2 AD2 out LCD D4 // PC3 AD3 out LCD D5 // PC4 AD4 out LCD D6 // PC5 AD5 out LCD D7 // PD0 IO0 in RXD // PD1 IO1 out TXD // PD2 IO2 in INT0 ロータリーエンコーダ sw-A // PD3 IO3 in INT1 ロータリーエンコーダ sw-B // PD4 IO4 out (!!!) // PD5 IO5 in T1入力 OC2Aと接続 // PD6 IO6 in SW1 // PD7 IO7 in SW2 // タイマー0 1ms割り込み (システムで使っているOVF0は禁止) // 計時、スイッチ入力 // タイマー1 PWM出力制御 // OCR1Aで周期 OCR1Bでパルス幅 // タイマー2 PWM用クロックを発生 // 1MHz,100kHz,10kHz,1kHzの4種類 // INT0割り込み // ロータリーエンコーダA相の↓エッジで割り込み // B相はH/Lを見ているだけ // EEPROM 動作設定パラメータを保存 // 液晶 16文字 x 2行 // 画面例 ・周期設定時   ・パルス幅設定時 // ________________ ________________ // |P 10.000ms 1us| |W 0.1100s 0.1ms| // |100.0000Hz Pos| |duty 89.00% Neg| // ---------------- ---------------- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //#include // コメントアウトしても //#include // コンパイルできる //#include #include LiquidCrystal LCD(A0, A1, A2, A3, A4, A5); // 液晶接続 RS,E,D4~D7 /***** タイトル *****/ const char pgm_ttl1[] PROGMEM = "P-Gen 2022-08-05"; const char pgm_ttl2[] PROGMEM = "mode <- -> clk"; /***** マクロ *****/ #define DIMSIZ(a) (sizeof(a)/sizeof(*a)) // 配列のデータ数を返す #define nop() asm volatile("nop;") // NOPコード // エンコーダ入力 H/Lチェック(1でH) #define INP_ENCA (PIND & (1 << PD2)) // ↓エッジでカウント #define INP_ENCB (PIND & (1 << PD3)) // Hならup,Lならdown // SW入力 on,offチェック (L=onで1) #define INP_SW1 ((~PIND) & (1 << PD6)) // mode 周期,デューティ切り替え #define INP_SW2 ((~PIND) & (1 << PD7)) // カーソル← #define INP_SW3 ((~PINB) & (1 << PB0)) // カーソル→ #define INP_SW4 ((~PINB) & (1 << PB1)) // clk sel : 1us,10us,0.1ms,1ms // PWM出力ポート duty 0%,100%制御で #define PWM_H (PORTB |= (1 << PB2)) // PB2 H, OC1Bは切断状態で #define PWM_L (PORTB &= ~(1 << PB2)) // PB2 Lレベル出力 #define PWM_ON (TCCR1A = 0b00110011) // OC1B PWM反転動作(PB2) #define PWM_OFF (TCCR1A = 0b00000011) // OC1B 切断 // |||| ++---- PWM OCR1A モード // ||++-------- COM1B 00でPB2に,11でPWM反転モード // ++---------- COM1A Port PB1 // テスト用ポートH/L制御出力 (SBI,CBI命令に展開) #define PB4_H (PORTB |= (1 << PB4)) // PB4 (D12) #define PB4_L (PORTB &= ~(1 << PB4)) #define PD4_H (PORTD |= (1 << PD4)) // PD4 (D4) #define PD4_L (PORTD &= ~(1 << PD4)) #define LED_ON (PORTB |= (1 << PB5)) // PB5 (D13) #define LED_OFF (PORTB &= ~(1 << PB5)) #define LED_BLINK (PINB |= (1 << PB5)) // 点滅 // SWコード #define SW_MODE 1 // SW1 入力モード切り替え #define SW_CURL 2 // SW2 カーソル← #define SW_CURR 3 // SW3 カーソル→ #define SW_CLK 4 // SW4 クロック選択 #define SW_EEP 5 // SW1長押し EEPORM書き込み #define SW_D50 6 // SW2,3長押し デューティ50%に #define SW_POL 7 // SW4長押し 出力極性切り替え /***** データ *****/ // 基準クロック byte clk_sel; // PWM基準クロック選択 // 0 1us // 1 10us // 2 0.1ms // 3 1ms byte f_clkud ; // clk_sel ±1 UP/DOWNフラグ // 0:up 1:down byte f_disp_clk; // クロック表示指令フラグ // 1でclkdispで表示 const float clk_hz[] PROGMEM ={ // Hz単位のクロック値 1000000.0, // 0 1us 1MHz 100000.0, // 1 10us 100KHz 10000.0, // 2 0.1ms 10kHz 1000.0, // 3 1ms 1kHz }; const char clk_msg0[] PROGMEM = " 1us"; const char clk_msg1[] PROGMEM = " 10us"; const char clk_msg2[] PROGMEM = "0.1ms"; const char clk_msg3[] PROGMEM = " 1ms"; PGM_P clk_msg[]={ // 01234 clk_msg0, // 0 1us clk_msg1, // 1 10us clk_msg2, // 2 0.1ms clk_msg3, // 3 1ms }; // タイマー2設定データ トグル出力で1/2 const byte clk_tccr2b[] PROGMEM = { 0b00000001, // 0 1us 16MHz 2MHz 0b00000001, // 1 10us 16MHz 200kHz 0b00000011, // 2 0.1ms 500kHz 20kHz 0b00000011, // 3 1ms 500kHz 2kHz }; // +++--- CS 2,1,0 const byte clk_ocr2a[] PROGMEM = { 8 - 1, // 0 16MHz/8 80 - 1, // 1 16MHz/80 25 - 1, // 2 500kHz/25 250 - 1, // 3 500kHz/250 }; // カウントデータ表示用 printf指定文字 const char cnt_pf0[] PROGMEM = "%2u.%03ums"; // 65.535ms const char cnt_pf1[] PROGMEM = "%3u.%02ums"; // 655.35ms const char cnt_pf2[] PROGMEM = "%1u.%04us "; // 6.5535s const char cnt_pf3[] PROGMEM = "%2u.%03us "; // 65.535s PGM_P cnt_pf[]={ cnt_pf0, // 0 1us cnt_pf1, // 1 10us cnt_pf2, // 2 0.1ms cnt_pf3, // 3 1ms }; // 表示用 1/n, 1%n データ 整数部と小数部計算 const word cnt_div[] PROGMEM ={ // d/n, d%n値 1000, // 0 1us 65.535ms 100, // 1 10us 655.35ms 10000, // 2 0.1ms 6.5535s 1000, // 3 1ms 65.535s }; // 入力モード byte inp_mode; // 0 周期設定 // 1 パルス幅設定 byte f_disp_mode; // モード表示指令 // modedispでP,Wを表示 // 出力極性 デューティー計算のパルス幅をH/Lどちらでするか byte pol_sel; //    _□□□□□___□□ // 0 正  |←--→| | // 1 負       |←-→| byte f_disp_pol; // 出力極性状態表示指令 // poldispでPos Negを表示 // 極性表示文字 const char pol_msg0[] PROGMEM = "Pos"; // 0 const char pol_msg1[] PROGMEM = "Neg"; // 1 PGM_P pol_msg[]={ pol_msg0, // 0 Pos pol_msg1, // 1 Neg }; // カーソル位置 0:10^0桁 1:10^1桁 byte cur_pos; // 0~4 clk選択で位置が変わる // 01234567 // 0 1us 65.535ms // 43 210 // 1 10us 655.35ms // 432 10 // 2 0.1ms 6.5535s // 4 3210 // 3 1ms 65.535s // 43 210 const byte c_pos0[] PROGMEM = { 5, 4, 3, 1, 0, }; const byte c_pos1[] PROGMEM = { 5, 4, 2, 1, 0, }; const byte c_pos2[] PROGMEM = { 5, 4, 3, 2, 0, }; const byte c_pos3[] PROGMEM = { 5, 4, 3, 1, 0, }; const byte *c_pos[] ={ // 0 1 2 3 4 c_pos0, // 0 1us c_pos1, // 1 10us c_pos2, // 2 0.1ms c_pos3, // 3 1ms }; // encadd処理での乗数 cur_posで変える const word cur_mlt[] PROGMEM = { 1, // 0 (右端) 10, // 1 100, // 2 1000, // 3 10000, // 4 (左端) }; // PWM制御データ word pwm_period; // PWM周期設定値 timer1のTOP値を設定(OCR1A) word pwm_width; // PWMパルス幅 OCR1Bを設定 float pwm_frq; // PWM周波数 // pwm_periodとclk_hz[clk_sel]から計算 float pwm_duty; // PWMデューティ値 // pwm_periodとpwm_width,pol_selから計算 // エンコーダデータ word enc_data; // 16bitエンコーダデータ // エンコーダ操作でup/down // これを元にPWM出力処理 byte f_disp_enc; // エンコーダデータ表示指令フラグ // 周期とPWM値を設定 // エンコーダ割り込み処理データ volatile short cnt_add; // エンコーダ加算値 +/-の値 volatile byte f_cnt; // エンコーダーup/down検出フラグ // encadd()で処理 // 表示文字バッファ char disp_bff[20]; // 16文字x2行だけど // Serial.printでも流用 /*******************************/ /* EEPROM保存と読み出し */ /*******************************/ /***** EEPROMデータ *****/ word EEMEM E_chk; // 0 byte EEMEM E_inp_mode; // 1 byte EEMEM E_clk_sel; // 2 byte EEMEM E_pol_sel; // 3 byte EEMEM E_cur_pos; // 4 word EEMEM E_pwm_period; // 5 word EEMEM E_pwm_width; // 6 // パラメータ区分 #define P_CHK 0 // EEPROM書き込みチェックデータ #define P_BYTE1 1 // byteデータ 0,1 #define P_BYTE3 2 // byteデータ 0~3 #define P_BYTE4 3 // byteデータ 0~4 #define P_WORD0 4 // wordデータ 0~65535 #define P_WORD2 5 // wordデータ 2~65535 // パラメータのアドレスと区分 struct st_para{ const byte typ PROGMEM; // データ区分 const void *ram PROGMEM; // RAMデータアドレス const void *eep PROGMEM; // EEPROMアドレス }; #define EP_CK 0x1234 // EEPROMチェックデータ // 起動時にチェックして異なっていれば初期化 const st_para para_tbl[] PROGMEM={ {P_CHK , NULL , &E_chk }, // 0 {P_BYTE1 , &inp_mode , &E_inp_mode }, // 1 {P_BYTE3 , &clk_sel , &E_clk_sel }, // 2 {P_BYTE1 , &pol_sel , &E_pol_sel }, // 3 {P_BYTE4 , &cur_pos , &E_cur_pos }, // 4 {P_WORD0 , &pwm_period , &E_pwm_period }, // 5 {P_WORD2 , &pwm_width , &E_pwm_width }, // 6 }; /***** EEPROMパラメータ読み出し *****/ // okならデータを設定 違えば初期値に void loadeprom(void) { byte i, er, typ; word d; void *r; // RAMアドレス void *p; // EEPROMアドレス er = 0; // EEPROMチェック結果 // EEPROMデータチェック for(i = 0; i < DIMSIZ(para_tbl); i++){ // loop typ = pgm_read_byte(¶_tbl[i].typ); // データ区分 p = pgm_read_word(¶_tbl[i].eep); // EEPROMアドレス switch(typ){ // データ区分 case P_CHK: // wordデータ case P_WORD0: case P_WORD2: d = eeprom_read_word((word *)p); break; case P_BYTE1: // byteデータ case P_BYTE3: case P_BYTE4: d = eeprom_read_byte((byte *)p); break; } switch(typ){ // データ区分 case P_CHK: // 0x1234 if(d != EP_CK) er = 1; break; case P_WORD2: // 2~65535 if(d < 2) er = 1; break; case P_BYTE1: // 0,1 if(d > 1) er = 1; break; case P_BYTE3: // 0~3 if(d > 3) er = 1; break; case P_BYTE4: // 0~4 if(d > 4) er = 1; break; } } // エラー発生で初期値書き込み if(er){ // エラーあり? for(i = 0; i < DIMSIZ(para_tbl); i++){ // loop typ = pgm_read_byte(¶_tbl[i].typ); // データ区分 p = pgm_read_word(¶_tbl[i].eep); // EEPROMアドレス switch(typ){ // データ区分 case P_CHK: // 0x1234 eeprom_write_word((word *)p, EP_CK); break; case P_WORD0: // 0~65535 (周期) eeprom_write_word((word *)p, 1000); break; case P_WORD2: // 2~65535 (幅) eeprom_write_word((word *)p, 500); break; case P_BYTE1: // 0,1 case P_BYTE3: // 0~3 case P_BYTE4: // 0~4 eeprom_write_byte((byte *)p, 0); break; } } } // データ読み出し RAMに書き込む for(i = 0; i < DIMSIZ(para_tbl); i++){ // loop typ = pgm_read_byte(¶_tbl[i].typ); // データ区分 r = pgm_read_word(¶_tbl[i].ram); // RAMアドレス p = pgm_read_word(¶_tbl[i].eep); // EEPROMアドレス switch(typ){ // データ区分 case P_CHK: d = eeprom_read_word((word *)p); // 読むだけ break; case P_WORD0: // wordデータ case P_WORD2: d = eeprom_read_word((word *)p); *(word *)r = d; break; case P_BYTE1: // byteデータ case P_BYTE3: case P_BYTE4: d = eeprom_read_byte((byte *)p); *(byte *)r = d; break; } // チェックのためにシリアル出力 sprintf_P(disp_bff, PSTR("%d %04X:%u"), i, p, d); // (!!!) Serial.println(disp_bff); } } /***** EEPROMパラメータ保存 *****/ void saveeprom(void) { byte i, typ; void *r; // RAMアドレス void *p; // EEPROMアドレス // RAMから読み出してEEPROMに書き込む for(i = 0; i < DIMSIZ(para_tbl); i++){ // loop typ = pgm_read_byte(¶_tbl[i].typ); // データ区分 r = pgm_read_word(¶_tbl[i].ram); // RAMアドレス p = pgm_read_word(¶_tbl[i].eep); // EEPROMアドレス switch(typ){ // データ区分 case P_WORD0: // wordデータ case P_WORD2: eeprom_write_word((word *)p, *(word *)r); break; case P_BYTE1: // byteデータ case P_BYTE3: case P_BYTE4: eeprom_write_byte((byte *)p, *(byte *)r); break; } } } /************************/ /* 表示処理 */ /************************/ /***** 書式付液晶表示 *****/ // PSTR("%d")で書式指定 void LCDprintf_P(const char *s, ...) { va_list vp; va_start(vp, s); vsnprintf_P(disp_bff, sizeof disp_bff, s, vp); LCD.print(disp_bff); va_end(vp); } /***** エンコーダ設定位置へカーソル表示 *****/ // ロータリースイッチでの入力する位置を表示 // cur_pos:0~4の5桁 0が右端 // clk_selで小数点が間に入る void enccuron(void) { byte m; const byte *p; p = c_pos[clk_sel]; // clk選択 m = pgm_read_byte(p + cur_pos); // 小数点位置 LCD.setCursor(2 + m, 0); // 1行目の2コラム目から LCD.cursor(); // カーソル位置文字の下段に[_] } /***** クロック選択表示 *****/ // 右上に選択クロック周期を表示 // タイマー2 OC2Aでクロックを出力 1MHz,100kHz,10kHz,1kHzの4種類 void clkdisp(void) { if(f_disp_clk){ // 表示指令あり? f_disp_clk = 0; LCD.noCursor(); // いったんカーソルオフ LCD.setCursor(11, 0); // 右上 strcpy_P(disp_bff, clk_msg[clk_sel]); LCD.print(disp_bff); // クロック周期表示 enccuron(); // エンコーダー入力位置カーソル表示 // クロック出力 16MHzを分周 TCCR2B = pgm_read_byte(&clk_tccr2b[clk_sel]); // 入力クロック OCR2A = pgm_read_byte(&clk_ocr2a[clk_sel]); // 1/n値 } } /***** 入力モード表示 *****/ // inp_modeを左上に表示 // 0:P 周期設定 1:W パルス幅設定 void dispmode(void) { static const char s[] PROGMEM = "PW"; char c; if(f_disp_mode){ // 入力モード表示指令あり? f_disp_mode = 0; LCD.noCursor(); // いったんカーソルオフ LCD.setCursor(0, 0); // 左上 c = pgm_read_byte(&s[inp_mode]); // P W LCD.write(c); // クロック周期表示 enccuron(); // エンコーダー入力位置カーソル表示 } } /***** 出力極性表示 *****/ // 右下にpol_selの状態を表示 void disppol(void) { if(f_disp_pol){ // 表示指令? f_disp_pol = 0; LCD.noCursor(); // いったんカーソルオフ LCD.setCursor(13, 1); // 右下 strcpy_P(disp_bff, pol_msg[pol_sel]); LCD.print(disp_bff); // Pos,Neg極性表示 enccuron(); // エンコーダー入力位置カーソル表示 } } /***** PWM周波数を計算 *****/ // pwm_periodとclk_hz[clk_sel]から計算 void calcfrq(void) { float hz; hz = pgm_read_float(&clk_hz[clk_sel]); // CLK周波数 pwm_frq = hz / (float)pwm_period; // CLK周波数÷カウント値 } /***** PWMデューティ比を計算 *****/ // pwm_periodとpwm_width,pol_selから計算 void calcduty(void) { if(pol_sel == 0){ // 極性Posモード if(pwm_width >= pwm_period){ // Wが大なら pwm_duty = 100.0; // 100%に } else{ // Wが小なら比を計算 pwm_duty = 100.0 * (float)pwm_width / (float)pwm_period; } } else{ // 極性Negモード if(pwm_width >= pwm_period){ // Wが大なら pwm_duty = 0.0; // 0%に } else{ // Wが小なら比を計算 pwm_duty = 100.0 * (float)(pwm_period - pwm_width) / (float)pwm_period; } } } /***** PWM周波数表示 *****/ // 左下に表示 // 数値でkHz,Hzを区分 void dispfrq(void) { char s[10]; LCD.setCursor(0, 1); // 左下 if(pwm_frq >= 1000.0){ // 1kHz以上 123.5678kHz dtostrf(pwm_frq / 1000.0, 8, 4, s); sprintf_P(disp_bff, PSTR("%skHz "), s); } else{ // 1kHz未満 123.5678Hz dtostrf(pwm_frq, 8, 4, s); sprintf_P(disp_bff, PSTR("%sHz "), s); } LCD.print(disp_bff); // 周波数表示 } /***** PWMデューティ表示 *****/ // 左下に表示 void dispduty(void) { char s[10]; LCD.setCursor(0, 1); // 左下 dtostrf(pwm_duty, 6, 2, s); // 100.00 sprintf_P(disp_bff, PSTR("duty%s%c "), s, '%'); LCD.print(disp_bff); // デューティ表示 } /******************************/ /* スイッチ入力 */ /******************************/ // 短押し→長押し変換 const byte sw_hold[] PROGMEM = { 0, // off SW_EEP, // SW1 長押し EEPORM書き込み SW_D50, // SW2,3長押し デューティ50%に SW_D50, // SW2,3長押し デューティ50%に SW_POL, // SW4 長押し 出力極性切り替え }; // スイッチチェックデータ volatile byte f_swon; // SW入力オン確定フラグ volatile byte sw_code; // SW on コード /***** スイッチ入力 *****/ // onでL, 1~4のSWコードで返す // 長押しの判断でコードを変える byte swinp(void) { if(INP_SW1) return SW_MODE; // モード if(INP_SW2) return SW_CURL; // カーソル← if(INP_SW3) return SW_CURR; // カーソル→ if(INP_SW4) return SW_CLK; // clk選択 return 0; // 4つともoff } /***** スイッチ入力チェック *****/ // 1msタイマー割り込みで処理 // 結果をf_swonとsw_codeに残す void swscan(void) { byte d; static byte dx; // 前回SWデータ static byte chk = 0; // 実行区分 static word tm1 = 0; // 1msタイマー チャタリング除去 if(tm1) tm1--; // 1ms計時 d = swinp(); // SW入力 1~3のSWコード switch(chk){ case 0: // オフチェックタイマーをセット tm1 = 10; // 10ms chk++; break; case 1: // 離されるのを待つ if(d == 0){ // 全オフ確認 if(tm1 == 0) chk++; // タイムアップで次stepへ } else{ // どれか押されている chk--; // タイマー再セット } break; case 2: // どれかon待ち if(d != 0){ // on? dx = d; // SWコードを保存 tm1 = 10; // 10ms チャタリング除去 chk++; // 安定チェックへ } break; case 3: // on安定待ち if(dx != d){ // 異なればもういちど chk--; } else{ // 安定 if(tm1 == 0){ // タイムアップでSW確定 tm1 = 1000; // 1.0秒経過で長押し chk++; // 長押しの判断へ } } break; case 4: // 短押し、長押しの判断 if(d == dx){ // on継続 if(tm1 == 0){ // タイムアップ =長押し sw_code = pgm_read_byte(&sw_hold[d]); // 長押しコード確定 f_swon = 1; // onフラグ chk = 0; // オフ確認に } } else{ // 離された =短押し sw_code = dx; // 短押しコード確定 f_swon = 1; // onフラグ chk = 0; // オフ確認に } break; } } /***** SW オンチェック *****/ // onしたらコードを持ってリターン // offなら0 byte swonchk(void) { if(f_swon == 0){ return 0; // SW off =0 } else{ f_swon = 0; // フラグをオフして return sw_code; // SW on =SWコード } } /***************************/ /* タイマー処理 */ /***************************/ // タイマーデータ volatile byte tm_10ms; // 10msダウンカウントタイマー volatile byte tm_enc1; // エンコーダーチェックタイマー // 1msでカウントアップ /***** タイマー0 : 1ms割り込み *****/ // 10msを計時 ISR(TIMER0_COMPA_vect) { static byte cnt_10 = 0; // 10msカウント // PD4_H; // (!!!) // 1ms エンコーダチェックタイマー if(tm_enc1 != 255) tm_enc1++; // タイマー1 +1 // 10ms cnt_10++; // 10ms計時 if(cnt_10 >= 10){ cnt_10 = 0; if(tm_10ms) tm_10ms--; // ダウンカウント } // スイッチ入力 swscan(); // スイッチ入力 // PD4_L; // (!!!) } /***** 10ms待ち *****/ // n=255で2.55秒 n=100で1秒 void wait10ms(byte n) { tm_10ms = n; // タイマーセット while(tm_10ms); // タイムアップまでwait } /********************************/ /* エンコーダー処理 */ /********************************/ /***** エンコーダーA相↓エッジ割り込み *****/ // INT0割り込み // A相↓エッジ, B相がHならカウントアップ // Lならカウントダウン // 結果をカウント加算値cnt_addに残す ISR(INT0_vect) { short d; PD4_H; // (!!!) if((tm_enc1 >= 5) && // 前のパルスから5ms経過で新↓エッジ (INP_ENCA == 0)){ // A相がHならミストリガ PB4_H; // (!!!) if(tm_enc1 >= 40) d = 1; // 40ms経過してたら+1 else{ d = 2; // 以内に操作したら+2 } if(INP_ENCB) cnt_add += d; // inB=H カウントアップ else cnt_add -= d; // inB=L カウントダウン f_cnt = 1; // カウント値更新 tm_enc1 = 0; // タイマー1クリアー PB4_L; // (!!!) } PD4_L; // (!!!) } /***** エンコーダ加算処理 *****/ // f_cntオンでenc_dataにcnt_addを加算 // cur_posで加算桁位置を移動 // 0:1の桁 1:10の桁 ~ 4:10000の桁 // inp_modeで最小値を決定 // 0 : 周期 2~65535 // 1 : パルス幅 0~65535 void encadd(void) { int32_t a; // 加算値 cnt_add (エンコーダ割り込みから) int32_t d; // 現在データ(end_data) int32_t m; // cur_posによる乗数 int32_t n; // min値 if(f_cnt){ // エンコーダパルスあり? if(inp_mode == 0) // P:周期設定 enc_data = pwm_period; else // W:パルス幅 enc_data = pwm_width; // エンコーダからのパルスを加算 cli(); // いったん割込禁止 a = (int32_t)cnt_add; // 加算値を32bit値に cnt_add = 0; // ゼロクリアして次を待つ f_cnt = 0; // パルスフラグをクリア sei(); // 割込再開 if(inp_mode == 0) n = 2L; // min値 else n = 0L; // 周期なら2,パルス幅なら0 d = (int32_t)enc_data; // エンコーダデータ m = (int32_t)pgm_read_word(&cur_mlt[cur_pos]); // 乗数 a *= m; // *1,*10~*10000 d += a; // 新パルス値を加算 if((d <= 65535L) && // maxは65535 (d >= n)){ // min値ok? 範囲内の時は enc_data = (word)d; // 16bit値でセーブ } // 入力モードで設定値を区分 if(inp_mode == 0) // P:周期設定 pwm_period = enc_data; else // W:パルス幅 pwm_width = enc_data; // データ表示へ f_disp_enc = 1; // データ表示指令,PWM設定指令 } } /***** エンコーダデータ表示 *****/ // enc_dataを表示 // clk_selで表示桁を変える // 画面の左上に表示 // inp_modeで周波数とデューティを計算して表示 // OCR1A,OCR1Bに周期とPWM値を設定 void encdisp(void) { word d1, d2, n; if(f_disp_enc){ // 表示指令あり? f_disp_enc = 0; LCD.noCursor(); // いったんカーソルオフ LCD.setCursor(2, 0); // 2コラム目から n = pgm_read_word(&cnt_div[clk_sel]); // 1/n, 1%n値 d1 = enc_data / n; // 整数部 d2 = enc_data % n; // 小数部 sprintf_P(disp_bff, cnt_pf[clk_sel], d1, d2); LCD.print(disp_bff); // 65.535ms表示 // 周波数,デューティ計算と表示 if(inp_mode == 0){ // P:周期表示の時 calcfrq(); // 周波数計算して表示 dispfrq(); } else{ // W:パルス幅表示 calcduty(); // デューティ計算して表示 dispduty(); } enccuron(); // エンコーダー入力位置カーソル表示 // タイマー1 TOP値とPWM値を設定 OCR1A = pwm_period - 1; // 周期を設定 if(pol_sel == 0){ // Posモード if(pwm_width >= pwm_period){ // Wが大ならduty100% PWM_H; // H出力 PWM_OFF; // OC1B 切断 } else{ PWM_ON; // OC1B PWM反転動作(PB2) OCR1B = OCR1A - pwm_width; // PWM設定 } } else{ // Negモード if(pwm_width == 0){ // duty100% PWM_H; // H出力 PWM_OFF; // OC1B 切断 } else{ PWM_ON; // OC1B PWM反転動作(PB2) OCR1B = pwm_width - 1; // PWM反転モード出力 } } } } /***************************/ /* SETUP */ /***************************/ /***** SET UP *****/ void setup() { cli(); // 割込禁止 // I/Oイニシャル PORTB = 0b00000011; // data/pull up DDRB = 0b00111100; // port i/o指定 // |||||+---- PB0 IO8 in SW3 // ||||+----- PB1 IO9 in SW4 // |||+------ PB2 IO10 out OC1B PWM出力 // ||+------- PB3 IO11 out OC2A CLK出力 // |+-------- PB4 IO12 out (!!!) // +--------- PB5 IO13 out LED (!!!) PORTC = 0b00000000; // data/pull up DDRC = 0b00111111; // port i/o指定 // |||||+---- PC0 AD0 out LCD RS // ||||+----- PC1 AD1 out LCD E // |||+------ PC2 AD2 out LCD D4 // ||+------- PC3 AD3 out LCD D5 // |+-------- PC4 AD4 out LCD D6 // +--------- PC5 AD5 out LCD D7 PORTD = 0b11101111; // data/pull up DDRD = 0b00010010; // port i/o指定 // |||||||+---- PD0 IO0 in RXD // ||||||+----- PD1 IO1 out TXD // |||||+------ PD2 IO2 in INT0 ROT-SW A // ||||+------- PD3 IO3 in INT1 ROT-SW B // |||+-------- PD4 IO4 out (!!!) // ||+--------- PD5 IO5 in T1入力 // |+---------- PD6 IO6 in SW1 // +----------- PD7 IO7 in SW2 // タイマー0 (システムで使っている) // 横取りしてOC0Aによるコンペアマッチに変更 TIMSK0 = 0b00000000; // 割り込み許可レジスタ // ||+--- TOIE0  全部禁止に // |+---- OCIE0A // +----- OCIE0B TCCR0A = 0b00000010; // |||| ++---- WGM01,00 OCR0A コンペアマッチ // ||++-------- COM0B // ++---------- COM0A TCCR0B = 0b00000011; // |+++--- CLK/64 = 16Hz / 64 = 250kHz 4us // +------ WGM02 OCR0A = 250 - 1; // 1/250 = 1kHz TIMSK0 = 0b00000010; // 割り込み // ||+--- TOIE0 // |+---- OCIE0A 割込on // +----- OCIE0B // タイマー1 PWM出力 (OC1B:PB2) // PWM_ON,PWM_OFF, PWM_H,PWM_LでOC1B出力パルス制御 TCCR1A = 0b00000011; // PWM_OFF状態 // |||| ++---- PWM OCR1A モード // ||++-------- COM1B OC1Bオフ,PB2に (11でPWM反転制御) // ++---------- COM1A Port PB1 TCCR1B = 0b00011110; // || ||+++---- クロックセレクト T1入力↓エッジ // || ++------- PWM OCR1A モード // |+---------- ICES1 // +----------- ICNC1 OCR1A = 10 - 1; // 仮に1/10 (TOP値) OCR1B = OCR1A - 5; // PWM duty 50% // TIMSK1 = 0b00000000; // 割り込み 使わない // | ||+--- TOIE1 // | |+---- OCIE1A // | +----- OCIE1B // +-------- ICIE1 // タイマー2 OC2Aパルス出力 TCCR2A = 0b01000010; // |||| ++--- WGM1,0 CTC // ||++------- COM2B Port PD3 // ++--------- COM2A トグル出力 PB3 TCCR2B = 0b00000001; // |+++--- CS 2,1,0 16MHz/1 // +------ WGM2 OCR2A = 8 - 1; // 1/8してトグルで1/2 =1/16 // INT0 ↓エッジ割り込みに エンコーダA相入力 EICRA = 0b00000010; // 割り込み入力エッジ // ||++---- ISC0 INT0 ↓A相エッジ // ++------ ISC1 INT1 なし B相 EIMSK = 0b00000001; //外部割り込み有効 // |+---- INT0 ↓ // +----- INT1 なし // 液晶 sei(); // 割込許可 LCD.begin(16, 2); // 液晶初期化 16文字x2行 // シリアル Serial.begin(9600); // 9.6kBPSで } /**********************************/ /*  LOOP */ /**********************************/ /***** LOOP *****/ void loop() { word n = 0; byte sw; LCD.setCursor(0, 0); // タイトル表示 strcpy_P(disp_bff, pgm_ttl1); LCD.print(disp_bff); Serial.println(disp_bff); // シリアルにも LCD.setCursor(0, 1); strcpy_P(disp_bff, pgm_ttl2); LCD.print(disp_bff); wait10ms(200); // 2秒待ち LCD.clear(); // パラメータ loadeprom(); // EEPROMパラメータ読み出し f_cnt = 1; // エンコーダカウント入力ありに f_disp_clk = 1; // 選択クロック表示 f_disp_mode = 1; // 入力モード表示 P,W f_disp_pol = 1; // 極性選択表示 Pos,Neg // loop while(1){ LED_BLINK; // PB5 LED点滅 encadd(); // エンコーダ カウント値加算 encdisp(); // エンコーダ値表示 clkdisp(); // 選択クロック表示 dispmode(); // 入力モード表示 disppol(); // 出力極性表示 // スイッチチェック switch(swonchk()){ // スイッチ入力 case SW_MODE: // SW1 入力モード切り替え inp_mode ^= 1; // 入力モード0:P 1:W f_disp_mode = 1; // 入力モード表示フラグをon f_cnt = 1; // データ表示指令,PWM設定指令 break; case SW_CURL: // SW2 カーソル← if(cur_pos == 4) cur_pos = 0; // 左端なら右端へ else cur_pos++; // 左へ enccuron(); // encカーソル位置表示 break; case SW_CURR: // SW3 カーソル→ if(cur_pos == 0) cur_pos = 4; // 右端なら左端へ else cur_pos--; // 右へ enccuron(); // encカーソル位置表示 break; case SW_CLK: // SW4 クロック選択 (0~3) if(clk_sel == 0){ f_clkud = 0; // 0ならupに } else if(clk_sel == (DIMSIZ(clk_hz) - 1)){ f_clkud = 1; // 3ならdownに } if(f_clkud == 0) clk_sel++; // up 1us→10us→ else clk_sel--; // down 1ms→0.1ms→ TCNT1 = 0; // タイマー1カウンタをゼロクリア f_disp_clk = 1; // 選択クロック表示指令 f_cnt = 1; // エンコーダ値表示,PWM設定指令 break; case SW_EEP: // SW1長押し EEPORM書き込み LCD.noCursor(); // カーソルオフ LCD.setCursor(0, 1); // 下行 LCD.print(F("Save EEPROM ")); saveeprom(); // パラメータ保存 wait10ms(100); // 1秒待ち f_disp_enc = 1; // データ表示指令 break; case SW_D50: // SW2,3長押し デューティ50%に pwm_width = pwm_period / 2; // 周期の1/2をパルス幅に TCNT1 = 0; // タイマー1カウンタをゼロクリア f_cnt = 1; // データ表示,PWM設定指令 break; case SW_POL: // SW4長押し 出力極性切り替え pol_sel ^= 1; // 極性切り替え f_disp_pol = 1; // 極性状態表示フラグをon f_disp_enc = 1; // データ表示指令,PWM設定指令 break; } } } /*==== end of "P_Gen16a.ino" ====*/