通过串口中断的方式进行ASR-01S模块与STM32通信(问题与解决)

前言:

最近在做一个智能家居的项目,需要实现语音控制的功能,于是我选用了ASR-01S模块与STM32通信,这个模块最大的好处在于有配套的编程软件和语音库,不用自己训练且编程简单(少儿编程的程度)。ASR-01S的代码架构在这不多说,总之在收到语音后它会通过串口发送一串命令给STM32,STM32收到后通过串口中断的方式进行一系列操作。但没想到在这块看起来很简单的地方翻车了(太丢人了。。。),经过求助之后终于解决了,在这里浅浅记录一下自己的翻车过程及解决方案。

问题引入

代码这里偷了一下懒,直接问了GPT,可以直接看我这篇文章:GPT对话代码库——基于STM32F103 1,标志位切换模式 & 2,串口的接受和发送

基础的就不多说了,主要讲一下核心问题,先放一下代码

#define BUFFER_SIZE 100 // 定义缓冲区大小为100char buffer[BUFFER_SIZE]; // 定义一个缓冲区数组用于存储接收到的数据
volatile unsigned int buffer_index = 0; // 声明一个用于记录缓冲区当前索引的变量,使用 volatile 关键字修饰以确保在中断中的可见性void USART3_IRQHandler(void) {// 检查是否接收到数据if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {char data = (char)USART_ReceiveData(USART3); // 读取接收到的数据// 简单的字符串终止判断(例如以换行结束)if (data != '\n' && buffer_index < BUFFER_SIZE - 1) { // 如果接收到的数据不是换行符且缓冲区未满buffer[buffer_index++] = data; // 将接收到的数据存储到缓冲区中} else { // 如果接收到换行符或者缓冲区已满buffer[buffer_index] = '\0'; // 确保字符串结束,即在缓冲区末尾添加字符串结束符'\0'// 检查接收到的命令if (strcmp(buffer, "led on") == 0) { // 如果接收到的命令是"led on"GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 点亮LEDchar *msg = "已打开\n"; // 定义提示消息while (*msg) { // 循环发送消息中的每个字符USART3_SendChar(*msg++); // 通过串口发送字符}}// 重置索引,准备下一次接收buffer_index = 0; // 重置缓冲区索引,准备接收下一条指令}USART_ClearITPendingBit(USART3, USART_IT_RXNE); // 清除接收中断标志位}
}

按照我原本的想法是先通过 USART3 接收中断判断是否接收到数据,然后读取接收到的数据并存储到缓冲区中。当接收到换行符或者缓冲区已满时,将缓冲区末尾添加字符串结束符'\0',然后检查接收到的命令,如果是"led on"则点亮LED,并通过串口发送提示消息"已打开\n",最后重置缓冲区索引,准备接收下一条指令。乍一看没啥毛病,但发现虽然buffer[]这个数组接收到了来自data的信息,最后经过仿真调试发现是在代码实现的时候给自己埋了雷。

strcmp这个函数用于比较两个字符串是否相等,函数原型:

int strcmp(const char *str1, const char *str2);

strcmp函数接受两个参数,分别是要比较的两个字符串 str1str2。它会按照字典顺序逐个比较两个字符串中的字符,直到遇到不相等的字符或者到达字符串结尾(即遇到 '\0' 终止符)。

如果两个字符串相等,则返回值为0;如果第一个字符串小于第二个字符串,则返回值为负数;如果第一个字符串大于第二个字符串,则返回值为正数。

GPT在这里是这样写的:

if (strcmp(buffer, "led on") == 0)  // 如果接收到的命令是"led on"

也就是要求我发送的字符串完全等于 "led on"才能进入,但在串口助手中却选择了“发送新行”这个选项,就相当于每次发送的都是"led on\n",由于strcmp函数的作用自然不会让我们进入逻辑,而且这段代码中并没有清空缓冲区数据的操作,也就是说就算没有发送新行,在第一次发送结束之后buffer[]中的数据就是“led onled onled on...”这种,所以需要使用memset函数在每次接收后清空缓冲区数据,并且最好将strcmp函数换成strstr函数,strstr作用和strcmp很相似,但更适合这个场景,简单来说使用strstr就是只要buffer[]这个数组中出现“led on”就能进入逻辑,只要在每次接收完数据后清空缓冲区数据就行。下面看我修正优化过的正确代码。

注:

1,strstr 函数是 C 标准库中的一个字符串查找函数,用于在一个字符串中查找另一个子字符串的第一次出现位置。其函数原型为:

char *strstr(const char *haystack, const char *needle);

strstr 函数接受两个参数,分别是要搜索的主字符串 haystack 和要查找的子字符串 needle。它会在主字符串中从头开始逐个字符地搜索子字符串,直到找到子字符串的第一次出现或者到达主字符串的结尾。如果找到子字符串,则返回指向该子字符串在主字符串中第一次出现位置的指针;如果没有找到子字符串,则返回 NULL

2,memset 函数是 C 标准库中的一个函数,用于将一段内存块的内容设置为指定的值。其函数原型为:

void *memset(void *ptr, int value, size_t num);

memset 函数接受三个参数,分别是指向要设置的内存块的指针 ptr、要设置的值 value、以及要设置的字节数 num

具体来说,ptr 是要设置的内存块的起始地址,value 是要设置的值(通常是一个字节的值,即 0 到 255 之间的整数),num 是要设置的字节数。函数会将 ptr 指向的内存块中的前 num 个字节的内容都设置为 value

代码讲解

串口的头文件

#ifndef __USART_H
#define	__USART_H#include "stm32f10x.h"
#include <stdio.h>// 串口3-USART3
#define  DEBUG_USARTx                   USART3
#define  BUFFER_SIZE                    100void 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端口设置GPIO_InitTypeDef GPIO_InitStructure; // 声明GPIO初始化结构体变量USART_InitTypeDef USART_InitStructure; // 声明USART初始化结构体变量NVIC_InitTypeDef NVIC_InitStructure; // 声明中断向量表初始化结构体变量// 使能GPIOB时钟和USART3时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // 使能USART3时钟// USART3 TX -> PB10,RX -> PB11GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 配置GPIOB的引脚10(USART3的TX引脚)GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置为复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置输出速度为50MHzGPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB的引脚10GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // 配置GPIOB的引脚11(USART3的RX引脚)GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置为浮空输入GPIO_Init(GPIOB, &GPIO_InitStructure); // 初始化GPIOB的引脚11// USART3配置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模式为收发模式USART_Init(USART3, &USART_InitStructure); // 根据USART_InitStruct中指定的参数初始化USARTx寄存器// 配置USART3中断NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; // 设置USART3中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 设置响应优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道NVIC_Init(&NVIC_InitStructure); // 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器// 使能USART3的接收中断USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); // 使能USART3的接收中断// 使能USART3USART_Cmd(USART3, ENABLE); // 使能USART3
}

这个没什么好说的,直接复制就行,注意自己头文件的引用

串口发送函数及printf和scanf的重定向

/*****************  发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{/* 发送一个字节数据到USART */USART_SendData(pUSARTx,ch);/* 等待发送数据寄存器为空 */while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{unsigned int k=0;do {Usart_SendByte( pUSARTx, *(str + k) );k++;} while(*(str + k)!='\0');/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET){}
}/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch&0XFF00)>>8;/* 取出低八位 */temp_l = ch&0XFF;/* 发送高八位 */USART_SendData(pUSARTx,temp_h);	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);/* 发送低八位 */USART_SendData(pUSARTx,temp_l);	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口 */USART_SendData(DEBUG_USARTx, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		return (ch);
}///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(DEBUG_USARTx);
}

重头戏来了,编写中断服务函数来存放PC端发送的数据

char buffer[BUFFER_SIZE];
// 定义一个大小为 BUFFER_SIZE 的字符数组 buffer,用于存储接收到的数据。volatile unsigned int buffer_index = 0;
// 定义一个无符号整数变量 buffer_index,表示当前接收到的数据在 buffer 中的索引。
// volatile 修饰表示该变量可能会被中断修改,编译器不会对其进行优化。uint8_t buffer_ready = 0;
// 定义一个无符号 8 位整数变量 buffer_ready,表示缓冲区是否准备好。
// 0 表示未准备好,1 表示准备好。void USART3_IRQHandler(void) 
{// 进入 USART3 的中断服务程序// 检查是否接收到数据if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {char data = (char)USART_ReceiveData(USART3); // 读取接收到的数据并存储在 data 中buffer[buffer_index++] = data;// 将接收到的数据存储在 buffer 数组中,并将 buffer_index 索引递增buffer_ready = 1;// 设置 buffer_ready 标志为 1,表示缓冲区已经准备好了if (buffer_index > BUFFER_SIZE - 1) memset(buffer, 0, 100);// 如果 buffer_index 大于等于 BUFFER_SIZE - 1,则清空 buffer 数组中的内容,大小为 100 字节}	USART_ClearITPendingBit(USART3, USART_IT_RXNE);// 清除接收中断标志位,以便下一次接收
}

简单来说,当 USART3 接收到数据时,将数据存储到 buffer 数组中,并设置 buffer_ready 标志为 1 表示缓冲区已经准备好。如果 buffer_index 超出缓冲区大小,则通过 memset 函数将 buffer 数组清空。最后,清除 USART3 的接收中断标志位,以便下一次接收。

编写串口接收检查函数来判断PC端发送的数据,并返回相应的返回值

// 串口接收检查函数
uint8_t Check_devices(void)
{if (buffer_ready) // 检查缓冲区是否已准备好{buffer_ready = 0; // 清除缓冲区准备好的标志位// 判断接收到的命令if (strstr((const char*)buffer, "UVC on") != 0) {buffer_index = 0; // 重置缓冲区索引,准备下一次接收memset(buffer, 0, 100); // 清空缓冲区数据return 1; // 返回命令标识}// 判断接收到的命令if (strstr((const char*)buffer, "UVC off") != 0){buffer_index = 0; // 重置缓冲区索引,准备下一次接收memset(buffer, 0, 100); // 清空缓冲区数据return 2; // 返回命令标识}// 判断接收到的命令if (strstr((const char*)buffer, "fan on") != 0){buffer_index = 0; // 重置缓冲区索引,准备下一次接收memset(buffer, 0, 100); // 清空缓冲区数据return 3; // 返回命令标识}// 判断接收到的命令if (strstr((const char*)buffer, "fan off") != 0){buffer_index = 0; // 重置缓冲区索引,准备下一次接收memset(buffer, 0, 100); // 清空缓冲区数据return 4; // 返回命令标识}}
}

编写串口控制设备函数,并且根据接收检查函数的返回值来执行相应的逻辑

// 串口控制设备函数
uint8_t Control_devices(void)
{	if (Check_devices() == 1){		GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 点亮LEDUsart_SendString(USART3, "消毒灯已开启\n\r"); // 发送消息到串口}if (Check_devices() == 2){GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LEDUsart_SendString(USART3, "消毒灯已关闭\n\r"); // 发送消息到串口}if (Check_devices() == 3){GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 点亮LEDUsart_SendString(USART3, "通风扇已打开\n\r"); // 发送消息到串口}if (Check_devices() == 4){GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LEDUsart_SendString(USART3, "通风扇已关闭\n\r"); // 发送消息到串口}
}

总的来说,通过上面三段代码实现了一个通过 USART3 接收指令并控制设备的功能。

首先,通过 USART3 接收中断函数 USART3_IRQHandler 接收数据并存储到 buffer 缓冲区中。

然后,通过 Check_devices 函数检查缓冲区中是否有指令,根据指令执行相应的操作,并通过串口发送反馈信息。

Control_devices 函数根据 Check_devices 的返回值执行相应的设备控制操作。

总结:

通过这个问题,我熟悉了使用串口中断实现单片机与STM32之间的高效通信,同时也加深了对于串口通信的理解和掌握。希望这篇博客能够帮助到有类似学习目标的读者,也欢迎大家分享自己的经验和观点。

参考链接:

STM32串口通信—串口的接收和发送详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/751771.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LEETCODE LCS 03. 主题空间

题目描述如上&#xff0c;这个题主要运用了DFS的思想&#xff0c;同时走过的路径标记为6&#xff0c;即可在后续的遍历中过滤掉重复的元素&#xff0c;其他则类似边界条件的判断和题目条件的判断&#xff0c;求最大值&#xff0c;只需要一次遍历中累加对比每一次得即可。 模板&…

数据结构与算法-树-二分搜索树(一)

二分搜索树 今天我们尝试构建一颗二分搜索树&#xff0c;很多同学只有理论&#xff0c;并没有对树有其编码实践。通过一步步的实现一颗二分搜索树&#xff0c;加深对数据结构树的理解。 二分搜索树&#xff0c;又名二分排序树&#xff0c;有人也叫它二分查找树。 特点 二分搜索…

最强AI换脸工具Rope使用教程,Rope整合包下载【全网最全安装步骤】

Rope的汉化整合包&#xff08;包含模型&#xff09;以及下面教程所涉及到的所有安装包我都打包好了&#xff0c;需要的小伙伴可以关注文章底部公众号&#xff0c;回复关键词【rope】获取。 AI换脸软件简介必读 Rope 是一个免费开源的 AI 换脸软件&#xff0c;它具有图形化界面…

Centos启用rc-local服务

Centos启用rc-local服务 简介问题分析解决方法启动并查看状态 简介 大多时候我们可以在/etc/rc.d/rc.local中写一些命令来实现随着服务器的启动(重启)运行自己的程序或服务&#xff0c;但是配置后无法启动&#xff0c;查看了下rc-local.service服务状态显示未启动。 [rootmas…

[ROS 系列学习教程] rosbag Python API

ROS 系列学习教程(总目录) 本文目录 1. 构造函数与关闭文件2. 属性值3. 写bag文件内容4. 读bag文件内容5. 将bag文件缓存写入磁盘6. 重建 bag 文件索引7. 获取bag文件的压缩信息8. 获取bag文件的消息数量9. 获取bag文件记录的起止时间10. 获取话题信息与消息类型 rosbag 的 Pyt…

如何创建用户流(User Flow):分步指南

原文作者&#xff1a;Camren Browne&#xff0c;CareerFoundry 翻译&#xff1a;数字营销工兵 (sources: 图片来源于网络&#xff09; 用户流(User Flow)是当今用户体验行业中最有用但被误解的工具之一。资深设计师经常避开它们&#xff0c;而初级设计师则很难抓住它们。 事…

炸裂!全球首个AI程序员!

近年来&#xff0c;人工智能&#xff08;AI&#xff09;在多个领域取得了显著进展&#xff0c;不断拓展其能力边界。一个引人注目的突破是全球首个AI程序员——Devin的诞生。 这一创新不仅展示了AI技术的快速进步&#xff0c;而且对软件开发领域和未来的工作场景产生了深远的影…

关于ffmpeg height not divisible by 2的错误

在我们线上视频生产过程中&#xff0c;我们用ffmpeg对视频做了resize&#xff0c;讲原有的分辨率resize到1280p&#xff0c;使用了参数 -vf "scale1280:-1"&#xff0c;作用是将原始视频宽度缩放成1280&#xff0c;-1是指高度等比例缩放。 之前一直运行的好好的&…

网络基础知识-操作系统作用+进程管理-嵌入式系统设计师备考笔记

0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记&#xff0c;未经本人许可&#xff0c;请勿转载&#xff0c;如发现本笔记内容的错误还望各位不吝赐教&#xff08;笔记内容可能有误怕产生错误引导&#xff09;。 本章的主要内容见下图&#xff1a; 本章知识和计算机…

Twitter代运营服务商哪家好?CloudNEO为您提供全链解决方案

在当今社交媒体盛行的时代&#xff0c;Twitter作为全球最知名的社交平台之一&#xff0c;已成为企业推广品牌、吸引客户和增加曝光的重要渠道。然而&#xff0c;如何有效地利用Twitter进行品牌推广和营销&#xff0c;成为许多企业面临的挑战。在这个背景下&#xff0c;选择一家…

ES6:可迭代对象(Iterable object)

一、概念 可迭代对象是数组的泛化&#xff0c;是定义了内置迭代器方法 Symbol.iterator 的对象。是可以在for..of 循环中使用的对象。 二、迭代器&#xff08;iterator&#xff09; 为了让对象可以迭代&#xff0c;我们需要给对象添加一个迭代器--Symbol.iterator。 迭代器…

HarmonyOS(鸿蒙)不再适合JS语言开发

ArkTS是鸿蒙生态的应用开发语言。它在保持TypeScript&#xff08;简称TS&#xff09;基本语法风格的基础上&#xff0c;对TS的动态类型特性施加更严格的约束&#xff0c;引入静态类型。同时&#xff0c;提供了声明式UI、状态管理等相应的能力&#xff0c;让开发者可以以更简洁、…

OpenvSwitch VXLAN 隧道实验

OpenvSwitch VXLAN 隧道实验 最近在了解 openstack 网络&#xff0c;下面基于ubuntu虚拟机安装OpenvSwitch&#xff0c;测试vxlan的基本配置。 节点信息&#xff1a; 主机名IP地址OS网卡node1192.168.95.11Ubuntu 22.04ens33node2192.168.95.12Ubuntu 22.04ens33 网卡信息&…

通过键盘对机械臂进行操作

1 #include<myhead.h>2 #include<linux/input.h>3 #define SER_PORT 88884 #define SER_IP "192.168.116.225"5 #define CLI_PORT 99996 #define CLI_IP "192.168.65.129"7 int main(int argc, const char *argv[])8 {9 //1、创建用于连接…

28.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-数据推测结果用提示框的形式显示

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;27.数据推测功能…

Pytorch NLP入门3:用嵌入表示单词

初次编辑时间&#xff1a;2024/3/17&#xff1b;最后编辑时间&#xff1a;2024/3/17 本栏目链接&#xff1a;https://blog.csdn.net/qq_33345365/category_12597850.html 本人的其他栏目&#xff1a; pytorch 基础的栏目链接&#xff1a;https://blog.csdn.net/qq_33345365/…

便利店小程序有哪些功能

​便利店小程序为附近的住户提供小程序在线购物的服务。用户只需要打开小程序&#xff0c;就可以购买需要的商品&#xff0c;可以选择自取或者配送。整个过程非常简单快速。下面具体介绍便利店小程序的功能。 1. **商品展示**&#xff1a;展示便利店的商品信息&#xff0c;包括…

快速幂算法详解

一、引言 快速幂算法是数学和计算机科学中用于大数幂运算的一种高效算法。它采用了二进制分治策略&#xff0c;将幂运算分解为更小的部分&#xff0c;从而显著降低运算的时间和空间复杂度。在处理大规模数幂运算、加密算法和某些数学问题时&#xff0c;快速幂算法显示出其卓越…

贪心算法题解

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;这篇文章将给大家介绍贪心算法和贪心算法题目的练习和解析&#xff0c;贪心算法的本质就是每一个阶段都是局部最优&#xff0c;从而实现全局最优。我们在做题的同时&#xff0c;不仅要把题目做出来&#xff0c;还要有严格的证…

9 个顶级免费视频压缩软件精选

视频有多种格式、不同的大小和不同的压缩级别。但是&#xff0c;您可以使用最好的视频压缩器来&#xff1a; 减小文件大小提高压缩质量更好地服务您的观众 我们将列出九个领先的视频压缩软件精选&#xff0c;您今天可以免费使用它们来增强您的视频。 9 个顶级免费视频压缩器精…