文章目录
- 需求
- 一、SU03T语音识别模块
- 二、模块配置流程
- 1.固件烧录
- 2.配置串口和传输引脚
- 3.中断函数
- 4.double类型转换
- 5 数据发送
- 6.接收处理
- 三、该模块完整代码
- 总结
需求
基于上次完成空气质量传感器,利用SU03T语音识别模块,实现空气质量的语音问答播报。
一、SU03T语音识别模块
该模块是固件是通过智能公元设计的。
硬件层和协议层以及识别的语音指令也是由固件决定的。
所以使用前需要去该固件的官网进行设计烧录(www.smartpi.cn)
二、模块配置流程
思路:
1.硬件先检测语音指令,将语音指令发送给串口5。(AA 55 XX 55 AA)
2.串口5以10位为一组存到数组中。
3.校验指令是否正确(头尾固定)。
4.提取得到的指令数组中的第三位,进行判断是哪一个指令。
5.根据接收到的指令和外部声明的空气质量数据传输到指令拼接函数中。
6. 制作指令拼接函数,其中要将空气质量的double数据转换为8位16进制的。AA 55 04 00 00 00 00 00 80 37 40 55 AA
7. 将拼接好的数据发送给SU03T语音识别模块,进行播报。
1.固件烧录
1.先去智能公元的官网设计所需的语音指令和接口,本例程选取的以串口5 PC12(TX)和PD2(RX)进行数据的通信。设计完成后会生成一个bin二进制文件。
2.清空板子上的代码,然后利用板子上的ch340进行烧录。
具体步骤官方也有详细介绍文档,一步一步来就可以。
2.配置串口和传输引脚
该模块的协议层为:
波特率:9600
数据位8位
校验位 0 位
停止位1位
代码如下:
void Su03t_U5Config()//串口5 PC12(TX) PD2(RX)
{
//开时钟U5 PD12(TX) PD2(RX)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);
//配置PC12(TX)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推完输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
//PD2(RX)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStruct);
//配置串口5 波特率9600 数据位8,校验位0,停止位1
USART_InitTypeDef USART_InitStruct = {0};//可以通过结构体类型跳转
USART_InitStruct.USART_BaudRate = 9600;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流不开
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//打开接收
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(UART5,&USART_InitStruct);
//使能串口
USART_Cmd(UART5,ENABLE);
//配置串口5的中断(采用中断接收)
USART_ITConfig(UART5,USART_IT_RXNE,ENABLE);//使能串口5 的接收非空中断
USART_ITConfig(UART5,USART_IT_IDLE,ENABLE);//总线空闲中断
NVIC_SetPriority(UART5_IRQn,6);//设置优先级0~15
NVIC_EnableIRQ(UART5_IRQn);//使能中断通道
}
3.中断函数
首先,先创建一个结构体方便之后操作。
里面包含需要发送的10位数组、数组下标和数据类型。
typedef struct{
uint8_t u5_recv[10];//保存数据数组
uint8_t u5_cnt;//数组下标
uint8_t u5_tflag;
}UART5DATA;//数据类型
然后依旧是去启动文件中找到然后复制。
代码如下:
UART5DATA u5_data={0};
void UART5_IRQHandler()//串口5中断执行
{
uint8_t data=0;
//判断接收中断是否发生
if(USART_GetITStatus(UART5,USART_IT_RXNE)==SET)
{
data = UART5->DR;
//USART1->DR = data;//回显
u5_data.u5_recv[u5_data.u5_cnt]=data;
u5_data.u5_cnt++;
u5_data.u5_cnt%=10;
}
//触发空闲中断,表示总线空闲,接收完毕
if(USART_GetITStatus(UART5,USART_IT_IDLE)==SET)
{
data = UART5->SR;//清理空闲中断,先读SR再读DR
data = UART5->DR;
u5_data.u5_tflag=1;
}
}
该处串口5的中断用来接收数据,并把数据传送给数组里,每十个为一组。
空闲中断用来进行标志位置1,方便之后的程序进行判断。
u5_data.u5_tflag为1时,代表一组数据传输完成。
4.double类型转换
由于该串口接收的空气质量数据为double类型,而传输类型为8字节16进制,所以此时需要进行数据转换。
//将double数据转换成8位类型存放到数组arr中
void DoubleToUint8(double data,uint8_t *arr)
{
uint8_t *p = (uint8_t *)&data;
uint8_t i=0;
for(i=0;i<8;i++)
{
arr[i]=p[i];
printf("%02x ",*(p+i));
}
printf("\r\n");
return;
}
5 数据发送
根据该语音模块要求进行数据拼接
//拼接指令函数 AA 55 04 00 00 00 00 00 80 37 40 55 AA
void Su03tSendMsg(uint8_t cmd,double data)
{
uint8_t msg[13]={0};//存放要发送的指令
msg[0]=0xAA;
msg[1]=0x55;
msg[2]=cmd;
DoubleToUint8(data,&msg[3]);
msg[11]=0x55;
msg[12]=0xAA;
//通过串口5发送
U5_Sendarr(msg,13);
}
cmd为指令,data为数据。
不同的指令对应不同的语音播报数据
要将整合的数据进行发送,此时还需要构造两个串口5的发送函数
//串口5发送单字节函数
void Uart5Senddata(uint8_t data)
{
//等待发送完成
while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==0);
//如果上次发送完成,就发送
USART_SendData(UART5,data);
}
//串口5发送数组函数
void U5_Sendarr(uint8_t * data,uint32_t len)
{
uint32_t i=0;
for(i=0;i<len;i++){
Uart5Senddata(*data);
data++;
}
}
6.接收处理
将kqm处理后的数据进行外部声明,再判断头尾的固定值,查看是否有误,然后进行数据类型标志位判断,判断收到的指令要播报那个数据,最后记得释放结构体,防止重复播报。
extern float voc;
extern float ch2o;
extern float co2;
int Su03tDealData()
{
if(u5_data.u5_tflag!=1)
{
return 1;
}
if(u5_data.u5_recv[0]!=0xAA||u5_data.u5_recv[1]!=0x55)
{
printf("数据帧头出错\r\n");
return 2;
}
if(u5_data.u5_recv[4]!=0xAA||u5_data.u5_recv[3]!=0x55)
{
printf("数据帧尾出错\r\n");
return 3;
}
switch(u5_data.u5_recv[2])
{
case 1:printf("接收01,空气质量指令\r\n");
Su03tSendMsg(1,voc);//voc
break;
case 2: printf(" 接收02,甲醛指令\r\n");
Su03tSendMsg(2,ch2o);//voc
break;
case 3: printf(" 接收03,Co2指令\r\n");
Su03tSendMsg(3,co2);//voc
break;
}
memset(&u5_data,0,sizeof(u5_data));
return 0;
}
完成后在主函数中调用 :
Kqm_U4Config();//空气质量检测
Su03t_U5Config();//语音播报
在while(1)中调用:
KQM_DealData();//空气质量处理
Su03tDealData();
即可实现
三、该模块完整代码
su03t.c
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
#include "su03t.h"
typedef struct{
uint8_t u5_recv[10];//保存数据数组
uint8_t u5_cnt;//数组下标
uint8_t u5_tflag;
}UART5DATA;//数据类型
void Su03t_U5Config()//串口5 PC12(TX) PD2(RX)
{
//开时钟U5 PD12(TX) PD2(RX)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);
//配置PC12(TX)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推完输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStruct);
//PD2(RX)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStruct);
//配置串口5 波特率9600 数据位8,校验位0,停止位1
USART_InitTypeDef USART_InitStruct = {0};//可以通过结构体类型跳转
USART_InitStruct.USART_BaudRate = 9600;//波特率
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件控制流不开
USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;//打开接收
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(UART5,&USART_InitStruct);
//使能串口
USART_Cmd(UART5,ENABLE);
//配置串口4的中断(采用中断接收)
USART_ITConfig(UART5,USART_IT_RXNE,ENABLE);//使能串口4 的接收非空中断
USART_ITConfig(UART5,USART_IT_IDLE,ENABLE);//总线空闲中断
NVIC_SetPriority(UART5_IRQn,6);//设置优先级0~15
NVIC_EnableIRQ(UART5_IRQn);//使能中断通道
}
//串口5发送单字节函数
void Uart5Senddata(uint8_t data)
{
//等待发送完成
while(USART_GetFlagStatus(UART5,USART_FLAG_TC)==0);
//如果上次发送完成,就发送
USART_SendData(UART5,data);
}
//串口5发送数组函数
void U5_Sendarr(uint8_t * data,uint32_t len)
{
uint32_t i=0;
for(i=0;i<len;i++){
Uart5Senddata(*data);
data++;
}
}
UART5DATA u5_data={0};
void UART5_IRQHandler()//串口5中断执行
{
uint8_t data=0;
//判断接收中断是否发生
if(USART_GetITStatus(UART5,USART_IT_RXNE)==SET)
{
data = UART5->DR;
//USART1->DR = data;//回显
u5_data.u5_recv[u5_data.u5_cnt]=data;
u5_data.u5_cnt++;
u5_data.u5_cnt%=10;
}
//触发空闲中断,表示总线空闲,接收完毕
if(USART_GetITStatus(UART5,USART_IT_IDLE)==SET)
{
data = UART5->SR;//清理空闲中断,先读SR再读DR
data = UART5->DR;
u5_data.u5_tflag=1;
}
}
//将double转换成8位类型数组arr
void DoubleToUint8(double data,uint8_t *arr)
{
uint8_t *p = (uint8_t *)&data;
uint8_t i=0;
for(i=0;i<8;i++)
{
arr[i]=p[i];
printf("%02x ",*(p+i));
}
printf("\r\n");
return;
}
extern float voc;
extern float ch2o;
extern float co2;
int Su03tDealData()
{
if(u5_data.u5_tflag!=1)
{
return 1;
}
if(u5_data.u5_recv[0]!=0xAA||u5_data.u5_recv[1]!=0x55)
{
printf("数据帧头出错\r\n");
return 2;
}
if(u5_data.u5_recv[4]!=0xAA||u5_data.u5_recv[3]!=0x55)
{
printf("数据帧尾出错\r\n");
return 3;
}
switch(u5_data.u5_recv[2])
{
case 1:printf("接收01,空气质量指令\r\n");
Su03tSendMsg(1,voc);//voc
break;
case 2: printf(" 接收02,甲醛指令\r\n");
Su03tSendMsg(2,ch2o);//voc
break;
case 3: printf(" 接收03,Co2指令\r\n");
Su03tSendMsg(3,co2);//voc
break;
}
memset(&u5_data,0,sizeof(u5_data));
return 0;
}
//拼接指令函数 AA 55 04 00 00 00 00 00 80 37 40 55 AA
void Su03tSendMsg(uint8_t cmd,double data)
{
uint8_t msg[13]={0};//存放要发送的指令
msg[0]=0xAA;
msg[1]=0x55;
msg[2]=cmd;
DoubleToUint8(data,&msg[3]);
msg[11]=0x55;
msg[12]=0xAA;
//通过串口5发送
U5_Sendarr(msg,13);
}
su03t.h
#ifndef _SU03T_H_
#define _SU03T_H_
#include "stm32f10x.h"
void Su03t_U5Config();
void Uart5Senddata(uint8_t data);
void U5_Sendarr(uint8_t * data,uint32_t len);
void UART5_IRQHandler();
void DoubleToUint8(double data,uint8_t *arr);
int Su03tDealData();
void Su03tSendMsg(uint8_t cmd,double data);
#endif
总结
1.学会了如何配置半成品模块。
2.学会了如何根据需求进行数据处理和类型转换。