/****************************************/ /* USB電源供給 電流電圧測定回路 */ /****************************************/ // Arduino UNO(のATmega328P)で制御 // 16bit ADC LTC2460で電流を計測 // LTC2462も使えるように (#define LTC2462を1に) // 電流検出アンプはINA281A3(x100) // A/D ch0(10bit)で電圧を入力 // 4桁のカソードコモン7seg LEDに表示 // カソード側をNPN Trで駆動 // SW操作で表示(電流,電圧)を切り替え // 長押しでcyc_cnt=0にしてタイトル,データ出力から // 1秒サイクルで電流,電圧値をシリアル出力 // 1,2,5~1800,3600秒で設定できるように // シリアル入力で校正操作 // Ent連続2回で校正(パラメータ設定)モードに // 入力はEntでスキップ // +-で数値増減してEntで確定 (+は^で代替可) // A/D値は ピリオドで現A/Dデータを設定 // ESCでキャンセル // キー入力がバッファリングされないターミナル(Tera termなど)で操作。 // Tera termでのテンキー「+/-」の扱いはhelpを検索のこと。 // 設定の順番 // データ送信タイミング 1,2,5,10,30,60,300,600,1800,3600秒を選択 // 校正点A1 電流値を設定 0.0mA Entでスキップ +-増減 // A1 A/D値を設定 「.」でA/D値を代入 Entで確定 // 校正点A2 電流値を設定 900.0mA // A2 A/D値を設定 // 校正点V1 電圧値を設定 5.0V // V1 A/D値を設定 // 校正点V2 電圧値を設定 24.0V // V2 A/D値を設定 // 通常運転に戻る // I/Oポート // PB0 out LTC2460 ADC /CS出力 // PB1 out LTC2460 ADC CLK出力 // PB2 in LTC2460 ADC DO出力を入力(pull down) // PB3 out 7seg A カソードコモン Hでon // PB4 out 7seg B // PB5 out 割り込みタイミング // PC0 in A/D ch5 電圧入力 // PC1 in SW入力 Lでon(pull up) // PC2 out LED COM1 右桁 カソードコモン Hでon // PC3 out LED COM2 NPN Trで駆動 // PC4 out LED COM3 // PC5 out LED COM4 左桁 // PD0 in RXD // PD1 out TXD // PD2 out 7seg C Hでon // PD3 out 7seg D // PD4 out 7seg E // PD5 out 7seg F // PD6 out 7seg G // PD7 out 7seg dp // タイマー0 (システムで使用) 停止 // タイマー1 500HzでLEDダイナミックスキャン // コンペアマッチBでLEDを消灯 // コンペアマッチAでA/D変換開始 // A/D変換完了割り込みでLED表示再開 // 2023-02-07 # CUR_16BIT1.ino // 2024-03-19 # CUR_16BIT1a.ino // 1秒サイクルでの電流,電圧値をシリアル出力を // 1~3600秒で設定できるようにパラメータを増やす /***** ライブラリ ****/ #include /***** タイトルと日付 *****/ const char msg_ttl0[] PROGMEM = "# Cur,Volt checker. 16bit ADC"; const char msg_ttl1[] PROGMEM = "# Cur :999.9mA max"; const char msg_ttl2[] PROGMEM = "# Volt: 30.0V max"; const char msg_ttl3[] PROGMEM = "# CUR_16BIT1a.ino 2024-03-19"; PGM_P msg_ttl[]={ msg_ttl0, msg_ttl1, msg_ttl2, msg_ttl3, }; /***** マクロ *****/ #define DIMSIZ(a) (sizeof(a)/sizeof(*a)) // 配列のデータ数を返す #define nop() asm("nop") /***** I/O制御 *****/ // SW入力 on,offチェック(onで1) #define INP_SW ((~PINC) & (1 << PORTC1)) // PC1 表示切り替えSW // ポートH/L制御出力 #define PB5_H (PORTB |= (1 << PB5)) // PB5 19pin #define PB5_L (PORTB &= ~(1 << PB5)) // ADC /CS,CLK制御, DO入力 #define AD_INP (PINB & (1 << PORTB2)) // PB2 H/L入力 #define ADCS_H (PORTB |= (1 << PB0)) // PB0 ADC /CS制御 #define ADCS_L (PORTB &= ~(1 << PB0)) #define ADCK_H (PORTB |= (1 << PB1)) // PB1 ADC CLK制御 #define ADCK_L (PORTB &= ~(1 << PB1)) // /***** タイマーデータ *****/ volatile byte tm_10ms; // 10ms ダウンカウントタイマー // 0で停止 volatile byte tm_ent; // Entキー入力間隔チェックタイマー // 10msでダウンカウント volatile byte cnt_10ms; // 10ms up カウンタ // 0~99 100でf_1secをon volatile byte f_10ms; // 10ms経過フラグ volatile byte f_1sec; // 1秒経過フラグ /***** A/Dデータ *****/ // LTC2460,LTC2462を入力 #define LTC2462 0 // ADC種別 // 0 : LTC2460 シングルエンド入力 // 1 : LTC2462 差動入力(15bit分解能に) // CS=Lにしてbusy(H)をチェック // Hならreadyなので読み出し // A/Dデータ busy中は前回値を保持 volatile word ltc_2460; // LTC2460 A/Dデータ // 16bit 0~65535 // ADC 区分 #define CH_A 0 // 0:電流 16bit ADC LTC2460 #define CH_V 1 // 1:電圧 10bit 内蔵ADC #define AD_ACNT 256 // A/D平均回数 内蔵ADCの変換回数 // 2msサイクルで2ch, 256回 =512msで確定 volatile long ad_adx[2]; // A/D平均処理用加算データ // longで 割り込みで加算 volatile long ad_add[2]; // 平均加算データの結果 回数チェックでコピー // 平均回数経過後メインで平均処理を volatile word ad_cntup[2]; // A/D平均回数 // 割り込みでカウントアップ volatile word ad_cnt[2]; // A/D平均回数 // ad_addが確定した時の回数 volatile byte f_adok; // A/D変換完了フラグ // 内蔵ADCの回数をチェック 割り込みでセット // A/D平均値 word ad_avr[2]; // A/D平均処理結果 // 0:電流 16bit A/D値 0~65535 // 1:電圧 10bit A/D値 0~1023 // いつでもアクセス可能 // 測定データ 出力値 ad_avrからmapfで計算 word mes_data[2]; // 平均値から算出 // 0:電流値(CH_A) 000.0~999.9mA // 1:電圧値(CH_V) 0.0~24.0V long cyc_cnt; // 1秒ごとに出力するサイクルカウンタ // max 999999 6桁 277時間で11日 short sec_cnt; // 電流電圧出力タイミングチェック用カウンタ // f_1secでカウントアップ // tx_secでデータ出力 /***********************************/ /* パラメータ設定データ */ /***********************************/ // パラメータ設定データ(+/-するのでlongで) long a1_y; // 2点cal 電流値 Lo 0.0mA long a1_x; // A/D値 long a2_y; // 2点cal 電流値 Hi 900.0mA long a2_x; // A/D値 long v1_y; // 2点cal 電圧値 Lo 5.0V long v1_x; // A/D値 long v2_y; // 2点cal 電圧値 Hi 24.0V long v2_x; // A/D値 long tx_sec; // 電流電圧データ出力サイクル(1秒単位) // パラメータ区分 #define P_CHK 0 // チェックデータ #define P_AY 1 // 電流 mA値設定 #define P_AX 2 // 電流 A/D値設定 #define P_VY 3 // 電圧 V値設定 #define P_VX 4 // 電圧 A/D値設定 #define P_SEC 5 // 秒設定 データ出力サイクル // min,max値 struct st_minmax{ const long pmin PROGMEM; // min値 const long pmax PROGMEM; // max値 }; const st_minmax p_minmax[] PROGMEM={ { 0x1234, 0x1234 }, // 0 PCHK チェックデータ { 0, 9999 }, // 1 P_AY 電流 mA値設定 999.9mA max { 0, 65535 }, // 2 P_AX 電流 A/D値設定 16bit { 0, 400 }, // 3 P_VY 電圧 V値設定 40.0V max { 0, 1023 }, // 4 P_VX 電圧 A/D値設定 10bit { 0, 9 }, // 5 P_SEC 秒設定 データ出力サイクル(txsec_tbl) }; // パラメータ設定データ struct st_param{ const byte typ PROGMEM; // パラメータ区分 const long *data PROGMEM; // データアドレス const long ini PROGMEM; // 初期値 }; const st_param p_param[] PROGMEM={ { P_CHK, NULL, 0x1234 }, // 0 チェックデータ { P_SEC, &tx_sec, 3 }, // 1 秒設定 データ出力サイクル(txsec_tbl) { P_AY, &a1_y, 100 }, // 2 電流 Lo 10.0mA { P_AX, &a1_x, 860 }, // 3 電流 Lo A/D値 { P_AY, &a2_y, 9000 }, // 4 電流 Hi 900.0mA { P_AX, &a2_x, 45932 }, // 5 電流 Hi A/D値 { P_VY, &v1_y, 50 }, // 6 電圧 Lo 5.0V { P_VX, &v1_x, 175 }, // 7 電圧 Lo A/D値 { P_VY, &v2_y, 240 }, // 8 電圧 Hi 24.0V { P_VX, &v2_x, 848 }, // 9 電圧 Hi A/D値 }; // パラメータ設定メニュー const char msg_p0[] PROGMEM = ""; // 0 チェックデータ const char msg_p1[] PROGMEM = "1 TXcyc sec"; // 1 秒設定 データ出力サイクル const char msg_p2[] PROGMEM = "2 mA-Lo"; // 2 電流 Lo 1.0mA const char msg_p3[] PROGMEM = "3 mA-Lo A/D"; // 3 電流 Lo A/D値 const char msg_p4[] PROGMEM = "4 mA-Hi"; // 4 電流 Hi 300.0mA const char msg_p5[] PROGMEM = "5 mA-hi A/D"; // 5 電流 Hi A/D値 const char msg_p6[] PROGMEM = "6 V-Lo"; // 6 電圧 Lo 4.50V const char msg_p7[] PROGMEM = "7 V-Lo A/D"; // 7 電圧 Lo A/D値 const char msg_p8[] PROGMEM = "8 V-Hi"; // 8 電圧 Hi 5.50V const char msg_p9[] PROGMEM = "9 V-Hi A/D"; // 9 電圧 Hi A/D値 PGM_P msg_para[]={ // 012345679801 msg_p0, // 0 (チェックデータ) msg_p1, // 1 tx_cyc msg_p2, // 2 a1_y msg_p3, // 3 a1_x msg_p4, // 4 a2_y msg_p5, // 5 a2_x msg_p6, // 6 v1_y msg_p7, // 7 v1_x msg_p8, // 8 v2_y msg_p9, // 9 v2_x }; // データ出力サイクルの秒値テーブル tx_sec:0~9 const word txsec_tbl[] PROGMEM={ 1, // 0 1秒 2, // 1 2秒 5, // 2 5秒 10, // 3 10秒 30, // 4 30秒 60, // 5 1分 300, // 6 5分 600, // 7 10分 1800, // 8 30分 3600, // 9 1時間 }; /***** EEPROMパラメータチェック *****/ // データをmin,max値と比較 // okならパラメータを呼び出し(0でリターン) // 違えば初期値を書き込み (1以上でリターン) // チェック時にSWがonなら初期値書き込み byte eparack(void) { byte i, typ; long ini, d, m, n, *p; byte er = 0; // EEPROMチェック結果 for(i = 0; i < DIMSIZ(p_param); i++){ // EEPROMのアドレスと同じ typ = pgm_read_byte(&p_param[i].typ); // データ区分 EEPROM.get(4*i, d); // EEPROMの値をリード if(typ == P_CHK){ // チェックデータ ini = pgm_read_dword(&p_param[i].ini); // 初期値 if(d != ini) er++; // 異なったらエラー } else{ // P_CHK以外 m = pgm_read_dword(&p_minmax[typ].pmax); // 区分からmax値 n = pgm_read_dword(&p_minmax[typ].pmin); // 区分からmin値 if((d < n) || (d > m)) er++; // 範囲外はエラー } } // SWがオンなら強制的に初期化 if(INP_SW) er += 100; // 強制初期化 // エラーあれば初期値をEEPROMに if(er){ // EEPROMに初期値を書き込む for(i = 0; i < DIMSIZ(p_param); i++){ // EEPROMアドレス ini = pgm_read_dword(&p_param[i].ini); // 初期値 EEPROM.put(4*i, ini); // EEPROM書き込み } } // EEPROMからパラメータを読む for(i = 0; i < DIMSIZ(p_param); i++){ // EEPROMのアドレスと同じ p = pgm_read_word(&p_param[i].data); // データアドレス EEPROM.get(4*i, d); // EEPROMの値 if(p != NULL) *p = d; // パラメータにセット } return er; // 1以上なら初期化した } /**************************/ /* 計算 */ /**************************/ /***** 線形補間 *****/ // 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; } /***** 電流電圧計算用テーブル *****/ struct st_cv{ long *x1; // X Lo A/D値 (10bit,16bit) long *x2; // X Hi long *y1; // Y Lo 変換値(mA,V) long *y2; // Y Hi }; st_cv p_cv[]={ { &a1_x, &a2_x, &a1_y, &a2_y }, // 0: 電流 { &v1_x, &v2_x, &v1_y, &v2_y }, // 1: 電流 }; /***** 電流電圧値計算 *****/ // ch: 0=電流(CH_A), 1=電圧(CH_V) // 電流:4桁の0.1mA値で 電圧:3桁の0.1V値で // mes_dataに結果を残す void curvolt(byte ch) { float a; long g; a = mapf((float)ad_avr[ch], // 電流 A/D値 (float)*p_cv[ch].x1, // X1 (float)*p_cv[ch].x2, // X2 (float)*p_cv[ch].y1, // Y1 (float)*p_cv[ch].y2); // Y2 if(a < 1.0) a = 0.0; // 1.0未満は0.0に g = lround(a); // 四捨五入 // if(g < 0) g = 0; // マイナスは0に mes_data[ch] = (word)g; // 測定値 } /**************************/ /* シリアル入出力 */ /**************************/ /***** 1文字送信 *****/ void tx(char c) { Serial.write(c); } /***** 改行 *****/ void txcrlf(void) { Serial.println(); // 改行 } /***** 固定メッセージ出力 *****/ void txstrP(PGM_P s) { char c; while(1){ c = pgm_read_byte(s); if(c == '\x0') break; tx(c); s++; } } /***** 複数行の固定メッセージ出力 *****/ // s:文字アドレスのポインタ n:行数 void txmsgP(PGM_P *s, byte n) { byte i; for(i = 0; i < n; i++){ // 表示行数 txstrP(*(s + i)); // 固定メッセージ出力 Serial.println(); // 改行 } } /***** 書式付シリアル出力 *****/ // PSTR("")で書式を指定 void txprintfP(const char *s, ...) { va_list vp; char bff[40]; // バッファを確保 va_start(vp, s); vsnprintf_P(bff, sizeof(bff), s, vp); // PSTRで書式を指定 Serial.print(bff); // シリアル出力 va_end(vp); } /***** 桁指定BCD出力 *****/ static char b_bff[18]; // 18文字(null入れず) // n:整数部文字数 p:小数部文字数 // 数値は整数を入力 浮動小数点ではない // 小数部は整数の基数が0.01などの時に用いる // 12345が123.56を意味する時 (d,3,2)と指定 void txbcd(int32_t d, byte n, byte p) { byte i, m; byte sgn = 0; // 符号フラグ memset(b_bff, ' ', sizeof(b_bff)); // スペースで埋める if(d < 0){ // マイナス? d = -d; sgn = 1; } m = n + p; // 整数+小数 文字数 for(i = 0; i < (sizeof(b_bff) - 2); i++){ // マイナスの分を残して b_bff[i] = (d % 10) + '0'; // 下位桁から0~9に d = d / 10; // 1/10して上位桁の処理 if((d == 0) && // 0になったら終わり (i >= p)) break; // 小数桁ok? } if(sgn){ // 元値がマイナス? b_bff[i + 1] = '-'; } for(i = 0; i < m; i++){ // 文字数loop tx(b_bff[m - i - 1]); // バッファを逆順で出力 if(p){ // 小数出力? if(i == (m - p - 1)) tx('.'); // 小数点 } } } /********************************/ /* スイッチ入力チェック */ /********************************/ volatile byte f_swon; // スイッチ短押しonフラグ volatile byte f_swhold; // スイッチ長押しonフラグ // onエッジを検出 /***** スイッチ入力チェック *****/ // 2msサイクルで処理 void swchk(void) { static byte exc1; // SWチェック 実行区分 static word tm1;; // SWチェック タイマー if(tm1) tm1--; // タイマー -1 switch(exc1){ case 0: // オフチェック tm1 = 10 / 2; // 10msセット exc1++; break; case 1: // オフ安定チェック if(INP_SW){ // まだonならタイマー再設定 exc1--; } else{ // off if(tm1 == 0){ // 安定でonチェックへ exc1++; } } break; case 2: // オン待ち if(INP_SW){ // on ? tm1 = 10 / 2; // 10ms exc1++; } break; case 3: // 10ms安定チェック if(INP_SW){ // on 継続 if(tm1 == 0){ // タイムアップでon安定 tm1 = 1000 / 2; // 長押しは1秒 exc1++; } } else{ // offした exc1--; // オン待ちに戻す } break; case 4: // 長押し判定 if(INP_SW){ // on 継続 if(tm1 == 0){ // 1秒経過? f_swhold = 1; // 長押し確定フラグをon exc1 = 0; // オフ待ちへ } } else{ // 途中で離されたら f_swon = 1; // 短on確定 exc1 = 0; // オフ待ちへ } break; } } /****************************/ /* LED表示 */ /****************************/ // 4桁LED駆動データ volatile byte led_bff[4]; // COM1,2,3,4(最左桁)の順 volatile byte led_ptr; // 表示COM駆動ポインター 0~3 // 0でCOM1(最右桁) // 7seg セグメントデータ Hでオン // カソードコモン const byte led_seg[] PROGMEM ={ // dp GFEDCBAセグメント 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 }; // |||||||+----- A // ||||||+------ B // |||||+------- C // ||||+-------- D // |||+--------- E // ||+---------- F // |+----------- G // +------------ dp // カソードコモン駆動 // NPN Trを入れているのでHでon const byte led_com[] PROGMEM ={ // COM駆動 PC0,1,2,3 0b00000100, // COM1 PC2 1でon 0b00001000, // COM2 PC3 0b00010000, // COM3 PC4 0b00100000, // COM4 PC5 }; /***** 電流表示区分 *****/ byte cur_range; // 電流表示レンジ // 0:123.4mA 1:1.234A // 10000以上で1.234A表示に // 9000未満で123.4mAに戻す /***** LED 4桁表示 *****/ // d 0~9999 表示データ // CH_Aはmes_data[CH_A]が9999越えで点滅 // CH_Vはad_avr[CH_V]が1023越えで点滅 // マイナスは0に // mode 0:CH_A 123.4 電流表示用 上位ゼロサプレス // 1:CH_V 12.3 電圧表示用 下位はブランクで void led4bcd(byte ch) { byte i, b; byte blk = 0; // 点滅フラグ word a, d; a = ad_avr[ch]; // A/D値 d = mes_data[ch]; // 測定値(電流,電圧) if(ch == CH_A){ // 電流A/Dがmaxなら点滅 if(a >= 65535) blk = 1; if(cur_range == 0){ // 123.4mA表示の時 if(d >= 10000) cur_range = 1; // 10000以上で1.234A表示に } else{ // 1.234A表示の時 if(d <= 9000) cur_range = 0; // 9000以下で123.4mAに戻す } } else if(ch == CH_V){ // 電圧A/Dがmaxなら if(a >= 1023) blk = 1; // 点滅する } if(blk){ if(cnt_10ms >= 75){ // 0~74は表示75~99はブランク led_bff[0] = // 4桁ともブランクに led_bff[1] = led_bff[2] = led_bff[3] = 0; return; // ここでおわり } } // chで表示を区分 ゼロサプレスと小数点の処理 // CH_A __0.9 CH_V _5.0_ if(ch == CH_A){ // 電流表示 if(cur_range == 0){ // 123.4mA表示 for(i = 0; i < DIMSIZ(led_bff); i++){ // COM1(1桁から) b = pgm_read_byte(&led_seg[d % 10]); // 下位桁 0~9 if((d == 0) && // データゼロ? (i > 1)){ // 10桁より左 b = 0; // ブランクに } else{ if(i == 1){ // 10桁ならdp b |= 0b10000000; // dpをオン } } led_bff[i] = b; // segデータ書き込み d = d / 10; // データを1/10してloop } } else{ // 1.234A表示 d += 5; // 0.1mA桁で四捨五入 d = d / 10; // 1mAを最小桁に for(i = 0; i < DIMSIZ(led_bff); i++){ // COM1(1桁から) b = pgm_read_byte(&led_seg[d % 10]); // 下位桁 0~9 if(i == 3){ // 1000桁ならdp b |= 0b10000000; // dpをオン } led_bff[i] = b; // segデータ書き込み d = d / 10; // データを1/10してloop } } } else{ // 電圧表示 led_bff[0] = 0; // 1桁目はブランク for(i = 1; i < DIMSIZ(led_bff); i++){ // COM2(10桁から) b = pgm_read_byte(&led_seg[d % 10]); // 下位桁 0~9 if((d == 0) && // データゼロ? (i > 2)){ // 100桁より左 b = 0; // ブランクに } else{ if(i == 2){ // 100桁ならdp b |= 0b10000000; // dpをオン } } led_bff[i] = b; // segデータ書き込み d = d / 10; // データを1/10してloop } } } /****************************/ /* 16 bit A/D入力 */ /****************************/ /***** A/D入力 *****/ // LTC2460の16bit値を返す // 変換時間 13~16.6(typ 60Hz)~23ms // CLKの↓でSDOデータ出力 // CLK=H,CS=Lで変換完了チェック(Hなら変換中) // CS,CLK = Hでリターン void ltc2460(void) { byte i; word d = 0; // 0~0xFFFF ADCK_H; // A/D CLK = H ADCS_L; // A/D CS = L nop(); // 4clk nop(); nop(); nop(); // busyチェック if(AD_INP == 0){ // SDOがHなら変換中,Lなら変換完了 for(i = 0; i < 16; i++){ // clkパルス数 d <<= 1; // 左シフト ADCK_L; // A/D CLK = L nop(); nop(); nop(); ADCK_H; // A/D CLK = H if(AD_INP) d |= 1; // データ1/0 LSBに } #if LTC2462 // LTC2462の時 15bit分解能で // IN+ < IN- : 0~0x7FFF   ゼロに // IN+ >= IN- : 0x8000~0xFFFF これを15bitにして使う if(d != 65535){ // フルスケール以外の時 if(d < 32768) d = 32768; // 32767以下を0に d = (d - 32768) * 2; // 0x8000~0xFFFFを 0~0xFFFEに } #endif ltc_2460 = d; // 新A/Dデータ確定 // 平均加算処理 ad_adx[CH_A] += (long)d; // 16bit A/D 電流データ加算 ad_cntup[CH_A]++; // 加算回数 } ADCS_H; // A/D CS = Hに戻す } /*******************************/ /* タイマー1割り込み */ /*******************************/ /***** タイマー1A割り込み *****/ // 500Hz (2ms) // 1B割り込みの10usあと A/D変換開始 // 7seg LEDはオフ状態 ISR(TIMER1_COMPA_vect) { byte i; word d; PB5_H; // (!!!) // 16bit ADC(LTC2460入力 ltc2460(); // busyチェックしてokなら入力 // ltc_2460に16bitデータを残す // 内蔵A/D変換開始 ADCSRA |= (1 << ADSC); // A/D変換開始 // データ取得はA/D完了割り込みで PB5_L; // (!!!) } /***** タイマー1B割り込み *****/ // 500Hz (2ms) // 1A割り込みの10us手前でLEDをオフ // セグメント,コモンともLに ISR(TIMER1_COMPB_vect) { PB5_H; // (!!!) // LEDを消灯 PORTD &= 0b00000011; // セグメントをオフ(Lでオフ) // ++++++------ seg dp,G,F,E,D,C PORTB &= 0b11100111; // Lでオフ // ++------- seg B,A PORTC &= 0b11000011; // コモンをLに // ++++------- COM4,3,2,1 PB5_L; // (!!!) } /*****************************/ /* A/D処理 */ /*****************************/ /***** A/D割り込み処理 *****/ // タイマー1割り込みで変換開始 // 500Hz(2msサイクル)で変換 // LED表示 セグメントを駆動してコモンをオン // A/D値変換結果を平均処理 (256回 x2ms 512msごとに確定) // A/D ch0とLTC2460を処理 ISR(ADC_vect) { volatile byte hi, lo; word d; static byte cnt5; // 100Hz(10ms)カウント 5サイクルで PB5_H; // (!!!) // LED表示 hi = led_bff[led_ptr]; // セグメント Hアクティブ lo = pgm_read_byte(&led_com[led_ptr]); // COM1~COM4 Hアクティブ PORTC |= lo; // COMを出力 PORTB |= (hi & 0b00000011) << 3; // seg B,A Hでオン PORTD |= (hi & 0b11111100); // seg dp,G~C // 次桁 led_ptr++; // 次ポインター if(led_ptr >= DIMSIZ(led_bff)) led_ptr = 0; // A/D値読み出し d = ADC; // 内蔵 10bit A/Dデータ ad_adx[CH_V] += (long)d; // 電圧データ longで加算 // 加算回数チェック ad_cntup[CH_V]++; // 電圧平均加算回数 +1 if(ad_cntup[CH_V] >= AD_ACNT){ // 256回? (512msサイクル) ad_add[CH_A] = ad_adx[CH_A]; // 電流加算値をコピー ad_add[CH_V] = ad_adx[CH_V]; // 電圧加算値をコピー ad_cnt[CH_A] = ad_cntup[CH_A]; // 電流の平均回数 ad_cnt[CH_V] = ad_cntup[CH_V]; // 電圧の平均回数 ad_adx[CH_A] = 0L; // 次加算データクリア ad_adx[CH_V] = 0L; ad_cntup[CH_A] = 0; // 平均回数も0に ad_cntup[CH_V] = 0; f_adok = 1; // 変換完了 } // タイマー計時 cnt5++; // 10msカウント if(cnt5 >= 5){ // 2msサイクル×5で10ms cnt5 = 0; f_10ms = 1; // 10ms経過フラグをon if(tm_10ms) tm_10ms--; // 10msダウンカウント if(tm_ent) tm_ent--; // Entキー入力間隔チェックタイマー cnt_10ms++; // 10msサイクルカウンタ if(cnt_10ms >= 100){ // 1秒経過 cnt_10ms = 0; f_1sec = 1; // 1秒を知らせる } } // スイッチ入力チェック swchk(); // SW1のonエッジをチェック PB5_L; // (!!!) } /******************************/ /* セットアップ */ /******************************/ /***** SET UP *****/ void setup() { cli(); // 割込禁止 // I/Oイニシャル PORTB = 0b00000011; // data/pull up DDRB = 0b00111011; // port i/o指定 // |||||+---- PB0 IO8 out LTC2460 ADC /CS出力 =H // ||||+----- PB1 IO9 out LTC2460 ADC CLK出力 =H // |||+------ PB2 IO10 in LTC2460 ADC DO出力を入力(pull down) // ||+------- PB3 IO11 out 7seg A カソードコモン Hでon // |+-------- PB4 IO12 out 7seg B // +--------- PB5 IO13 out 割り込みタイミング PORTC = 0b00000010; // data/pull up DDRC = 0b00111100; // port i/o指定 // |||||+---- PC0 AD0 in A/D ch5 電圧入力 // ||||+----- PC1 AD1 in SW入力 Lでon(pull up) // |||+------ PC2 AD2 i/o LED COM1 右桁 カソードコモン Hでon // ||+------- PC3 AD3 i/o LED COM2 NPN Trで駆動 // |+-------- PC4 AD4 i/o LED COM3 // +--------- PC5 AD5 i/o LED COM4 左桁 PORTD = 0b00000011; // data/pull up DDRD = 0b11111110; // port i/o指定 // |||||||+---- PD0 IO0 in RXD // ||||||+----- PD1 IO1 out TXD // |||||+------ PD2 IO2 out seg C Hでオン // ||||+------- PD3 IO3 out seg D // |||+-------- PD4 IO4 out seg E // ||+--------- PD5 IO5 out seg F // |+---------- PD6 IO6 out seg G // +----------- PD7 IO7 out seg dp // タイマー0 (システムで使っている) →割り込みなしに // オーバーフロー割り込み(1.024msサイクル) TIMSK0 = 0b00000000; // 割り込み許可レジスタ // ||+--- TOIE0 全部禁止に // |+---- OCIE0A // +----- OCIE0B // タイマー1 LED表示割り込み TCCR1A = 0b00010000; // PWM_OFF状態 // |||| ++---- WGM11,10 CTCモード TOP=OCR1A // ||++-------- COM1B OC1B出力 // ++---------- COM1A TCCR1B = 0b00001010; // || ||+++---- CS12,11,10 clk 1/8 2MHz(0.5us) // || ++------- WGM13,12 CTCモード TOP=OCR1A // |+---------- ICES1 // +----------- ICNC1 OCR1A = 4000 - 1; // 2MHz/4000 500Hz OCR1B = 4000 - 20 -1; // 10us手前 OC1Bトグル 250Hz出力 TIMSK1 = 0b00000110; // 割り込み // | ||+--- TOIE1 // | |+---- OCIE1A コンペアマッチA // | +----- OCIE1B コンペアマッチB // +-------- ICIE1 // A/D入力 DIDR0 = 0b00000001; // デジタル入力禁止 // |||||+---- ADC0D A/D ch0入力 // +--------- ADC5D,4,3,2,1 デジタル入力禁止 ADMUX = 0b11000000; // 内蔵A/D // ||| ++++---- ADC0 MPX ch0 PC0を使う // ||+--------- ADLAR 右詰めで // ++---------- REFS 内部1.1V ADCSRA = 0b10001111; // |||||+++---- プリスケーラ 1/128 125kHz // ||||+------- ADIE 割込あり // |||+-------- ADIF // ||+--------- ADATE // |+---------- ADSC // +----------- ADEN 有効に // 割り込み開始 sei(); // 割込許可 // シリアル Serial.begin(9600); // 9.6kBPSで } /****************************/ /* 実 行 */ /****************************/ // 実行区分 #define J_TTL 0 // タイトル、設定値出力 #define J_MESR 1 // 電流,電圧測定値表示 #define J_PARA1 2 // 校正モード 開始 #define J_ADSET 5 // A/Dデータの設定 #define J_NEXT 6 // 次のパラメータデータ // 実行用データ // ※volatileを付けておかないと最適化で処理が飛ばされることがあった volatile byte exc_j; // 実行区分 // 「void (*exc_tbl[])(void)」で実行 volatile byte rx_c; // シリアル受信文字 // CRで確定 ESC:キャンセル // +:up -:down .:現在A/D値 volatile byte p_nbr; // パラメータ設定番号 1~8 // 0はチェックデータ volatile byte f_px; // パラメータデータ変更フラグ // 0なら数値変更なしで次へ // 1なら変更した byte disp_sel; // 表示切り替え SW1の操作でトグル // 0:電流 CH_A, 1:電圧 CH_V byte debug_ad; // SW長押しでA/D値をシリアルデータに付加 // mA、Vに続いて16bit,10bit A/D値を出力(機能停止中) byte f_ttl2; // タイトル表示と出力2回目以降は // 時間待ちを無くす // パラメータ設定 byte p_typ; // パラメータ設定区分 long p_data; // パラメータデータ現在値 // マイナスをチェックするのでshortで long *p_ptr; // パラメータ読み書きポインタ // データのアドレス long p_max; // 設定できる最大値 long p_min; // 最小値 char str_bff[24]; // 文字バッファ /***** タイトル、設定値出力 *****/ void jttl(void) { byte i; long d, *p; Serial.println(); // 改行 txmsgP(msg_ttl, DIMSIZ(msg_ttl)); // タイトル出力 led_bff[3] = 0b00111001; // C LED表示 led_bff[2] = 0b00011100; // u led_bff[1] = 0b11010000; // r. led_bff[0] = 0b00000110; // 1 // 設定値出力 for(i = 1; i < DIMSIZ(p_param); i++){ // EEPROMのアドレスと同じ p = pgm_read_word(&p_param[i].data); // データアドレス d = *p; if(pgm_read_byte(&p_param[i].typ) == P_SEC){ // 秒値なら d = pgm_read_word(&txsec_tbl[d]); // テーブルから読み出し } strcpy_P(str_bff, msg_para[i]); // パラメータ区分 txprintfP(PSTR("# %-12s:%5lu\r\n"), str_bff, d); // 現データ出力 } tm_10ms = 200; // byteなので割り込み禁止処理は不要 if(f_ttl2 == 0){ // はじめてのタイトル表示,出力 while(tm_10ms); // 2秒待ち } f_ttl2 = 1; // タイトル表示した2回目以降は待ち無しで Serial.println(F("# Run.")); f_swon = 0; // SW押 はキャンセル f_swhold = 0; // SW長押しもオフ cnt_10ms = 0; // 次のデータ出力まで1秒待たせる f_1sec = 0; cyc_cnt = 0L; // サイクルカウンタを0に sec_cnt = 0; // 1秒カウンタ exc_j++; // next } /***** 電流,電圧測定値表示 *****/ // シリアル:cyc xxx.xmA xx.xV void jmesr(void) { if(f_10ms){ // 10msごと f_10ms = 0; led4bcd(disp_sel); // 表示区分で電流,電圧値LED表示 } if(f_1sec){ // 1秒経過でシリアル出力 f_1sec = 0; if(sec_cnt) sec_cnt--; // 出力タイミング秒値を-1 if(sec_cnt == 0){ // データ送出タイミング txbcd(cyc_cnt, 6, 0); // 6桁サイクルカウンタ txbcd(mes_data[CH_A], 5, 1); // 電流値 123.4mA txstrP(PSTR("mA")); txbcd(mes_data[CH_V], 3, 1); // 電圧値 12.3V txstrP(PSTR("V")); if(debug_ad){ // A/D値出力? txbcd(ad_avr[CH_A], 6, 0); // 16bit A/D txbcd(ad_avr[CH_V], 5, 0); // 10bit A/D } Serial.println(); // 出力サイクル秒値をセット sec_cnt = pgm_read_word(&txsec_tbl[tx_sec]); } cyc_cnt++; // サイクルカウンタ +1 if(cyc_cnt > 999999L) cyc_cnt = 0; // max6桁 } if(rx_c){ // シリアル入力あり if(rx_c == '\r'){ // Ent x2を待つ if(tm_ent == 0){ // 1秒経過してる tm_ent = 100; // 2回目を待つ } else{ // 1秒内に2回のEnt Serial.println(F("# Cal mode.")); led_bff[3] = 0b00000000; // LED表示 led_bff[2] = 0b00111001; // C led_bff[1] = 0b01110111; // A led_bff[0] = 0b10111000; // L. p_nbr = 1; // パラメータ入力位置 mA-Loから exc_j = J_PARA1; // 校正モードへ } } else{ // 他の文字は無視 tm_ent = 0; } rx_c = 0; // 入力文字なしに } } /***** 校正モード 開始 *****/ // パラメータを設定 void jpara1(void) { f_px = 0; // パラメータデータ変更フラグ f_1sec = 1; // 1秒経過に p_ptr = pgm_read_word(&p_param[p_nbr].data); // データアドレス p_data = *p_ptr; // 現データ読み出し strcpy_P(str_bff, msg_para[p_nbr]); // パラメータ区分文字 p_typ = pgm_read_byte(&p_param[p_nbr].typ); // データ区分 p_max = pgm_read_dword(&p_minmax[p_typ].pmax); // 区分からmax値 p_min = pgm_read_dword(&p_minmax[p_typ].pmin); // min値 if((p_typ == P_AX) || (p_typ == P_VX)){ // A/D値設定? exc_j = J_ADSET; // A/D値設定へ } else{ // mA,V Lo,Hi設定へ exc_j++; } } /***** 電流,電圧のLo,Hi値設定 *****/ // 現在データを表示 // p_typがP_AYならxxxx.xmAで P_VYならxx.xVで void jpara2(void) { txprintfP(PSTR("\r# %-12s:"), str_bff); // データタイトル switch(p_typ){ // パラメータ区分 case P_AY: // 電流 txbcd(p_data, 4, 1); txstrP(PSTR("mA")); break; case P_VY: // 電圧 txbcd(p_data, 2, 1); txstrP(PSTR("V")); break; case P_SEC: // 秒値 txbcd(pgm_read_word(&txsec_tbl[p_data]), 4, 0); txstrP(PSTR("sec")); break; } exc_j++; // キー操作待ちへ } /***** 電流,電圧のLo,Hi値設定 *****/ // キー入力で設定値を更新 void jpara3(void) { if(rx_c){ // 文字入力あり? switch(rx_c){ case '\r': // Ent if(f_px == 0){ // いきなりEntキー exc_j = J_NEXT; // 次の項目へ } else{ // データ操作した *p_ptr = p_data; // RAMにストア EEPROM.put(4*p_nbr, p_data); // EEPROMに保存 exc_j = J_NEXT; // 次へ } break; case '+': // データ+1 case '^': if(p_data < p_max){ // maxでない p_data++; // +1 f_px = 1; // データ変更した exc_j--; // データ表示へ戻る } break; case '-': // データ-1 if(p_data > p_min){ // minでない p_data--; // データ-1 f_px = 1; exc_j--; // データ表示へ戻る } break; case '\x1B': // ESC:操作キャンセル p_nbr = DIMSIZ(p_param) - 1; // 最終パラメータで exc_j = J_NEXT; // 次の項目へ break; } rx_c = 0; // 入力文字なしに } } /***** A/Dデータの設定 *****/ // 1秒ごとにパラメータとA/D値をシリアル出力 // キー入力で設定値を更新 // ピリオドで現A/D値を採用 void jadset(void) { static word ad; if(f_1sec){ // 1秒ごとにA/D値を表示 f_1sec = 0; if(p_typ == P_AX) ad = ad_avr[CH_A]; // 電流 A/D値 else ad = ad_avr[CH_V]; // 電圧 A/D値 txprintfP(PSTR("\r# %-12s:%5lu %5u"), // 現データ出力 str_bff, p_data, ad); // A/D値も出力 } if(rx_c){ // 文字入力あり? switch(rx_c){ case '\r': // Ent if(f_px == 0){ // いきなりEntキー exc_j = J_NEXT; // 次へ } else{ // キー操作した *p_ptr = p_data; // RAMにストア EEPROM.put(4*p_nbr, p_data); // EEPROMに保存 exc_j = J_NEXT; // 次へ } break; case '.': // ピリオドでA/D値確定 p_data = (long)ad; // 現A/D値を新データに f_px = 1; break; case '+': // データ+1 case '^': if(p_data < p_max){ // maxでない p_data++; // データ+1 f_px = 1; } break; case '-': // データ-1 if(p_data > p_min){ // minでない p_data--; // データ-1 f_px = 1; } break; case '\x1B': // ESC:操作キャンセル p_nbr = DIMSIZ(p_param) - 1; // 最終パラメータで exc_j = J_NEXT; // 次の項目へ break; } rx_c = 0; // 入力文字なしに } } /***** 次のパラメータデータに *****/ // 最後まで来たらパラメータ設定おわり void jnext(void) { Serial.println(); // 改行 p_nbr++; // パラメータ番号+1 if(p_nbr >= DIMSIZ(p_param)){ // おわり? exc_j = J_TTL; // タイトル、設定データ出力から } else{ // まだ exc_j = J_PARA1; // 設定の続き } f_px = 0; // データ変更なし状態に rx_c = 0; // 入力文字なしに tm_ent = 0; // Ent間隔1秒チェックタイマークリア } /***** 測定実行 *****/ void (*exc_tbl[])(void)={ jttl, // 0 タイトル、設定値出力 jmesr, // 1 電流,電圧測定値表示 jpara1, // 2 パラメータ設定開始 jpara2, // 3 電流,電圧のLo,Hi値設定 現在データを表示 jpara3, // 4 電流,電圧のLo,Hi値設定 キー入力で設定値を更新 jadset, // 5 A/Dデータの設定 jnext, // 6 次パラメータデータに }; /****************************/ /* LOOP */ /****************************/ /***** LOOP *****/ void loop() { byte i; word cnt; long g; // EEPROMから校正パラメータを読む if(eparack()){ // EEPROMパラメータチェック Serial.println(F("# init EEPROM")); // 初期化した } // 実行開始 while(1){ if(Serial.available() && // 受信データあり (rx_c == 0)){ // 前文字処理済み rx_c = Serial.read(); // 1文字読み出し // txprintfP(PSTR("<%02X>"), rx_c); // 文字コード確認 } if(f_swon){ // SW1 on? 短押し f_swon = 0; disp_sel ^= 1; // 表示切り替え 電流,電圧をトグル } if(f_swhold){ // SW長押し? exc_j = J_TTL; // タイトル、設定データ出力から再スタート // debug_ad ^= 1; // デバッグ用A/D値出力をon/off } // A/Dデータを平均 if(f_adok){ // A/D値確定 256回平均(512msサイクル) f_adok = 0; PB5_H; // (!!!) for(i = 0; i < DIMSIZ(ad_add); i++){ // 2ch loop cli(); // いったん割り込み禁止で g = ad_add[i]; // 平均加算値(割り込みで確定) cnt = ad_cnt[i]; // 平均回数 sei(); // 割込再開 if(cnt > 0){ // 測定した時だけ g = lround((float)g / (float)cnt); // 加算値から平均値算出 ad_avr[i] = (word)g; // A/Dデータ平均値を保存 } } // 測定値算出 curvolt(CH_A); // 電流値計算 ad_avr→mes_data curvolt(CH_V); // 電圧値計算 PB5_L; // (!!!) } exc_tbl[exc_j](); // 計測実行 } } /*===== end of "CUR_16BIT1a.ino" =====*/