#include "sys.h" #include "usart.h" ////////////////////////////////////////////////////////////////// //加入以下代碼,支持printf函數,而不需要選擇use MicroLIB #if 1 #pragma import(__use_no_semihosting) //標准庫需要的支持函數 struct __FILE { int handle; }; FILE __stdout; //定義_sys_exit()以避免使用半主機模式 void _sys_exit(int x) { x = x; } //重定義fputc函數 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0);//循環發送,直到發送完畢 USART1->DR = (u8) ch; return ch; } #endif //串口1中斷服務程序 //注意,讀取USARTx->SR能避免莫名其妙的錯誤 u8 USART_RX_BUF[USART_REC_LEN]; //接收緩沖,最大USART_REC_LEN個字節. //接收狀態 //bit15, 接收完成標志 //bit14, 接收到0x0d //bit13~0, 接收到的有效字節數目 u16 USART_RX_STA=0; //接收狀態標記 void uart_init(u32 bound){ //GPIO端口設置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA時鍾 //USART1_TX GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9 //USART1_RX GPIOA.10初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶占優先級3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器 //USART 初始化設置 USART_InitStructure.USART_BaudRate = bound;//串口波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式 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); //初始化串口1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟串口接受中斷 USART_Cmd(USART1, ENABLE); //使能串口1 } void USART1_IRQHandler(void) //串口1中斷服務程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾) { Res =USART_ReceiveData(USART1); //讀取接收到的數據 if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始 else USART_RX_STA|=0x8000; //接收完成了 } else //還沒收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收 } } } } }
usart文件夾內包含了 usart.c 和 usart.h 兩個文件。這兩個文件用於串口的初始化和中斷接收。這里只是針對串口 1 ,
比如你要用串 口 2 或者其他的串口,只要對代碼稍作修改就可以了。usart.c 里面包含了 2 個函數一個是 void USART1_IRQHandler(void)
另外一個是 void uart_init(u32 bound) 里面還有一段對串口 printf 的支持代碼,如果去掉,則會導致 printf 無法使用,雖然軟件編譯不會報錯,
但是硬件上 STM32 是無法啟動的,這段代碼不要去修改。
下面將貼上main函數,對這個usart串口中斷法如何實現數據的接收和發送的,過程基本如下所述:
main 函數就是開發板將從電腦虛擬串口助手發送過來的數據接收之后,開發板發送這些接收的數據給電腦的電腦虛擬串口助手。
#include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "usart.h" int main(void) { u16 t; u16 len; u16 times=0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級 uart_init(9600); //串口初始化為115200 LED_Init(); //LED端口初始化 delay_init(); //延時函數初始化 KEY_Init(); //初始化與按鍵連接的硬件接口 while(1) { if(USART_RX_STA&0x8000) { len=USART_RX_STA&0x3fff;//得到此次接收到的數據長度 printf("\r\n您發送的消息為:\r\n\r\n"); for(t=0;t<len;t++) { USART_SendData(USART2, USART_RX_BUF[t]);//向串口1發送數據 while(USART_GetFlagStatus(USART2,USART_FLAG_TC)!=SET);//等待發送結束 } printf("\r\n\r\n");//插入換行 USART_RX_STA=0; } else { times++; if(times%5000==0) { printf("\r\n精英STM32開發板 串口實驗\r\n"); printf("正點原子@ALIENTEK\r\n\r\n"); } if(times%200==0)printf("請輸入數據,以回車鍵結束\n"); if(times%30==0)LED0=!LED0;//閃爍LED,提示系統正在運行. delay_ms(10); } } }
下面將是關鍵的printf支持的原理以及串口中斷的處理原理的詳解!!!
5.3.1 printf函數支持
這段引入 printf 函數支持的代碼在 usart.h 頭文件的最上方,這段代碼加入之后便可以通過
printf 函數向 串口發送我們需要的內容,方便開發過程中查看代碼執行情況以及一些變量值。這
段代碼不需要修改,引入到 usart.h 即可。
//加入以下代碼,支持printf函數,而不需要選擇use MicroLIB #if 1 #pragma import(__use_no_semihosting) //標准庫需要的支持函數 struct __FILE { int handle; }; FILE __stdout; //定義_sys_exit()以避免使用半主機模式 void _sys_exit(int x) { x = x; } //重定義fputc函數 int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0);//循環發送,直到發送完畢 USART1->DR = (u8) ch; return ch; } #endif
初始化完相關IO口、NVIC中斷、串口等之外,還要開啟串口接收中斷以及串口使能
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟串口接受中斷 USART_Cmd(USART1, ENABLE); //使能串口1
不然,初始化完成之后,串口依然不能正常功能,例如串口通訊,或者通過printf發送數據到串口調試助手。
這里我們設計了一個小小的接收協議:通過這個函數,配合一個數組USART_RX_BUF一個接收狀態寄存器 USART_RX_STA
(此寄存器其實就是一個全局變量,由作者自行添加。由於它起到類似寄存器的功能,這里暫且稱之為寄存器)
實現對串口數據的接收管理。USART_RX_BUF 的大小由 USART_REC_LEN 定義,也就是 一次接收的數據最大不能超過
USART_REC_LEN 個字節。 SART_RX_STA 是一個接收狀態寄存器其各的定義如下圖所示。
原理:
當接收到從電腦發過來的數據,把接收到的數據保存在USART_RX_BUF 中,同時在接收狀態寄存器( USART_RX_STA )
中計數接收到的有效數據 個數,當收到回車(回車的表示由 2個字節組成: 0X0D 和 0X0A )的第一個字節 0X0D 時,
計數器將不再增加,等待 0X0A 的到來,而如果 0X0A 沒有來到,則認為這次接收失敗,重新開始下一次接收。
如果順利接收到 0X0A則標記 USART_RX_STA 的第 15 位,這樣完成一次接收,並等待該位被其他程序清除,
從而開始下一次的接收,而如果遲遲沒有收到 0X0D ,那么在接收數據超過 USART_REC_LEN 的時候,
則會丟棄前面的數據,重新接收。中斷相應函數代碼如下:
void USART1_IRQHandler(void) //串口1中斷服務程序 { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾) { Res =USART_ReceiveData(USART1); //讀取接收到的數據 if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始 else USART_RX_STA|=0x8000; //接收完成了 } else //還沒收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收 } } } } }
請各位大俠,覺得筆者寫的不錯的,給個贊!