干貨|高質(zhì)量代碼是怎么寫(xiě)出來(lái)的?串口環(huán)形隊(duì)列
摘要:串口是通信中最常用的通信方式,可能寫(xiě)串口的驅(qū)動(dòng),能寫(xiě)幾十種方法, 查詢(xún)方式,中斷方式,DMA方式,定時(shí)器方式。可能也其中幾種方式的組合形式,經(jīng)典的用法是:發(fā)送用查詢(xún)方式,接收用中斷方式,或者DMA+空閑中斷。本篇不講串口是啥,現(xiàn)在還在講串口是啥,估計(jì)會(huì)被噴。今天來(lái)聊一聊串口常用的幾種方式,最簡(jiǎn)單的方法就不說(shuō)了。
一、經(jīng)典方法
-
查詢(xún)方式 可靠性很高,要考慮下個(gè)數(shù)據(jù)包覆蓋上一個(gè)數(shù)據(jù)包的問(wèn)題,小數(shù)據(jù)量,在10個(gè)字節(jié)以?xún)?nèi),可以這樣考慮, 很簡(jiǎn)單,很方便,很可靠。但是在數(shù)據(jù)量大的時(shí)候,程序阻塞的時(shí)間特別長(zhǎng),影響其他比較重要的外設(shè)的處理。
-
中斷方式 中斷方式 , 不占用系統(tǒng)資源,但是如果數(shù)據(jù)量大,會(huì)頻繁中斷cpu, 會(huì)其他高優(yōu)先的數(shù)據(jù)處理造成影響。但是沒(méi)有DMA不占用資源的好處, 如果沒(méi)有串口隊(duì)列的實(shí)現(xiàn),必須通過(guò)標(biāo)志位判斷上一個(gè)包數(shù)據(jù)是否發(fā)送完成,在把新的數(shù)據(jù)覆蓋到串口的緩沖區(qū)。
-
DMA方式 優(yōu)點(diǎn): 不占用系統(tǒng)資源,減少CPU對(duì)中斷的響應(yīng)。如何不建立數(shù)據(jù)包的隊(duì)列,還是會(huì)出現(xiàn),需要等待阻塞的問(wèn)題。
二、環(huán)形隊(duì)列
隊(duì)列這個(gè)詞在數(shù)據(jù)局結(jié)構(gòu)中出現(xiàn)的比較多,與之對(duì)應(yīng)的就是堆棧,但是兩者的讀取方式又完全不同。
FIFO 是First-In First-Out的縮寫(xiě),它是一個(gè)具有先入先出特點(diǎn)的緩沖區(qū)。串口設(shè)計(jì)FIFO的目的是為了提高串口的通訊性能。如果沒(méi)有FIFO或者說(shuō)緩沖區(qū)的長(zhǎng)度只有1字節(jié),那么使用接收中斷,就意味著每次收到一個(gè)字節(jié)的數(shù)據(jù)就要進(jìn)一次中斷,這樣頻繁進(jìn)中斷會(huì)占用CPU資源。另外如果沒(méi)有及時(shí)讀走數(shù)據(jù),那么下一個(gè)字節(jié)數(shù)據(jù)就會(huì)覆蓋之前的數(shù)據(jù),導(dǎo)致數(shù)據(jù)丟失,這在通訊速率高的場(chǎng)合很有可能出現(xiàn)。
使用FIFO,可以在連續(xù)接收若干個(gè)數(shù)據(jù)后才產(chǎn)生一次中斷,然后一起進(jìn)行處理。這樣可以提高接收效率,避免頻繁進(jìn)中斷,適用于大數(shù)據(jù)傳輸。你可能會(huì)想到如果FIFO中的數(shù)據(jù)沒(méi)有達(dá)到指定長(zhǎng)度而無(wú)法產(chǎn)生中斷怎么辦,通常MCU會(huì)有接收超時(shí)中斷,即在一定的時(shí)間內(nèi)沒(méi)有接收到數(shù)據(jù)會(huì)進(jìn)入中斷,可以利用這個(gè)中斷把不足FIFO長(zhǎng)度的數(shù)據(jù)最后都讀取完。
FIFO類(lèi)似售票排隊(duì)窗口,先到的人看到能先買(mǎi)到票,然后先走,后來(lái)的人只能后買(mǎi)到票。
在計(jì)算機(jī)中,每個(gè)信息都是存儲(chǔ)在存儲(chǔ)單元中的,當(dāng)有大量數(shù)據(jù)的時(shí)候,我們不能存儲(chǔ)所有的數(shù)據(jù),那么計(jì)算機(jī)處理數(shù)據(jù)的時(shí)候,只能先處理先來(lái)的,那么處理完后呢,就會(huì)把數(shù)據(jù)釋放掉,再處理下一個(gè)。那么,已經(jīng)處理的數(shù)據(jù)的內(nèi)存就會(huì)被浪費(fèi)掉。因?yàn)楹髞?lái)的數(shù)據(jù)只能往后排隊(duì),如過(guò)要將剩余的數(shù)據(jù)都往前移動(dòng)一次,那么效率就會(huì)低下了,肯定不現(xiàn)實(shí),所以,環(huán)形隊(duì)列就出現(xiàn)了。
點(diǎn)擊下方視頻動(dòng)態(tài)演示出隊(duì)入隊(duì)
1、環(huán)形隊(duì)列的實(shí)現(xiàn)
在計(jì)算機(jī)中,是沒(méi)有環(huán)形的內(nèi)存的,只不過(guò)是我們將順序的內(nèi)存處理過(guò),讓某一段內(nèi)存形成環(huán)形,使他們首尾相連,簡(jiǎn)單來(lái)說(shuō),這其實(shí)就是一個(gè)數(shù)組,只不過(guò)有兩個(gè)指針,一個(gè)指向列隊(duì)頭,一個(gè)指向列隊(duì)尾。指向列隊(duì)頭的指針是緩沖區(qū)可讀的數(shù)據(jù),指向列隊(duì)尾的指針是緩沖區(qū)可寫(xiě)的數(shù)據(jù),通過(guò)移動(dòng)這兩個(gè)指針即可對(duì)緩沖區(qū)的數(shù)據(jù)進(jìn)行讀寫(xiě)操作了,直到緩沖區(qū)已滿(頭尾相接),將數(shù)據(jù)處理完,可以釋放掉數(shù)據(jù),又可以進(jìn)行存儲(chǔ)新的數(shù)據(jù)了。
實(shí)現(xiàn)的原理:
視頻來(lái)自正在一名考研的UP主:禿頭少女王某人。計(jì)算機(jī)專(zhuān)業(yè)考研這個(gè)是必考點(diǎn),視頻講的很棒,祝她一戰(zhàn)成碩,金榜題名!
串口環(huán)形緩沖區(qū)收發(fā):在初學(xué)單片機(jī)的時(shí)候我們知道的串口收發(fā)都是:接收一個(gè)數(shù)據(jù),觸發(fā)中斷,然后把數(shù)據(jù)發(fā)回來(lái)。這種處理方式是沒(méi)有緩沖的,當(dāng)數(shù)量太大的時(shí)候,亦或者當(dāng)數(shù)據(jù)接收太快的時(shí)候,我們來(lái)不及處理已經(jīng)收到的數(shù)據(jù),那么,當(dāng)再次收到數(shù)據(jù)的時(shí)候,就會(huì)將之前還未處理的數(shù)據(jù)覆蓋掉。那么就會(huì)出現(xiàn)丟包的現(xiàn)象了,對(duì)我們的程序是一個(gè)致命的創(chuàng)傷。
那么如何避免這種情況的發(fā)生呢,很顯然,上面說(shuō)的一些隊(duì)列的特性很容易幫我們實(shí)現(xiàn)我們需要的情況。將接受的數(shù)據(jù)緩存一下,讓處理的速度有些許緩沖,使得處理的速度趕得上接收的速度,上面又已經(jīng)分析了普通隊(duì)列與環(huán)形隊(duì)列的優(yōu)劣了,那么我們肯定是用環(huán)形隊(duì)列來(lái)進(jìn)行實(shí)現(xiàn)了。下面就是代碼的實(shí)現(xiàn):
2、定義一個(gè)結(jié)構(gòu)體
typedef struct
{
uint16_t usWrite;
uint16_t usRead;
uint16_t usLenght;
/* FIFO 結(jié)構(gòu) */
uint8_t ucRing_Buff[RINGBUFF_LEN];
}RingBuff_T;
extern RingBuff_T g_ringBuff;
3、初始化隊(duì)列
初始化結(jié)構(gòu)體相關(guān)信息:使得我們的環(huán)形緩沖區(qū)是頭尾相連的,并且里面沒(méi)有數(shù)據(jù),也就是空的隊(duì)列,所有元素清0。
void RingBuff_Init(void)
{
g_ringBuff.usWrite = 0;
g_ringBuff.usRead = 0;
g_ringBuff.usLenght = 0;
}
4、數(shù)據(jù)壓入隊(duì)列
/**
* @brief Write_RingBuff
* @param uint8_t _ucWriteData
* @return 0:環(huán)形緩沖區(qū)已滿,寫(xiě)入失敗;1:寫(xiě)入成功
* @note 往環(huán)形緩沖區(qū)寫(xiě)入uint8_t類(lèi)型的數(shù)據(jù)
*/
uint8_t Write_RingBuff(uint8_t _ucWriteData)
{
if(g_ringBuff.usLenght >= RINGBUFF_LEN) /*判斷緩沖區(qū)是否已滿*/
{
return 0;
}
g_ringBuff.ucRing_Buff[g_ringBuff.usRead] = _ucWriteData;
g_ringBuff.usRead = (g_ringBuff.usRead + 1) % RINGBUFF_LEN; /*防止越界非法訪問(wèn)*/
g_ringBuff.usLenght++;
return 1;
}
5、從隊(duì)列中讀出數(shù)據(jù)
/**
* @brief Read_RingBuff
* @param u8 *rData,用于保存讀取的數(shù)據(jù)
* @return 0:環(huán)形緩沖區(qū)沒(méi)有數(shù)據(jù),讀取失敗; 1:讀取成功
* @note 從環(huán)形緩沖區(qū)讀取一個(gè)uint8_t類(lèi)型的數(shù)據(jù)
*/
uint8_t Read_RingBuff(uint8_t *_usReadData)
{
if(g_ringBuff.usLenght == 0)/*判斷非空*/
{
return 0;
}
*_usReadData = g_ringBuff.ucRing_Buff[g_ringBuff.usWrite];/*先進(jìn)先出FIFO,從緩沖區(qū)頭出*/
g_ringBuff.usWrite = (g_ringBuff.usWrite + 1) % RINGBUFF_LEN; /*防止越界非法訪問(wèn)*/
g_ringBuff.usLenght--;
return 1;
}
對(duì)于讀寫(xiě)操作需要注意的地方有兩個(gè):
“1:判斷隊(duì)列是否為空或者滿,如果空的話,是不允許讀取數(shù)據(jù)的,返回0。如果是滿的話,也是不允許寫(xiě)入數(shù)據(jù)的,避免將已有數(shù)據(jù)覆蓋掉。那么如果處理的速度趕不上接收的速度,可以適當(dāng)增大緩沖區(qū)的大小,用空間換取時(shí)間。2:防止指針越界非法訪問(wèn),程序有說(shuō)明,需要使用者對(duì)整個(gè)緩沖區(qū)的大小進(jìn)行把握。
”
四、環(huán)形緩沖器
環(huán)形緩沖器(ringr buffer),也稱(chēng)作圓形隊(duì)列(circular queue),循環(huán)緩沖區(qū)(cyclic buffer),圓形緩沖區(qū)(circula buffer),是一種用于表示一個(gè)固定尺寸、頭尾相連的緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),適合緩存數(shù)據(jù)流。
圓形緩沖區(qū)的一個(gè)有用特性是:當(dāng)一個(gè)數(shù)據(jù)元素被用掉后,其余數(shù)據(jù)元素不需要移動(dòng)其存儲(chǔ)位置。相反,一個(gè)非圓形緩沖區(qū)(例如一個(gè)普通的隊(duì)列)在用掉一個(gè)數(shù)據(jù)元素后,其余數(shù)據(jù)元素需要向前搬移。換句話說(shuō),圓形緩沖區(qū)適合實(shí)現(xiàn)先進(jìn)先出緩沖區(qū),而非圓形緩沖區(qū)適合后進(jìn)先出緩沖區(qū)。
那么如何將環(huán)形緩沖器ringr buffer應(yīng)用到串口上面呢?這里我們使用RT-Thread的源碼。
1、定義一個(gè)結(jié)構(gòu)體
/* ring buffer */
struct rt_ringbuffer
{
uint8_t *buffer_ptr;
uint16_t read_mirror : 1;
uint16_t read_index : 15;
uint16_t write_mirror : 1;
uint16_t write_index : 15;
uint16_t buffer_size;
};
2、初始化ringbuffer
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
uint8_t *pool,
uint16_t size)
{
RT_ASSERT(rb != NULL);
RT_ASSERT(size > 0);
/* initialize read and write index */
rb->read_mirror = rb->read_index = 0;
rb->write_mirror = rb->write_index = 0;
/* set buffer pool and size */
rb->buffer_ptr = pool;
rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}
3、將數(shù)據(jù)壓入ringbuffer
unsigned long rt_ringbuffer_put(struct rt_ringbuffer *rb,
const uint8_t *ptr,
uint16_t length)
{
uint16_t size;
RT_ASSERT(rb != NULL);
/* whether has enough space */
size = rt_ringbuffer_space_len(rb);
/* no space */
if (size == 0)
return 0;
/* drop some data */
if (size < length)
length = size;
if (rb->buffer_size - rb->write_index > length)
{
/* read_index - write_index = empty space */
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
/* this should not cause overflow because there is enough space for
* length of data in current mirror */
rb->write_index += length;
return length;
}
memcpy(&rb->buffer_ptr[rb->write_index],
&ptr[0],
rb->buffer_size - rb->write_index);
memcpy(&rb->buffer_ptr[0],
&ptr[rb->buffer_size - rb->write_index],
length - (rb->buffer_size - rb->write_index));
/* we are going into the other side of the mirror */
rb->write_mirror = ~rb->write_mirror;
rb->write_index = length - (rb->buffer_size - rb->write_index);
return length;
}
4、從ringbuffer中讀數(shù)據(jù)
unsigned long rt_ringbuffer_get(struct rt_ringbuffer *rb,
uint8_t *ptr,
uint16_t length)
{
unsigned long size;
RT_ASSERT(rb != NULL);
/* whether has enough data */
size = rt_ringbuffer_data_len(rb);
/* no data */
if (size == 0)
return 0;
/* less data */
if (size < length)
length = size;
if (rb->buffer_size - rb->read_index > length)
{
/* copy all of data */
memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
/* this should not cause overflow because there is enough space for
* length of data in current mirror */
rb->read_index += length;
return length;
}
memcpy(&ptr[0],
&rb->buffer_ptr[rb->read_index],
rb->buffer_size - rb->read_index);
memcpy(&ptr[rb->buffer_size - rb->read_index],
&rb->buffer_ptr[0],
length - (rb->buffer_size - rb->read_index));
/* we are going into the other side of the mirror */
rb->read_mirror = ~rb->read_mirror;
rb->read_index = length - (rb->buffer_size - rb->read_index);
return length;
}
5、移植ringbuffer
1.首先將RT-Thread的ringbuffer.c和ringbuffer.h文件加入到我們的MDK工程中去。
2.編寫(xiě)串口相關(guān)的底層硬件bsp代碼,也就是初始化GPIO和串口相關(guān)的配置,這個(gè)就很簡(jiǎn)單,大家應(yīng)該都會(huì)。在串口初始化代碼中記得要手動(dòng)將串口的非空中斷和空閑中斷打開(kāi)。
UART_HandleTypeDef huart1;//這個(gè)要定義為全局的變量
void uart_init(u32 bound)
{
huart1.Instance=USART1; //USART1
huart1.Init.BaudRate=bound; //波特率
huart1.Init.WordLength=UART_WORDLENGTH_8B; //字長(zhǎng)為8位數(shù)據(jù)格式
huart1.Init.StopBits=UART_STOPBITS_1; //一個(gè)停止位
huart1.Init.Parity=UART_PARITY_NONE; //無(wú)奇偶校驗(yàn)位
huart1.Init.HwFlowCtl=UART_HWCONTROL_NONE; //無(wú)硬件流控
huart1.Init.Mode=UART_MODE_TX_RX; //收發(fā)模式
HAL_UART_Init(&huart1);//HAL_UART_Init()會(huì)使能UART1
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);/*使能串口接收非空中斷 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); /*使能串口接收空閑中斷 */
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口設(shè)置
GPIO_InitTypeDef GPIO_InitStruct;
if(huart->Instance==USART1)//如果是串口1,進(jìn)行串口1 MSP初始化
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//使用中斷肯定要配置NVIC
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
3.定義一個(gè)結(jié)構(gòu)頭rt_ringbuffer 類(lèi)型的變量ring_buf,變量名隨便取,阿貓阿狗都可以,只要你自己認(rèn)得就行。
struct rt_ringbuffer ring_buf;
4.定義一個(gè)串口接收緩沖區(qū)數(shù)組,數(shù)組名隨便取,阿貓阿狗都可以,只要你自己認(rèn)得就行。
static uint8_t s_USART1_RxBuf[256];
5.初始化ringbuffer
rt_ringbuffer_init(&ring_buf,s_USART1_RxBuf,256);
位置放在哪里都可以,我這里就放在串口串口初始化之前了。
6.編寫(xiě)中斷服務(wù)函數(shù)
void USART1_IRQHandler(void)
{
uint8_t receive_char;
if((__HAL_USART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)) //接收非空中斷
{
HAL_UART_Receive(&huart1, &receive_char, 1, 1000);//將收到的數(shù)據(jù)放入receive_char
rt_ringbuffer_put(&ring_buf,&receive_char,1);//將receive_char壓入到ring_buf中
USART1_Len++;
__HAL_USART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);//清除接收非空中斷
}
if((__HAL_USART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)) //接收空閑中斷
{
rt_ringbuffer_get(&ring_buf,s_USART1_RxBuf, USART1_Len);//將rring_buf的數(shù)據(jù)讀出到到s_USART1_RxBuf中
s_USART1_RxBuf[USART1_Len] = '\0';
printf("%s\r\n",s_USART1_RxBuf);
USART1_Len = 0 ;//數(shù)據(jù)幀長(zhǎng)度復(fù)位
__HAL_USART_CLEAR_IDLEFLAG(&huart1);
}
}
這里面的代碼我寫(xiě)的應(yīng)該很簡(jiǎn)單了,首先我們?cè)诔跏蓟惺悄芰私邮罩袛嗪涂臻e中斷,那么如果有數(shù)據(jù)過(guò)來(lái),就會(huì)觸發(fā)中斷。進(jìn)入中斷服務(wù)函數(shù),進(jìn)來(lái)之后首先判斷接收中斷標(biāo)志位是否置位為1,如果是1說(shuō)明收數(shù)據(jù)來(lái)了,通過(guò)HAL_UART_Receive函數(shù)將數(shù)據(jù)存入receive_char中,再通過(guò)rt_ringbuffer_put將數(shù)據(jù)壓入隊(duì)列之中。如果數(shù)據(jù)接收完了就會(huì)觸發(fā)空閑中斷,這時(shí)通過(guò)rt_ringbuffer_get函數(shù)將數(shù)據(jù)讀出到我們定義的數(shù)組中打印出來(lái)。
7.主函數(shù)
int main(void)
{
HAL_Init();
Stm32_Clock_Init();
delay_init(72);
uart_init(115200);
while(1)
{
}
}
主函數(shù)啥都不要寫(xiě),完了。
五、隊(duì)列FIFO
隊(duì)列 (Queue):是一種先進(jìn)先出(First In First Out ,簡(jiǎn)稱(chēng) FIFO)的線性表,只允許在一端插入,在另一端進(jìn)行刪除。
FIFO一般用于不同時(shí)鐘域之間的數(shù)據(jù)傳輸,比如FIFO的一端是AD數(shù)據(jù)采集,另一端是計(jì)算機(jī)的PCI總線,假設(shè)其AD采集的速率為16位 100K SPS,那么每秒的數(shù)據(jù)量為100K×16bit=1.6Mbps,而PCI總線的速度為33MHz,總線寬度32bit,其最大傳輸速率為1056Mbps,在兩個(gè)不同的時(shí)鐘域間就可以采用FIFO來(lái)作為數(shù)據(jù)緩沖。另外對(duì)于不同寬度的數(shù)據(jù)接口也可以用FIFO,例如單片機(jī)位8位數(shù)據(jù)輸出,而DSP可能是16位數(shù)據(jù)輸入,在單片機(jī)與DSP連接時(shí)就可以使用FIFO來(lái)達(dá)到數(shù)據(jù)匹配的目的。
1、定義一個(gè)結(jié)構(gòu)體
/* 串口設(shè)備結(jié)構(gòu)體 */
typedef struct
{
USART_TypeDef *uart; /* STM32內(nèi)部串口設(shè)備指針 */
uint8_t *pTxBuf; /* 發(fā)送緩沖區(qū) */
uint8_t *pRxBuf; /* 接收緩沖區(qū) */
uint16_t usTxBufSize; /* 發(fā)送緩沖區(qū)大小 */
uint16_t usRxBufSize; /* 接收緩沖區(qū)大小 */
__IO uint16_t usTxWrite; /* 發(fā)送緩沖區(qū)寫(xiě)指針 */
__IO uint16_t usTxRead; /* 發(fā)送緩沖區(qū)讀指針 */
__IO uint16_t usTxCount; /* 等待發(fā)送的數(shù)據(jù)個(gè)數(shù) */
__IO uint16_t usRxWrite; /* 接收緩沖區(qū)寫(xiě)指針 */
__IO uint16_t usRxRead; /* 接收緩沖區(qū)讀指針 */
__IO uint16_t usRxCount; /* 還未讀取的新數(shù)據(jù)個(gè)數(shù) */
}UART_T;
2、初始化FIFO
static void Uart_FIFO_Init(void)
{
g_tUart1.uart = USART1; /* STM32 串口設(shè)備 */
g_tUart1.pTxBuf = g_TxBuf1; /* 發(fā)送緩沖區(qū)指針 */
g_tUart1.pRxBuf = g_RxBuf1; /* 接收緩沖區(qū)指針 */
g_tUart1.usTxBufSize = 1024; /* 發(fā)送緩沖區(qū)大小 */
g_tUart1.usRxBufSize = 1024; /* 接收緩沖區(qū)大小 */
g_tUart1.usTxWrite = 0; /* 發(fā)送FIFO寫(xiě)索引 */
g_tUart1.usTxRead = 0; /* 發(fā)送FIFO讀索引 */
g_tUart1.usRxWrite = 0; /* 接收FIFO寫(xiě)索引 */
g_tUart1.usRxRead = 0; /* 接收FIFO讀索引 */
g_tUart1.usRxCount = 0; /* 接收到的新數(shù)據(jù)個(gè)數(shù)*/
g_tUart1.usTxCount = 0; /* 待發(fā)送的數(shù)據(jù)個(gè)數(shù) */
}
3、初始化串口
UART_HandleTypeDef UartHandle;
static void Uart_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 使能 GPIO TX/RX 時(shí)鐘 */
USART1_TX_GPIO_CLK_ENABLE();
USART1_RX_GPIO_CLK_ENABLE();
/* 使能 USARTx 時(shí)鐘 */
USART1_CLK_ENABLE();
/* 配置TX引腳 */
GPIO_InitStruct.Pin = USART1_TX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(USART1_TX_GPIO_PORT, &GPIO_InitStruct);
/* 配置RX引腳 */
GPIO_InitStruct.Pin = USART1_RX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(USART1_RX_GPIO_PORT, &GPIO_InitStruct);
UartHandle.Instance = USART1;
UartHandle.Init.BaudRate = 115200;
UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&UartHandle);
__HAL_USART_CLEAR_FLAG(&UartHandle, USART_FLAG_TC); /* 清除TC發(fā)送完成標(biāo)志 */
__HAL_USART_CLEAR_FLAG(&UartHandle, USART_FLAG_RXNE);/* 清除RXNE接收標(biāo)志 */
__HAL_USART_ENABLE_IT(&UartHandle, USART_IT_RXNE); /* 使能接收數(shù)據(jù)寄存器非空中斷 */
/* 配置NVIC the NVIC for UART */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
4、將數(shù)據(jù)壓入FIFO
什么時(shí)候要將數(shù)據(jù)壓入FIFO?當(dāng)然是在向串口發(fā)送數(shù)據(jù)的時(shí)候?qū)?shù)據(jù)壓入FIFO去啦,這里面的邏輯也很簡(jiǎn)單就是將要發(fā)送的數(shù)據(jù)壓入FIFO緩沖區(qū),之后打開(kāi)發(fā)送中斷就可以了。
void UartSendBuf(uint8_t *_ucaBuf, uint16_t _usLen)
{
uint16_t i;
for (i = 0; i < _usLen; i++)
{
/* 如果發(fā)送緩沖區(qū)已經(jīng)滿了,則等待緩沖區(qū)空 */
while (1)
{
__IO uint16_t usCount;
usCount = g_tUart1.usTxCount;
if (usCount < g_tUart1.usTxBufSize)
{
break;/*如果發(fā)送緩沖區(qū)沒(méi)有滿就跳出去,讓發(fā)送緩沖區(qū)填滿*/
}
else if(usCount == g_tUart1.usTxBufSize)/* 數(shù)據(jù)已填滿緩沖區(qū) */
{
if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TXE)== RESET)
{
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TXE); /* 使能發(fā)送中斷(緩沖區(qū)空) */
}
}
}
/* 將新數(shù)據(jù)填入發(fā)送緩沖區(qū) */
g_tUart1.pTxBuf[g_tUart1.usTxWrite] = _ucaBuf[i];
DISABLE_INT();
if (++g_tUart1.usTxWrite >= g_tUart1.usTxBufSize)
{
g_tUart1.usTxWrite = 0;
}
g_tUart1.usTxCount++;
ENABLE_INT();
}
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TXE); /* 使能發(fā)送中斷(緩沖區(qū)空) */
}
如果要發(fā)送的數(shù)據(jù)沒(méi)有超過(guò)發(fā)送緩沖區(qū)大小,實(shí)現(xiàn)起來(lái)還比較容易,直接把數(shù)據(jù)填到 FIFO 里面,并使能發(fā)送空中斷即可。如果超過(guò)了 FIFO 大小,就需要等待有空間可用,針對(duì)這種情況有個(gè)重要的知識(shí)點(diǎn),就是當(dāng)緩沖剛剛填滿的時(shí)候要判斷發(fā)送空中斷是否開(kāi)啟了,如果填滿了還沒(méi)有開(kāi)啟,就會(huì)卡死在 while 循環(huán)中,所以多了一個(gè)剛填滿時(shí)的判斷,填滿了還沒(méi)有開(kāi)啟發(fā)送空中斷,要開(kāi)啟下。
5、從FIFO中讀出數(shù)據(jù)
什么時(shí)候要從FIFO中讀出?當(dāng)然是在從串口獲取數(shù)據(jù)的時(shí)候?qū)腇IFO中讀數(shù)據(jù)啦。
uint8_t UartGetChar(uint8_t *_pByte)
{
uint16_t usCount;
/* usRxWrite 變量在中斷函數(shù)中被改寫(xiě),主程序讀取該變量時(shí),必須進(jìn)行臨界區(qū)保護(hù) */
DISABLE_INT();
usCount = g_tUart1.usRxCount;
ENABLE_INT();
/* 如果讀和寫(xiě)索引相同,則返回0 */
if (usCount == 0) /* 已經(jīng)沒(méi)有數(shù)據(jù) */
{
return 0;
}
else
{
*_pByte = g_tUart1.pRxBuf[g_tUart1.usRxRead];/* 從串口接收FIFO取1個(gè)數(shù)據(jù) */
/* 改寫(xiě)FIFO讀索引 */
DISABLE_INT();
if (++g_tUart1.usRxRead >=g_tUart1.usRxBufSize)
{
g_tUart1.usRxRead = 0;
}
g_tUart1.usRxCount--;
ENABLE_INT();
return 1;
}
}
6、中斷服務(wù)函數(shù)
void USART1_IRQHandler(void)
{
/* 處理接收緩沖區(qū)中斷 */
if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_RXNE)!= RESET)
{
/* 從串口接收數(shù)據(jù)寄存器讀取數(shù)據(jù)存放到接收FIFO */
uint8_t ch;
ch = READ_REG(g_tUart1.uart->DR);
g_tUart1.pRxBuf[g_tUart1.usRxWrite] = ch;
if (++g_tUart1.usRxWrite >= g_tUart1.usRxBufSize)
{
g_tUart1.usRxWrite = 0;
}
if (g_tUart1.usRxCount < g_tUart1.usRxBufSize)
{
g_tUart1.usRxCount++;
}
}
/* 處理發(fā)送緩沖區(qū)空中斷 */
if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TXE)!= RESET)
{
if (g_tUart1.usTxCount == 0)
{
/* 發(fā)送緩沖區(qū)的數(shù)據(jù)已取完時(shí), 禁止發(fā)送緩沖區(qū)空中斷 (注意:此時(shí)最后1個(gè)數(shù)據(jù)還未真正發(fā)送完畢)*/
CLEAR_BIT(g_tUart1.uart->CR1, USART_CR1_TXEIE);
/* 使能數(shù)據(jù)發(fā)送完畢中斷 */
__HAL_UART_ENABLE_IT(&UartHandle,UART_IT_TC);/* 使能數(shù)據(jù)發(fā)送完畢中斷 */
}
else
{
/* 從發(fā)送FIFO取1個(gè)字節(jié)寫(xiě)入串口發(fā)送數(shù)據(jù)寄存器 */
g_tUart1.uart->DR = g_tUart1.pTxBuf[g_tUart1.usTxRead];
if (++g_tUart1.usTxRead >= g_tUart1.usTxBufSize)
{
g_tUart1.usTxRead = 0;
}
g_tUart1.usTxCount--;
}
}
/* 數(shù)據(jù)bit位全部發(fā)送完畢的中斷 */
if(__HAL_UART_GET_FLAG(&UartHandle,UART_FLAG_TC)!= RESET)
{
if (g_tUart1.usTxCount == 0)
{
/* 如果發(fā)送FIFO的數(shù)據(jù)全部發(fā)送完畢,禁止數(shù)據(jù)發(fā)送完畢中斷 */
__HAL_USART_CLEAR_FLAG(&UartHandle,USART_FLAG_TC);/*發(fā)送完畢中斷*/
}
}
7、主函數(shù)
const char buf1[] = "接收到串口命令1\r\n";
const char buf2[] = "接收到串口命令2\r\n";
const char buf3[] = "接收到串口命令3\r\n";
const char buf4[] = "接收到串口命令4\r\n";
int main(void)
{
uint8_t read[100] = {0} ;
bsp_Init();
while(1)
{
/* 接收到的串口命令處理 */
if (UartGetChar(read)!=0)
{
UartSendBuf((uint8_t *)read, sizeof(read)/sizeof(uint8_t));
switch (read[0])
{
case '1':
UartSendBuf((uint8_t *)buf1, strlen(buf1));
break;
case '2':
UartSendBuf((uint8_t *)buf2, strlen(buf2));
break;
case '3':
UartSendBuf((uint8_t *)buf3, strlen(buf3));
break;
case '4':
UartSendBuf((uint8_t *)buf4, strlen(buf4));
break;
default:
break;
}
}
}
}
8、關(guān)于串口掃盲
串口掃盲就是幾個(gè)狀態(tài)標(biāo)志位不好理解,只要理解好這張圖就好辦了,其他的請(qǐng)參考相關(guān)的教程。
-
USART_FLAG_TXE當(dāng)發(fā)送數(shù)據(jù)寄存器里的數(shù)據(jù)被全部取完時(shí),該寄存器是空的,那么該標(biāo)志位就會(huì)被置1。通過(guò)這個(gè)標(biāo)志位的值可以判斷發(fā)送數(shù)據(jù)寄存器中的數(shù)據(jù)有沒(méi)有完全被取走,當(dāng)該寄存器是空的時(shí)候,可以提醒CPU繼續(xù)往該寄存器里存入新的數(shù)據(jù); -
USART_FLAG_TC當(dāng)發(fā)送移位寄存器里的每個(gè)字節(jié)通過(guò)TX腳一位一位發(fā)送出去之后,該標(biāo)志位值就會(huì)被置1。通過(guò)這個(gè)標(biāo)志位的值可以判斷發(fā)送移位寄存器里的數(shù)據(jù)有沒(méi)有被全部發(fā)送出去; -
USART_FLAG_TXE和USART_FLAG_TC之間的聯(lián)系 結(jié)合上面流程圖來(lái)進(jìn)行說(shuō)明,實(shí)際上發(fā)送移位寄存器通過(guò)TX腳發(fā)送數(shù)據(jù)這個(gè)過(guò)程是比較耗時(shí)的,所以在此過(guò)程進(jìn)行時(shí),可通過(guò)判斷當(dāng)USART_FLAG_TXE = 1,即發(fā)送數(shù)據(jù)寄存器里的數(shù)據(jù)已被全部轉(zhuǎn)入發(fā)送移位寄存器時(shí),就讓CPU往發(fā)送數(shù)據(jù)寄存器轉(zhuǎn)入新的數(shù)據(jù)。當(dāng)發(fā)送移位寄存器把數(shù)據(jù)幀全部發(fā)送出去之后,可通過(guò)判斷USART_FLAG_TC = 1,證明數(shù)據(jù)幀的最后一個(gè)字節(jié)都已經(jīng)通過(guò)TX腳發(fā)送完了。
工程師必備
- 項(xiàng)目客服
- 培訓(xùn)客服
- 平臺(tái)客服
TOP




















