目錄
前言:
STM32串口通信基礎(chǔ)知識:
1,STM32里的串口通信
2,串口的發(fā)送和接收
串口發(fā)送:
串口接收:
串口在STM32中的配置:
1. RCC開啟USART、串口TX/RX所對應(yīng)的GPIO口
2. 初始化GPIO口
3. 串口初始化
4. 串口使能
5. 串口發(fā)送數(shù)據(jù)
串口接收的兩種實現(xiàn)方式:
1,輪詢方式:
2,中斷方式:
查詢RXNE標(biāo)志位
?使用中斷
實戰(zhàn)演練:
1. 初始化LED燈相應(yīng)的GPIO口
2. 初始化USART3
3. 實現(xiàn)發(fā)送功能
4. 實現(xiàn)接收字符串功能
1,通過輪詢的方式檢查是否接收到了特定的字符串:
2,通過中斷的方式實現(xiàn)USART3接收特定的字符串:
1. 配置NVIC以使能USART3中斷
2. 在USART3初始化函數(shù)中開啟接收中斷
3. 編寫USART3的中斷服務(wù)函數(shù)來處理接收到的字節(jié)
5. 主函數(shù)
總結(jié):
前言:
本文在于記錄自己最近做項目過程中遇到的問題和總結(jié),各種情況下串口通信在STM32的實際使用方面占有很大的比重,本文主要對串口的發(fā)送和接受做了一個詳細(xì)的總結(jié)和規(guī)劃,同時也對串口通信做一個簡要的總結(jié)。
STM32串口通信基礎(chǔ)知識:
1,STM32里的串口通信
在STM32里,串口通信是USART,STM32可以通過串口和其他設(shè)備進(jìn)行傳輸并行數(shù)據(jù),是全雙工,異步時鐘控制,設(shè)備之間是點(diǎn)對點(diǎn)的傳輸。對應(yīng)的STM32引腳分別是RX和TX端。STM32的串口資源有USART1、USART2、USART3.
串口的幾個重要的參數(shù):
- 波特率,串口通信的速率
- 空閑,一般為高電平
- 起始位,標(biāo)志一個數(shù)據(jù)幀的開始,固定為低電平。當(dāng)數(shù)據(jù)開始發(fā)送時,產(chǎn)生一個下降沿。(空閑–>起始位)
- 數(shù)據(jù)位,發(fā)送數(shù)據(jù)幀,1為高電平,0為低電平。低位先行。
比如 發(fā)送數(shù)據(jù)幀0x0F 在數(shù)據(jù)幀里就是低位線性 即 1111 0000- 校驗位,用于數(shù)據(jù)驗證,根據(jù)數(shù)據(jù)位的計算得來。有奇校驗,偶校驗和無校驗。
- 停止位,用于數(shù)據(jù)的間隔,固定為高電平。數(shù)據(jù)幀發(fā)送完成后,產(chǎn)生一個上升沿。(數(shù)據(jù)傳輸–>停止位)
下方就是一個字節(jié)數(shù)據(jù)的傳輸過程,從圖中可以看出,串口發(fā)送的數(shù)據(jù)一般都是以數(shù)據(jù)幀的形式進(jìn)行傳輸,每個數(shù)據(jù)幀都由起始位,數(shù)據(jù)位,停止位組成, 且停止位可變。
2,串口的發(fā)送和接收
USART是STM32內(nèi)部集成的硬件外設(shè),可以根據(jù)數(shù)據(jù)寄存器的一個字節(jié)數(shù)據(jù)自動生成數(shù)據(jù)幀時序,從TX引腳發(fā)送出去,也可以自動接收RX引腳的數(shù)據(jù)幀時序,拼接成一個字節(jié)數(shù)據(jù),存放在數(shù)據(jù)寄存器里。
當(dāng)配置好USART的電路之后,直接讀取數(shù)據(jù)寄存器,就可以自動發(fā)送數(shù)據(jù)和接收數(shù)據(jù)了。在發(fā)送和接收的模塊有4個重要的寄存器
- 發(fā)送數(shù)據(jù)寄存器TDR
- 發(fā)送移位寄存器,把一個字節(jié)的數(shù)據(jù)一位一位的移出去
- 接收數(shù)據(jù)寄存器RDR
- 接收移位寄存器,把一個字節(jié)的數(shù)據(jù)
下方為串口的發(fā)送和接收圖解:
串口發(fā)送:
在配置串口的各個參數(shù)時,可以選擇發(fā)送數(shù)據(jù)幀的數(shù)據(jù)位的大小,可選8位或9位。
串口發(fā)送數(shù)據(jù)實際上就是對發(fā)送數(shù)據(jù)寄存器TDR進(jìn)行寫操作。
1. 當(dāng)串口發(fā)送數(shù)據(jù)時,會檢測發(fā)送移位寄存器是不是有數(shù)據(jù)正在移位,如果沒有移位,那么這個數(shù)據(jù)就會立刻轉(zhuǎn)移到發(fā)送移位寄存器里。準(zhǔn)備發(fā)送。
2. 當(dāng)數(shù)據(jù)移動到移位寄存器時,會產(chǎn)生一個TXE發(fā)送寄存器空標(biāo)志位,該位描述如下。當(dāng)TXE被置1,那么就可以在TDR寫入下一個數(shù)據(jù)了。即發(fā)送下一個數(shù)據(jù)。
3. 發(fā)送移位寄存器在發(fā)送器控制的控制下,向右移位,一位一位的把數(shù)據(jù)傳輸?shù)絋X引腳。
4. 數(shù)據(jù)移位完成后,新的數(shù)據(jù)就會再次從TDR轉(zhuǎn)移到發(fā)送移位寄存器里來,依次重復(fù)1-3的過程。通過讀取TXE標(biāo)志位來判斷是否發(fā)送下一個數(shù)據(jù)。
串口接收:
- 數(shù)據(jù)從RX引腳通向接收移位寄存器,在接收控制的控制下,一位一位的讀取RX的電平,把第一位放在最高位,然后右移,移位八次之后就可以接收一個字節(jié)了。
- 當(dāng)一個字節(jié)數(shù)據(jù)移位完成之后,這一個字節(jié)的數(shù)據(jù)就會整體的移到接收數(shù)據(jù)寄存器RDR里來。
- 在轉(zhuǎn)移時會置RXNE接收標(biāo)志位,即RDR寄存器非空,下方為該位的描述。當(dāng)被置1后,就說明數(shù)據(jù)可以被讀出。
下圖即為串口接收的工作流程
串口在STM32中的配置:
1. RCC開啟USART、串口TX/RX所對應(yīng)的GPIO口
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //開啟USART2的時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //開啟GPIOA的時鐘
2. 初始化GPIO口
這里注意哈,根據(jù)自己的需求來配置GPIO口,發(fā)送和接收是都需要還是只需要其中一個。然后對應(yīng)的根據(jù)引腳定義表來初始化對應(yīng)的GPIO口。
USART3對應(yīng)的引腳
USART2對應(yīng)的引腳
USART1對應(yīng)的引腳
這里根據(jù)手冊來看,RX引腳模式配置成浮空輸入或者上拉輸入。TX引腳模式配置成復(fù)用推挽輸出。
GPIO_InitTypeDef GPIO_InitStructure;
// USART3 TX -> PB10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// USART3 RX -> PB11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
3. 串口初始化
注意哈,USART_Init()這個函數(shù),是用來配置串口的相關(guān)參數(shù)的。
- USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,這里我們給9600
- USART_HardwareFlowControl 是否選擇硬件流觸發(fā),一般這個我們也不選,所以選擇無硬件流觸發(fā)。
- USART_Mode 這個參數(shù)要注意了哈,串口的模式,發(fā)送模式還是接收模式,還是兩者都有,這里使用收發(fā)模式
- USART_Parity 校驗位,可以選擇奇偶校驗和不校驗。沒有需求就直接無校驗
- USART_StopBits 停止位 有1、0.5、2位,我們這里選1位停止位
- USART_WordLength 數(shù)據(jù)位 有8位和9位可以選擇
//串口初始化
USART_InitTypeDef USART_InitStruct;
USART_StructInit(&USART_InitStruct); //初始默認(rèn)值
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流觸發(fā)
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; //收發(fā)模式(TX 發(fā)送模式 RX 接收模式)
USART_InitStruct.USART_Parity=USART_Parity_No; //不選擇校驗
USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
USART_InitStruct.USART_WordLength=USART_WordLength_8b; //數(shù)據(jù)位8位
USART_Init(USART3,&USART_InitStruct);
4. 串口使能
//串口使能
USART_Cmd(USART3,ENABLE);
5. 串口發(fā)送數(shù)據(jù)
注意哈,我們要判斷TXE標(biāo)志位的狀態(tài)。0,數(shù)據(jù)還沒有被轉(zhuǎn)移到移位寄存器;1,數(shù)據(jù)已經(jīng)被轉(zhuǎn)移到移位寄存器。當(dāng)TXE標(biāo)志位為1時,就說明可以發(fā)送下一個數(shù)據(jù)了。詳細(xì)過程可看上面串口發(fā)送的解釋。
//串口3發(fā)送一個字節(jié)
void Usart3_SendByte(u8 val)
{
USART_SendData(USART3, val);
//0 表示數(shù)據(jù)還未轉(zhuǎn)移到移位寄存器 循環(huán)等待 1 數(shù)據(jù)已經(jīng)被轉(zhuǎn)移到了移位寄存器可以發(fā)送數(shù)據(jù)
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); //等待發(fā)送完成,不需要手動清零 再次寫入TDR時會自動清零
}
經(jīng)過上述五步的配置,單片機(jī)就可以通過串口發(fā)送數(shù)據(jù)了。
串口接收的兩種實現(xiàn)方式:
串口接收通??梢酝ㄟ^輪詢(Polling)和中斷(Interrupt)兩種方式來實現(xiàn)。
- 輪詢方式就是通過不斷的查詢RXNE標(biāo)志位,通過判斷RXNE位的狀態(tài)來確定數(shù)據(jù)是否接收。
- 中斷方式就是通過配置接收輸出控制通道,配置NVIC,在中斷服務(wù)子函數(shù)里進(jìn)行數(shù)據(jù)的接收。
1,輪詢方式:
在輪詢方式中,程序通過不斷地查詢串口接收緩沖區(qū)是否有數(shù)據(jù)到達(dá)。當(dāng)檢測到數(shù)據(jù)到達(dá)時,程序立即讀取接收緩沖區(qū)中的數(shù)據(jù)。
優(yōu)點(diǎn):
- 實現(xiàn)簡單,易于理解。
- 可以直接在接收到數(shù)據(jù)后立即進(jìn)行處理。
- 適合于數(shù)據(jù)傳輸量不大且CPU負(fù)荷較輕的場合。
缺點(diǎn):
- 需要不斷地輪詢串口接收緩沖區(qū),占用 CPU 資源。
- 無法及時響應(yīng)其他任務(wù)或事件。
- 效率較低,可能會錯過一些數(shù)據(jù)。
實現(xiàn)步驟:
配置串口:設(shè)置串口的波特率、數(shù)據(jù)位數(shù)、停止位等參數(shù)。
輪詢狀態(tài)寄存器:不斷檢查USART的狀態(tài)寄存器,判斷接收緩沖區(qū)是否有新數(shù)據(jù)。
讀取數(shù)據(jù):一旦發(fā)現(xiàn)有新數(shù)據(jù),立即從數(shù)據(jù)寄存器讀取數(shù)據(jù)。?
示例代碼:
// 定義一個函數(shù),用于輪詢方式接收 USART 數(shù)據(jù)
void USART_Receive_Polling(void) {
// 進(jìn)入一個無限循環(huán)
while(1) {
// 檢查接收緩沖區(qū)是否有數(shù)據(jù)
if(USART_GetFlagStatus(USART3, USART_FLAG_RXNE) == SET) {
// 如果接收緩沖區(qū)有數(shù)據(jù)
// 從接收緩沖區(qū)讀取數(shù)據(jù)
uint8_t data = USART_ReceiveData(USART3);
// 處理接收到的數(shù)據(jù)
// 這里可以添加代碼來解析和處理接收到的數(shù)據(jù)
}
// 如果接收緩沖區(qū)無數(shù)據(jù),則繼續(xù)輪詢
// 可以添加延時以降低 CPU 占用率
// delay_ms(10);
}
}
2,中斷方式:
在中斷方式中,程序允許 MCU 在接收到數(shù)據(jù)時觸發(fā)串口接收中斷,并在中斷服務(wù)函數(shù)中處理接收到的數(shù)據(jù)。當(dāng)接收緩沖區(qū)有新數(shù)據(jù)時,硬件自動產(chǎn)生一個中斷,CPU響應(yīng)這個中斷并執(zhí)行中斷服務(wù)程序來處理接收到的數(shù)據(jù)。
優(yōu)點(diǎn):
- 采用了中斷機(jī)制,不需要不斷地輪詢串口接收緩沖區(qū),減少了 CPU 的占用率。
- 效率高,可以及時響應(yīng)其他任務(wù)或事件,提高了系統(tǒng)的實時性。
- 適合于數(shù)據(jù)量大或?qū)崟r性要求高的應(yīng)用
缺點(diǎn):
- 實現(xiàn)相對復(fù)雜,需要編寫中斷服務(wù)函數(shù)。
- 在中斷服務(wù)函數(shù)中對數(shù)據(jù)的處理需要考慮中斷嵌套、優(yōu)先級等問題,需要謹(jǐn)慎設(shè)計。
實現(xiàn)步驟:
配置串口:同輪詢方式。
使能中斷:在串口初始化中,使能USART的接收中斷。
編寫中斷服務(wù)程序:實現(xiàn)USART的中斷服務(wù)函數(shù),該函數(shù)會在接收到新數(shù)據(jù)時被調(diào)用。
數(shù)據(jù)處理:在中斷服務(wù)程序中讀取接收到的數(shù)據(jù),并進(jìn)行相應(yīng)的處理。
示例代碼:
// USART3中斷處理函數(shù)
void USART3_IRQHandler(void) {
// 檢查 USART3 接收中斷標(biāo)志位是否被設(shè)置
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
// 如果接收緩沖區(qū)非空
// 從接收緩沖區(qū)讀取數(shù)據(jù)
uint8_t data = USART_ReceiveData(USART3);
// 處理接收到的數(shù)據(jù)
// 在這里可以添加代碼來處理接收到的數(shù)據(jù)
}
}
// 使 USART3 接收中斷
void USART_Receive_Interrupt(void) {
// 使能串口接收中斷
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
// 配置 USART3 中斷優(yōu)先級
NVIC_InitTypeDef NVIC_InitStructure;
// 設(shè)置中斷通道為 USART3
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
// 設(shè)置中斷搶占優(yōu)先級為0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 設(shè)置中斷響應(yīng)優(yōu)先級為0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
// 使能中斷通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 初始化 NVIC
NVIC_Init(&NVIC_InitStructure);
// 進(jìn)入一個無限循環(huán),等待中斷服務(wù)函數(shù)處理接收到的數(shù)據(jù)
while(1) {
// 在中斷服務(wù)函數(shù)中處理接收到的數(shù)據(jù)
}
}
查詢RXNE標(biāo)志位
這里我們還是來看一看RXNE標(biāo)志位的描述
上圖描述,為0時數(shù)據(jù)沒有收到,為1時收到了數(shù)據(jù),數(shù)據(jù)可以從RDR里讀出
所以在主程序里不斷讀取RXNE標(biāo)志位,如果為1,表示數(shù)據(jù)可以讀出
uint8_t RX_Data; // 定義一個全局變量 RX_Data,用于存儲接收到的數(shù)據(jù)
int main() // 主函數(shù)入口
{
Serial_Init(); // 初始化串口
Serial_SendByte(0x16); // 向串口發(fā)送一個字節(jié)數(shù)據(jù) 0x16
while(1) // 進(jìn)入一個無限循環(huán)
{
if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET) // 檢查 USART2 接收緩沖區(qū)是否有數(shù)據(jù),0=RESET 循環(huán)等待 ,1=SET 可以接收數(shù)據(jù)
{
RX_Data = USART_ReceiveData(USART2); // 如果接收緩沖區(qū)有數(shù)據(jù),則從中讀取數(shù)據(jù)并存儲到 RX_Data 中
Serial_SendByte(RX_Data); // 將接收到的數(shù)據(jù)發(fā)送回串口
}
}
}
if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET)
// 檢查 USART2 接收緩沖區(qū)是否有數(shù)據(jù),0=RESET 循環(huán)等待 ,1=SET 可以接收數(shù)據(jù)
下圖為程序現(xiàn)象:pc向單片機(jī)發(fā)送數(shù)據(jù)0x15,單片機(jī)接收數(shù)據(jù)0x15,并且把接收到的數(shù)據(jù)作為數(shù)據(jù)發(fā)送到pc,在pc上顯示0x15。
?使用中斷
- 通過配置串口的接收作為中斷源,開啟中斷輸出控制,配置NVIC。開啟中斷通道。
// 開啟 USART2 接收中斷
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
// 配置 NVIC(Nested Vectored Interrupt Controller,嵌套向量中斷控制器)
// 設(shè)置 NVIC 分組優(yōu)先級,選擇分組 2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 定義一個 NVIC_InitTypeDef 結(jié)構(gòu)體變量,用于配置中斷控制器
NVIC_InitTypeDef NVIC_InitStruct;
// 設(shè)置中斷通道為 USART2,即選擇 USART2 的中斷通道
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
// 使能中斷通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
// 設(shè)置搶占優(yōu)先級為 1
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
// 設(shè)置子優(yōu)先級為 1
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
// 將配置好的 NVIC_InitStruct 結(jié)構(gòu)體變量傳入 NVIC_Init 函數(shù)中,對 NVIC 進(jìn)行配置
NVIC_Init(&NVIC_InitStruct);
- 中斷服務(wù)子函數(shù)
中斷服務(wù)子函數(shù)寫好后,就可以在中斷里讀取接收到的數(shù)據(jù)了。
當(dāng)接收到數(shù)據(jù)后,觸發(fā)接收中斷,主程序暫停執(zhí)行。接收完數(shù)據(jù)后主程序回復(fù)執(zhí)行。當(dāng)接收到數(shù)據(jù)時,就觸發(fā)中斷。
void USART2_IRQHandler(void)
{
// 檢查 USART2 接收緩沖區(qū)是否有數(shù)據(jù)
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET) // RXNE 標(biāo)志位為1 表示可以接收數(shù)據(jù)
{
// 從接收緩沖區(qū)讀取數(shù)據(jù)并存儲到 RX_Data 中
RX_Data = USART_ReceiveData(USART2);
// 設(shè)置標(biāo)志位 Flag 為 1,表示已接收到數(shù)據(jù)
Flag = 1;
// 清除 USART2 接收中斷標(biāo)志位 RXNE
USART_ClearITPendingBit(USART2, USART_IT_RXNE); // 清除 RXNE 標(biāo)志位
}
}
- 主程序測試
uint8_t RX_Data; // 定義一個全局變量 RX_Data,用于存儲接收到的數(shù)據(jù)
uint8_t Flag; // 定義一個全局變量 Flag,用于表示是否接收到數(shù)據(jù)的標(biāo)志位
int main() // 主函數(shù)入口
{
Serial_Init(); // 初始化串口
Serial_SendByte(0x16); // 向串口發(fā)送一個字節(jié)數(shù)據(jù) 0x16
while(1) // 進(jìn)入一個無限循環(huán)
{
if(Flag == 1) // 如果接收到數(shù)據(jù)的標(biāo)志位為 1
{
Serial_SendByte(RX_Data); // 向串口發(fā)送接收到的數(shù)據(jù),這里可以改為自己需要的邏輯
}
}
}
void USART2_IRQHandler(void) // USART2 中斷服務(wù)函數(shù)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET) // 如果 USART2 接收到數(shù)據(jù)
{
RX_Data = USART_ReceiveData(USART2); // 從接收緩沖區(qū)讀取數(shù)據(jù)并存儲到 RX_Data 中
Flag = 1; // 設(shè)置接收到數(shù)據(jù)的標(biāo)志位 Flag 為 1
USART_ClearITPendingBit(USART2, USART_IT_RXNE); // 清除 USART2 的接收中斷標(biāo)志位 RXNE,準(zhǔn)備接收下一次數(shù)據(jù)
}
}
下圖為程序現(xiàn)象:可以看到,串口確實收到了數(shù)據(jù),只是我把接收到的數(shù)據(jù)0xFE放在了while循環(huán)里,這說明數(shù)據(jù)接收是成功的,使用中斷是可行的。
實戰(zhàn)演練:
要求:使用stm32f103C8T6,使用標(biāo)準(zhǔn)庫,硬件方面使用到了一個LED燈,要求在PC端串口助手發(fā)送"led on",單片機(jī)的usart3接收到PC端發(fā)送的led on時,打開LED燈,同時向PC端發(fā)送“已打開”
為了實現(xiàn)提出的要求,你需要按照以下步驟進(jìn)行編程和硬件配置:
硬件連接:
- LED 燈連接到單片機(jī)的一個 GPIO 端口(比如 PA0)。
- USART3 需要連接到 PC 通過串口或者通過串口轉(zhuǎn)USB模塊。
軟件實現(xiàn)(使用STM32標(biāo)準(zhǔn)庫):
以下提供一個粗略的實現(xiàn)示例:
首先,確保你已經(jīng)在項目中正確配置了 STM32F103 的標(biāo)準(zhǔn)庫,以及正確設(shè)置系統(tǒng)時鐘。
1. 初始化LED燈相應(yīng)的GPIO口
void LED_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定義一個 GPIO_InitTypeDef 結(jié)構(gòu)體變量,用于配置 GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 開啟 GPIOA 時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 設(shè)置要初始化的引腳為 PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 設(shè)置引腳工作模式為推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 設(shè)置引腳的輸出速度為 50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA 的 PA0 引腳
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 將 PA0 引腳輸出高電平,默認(rèn)關(guān)閉 LED
}
2. 初始化USART3
void USART3_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure; // 定義一個 GPIO_InitTypeDef 結(jié)構(gòu)體變量,用于配置 GPIO
USART_InitTypeDef USART_InitStructure; // 定義一個 USART_InitTypeDef 結(jié)構(gòu)體變量,用于配置 USART
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 打開 GPIOB 的時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // 打開 USART3 的時鐘
// 配置 USART3 的 TX 引腳為復(fù)用推挽輸出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 設(shè)置要初始化的引腳為 PB10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 設(shè)置引腳工作模式為復(fù)用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 設(shè)置引腳的輸出速度為 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 GPIOB 的 PB10 引腳
// 配置 USART3 的 RX 引腳為浮空輸入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // 設(shè)置要初始化的引腳為 PB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 設(shè)置引腳工作模式為浮空輸入
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化 GPIOB 的 PB11 引腳
// 配置 USART3 的通信參數(shù)
USART_InitStructure.USART_BaudRate = 9600; // 設(shè)置波特率為 9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 設(shè)置數(shù)據(jù)位長度為 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 設(shè)置停止位為 1 位
USART_InitStructure.USART_Parity = USART_Parity_No; // 設(shè)置校驗位為無校驗
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 設(shè)置硬件流控制為無流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 設(shè)置 USART 的工作模式為接收和發(fā)送都使能
USART_Init(USART3, &USART_InitStructure); // 初始化 USART3
USART_Cmd(USART3, ENABLE); // 使能 USART3
}
3. 實現(xiàn)發(fā)送功能
void USART3_SendChar(char ch) {
// 發(fā)送字符數(shù)據(jù) ch 到 USART3
USART_SendData(USART3, (uint8_t) ch);
// 等待發(fā)送完成
while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
}
4. 實現(xiàn)接收字符串功能
此部分可以通過中斷或者輪詢的方式實現(xiàn)。
1,通過輪詢的方式檢查是否接收到了特定的字符串:
void checkReceive(void) {
uint8_t data; // 定義一個無符號 8 位整數(shù)型變量 data,用于存儲接收到的數(shù)據(jù)
char buffer[8]; // 定義一個長度為 8 的字符數(shù)組 buffer,用于存儲接收到的數(shù)據(jù)
int i = 0; // 定義一個整型變量 i,用于索引 buffer 數(shù)組
// 進(jìn)入一個循環(huán),循環(huán)條件是 i 小于 7
while (i < 7) {
// 檢查 USART3 接收緩沖區(qū)是否非空,即是否有數(shù)據(jù)可讀
if (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) != RESET) {
// 如果接收緩沖區(qū)非空,則讀取接收到的數(shù)據(jù)并存儲到 data 變量中
data = (uint8_t)USART_ReceiveData(USART3);
// 將讀取到的數(shù)據(jù)存儲到 buffer 數(shù)組中,并將索引 i 自增
buffer[i++] = data;
}
}
// 在 buffer 數(shù)組末尾添加字符串結(jié)束標(biāo)志 '\0'
buffer[i] = '\0';
// 比較 buffer 數(shù)組中的內(nèi)容是否為 "led on"
if (strcmp(buffer, "led on") == 0) {
// 如果接收到的數(shù)據(jù)是 "led on",則執(zhí)行以下操作
// 打開 LED 燈,即將 GPIOA 的 PA0 引腳輸出低電平
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
// 定義一個指向字符串常量 "已打開" 的指針 msg
char *msg = "已打開";
// 進(jìn)入一個循環(huán),循環(huán)條件是指針 msg 指向的字符不為空字符 '\0'
while (*msg) {
// 發(fā)送指針 msg 指向的字符到 USART3,然后指針 msg 自增
USART3_SendChar(*msg++);
}
}
}
注意:這里并沒有添加中斷服務(wù)程序,也沒有實現(xiàn)字符緩存區(qū)的溢出處理,此外,發(fā)送和接收字符的精確處理邏輯可能需要根據(jù)實際需求調(diào)整。實際項目中還可能需要考慮debounce(消抖)和更加復(fù)雜的串口命令解析。
務(wù)必確保單片機(jī)的時鐘配置正確,并且USART3的引腳與你連接的外設(shè)相匹配。在進(jìn)行硬件連線時也要確保正確連接。
2,通過中斷的方式實現(xiàn)USART3接收特定的字符串:
要通過中斷方式實現(xiàn)USART3接收字符串,我們需要做幾件事情:
- 配置NVIC以使能USART3中斷。
- 在USART3初始化函數(shù)中開啟接收中斷。
- 編寫USART3的中斷服務(wù)函數(shù)來處理接收到的字節(jié)。
這種實現(xiàn)方式相比輪詢,可以有效減少CPU的負(fù)擔(dān),特別是在數(shù)據(jù)不頻繁接收時。
1. 配置NVIC以使能USART3中斷
在USART3_Init
函數(shù)中,初始化USART3后,你應(yīng)該使能中斷:
NVIC_InitTypeDef NVIC_InitStructure; // 定義一個 NVIC_InitTypeDef 結(jié)構(gòu)體變量,用于配置 NVIC 中斷控制器
// 設(shè)置 NVIC 優(yōu)先級分組為 2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 設(shè)置中斷源為 USART3
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
// 設(shè)置搶占優(yōu)先級為 1
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// 設(shè)置子優(yōu)先級為 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// 使能中斷通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 初始化 NVIC
NVIC_Init(&NVIC_InitStructure);
// 使能 USART3 接收中斷
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
2. 在USART3初始化函數(shù)中開啟接收中斷
已經(jīng)在上面的步驟中通過調(diào)用USART_ITConfig
來實現(xiàn)了。
3. 編寫USART3的中斷服務(wù)函數(shù)來處理接收到的字節(jié)
你要定義一個緩沖區(qū)來存儲接收到的字符,并在接收到整個字符串后進(jìn)行處理:
#define BUFFER_SIZE 100 // 定義緩沖區(qū)大小為 100
char buffer[BUFFER_SIZE]; // 聲明一個大小為 BUFFER_SIZE 的字符數(shù)組作為接收緩沖區(qū)
volatile unsigned int buffer_index = 0; // 聲明一個無符號整數(shù)變量,用于表示當(dāng)前緩沖區(qū)的索引位置,使用 volatile 關(guān)鍵字聲明,表示在中斷中可能被改變,需要及時更新
void USART3_IRQHandler(void) { // 定義 USART3 的中斷服務(wù)函數(shù)
// 檢查是否接收到數(shù)據(jù)
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { // 如果接收到 USART3 的接收中斷標(biāo)志位
char data = (char)USART_ReceiveData(USART3); // 讀取接收到的數(shù)據(jù),并轉(zhuǎn)換為字符類型
// 簡單的字符串終止判斷(例如以換行結(jié)束)
if (data != '\n' && buffer_index < BUFFER_SIZE - 1) { // 如果接收到的字符不是換行且緩沖區(qū)索引未超過最大長度減一
buffer[buffer_index++] = data; // 將接收到的字符存入緩沖區(qū)中,并更新索引
} else {
buffer[buffer_index] = '\0'; // 確保字符串結(jié)束,即在緩沖區(qū)最后添加 '\0' 表示字符串結(jié)束
// 檢查接收到的命令
if (strcmp(buffer, "led on") == 0) { // 如果接收到的命令是 "led on"
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 點(diǎn)亮 LED
char *msg = "已打開\n"; // 定義一個指向字符串的指針,表示要發(fā)送的消息
while (*msg) { // 循環(huán)發(fā)送消息中的每一個字符,直到遇到 '\0' 表示字符串結(jié)束
USART3_SendChar(*msg++); // 發(fā)送字符并將指針移向下一個字符
}
}
// 重置索引,準(zhǔn)備下一次接收
buffer_index = 0; // 將緩沖區(qū)索引重置為 0,準(zhǔn)備接收下一條命令
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE); // 清除接收中斷標(biāo)志位,準(zhǔn)備下一次接收中斷
}
}
這個示例代碼會在接收到一串字符后處理這串字符。如果接收到的字符串是"led on"加上換行符'\n'
,它將點(diǎn)亮LED并通過USART3發(fā)送回"已打開\n"。
注意:實際上你可能需要添加更多的錯誤處理和緩沖區(qū)管理來處理可能出現(xiàn)的錯誤和異常情況(比如緩沖區(qū)溢出)。
此外,為了讓上述代碼正常工作,請確保你的USART3接收中斷已經(jīng)正確配置,并且你的系統(tǒng)時鐘設(shè)置支持你的串口通信需求。你可能還需要根據(jù)你的具體硬件連接調(diào)整GPIO端口初始化和LED操作的代碼。
5. 主函數(shù)
在主函數(shù)里初始化LED和USART3,然后不斷檢查串口接收:
int main(void) {
SystemInit(); // 調(diào)用 SystemInit() 函數(shù)初始化系統(tǒng)時鐘,這通常是啟動代碼中的一部分,用于初始化系統(tǒng)的時鐘和基本的硬件設(shè)置。
LED_Init(); // 調(diào)用 LED_Init() 函數(shù)初始化 LED,準(zhǔn)備控制 LED 燈的狀態(tài)。
USART3_Init(); // 調(diào)用 USART3_Init() 函數(shù)初始化 USART3,配置 USART3 的通信參數(shù)和引腳連接等。
while (1) {
checkReceive(); // 循環(huán)調(diào)用 checkReceive() 函數(shù),用于檢查是否接收到特定命令,并根據(jù)接收到的命令執(zhí)行相應(yīng)的操作。
}
}
注:這里的實戰(zhàn)演練講的不是很清晰,建議移步這篇文章:通過串口中斷的方式進(jìn)行ASR-01S模塊與STM32通信(問題與解決)
總結(jié):
本文大致總結(jié)了串口的發(fā)送和接收。
串口的配置,使用查詢或者中斷來接收數(shù)據(jù)。
串口的使用會很常用到,所以在這里對串口做一個總結(jié),也算是對之前知識的一個回顧和總結(jié),加強(qiáng)印象。文章來源:http://www.zghlxwxcb.cn/news/detail-844660.html
參考鏈接:
https://gitcode.csdn.net/65e6e6b81a836825ed787581.html?dp_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjgwNDA1NSwiZXhwIjoxNzEwNjEwNTI5LCJpYXQiOjE3MTAwMDU3MjksInVzZXJuYW1lIjoid2VpeGluXzUxMDI4NTg0In0.1Eac2jHw_Iz7Nc6l36BNEre9cCLz_gXA_Lz_OQnvtLc文章來源地址http://www.zghlxwxcb.cn/news/detail-844660.html
到了這里,關(guān)于STM32串口通信—串口的接收和發(fā)送詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!