/************************************************/ /* 有効電力測定のためのパルス周波数測定実験 */ /* "pwmmon3.ino" */ /************************************************/ // 2021-05-09 D/Aコンバータを付加 // 10 SWオートリピート処理変更 // 12 周波数→電力変換 0~Lo~Hiと2分割に // 13 パラメータ設定値をシリアル出力 // 14 A/D0,A/D1でアナログ入力 // LCD DB4とD/A SCK, DB5とSDIを共用に // 16 A/D入力電圧計算のためのVrefをパラメータ設定に // 計測IC AD71056 (AD7757)が出す周波数を測定 // 約1.6kHzで50W // PC0 AD0 A/D入力ch0 // PC1 AD1 A/D入力ch1 // PC2 DB6 8文字x2行液晶 // PC3 DB7 // PC4 RS // PC5 E // PD0 RXD (boot loader) // PD1 TXD シリアルデータ出力 // PD2 ENT SW // PD3 ↑ SW // PD4 T0 タイマー0 clk入力 (500Hz) // PD5 T1 タイマー1 clk入力 (計測周波数) // PD6 OC0A タイマー0 1Hz出力 // PD7 ↓ SW // PB0 ICP1 タイマー1 キャプチャ入力(1Hzパルス) // PB1 D/A /CS // PB2 液晶 DB4, D/A SCK (共用) // PB3 OC2A タイマー2 500Hz出力 // PB4 液晶 DB5, D/A SDI (共用) // PB5(LED) // 周波数測定の経路 // 測定クロック入力 T1 // キャプチャタイミング ICP1(1秒周期) // OC2A(500Hz)→T0→1/250→OC0A(1Hz)→ICP1 // タイマー0を横取りするためdelayやmillisは使用不可に。 // 1秒ごとに周波数から電力を計算して表示とシリアル出力およびD/A出力 // D/AコンバータはMCP4911 10bit 0~4.096V出力 // 4.0Vを出力する電力値をパラメータ設定 // SW1を押しながらパワーオンでEEPROMを初期化 /***** ライブラリ *****/ #include // 8文字×2行液晶を4bitモードで使用 #include // EEPROMデータ保存 /***** タイトルと日付 *****/ const char msg_titl[] PROGMEM = "Pwr Moni"; // タイトル const char msg_date[] PROGMEM = "21-05-16"; // 日付 /***** 液晶ピン指定 *****/ #define LCD_RS A4 // デジタルピン指定 #define LCD_E A5 // 4bitモードで使用 #define LCD_D4 10 // D/A SCKと共用 #define LCD_D5 12 // D/A SDIと共用 #define LCD_D6 A2 #define LCD_D7 A3 // 文字数と行数 #define LCD_COLS 8 // 文字数 #define LCD_ROWS 2 // 行数 // initialize the library with the numbers of the interface pins LiquidCrystal lcd(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7); /***** マクロ *****/ #define DIMSIZ(a) (sizeof(a)/sizeof(*a)) // 配列のデータ数を返す /***** I/O入出力指定 *****/ // SW入力 on,offチェック(onで1) #define INP_SW1 ((~PIND) & (1 << PORTD2)) // PD2 SW1 ENT #define INP_SW2 ((~PIND) & (1 << PORTD3)) // PD3 SW2 ↑ #define INP_SW3 ((~PIND) & (1 << PORTD7)) // PD7 SW3 ↓ // SWコード #define SW_ENT 1 // ENT SW1スイッチ(短押し) #define SW_CAN 2 // CAN SW1スイッチ(長押し) #define SW_UP 3 // ↑ SW2スイッチ #define SW_DN 4 // ↓ SW3スイッチ /***** I/O制御マクロ *****/ #define DACS_H (PORTB |= (1 << PB1)) // PB1 15pin #define DACS_L (PORTB &= ~(1 << PB1)) // D/A /CS #define DASCK_H (PORTB |= (1 << PB2)) // PB2 16pin #define DASCK_L (PORTB &= ~(1 << PB2)) // D/A SCK #define DASDI_H (PORTB |= (1 << PB4)) // PB4 18pin #define DASDI_L (PORTB &= ~(1 << PB4)) // D/A SDI #define PB5_H (PORTB |= (1 << PB5)) // PB5 19pin #define PB5_L (PORTB &= ~(1 << PB5)) // (LED) /*******************************/ /* パラメータ設定データ */ /*******************************/ // パラメータ設定データ(+/-するのでshortで) short pwr_lo; // 2点cal 電力 Lo 0~199.99W short frq_lo; // 2点cal 周波数 Lo 0~9999Hz short pwr_hi; // 2点cal 電力 Hi 0~199.99W short frq_hi; // 2点cal 周波数 Hi 0~9999Hz short da_4v; // D/A出力 フルスケール電力 0~199.99W short v_ref; // 基準電圧 4.096V short tx_cyc; // データ送信周期 1s,10s,1m short avr_frq; // 周波数計算平均回数 1,2,5,10 0~3 short tx_style; // 送信データスタイル // パラメータ区分 #define P_EPCK 0 // EEPROMチェックデータ 0x1234 #define P_PWR 1 // 2点cal 電力値 0~199.99W #define P_FRQ 2 // 2点cal 周波数 0~9999Hz #define P_VRE 3 // Vref #define P_CYC 4 // データ送信サイクル #define P_AVR 5 // 周波数計算平均回数 0~3 #define P_STL 6 // 送信データのスタイル // パラメータデータの最大値 (最小値は0) const short Parad_max[] PROGMEM ={ 0, // 0 EEPROMチェックデータ 0x1234 19999, // 1 P_PWR 2点cal 電力値 0~199.99W 9999, // 2 P_FRQ 2点cal 周波数 0~9999Hz 5500, // 3 P_VRE 基準電圧 4.096V 5, // 4 P_CYC データ送信サイクル 0~5 3, // 5 P_AVR 周波数計算平均回数 0~3 3, // 6 P_STL 送信データのスタイル 0~3 }; // データ送信サイクル const short Parad_cyc[] PROGMEM ={ 1, // 0 1秒 5, // 1 5秒 10, // 2 10秒 30, // 3 30秒 60, // 4 60秒 600, // 5 10分 }; // 周波数計算平均回数 0~3→1~10に const short Parad_avr[] PROGMEM ={ 1, // 0 2, // 1 5, // 2 10, // 3 ← これで0.1Hz単位で測定 }; // 送信データスタイル const char msg_txs0[] PROGMEM = "DataOnly"; const char msg_txs1[] PROGMEM = "D+minmax"; const char msg_txs2[] PROGMEM = "No.+Data"; const char msg_txs3[] PROGMEM = "No.+D+m "; PGM_P msg_txs[]={ // 01234567 msg_txs0, // 0 データ(電力,A/Dの値,電力量)だけ msg_txs1, // 1 データ+min,max msg_txs2, // 2 連番+データ msg_txs3, // 3 連番+データ+min,max }; // パラメータのアドレスと区分 struct st_para{ const byte typ PROGMEM; // データ区分 const short *data PROGMEM; // データアドレス const short ini PROGMEM; // 初期値 }; const st_para Para_tbl[] PROGMEM={ {P_EPCK , NULL , 0x1234}, // 0 EEPROMチェックデータ#1 {P_PWR , &pwr_lo , 500}, // 1 2点cal 電力 Lo 0~199.99W {P_FRQ , &frq_lo , 160}, // 2 2点cal 周波数 Lo 0~9999Hz {P_PWR , &pwr_hi , 5000}, // 3 2点cal 電力 Hi 0~199.99W {P_FRQ , &frq_hi , 1600}, // 4 2点cal 周波数 Hi 0~9999Hz {P_PWR , &da_4v , 4000}, // 5 D/Aフルスケール(4V)電力 0~199.99W {P_VRE , &v_ref , 4096}, // 6 基準電圧 4.096V {P_CYC , &tx_cyc , 0}, // 7 データ送信サイクル 1秒 {P_AVR , &avr_frq , 3}, // 8 周波数計算平均回数 0~3=10回 {P_STL , &tx_style, 0}, // 9 送信データのスタイル 電力数字だけ }; // 設定メニュー const char msg_menu0[] PROGMEM = "0 Mesure"; const char msg_menu1[] PROGMEM = "1 PWR-Lo"; const char msg_menu2[] PROGMEM = "2 FRQ-Lo"; const char msg_menu3[] PROGMEM = "3 PWR-Hi"; const char msg_menu4[] PROGMEM = "4 FRQ-Hi"; const char msg_menu5[] PROGMEM = "5 DA4.0V"; const char msg_menu6[] PROGMEM = "6 Vref "; const char msg_menu7[] PROGMEM = "7 TxCyc "; const char msg_menu8[] PROGMEM = "8 Avrg "; const char msg_menu9[] PROGMEM = "9 TxStyl"; PGM_P msg_menu[]={ // 01234567 msg_menu0, // "0 Mesure" msg_menu1, // "1 PWR-Lo" msg_menu2, // "2 FRQ-Lo" msg_menu3, // "3 PWR-Hi" msg_menu4, // "4 FRQ-Hi" msg_menu5, // "5 DA4.0V" msg_menu6, // "6 Vref " msg_menu7, // "7 TxCyc " msg_menu8, // "8 Avrg " msg_menu9, // "9 TxStyl" }; /***** EEPROMパラメータチェック *****/ // メニュー位置のデータを初期値と比較 // okならパラメータを呼び出し(0でリターン) // 違えば初期値を書き込み (1でリターン) // チェック時にSWがonなら初期値書き込み byte eparack(void) { byte i, typ; short ini, d, m, *p; byte er = 0; // EEPROMチェック結果 for(i = 0; i < DIMSIZ(Para_tbl); i++){ // EEPROMのアドレスと同じ typ = pgm_read_byte(&Para_tbl[i].typ); // データ区分 EEPROM.get(2*i, d); // EEPROMの値をリード if(typ == P_EPCK){ // チェックデータ ini = pgm_read_word(&Para_tbl[i].ini); // 初期値 if(d != ini) er++; // 異なったらエラー } else{ // P_EPCK以外 m = pgm_read_word(&Parad_max[typ]); // 区分からmax値 if(d > m) er++; // 大きければエラー } } // ENT SWがオンなら強制的に初期化 if(INP_SW1) er += 100; // 強制初期化は100で // エラーあれば初期値をEEPROMに if(er){ // EP_CK以外,データ大ならEEPROMに初期値を書き込む for(i = 0; i < DIMSIZ(Para_tbl); i++){ // EEPROMアドレス ini = pgm_read_word(&Para_tbl[i].ini); // 初期値 EEPROM.put(2*i, ini); // EEPROM書き込み } } // EEPROMからパラメータを読む for(i = 0; i < DIMSIZ(Para_tbl); i++){ // EEPROMのアドレスと同じ p = pgm_read_word(&Para_tbl[i].data); // データアドレス EEPROM.get(2*i, d); // EEPROMの値 if(p != NULL) *p = d; // パラメータにセット } return er; // 1なら初期化した } /****************************/ /* 周波数測定 */ /****************************/ /***** 周波数データ *****/ volatile byte f_cap; // キャプチャーフラグ // 1Hz周期 volatile word cap_old; // 前回のキャプチャ値 ICR1を保存 volatile word cap_cnt; // キャプチャカウント値 // ICR1値からcap_oldを引いた値 volatile word frq_bff[10]; // 測定周波数バッファ // 最大10秒分 volatile word frq_wp; // 測定周波数データ書き込みポインタ // キャプチャごとに書き込み // OVFを使った周波数測定 volatile long ovf_frq; // OVFも加味した周波数測定 // 1秒で確定 volatile long ovf_add; // OVF発生した時のパルス加算結果 // OVF1割り込みで処理 volatile word ovf_cap; // 前回のキャプチャ値 ICR1を保存 // OVF1発生で0クリア volatile word ovf_cnt; // オーバーフロー回数 // OVF1処理回数 /***** タイマー1インプットキャプチャー割込 *****/ // ICP1の↓エッジ(1Hz)でT1clk入力キャプチャー ISR(TIMER1_CAPT_vect) { static byte f1 = 0; // はじめてフラグ word a, d; byte i; // PB5_H; // (!!!) d = ICR1; // キャプチャデータ 0~65535 a = d - cap_old; // カウント値の差(0~65535) = 周波数 cap_old = d; // キャプチャデータを保存 if(a > 9999) a = 9999; // 9999を最大に // 回数で処理を変える if(f1 < 4){ // 0~3回目のキャプチャ f1++; // まだ周波数値は確定させない } else if(f1 == 4){ // 4回目 cap_cnt = a; // カウント値確定 for(i = 0; i < DIMSIZ(frq_bff); i++){ // frq_bffを埋める frq_bff[i] = a; } f1 = 5; f_cap = 1; // キャプチャフラグをオン } else{ // 5回目以降 cap_cnt = a; // カウント値確定 frq_bff[frq_wp] = a; // キャプチャ値を周波数として保存 frq_wp++; // 書き込みポインタを+1 if(frq_wp >= DIMSIZ(frq_bff)) frq_wp = 0; // 一巡した? f_cap = 1; // キャプチャフラグをオン } // OVFを加味した周波数測定 16bit以上のカウント値もokに if((d <= 0x7FFF) && // capとovfが重なったかも (TIFR1 & (1 << TOV1))){ // オーバーフロー未処理あり ovf_cnt++; // OVF回数+1 ovf_add += 65536L - (long)ovf_cap; // 前cap値からの差を加算 ovf_cap = 0; // キャプチャ値 次回用 TIFR1 = (1 << TOV1); // オーバーフロー未処理を消す } if(ovf_cnt == 0){ // オーバーフローがなかった ovf_frq = (long)(d - ovf_cap); // 前回値との差が周波数 ovf_cap = d; // キャプチャ値 次回用 } else{ // オーバーフローがあった ovf_frq = ovf_add + (long)d; // パルス加算結果に現cap値を加算 ovf_cap = d; // キャプチャ値 次回用 ovf_cnt = 0; // オーバーフロー回数ゼロに } ovf_add = 0; // 加算値を0に // PB5_L; // (!!!) } /***** タイマー1オーバーフロー割込 *****/ ISR(TIMER1_OVF_vect) { // PB5_H; // (!!!) ovf_cnt++; // オーバーフロー回数+1 ovf_add += 65536L - (long)ovf_cap; // 前cap値からの差を加算 ovf_cap = 0; // キャプチャ値 次回用 // PB5_L; // (!!!) } /***** 周波数計算 *****/ // frq_bffに保存された周波数を平均処理 // n:平均回数 1~10 (frq_bffの数) // f_capのタイミングで処理 float frqavr(byte n) { byte i, rp; long d; float f; // PB5_H; // (!!!) d = 0; cli(); // いったん割り込み禁止 for(i = 0; i < n; i++){ // n個のデータ読み出しloop rp = frq_wp - i - 1; // 読み出しポインタ if(rp >= DIMSIZ(frq_bff)) rp += DIMSIZ(frq_bff); d += (long)frq_bff[rp]; // longで加算 } sei(); // 割り込み再開 f = (float)d / (float)n; // floatで割算 // PB5_L; // (!!!) return f; } /******************************/ /* 電力計算 */ /******************************/ /***** 線形補間 *****/ // map関数をfloatで float mapf(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } /***** 周波数から電力に *****/ // 周波数0Hz~LoとLo~Hiに分けて計算 float frqtopwr(float frq) { float pwr; if(frq < frq_lo){ // 周波数 0~Lo pwr = mapf(frq, 0, frq_lo, 0, pwr_lo); // 線形補間 0~Lo } else{ // 周波数 Lo~Hi~ pwr = mapf(frq, frq_lo, frq_hi, pwr_lo, pwr_hi); // 線形補間 Lo~Hi } if(pwr < 0.0 ) pwr = 0.0; // マイナスなら0に return pwr; } /******************************/ /* スイッチ入力 */ /******************************/ volatile byte f_swon; // SW入力オン確定フラグ volatile byte sw_code; // SW on コード volatile byte sw_rpt; // SWリピート回数 /***** スイッチ入力 *****/ // PD2,3,7 onでL 1~3のSWコードで返す // SW1は短押し(SW_ENT)、長押し(SW_CAN)の判断でコードを変える byte swinp(void) { if(INP_SW1) return SW_ENT; // ENT スイッチ if(INP_SW2) return SW_UP; // ↑ スイッチ if(INP_SW3) return SW_DN; // ↓ スイッチ return 0; // 3ともoff } /***** スイッチ入力チェック *****/ // 1ms割り込みで処理 // 結果をf_swonとsw_codeに残す void swscan(void) { byte d; static byte dx; // 前回SWデータ static byte chk = 0; // 実行区分 static word tm1 = 0; // 1msタイマー チャタリング除去 static word tmr = 0; // オート if(tm1) tm1--; // 1ms計時 if(tmr < 65535) tmr++; // リピート回数チェックタイマー+1 d = swinp(); // SW入力 1~3のSWコード switch(chk){ case 0: // オフチェックタイマーをセット tm1 = 10; // 10ms tmr = 0; // リピート回数チェックタイマー 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確定 if(tmr >= 800){ // オフが長かったら sw_rpt = 0; // リピート回数=0から } if(d == SW_ENT){ // "ENT"はオートリピートしない tm1 = 1200; // 1.2秒経過で長押し chk = 5; // 長押しの判断へ } else{ // SW1以外 sw_code = d; // SWコード確定 f_swon = 1; // onフラグ tm1 = 800; // 0.8秒後から chk++; // オートリピート確認へ } } } break; case 4: // オートリピートチェック if(dx != d){ // 異なれば chk = 0; // オフ確認に } else{ // 安定している if(tm1 == 0){ // タイムアップで確定 if(sw_rpt < 255) sw_rpt++; // リピート回数+1 sw_code = d; // SWコード確定 f_swon = 1; // onフラグ tm1 = 50; // オートリピート継続(毎秒20回) } } break; case 5: // SW1の短押し、長押しの判断 if(d == dx){ // on継続 if(tm1 == 0){ // タイムアップ sw_code = SW_CAN; // SW1長押しコード確定 f_swon = 1; // onフラグ chk = 0; // オフ確認に } } else{ // 離された sw_code = SW_ENT; // SW1短押しコード確定 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コード } } /****************************/ /* タイマー割り込み */ /****************************/ // タイマー0を横取りしているためdelayやmillisは使えない。 // 欲しいタイマーはここに記述。 /***** タイマーデータ *****/ volatile byte f_1ms; // 1msフラグ volatile byte f_1sec; // 1秒フラグ volatile byte tm_100ms; // 0.1秒ダウンカウントタイマー /***** タイマー2コンペアマッチA割込み *****/ // 1kHz周期 OC2Aに500Hzパルスを出力→T0入力に ISR(TIMER2_COMPA_vect) { static byte cnt100 = 0; // 100ms計時 static byte cnt10 = 0; // 1秒計時 PB5_H; // (!!!) f_1ms = 1; // 1msフラグ cnt100++; // 100ms if(cnt100 >= 100){ // 0.1秒経過 cnt100 = 0; if(tm_100ms) tm_100ms--; cnt10++; // 1秒 if(cnt10 >= 10){ cnt10 = 0; f_1sec = 1; // 1秒フラグ } } swscan(); // SW入力スキャン // 内蔵A/D変換開始 ADCSRA |= (1 << ADSC); // A/D変換開始 // データ取得はA/D割り込みで PB5_L; // (!!!) } /******************************/ /* 内蔵A/D処理 */ /******************************/ /**** A/D データ *****/ // A/D変換チャンネル (ADMUX) const byte ad_mpx[] PROGMEM ={ 0b00000000, // 0 ADC0 ch0 0b00000001, // 1 ADC1 ch1 }; // ||| ++++---- MPX // ||+--------- ADLAR // ++---------- 外部 AREF接続 外づけ2.5V // A/Dデータ(2ch) volatile word ad_avr[2]; // A/D変換データ 0~1023 // 平均処理された結果 volatile uint32_t ad_add[2]; // A/D平均処理用加算データ volatile byte f_adok; // 2ch A/D変換完了フラグ // 割り込みでセット // A/D値確定でad_avrを転送 word ad_data[2]; // 10bit A/D値 0~1023 // 平均処理結果 float ad_volt[2]; // mV単位の電圧値 0~4096mV // addata()で計算 /***** A/D割り込み処理 *****/ // タイマー割り込みでA/D変換開始 // 256回(2chで512ms)で平均値算出 ISR(ADC_vect) { word d; byte i; static byte ch = 0; // A/D変換チャンネル static word cnt = 0; // A/D変換平均回数 PB5_H; // (!!!) d = (word)ADCL; // 10bit A/Dデータ d |= (ADCH & 0x03) << 8; // 符号なしで 0~3FF // 測定値 ad_add[ch] += (uint32_t)d; // 平均用に加算 // 次変換チャンネル ch++; // ch0,1繰り返し if(ch >= DIMSIZ(ad_add)){ ch = 0; // 0に戻す cnt++; // 平均加算回数 if(cnt >= 256){ // 256回? cnt = 0; for(i = 0; i < DIMSIZ(ad_avr); i++){ // 2ch loop ad_avr[i] = (word)(ad_add[i] / 256L); // 平均 ad_add[i] = 0L; // 次加算データクリア } f_adok = 1; // 変換完了 } } ADMUX = pgm_read_byte(&ad_mpx[ch]); // 次ch PB5_L; // (!!!) } /***** A/Dデータ転送 *****/ // 割込でのwordアクセスを避けるため // 転送以降ad_dataは割り込みを気にせずアクセス可能 void addata(void) { byte i; float v; if(f_adok){ // A/D確定 f_adok = 0; cli(); // 割込禁止にしてwordデータ転送 ad_data[0] = ad_avr[0]; // ch0 10bitデータ ad_data[1] = ad_avr[1]; // ch1 sei(); // 割込再開 for(i = 0; i < DIMSIZ(ad_avr); i++){ // 2ch loop v = ((float)ad_data[i] * (float)v_ref) / 1024.0; // 電圧値に ad_volt[i] = v * 0.001; // mV→V値に変換 (VrfがmVだから) } } } /**************************/ /* シリアル出力 */ /**************************/ /***** 書式付シリアル出力 *****/ void txprintf(const char *s, ...) { va_list vp; char bff[64]; // バッファを確保 va_start(vp,s); vsnprintf(bff, sizeof bff, s, vp); Serial.print(bff); va_end(vp); } /***** メッセージ出力 *****/ void txputsP(PGM_P s) { char c; while(1){ c = pgm_read_byte(s); if(c == '\x0') break; Serial.write(c); s++; } } /**************************/ /* 液晶表示 */ /**************************/ // lcd.print(); // lcd.write(uint8_t value); // lcd.setCursor(uint8_t col, uint8_t row); // lcd.clear(); // lcd.home(); // lcd.noDisplay(); // lcd.display(); // lcd.noBlink(); // lcd.blink(); // lcd.noCursor(); // lcd.cursor(); // lcd.scrollDisplayLeft(); // lcd.scrollDisplayRight(); // lcd.leftToRight(); // lcd.rightToLeft(); // lcd.autoscroll(); // lcd.noAutoscroll(); /***** 書式付液晶表示 *****/ void lcdprintf(const char *s, ...) { va_list vp; char bff[40]; // バッファを確保 va_start(vp,s); vsnprintf(bff, sizeof bff, s, vp); lcd.print(bff); va_end(vp); } /***** 1行消去 *****/ // n : 0,1 行目を指定 (8文字) void lcdclrline(byte n) { byte i; lcd.setCursor( 0, n); // カーソル位置 for(i = 0; i < 8; i++){ // 8文字 lcd.write(' '); } lcd.setCursor( 0, n); // カーソルを先頭位置に戻す } /***** 固定メッセージ表示出力 *****/ void lcdputsP(PGM_P s) { char c; while(1){ c = pgm_read_byte(s); if(c == '\x0') break; lcd.write(c); s++; } } /***** 位置を指定してのメッセージ表示出力 *****/ // s:文字アドレスのポインタ p:表示位置0~ n:表示行数 void lcdmsgP(PGM_P *s, byte p, byte n) { byte i; lcd.clear(); // 全消去 lcd.noBlink(); // カーソルoff for(i = 0; i < n; i++){ // 表示行数 lcd.setCursor(0, i); // カーソル位置 lcdputsP(*(s+p+i)); // 固定メッセージ表示 } } /********************************/ /* D/A出力 */ /********************************/ /***** 10bit D/A出力 *****/ // D/A=MCP4911 Vref=4.096V // in d=10bit D/Aデータ 0~1023 // VrefBFFなし,ゲイン=1,アクティブに void daout(word d) { byte i; d &= 0x03FF; // 10bitマスク d <<= 2; // 2bit左へ d |= 0x3000; // ゲイン1でアクティブに DASCK_L; // SCK=L DACS_L; // CS=Lにして転送開始 for(i = 0; i < 16; i++){ // 16bit if(d & 0x8000) DASDI_H; // MSBから else DASDI_L; // データ転送 DASCK_H; // クロックH/L d <<=1; // 左シフト MSBへ DASCK_L; } DACS_H; // CS=Hにして転送終了 } /********************************/ /* 計測実行処理 */ /********************************/ // 計測実行区分 byte j_exc; // 計測モード #define J_DISP 3 // データ表示 #define J_MENU 5 // メニュー選択 #define J_FRQCK 7 // 周波数チェック #define J_ADCK 9 // A/Dチェック #define J_PARA 11 // パラメータ設定 // 送信データシリアル番号 long tx_num; // tx_styleでNum指定がある時に出力 // 6桁(0~999999:11日)でゼロサプレス出力 // 最大6桁で0に戻す // ENT SW長押しでクリア word tx_cnt; // tx_cycチェック用カウンタ // 0でシリアル出力 // 電力量 float pwr_wh; // 1秒ごとに計算される電力を加算してWhを計算 // 電力量として表示,シリアル出力 byte f_pwhclr; // pwr_hrデータクリア指定 // onしていたらpwr_wh加算のタイミングでゼロクリア // 電力 min,max float pwr_min; // 電力min値 float pwr_max; // 電力max値 byte f_minmax; // 1の時にmin,max値を初期化 // tx_cnt==0の時 // データ送信開始フラグ byte f_datatxon; // 準備完了でデータ送信開始フラグをon // 表示データ byte f_disp; // f_capで電力計算した時にon char str_pwr[12]; // 電力表示文字列 xxx.xxW char str_frq[12]; // 周波数表示文字列 xxxx.xHz char str_pwh[12]; // 電力量 Wh // シリアル出力する時に設定 // 表示ルーチンで使う // A/Dチェックチャンネル byte adchk_ch; // ch0とch1をチェック // ↑↓で切り替え /***** パラメータ設定値をシリアル出力 *****/ void paratx(void) { byte i, typ; short d, *p; for(i = 0; i < DIMSIZ(Para_tbl); i++){ // EEPROMのアドレスと同じ typ = pgm_read_byte(&Para_tbl[i].typ); // データ区分 p = pgm_read_word(&Para_tbl[i].data); // データアドレス d = *p; if(typ != P_EPCK){ // EEPROMチェックはスキップ Serial.print(F("# ")); txputsP(msg_menu[i]); // メニュー文字 Serial.print(F(" ")); switch(typ){ case P_PWR: // 2点cal 電力値 0~99.99W txprintf("%4d.%02d W", d / 100, d % 100); break; case P_FRQ: // 2点cal 周波数 0~9999Hz txprintf("%7d Hz", d); break; case P_VRE: // Vref 基準電圧 4.096V txprintf("%3d.%03d V", d / 1000, d % 1000); break; case P_CYC: // データ送信サイクル 1s,10s,60s d = pgm_read_word(&Parad_cyc[d]); txprintf("%7d sec", d); break; case P_AVR: // 周波数計算平均回数 0~3 d = pgm_read_word(&Parad_avr[d]); txprintf("%3d ticks", d); break; case P_STL: // 送信データのスタイル 0~5 txputsP(msg_txs[d]); break; } Serial.println(); } } } /***** 電力量を表示データに *****/ // pを文字列str_pwhに設定 void strpwh(float pw) { float p; p = pw / 100.0 / 3600.0; // 0.01WsecからWhourに if(p < 999.99) dtostrf(p, 6, 2, str_pwh); // 123.56Wh else if(p < 9999.9) dtostrf(p, 6, 1, str_pwh); // 1235.6Wh else if(p < 99900.0) dtostrf(p/1000.0, 5, 2, str_pwh); // 12.34kWh else if(p < 999900.0) dtostrf(p/1000.0, 5, 1, str_pwh); // 123.5kWh else strcpy(str_pwh, "999.9"); if(p < 9999.9) strcat(str_pwh, "Wh"); else strcat(str_pwh, "kWh"); } /***** タイトル表示 *****/ void jttl(void) { lcd.clear(); lcd.setCursor( 0, 0); // LCD 上段 lcdputsP(msg_titl); // タイトル lcd.setCursor( 0, 1); // 下段 lcdputsP(msg_date); // 日付 Serial.println(); Serial.print(F("# ")); // タイトルと日付シリアル出力 txputsP(msg_titl); Serial.print(F(" 20")); // 2021- txputsP(msg_date); Serial.println(); tm_100ms = 20; // 2秒 j_exc++; } /***** タイトル表示 #1 *****/ void jttl1(void) { byte d; if(tm_100ms == 0){ // タイムアップ d = eparack(); // EEPROMパラメータチェック if(d){ // 0以外はエラー lcd.setCursor(0, 1); // 下段にイニシャル表示 lcdprintf("Init%4d", d); // 初動初期化は100を表示 tm_100ms = 20; // 2秒 } // パラメータをシリアル出力 paratx(); // パラメータ現在値を出力 j_exc++; // next } } /***** タイトル表示 #2 *****/ void jttl2(void) { if((tm_100ms == 0) && // タイムアップ (f_disp != 0)){ // 最初の周波数確定 f_swon = 0; // SW onフラグをクリア f_pwhclr = 1; // pwr_hrデータクリア指定on f_datatxon = 1; // データ送信開始フラグをon f_minmax = 1; // 電力min,max値を初期化指令 j_exc++; // next } } /******************************/ /* 液晶データ表示 * /******************************/ // 表示指令(1秒サイクル)で電力と電力量を表示 // SW入力をチェック /***** データ表示 *****/ void jdisp(void) { lcd.clear(); tx_cnt = 0; // tx_cycチェックカウンタもクリア f_minmax = 1; // 次capタイミングでmin,max初期化 f_disp = 1; // 表示指令on j_exc++; // next } /***** データ表示 #1 *****/ // 現在電力(W)と電力量(Wh)を表示 void jdisp1(void) { if(f_disp){ // 表示指令あり f_disp = 0; lcd.setCursor( 0, 0); // LCD 上段 lcd.print(str_pwr); // 1234.56Wで電力表示 lcd.print("W"); lcd.setCursor( 0, 1); // LCD 下段 lcd.print(str_pwh); // 123.45whで表示 } // スイッチ入力チェック switch(swonchk()){ case SW_ENT: // ENT (短押し) j_exc = J_MENU; // メニューへ break; case SW_CAN: // CAN (長押し) tx_num = 0L; // 送信連番クリア tx_cnt = 0; // tx_cycチェックカウンタもクリア f_minmax = 1; // 次capタイミングでmin,max初期化 f_pwhclr = 1; // pwr_hrデータクリア指定on paratx(); // パラメータ現在値を出力 break; case SW_UP: // ↑ j_exc = J_FRQCK; // 周波数チェックへ break; case SW_DN: // ↓ j_exc = J_ADCK; // A/Dチェックへ break; } } /***********************************/ /* メニュー */ /***********************************/ byte menu_cp; // カーソル位置 0~6 byte menu_dp; // 表示先頭位置 0~5 /***** メニュー *****/ void jmenu(void) { lcdmsgP(msg_menu, menu_dp, LCD_ROWS); // メニュー表示 2行 lcd.setCursor(1, menu_cp - menu_dp); // カーソル位置 lcd.blink(); // カーソル点滅 j_exc++; // 次exc } /***** メニュー #1 *****/ void jmenu1(void) { static const byte j[] PROGMEM = { // mes_exc番号 J_DISP, // 0 "0 Mesure" J_PARA, // 1 "1 PWR-Lo" ※パラメータの先頭 J_PARA, // 2 "2 FRQ-Lo" J_PARA, // 3 "3 PWR-Hi" J_PARA, // 4 "4 FRQ-Hi" J_PARA, // 5 "5 DA4.0V" J_PARA, // 6 "6 Vref " J_PARA, // 7 "7 TxCyc " J_PARA, // 8 "8 Avrg " J_PARA, // 9 "9 TxStyl" }; switch(swonchk()){ case SW_ENT: // ENT (短押し) lcd.clear(); // 液晶消して lcdmsgP(msg_menu, menu_cp, 1); // 項目表示 j_exc = pgm_read_byte(&j[menu_cp]); // それぞれへ f_disp = 1; // 表示指令on break; case SW_CAN: // CAN (長押し) lcd.clear(); j_exc = J_DISP; // データ表示へ break; case SW_UP: // ↑ if(menu_cp > 0){ // カーソルが先頭以外の時 if(menu_cp == menu_dp){ menu_dp--; // 表示先頭を上へ } menu_cp--; // カーソルを上へ j_exc--; } break; case SW_DN: // ↓ if(menu_cp < (DIMSIZ(msg_menu) - 1)){ // 最終行以外の時 if((menu_cp - menu_dp) >= (LCD_ROWS - 1)){ menu_dp++; // 表示先頭を下へ } menu_cp++; // カーソル位置を下へ j_exc--; } } } /********************************/ /* 周波数チェック */ /********************************/ /***** 周波数チェック *****/ void jfrqck(void) { lcd.clear(); // いったんクリア f_disp = 1; // 表示指令on j_exc++; // next } /***** 周波数チェック #1 *****/ // 10回平均処理値と生周波数を表示 void jfrqck1(void) { float frq; long fov; char s[12]; if(f_disp){ // 表示指令 f_disp = 0; frq = frqavr(10); // 周波数計算(平均処理) cli(); // 割り込み禁止 fov = ovf_frq; // OVF処理した周波数 sei(); // 割り込み再開 dtostrf(frq, 6, 1, s); // xxxx.xに lcd.setCursor( 0, 0); // LCD 上段 lcd.print(s); // 1234.5Hzで表示 lcd.print("Hz"); // OVF処理した周波数 lcd.setCursor( 0, 1); // LCD 下段 if(fov < 9999){ // 9.999kHzまで lcdprintf("%1ld.%03ldkHz", fov / 1000, fov % 1000); // kHzで表示 } else{ // 10kHz以上 lcdprintf("%4ld.%03ld", // 9.99MHzまで fov / 1000, fov % 1000); // kHzで表示 } } switch(swonchk()){ case SW_ENT: // ENT (短押し) j_exc = J_MENU; // メニューヘ break; case SW_CAN: // CAN (長押し) j_exc = J_DISP; // データ表示へ case SW_UP: // ↑ case SW_DN: // ↓ j_exc = J_ADCK; // A/Dチェックへ break; } } /********************************/ /* A/Dデータチェック */ /********************************/ /***** A/D チェック *****/ void jadck(void) { lcd.clear(); // いったんクリア f_disp = 1; // 表示指令on j_exc++; // next } /***** A/D チェック #1 *****/ // ↓SWでch切り替え void jadck1(void) { char s[12]; if(f_disp){ // 表示指令あり? f_disp = 0; lcd.setCursor( 0, 0); // LCD 上段 lcdprintf("ch%d %4d", // chとA/D生データ 0~1023 adchk_ch, ad_data[adchk_ch]); lcd.setCursor( 0, 1); // LCD 下段 dtostrf(ad_volt[adchk_ch], 7, 3, s); // xxx.xxxに lcdprintf("%sV", s); // 1.234Vで表示 } switch(swonchk()){ case SW_ENT: // ENT (短押し) j_exc = J_MENU; // メニューヘ break; case SW_CAN: // CAN (長押し) j_exc = J_DISP; // データ表示へ break; case SW_UP: // ↑ j_exc = J_FRQCK; // 周波数チェックへ break; case SW_DN: // ↓ adchk_ch += 1; // chを変える if(adchk_ch >= DIMSIZ(ad_data)) adchk_ch = 0; j_exc--; break; } } /********************************/ /* パラメータ設定 */ /********************************/ byte para_typ; short para_data; short para_max; short *para_ptr; /***** オートリピート時の加減算データ *****/ // 押し続けていたら加速 short pararept(void) { short a; a = 1; // 通常は+/-1 if((sw_rpt >= 100) && // SWリピート回数 5.5秒 ((para_data % 100) == 0)){ // 000,100,200... a = 100; } else if((sw_rpt >= 40) && // SWリピート回数 2.5秒 ((para_data % 20) == 0)){ // 00,20,40... a = 20; } return a; } /***** パラメータ設定 *****/ void jpara(void) { para_typ = pgm_read_byte(&Para_tbl[menu_cp].typ); // データ区分 para_ptr = pgm_read_word(&Para_tbl[menu_cp].data); // データアドレス para_max = pgm_read_word(&Parad_max[para_typ]); // 区分からmax値 para_data = *para_ptr; // データ j_exc++; } /***** パラメータ設定 #1 *****/ // データ表示 void jpara1(void) { short d; lcd.setCursor( 0, 1); // LCD 下段 switch(para_typ){ case P_PWR: // 2点cal 電力値 0~99.99W lcdprintf("%4d.%02dW", para_data / 100, para_data % 100); break; case P_FRQ: // 2点cal 周波数 0~9999Hz lcdprintf("%6dHz", para_data); break; case P_VRE: // Vref 基準電圧 4.096V lcdprintf("%3d.%03dV", para_data / 1000, para_data % 1000); break; case P_CYC: // データ送信サイクル 1s,10s,60s d = pgm_read_word(&Parad_cyc[para_data]); lcdprintf("%5dsec", d); break; case P_AVR: // 周波数計算平均回数 0~3 d = pgm_read_word(&Parad_avr[para_data]); lcdprintf("%3dticks", d); break; case P_STL: // 送信データのスタイル 2種 lcdputsP(msg_txs[para_data]); break; } j_exc++; } /***** パラメータ設定 #2 *****/ // 数値変更 void jpara2(void) { switch(swonchk()){ case SW_ENT: // ENT (短押し) *para_ptr = para_data; //パラメータ保存 EEPROM.put(2*menu_cp, para_data); // EEPROM書き込み j_exc = J_MENU; // メニューへ戻る break; case SW_CAN: // CAN (長押し) lcd.clear(); j_exc = J_DISP; // データ表示へ break; case SW_UP: // ↑ para_data += pararept(); // データinc if(para_data > para_max) para_data = para_max; j_exc--; // 表示へ break; case SW_DN: // ↓U para_data -= pararept(); // データdec if(para_data < 0) para_data = 0; j_exc--; // 表示へ break; } } /********************************/ /* 計測実行テーブル */ /********************************/ /***** 測定実行 *****/ void (*job_tbl[])(void)={ jttl, // 0 タイトル表示 jttl1, // 1 jttl2, // 2 jdisp, // *3 表示実行 jdisp1, // 4 jmenu, // *5 メニュー jmenu1, // 6 jfrqck, // *7 周波数チェック jfrqck1, // 8 jadck, // *9 A/Dチェック jadck1, // 10 jpara, // *11 パラメータ設定 jpara1, // 12 jpara2, // 13 }; /*****************************/ /* セットアップ */ /*****************************/ /***** SETUP *****/ void setup() { cli(); // 割り込み禁止 // I/Oイニシャル PORTB = 0b00000011; // data/pull up DDRB = 0b00111110; // port in/out指定 // |||||+---- PB0 D8 in ICP1キャプチャー 1Hzパルス入力 // ||||+----- PB1 D9 out D/A /CS // |||+------ PB2 D10 out LCD DB4,D/A SCK(共用) // ||+------- PB3 D11 out OCA2A 500Hz出力 // |+-------- PB4 D12 out LCD DB5,D/A SDI(共用) // +--------- PB5 D13 out (LED) PORTC = 0b00000000; // data/pull up DDRC = 0b00111100; // portin/out指定 // |||||+---- PC0 A0 in A/D ch0入力 // ||||+----- PC1 A1 in A/D ch1入力 // |||+------ PC2 A2 out LCD DB6 // ||+------- PC3 A3 out LCD DB7 // |+-------- PC4 A4 out LCD RS // +--------- PC5 A5 out LCD E PORTD = 0b10111111; // data/pull up DDRD = 0b01000010; // portin/out指定 // |||||||+---- PD0 D0 in RXD シリアルデータ入力 // ||||||+----- PD1 D1 out TXD シリアルデータ出力 // |||||+------ PD2 D2 in SW1 // ||||+------- PD3 D3 in SW2 // |||+-------- PD4 D4 in T0 500Hz入力 // ||+--------- PD5 D5 in T1 測定周波数入力 // |+---------- PD6 D6 out OC0A 1Hz出力 // +----------- PD7 D7 in SW3 // タイマー0,T0を500Hzクロック入力にしてOC0Aに1Hz出力 // ※タイマー0はシステムで使われている TCCR0B = 0b00000000; // タイマー0停止 TIMSK0 = 0b00000000; // 割り込み禁止に TCCR0A = 0b01000010; // |||| ++--- WGM CTCモード CR0A // ||++------- COM0B ポート // ++--------- COM0A ポート トグル出力 TCCR0B = 0b00000110; // || |+++--- CS clk T0↓エッジ入力 // || +------ WGM // |+--------- FOC0B // |---------- FOC0A OCR0A = 250 - 1; // コンペアマッチでトグル出力 1/500 // タイマー1,インプットキャプチャ TCCR1A = 0b00000000; // |||| ++--- WGM 標準動作 // ||++------- COM1B ポート // ++--------- COM1A ポート TCCR1B = 0b00000110; // || ||+++--- CS clk T1↓エッジ入力 // || ++------ WGM // |+--------- ICES1 ICP1入力↓エッジ // |---------- ICNC1 ノイズキャンセルなし TIMSK1 = 0b00100001; // 割り込み設定 // | ||+--- TOIE1 オーバーフロー割込on // | |+---- OCIE1A // | +----- OCIE1B // +-------- ICIE1 キャプチャー割込on // タイマー2,CTCモード 1msで割り込み OC2Aに500Hz出力 TCCR2A = 0b01000010; // |||| ++--- WGM1,0 CTCモード // ||++------- OC2B ポート // ++--------- OC2A ポート トグル出力 TCCR2B = 0b00000101; // |+++--- CS 2,1,0 16MHz/128 125kHz // +------ WGM2 OCR2A = 125 - 1; // 1kHz TIMSK2 = 0b00000010; // 割り込み // ||+--- TOIE2 // |+---- OCIE2A 割込on // +----- OCIE2B // A/D入力 DIDR0 = 0b00000011; // AD0,AD1 デジタル入力禁止 ADMUX = 0b00000000; // 内蔵A/D // ||| ++++---- ADC0 // ||+--------- ADLAR // ++---------- 外部 AREF接続 ADCSRA = 0b10001111; // |||||+++---- プリスケーラ 1/128 125kHz // ||||+------- ADIE 割込あり // |||+-------- ADIF // ||+--------- ADATE // |+---------- ADSC // +----------- ADEN 有効に sei(); // 割込許可 // シリアル Serial.begin(9600); // 9600BPSで // 液晶 lcd.begin(LCD_COLS, LCD_ROWS); // LCD 8文字x2行 } /******************************/ /* ループ */ /******************************/ /***** LOOP *****/ void loop() { word n; float frq, pwr; short da; char s0[10], s1[10]; // A/D ch0,1 電力min,max while(1){ job_tbl[j_exc](); // 計測実行 addata(); // A/Dデータ転送 // ad_dataは割り込みを気にせずアクセス可能 // 毎秒のキャプチャーのたびにシリアル出力 if(f_cap){ // キャプチャあり f_cap = 0; // 周波数 n = pgm_read_word(&Parad_avr[avr_frq]); // 平均回数 1,2,5,10 frq = frqavr(n); // 周波数計算(平均処理) dtostrf(frq, 6, 1, str_frq); // xxxx.xに // 電力 pwr = frqtopwr(frq); // 電力に変換 dtostrf(pwr/100.0, 7, 2, str_pwr); // 0.01W単位で表示 1234.56 // 電力min,maxチェック if(f_minmax){ // min,max初期化 f_minmax = 0; pwr_min = pwr_max = pwr; // min,maxを現在値に } else{ // min,max判定 if(pwr < pwr_min) pwr_min = pwr; if(pwr > pwr_max) pwr_max = pwr; } // 電力量 pwr_wh += pwr; // 毎秒電力量に加算 strpwh(pwr_wh); // xxx.xxWh表示文字列に変換 // D/A出力 da = (short)((pwr * 4.0 * 1024.0) / // スケーリング ((float)da_4v * ((float)v_ref * 0.001))); if(da < 0) da = 0; else if(da > 1023) da = 1023; // 10bit max daout(da); // D/A出力 // A/D値を電圧に dtostrf(ad_volt[0], 4, 2, s0); // A/D ch0 x.xxに dtostrf(ad_volt[1], 4, 2, s1); // ch1 // シリアル出力, tx_cycをチェック if(tx_cnt) tx_cnt--; // 送信回数カウンタ -1 if((tx_cnt == 0) && // 0になったら出力 (f_datatxon)){ // データ送信開始ok? if(f_pwhclr){ // pwr_hrデータクリア指定on? f_pwhclr = 0; pwr_wh = 0.0; // 電力量クリア pwr_min = pwr_max = pwr; // 電力min,max初期化 } tx_cnt = pgm_read_word(&Parad_cyc[tx_cyc]); // 次サイクル f_minmax = 1; // 次capタイミングでmin,max初期化 // 出力スタイル switch(tx_style){ // 出力データのスタイル case 2: // 連番 + データ case 3: // 連番 + データ + min,max txprintf("%6ld ", tx_num); // 連番出力 break; } strpwh(pwr_wh); // xxx.xxWh表示文字列に変換 txprintf("%sW %sV %sV %s", // 電力,A/Dch0,A/Dch1,電力量 str_pwr, s0, s1, str_pwh); switch(tx_style){ // 出力データのスタイル case 1: // データ + min,max case 3: // 連番 + データ + min,max dtostrf(pwr_min/100.0, 7, 2, s0); // min,maxをs0,s1に dtostrf(pwr_max/100.0, 7, 2, s1); // 0.01W単位で 1234.56 txprintf(" %sW %sW", s0, s1); // シリアル出力追加 break; } Serial.println(); // 改行 // 送信連番+1 tx_num++; if(tx_num > 999999L) tx_num = 0L; // 6桁maxで0に戻す } // 表示指令 f_disp = 1; // 表示okフラグをオン } } } /*===== end of "pwrmon3.ino" =====*/