主機環境:Windows XP SP3
開發環境:MDK 5.20
目標芯片:STM32F030C8T6
前兩天在群里看到有人在詢問有關STM32 串口總線空閑檢測的事情,根據串口總線是否空閑來判斷一幀數據是否發送完成,之前使用串口一直沒怎么注意過這一串口特性,所以后來特意去看了下手冊中有關總線空閑檢測的指示,發現它的確是個好特性,之前都只是在串口中斷中接收數據在主循環中不斷的讀取數據然后檢測是否是一幀完整的數據,之后再進行后續處理。這樣處理有一個不是很好的問題就是在主循環讀取串口數據時需要有個超時計數器來避免無串口數據時死等在那里,但如果使用串口總線空閑檢測的話,我們就不需要超時計數器了,只需要在檢測到串口總線空閑時把收到的數據全部讀走,然后檢測是否滿足一定的格式進而處理,這樣是的主循環的時間進一步減少,加速了系統的處理速度。
在STM32F030C8T6的參考手冊中串口中斷狀態寄存器USARTx_ISR中有一個IDLE位來表明是否檢測到總線空閑,如下圖所示:
并且給出了如何清除該標識,STM32F1系列芯片清除該標識的方法不同,可根據參考手冊來查詢,且該標識置位后就不再置位除非RXNE位再次置位,如果上位機一次性發送了1個字節數據則RXNE置位1次,IDLE置位1次,而如果上位機一次性發送了6個字節數據,則RXNE置位6次,IDLE依然置位1次,只要在CR1寄存器中使能了串口總線空閑檢測就可以使用該特性了,使用標準庫編輯了一下測試代碼,uart頭文件如下
#ifndef __UART_H__
#define __UART_H__
#include
#include "stm32f0xx.h"
#include
#define USARTx USART1
#define USARTx_GPIO_PORT GPIOA
#define USARTx_GPIO_CLK RCC_AHBPeriph_GPIOA
#define USARTx_TX_PIN GPIO_Pin_9
#define USARTx_TX_SOURCE GPIO_PinSource9
#define USARTx_TX_AF GPIO_AF_1
#define USARTx_RX_PIN GPIO_Pin_10
#define USARTx_RX_SOURCE GPIO_PinSource10
#define USARTx_RX_AF GPIO_AF_1
#define USARTx_IRQn USART1_IRQn
#define USARTx_IRQHandler USART1_IRQHandler
#define USARTx_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)
void uart_init (uint32_t baud);
#endif
uart的源碼文件如下
#include "uart.h"
uint8_t buffer[100];
uint8_t cnt = 0,idle_detect = 0;
void uart_init (uint32_t baud)
{
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//初始化串口時鐘以及串口端口時鐘
RCC_AHBPeriphClockCmd(USARTx_GPIO_CLK, ENABLE);
USARTx_CLK_ENABLE();
GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_TX_SOURCE, USARTx_TX_AF);
GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_RX_SOURCE, USARTx_RX_AF);
GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN| USARTx_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;
GPIO_Init(USARTx_GPIO_PORT, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baud ; //設置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位數據位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //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(USARTx, &USART_InitStructure);
USART_ITConfig(USARTx,USART_IT_RXNE,ENABLE); //使能接收中斷,在接收移位寄存器中有數據時產生
USART_ITConfig(USARTx,USART_IT_PE,ENABLE);
USART_ITConfig(USARTx,USART_IT_ERR,ENABLE);
USART_ITConfig(USARTx,USART_IT_IDLE,ENABLE); //使能總線空閑檢測中斷
/* 使能 USARTx 中斷 */
NVIC_InitStructure.NVIC_IRQChannel = USARTx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority=0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USARTx, ENABLE);
}
int fputc(int ch, FILE *f)
{
USART_SendData(USARTx,(uint8_t)ch);
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) != SET);
return ch;
}
void USARTx_IRQHandler(void)
{
uint8_t temp = 0;
if(USART_GetFlagStatus(USARTx,USART_FLAG_ORE) != RESET)
{
temp = USART_ReceiveData(USARTx);
(void)temp;
USART_ClearFlag(USARTx,USART_FLAG_ORE);
}
if(USART_GetFlagStatus(USARTx,USART_FLAG_NE) != RESET)
{
USART_ClearFlag(USARTx,USART_FLAG_NE);
}
if(USART_GetFlagStatus(USARTx,USART_FLAG_FE) != RESET)
{
USART_ClearFlag(USARTx,USART_FLAG_FE);
}
if(USART_GetFlagStatus(USARTx,USART_FLAG_PE) != RESET)
{
USART_ClearFlag(USARTx,USART_FLAG_PE);
}
if(USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET)//判斷寄存器中是否有數據
{
buffer[cnt++]=USART_ReceiveData(USARTx); //讀取數據,讀數據的同時清空了接收中斷標志;
if(cnt >= 100)
{
cnt = 0;
}
USART_ClearITPendingBit(USARTx, USART_IT_RXNE);
}
if(USART_GetITStatus(USARTx, USART_IT_IDLE) != RESET)
{
//清除總線空閑中斷標志位
USART_ClearITPendingBit(USARTx, USART_IT_IDLE);
idle_detect = 1;
}
return;
}
這里檢測到IDLE標識置位后置位idle_detect變量,cnt變量標記了本次接收到的字節個數,主函數測試代碼如下:
#include "uart.h"
extern uint8_t idle_detect,cnt;
extern uint8_t buffer[100];
int main (void)
{
uint8_t i = 0;
uart_init(115200);
while(1)
{
if(1 == idle_detect)
{
idle_detect = 0;
printf("\r\ncnt:%X,ctx:",cnt);
for(i = 0; i < cnt; i++)
{
printf("%02X ",buffer[i]);
}
cnt = 0;
}
}
}
代碼比較簡單,這里使用printf來輸出本次接收到的字節數以及字節內容,運行結果如下:
可以看到檢測結果是正常的,只是在開機后有一次cnt為0的結果,應該是上電之后總線默認就是空閑的,的確也沒有收到數據,因此就想把cnt為0的結果去掉,這里我遇到了一個很糾結的問題,在判斷idle_detect變量的同時也檢測cnt是否大于0,測試代碼更改如下:
#include "uart.h"
extern uint8_t idle_detect,cnt;
extern uint8_t buffer[100];
int main (void)
{
uint8_t i = 0;
uart_init(115200);
while(1)
{
if(1 == idle_detect && cnt > 0)
{
idle_detect = 0;
printf("\r\ncnt:%X,ctx:",cnt);
for(i = 0; i < cnt; i++)
{
printf("%02X ",buffer[i]);
}
cnt = 0;
}
}
}
感覺邏輯是對的,再次運行,結果如下:
這個時候發現cnt變量的值輸出一直為1,但輸出的字節內容卻是對的,一直想不通這是為啥?思來想去邏輯是沒啥問題的,后來又把cnt是否為0的條件判斷不放在跟idle_detect變量檢測同一水平,而是放在它里面,更改測試代碼如下:
#include "uart.h"
extern uint8_t idle_detect,cnt;
extern uint8_t buffer[100];
int main (void)
{
uint8_t i = 0;
uart_init(115200);
while(1)
{
if(1 == idle_detect)
{
idle_detect = 0;
if(cnt == 0)
continue;
printf("\r\ncnt:%X,ctx:",cnt);
for(i = 0; i < cnt; i++)
{
printf("%02X ",buffer[i]);
}
cnt = 0;
}
}
}
這個時候再次運行代碼,結果如下:
這個時候輸出的結果是正確的,難道兩個條件檢測放在一起會有問題嗎?這個是不應該的,因為idle_detect為0時CPU是不會再去檢測cnt變量的,只有idle_detect為1時才去檢測cnt變量,因此cnt的條件檢測放在里面和放在外面應該是一樣的效果才對。后來想是否是printf引起的問題,就把printf去掉改成了自己的輸出函數,更改測試代碼如下:
#include "uart.h"
extern uint8_t idle_detect,cnt;
extern uint8_t buffer[100];
int main (void)
{
uint8_t i = 0;
uart_init(115200);
while(1)
{
if(1 == idle_detect && cnt > 0)
{
idle_detect = 0;
uart_puts("\r\ncnt:");
uart_char(0x30+cnt);
uart_puts(",ctx:");
uart_write(buffer,cnt);
cnt = 0;
}
}
}
這里cnt的輸出是有問題的,但我測試時保證cnt不會大于10,因此不影響測試結果,在編譯時把微庫去掉,運行結果如下:
結果發現在數據小于等于6時結果是正確的,而當數據大于6時cnt一直為6但字節內容同樣是正確的,很是費解,在后來我又測試過當發送的字節為16時,輸出的數據內容會少幾個字節,總而言之把cnt變量的判斷放在idle_detect變量檢測的后面就會導致結果不對,而把cnt變量的判斷放在里面就不會有問題,所以應該不是printf引起的問題,感覺像是if條件語句引起的問題,嘗試過很多方法也沒搞定該問題,雖然最后我們也能回避這個問題,但沒找到原因真糾結,如果有人知道是啥問題的話,麻煩告知一下。
最后串口總線空閑檢測這一特性的確很有用,尤其是對收到的數據是不定長時更有效果,大家以后可以嘗試使用一下該特性。
PS:
今天下午不死心又進行了一些測試,還懷疑過是否是時序的問題,對比了一下cnt判斷所在位置不同的匯編代碼,對比結果如下:
左側是cnt判斷和idle_detect判斷在同一水平上,右側是cnt判斷放在了idle_detect條件檢測里面,兩者差別不大,再次證明我們的代碼邏輯是沒有問題的,后來又想起使用printf和使用我們自己的輸出函數cnt的值會有變化,在使用printf時cnt一直為1,而使用我們自己的輸出函數時cnt在數據長度小于等于6時正確,兩者的區別就是printf會使用微庫,延遲不同,所以增加了一個延遲函數,如下:
void delay(void)
{
uint32_t t = 5000;
while(t)
{
__NOP();__NOP();__NOP();__NOP();__NOP();
t-=1;
}
}
更改主函數的測試代碼,delay()函數可以有兩處放置,如下:
int main (void)
{
uint8_t i = 0;
uart_init(115200);
while(1)
{
delay();
if(1 == idle_detect && cnt > 0)
{
idle_detect = 0;
//delay();
printf("\r\ncnt:%X,ctx:",cnt);
for(i = 0; i < cnt; i++)
{
printf("%02X ",buffer[i]);
}
cnt = 0;
}
}
}
加上delay()函數后代碼運行的結果就正確了,區別是delay()函數如果放在if條件語句里面時當上位機發送的數據量多時需要的延遲就會長些才能保證結果的正確性,而delay()函數放在if條件語句的外面則不會這樣,總算找到問題了,但還是推薦把cnt的判斷放在if條件的里面,這樣就不需要增加延遲函數了,就這樣吧。
上一篇:STM32CubeMX:UART(DMA空閑方式)
下一篇:STM32:DMA方式接收SPI總線數據,并按照協議進行處理
推薦閱讀
史海拾趣