USBでPCと接続する多チャンネル温度ロガー

2012.3.9 訂正    ADCを使うときはHSIじゃないとダメだというウソを訂正しました.
2012.1.21 訂正   ADCのclock周波数は1〜4MHzで使うべしという制約があることがわかりましたのでそれを修正します.


実例でADCの多チャンネル動作と、不揮発性データと、UARTについて学びました.

これらを統合した応用例として、多チャンネル温度計を製作してみます.

ADCが10ch切り替え型なので、最大で10箇所の温度を測定できます.

温度センサは秋月電子で売られている LM61CZ というセンサを使いました.  (LM61CZのpdf)
LM61CZには誤差がありますので、不揮発性データで誤差をキャンセルします.

USB接続はこのウェブページでおなじみの、シリアル-USB変換基板(秋月電子)を使います.


温度センサはシリコン接着剤で覆う


Terminal Softの動作画面

resetすると毎秒このような表示が出ます.
時分秒の後に続いて、AIN0-9のセンサの温度を表示します.
気温を表示しているのですから、AIN0が40度とはわたしの部屋は灼熱地獄のようですけど、そうではありません.
温度センサを取り付けたのは、AIN2/AIN6/AIN9 の3つだけなのです.
温度センサを10ヶつければ10チャンネルの温度測定ができます.
それぞれを読むと、27.6/28.7/27.0度とそれっぽい温度が表示されています.

=====start temperature log=====
07:49:09 0:39.9 1:47.1 2:27.0 3:40.3 4:47.3 5:50.1 6:28.7 7:42.3 8:47.9 9:26.9
07:49:10 0:40.0 1:47.0 2:27.6 3:40.7 4:47.4 5:50.2 6:28.8 7:42.3 8:47.9 9:27.0
07:49:11 0:40.0 1:47.0 2:27.5 3:40.6 4:47.5 5:50.2 6:28.6 7:42.4 8:47.9 9:26.8
07:49:13 0:39.8 1:47.0 2:27.6 3:40.7 4:47.4 5:50.2 6:28.6 7:42.3 8:48.0 9:26.8
07:49:14 0:39.9 1:47.1 2:27.6 3:40.6 4:47.5 5:50.1 6:28.7 7:42.2 8:48.0 9:27.0
~~~~~~~~ ^^^^^^ ~~~~~~ ^^^^^^ ~~~~~~ ^^^^^^ ~~~~~~ ^^^^^^ ~~~~~~ ^^^^^^ ~~~~~~
時 分秒    AIN0   AIN1    AIN2   AIN3   AIN4   AIN5   AIN6   AIN7  AIN8   AIN9の温度


27.6/28.7/27.0度といくらか誤差があります.
これを補正しないと多チャンネル温度計としては使えません.
現在の補正値を表示してみます.
dumpと打つと、EEPROMの補正値がダンプ表示されます.
使用履歴のないSTM8S-DISCOVERYの場合は最初はゼロです.
dump
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................


では、補正値を記憶させましょう.
水に温度計を挿し、本機の温度センサも同じ水に挿します.
温度計の表示値と本機の表示値の差を補正値として記憶させます.
たとえば温度計=18.7度で、本機の表示値=19.3度だったら、補正値は-0.6です.
次のように打つと、dump表示には記憶された補正値が表示されます.
10行表示されるのは、AIN0〜AIN9の10ヶ分だからです.
補正値はEEPROMに記憶されたのでreset後にも消えません.
補正値は符号と小数点を含めて最大で15文字です.
ofs 2 -0.6
ofs 6 0.8
ofs 9 0.4
dump
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
2d 30 2e 36 00 f2 60 00 00 00 00 ff 01 cf 18 fb  | -0.6..`......マ..
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
30 2e 38 00 00 f2 60 00 00 00 c8 ff 01 cf 18 fb  | 0.8...`...ネ..マ..
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
30 2e 34 00 00 f2 60 ff 00 fe c8 ff 01 cf 18 fb  | 0.4...`...ネ..マ..


補正値をクリアしたいときには、clrと打ちます.dumpすると空っぽになっています.
clr
dump
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................


時刻表示を変えたいときはtimeコマンドを使います.
時刻表示が8時31分からに変わります.時刻表示はresetすると消えます.
time 8 31
08:31:04 0:36.5 1:45.5 2:24.4 3:38.8 4:46.7 5:50.1 6:15.5 7:39.2 8:46.8 9:22.7
08:31:05 0:36.1 1:45.5 2:24.3 3:38.9 4:46.6 5:49.9 6:23.8 7:39.2 8:46.7 9:22.8
08:31:06 0:36.5 1:45.6 2:24.5 3:38.9 4:46.6 5:50.1 6:23.6 7:39.3 8:46.6 9:22.9
08:31:07 0:36.4 1:45.5 2:24.4 3:38.6 4:46.6 5:49.8 6:31.9 7:39.3 8:46.7 9:22.9


表示インターバルを0.5秒毎にしたいときは、
int 0.5
表示インターバルを3秒毎にしたいときは、
int 3
と打ちます.0.1秒〜60秒の範囲で可変できます.それを越えると却下されます.
COMMAND NG !!!
コマンドメニューが表示されます.
コマンドvはverboseの意味で、ADC値などの詳細な値がダーッと表示されます.
もういちどvと押すと通常表示に戻ります.
time 21 22 --> set time hour/min
int 3      --> interval time 3 seconds
ofs 4 -0.5 --> set AIN4 offset to -0.5
v          --> switch verbose <--> silent
dump       --> dump EEPROM
clr        --> clear EEPROM




基板回路図


ハードウエアの製作

@USBシリアル変換回路

まず、UARTをUSBに変換する回路を、STM8S-DISCOVERYにどうやって取り付けるかを説明します.
秋月のUSBシリアル変換基板をSTM8S-DISCOVERYに取り付けます.
回路図は下記です.配線は上記回路図の4本です.
●STM8SのPD5とPD6には、UARTの送信信号と受信信号が出てきますので、それをUSBシリア ル基板に配線しています.
STM8S-DISCOVERYでは、電源はFLASH焼き回路から5V or 3.3Vが供給されますが、これを切断し、USBシリアル変換基板から3.3Vが供給されるようにします.
ただし5Vじゃダメってことではありません.3.3Vにしたのはなんとなくです.
そのために、ジャンパーをつぎの1)2)のように設定 してください.
  1) USBシリアル変換基板のジャンパーピンは、下図の赤色の位置にします.
  2) STM8S-DISCOVERYのJP1のジャンパーピンは、外しておきます.

USBシリアル変換基板の取付完了状態はこのようになります.
右の黄色の囲いがUSBシリアル変換回路です.同基板に刺さっているMini-USBがPCのUSBへ行きます.
中央の水色の囲いがSTM8Sマイコンです.USBシリアル変換回路と配線されています.
左の赤い囲いは、ブロック図には描きませんでしたがSTM8Sを焼くために必須な回路です.同基板に刺さっているUSBケーブルは、STM8Sを焼くためのケーブルです.やはりPCに接続します.
この回路は、PCのUSBポートを都合2ヶ占有することになります.

プログラムを焼いてしまえば、焼き用のUSBケーブルは不要になるわけで、下図のようにUSBを外してしまいたくなるのが人情です.
ところが、USBケーブルを外しただけでは、STM8Sにリセットがかかってしまって動かなくなってしまいます.
そこで、下の写真の丸の中にあるSB1とSB2をハンダごてでOPENにしてやればリセット線が切断されるので、STM8Sが動きます.
この方法は憶えておきましょう.


AリセットSW
これは適当につけてください.
プルアップ抵抗は不要で、対GNDへ落とす形式でOKです.


B温度センサ
仕様はこちらです.
  (LM61CZのpdf)
トランジスタ型の3端子で、電源をつなぐと、温度相関のある電圧が出てきます.25度で850mVです.10mV/度の感度です.
回路図のように接続します.リード線を1mぐらい長く伸ばしても動きました.

   



ソースコードの解説

サンプルプログラムをこちらに置きます

今回は、ソースコードサイズが大きいので、STVDをこの設定でbuildしました.
ADCのライブラリを使うとサイズが大きくなるようです.
Global Variables = in Data
Program model = STM8 Large model


main.c

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
文字列操作の関数をいろいろ使うので、Cの標準ライブラリをインクルードしておきます.

#include "stm8s.h"
STMicroのライブラリを使うためのインクルードです.

#include "usrlib-uart.h"
UARTを使うための自作のライブラリのためのインクルードです.

void UART_Menu(void);
メニュールーチンのプロトタイプ宣言です.

// address of data on EEPROM  (offset calinration values)
char* EEPROM_OFFSET_AIN0 = (char*)0x4000;
char* EEPROM_OFFSET_AIN1 = (char*)0x4010;
char* EEPROM_OFFSET_AIN2 = (char*)0x4020;
char* EEPROM_OFFSET_AIN3 = (char*)0x4030;
char* EEPROM_OFFSET_AIN4 = (char*)0x4040;
char* EEPROM_OFFSET_AIN5 = (char*)0x4050;
char* EEPROM_OFFSET_AIN6 = (char*)0x4060;
char* EEPROM_OFFSET_AIN7 = (char*)0x4070;
char* EEPROM_OFFSET_AIN8 = (char*)0x4080;
char* EEPROM_OFFSET_AIN9 = (char*)0x4090;
これらが不揮発性データです.
不揮発性データ名をキャラクタポインタで宣言し、それにEEPROMアドレスをあてはめています.

EEPROMは0x4000以降です.
各変数に16BYTEを割り当てます.

// work area
char UARTstrTX[80];
char dump_ascii[4];
char dump_char[17];
Terminal Softへ表示するための文字列をつくるためのワークエリアです.

float offset_AIN[10];
AINxのoffset値のワークエリアです.

int i,k;

void main(void)
{
メイン関数の始まりです.

2012.3.9訂正
CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
clockを外部水晶の16MHzにします.


2012.3.9訂正
TIM3_DeInit();
TIM3_TimeBaseInit(TIM3_PRESCALER_512,3124); // 0.1sec timer
TIM3_ITConfig(TIM3_IT_UPDATE, ENABLE);
TIM3_Cmd(ENABLE);
インジケータLEDをチカチカさせるためのタイマ割り込みの設定です.約0.1Secで割り込みします.
なんで0.1Secになるかというと、16MHz/512/3125=0.1だからです.

 ADC1_DeInit();
一応ADCを初期化してから.

 ADC1_Init(@ADC1_CONVERSIONMODE_CONTINUOUS, AADC1_CHANNEL_9, BADC1_PRESSEL_FCPU_D4, CADC1_EXTTRIG_TIM, DDISABLE, EADC1_ALIGN_RIGHT, FADC1_SCHMITTTRIG_ALL, GDISABLE);
ADCを初期化する引数がたくさんある関数です.
@ADCを連続変換モードにするか、一発変換モードにするかの選択.
_CONTINUOUSは連続変換、_SINGLEは一発変換.
この用途では、_CONTINUOUSにすること.
AADCの入力AINxのどれを変換するかを設定する.
_9だと10個全部._8だとAIN0〜AIN8までの9個を変換._0だとAIN0だけを変換するようになります.
変換しないピンは、普通のデジタルIOピンとして使えると思いますが、未確認です.
B
訂正 2012.3.9
ADC clock を fMASTER/4=4MHzにします.次の選択肢があります.
    ADC1_PRESSEL_FCPU_D2  Prescaler selection fADC1 = fcpu/2.  
    ADC1_PRESSEL_FCPU_D3  Prescaler selection fADC1 = fcpu/3.  
    ADC1_PRESSEL_FCPU_D4  Prescaler selection fADC1 = fcpu/4.  
    ADC1_PRESSEL_FCPU_D6  Prescaler selection fADC1 = fcpu/6.  
    ADC1_PRESSEL_FCPU_D8  Prescaler selection fADC1 = fcpu/8.  
    ADC1_PRESSEL_FCPU_D10  Prescaler selection fADC1 = fcpu/10.  
    ADC1_PRESSEL_FCPU_D12  Prescaler selection fADC1 = fcpu/12.  
    ADC1_PRESSEL_FCPU_D18  Prescaler selection fADC1 = fcpu/18.  

ADCのclockは1〜4MHzという制限がありますので、適切なfADC1になるように設定する必要があります.
ここでは16MHz÷4=4MHzにしています.

ちなみに、FCPUという名前でdefineされているのでfCPUを分周するかのごとく誤解してしまいますが、正しくはfMASTERを分周します.これはSTM8Sのライブラリを書いた人の誤解による誤記と思われます.
fMASTERはこのサンプルプログラムでは16MHzですから、ADC clockを8MHz〜888kHzの範囲で選択できます.
ただし、AD変換レートが8MHz〜888kHzになるわけではありません.
なぜなら、1回のAD変換に14clock必要だからです.

CDはADC変換のトリガ信号を選択する引数ですが、DがDISABLEになっているので、Cはドントケアでいいでしょう.
EADCは10bitですが、変換結果は16bitで返されます.
なので、左詰にするか右詰にするかをこの引数で選択します.
普通は_RIGHTの右詰でつかいます.
FGはAINxのシュミット特性をオンオフするために使います.
わたしの感触では、シュミット特性がオンでもオフでもAD変換動作には影響ないようです.
しかし問題は、シュミット回路にアナログ信号が入力されると消費電力が増えてしまうことだそうです.
ですから、この機能は、アナログ入力ピンのシュミット特性をオフし、デジタル入力ピンにはシュミット特性をオンしておくべきだという正しい運用をするための引数です.

ADC1_ScanModeCmd(ENABLE);
AINxを一気にスキャンするモードにするおまじないです.
AINxのどこをスキャンするかは、
ADC1_Init()のAADC1_CHANNEL_xで決めます.

ADC1_DataBufferCmd(ENABLE);
AINxを一気にスキャンするモードで、スキャン結果を保存するためのレジスタをオンにするおまじないです.

ADC1_StartConversion();
ここでADCを変換スタートすればOKです.
(実例31 10ch電圧計では割り込みルーチンで毎回この関数をコールしていますが、あれは冗長な操作だったようです.orz)

UART2_DeInit();
UART2_Init((u32)115200, UART2_WORDLENGTH_9D, UART2_STOPBITS_1, UART2_PARITY_ODD, UART2_SYNCMODE_CLOCK_DISABLE, UART2_MODE_TXRX_ENABLE);   
UART2_ITConfig(UART2_IT_RXNE_OR, ENABLE);
UART2_Cmd(ENABLE);
UARTを使うための設定です.おなじみなので説明は省略します.

GPIO_Init(GPIOD, GPIO_PIN_0, GPIO_MODE_OUT_PP_HIGH_FAST); // LED
LEDが接続されたポートを出力に設定しています.

offset_AIN[0]=atof(EEPROM_OFFSET_AIN0);
offset_AIN[1]=atof(EEPROM_OFFSET_AIN1);
offset_AIN[2]=atof(EEPROM_OFFSET_AIN2);
offset_AIN[3]=atof(EEPROM_OFFSET_AIN3);
offset_AIN[4]=atof(EEPROM_OFFSET_AIN4);
offset_AIN[5]=atof(EEPROM_OFFSET_AIN5);
offset_AIN[6]=atof(EEPROM_OFFSET_AIN6);
offset_AIN[7]=atof(EEPROM_OFFSET_AIN7);
offset_AIN[8]=atof(EEPROM_OFFSET_AIN8);
offset_AIN[9]=atof(EEPROM_OFFSET_AIN9);
EEPROMの補正値は文字列なのでfloatに変換します.あとで使います.

enableInterrupts();
割り込みイネーブル.

SerialPutString("=====start temperature log=====\r\n");
オープニングメッセージを表示します.

while (1){ UART_Menu();    } // Main loop
}
メニュールーチンを永久ループしてメインルーチンを終えます.


u16 adc[10]; // ADC0-9 values
u32 adc_sum[10];  // ADC0-9 sum values
float mV[10]; // ADC0-9 mV values
float temperature[10]; // calculated temperature
char VERBOSE='s';   // silent or verbose
float INTERVAL=1;     // display interval second
u16 cnt=0; // 0-9 to counte 1sec
u16 adc_sum_cnt; // AINx summation times
u32 count_01sec;    // interrupt counter (0.1sec)
u8 now_hour, now_min, now_sec;
ワークエリアをいろいろ宣言します.
スタックメモリ容量が小さなマイコンなので、わたしの好みで変数の多くをこのようにglobal変数定義しています.
一般にソフトウエアテクニックとしてどうなんでしょうか?

void Tim3Update(void) interrupt 15 // 0.1Sec interrupt
{
タイマ3割り込みルーチンです.0.1Secごとにここに飛んできます.

    GPIO_WriteReverse(GPIOD,GPIO_PIN_0); // LED flash
LEDをチカチカさせます.

    count_01sec++;
時刻表示のためのカウンタです.0.1Sec刻みで時刻をカウントする変数です.

    cnt++;
表示インターバルをカウントする0.1Sec刻みのカウンタです.

    if(cnt>(10*INTERVAL)){
        cnt=0; // 1sec counter
INTERVALはデフォルトで1Secになっています.
なので、cnt==10になったら1Sec経過したことになりますので、以下の表示ルーチンに入ってゆきます.


        // calculations of voltage and temperature
        for(i=0;i<=9;i++)
        {
            mV[i] = (float)adc_sum[i]/(float)adc_sum_cnt/1024.0*3333;
            temperature[i] = ( mV[i] - 850 ) / 10 + 25 + offset_AIN[i];
        }
AD変換値を、電圧変数であるmVに変換し、さらに温度変数であるtemperatureに変換します.
配列はAIN0〜AIN9を意味します.
ここでadc_sumという変数が唐突に出てきていますが、裏で0.1Secの割り込みごとにAD変換値を積算しているんです.
積算する理由は、ノイズを減らすためです.
adc_sumが積算値で、adc_sum_cntが積算回数です.
ADCは10bitです.かつフルスケールで3.3Vです.なのでmV=ADC/1024x3333で電圧値に変換します.
3300でも良かったかもですが....
温度に変換するのは、25度で850mVで、10mV/度ですので、上の数式になっています.

        // current time to display
        now_hour= (u8)((count_01sec/36000) % 24);
        now_min = (u8)((count_01sec/600) % 60);
        now_sec = (u8)((count_01sec/10) % 60);
        sprintf(UARTstrTX,"%02d:%02d:%02d ",(int)now_hour,(int)now_min,(int)now_sec);
        SerialPutString(UARTstrTX);
時刻を表示します.
0.1Sec刻みでcount_01secがカウントしていますので、時分秒に変換してからUARTに送信します.

        for(i=0;i<=9;i++)
        {
            sprintf(UARTstrTX,"%d:%2.1f ",i,temperature[i]);
            SerialPutString(UARTstrTX);
        }
        SerialPutString("\r\n");
温度を10チャンネル分表示します.

        if(VERBOSE=='v')
        {
            SerialPutString("ADC(0-9)=");
            for(i=0;i<=9;i++)
            {
                sprintf(UARTstrTX,"%d:%d ",i,(int)((float)adc_sum[i]/(float)adc_sum_cnt));
                SerialPutString(UARTstrTX);
            }
            SerialPutString("\r\n");
            SerialPutString("mV(0-9)=");
            for(i=0;i<=9;i++)
            {
                sprintf(UARTstrTX,"%d:%d ",i,(int)mV[i]);
                SerialPutString(UARTstrTX);
            }
            SerialPutString("\r\n");
        }
verboseモードの場合はVERBOSEという変数に”v”の文字が入っています.
ADC値とmV値を表示します.


        // prepair for next turn
        adc_sum_cnt=0;
        for(i=0;i<=9;i++){ adc_sum[i]=0; }
    }
表示インターバルごとの表示処理はこれで終わりです.
次の表示インターバルの準備のために、変数を初期化しています.


    else {
        adc_sum_cnt++;
        for(i=0;i<=9;i++)
        {
            adc[i]=ADC1_GetBufferValue(i);
            adc_sum[i]+=adc[i];
        }
        if(VERBOSE=='v'){
            SerialPutString("ADC(0-9)=");
            for(i=0;i<=9;i++){
                sprintf(UARTstrTX,"%d:%4d ",i,adc[i]);
                SerialPutString(UARTstrTX);
            }
            SerialPutString("\r\n");
        }
    }
このelse以降は、表示インターバルではない、裏の処理です.
裏の処理では、0.1Secごとに、ADC値を積算しています.
ADC1_GetBufferValue(i) がADC値を取ってくる関数です.
verboseモードではADC値も表示します.


    TIM3_ClearFlag(TIM3_FLAG_UPDATE);
}
次回のタイマ3割り込みのために、割り込みフラグをクリアしておきます.
これで、タイマ3割り込み処理ルーチンはおしまいです.



void UART_Menu(void)
{
メニュー処理ルーチンの始まり.

int fieldnumber;
Terminalへ打ち込まれたコマンドラインの単語の数が入る変数です.

if(UART_STR_EXIST==1) // there is a command line
  {
UARTの処理ルーチンから、コマンドを1行受信するとUART_STR_EXISTフラグがセットされます.

     fieldnumber = separate_line(command);
separate_line()を呼ぶと、コマンドラインを単語ごとに分離してくれます.
単語が2つだったら、2を返します.

単語はField[]に入ります.


        // verbose mode or not
        if(@strcmp(Field[0],"v")==0 && Afieldnumber==1)
        {
            A(VERBOSE=='v') ? (VERBOSE='s') : (VERBOSE='v');
        }

コマンドvを処理するところです.
@最初の単語=vで、単語の数が1つであることが条件です.
A変数VERBOSEはvとsを交互にスイッチさせます.
ここでvがセットさせると、タイマ3割り込みルーチンで詳細に表示します.

        // set interval second
        else if(strcmp(Field[0],"int")==0 && fieldnumber==2)
        {
            if(atof(Field[1])<0.1)goto NG;
            else if(atof(Field[1])>60)goto NG;
            else INTERVAL = atof(Field[1]);
        }
表示インターバル時間をセットするコマンドintの処理をするところです.
コマンド名=intで、単語数=2であることが条件です.
さらに、2nd引数が0.1未満ならNGとします.60超ならNGとします.
最期に、変数INTERVALに引数をfloat変換して代入します.

        // set date and time
        else if(strcmp(Field[0],"time")==0 && fieldnumber==3)
        {
            u8 hour, min;
            hour = (int)atof(Field[1]);
            min  = (int)atof(Field[2]);
            count_01sec = (u32)hour*60*60*10 + (u32)min*60*10;
        }
時刻をセットするコマンドtimeの処理をするところです.
コマンド名=timeで、単語数=3であることが条件です.
0.1Sec刻みのカウンタに時刻を代入します.
カウント操作はタイマ3割り込みで実行されます.

        // set offset values
        else if(strcmp(Field[0],"ofs")==0 && fieldnumber==3)
        {
            int AINx;
            AINx = (int)atof(Field[1]);
            if(AINx<0 || 9<AINx) goto NG; // check AINx
            else
            {
                @FLASH_Unlock(FLASH_MEMTYPE_DATA);
                for(i=0;i<=15;i++)
                {
                    AFLASH_EraseByte(0x4000+AINx*16+i);
                    BFLASH_ProgramByte(0x4000+AINx*16+i,(u8)Field[2][i]);
                }
                CFLASH_Lock(FLASH_MEMTYPE_DATA);
                offset_AIN[AINx]=atof(Field[2]);
            }
        }
補正値を設定するコマンドofsを処理するところです.
コマンド名=ofsで、単語数=3であることが条件です.
2nd引数=AINxを指定し、3rd引数=補正値です.
AINxが0〜9でなければNGです.
@EEPROMへの書き込みロックを解除します.
AEEPROMを1BYTE消します.
BEEPROMに1BYTE書きます.
CEEPROMを書き込みロックします.
最後にoffset_AIN変数に3rd引数を代入します.

EEPROM上の格納のされ方は、次のように文字列の15文字です.
領域は16文字確保してありますが、末尾は00ですので、文字数は15文字です.
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00  | ABCDEFGHIJKLMNO.

        // dump EEPROM
        else if(strcmp(Field[0],"dump")==0 && fieldnumber==1)
        {
            @disableInterrupts();
            SerialPutString("\r\n");
            for(i=0;i<=9;i++)
            {
                UARTstrTX[0]=0; // null string
                dump_char[16]=0;
                for(k=0;k<=15;k++)
                {
                    uint8_t a;
                    Aa=FLASH_ReadByte(0x4000+16*i+k);
                    sprintf(dump_ascii,"%02x ",a);
                    strcat(UARTstrTX,dump_ascii);
                    if(a<32)a=46;
                    if(a==0x7F)a=46;
                    if((a&0xF0)==0x80)a=46;
                    if((a&0xF0)==0x90)a=46;
                    if((a&0xF0)==0xE0)a=46;
                    if((a&0xF0)==0xF0)a=46;
                    dump_char[k]=a;
                }
                SerialPutString(UARTstrTX);
                SerialPutString(" | ");
                SerialPutString(dump_char);
                SerialPutString("\r\n");
            }
          BenableInterrupts();
        }
EEPROMを表示するコマンドdumpを処理するところです.
@dump中にタイマ3割り込みによる表示が邪魔するので、一旦割り込みを禁止します.
AEEPROMから1BYTE読むには、
FLASH_ReadByte() 関数を使います.
表示の詳細は省きます.
Bdumpが終わったら割り込みを許可します.

        // clear EEPROM
        else if(strcmp(Field[0],"clr")==0 && fieldnumber==1)
        {
            disableInterrupts();
            SerialPutString("\r\n");
            FLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=0x9f;i++) FLASH_EraseByte(0x4000+i);
            FLASH_Lock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=9;i++)offset_AIN[i]=0;
          enableInterrupts();
        }
EEPROMをクリアするコマンドclrを処理するところです.
0x4000〜0x409FまでをFLASH_Erase()しています.

        else
        {
            NG:
            SerialPutString("\r\nCOMMAND NG !!!\r\n\n");
            SerialPutString("time 21 22 --> set time hour/min\r\n");
            SerialPutString("int 3      --> interval time 3 seconds\r\n");
            SerialPutString("ofs 4 -0.5 --> set AIN4 offset to -0.5\r\n");
            SerialPutString("v          --> switch verbose <--> silent\r\n");
            SerialPutString("dump       --> dump EEPROM\r\n");
            SerialPutString("clr        --> clear EEPROM\r\n");
        }
該当するコマンドがなかった場合は、コマンドNGと表示します.メニューを表示します.

        UART_STR_EXIST=0;
フラグをクリアします.
    }
}



usrlib_uart.h

UARTを使うための自作ライブラリです.
普段はあまりいじらないのですが、今回は、単語の文字数を15文字に制限しないと不揮発性データ領域の内容を破壊しますので、文字数を15文字に制限する役目をここで担っています.
下記の、FIELD_CHAR 16というところが制限です.末尾のNULL文字を含めて16BYTEの文字列領域を確保します.
もしも16文字以上だと、その単語は無視されますので、トラブルを防いでいます.

void SerialPutChar(char c);
void SerialPutString(char *s);
int separate_line(char *tmp);
void UART2RX_isr(void);

#define FIELD_NUM 4   // 4 words in a line
#define FIELD_CHAR 16  // 15 characters in a word

extern char Field[FIELD_NUM][FIELD_CHAR];
extern char command[FIELD_NUM*FIELD_CHAR];
extern u8 UART_STR_EXIST;
extern int bytes_read;







inserted by FC2 system