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