關於STM32串口的資料可以在RM0008 Reference Manual中找到,有中文版的資料。STM32F103支持5個串口,選取USART1用來實驗,其對應的IO口為PA9和PA10。這次的實驗基於ALIENTEK的開發板,開發版通過CH340G實現將串口轉成USB。因此需要做好一些准備工作。
1.PC端安裝Keil v5 MDK開發工具;
2.PC端安裝CH340G的驅動;
3.PC端安裝ATK XCOM串口收發程序
STM32的串口編程思路:
1.串口時鍾設置和復位;
2.選取發射口和接收口的引腳,並設置GPIO端口參數;
3.串口參數的初始化(完成波特率、字長、奇偶校驗、收發模式等參數的設置);
4.初始化NVIC(Nested Vectored Interrupt Controller,內嵌向量中斷控制器);
5.開啟中斷和使能串口
代碼如下:
1 //main.c: 2 #include "uart.h"
3
4
5 int main() 6 { 7 uart1_init(); 8 while(1) 9 { 10 } 11 }
1 //USART.c 2 #include "uart.h"
3
4
5 #define USART1_REC_LEN 256
6
7 u8 Uart1_RevBuf_Tail = 0;//接收緩沖區尾部 8 u8 Uart1_RevBuf[USART1_REC_LEN];//接收緩沖區數組 9
10 void uart1_init() 11 { 12 //GPIO端口設置
13 GPIO_InitTypeDef GPIO_InitStructure; 14 USART_InitTypeDef USART_InitStructure; 15 NVIC_InitTypeDef NVIC_InitStructure; 16
17 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); 18 USART_DeInit(USART1); 19
20
21 //USART1端口配置 22 //UASART_TX PA9
23 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
24 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 25 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
26 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9 27 //USART1_RX PA10
28 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 29 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
30 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10 31
32 //USART1 初始化設置
33 USART_InitStructure.USART_BaudRate = 9600;//波特率設置
34 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式
35 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
36 USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
37 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
38 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
39 USART_Init(USART1, &USART_InitStructure); //初始化串口1 40
41 //Usart1 NVIC 配置
42 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中斷通道
43 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//搶占優先級3
44 NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子優先級3
45 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
46 NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
47
48 USART_Init(USART1, &USART_InitStructure); 49 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟相關中斷
50 USART_Cmd(USART1, ENABLE); //使能串口1
51
52 } 53
54 //串口1中斷服務程序
55 void USART1_IRQHandler(void) 56 { 57 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷
58 { 59
60 Uart1_RevBuf[Uart1_RevBuf_Tail] = USART_ReceiveData(USART1);//讀取接收到的數據,將尾標后移
61 USART_SendData(USART1,Uart1_RevBuf[Uart1_RevBuf_Tail]);//發送接收到的數據
62 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) 63 {} 64 Uart1_RevBuf_Tail++; 65 if(Uart1_RevBuf_Tail>USART1_REC_LEN-1) 66 { 67 Uart1_RevBuf_Tail = 0; 68 } 69 } 70 }
主函數非常簡單,就是調用uart_init()然后等待串口1的接收中斷觸發。串口1的中斷服務函數功能是:當PC端發送據后,將接收到的數據重新發回給PC機。uart_init()的功能是完成串口的配置。在接收數據的時候設置了一個容量位256的數據緩沖區Uart1_RevBuf,用來存放接收到的數據。
程序的運行結果如下。分別發送AA,BB,CC后PC端接收到了AA 0D 0A BB 0D 0A CC 0D 0A,0D和0A分別表示回車和換行。說明結果正確。
在實際應用中,上位機可以通過多個串口和多個從設備進行通信,因此在串口通信的時候要自行規定一個通信協議。比如由1.頭,2.設備號,3.數據長度,4.數據,5.結束位,6.間隔位組成一個數據包。根據協議編寫解包函數。解包函數的大致思路就是將接收到的數據一步一步的進行判斷,最終完成解出數據的功能。
1.數據包定義:
頭:0xAB,設備號:0x01(一號設備),數據長度:0x08(8位數據),數據位:DATA,結束位:0xFF,間隔位:0xFF 0xFF
2.解包函數:
PC機發送一個數據包:AB 01 08 00 01 02 03 04 05 06 07 FF FF FF,解包函數能夠將數據00 01 02 03 04 05 06 07取出來並再次發送給PC機。PC機將數據發送給STM32F103,觸發接收中斷,將數據存入接收緩沖區中,解包函數從緩沖區的頭部開始檢索,完成數據分析,取出數據。代碼如下:
#include "stm32f10x.h" #include <stdio.h>
#define Usart1RecLength 256 u8 Uart1_RevBuf_Tail = 0; u8 Uart1_RevBuf_Head = 0; u8 Uart1_RevBuf[Usart1RecLength]; u8 RecState = 0; u8 TemplateData; u8 DataLength = 8; u8 Data[8]={0}; typedef struct { u8 StartDataError; u8 DeviceDataError; u8 LengthDataError; u8 StopDataError; u8 DataReady; }DataFrameFlag; DataFrameFlag USART1_FrameFlags; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷
{ Uart1_RevBuf[Uart1_RevBuf_Tail] = USART_ReceiveData(USART1); //讀取接收到的數據
Uart1_RevBuf_Tail++; if(Uart1_RevBuf_Tail > Usart1RecLength-1) { Uart1_RevBuf_Tail = 0; } } } void RecDataAnalysis() { u8 i = 0; if(Uart1_RevBuf_Head != Uart1_RevBuf_Tail)//判斷是否有數據
{ TemplateData = Uart1_RevBuf[Uart1_RevBuf_Head];//從數據緩沖區取數據
Uart1_RevBuf_Head ++; if(Uart1_RevBuf_Head > Usart1RecLength-1) { Uart1_RevBuf_Head = 0; } USART1_FrameFlags.DeviceDataError = 0; USART1_FrameFlags.StopDataError = 0; USART1_FrameFlags.LengthDataError = 0; USART1_FrameFlags.StartDataError = 0; switch(RecState) { case 0: if(TemplateData == 0xAB)//頭
{ RecState = 1; } else { RecState = 0; USART1_FrameFlags.StartDataError = 1; } break; case 1: if(TemplateData == 0x01)//設備號
{ RecState = 2; } else { RecState = 0; USART1_FrameFlags.DeviceDataError = 1; } break; case 2: if(TemplateData == 0x08)//數據位
{ RecState = 3; } else { RecState = 0; USART1_FrameFlags.LengthDataError = 1; } break; case 3://轉存數據
if(DataLength == 0) { RecState = 4; USART1_FrameFlags.DataReady = 1; } else if(DataLength != 0) { Data[8-DataLength] = TemplateData; DataLength = DataLength -1; } break; case 4: if(TemplateData == 0xFF)//尾部
{ RecState = 0; DataLength = 8; } else { for(i=0;i < 8;i++) { Data[i] = 0; } RecState = 0; DataLength = 8; USART1_FrameFlags.StopDataError = 1; USART1_FrameFlags.DataReady = 0; } break; default: for(i=0;i < 8;i++) { Data[i] = 0; } RecState = 0; DataLength = 8; break; } } } void Resend()//測試用重發數據函數
{ u8 i = 0; if(USART1_FrameFlags.DataReady == 1) { for(i=0;i<8;i++) { USART_SendData(USART1,Data[i]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); USART1_FrameFlags.DataReady = 0; } } }
函數RecDataAnalysis()完成數據解包,函數Resend()在解包函數准備好數據將數據回發給PC機。結構體DataFrameFlag的作用是當數據出現錯誤時完成報錯,是可選功能,程序中給了一種思路,未做調試。結果如下: