熱帯魚水槽の温度ロガー回路の製作

   → 改善版   補正値を不揮発メモリに保存する機能を追加したバージョン を作りました!



今年の夏は(2010)とても暑いです.私は金魚とグッピーとミナミヌマエビを飼っているんですが、放置すると部屋の温度が37度にもなる日がありまして、これはすでにお風呂の温度なので、熱帯魚のグッピーといえどもさすがにのぼせて死んでしまうんじゃないかと心配です.

今年は、水槽のフタをくりぬいて、12cmのCPUファンを一日中まわして対処しました.これで、たぶん30度を超えることはなくなった気がします.

といっても「気がします」だけで、昼のうだるような暑さの時間帯にいったい水槽は何度になっているかはわからないので、水槽の温度ロガーを製作しました.

目標仕様

●金魚水槽+グッピー水槽+部屋の温度を同時に測る
●USBインターフェース
●PC側のアプリはターミナルソフトだけでよい
●温度はテキストファイルでHDDに保存される
●測定インターバルは、最短1秒、最長1分程度までターミナルソフトから設定できること
●温度センサの誤差を、ターミナルソフトから補正できること


回路概要

回路図はこちら (別ウインドウ)
●温度センサには、秋月電子で入手できる、LM61CZを3ヶつかいます (4ヶで\200)    (pdfはこちら
●LM61CIZの外形は、フツーのトランジスタサイズです (下写真)
●接続は適当な電源へ接続するだけ  (下写真)
●出力電圧が温度によって動くわけですが、10mV/度です.25度で850mV です  (下表)
●3つのLM61CIZを同時に温度測定したところ、あまり精度はよくありません.1度以上ばらついていましたので、なにも補正せずに水温計としてつかうのはムリでした.ソフトウエアで補正する必要があります.
●ADCのフルスケールは、VDDで決まります.一方USBの5V電源は、ホストPCによってばらつくと思われます.すると、せっかく補正したとしても、ホストPCを変えると温度測定値がズレてしまう恐れがあります.そこで、STM8Mの電源は、レギュレータの3.3Vにします.レギュレータは、FT232RLのレギュレータをつかいます.
●STM8Sの内蔵UARTのTX/RXを、FT232RLでUSBに変換し、ホストPCに接続します.



ソフト概要

visual studio 2008 のプロジェクトフォルダは、こちらをダウンロード (zip) してください.

●ST Visual Development(STVD)のprojectフォルダをアーカイブした(lzh)ファイルをここにおきます.
●3つのLM61CIZを、STM8Sの、ポート PB2,PB6,PB7 に接続します.ADCのポートでいうと、AIN2,AIN6,AIN7 に接続することになります.
●ADCは、自動的に10個の入力ポートを切り替えてAD変換する、scan modeで動かします.
●ADCは、0.1秒後との割り込みでAD変換します.
●表示インターバルが5秒ごとであれば、50回のAD変換データを平均した値を温度として表示します.
●UARTは、STM8Sのソフトウエア実装例からパクったcodeを流用します.
●UARTの接続緒元は、  115200bps  7bit  parity1bit  フロー制御ナシ
●STM8S内蔵タイマは、 TIM2--->ADC0.1Sec割り込み用     TIM3--->UART割り込み用
●ターミナルソフトから設定できるコマンドは、
        i 5 enter     --->  表示interval 5秒ごと
        t 20 21 22 enter --->  表示時刻のスタート時刻を、20日21時22分0秒 に設定する
        v   enter     ---> AD変換データを毎回表示する  (もう一度 v enter と入力すると表示しなくなる)
        h2 -0.5  enter   --->  AIN2の温度表示を-0.5する補正をかける
        h6  0.2   enter   --->  AIN6の温度表示を+0.2する補正をかける
        h7  0   enter     --->  AIN7の温度表示に補正をかけない
●ターミナルソフトの画面表示はこうなります
          日  時 分 秒                                  温度 温度 温度
DHMS 000 00 00 34  TEMP(AIN2,AIN6,AIN7) 24.13 24.13 28.27
DHMS 000 00 00 37  TEMP(AIN2,AIN6,AIN7) 24.13 24.12 28.30
DHMS 000 00 00 40  TEMP(AIN2,AIN6,AIN7) 24.18 24.09 28.40
DHMS 000 00 00 43  TEMP(AIN2,AIN6,AIN7) 24.10 24.08 28.28
DHMS 000 00 00 46  TEMP(AIN2,AIN6,AIN7) 24.16 24.14 28.28
DHMS 000 00 00 49  TEMP(AIN2,AIN6,AIN7) 24.10 24.08 28.24
●このような表示結果をlogに落としたいときは、ターミナルソフトのlog機能をつかってHDDに記録します
●デフォルトで、インターバル1秒、補正ナシ、日付0で温度測定を開始します.なので、logを記録開始するまえに、次のようなコマンドで設定する必要があります.
   t 20 21 22           -->     測定開始日時の設定
   i 5                     -->      インターバル5秒
   h2 -0.5               -->      AIN2補正-0.5度
   h6 0.2                 -->      AIN6補正+0.2度
   h7 0                   -->      AIN7補正ナシ
●温度推移をグラフに描きたいなら、excelでlogを加工して描けばよいでしょう


実物の写真

STM8S discovery にLM61CIZをひとつじか付けし、2つのLM61CIZを1mぐらい配線で延長して取り付けました.
FT232RLは、出来合いの基板が手元にあったので、それを両面接着剤で取り付けました.写真の左上の子基板がそれです.



1mぐらい延長したLM61CIZは、センサの足がむき出しだと、電解質的な水槽の水に触れて誤動作すると予想されるので、シリコン接着剤でコーティングしました.このコーティングは、温度センサの誤動作防止だけでなく、金魚さんとエビさんの保護のためにも重要とおもわれます.特にエビさんは、銅が水中にあるとそれだけで死んでしまうほど毒性に弱い生き物なんです.


また、シリコン接着剤は、ホルムアルデヒドのような有害物質を含まない製品を使う必要があります.さもないとエビさんが死んでしまいます.キッチン用途のシリコン接着剤などは、防カビ材配合と表記されています.そういうのはエビさんにとって猛毒なんです.私が使ったシリコン接着剤は、この写真の製品です.#55388が製品番号のようです.




ソースコード説明

main.c  のキモのところを説明

#include "stdlib.h"       温度計算を浮動小数点計算するので    linkerの設定は こちらのB に留意してください
#include "hyperterminal.h"      UARTを動かすサブルーチンが入っています

●CLK_ClockSwitchConfig ( CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE );
外部16MHzを使うように切り替えます.

●UARTの割り込みはTIM3をつかいます
 TIM3_DeInit();
 TIM3_TimeBaseInit(TIM3_PRESCALER_1,0xFFFF);
 TIM3_ITConfig(TIM3_IT_UPDATE, ENABLE);

●ADCの割り込みはTIM2を使います.0.1秒ごとに割り込みます.源発信が16MHzで、プリスケーラで512分周し、さらにタイマカウンタで3125分周すると10Hzになるというからくりです.
 TIM2_DeInit();
 TIM2_TimeBaseInit(TIM2_PRESCALER_512,3125-1);  // 16000000/512/3125=10Hz
 TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);
  TIM2_Cmd(ENABLE);

●GPIOの出力を定義    割り込みモニタ程度のインジケータなのでさほど重要ではないです
 GPIO_Init(GPIOD, 0b00000001, GPIO_MODE_OUT_PP_HIGH_FAST); // LED
 GPIO_Init(GPIOE, 0b00001000, GPIO_MODE_OUT_PP_LOW_FAST); // int monitor

●ADCをscan modeに設定します.データシートを読んでもそうかかれているとは思えなかったんですが、CONTINUOUSで、かつCH[3:0]=15に設定しないとscan modeがうまく動かなかったので、理由もわからずにこうしました.まぁ、おまじないと思ってつかうしかないだろう.
 ADC1_DeInit();
 ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, 15, ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, ADC1_ALIGN_LEFT, ADC1_SCHMITTTRIG_CHANNEL7, DISABLE);
 ADC1_DataBufferCmd(ENABLE);
 ADC1_ScanModeCmd(ENABLE);

●UARTの設定   11520bps    7bit    parity1bit   フロー制御ナシ  
 UART2_DeInit();
 UART2_Init((u32)115200, UART2_WORDLENGTH_8D, UART2_STOPBITS_1, UART2_PARITY_ODD, UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);

●enableInterrupts();              割り込み許可

●メインループ
 while (1) { UART_Menu();  }         ホストPCからターミナルソフト経由でコマンドを受け付けるルーチン


ADC割り込みルーチンのキモのところを説明

●@far @interrupt void Tim2Update_isr(void)      0.1秒ごとに割り込み処理するルーチン
{
● TIM2_ClearFlag(TIM2_FLAG_UPDATE);     次回の割り込み準備のためTIM2割りl込みフラグをクリアするおまじない

●if(cnt>(INTERVAL*10)){         たとえばintervalが5秒だったら、割り込み50回ごとに表示する処理
  now_sec = (u8)((int_cnt/10) % 60);      現在秒
  now_min = (u8)((int_cnt/600) % 60);     現在分
  now_hour= (u8)((int_cnt/36000) % 24);  現在時刻
  now_date= (u8)((int_cnt/864000));       現在日付

●AD変換データを、温度に換算する式
mV = (float)adc_data_sum2/(float)INTERVAL/10/65535*3333; temp2 = ( mV - 850 ) / 10 + 25 + HOSEI2;
mV = (float)adc_data_sum6/(float)INTERVAL/10/65535*3333; temp6 = ( mV - 850 ) / 10 + 25 + HOSEI6;
mV = (float)adc_data_sum7/(float)INTERVAL/10/65535*3333; temp7 = ( mV - 850 ) / 10 + 25 + HOSEI7;

●SerialPutString(a);      ターミナルソフトへ、1行の文字列を送信する関数

●ADC1_StartConversion();       scan modeでAD変換せよというおまじない

●各温度センサのAD変換データを、積算する  (あとで積算回数で割る)
adc_data2=ADC1_GetBufferValue(2); adc_data_sum2+=adc_data2;
adc_data6=ADC1_GetBufferValue(6); adc_data_sum6+=adc_data6;
adc_data7=ADC1_GetBufferValue(7); adc_data_sum7+=adc_data7;

●割り込みインジケータLEDを光らせる
  GPIO_WriteReverse(GPIOD,GPIO_PIN_0); // LED


UART処理ルーチンのキモのところを説明

STMicronのUART実装例をパクったので、詳細は知らないんです.

なので、キモのところではありませんが、ターミナルソフトのコマンドメニュー画面を説明します.

●void UART_Menu(void)        ホストPCとターミナルソフトを通じて通信するルーチン

●GetInputString(choice);      ホストPCからコマンドを1行受診する関数

●fieldnumber = separate_line(choice);      コマンドを分離する関数

●コマンドを解釈し、変数にパラメータを格納する
   if(     fieldnumber==1 && Field[0][0]=='v'){ (VERBOSE=='v') ? (VERBOSE='s') : (VERBOSE='v'); goto NEXT;}
   else if(fieldnumber==2 && Field[0][0]=='i'){ INTERVAL = (int)atof(Field[1]); goto NEXT;}
   else if(fieldnumber==4 && Field[0][0]=='t'){
      DATE = (int)atof(Field[1]);
      HOUR = (int)atof(Field[2]);
      MIN  = (int)atof(Field[3]);
      SEC = 0;
      int_cnt = (u32)DATE*24*60*60*10 + (u32)HOUR*60*60*10 + (u32)MIN*60*10 + (u32)SEC*10;
      goto NEXT;
     }
   else if(fieldnumber==2 && Field[0][0]=='h' && Field[0][2]==0){
      if(Field[0][1]=='2') HOSEI2 = atof(Field[1]);      補正値AIN2
      if(Field[0][1]=='6') HOSEI6 = atof(Field[1]);      補正値AIN6
      if(Field[0][1]=='7') HOSEI7 = atof(Field[1]);      補正値AIN7
     }
  NEXT: ;


hyperterminal.c   stm8s_it.c

STMicroのUARTサンプルプログラムからパクりましたので、内容知らない.


stm8_interrupt_vector.c

●extern @far @interrupt void Tim2Update_isr(void);       main.cで定義した関数をexternしておきます.

●{0x82, Tim2Update_isr}, /* irq13 */       タイマ2の割り込み信号はIRQ13に接続されていますので、割り込みルーチンを、IRQ13にヒモ付けしておきます.

●{0x82, (interrupt_handler_t)TIM3_UPD_OVF_BRK_IRQHandler }, /* irq15 */     タイマ3の割り込みはIRQ15に接続されていますので、このようにヒモづけしておきます


stm8s.h

 /* #define STM8S208 */
 /* #define STM8S207 */
#define STM8S105                       これのコメントを外す
 /* #define STM8S103 */
 /* #define STM8S903 */


stm8s_conf.h

#define _ADC1 (1)     
#define _CLK (1)          FWライブラリをつかうペリフェラルのコメントを外す
#define _GPIO (1)
#define _TIM2 (1)
#define _TIM3 (1)

それぞれに対応する、.cと.hを、FWライブラリフォルダから、コピーして、projectに追加してください.


release  2010.8.22
project folderをリンクするのを忘れていた   2010.9
補正値不揮発メモリ保存  2010.9

inserted by FC2 system