前言:
最近在做一個(gè)智能家居的項(xiàng)目,需要實(shí)現(xiàn)語音控制的功能,于是我選用了ASR-01S模塊與STM32通信,這個(gè)模塊最大的好處在于有配套的編程軟件和語音庫,不用自己訓(xùn)練且編程簡單(少兒編程的程度)。ASR-01S的代碼架構(gòu)在這不多說,總之在收到語音后它會(huì)通過串口發(fā)送一串命令給STM32,STM32收到后通過串口中斷的方式進(jìn)行一系列操作。但沒想到在這塊看起來很簡單的地方翻車了(太丟人了。。。),經(jīng)過求助之后終于解決了,在這里淺淺記錄一下自己的翻車過程及解決方案。
問題引入
代碼這里偷了一下懶,直接問了GPT,可以直接看我這篇文章:GPT對(duì)話代碼庫——基于STM32F103 1,標(biāo)志位切換模式 & 2,串口的接受和發(fā)送
基礎(chǔ)的就不多說了,主要講一下核心問題,先放一下代碼
#define BUFFER_SIZE 100 // 定義緩沖區(qū)大小為100
char buffer[BUFFER_SIZE]; // 定義一個(gè)緩沖區(qū)數(shù)組用于存儲(chǔ)接收到的數(shù)據(jù)
volatile unsigned int buffer_index = 0; // 聲明一個(gè)用于記錄緩沖區(qū)當(dāng)前索引的變量,使用 volatile 關(guān)鍵字修飾以確保在中斷中的可見性
void USART3_IRQHandler(void) {
// 檢查是否接收到數(shù)據(jù)
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
char data = (char)USART_ReceiveData(USART3); // 讀取接收到的數(shù)據(jù)
// 簡單的字符串終止判斷(例如以換行結(jié)束)
if (data != '\n' && buffer_index < BUFFER_SIZE - 1) { // 如果接收到的數(shù)據(jù)不是換行符且緩沖區(qū)未滿
buffer[buffer_index++] = data; // 將接收到的數(shù)據(jù)存儲(chǔ)到緩沖區(qū)中
} else { // 如果接收到換行符或者緩沖區(qū)已滿
buffer[buffer_index] = '\0'; // 確保字符串結(jié)束,即在緩沖區(qū)末尾添加字符串結(jié)束符'\0'
// 檢查接收到的命令
if (strcmp(buffer, "led on") == 0) { // 如果接收到的命令是"led on"
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 點(diǎn)亮LED
char *msg = "已打開\n"; // 定義提示消息
while (*msg) { // 循環(huán)發(fā)送消息中的每個(gè)字符
USART3_SendChar(*msg++); // 通過串口發(fā)送字符
}
}
// 重置索引,準(zhǔn)備下一次接收
buffer_index = 0; // 重置緩沖區(qū)索引,準(zhǔn)備接收下一條指令
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE); // 清除接收中斷標(biāo)志位
}
}
按照我原本的想法是先通過 USART3 接收中斷判斷是否接收到數(shù)據(jù),然后讀取接收到的數(shù)據(jù)并存儲(chǔ)到緩沖區(qū)中。當(dāng)接收到換行符或者緩沖區(qū)已滿時(shí),將緩沖區(qū)末尾添加字符串結(jié)束符'\0',然后檢查接收到的命令,如果是"led on"則點(diǎn)亮LED,并通過串口發(fā)送提示消息"已打開\n",最后重置緩沖區(qū)索引,準(zhǔn)備接收下一條指令。乍一看沒啥毛病,但發(fā)現(xiàn)雖然buffer[]這個(gè)數(shù)組接收到了來自data的信息,最后經(jīng)過仿真調(diào)試發(fā)現(xiàn)是在代碼實(shí)現(xiàn)的時(shí)候給自己埋了雷。
strcmp這個(gè)函數(shù)用于比較兩個(gè)字符串是否相等,函數(shù)原型:
int strcmp(const char *str1, const char *str2);
strcmp函數(shù)接受兩個(gè)參數(shù),分別是要比較的兩個(gè)字符串 str1
和 str2
。它會(huì)按照字典順序逐個(gè)比較兩個(gè)字符串中的字符,直到遇到不相等的字符或者到達(dá)字符串結(jié)尾(即遇到 '\0'
終止符)。
如果兩個(gè)字符串相等,則返回值為0;如果第一個(gè)字符串小于第二個(gè)字符串,則返回值為負(fù)數(shù);如果第一個(gè)字符串大于第二個(gè)字符串,則返回值為正數(shù)。
GPT在這里是這樣寫的:
if (strcmp(buffer, "led on") == 0) // 如果接收到的命令是"led on"
也就是要求我發(fā)送的字符串完全等于?"led on"才能進(jìn)入,但在串口助手中卻選擇了“發(fā)送新行”這個(gè)選項(xiàng),就相當(dāng)于每次發(fā)送的都是"led on\n",由于strcmp函數(shù)的作用自然不會(huì)讓我們進(jìn)入邏輯,而且這段代碼中并沒有清空緩沖區(qū)數(shù)據(jù)的操作,也就是說就算沒有發(fā)送新行,在第一次發(fā)送結(jié)束之后buffer[]中的數(shù)據(jù)就是“l(fā)ed onled onled on...”這種,所以需要使用memset函數(shù)在每次接收后清空緩沖區(qū)數(shù)據(jù),并且最好將strcmp函數(shù)換成strstr函數(shù),strstr作用和strcmp很相似,但更適合這個(gè)場景,簡單來說使用strstr就是只要buffer[]這個(gè)數(shù)組中出現(xiàn)“l(fā)ed on”就能進(jìn)入邏輯,只要在每次接收完數(shù)據(jù)后清空緩沖區(qū)數(shù)據(jù)就行。下面看我修正優(yōu)化過的正確代碼。
注:
1,strstr
函數(shù)是 C 標(biāo)準(zhǔn)庫中的一個(gè)字符串查找函數(shù),用于在一個(gè)字符串中查找另一個(gè)子字符串的第一次出現(xiàn)位置。其函數(shù)原型為:
char *strstr(const char *haystack, const char *needle);
strstr
函數(shù)接受兩個(gè)參數(shù),分別是要搜索的主字符串 haystack
和要查找的子字符串 needle
。它會(huì)在主字符串中從頭開始逐個(gè)字符地搜索子字符串,直到找到子字符串的第一次出現(xiàn)或者到達(dá)主字符串的結(jié)尾。如果找到子字符串,則返回指向該子字符串在主字符串中第一次出現(xiàn)位置的指針;如果沒有找到子字符串,則返回 NULL
。
2,memset
函數(shù)是 C 標(biāo)準(zhǔn)庫中的一個(gè)函數(shù),用于將一段內(nèi)存塊的內(nèi)容設(shè)置為指定的值。其函數(shù)原型為:
void *memset(void *ptr, int value, size_t num);
memset
函數(shù)接受三個(gè)參數(shù),分別是指向要設(shè)置的內(nèi)存塊的指針 ptr
、要設(shè)置的值 value
、以及要設(shè)置的字節(jié)數(shù) num
。
具體來說,ptr
是要設(shè)置的內(nèi)存塊的起始地址,value
是要設(shè)置的值(通常是一個(gè)字節(jié)的值,即 0 到 255 之間的整數(shù)),num
是要設(shè)置的字節(jié)數(shù)。函數(shù)會(huì)將 ptr
指向的內(nèi)存塊中的前 num
個(gè)字節(jié)的內(nèi)容都設(shè)置為 value
。
代碼講解
串口的頭文件
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
#include <stdio.h>
// 串口3-USART3
#define DEBUG_USARTx USART3
#define BUFFER_SIZE 100
void USART3_Config(u32 BAUD);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);
uint8_t Check_devices(void);
uint8_t Control_devices(void);
#endif /* __USART_H */
USART3配置及其中斷配置
void USART3_Config(u32 BAUD)
{
// GPIO端口設(shè)置
GPIO_InitTypeDef GPIO_InitStructure; // 聲明GPIO初始化結(jié)構(gòu)體變量
USART_InitTypeDef USART_InitStructure; // 聲明USART初始化結(jié)構(gòu)體變量
NVIC_InitTypeDef NVIC_InitStructure; // 聲明中斷向量表初始化結(jié)構(gòu)體變量
// 使能GPIOB時(shí)鐘和USART3時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB時(shí)鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // 使能USART3時(shí)鐘
// USART3 TX -> PB10,RX -> PB11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 配置GPIOB的引腳10(USART3的TX引腳)
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的引腳10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // 配置GPIOB的引腳11(USART3的RX引腳)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 設(shè)置為浮空輸入
GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB的引腳11
// USART3配置
USART_InitStructure.USART_BaudRate = BAUD; // 設(shè)置波特率
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è)置奇偶校驗(yàn)位為無校驗(yàn)
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); // 根據(jù)USART_InitStruct中指定的參數(shù)初始化USARTx寄存器
// 配置USART3中斷
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; // 設(shè)置USART3中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 設(shè)置搶占優(yōu)先級(jí)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 設(shè)置響應(yīng)優(yōu)先級(jí)
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中斷通道
NVIC_Init(&NVIC_InitStructure); // 根據(jù)NVIC_InitStruct中指定的參數(shù)初始化外設(shè)NVIC寄存器
// 使能USART3的接收中斷
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 使能USART3的接收中斷
// 使能USART3
USART_Cmd(USART3, ENABLE); // 使能USART3
}
這個(gè)沒什么好說的,直接復(fù)制就行,注意自己頭文件的引用
串口發(fā)送函數(shù)及printf和scanf的重定向
/***************** 發(fā)送一個(gè)字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 發(fā)送一個(gè)字節(jié)數(shù)據(jù)到USART */
USART_SendData(pUSARTx,ch);
/* 等待發(fā)送數(shù)據(jù)寄存器為空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/***************** 發(fā)送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待發(fā)送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}
/***************** 發(fā)送一個(gè)16位數(shù) **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
uint8_t temp_h, temp_l;
/* 取出高八位 */
temp_h = (ch&0XFF00)>>8;
/* 取出低八位 */
temp_l = ch&0XFF;
/* 發(fā)送高八位 */
USART_SendData(pUSARTx,temp_h);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
/* 發(fā)送低八位 */
USART_SendData(pUSARTx,temp_l);
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
///重定向c庫函數(shù)printf到串口,重定向后可使用printf函數(shù)
int fputc(int ch, FILE *f)
{
/* 發(fā)送一個(gè)字節(jié)數(shù)據(jù)到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待發(fā)送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向c庫函數(shù)scanf到串口,重寫向后可使用scanf、getchar等函數(shù)
int fgetc(FILE *f)
{
/* 等待串口輸入數(shù)據(jù) */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
重頭戲來了,編寫中斷服務(wù)函數(shù)來存放PC端發(fā)送的數(shù)據(jù)
char buffer[BUFFER_SIZE];
// 定義一個(gè)大小為 BUFFER_SIZE 的字符數(shù)組 buffer,用于存儲(chǔ)接收到的數(shù)據(jù)。
volatile unsigned int buffer_index = 0;
// 定義一個(gè)無符號(hào)整數(shù)變量 buffer_index,表示當(dāng)前接收到的數(shù)據(jù)在 buffer 中的索引。
// volatile 修飾表示該變量可能會(huì)被中斷修改,編譯器不會(huì)對(duì)其進(jìn)行優(yōu)化。
uint8_t buffer_ready = 0;
// 定義一個(gè)無符號(hào) 8 位整數(shù)變量 buffer_ready,表示緩沖區(qū)是否準(zhǔn)備好。
// 0 表示未準(zhǔn)備好,1 表示準(zhǔn)備好。
void USART3_IRQHandler(void)
{
// 進(jìn)入 USART3 的中斷服務(wù)程序
// 檢查是否接收到數(shù)據(jù)
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
char data = (char)USART_ReceiveData(USART3); // 讀取接收到的數(shù)據(jù)并存儲(chǔ)在 data 中
buffer[buffer_index++] = data;
// 將接收到的數(shù)據(jù)存儲(chǔ)在 buffer 數(shù)組中,并將 buffer_index 索引遞增
buffer_ready = 1;
// 設(shè)置 buffer_ready 標(biāo)志為 1,表示緩沖區(qū)已經(jīng)準(zhǔn)備好了
if (buffer_index > BUFFER_SIZE - 1)
memset(buffer, 0, 100);
// 如果 buffer_index 大于等于 BUFFER_SIZE - 1,則清空 buffer 數(shù)組中的內(nèi)容,大小為 100 字節(jié)
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
// 清除接收中斷標(biāo)志位,以便下一次接收
}
簡單來說,當(dāng) USART3 接收到數(shù)據(jù)時(shí),將數(shù)據(jù)存儲(chǔ)到 buffer 數(shù)組中,并設(shè)置 buffer_ready 標(biāo)志為 1 表示緩沖區(qū)已經(jīng)準(zhǔn)備好。如果 buffer_index 超出緩沖區(qū)大小,則通過 memset 函數(shù)將 buffer 數(shù)組清空。最后,清除 USART3 的接收中斷標(biāo)志位,以便下一次接收。
編寫串口接收檢查函數(shù)來判斷PC端發(fā)送的數(shù)據(jù),并返回相應(yīng)的返回值
// 串口接收檢查函數(shù)
uint8_t Check_devices(void)
{
if (buffer_ready) // 檢查緩沖區(qū)是否已準(zhǔn)備好
{
buffer_ready = 0; // 清除緩沖區(qū)準(zhǔn)備好的標(biāo)志位
// 判斷接收到的命令
if (strstr((const char*)buffer, "UVC on") != 0)
{
buffer_index = 0; // 重置緩沖區(qū)索引,準(zhǔn)備下一次接收
memset(buffer, 0, 100); // 清空緩沖區(qū)數(shù)據(jù)
return 1; // 返回命令標(biāo)識(shí)
}
// 判斷接收到的命令
if (strstr((const char*)buffer, "UVC off") != 0)
{
buffer_index = 0; // 重置緩沖區(qū)索引,準(zhǔn)備下一次接收
memset(buffer, 0, 100); // 清空緩沖區(qū)數(shù)據(jù)
return 2; // 返回命令標(biāo)識(shí)
}
// 判斷接收到的命令
if (strstr((const char*)buffer, "fan on") != 0)
{
buffer_index = 0; // 重置緩沖區(qū)索引,準(zhǔn)備下一次接收
memset(buffer, 0, 100); // 清空緩沖區(qū)數(shù)據(jù)
return 3; // 返回命令標(biāo)識(shí)
}
// 判斷接收到的命令
if (strstr((const char*)buffer, "fan off") != 0)
{
buffer_index = 0; // 重置緩沖區(qū)索引,準(zhǔn)備下一次接收
memset(buffer, 0, 100); // 清空緩沖區(qū)數(shù)據(jù)
return 4; // 返回命令標(biāo)識(shí)
}
}
}
編寫串口控制設(shè)備函數(shù),并且根據(jù)接收檢查函數(shù)的返回值來執(zhí)行相應(yīng)的邏輯
// 串口控制設(shè)備函數(shù)
uint8_t Control_devices(void)
{
if (Check_devices() == 1)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 點(diǎn)亮LED
Usart_SendString(USART3, "消毒燈已開啟\n\r"); // 發(fā)送消息到串口
}
if (Check_devices() == 2)
{
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄滅LED
Usart_SendString(USART3, "消毒燈已關(guān)閉\n\r"); // 發(fā)送消息到串口
}
if (Check_devices() == 3)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 點(diǎn)亮LED
Usart_SendString(USART3, "通風(fēng)扇已打開\n\r"); // 發(fā)送消息到串口
}
if (Check_devices() == 4)
{
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄滅LED
Usart_SendString(USART3, "通風(fēng)扇已關(guān)閉\n\r"); // 發(fā)送消息到串口
}
}
總的來說,通過上面三段代碼實(shí)現(xiàn)了一個(gè)通過 USART3 接收指令并控制設(shè)備的功能。
首先,通過 USART3 接收中斷函數(shù)
USART3_IRQHandler
接收數(shù)據(jù)并存儲(chǔ)到buffer
緩沖區(qū)中。然后,通過
Check_devices
函數(shù)檢查緩沖區(qū)中是否有指令,根據(jù)指令執(zhí)行相應(yīng)的操作,并通過串口發(fā)送反饋信息。
Control_devices
函數(shù)根據(jù)Check_devices
的返回值執(zhí)行相應(yīng)的設(shè)備控制操作。
總結(jié):
通過這個(gè)問題,我熟悉了使用串口中斷實(shí)現(xiàn)單片機(jī)與STM32之間的高效通信,同時(shí)也加深了對(duì)于串口通信的理解和掌握。希望這篇博客能夠幫助到有類似學(xué)習(xí)目標(biāo)的讀者,也歡迎大家分享自己的經(jīng)驗(yàn)和觀點(diǎn)。文章來源:http://www.zghlxwxcb.cn/news/detail-855593.html
參考鏈接:
STM32串口通信—串口的接收和發(fā)送詳解文章來源地址http://www.zghlxwxcb.cn/news/detail-855593.html
到了這里,關(guān)于通過串口中斷的方式進(jìn)行ASR-01S模塊與STM32通信(問題與解決)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!