不揮発性データを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;