40c UARTでPCと通信 (USART1 default) (NVIC,TIM3,GPIO,LEDチカチカ)
STM32-DISCOVERYにUARTをとりつけてPCと通信してみます.
STM32F100RB にはUSART1,USART2,USART3が載っています.ブロック図はこちらです.
以下ではUSARTではなくUARTと呼称しますが、どっちも似たようなもんです.
ここでは、UART1のdefaultピンアサインでプログラムしてみます.
UARTのpinはどこ?
こちらの表にピンアサインが書かれています. LQFP64の列に着目します.
UART_TX と UART_RX のところを抜き出したのが下表です.
UART1のdefaultのピンはどこかというと、PA9(TX)とPA10(RX)だとわかります.
UARTをUSBにつなぐ回路は?
UARTをUSBに変換する回路を使ってUSB経由でPCに接続します.
つかうのは秋月電子で売られているこの回路です. http://akizukidenshi.com/catalog/g/gK-01977/
STM32-DISCOVERYとの実体配線図はこのようになります.
ここではUSART1 defaultを使うので、PA9(TX),PA10(RX)から配線を取り出します.
他のUSARTを使いたい場合は下図の各所から配線を取り出します.
terminal softは別途用意してPCにインストールしてください.
XPとかの古いwindowsではhyper-terminalというのが最初からインストールされていましたが、vista以降は無くなってしまったと思います.
サンプルプログラムでやること
PC上のterminal softを起動し、115200bps+parityなし+8bit+stop1bit+フロー制御なし で接続します.
●STM32-DISCOVERYを起動すると、下記のように1秒毎にTIM3 interrupted Xと表示されます.
行末の数字は表示毎にインクリメントされる数値.
starting UART test program
TIM3 interrupted 2
TIM3 interrupted 3
TIM3 interrupted 4
TIM3 interrupted 5
TIM3 interrupted 6
●STM32-DISCOVERY上のLEDは、青は1秒毎に点滅します.緑はUARTへ送信する度に光ります.
●terminal softでenter keyを入力すると、次のmenu表示がでます.
int 10は、LEDの点滅周期を10Hzにします.
offは、LEDをオフします.
onは、LEDの点滅を開始します.
command:
command error
-----UART TEST MENU-----
int 10 : LED flash 10 Hz
off : LED off
on : LED off
サンプルプログラム解説
サンプルプログラムはこのページの末尾からDLできます.projectの組み込み方法はこちらを参照してください.
重要な注意: 当初compile errorが出ました.エラー回避のために、syscalls.cというファイルを追加してあります.これはyagartからDLしたファイルですが、これがないとcompileできません.トラブルシュートの詳細はこちらのページを参照してください.
main.c
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "stm32f10x.h" ←STM32のhardwareに根ざした設定
#include "usrlib-uart1d.h" ←UARTを使うための自作ライブラリです
↓NVIC割り込み優先度、GPIO、TIM を設定するためのワーク構造体
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
void Menu(void);
int main(void)
{
↓各peripheralのclockをオンしています.
GPIOAはUSART1のIOがあるところです.
GPIOCはLEDのIOがあるところです.
AFIOというのは、USART1のIOとして利用することをalternate functionと呼びます.altanateとは代替という意味です.GPIOとして利用するのが本来の利用法であるのに対置してUSARTのIOとしての利用を代替と呼称しています.
// clock initialization
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); USART1はAPB2にぶら下がっています
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // UART TX/RX
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // LED
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
↓NVICは割り込み優先度をコントロールする機能です.
NVIC割り込みコントローラは4bitの優先度を持っていて、つまり最大で16個の割り込み優先度設定が出来るようです、
割り込み優先度には、PreemptionPriority とSubPriority にカテゴライズされています.
前者が大分類、後者が小分類というような階層になっています.
大分類に3bitを割り当て、小分類に1bitを割り当てるような階層作りができます.
// interrupt initialization
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
それをやるのがこの↑関数です.引数によって、大分類と小分類のbitアサインを決めます.
| PreemptionPriority | SubPriority |
---------------------------------------------------------
NVIC_PriorityGroup_0 | 0 (0bit) | 0-15 (4bit) |
NVIC_PriorityGroup_1 | 0-1 (1bit) | 0-7 (3bit) |
NVIC_PriorityGroup_2 | 0-3 (2bit) | 0-3 (2bit) |
NVIC_PriorityGroup_3 | 0-7 (3bit) | 0-1 (1bit) |
NVIC_PriorityGroup_4 | 0-15(4bit) | 0 (0bit) |
ここでは、USART3割り込みとTIM3割り込みの2つしか使いませんので、1~4のどれでも良いわけです.
↓USART3割り込み優先度の設定
①NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
②NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
③NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
④NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
⑤NVIC_Init(&NVIC_InitStructure);
①はどの割り込みかを指定します.選択肢はたくさんあるので末尾に掲載します
②はUSART1の割り込み優先度を下げています.理由は特にありません.気分でそうしました.
③小分類は最高優先度すなわち0にしておきます
④enable
⑤構造体をNVICに書く
↓TIM3割り込み優先度の設定
①NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // LED flash timer
②NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
③NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
④NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
⑤NVIC_Init(&NVIC_InitStructure);
①はどの割り込みかを指定します.選択肢はたくさんあるので末尾に掲載します
②はTIM3の割り込み優先度を最上位にしています.理由は特にありません.気分でそうしました.
③小分類は最高優先度すなわち0にしておきます
④enable
⑤構造体をNVICに書く
// USART port initialization
UART_Init(); UART1を初期化するルーチンです.あとで説明します.
↓LED portの初期設定
// LED port initialization
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // LED port
①GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
②GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure); 構造体をGPIOCに書く
①他の選択肢は GPIO_Speed_10MHz、GPIO_Speed_50MHz
②選択肢は、
GPIO_Mode_AIN アナログ入力
GPIO_Mode_IN_FLOATING floating入力(pull-upもpull-downもしない)
GPIO_Mode_IPD pull-down入力
GPIO_Mode_IPU pull-up入力
GPIO_Mode_Out_OD open-drain出力
GPIO_Mode_Out_PP push-pull出力 (フツーの出力)
GPIO_Mode_AF_OD Altanate Function open-drain出力 (peripheralのIOピンとしてアサイン)
GPIO_Mode_AF_PP Altanate Function push-pull (peripheralのIOピンとしてアサイン)
↓TIM3の初期設定
// LED PC8 flash timer
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
①TIM_TimeBaseStructure.TIM_Period = 24000; // 24MHz / 24000 / 1000 = 1Hz
②TIM_TimeBaseStructure.TIM_Prescaler = 1000;
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 構造体をTIM3へ書く
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); 16bitカウンタmaxで割り込み発生
TIM_Cmd(TIM3, ENABLE); 動作せよ
①②源clock 24MHzでプリスケ1000でカウンタmax 24000という設定だと1Hzで割り込みを仕掛けることになる
UART_PutString("starting UART test program\n"); opening messageをUARTへ出力
while(1) { Menu(); } メニューを繰り返す
} ここまででmain()はおしまい
↓debug用関数なので割愛
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line){ while (1) { } }
#endif
↓特定のGPIOを論理反転させるサブルーチン
void GPIO_ReverseBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
if(GPIO_ReadInputDataBit(GPIOx, GPIO_Pin)==0) GPIO_SetBits(GPIOx, GPIO_Pin);
else GPIO_ResetBits(GPIOx, GPIO_Pin);
return;
}
↓TIM3の割り込みルーチン
割り込みルーチンの名前 TIM3_IRQHandler は下記のファイルで決めうちされています.
C:\eclipse\workspace\stdperiph_40a_UART3_default\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7\startup_stm32f10x_md_vl.s
char UARTbuf[100]; UARTへ送信する文字列のバッファ
int times = 1; 割り込み回数
void TIM3_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); 割り込み発生フラグをクリア
GPIO_ReverseBit (GPIOC, GPIO_Pin_8); PC8を反転=LED点滅
sprintf(UARTbuf,"TIM3 interrupted %d\n",times++); UARTに表示する文字列作成
UART_PutString(UARTbuf); UARTに文字列を送信する関数
}
↓メニュー処理ルーチン
void Menu(void)
{
int n,i;
char tmp[30];
↓UARTの受信ルーチンから、1行受信したらUART_STR_EXISTフラグがオンになる.
フラグがオフならreturnする.
if(UART_STR_EXIST==0) return; // there is not any command line
↓コマンド行をspace区切りでブツ切りにします.nは単語の個数.
n=separate_line(command); // divide a line into words
↓ブツ切りにした単語はField[]に入ります.UARTに表示します.
// display command line
strcpy(UARTbuf,"\r\ncommand: ");
for(i=0;i<n;i++)
{
sprintf(tmp,"(%d)%s/ ",i,Field[i]);
strcat(UARTbuf,tmp);
}
strcat(UARTbuf,"\r\n");
UART_PutString(UARTbuf);
↓ここからがコマンド解析とコマンド処理です
if(strcmp(Field[0],"int")==0) Field[0]がintだったらTIM3の点滅周期を変更
{
float f;
f = atof(Field[1]); コマンド引数を浮動少数に変換します.周期Hzです.
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 24000; // 24MHz / 24000 / 1000 = 1Hz
TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)(1000.0/f); 周期f Hzを実現するためにプリスケ値を変更
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
}
else if(strcmp(Field[0],"off")==0) Field[0]がoffだったらLED消灯
{
GPIO_ResetBits(GPIOC, GPIO_Pin_8); LED消す
TIM_Cmd(TIM3, DISABLE); TIM3を止める
}
else if(strcmp(Field[0],"on")==0) Field[0]がonだったらTIM3を再起動する
{
TIM_Cmd(TIM3, ENABLE);
}
else UART_PutString("command error\r\n"); 該当しないときはコマンドエラー
↓メニュー表示
UART_PutString("\r\n");
UART_PutString("-----UART TEST MENU-----\r\n");
UART_PutString("int 10 : LED flash 10 Hz\r\n");
UART_PutString("off : LED off\r\n");
UART_PutString("on : LED off\r\n");
UART_STR_EXIST=0; 受信フラグをクリアしてexit
return;
}
usrlib-uart1d.c
UARTを制御するルーチンをひとまとめにしています.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stm32f10x.h" ←STM32のhardwareに根ざした設定
#include "usrlib-uart1d.h" ←UARTを使うための自作ライブラリです
GPIO_InitTypeDef GPIO_InitStructure; GPIO設定用構造体
USART_InitTypeDef USART_InitStructure; UART設定用構造体
char TxBuffer[TxBufferSize]; UART送信文字列を積んでおくリングバッファ
int TxPtrNow=0, TxPtrEnd=1; リングバッファの読み出し位置ポインタ 同書き込み位置ポインタ
void UART_Init(void) USART3初期化ルーチン
{
↓TX portを設定 PA9
// Configure USART Tx as alternate function push-pull
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
↓RX portを設定 PA10
// Configure USART Rx as input floating
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_InitStructure);
↓UART parameterを設定
// UART parameter
① USART_InitStructure.USART_BaudRate = 115200;
② USART_InitStructure.USART_WordLength = USART_WordLength_8b;
③ USART_InitStructure.USART_StopBits = USART_StopBits_1;
④ USART_InitStructure.USART_Parity = USART_Parity_No;
⑤ USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
⑥ USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure); 構造体をUSART1に書く
USART_Cmd(USART1, ENABLE); USART1を起動
①bpsの設定です.データシートによると上限は4.5Mbpsと書かれていますが、PCが115200bpsが上限であることが多いので限界突破の意欲が薄ければ115200bps止まりがいいんじゃないでしょうか?
②bit数の選択. USART_WordLength_8b または USART_WordLength_9b
③stopbitの選択.
USART_StopBits_0_5
USART_StopBits_1
USART_StopBits_1_5
USART_StopBits_2
④parityの選択. USART_Parity_Even または USART_Parity_No または USART_Parity_Odd
⑤フロー制御の選択.
USART_HardwareFlowControl_CTS
USART_HardwareFlowControl_None
USART_HardwareFlowControl_RTS
USART_HardwareFlowControl_RTS_CTS
⑥動作モード. USART_Mode_Rx または USART_Mode_Tx またはその両方
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); 受信で割り込み発生
USART_Cmd(USART1, ENABLE); USART1を起動
}
↓UARTに1文字出力する関数
void UART_PutChar(char c)
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); 送信し終わった割り込みを一時的に禁止
TxBuffer[TxPtrEnd]=c; リングバッファに1文字追加
TxPtrEnd++; 書き込みポインタを+1
if(TxPtrEnd>(TxBufferSize-1)) TxPtrEnd=0; 書き込みポインタがオーバーフローしたら0に戻す
USART_ITConfig(USART1, USART_IT_TXE, ENABLE); 送信し終わった割り込みを許可
}
↓UARTに文字列を出力する関数
UARTへ出力したい文字列は、一旦TxBuffer[300]に積まれ、割り込みルーチンで1文字づつ送信されます.
ここでは、リングバッファにただ単に積む作業しかしません.TxPtrEndが積み終わったひとつ先のアドレスを指し示します.
出力データは遅れて動くTxPtrNowが指し示します.TxPtrNowがTxPtrEndに到達するとバッファは空っぽという意味になります.
TxBufferに積む量が多すぎるとバッファがオーバーファー-してデータが化けますので、バッファ長を300バイトと大きめに確保しました.
void UART_PutString(char *s)
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); 送信し終わった割り込みを一時的に禁止
while(*s!=0) // untill end of string 文字列の末尾までループ
{
TxBuffer[TxPtrEnd]=*(s++); リングバッファに文字列をコピー
TxPtrEnd++; ポインタ+1
if(TxPtrEnd>(TxBufferSize-1)) TxPtrEnd=0; ポインタオーバーフローなら0に戻す
}
USART_ITConfig(USART1, USART_IT_TXE, ENABLE); 送信し終わった割り込みを許可
GPIO_SetBits(GPIOC, GPIO_Pin_9); LEDを点灯
}
↓UARTの割り込みルーチン.受信と送信を司ります.
// UART3 read write interrupt routine
char command[FIELD_NUM*FIELD_CHAR]; コマンド受信文字列
u8 UART_STR_EXIST = 0; 1行受信したら1になるフラグ
int bytes_read = 0; 受信バイトカウンタ
void USART1_IRQHandler(void)
{
char c;
↓受信手続き 1行受信してフラグを立てるのが役目
// recieve procedures
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) UART受信フラグチェック
{
if(UART_STR_EXIST==1) return; 1行受信フラグが立ったままなら受信しない
c = (char)USART_ReceiveData(USART1); 1文字受信
if(c == '\r'){ enterを受信したら、
command[bytes_read] = '\0'; 受信文字列を閉じる
bytes_read = 0;
UART_STR_EXIST = 1; 1行受信フラグを立ててreturn
return;
}
else if(c == '\b'){ back spaceを受信したら、
if (bytes_read > 0){
UART_PutString("\b \b"); 表示を戻す
bytes_read--; 受信ポインタを戻す
}
return;
}
else if(bytes_read >= FIELD_NUM*FIELD_CHAR ){ 受信文字数がオーバーフローしたら、
UART_PutString("Command too long\r\n"); エラー表示
bytes_read = 0; 受信済み文字列をご破算
return;
}
else if(c >= 0x20 && c <= 0x7E){ 正常な文字を受信したら、
command[bytes_read++] = c; 受信文字を格納
UART_PutChar(c); UARTにエコーバック
}
}
↓送信手続き リングバッファが空になるまで送信するのが役目
// transfer procedures
if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET) UART送信フラグチェック
{
USART_SendData(USART1, TxBuffer[TxPtrNow++]); 1文字送信
if(TxPtrNow>(TxBufferSize-1)) TxPtrNow=0; ポインタオーバーフローならゼロに戻す
if(TxPtrNow==TxPtrEnd) リングバッファが空か?
{
USART_ITConfig(USART1, USART_IT_TXE, DISABLE); 送信割り込みをオフ
GPIO_ResetBits(GPIOC, GPIO_Pin_9); LED消灯
}
}
}
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; のところの割り込み機能指定の選択肢リスト.
NonMaskableInt_IRQn Non Maskable Interrupt
MemoryManagement_IRQn Cortex-M3 Memory Management Interrupt
BusFault_IRQn Cortex-M3 Bus Fault Interrupt
UsageFault_IRQn Cortex-M3 Usage Fault Interrupt
SVCall_IRQn Cortex-M3 SV Call Interrupt
DebugMonitor_IRQn Cortex-M3 Debug Monitor Interrupt
PendSV_IRQn Cortex-M3 Pend SV Interrupt
SysTick_IRQn Cortex-M3 System Tick Interrupt
WWDG_IRQn Window WatchDog Interrupt
PVD_IRQn PVD through EXTI Line detection Interrupt
TAMPER_IRQn Tamper Interrupt
RTC_IRQn RTC global Interrupt
FLASH_IRQn FLASH global Interrupt
RCC_IRQn RCC global Interrupt
EXTI0_IRQn EXTI Line0 Interrupt
EXTI1_IRQn EXTI Line1 Interrupt
EXTI2_IRQn EXTI Line2 Interrupt
EXTI3_IRQn EXTI Line3 Interrupt
EXTI4_IRQn EXTI Line4 Interrupt
EXTI9_5_IRQn External Line[9:5] Interrupts
EXTI15_10_IRQn External Line[15:10] Interrupts
DMA1_Channel1_IRQn DMA1 Channel 1 global Interrupt
DMA1_Channel2_IRQn DMA1 Channel 2 global Interrupt
DMA1_Channel3_IRQn DMA1 Channel 3 global Interrupt
DMA1_Channel4_IRQn DMA1 Channel 4 global Interrupt
DMA1_Channel5_IRQn DMA1 Channel 5 global Interrupt
DMA1_Channel6_IRQn DMA1 Channel 6 global Interrupt
DMA1_Channel7_IRQn DMA1 Channel 7 global Interrupt
ADC1_IRQn ADC1 global Interrupt
TIM1_BRK_TIM15_IRQn TIM1 Break and TIM15 Interrupts
TIM1_UP_TIM16_IRQn TIM1 Update and TIM16 Interrupts
TIM1_TRG_COM_TIM17_IRQn TIM1 Trigger and Commutation and TIM17 Interrupt
TIM1_CC_IRQn TIM1 Capture Compare Interrupt
TIM2_IRQn TIM2 global Interrupt
TIM3_IRQn TIM3 global Interrupt
TIM4_IRQn TIM4 global Interrupt
TIM6_DAC_IRQn TIM6 and DAC underrun Interrupt
TIM7_IRQn TIM7 Interrupt
I2C1_EV_IRQn I2C1 Event Interrupt
I2C1_ER_IRQn I2C1 Error Interrupt
I2C2_EV_IRQn I2C2 Event Interrupt
I2C2_ER_IRQn I2C2 Error Interrupt
SPI1_IRQn SPI1 global Interrupt
SPI2_IRQn SPI2 global Interrupt
USART1_IRQn USART1 global Interrupt
USART2_IRQn USART2 global Interrupt
USART3_IRQn USART3 global Interrupt
RTCAlarm_IRQn RTC Alarm through EXTI Line Interrupt
CEC_IRQn HDMI-CEC Interrupt