一、串口的初始化和中斷設置
1、初始化GPIO:
根據手冊的8.1.11節,我們可以找到下表:
在全雙工的模式下,發送引腳需要設置爲推輓複用輸出,接收引腳則設置爲浮空輸入或帶上拉的輸入。因爲一般不用同步和流量控制的方式,所以CK、RST、CTS引腳不作配置。當然啦,在使用STM32外設的時候不要忘記打開外設時鐘(GPIO和USART的RCC)。
1. USART_FLAG_RXNE—接受數據寄存器非空標誌位(Receive data register not empty flag)
當RDR移位寄存器中的數據被轉移到USART_DR寄存器中,該位被硬件置位 (表示接受到一個字節) 。如果USART_CR1寄存器中的RXNEIE爲1,則產生中斷。對USART_DR的讀操作可以將該位清零。RXNE位也可以通過寫入0來清除,只有在多緩存通訊中才推薦這種清除程序。
0:數據沒有收到;
1:收到數據,可以讀出。
2. USART_FLAG_TXE —發送數據寄存器空標誌位(Transmit data register empty flag)
當TDR寄存器中的數據被硬件轉移到移位寄存器的時候,該位被硬件置位 (表示發送數據寄存器爲空,可以繼續寫入數據。注:此時並不確定數據是否發送完成) 。如果USART_CR1寄存器中的TXEIE爲1,則產生中斷。對USART_DR的寫操作,將該位清零。
0:數據還沒有被轉移到移位寄存器;
1:數據已經被轉移到移位寄存器。
3. USART_FLAG_TC — 發送完成標誌位(Transmission Complete flag)
包含有數據的一幀發送完成後,並且TXE=1時,由硬件將該位置’1’ (表示一幀數據發送完成,可以繼續發送下一幀)。如果USART_CR1中的TCIE爲’1’,則產生中斷。由軟件序列清除該位(先讀USART_SR,然後寫入USART_DR)。TC位也可以通過寫入’0’來清除,只有在多緩存通訊中才推薦這種清除程序。
0:發送還未完成;
1:發送完成。
有一種形象的說法是:TXE是指“彈倉”空;TC是“槍膛”空。
也就是說,你寫數據到串口時,是裝入彈倉,硬件會將數據移到槍膛,這時,TXE爲1,TC爲0,STM32硬件的TX腳正在發送數據,但你還可以裝入數據到彈倉,裝入後,TXE爲0,TC爲0.
TX發送完一個數據後,立即將數據從彈倉移入槍膛,這時,TXE爲1,TC爲0.
最後TX發送完數據,你又沒有裝入新數據,這時。TXE爲1,TC爲1.
————————————————
版權聲明:本文爲CSDN博主「LittleAshes」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_41746317/java/article/details/97754167
GPIO_InitTypeDef GPIO_InitStructure;
//開啓串口和GPIO的時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
//配置發送引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
//發送引腳設置爲推輓複用
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置接收引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//接收引腳設置爲浮空輸入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
2、配置串口參數
有專門用於初始化串口的庫函數(USART_Init)和對應的結構體(USART_InitTypeDef),好像每個外設都有這樣的配套,具體內容可參看《STM32F10xxx固件庫_3.xx.pdf》。
USART_InitTypeDef USART_InitStructure;
//波特率
USART_InitStructure.USART_BaudRate = 9600;
//數據長度
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;
//初始化串口1
USART_Init(USART1, &USART_InitStructure);
3、中斷配置
在使用STM32的中斷前,要對NVIC中斷控制器進行配置,設置中斷的優先級。
//配置中斷優先級
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
4、使能串口及串口中斷
注意1:初始化時不要隨意打開TXE中斷!只要TX-DR寄存器爲空,TX和TXE標誌都會馬上被置位而立即會產生中斷(參考《STM32中文參考手冊》的25.3.2節),即使中斷標誌被清除,也會被重新置位。因此,我採用的是TC中斷而不是採用TXE中斷。
注意2:不要採用在一箇中斷配置函數中同時打開兩個中斷!例如:USART_ITConfig(USART1, USART_IT_TC | USART_IT_RXNE, ENABLE); 咋眼一看,明明只打開TC中斷和RX中斷,然而卻會同時把TXE中斷也打開。
//串口1使能
USART_Cmd(USART1, ENABLE);
//清除接收中斷標記
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//清除發送完成中斷標記
USART_ClearITPendingBit(USART1, USART_IT_TC);
//打開串口1發送完中斷
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//打開串口1接收中斷 兩個中斷不能在一個函數中同時打開!!!太坑了T_T
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
————————————————
這樣,串口1配置好了。但代碼一運行就會發現不妥!爲什麼每次初始化完成就馬上進入中斷了呢???遇到這種現象千萬不要大驚小怪,我很淡(dan)定(teng)地做了個實驗,發現處理器復位後,串口的SR寄存器中的TC標誌會被置位。而根《STM32中文參考手冊》25.3.2節,在串口使能後會自動發送一個空閒幀,發送完畢後TC也會置位,所以初始化將導致串口初始化完畢後馬上進入TC中斷。爲了避免這種情況,可以在串口使能後等待空閒幀發送完畢,再打開TC中斷。
具體看下面完整的初始化代碼:
————————————————
//配置串口1
void USART1_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
//配置發送引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
//發送引腳設置爲推輓複用
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置接收引腳
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//接收引腳設置爲浮空輸入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//波特率
USART_InitStructure.USART_BaudRate = 9600;
//數據長度
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;
//初始化串口1
USART_Init(USART1, &USART_InitStructure);
//USART1->SR寄存器復位後TC位爲1,在此清零
USART_ClearFlag(USART1, USART_FLAG_TC);
//串口1使能
USART_Cmd(USART1, ENABLE);
//使能後串口發送一個空閒幀,等待空閒幀發送完畢後將TC標記位清零
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
//否則開啓TC中斷後會馬上中斷
USART_ClearFlag(USART1, USART_FLAG_TC);
//配置中斷優先級
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/***************************************************************
注意:初始化時不要隨意打開TXE中斷,
因爲只要TX-DR寄存器爲空,TX和TXE都會馬上被置位而立即會產生中斷
***************************************************************/
//清除接收中斷標記
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//清除發送完成中斷標記
USART_ClearITPendingBit(USART1, USART_IT_TC);
//打開串口1發送完中斷
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//打開串口1接收中斷 兩個中斷不能在一個函數中同時打開!!!太坑了T_T
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
————————————————
二、數據的發送和接收(注:以下代碼有bug,博主至今還未找到原因T-T,僅供思路以參考!)
爲了提高代碼的效率,我使用基於環形緩衝的串口通信方式。
發送數據原理:把要發送的數據全部加入到緩衝區中,讓處理器開始發送。一個數據發送結束後,即會產生TC中斷,此時在中斷服務程序中發送下一個數據。像吃飯看電視,在夾菜(發數據)的時候纔要把注意力放到菜盤子上,嚼飯的時候(數據發送中)可以看電視,在開始發送數據到數據發送完畢觸發中斷的這段時間裏,處理器可以去做別的事情。
接收數據原理:當一個數據接收完畢後,將數據立存入緩衝區而不處理,並在未處理數據的計數器上加1。等到處理器空閒,再從緩衝區讀取這些數據做並處理(不在中斷函數中)。
如此一來,串口的收發速率並不受影響,還能保證處理器在數據收發的過程中並行執行其他任務。
————————————————
#include "usart1.h"
#include "string.h"
//發送緩衝區
u8 Usart1_SendBuffer[USART_SendBufferSize];
//接收緩衝區
u8 Usart1_RecvBuffer[USART_RecvBufferSize];
//發送緩衝區指針
int Usart1_SendPointer = 0;
//接收緩衝區指針
int Usart1_RecvPointer = 0;
//發送字符隊列的長度
int Usart1_SendDataSize = 0;
//接收未處理字符數
int Usart1_RecvDataSize = 0;
//串口1發送狀態
int Usart1_SendStatus = USART_Status_Idle;
//生成字符串的緩衝區
char StringBuffer[100];
//發送字符串
void USART1_SendString(char *str)
{
// while(*str)
// {
// USART1_SendByte(*str++);
// }
USART1_SendArray((u8*)str, strlen(str));
}
//發送字節隊列
void USART1_SendArray(u8 *DataArray, int count)
{
if(count <= 0)
{
return;
}
while(count)
{
USART1_SendByte(*DataArray++);
count --;
}
}
//發送一個字節
void USART1_SendByte(u8 data)
{
int pos;
//如果緩衝區滿了,要等待
while(Usart1_SendDataSize >= USART_SendBufferSize);
//計算數據在緩衝區的位置
pos = Usart1_SendPointer + Usart1_SendDataSize;
//數據位置超過緩衝區尾地址
if(pos >= USART_SendBufferSize)
{
//重新計算位置
pos = pos - USART_SendBufferSize;
}
Usart1_SendBuffer[pos] = data;
Usart1_SendDataSize ++;
//如果串口空閒,立即發送
if(Usart1_SendStatus == USART_Status_Idle)
{
Usart1_SendStatus = USART_Status_Busy;
USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]);
//指針移動到緩衝區尾地址後,循環到緩衝區首地址
if(Usart1_SendPointer == USART_SendBufferSize)
{
Usart1_SendPointer = 0;
}
Usart1_SendDataSize --;
}
}
//串口1中斷服務程序
void USART1_IRQHandler()
{
//判斷髮送完成中斷
if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
{
//清空發送完成TC標記位
USART_ClearFlag(USART1, USART_FLAG_TC);
//清空串口發送完成中斷TCIE標記
USART_ClearITPendingBit(USART1, USART_IT_TC);
if(Usart1_SendDataSize > 0)
{
//發送下一個數據
USART_SendData(USART1, Usart1_SendBuffer[Usart1_SendPointer++]);
//指針移動到緩衝區尾地址後,循環到緩衝區首地址
if(Usart1_SendPointer == USART_SendBufferSize)
{
Usart1_SendPointer = 0;
}
//待發送數據減1
Usart1_SendDataSize --;
}
else
{
//發送完畢,串口1發送狀態:空閒
Usart1_SendStatus = USART_Status_Idle;
}
}
//接收中斷
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
//清空串口接收標記
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//獲取緩衝區數據
Usart1_RecvBuffer[Usart1_RecvPointer++] = USART_ReceiveData(USART1);
//如果沒有溢出,待處理數據+1。否則丟棄該數據
if(Usart1_RecvDataSize < USART_RecvBufferSize)
{
Usart1_RecvDataSize ++;
}
//指針移動到緩衝區尾地址後,循環到緩衝區首地址
if(Usart1_RecvPointer == USART_RecvBufferSize)
{
Usart1_RecvPointer = 0;
}
}
}
————————————————
版權聲明:本文爲CSDN博主「培培哥」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u014695839/java/article/details/62037269