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

这篇具有很好参考价值的文章主要介绍了通过串口中断的方式进行ASR-01S模块与STM32通信(问题与解决)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

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

问题引入

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

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

#define BUFFER_SIZE 100 // 定义缓冲区大小为100

char 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); // 点亮LED
                char *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                    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端口设置
    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 -> PB11
    GPIO_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; // 设置输出速度为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; // 设置为浮空输入
    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的接收中断
    
    // 使能USART3
    USART_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); // 点亮LED
        Usart_SendString(USART3, "消毒灯已开启\n\r"); // 发送消息到串口
    }
				
    if (Check_devices() == 2)
    {
        GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LED
        Usart_SendString(USART3, "消毒灯已关闭\n\r"); // 发送消息到串口
    }

    if (Check_devices() == 3)
    {
        GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 点亮LED
        Usart_SendString(USART3, "通风扇已打开\n\r"); // 发送消息到串口
    }

    if (Check_devices() == 4)
    {
        GPIO_SetBits(GPIOC, GPIO_Pin_13); // 熄灭LED
        Usart_SendString(USART3, "通风扇已关闭\n\r"); // 发送消息到串口
    }
}

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

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

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

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

总结:

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

参考链接:

STM32串口通信—串口的接收和发送详解文章来源地址https://www.toymoban.com/news/detail-855593.html

到了这里,关于通过串口中断的方式进行ASR-01S模块与STM32通信(问题与解决)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32使用三种方式(阻塞、中断、DMA)实现串口发送和接收数据

    记录下学习STM32开发板的心得的和遇见的问题。 板卡型号:STM32F405RGT6 软件:STM32CubeMX、IAR STM32串口外设提供了3种接收和发送方式:阻塞、中断、DMA,主要给大家分享中断方式接收不定长数据和DMA使用空闲中断接收不定长数据。 阻塞发送: 阻塞接收: 两个函数需要注意的就

    2024年02月03日
    浏览(35)
  • Arduino与LU-ASR01语音识别模块的双向串口通信实现

        之前我写了一篇《Arduino的智能语言输入实现》,讨论了Arduino与LU-ASR01之间通过串口通信实现Arduino的中文语音输入,不过那个通信是不完整的,因为LU-ASR01的串口只有一个发送端口TX,而没有接收端口RX。其实在真正的应用中,LU-ASR01通常也需要接收上位机的数据,例如为确

    2024年02月05日
    浏览(236)
  • STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号

    能够点进这篇文章的小伙伴肯定是对STM32串口DMA空闲中断接收数据感兴趣的啦,今天用这一功能实现串口解析航模遥控器sbus信号时,查阅了很多网友发布的文章(勤劳的搬运工~),包括自己之前写过一篇博客 STM32_HAL库_CubeMx串口DMA通信(DMA发送+DMA空闲接收不定长数据)。本文

    2024年02月09日
    浏览(56)
  • 嵌入式开发--STM32用DMA+IDLE中断方式串口接收不定长数据

    之前讲过用 利用IDLE空闲中断来接收不定长数据 ,但是没有用到DMA,其实用DMA会更加的高效,MCU也可以腾出更多的性能去处理应该做的事情。 IDLE顾名思义,就是空闲的意思,即当监测到串口空闲超过1个串口的数据帧时,会使状态寄存器(SR或ISR)的IDLE位置位,如果此时控制

    2024年04月17日
    浏览(54)
  • STM32通过DMA方式实现串口通信

    目录 一、DMA工作原理  二、创建工程项目 三、编写代码 1.在main.c写入以下函数 2.main函数中的while循环中写入以下代码

    2024年02月15日
    浏览(36)
  • STM32F4_HAL库_串口阻塞/中断/DMA三种方式发送数据的配置

    串口阻塞发送的意思就是,发送一段数据,在没有发送完所有数据之前,一直停留在此发送函数(可设定阻塞时间),这个过程中会阻塞别的程序运行; HAL库的配置分为两个层次,一个是HAL库内部调用的、与MCU硬件相关的初始化xxx_MspInit,一个是我们外部调用的初始化xxx_In

    2023年04月25日
    浏览(43)
  • STM32通过串口2使用ESP8266WIFI模块连接新大陆云平台

    目录 使用硬件: 分步骤:配置TCP连接,连接WIFI 1.使用ESP8266的复位引脚进行复位 2.发送基本AT指令 3.连接新大陆 4.新大陆云平台显示在线及上传数据测试成功 5.串口显示 5.涉及的函数 发生AT检测WIFI模块错误,如图,代码运行停留在了.AT,在while中一直循环,没有往下跑了,这种

    2024年04月24日
    浏览(48)
  • STM32 IAP应用开发——通过串口/RS485实现固件升级(方式1)

    什么是IAP? IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。在应用编程(IAP)是用户的应用代码对片内Flash存储器进行擦除/编程的方法。这种方式的典型应用就是用一小段代码来实现程序的下载,实际上单片机的ISP功能就

    2024年02月10日
    浏览(32)
  • STM32 IAP应用开发——通过串口/RS485实现固件升级(方式2)

    什么是IAP? IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。在应用编程(IAP)是用户的应用代码对片内Flash存储器进行擦除/编程的方法。这种方式的典型应用就是用一小段代码来实现程序的下载,实际上单片机的ISP功能就

    2024年02月14日
    浏览(39)
  • STM32(HAL库)驱动SHT30温湿度传感器通过串口进行打印

    目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置  2.1.2 RCC配置 2.2 软件IIC引脚配置 2.3 串口外设配置  2.4 项目生成  3、KEIL端程序整合 3.1 串口重映射 3.2 SHT30驱动添加 3.3 主函数代 3.4 效果展示 本文通过STM32F103C8T6单片机通过HAL库方式对SHT30传感器进行数据的读取,并

    2024年02月16日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包