温度計は何台か作りましたが正確、かつ簡単に校正するのは困難です。
出来るだけ無調整で精度の良い温度センサーがあれば温度の基準値として使えるのですが...。
秋月電子のカタログを眺めていていた時、ADT7410という温度センサーを発見したので購入しました。
SIP変換基板に実装された状態で500円で販売されていました。
精度は±0.4℃ということですが、サンプルデータのバラツキを見ると20℃近辺では+0℃〜−0.3℃位に
収まっているようです。(電源電圧3Vの時)
早速、試してみました。
回路図をクリックすると拡大表示されます。
拡大図から本文に戻るにはブラウザの←戻る釦を使用してください。
CPUはPIC12F1822、電源は単3電池2本です。
電池1本でも動作しますが電池の持ちを考えて2本にしています。
温度センサはピッチ変換基板に実装されて販売されています。
アドレスA0、A1はオープンのままで使用しないと、せっかくの低消費電流が台無しになります。
I2Cバスアドレスは0X48となりますが1ビット左シフトしてR/Wビットを最下位に付加する為、デバイスセレクト信号は
0X90又は0X91となります。
幸い液晶表示器のアドレスと競合しません。
動作試験の時は電池の代わりにCVCC電源を使いましたが電源を短時間でオンオフしたとき表示器が動作しない事が
ありました。
手元にあった2個の液晶表示器の1台が立ち上がれないのです。
もう1個は問題ないので不良品かもしれません。
電池使用で電源SWは付けていないので、このままでも良いかとは思いましたが、余っているポートを使いハードウエアリセット
した後に初期設定をしています。
3Vの時の平均消費電流は0.3mA弱ですので半年くらいは使えるとは思いますが、まだ確認出来ていません。
(1年間動作しました。)
長時間動作すると、どうしても最高温度、最低温度を記録したくなりますが、これだけでちょっと面倒になります。
押しボタンスイッチを付け、電池装着直時は古いデータのリセット、その後は最高温度、最低温度の表示用にします。
プログラムはMikroCで作成しました。
ディレー関数以外の組み込みライブラリーは使っていません。
その為、やや記述量が多くなっています。
上図はプロジェクトとコンパイル結果です。
ヘッダファイルです。
複数のファイルから参照される定数、変数、関数のプロトタイプを記述しています。
/////////////////////////////////////////////////// // global.h // // 高精度温度計ヘッダー // // H26/12/23 MikroC Pro for PIC Ver 6.0.0 // /////////////////////////////////////////////////// #ifndef _GLOBAL_H #define _GLOBAL_H ///////// 型の短縮名称 typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; ///////// ハードウエア依存部 #define S_ADDR 0x7c //表示器スレーブアドレス(Wのみ) #define S_ADDR2 0x90 //温度センサースレーブアドレス(90 | R/W) ///////// 関数のプロトタイプ宣言 extern void I2init(void); //I2C初期化 extern void I2startCon(void); //スタートコンディション extern void I2startRep(void); //スタートコンディション再送 extern void I2stop(void); //ストップコンディション extern uchar I2write(uchar); //1バイト送信 extern uchar I2read(uchar); //1バイト受信 extern void Lcd_init(void); //液晶表示器初期化 extern void Lcd_clear(void); //画面消去 extern void Lcd_cmd(uchar); //コマンド書き込み extern void Lcd_cursol(uchar); //カーソル位置のセット extern void Lcd_putc(char); //1文字表示 extern void Lcd_str(char *); //文字列表示 extern void Lcd_setCG(uchar,char *); //オリジナルキャラクタ登録 extern void tModeSet(void); //センサーモードセット extern void tRead(void); //温度の読み取り extern void tDisp(void); //現在温度の表示 extern void hlDisp(void); //最高/最低温度表示 extern int readHL(uchar); //最高、最低温度をEEPROMから読み込む extern void writeHL(uchar, int); //EEPROMに最高、最低温度を書き込む ///////// グローバル変数 extern int TempH; //最高温度 extern int TempL; //最低温度 extern volatile uchar IntI2c; //割り込みフラグのコピー #endif
秋月で扱っている8文字2行のI2C液晶表示器の自作ライブラリーです。
I2Cの基本関数も書いてあります。
PIC12F1822内蔵のハードウエアを使ったものです。
自作キャラクタの登録関数は使用していません。
//////////////////////////////////////////////////// // i2lcllib.C (I2C基本関数+液晶ライブラリー) // // 高精度温度計 PIC12F1822 // // H26/12/23 MikroC Pro for PIC Ver 6.0.0 // //////////////////////////////////////////////////// #include "global.h" //////// I2C初期化 // メインクロック4MHz: ボーレート100KHz // SSPADD = X , 0.1 = 4/(4 * (X + 1)) , X = 9 void I2init(void){ SSPCON1 = 0x8; //マスターモード SSPCON2 = 0; //リセット SSPSTAT = 0; SSP1ADD = 0x9; //ボーレート100KHz SSPCON1.SSPEN = 1; //MSSP使用 PIE1.SSP1IE = 1; //MSSP割り込み許可 INTCON.PEIE = 1; //周辺割り込み許可 INTCON.GIE = 1; //全割り込み許可 IntI2c = 0; //割り込みフラグのコピーをクリア } //////// スタートコンディション void I2startCon(void){ SSP1CON2.SEN = 1; //スタート while(IntI2c == 0); //終了待ち IntI2c = 0; //割り込みフラグクリア } //////// スタートコンディション再送 void I2startRep(void){ SSP1CON2.RSEN = 1; //再スタート while(IntI2c == 0); //終了待ち IntI2c = 0; //割り込みフラグクリア } //////// 1バイト送信 // 引数:送信データ 戻り値 0:ACK,1:NACK uchar I2write(uchar _data){ while(SSP1STAT.BF); //BUSY SSP1BUF = _data; //データ送信 while(IntI2c == 0); //送信完了待ち IntI2c = 0; //割り込みフラグクリア return SSP1CON2.ACKSTAT; } //////// 1バイト受信 // 引数 0:ACKを返す、1:NACKを返す // 戻り値 受信データ uchar I2read(uchar ACK){ uchar tmp; SSP1CON2.ACKDT = ACK; SSP1CON2.RCEN = 1; //受信許可 while(IntI2c == 0); //受信完了待ち tmp = SSPBUF; //読み取り IntI2c = 0; //割り込みフラグクリア SSP1CON2.ACKEN = 1; //ACKDT送信 while(IntI2c == 0); //ACK送信完了待ち IntI2c = 0; //割り込みフラグクリア return tmp; } //////// ストップシーケンス void I2stop(void){ SSP1CON2.PEN = 1; //ストップ出力 while(IntI2c == 0); //終了待ち IntI2c = 0; //割り込みフラグクリア } ///////// コマンド書き込み // 引数:書き込みコマンド // 戻り:0=成功、1=失敗 uchar Lcd_cmd(uchar cmd){ I2startCon(); //IIC 開始 if(I2write(S_ADDR)) goto err; //スレーブアドレス書き込み if(I2write(0b10000000)) goto err; //コントロールバイト if(I2write(cmd)) goto err; //コマンド書込 I2stop(); //IIC 終了 Delay_us(26); return 0; err: //エラー I2stop(); return 1; } ///////// 表示位置 // 引数:表示位置(1行目0x0〜0x7、2行目0x40〜0x47) void Lcd_cursol(uchar ddram_addr){ uchar dummy; ddram_addr |= 0x80; dummy = Lcd_cmd(ddram_addr); } ///////// 表示消去 void Lcd_clear(void){ Lcd_cmd(0x01); Delay_ms(1); Delay_us(80); } ///////// 液晶の初期化 void Lcd_init(){ I2init() ; //ポート初期化 Delay_ms(40); // 電源ON後40ms待つ Lcd_cmd(0x38); // function set 8ビットモード2行 Lcd_cmd(0x39); // function set 拡張コマンドを有効にする Lcd_cmd(0x14); // バイアスの 選択と内部発振周波数の調整 Lcd_cmd(0x70); // Contrast set コントラスト調整データ(下位4ビット) Lcd_cmd(0x56); // Contrast set 昇圧回路有効、コントラスト調整データ(上位2ビット) Lcd_cmd(0x6C); // Follower control フォロア回路をONし増幅率の調整を行う Delay_ms(200); // 電力が安定するまで待つ Lcd_cmd(0x38); // function set 拡張コマンドを無効にする Lcd_cmd(0x0c); // display control 画面表示はON・カーソル表示はOFF Lcd_cmd(0x01); // Clear Display 画面消去 Delay_ms(1); Delay_us(80); } ///////// 1文字表示 void Lcd_putc(char c){ uchar dummy; I2startCon(); //スタートコンディション if(I2write(S_ADDR))goto err; //スレーブアドレス dummy = I2write(0b11000000); //コントロールバイト dummy = I2write(c); //文字書き込み err: I2stop(); //ストップコンディション Delay_us(26); } ///////// 文字列の表示 void Lcd_str(char *s){ uchar dummy; I2startCon(); //スタートコンディション if(I2write(S_ADDR))goto err; //スレーブアドレス dummy = I2write(0b01000000); //コントロールバイト while(*s){ dummy = I2write(*s); *s++; } err: I2stop(); Delay_us(26); } ///////// 自作キャラクタの登録 // 引数:登録アドレス(0〜5)、登録データへのポインタ void Lcd_setCG(uchar addr,char *dp){ uchar dummy, i; I2startCon(); //IIC 開始 if(I2write(S_ADDR))goto err; //スレーブアドレス書き込み dummy = I2write(0b10000000); //コントロールバイト(コマンド書き込み) addr = addr << 3; // dummy = I2write(0x40 | addr); //書き込み位置 dummy = I2write(0b01000000); //コントロールバイト(データ書き込み) for(i=0;i<7;i++){ //キャラクタデータを送る I2write(*dp); dp++; } err: I2stop(); Delay_us(26); }
センサーから温度の読み出し、表示。
最高温度、最低温度の更新、表示。
//////////////////////////////////////////////////// // SENSOR.C (温度の読み取りと表示) // // 高精度温度計 PIC12F1822 // // H26/12/23 MikroC Pro for PIC Ver 6.0.0 // //////////////////////////////////////////////////// #include "global.h" int tdata; //現在温度(センサーフォーマット) int TempH; //最高温度更新バッファ int TempL; //最低温度更新バッファ ///////// MODE SET(13BIT ONESHOT) void tModeSet(void){ IntI2c = 0; I2startCon(); //スタートコンディション I2write(S_ADDR2); //デバイスセレクト I2write(0x3); //レジスタアドレス書き込み I2write(0x20); //13ビットワンショットモード I2stop(); //ストップシーケンス } ///////// 温度の読み取り void tRead(void){ IntI2c = 0; I2startCon(); //スタートコンディション I2write(S_ADDR2); //デバイスセレクト(ライト) I2write(0); //アドレス書き込み I2startRep(); //スタート再送 I2write(S_ADDR2 + 1); //デバイスセレクト(リード) tdata = I2read(0) << 8; //上位読み取り tdata |= I2read(1); //下位 I2stop(); } ///////// 温度の液晶表示 void lDisp(int tempt, char flg){ uchar n1, n2, n3, n4; n1 = tempt / 1000; tempt = tempt % 1000; n2 = tempt / 100; tempt = tempt % 100; n3 = tempt / 10; n4 = tempt % 10; if(flg) Lcd_putc('-'); else Lcd_putc(' '); if(n1) Lcd_putc(n1 + 0x30); else Lcd_putc(' '); if (n1 || n2) Lcd_putc(n2 + 0x30); else Lcd_putc(' '); Lcd_putc(n3 + 0x30); Lcd_putc('.'); Lcd_putc(n4 + 0x30); Lcd_putc('゚'); Lcd_putc('C'); } ///////// 温度を表示 void tDisp(void){ int temp; int tempHL; uchar nf; //負フラグ // 温度計算 nf = 0; temp = (uint)tdata; if(temp & 0x8000) nf = 1; temp = temp >> 3; temp = temp & 0x1fff; if(nf){ temp -= 8192; temp = -temp; //絶対値にする } temp = temp*10; //10倍の整数で扱う temp += 8; //四捨五入 temp = temp >> 4; // 1/16 // 最高温度 最低温度の更新 if(nf) tempHL = -temp; //負の数値に戻す else tempHL = temp; //もともと正の値 if(tempHL < TempL) TempL = tempHL; else if(tempHL > TempH) TempH = tempHL; // 現在温度の表示 Lcd_cursol(0); //1行目 lDisp(temp, nf); Lcd_cursol(0x40); Lcd_str(" "); //2行目を消去 } ///////// 最高温度 最低温度の表示 void hlDisp(void){ uchar nf; //負フラグ int buff; Lcd_cursol(0); //1行目 nf =0; buff = readHL(0); //EEPROMから最高温度を読む if(buff < 0){ nf = 1; buff = - buff; //絶対値にする } lDisp(buff, nf); //液晶に表示 Lcd_cursol(0x40); //2行目 nf =0; buff = readHL(2); //EEPROMから最低温度を読む if(buff < 0){ nf = 1; buff = -buff; //絶対値にする } lDisp(buff, nf); //液晶に表示 }
最高温度、最低温度はバッファでは毎回、更新していますが、EEPROMに書き込むのは約17分に1回です。
書き換えが必要な時だけ書き込むので書き込み回数は多くないはずです。
下記ソースはPIC内蔵のEEPROMの読み書き関数です。
//////////////////////////////////////////////////// // EEPROM.C (内蔵EEPROMの読み書き) // // 高精度温度計 PIC12F1822 // // H26/12/23 MikroC Pro for PIC Ver 6.0.0 // //////////////////////////////////////////////////// // eeprom addr 0: 最高温度上位, addr 1: 最高温度下位 // eeprom addr 2: 最低温度上位, addr 3: 最低温度下位 #include "global.h" ///// DATA EEPROMから読み出す char read_e2rom(uchar addr){ EEADR = addr; EECON1.EEPGD = 0; EECON1.CFGS = 0; EECON1.RD = 1; return EEDATL; } ///// DATA EEPROMに書き込む void write_e2rom(uchar addr, char _data){ EEADR = addr; EEDATL = _data; EECON1.EEPGD = 0; EECON1.CFGS = 0; EECON1.WREN = 1; EECON2 = 0x55; EECON2 = 0xaa; EECON1.WR = 1; } ///// 書き込み後ロックする void lock_e2rom(void){ EECON1.WREN = 0; } ///// EEPROM から最高/最低温度を読み出す int readHL(uchar _addr){ int buff; buff = 0; buff = read_e2rom(_addr); buff = buff << 8; buff |= read_e2rom(_addr + 1); return buff; } ///// 最高/最低温度を EEPROM に書き込む void writeHL(uchar _addr, int __data){ write_e2rom(_addr, __data >> 8); Delay_ms(10); write_e2rom(_addr +1, __data); Delay_ms(10); lock_e2rom(); }
メインルーチン、割り込みルーチン、EEPROMの初期設定と更新です。
温度は約4秒で更新されます。
殆どの時間CPUはスリープしていて約4秒周期のウオッチドッグタイマーで起こされます。
最初に前回の温度を読み取り、表示し、センサーをワンショット起動してスリープします。
センサーは計測に340mS要し、その後シャットダウンします。
計測中に押しボタンを長押しすると最高温度、最低温度を表示し、離すと温度表示に戻ります。
/////////////////////////////////////////////////// // i2test1.C // // 高精度温度計 PIC12F1822 // // H26/12/23 MikroC Pro for PIC Ver 6.0.0 // /////////////////////////////////////////////////// #include "global.h" // PIC12F1822 CLOCK 4MHz // Oscillator INTOSC, Watchdog SWDTEN, Power-up Timer ON, // MCLR Pin ON, Code Protection OFF, Data Protection OFF, // Brown-out Reset ON, Clock Out OFF, Int/Ext Switchover OFF, // Fail-safe Clock Monitor OFF, F-Memory Self W-protection OFF, Pll OFF // Stack Of/Uf Reset ON, Brown-out Reset 1.9V, Debug OFF, LVP OFF volatile uchar IntI2c; ////// EEPROMの更新 void update(void){ int temp; temp = readHL(0); if(temp < TempH) writeHL(0, TempH); //最高温度 temp = readHL(2); if(temp > TempL) writeHL(2, TempL); //最低温度 } ////// 初期値の書き込み void IniSet(void){ writeHL(0, -500); //最高温度初期値 TempH = -500; writeHL(2, 1500); //最低温度初期値 TempL = 1500; } void main(){ uchar cnt; OSCCON = 0x68; //内部 4MHz OPTION_REG = 0xc0; //タイマー0関連ダミー ANSELA = 0; //ALL DIGITAL TRISA = 0x1e; //ポート入出力設定 LATA.B0 = 0; //液晶リセット端子 WPUA = 0; //PULL UP OFF Delay_ms(500); //電源の安定を待つ LATA.B0 = 1; //リセット解除 Lcd_init(); //液晶表示器初期化 tModeSet(); //センサーのモードセット Lcd_cursol(0); Lcd_str("PUSH"); cnt = 0; while(cnt < 100){ Delay_ms(600); if(!PORTA.B4){ //1分間押しボタン待ち while (!PORTA.B4); break; } cnt++; } cnt = 0; Delay_ms(200); while(!PORTA.B4); Delay_ms(200); while(!PORTA.B4); Lcd_cursol(0x40); Lcd_str("RESET"); while(cnt < 100){ Delay_ms(50); if(!PORTA.B4){ //5秒間の間にボタンが押されたら IniSet(); //EEPROMに初期値を書く while (!PORTA.B4); break; } cnt++; } Delay_ms(200); while (!PORTA.B4); TempH = readHL(0); //EEPROMから最高温度を読みとる TempL = readHL(2); //EEPROMから最低温度を読みとる WDTCON = 0x19; //4秒で目覚める while(1){ cnt++; //4秒×256 if (0 == cnt) update(); //約17分に1回ROM更新 tRead(); //センサー読み取り if (PORTA.B4) tDisp(); //押しボタンが押されてなければ温度表示 else hlDisp(); //最高温度、最低温度表示 tModeSet(); //センサーワンショット起動 asm SLEEP; asm NOP; } } void interrupt(){ //MSSP割り込み PIR1.SSP1IF = 0; //割り込みフラグクリア IntI2c = 1; //コピーフラグセット }
電池を入れた時「PUSH」と表示されます。
押しボタンを押すと次に進みます。
放置した場合、1分後に次に進みます。
この画面は5秒間表示されます。
この間に押しボタンを押すと最高温度、最低温度の初期値がEEPROMに書き込まれます。
初期値は最高温度が−50℃、最低温度が150℃です。
初期値を書き込みたくない場合は5秒間放置すると温度表示に移ります。
プログラム直後は必ず初期値を書き込みます。
(プログラマーによって最高温度、最低温度共に−0.1℃と書き込まれるようです。)
温度表示画面です。
温度表示中に押しボタンを長押しすると1行目に最高温度、2行目に最低温度が表示されます。
手を離すと温度表示に戻ります。
初期化後、最初にEEPROMが書き代わるのは17分後ですので、それまでは最高温度−50℃、最低温度150℃と
表示されます。
(バッファの値では無くEEPROMの値を表示しています。)
もう1台作って表示値を比較してみました。
全く同じ値か0.1℃の違いです。
温度が変化しているとき稀に0.2℃の違いが出ましたがバラツキは少ないようです。
冷凍庫に入れて一晩置いてみました。
センサーは−50℃〜150℃の動作を保証されていますが、他の部品がどうなるか、あまり考えませんでした。
高温は注意するのですが...
取り出すと液晶が変色して固まっていました。
壊したかと思いましたが温度が上がると復活しました。
上の使い方の項目で表示した最低温度−23.4℃は、このとき記録されたものです。
製作後、2015年1月1日に温室に置きました。
2016年1月24日に表示が消え、動作を停止していました。
単三アルカリ電池2本で1年間は動作するようです。