不揮発性データをSTM8Sに書くには?

不揮発性データとは、電源を切っても記憶が残っているデータのことです.

どんな場面で使いたくなるかというと、製品の製造番号(シリアルナンバー)は1台1台固有の番号ですから、Cのプログラムに埋め込むわけにはいきません.
1台1台に固有の番号を記憶させなくちゃいけません.
そういうときに、不揮発性メモリに不揮発性データを書きたくなります.

また、装置1台1台の誤差を補正するパラメータも、1台1台固有の数値じゃないと困りますので、不揮発性メモリに不揮発性データを書きたくなります.

STM8Sでは、メモリ空間の、4000H〜43FFHまでの1kBが不揮発性メモリで占められていて、ユーザーが自由に使えます.
メモリマップはこちらの資料を参照してください.
資料の1kB DATA EEPROMと書かれたところが不揮発性メモリです.

STM8Sの不揮発性メモリには、RAMのように普通に書けるのでしょうか?
答えはノーです.
めんどくさい手続きの末に書き込みできます.
読み出しは自由にできます.

以下では、書くためのめんどくさい手続きを解説します.


どんなことをさせるか?

Terminal SoftからSTM8Sを制御します.
Resetすると、Terminal Softには次の表示が出ます.
上半分はコマンドの例を表示しています.
======CPU start======
commands:
manu ABC  --> manufacturer name is set to ABC
sn 12345  --> serian number is set to 12345
p0 98.777 --> parameter0 is set to 98.777
p1 0.0372 --> parameter1 is set to 0.0372
dump      --> dump EEPROM area
clr       --> clear EEPROM area

now settings:
manufacturer=
serial number=
parameter0=
parameter1=


dumpと打ちますと、EEPROMの先頭番地が表示されます.
まだ何も書いてないので、オールゼロです.
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  | ................


製造者名を書き込んでみましょう.
manu HRPと打ちますと、EEPROMにHRPという文字が書かれます.
次の表示が返ってきます.製造業者=HRPになりました.
now settings:
manufacturer=HRP
serial number=
parameter0=
parameter1=


次にシリアルナンバーを書き込んでみましょう.
sn SN000001と打ちますと、EEPROMにSN000001という文字が書かれます.
次の表示が返ってきます.シリアルナンバー=SN000001になりました.
now settings:
manufacturer=HRP
serial number=SN000001
parameter0=
parameter1=


次にパラメータを書き込んでみましょう.
p0 123.45と打ちますと、EEPROMに123.45という文字が書かれます.
次の表示が返ってきます.
now settings:
manufacturer=HRP
serial number=SN000001
parameter0=123.45
parameter1=


もうひとつパラメータを書き込んでみましょう.
p1 0.000111と打ちますと、EEPROMに0.000111という文字が書かれます.
次の表示が返ってきます.

now settings:
manufacturer=HRP
serial number=SN000001
parameter0=123.45
parameter1=0.000111


ここで、dumpと打ちますと、次の表示が返ってきます.
EEPROMにそれぞれの文字がどのように格納されているかがわかります.
dumpされたEEPROMのアドレスはこうなっています.
1行目の先頭アドレス=0x4000
2行目の先頭アドレス=0x4010
3行目の先頭アドレス=0x4020
4行目の先頭アドレス=0x4030
dump
48 52 50 00 f7 85 87 88 7b 01 c7 52 41 84 87 c6  | HRP.....{.ヌRA..ニ
53 4e 30 30 30 30 30 31 00 01 c7 52 41 84 87 c6  | SN000001..ヌRA..ニ
31 32 33 2e 34 35 00 31 00 01 c7 52 41 84 87 c6  | 123.45.1..ヌRA..ニ
30 2e 30 30 30 31 31 31 00 00 c7 52 41 84 87 c6  | 0.000111..ヌRA..ニ


ここで、本当に不揮発なのかどうかを試してみましょう.
USBを抜き差しするかResetするかして、表示を確認すると、ちゃんとデータが残っています.
now settings:
manufacturer=HRP
serial number=SN000001
parameter0=123.45
parameter1=0.000111


最後に、EEPROMをクリアします.clrと打ちます.
次にdumpすると、次のようにオールゼロになります.
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  | ................



基板写真
今回は動作確認のため頻繁にresetしたいのでreset SWもつけました.


基板回路図


ハードウエアの製作

@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です.




ソースコードの解説

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

今回は、STVDをこの設定でbuildしました.なんとなくやっていたらこの設定に落ち着きましたが、元に戻してもよいのかもしれません.
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
char* MANUFACTURER     = (char*)0x4000;
char* SN                        = (char*)0x4010;
char* PARAMETER0        = (char*)0x4020;
char* PARAMETER1        = (char*)0x4030;
これらが不揮発性データです.
不揮発性データ名をキャラクタポインタで宣言し、それにEEPROMアドレスをあてはめています.

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

float param_float0, param_float1;

不揮発データのPARAMETER0とPARAMETER1は数値を入れるつもりなので、atof(PARAMETER0)でfloatに変換してこれらの変数に格納します.

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

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

CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
CLK_HSIPrescalerConfig( CLK_PRESCALER_HSIDIV1 );
CLK_SYSCLKConfig( CLK_PRESCALER_CPUDIV1 );
とくに意味はありませんが、今回はSTM8S内蔵発振回路の16MHzを使いました.
プリスケーラ=1に設定していますので、タイマとCPUには16MHzが供給されます.

  

TIM3_DeInit();
TIM3_TimeBaseInit(TIM3_PRESCALER_512,3200); // 0.1sec timer
TIM3_ITConfig(TIM3_IT_UPDATE, ENABLE);
TIM3_Cmd(ENABLE);
インジケータLEDをチカチカさせるためのタイマ割り込みの設定です.約0.1Secで割り込みします.

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);
Terminal Softと通信するためのUARTの設定です.

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

// read non volative parameters
param_float0=atof(PARAMETER0);
param_float1=atof(PARAMETER1);
パラメータ文字列をfloatに変換します.
儀式的にやってますが、とくにこの変数は活用しません.

  

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

SerialPutString("\r\n======CPU start======\r\n");
SerialPutString("commands:\r\n");
SerialPutString("manu ABC  --> manufacturer name is set to ABC\r\n");
SerialPutString("sn 12345  --> serian number is set to 12345\r\n");
SerialPutString("p0 98.777 --> parameter0 is set to 98.777\r\n");
SerialPutString("p1 0.0372 --> parameter1 is set to 0.0372\r\n");
SerialPutString("dump      --> dump EEPROM area\r\n");
SerialPutString("clr       --> clear EEPROM area\r\n");
オープニングメッセージと、メニューリストを表示します.

SerialPutString("\r\nnow settings:\r\n");
sprintf(UARTstrTX,"manufacturer=%s\r\n",MANUFACTURER);SerialPutString(UARTstrTX);
sprintf(UARTstrTX,"serial number=%s\r\n",SN);                    SerialPutString(UARTstrTX);
sprintf(UARTstrTX,"parameter0=%s\r\n",PARAMETER0);    SerialPutString(UARTstrTX);
sprintf(UARTstrTX,"parameter1=%s\r\n",PARAMETER1);    SerialPutString(UARTstrTX);
不揮発データを表示します.

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



void Tim3Update(void) interrupt 15 // 0.1Sec interrupt
{
    GPIO_WriteReverse(GPIOD,GPIO_PIN_0); // LED flash
    TIM3_ClearFlag(TIM3_FLAG_UPDATE);
    return;
}
LEDをチカチカさせるためのタイマ3割り込みルーチンです.



void UART_Menu(void)

{
メニュールーチンの始まり.

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

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

     SerialPutString("\r\n");
Terminal画面を1行改行します.単なる見栄えの調節のため.

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

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


     // change manufacturer name
     @if(strcmp(Field[0],"manu")==0 && fieldnumber==2)
      {
            AFLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=15;i++)
            {
                BFLASH_EraseByte(0x4000+i);
                CFLASH_ProgramByte(0x4000+i,(u8)Field[1][i]);
            }
            DFLASH_Lock(FLASH_MEMTYPE_DATA);
      }

コマンドmanuを処理するところです.
@最初の単語=manuで、2番目の単語がなにかしらあることが条件です.
AEEPROMに書くには、FLASH_Unlock()でロック解除します.
B所望のアドレスのEEPROMを消します.製造者名は0x4000番地から16文字です.
C所望のアドレスのEEPROMにデータを書きます.書くデータはコマンドラインの第2単語Field[1]です.
DEEPROMを書けなくするためにFLASH_Lockでロックします.

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.

        // change serial number
        else if(strcmp(Field[0],"sn")==0 && fieldnumber==2)
        {
            FLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=15;i++)
            {
                FLASH_EraseByte(0x4010+i);
                FLASH_ProgramByte(0x4010+i,(u8)Field[1][i]);
            }
            FLASH_Lock(FLASH_MEMTYPE_DATA);
        }
コマンドsnを処理するところです.
やっているところはコマンドmanuと同じです.
EEPROMアドレスは0x4010です.

        // change parameter0
        else if(strcmp(Field[0],"p0")==0 && fieldnumber==2)
        {
            FLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=15;i++)
            {
                FLASH_EraseByte(0x4020+i);
                FLASH_ProgramByte(0x4020+i,(u8)Field[1][i]);
            }
            FLASH_Lock(FLASH_MEMTYPE_DATA);
        }
コマンド パラメータ0を処理するところです.
やっているところはコマンドmanuと同じです.
EEPROMアドレスは0x4020です.

        // change parameter1
        else if(strcmp(Field[0],"p1")==0 && fieldnumber==2)
        {
            FLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=15;i++)
            {
                FLASH_EraseByte(0x4030+i);
                FLASH_ProgramByte(0x4030+i,(u8)Field[1][i]);
            }
            FLASH_Lock(FLASH_MEMTYPE_DATA);
        }
コマンド パラメータ1を処理するところです.
やっているところはコマンドmanuと同じです.
EEPROMアドレスは0x4030です.

        // clear EEPROM
        else if(strcmp(Field[0],"clr")==0 && fieldnumber==1)
        {
            FLASH_Unlock(FLASH_MEMTYPE_DATA);
            for(i=0;i<=0x3f;i++)
            {
                FLASH_EraseByte(0x4000+i);
            }
            FLASH_Lock(FLASH_MEMTYPE_DATA);
        }
EEPROMをクリアするコマンド処理です.
0x4000〜0x403FまでをFLASH_Erase()しています.

        // dump EEPROM
        else if(strcmp(Field[0],"dump")==0 && fieldnumber==1)
        {
            for(i=0;i<=3;i++)
            {
                UARTstrTX[0]=0; // null string
                dump_char[16]=0;
                for(k=0;k<=15;k++)
                {
                    uint8_t a;
                    a=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");
            }
        }
EEPROMの0x4000〜0x403Fをダンプ表示する処理です.

        else

        {
            SerialPutString("\r\nCOMMAND NG!!!\r\n");
        }
該当するコマンドでなかった場合は、コマンドNGと表示します.

        // read non volative parameters
        param_float0=atof(PARAMETER0);
        param_float1=atof(PARAMETER1);
再度、パラメータをfloatに変換します.
形式的にやっているだけでfloat変数はなんら活用していません.

        // menu
        SerialPutString("\r\ncommands:\r\n");
        SerialPutString("manu ABC  --> manufacturer name is set to ABC\r\n");
        SerialPutString("sn 12345  --> serian number is set to 12345\r\n");
        SerialPutString("p0 98.777 --> parameter0 is set to 98.777\r\n");
        SerialPutString("p1 0.0372 --> parameter1 is set to 0.0372\r\n");
        SerialPutString("dump      --> dump EEPROM area\r\n");
        SerialPutString("clr       --> clear EEPROM area\r\n");
メニューを表示します.

        // display settings
        SerialPutString("\r\nnow settings:\r\n");
        sprintf(UARTstrTX,"manufacturer=%s\r\n",MANUFACTURER);SerialPutString(UARTstrTX);
        sprintf(UARTstrTX,"serial number=%s\r\n",SN);                    SerialPutString(UARTstrTX);
        sprintf(UARTstrTX,"parameter0=%s\r\n",PARAMETER0);      SerialPutString(UARTstrTX);
        sprintf(UARTstrTX,"parameter1=%s\r\n",PARAMETER1);      SerialPutString(UARTstrTX);
現在の不揮発性データを表示します.

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



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