UARTを動かす(その2)
----PWMでLEDの明度調節----COM PORTとの通信----


この連載の最初の方の、
STMicroのライブラリとRaisonance Cコンパイラをつかった割り込み処理でUARTを動かすプログラム(その1)
で、UARTについて少し解説しました.

ここでは、UARTの処理ルーチン全般について解説します.

どんなプログラムを作るか?
●PC上のTerminalソフト画面で
●up 3↓ と入力すると、3秒周期でLEDがゆっくり明るくなり、突然消える
●down 2↓ と入力すると、2秒周期でLEDがゆっくり暗くなり、突然明るくなる
●updown 5↓ と入力すると、5秒周期でLEDが明暗する
●秒数は0.01〜30の範囲とする
●起動時はupdown 2でゆるゆるとLEDが光る

システム構成
●LEDのPWM駆動
STM8S-DISCOVERYのLEDは、PD0に接続されています.
PD0は、TIM3_CH2ともピンを共有しています.
なので、LEDの発光強度をPWMで制御するには、TIM3_CH2をPWMモードでドライブすればよいです.
たいした理由はないのですが、PWM周波数を10kHzにしました.
強いて言えば、30Hz以下だとフリッカーが目につくから避けました.
かといってLEDの応答速度がそんなに高速なわけでもないので、10kHzにしました.

●PCとの通信
UARTでPCと通信します.
STM8S-DISCOVERYのUARTは、PCのCOM PORTに接続し通信するための回路です.
しかし、今のPCにはCOM PORTは付いてないので、FT232というICでUSBに変換します.
そして、USBケーブルでPCに接続します.
PCではTerminalソフトをつかって通信します.
したがって、PCを操作する人間から見えるのは、terminal ソフトという通信アプリの画面です.


●PWM dutyを変更する周期

TIM2で1000Hzで割り込んでもらいます.
TIM2を使ったことにたいした理由はないです.TIM1でもTIM4でもOKです.

●clock
たいした理由はないのですが、STM8S内部発振の16MHzにします.


割り込み構成
TIM2 update割り込み
13番
fMASTER=16MHzでTIM2へclockが供給されます.
プリスケーラはつかいません(=分周比1).
1000Hzで割り込みたいので、カウント周期ARRは、1600にすればOKってことです.
つまり、16MHz/1600=1000Hz
UART受信割り込み
21番
UARTが文字を受信したら割り込みをかける

画面表示例



ハードウエアの準備

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のジャンパーピンは、外しておきます.


STM8S-DISCOVERYのボードレイアウトはこうなっています.


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


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



ソースコードの説明

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

main.c

●#include "stdio.h"
#include "stdlib.h"
#include "string.h"
C言語の標準関数をインクルードします.

●#include "stm8s.h"
ライブラリをつかうのでインクルードします.

●#include "usrlib-uart.h"
UARTを使うための自作ルーチンをインクルードします.

●#define fMASTER    16000000  // 16MHz
#define TIM2_FREQ  1000      // duty modification cycle
#define TIM3_FREQ  10000     // LED PWM cycle
#define TIM2_ARR   ((u16)(fMASTER/TIM2_FREQ)-1)   // 16MHz/1000-1=1599
#define TIM3_ARR   ((u16)(fMASTER/TIM3_FREQ)-1)   // 16MHz/10000-1=159
ここはいろいろ定義しています.
クロック周波数16MHz、TIM2割り込み周期1000Hz、TIM3 PWM周期10kHz です.
TIMx_ARRは、タイマのカウンタのmax値を計算しています.

●void UART_Menu(void);
あとで出てくる、UARTとのコマンド処理ルーチンです.
main()の中から永久ループでcallされます.

●char TXStr[100];
UARTへ送信する文字列を格納する変数です.

●void main(void)
メインルーチンのはじまり

●@CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
ACLK_HSIPrescalerConfig( CLK_PRESCALER_HSIDIV1 );
BCLK_SYSCLKConfig( CLK_PRESCALER_CPUDIV1 );
@内部発振16MHzを源発振としています(HSI).
Aプリスケーラは1倍にしましたので fMASTER=16MHzです.タイマにも16MHzが供給されます.
Bプリスケーラは1倍にしましたので fCPU=16MHzです.CPUに16MHzが供給されます.

TIM2_DeInit();
CTIM2_TimeBaseInit(TIM2_PRESCALER_1, TIM2_ARR);
DTIM2_ITConfig(TIM2_IT_UPDATE, ENABLE);
TIM2_Cmd(ENABLE);
Cでは
プリスケーラ1倍ですから16MHzを1600カウントする設定になります.
その結果1000HzでTIM2のカウンタがグルグル回ります.
Dではカウンタがmaxになったら割り込みをかけるように設定しています.
その結果1000Hzで割り込みが発生します.

TIM3_DeInit();
ETIM3_TimeBaseInit(TIM3_PRESCALER_1, TIM3_ARR);
TIM3_Cmd(ENABLE);
Eプリスケーラ1倍ですから16MHzを160カウントする設定になります.
その結果10kHzでTIM3のカウンタがグルグル回ります.
割り込みはありません.


●UART2_DeInit();

UART2_Init( F(u32)115200,  GUART2_WORDLENGTH_9D,  HUART2_STOPBITS_1,  IUART2_PARITY_ODD, JUART2_SYNCMODE_CLOCK_DISABLE, KUART2_MODE_TXRX_ENABLE);   
LUART2_ITConfig(UART2_IT_RXNE_OR, ENABLE);
UART2_Cmd(ENABLE);
F
ボーレートを115200bpsに設定しています.9600とかいろいろ選択できます.
たぶん世の中にはこんなボーレートが選択肢として存在するようです.
110, 150, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 56000, 57600, 115200, 230400, 460800, 921600
ただ私は2400未満を使ったことがないです.また、115200超も使ったことがないです.
TeraTermというTerminalソフトでは921600という非常に高速な選択肢も可能ですが、mother-boardあるいはwindows7が対応しているのかは私は知らないです.
G1バイトの送信をパリティを含めて9bitに設定しています.すなわち文字8bit+パリティ1bitです.これをUART2_WORDLENGTH_8D にすると、文字7bit+パリティ1bit あるいは 文字8bit+パリティなし にもできます.
Hストップbitの長さを1bitに設定していますUART2_STOPBITS_2 でbitにもできます.
I奇数パリティに設定しています.UART2_PARITY_NO (パリティなし)UART2_PARITY_EVEN(偶数パリティ)にもできます.
JCOM通信にはclockがない(非同期通信)のですが、STM8SのUARTはclock付きの通信(同期通信)もできるようになっています.同期通信をしたいときにはUART2_SYNCMODE_CLOCK_ENABLE にします.同期clockはPC1(UART2_CK)に出力されるようですが、私は使ったことがありません.
K送受信するので
UART2_MODE_TXRX_ENABLE にします.受信のみとか送信のみとかにもできますが、使わないでしょう.

 蛇足かもですが、9bit, 8bit, パリティの有無についての一覧表です.
1バイトのbit数設定
パリティ設定
PCのterminalソフトのbit数
PCのterminalソフトのパリティ
UART2_WORDLENGTH_8DUART2_PARITY_NO8bit
なし
UART2_WORDLENGTH_8DUART2_PARITY_ODD7bit
ODD
UART2_WORDLENGTH_8DUART2_PARITY_EVEN7bit
EVEN
UART2_WORDLENGTH_9DUART2_PARITY_NO正しく動きません
正しく動きません
UART2_WORDLENGTH_9DUART2_PARITY_ODD8bit
ODD
UART2_WORDLENGTH_9DUART2_PARITY_EVEN8bit
EVEN

●enableInterrupts();
割り込み許可

●SerialPutString("start\r\n");
Terminalソフト画面にstartと文字を表示します

●while (1)    {      UART_Menu();    }
無限ループでUART_Menu()をcallしつづけます.


●void TIM2_Update_isr(void) interrupt 13
TIM2の割り込みルーチンです.1000Hzで割り込みがかかりここに飛んできます.
interrupt 13がRaisonance Cコンパイラの割り込み記述のやりかたです.
13はこちらの割り込みベクタ一覧表に従ったおきまりの値です.

diff = TIM3_ARR / ( T * TIM2_FREQ );
LEDのPWMは10kHzと決めました.LEDを明度調節するにはそのdutyを変えます.
変数Tは暗→明までT秒で推移させよと要求しています.
一方でTIM3のカウンタは0〜TIM3_ARR(159)までせわしなく動いています.
dutyを変えるには、TIM3コンパレータの比較値を0〜159の範囲で変えることで実現します.
T秒間のTIM2割り込み回数はT*1000回なので、T*1000回で0〜159まで渡ればよいので、TIM3コンパレータ値をTIM2割り込み1回あたり159/(T*1000)ずつ変えればよいということになります.
変数diffには、159/(T*1000) が入ります.

●TIM3_OC2 += diff*slant;
TIM3コンパレータ値 TIM3_OC2 をdiffだけ変更します.
変数slantは+1または-1でして、暗→明の局面と、明→暗の局面でコンパレータ値を増やすか減らすかを切り替えます.


●if(strcmp(mode,"up")==0 && TIM3_OC2>=TIM3_ARR) TIM3_OC2=0; // max?
変数modeが"up"なら、LEDを暗→明にします.
TIM3コンパレータ値 TIM3_OC2 が TIM3_ARR に達したら最大光度に達したので、真っ暗であるゼロに還します.

●else if(strcmp(mode,"down")==0 && TIM3_OC2<=0) TIM3_OC2=TIM3_ARR; // min?
変数modeが"down"なら、LEDを明→暗にします.
TIM3コンパレータ値 TIM3_OC2 が 0 に達したら真っ暗に達したので、最大光度であるTIM3_ARRに還します.


●else // updown mode
    {
        if(TIM3_OC2>=TIM3_ARR) // max?
        {
            TIM3_OC2=TIM3_ARR;
            slant=(-1); // down modification of PWM threshold
        }
        else if(TIM3_OC2<=0) // min?
        {
            TIM3_OC2=0;
            slant=1; // up modification of PWM threshold
        }
    }
変数modeが"updowm"なら、LEDを暗→明→暗→明と推移させます.
そのために、TIM3コンパレータ値 TIM3_OC2 が最大になったら slant=-1にして減光させます.
あるいは、TIM3_OC2 がゼロになったら、slant=1 にして増光させます.

TIM3_OC2Init(TIM3_OCMODE_PWM1, TIM3_OUTPUTSTATE_ENABLE, (u16)TIM3_OC2, TIM3_OCPOLARITY_LOW);
ようやく、TIM3のコンパレータ2に、コンパレート値 TIM3_OC2 を設定します.
最後の引数がTIM3_OCPOLARITY_LOWになってるのはLEDがLOWで点灯するからです.

●TIM2_ClearFlag(TIM2_FLAG_UPDATE);
次回の割り込みを受け付けるためにフラグを消します


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

●if(UART_STR_EXIST==1)     {
UARTからコマンドを1行受け取ったら1になります.
コマンドが来なかったらなにもしません.


●fieldnumber = separate_line(command);
コマンドライン変数commandが含む単語を切り出すルーチンです.
たとえば、command="up down updown 12" だったら、upとdownとupdownと12にバラバラの単語にし変数Fieldに格納します.
変数fieldnumberには、単語の個数4が入ります.

●if(strcmp(Field[0],"up")==0 && fieldnumber==2)
コマンドラインの先頭の単語Field[0]が"up"であり、単語の個数が2ヶなら、コマンド処理します.

a=atof(Field[1]);
if(a<0.01 || 30<a) goto NG;
T=a;
strcpy(mode,Field[0]);
slant=1;
goto RET;
コマンドラインの2ヶ目の単語は明滅の周期です.
値の範囲が0.01秒〜30秒の範囲であることを確認して変数Tに格納します.
変数modeに"up"を格納します.
upなので、増光させるためにslant=1にセットします.

else if(strcmp(Field[0],"down")==0 && fieldnumber==2)
{
   a=atof(Field[1]);
   if(a<0.01 || 30<a) goto NG;
   T=a;
   strcpy(mode,Field[0]);
   slant=(-1);
   goto RET;
}
コマンドが"down"のときのコマンド処理です.

●else if(strcmp(Field[0],"updown")==0 && fieldnumber==2)
{
    a=atof(Field[1]);
    if(a<0.01 || 30<a) goto NG;
    T=a/2;
    strcpy(mode,Field[0]);
    slant=1;
    goto RET;
}
コマンドが"updown"のときのコマンド処理です.

●NG:
   SerialPutString("\r\nCOMMAND NG!");
コマンドがおかしかったら表示する
       
●RET:
   SerialPutString("\r\nupdown 3 ---> light up and darken by 3Hz (0.01-30)\r\n");
   SerialPutString("up 2     ---> light up by 2Hz (0.01-30)\r\n");
   SerialPutString("down 1   ---> darken by 1Hz (0.01-30)\r\n\n");
メニューを表示します.

●UART_STR_EXIST=0;
コマンドラインを1行処理し終えたのでフラグをクリアします.


usrlib_uart.c

●void SerialPutChar(char c)
{
    UART2_SendData8(c);
    while ((UART2->SR & UART2_SR_TXE ) != UART2_SR_TXE );
}
文字を1文字送信するルーチンです.

●void SerialPutString(char *s)
{
    while (*s != '\0')
    {
        SerialPutChar(*s);
        s ++;
    }
}
文字列を送信するルーチンです.

●int separate_line(char *tmp){
文字列
tmpが含む単語を切り出すルーチンです.
たとえば、tmp="up down updown 12" だったら、upとdownとupdownと12にバラバラの単語にし変数Fieldに格納します.
単語の個数4を返します.

ルーチンの中身の説明は省略します.

●void UART2RX_isr(void) interrupt 21
UARTから1行を受信する関数です.
変数commandに格納します.

●if(UART2_GetITStatus(UART2_IT_RXNE)==SET)
受信した文字がるかどうかをチェック

if(UART_STR_EXIST==1) return;
受信したコマンドラインが未処理であったとしたら、何もせずにreturn

●c = UART2_ReceiveData8();
1文字をUARTから取り出します

●if(c == '\r'){  // CR

  command[bytes_read] = '\0';
  bytes_read = 0;
  UART_STR_EXIST = 1;   // command string exist flag
  return;   }
取り出した1文字がCRだったら1行の末尾なので、UART_STR_EXISTフラグを立ててreturn

else if(c == '\b'){  // BS
  if (bytes_read > 0){
    SerialPutString("\b \b");
    bytes_read--;   }
  return;  }
取り出した1文字がBSだったら1文字消去

●else if(bytes_read >= FIELD_NUM*FIELD_CHAR ){

  SerialPutString("Command too long\r\n");
  bytes_read = 0;
  return;      }
あまりにも1行の文字数が長かったらエラー

●else if(c >= 0x20 && c <= 0x7E){
   command[bytes_read++] = c;
   SerialPutChar(c);    }
取り出した1文字をcommand変数に追加する.
エコーを返す.

inserted by FC2 system