Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
ART-Pi
ld3320
RT-Thread实战笔记|LD3320非限定词条语音控制器使用详解
发布于 2022-01-21 12:59:18 浏览:1158
订阅该版
[tocm] # 前言 物联网设计这么火了,这么能离得开语音控制,搭载上国产物联网操作系统RT-Thread,本期跟小飞哥一起学习RT-Thread SPI设备驱动的使用,如何在RT-Thread系统上操作LD3320。 # SPI通讯协议介绍 注:此部分摘自rt-thread官网 SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。SPI 一般使用 4 根线通信,如下图所示: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/bb5b1cff4f3b71b0cee3909b26fa5f19.png) - MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。 - MISO –主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。 - SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备。 - CS –从设备选择线 (Chip select)。也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备。 SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。 如下图所示芯片有 2 个 SPI 控制器,SPI 控制器对应 SPI 主设备,每个 SPI 控制器可以连接多个 SPI 从设备。挂载在同一个 SPI 控制器上的从设备共享 3 个信号引脚:SCK、MISO、MOSI,但每个从设备的 CS 引脚是独立的。 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/7281faf63f45caf0ef9d9775e94651f4.png) 从设备的时钟由主设备通过 SCLK 提供,MOSI、MISO 则基于此脉冲完成数据传输。SPI 的工作时序模式由 CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)之间的相位关系决定,CPOL 表示时钟信号的初始电平的状态,CPOL 为 0 表示时钟信号初始状态为低电平,为 1 表示时钟信号的初始电平是高电平。CPHA 表示在哪个时钟沿采样数据,CPHA 为 0 表示在首个时钟变化沿采样数据,而 CPHA 为 1 则表示在第二个时钟变化沿采样数据。 根据 CPOL 和 CPHA 的不同组合共有 4 种工作时序模式: - CPOL=0,CPHA=0 - CPOL=0,CPHA=1 - CPOL=1,CPHA=0 - CPOL=1,CPHA=1。如下图所示: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/fcc23d81f83f6a8ee737667f7d163d06.png) # RT-Thread SPI 设备驱动使用 ## 1、挂载SPI设备 函数原型: ```java rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, const char *name, const char *bus_name, void *user_data) ``` | 参数 | 描述 | | :--------- | :--: | -----------: | | device | SPI设备名称 | | name | SPI设备名称 | | bus_name | SPI总线名称 | | user_data | 用户数据指针 | | 返回 | -- | | busRET_EOK_name | 成功 | | 其他错误码 | 失败 | 此函数主要是用来挂载一个 SPI 设备到指定的 SPI 总线,并向内核注册 SPI 设备,并将 user_data 保存到 SPI 设备的控制块里。 一般 SPI 总线命名原则为 spix, SPI 设备命名原则为 spixy ,如 spi10 表示挂载在 spi1 总线上的 0 号设备。user_data 一般为 SPI 设备的 CS 引脚指针,进行数据传输时 SPI 控制器会操作此引脚进行片选。 光说不练假把式,结合我们本次要操作的LD3320驱动来看看这个函数如何使用 我们以SPI4为例,CS引脚选择PE4 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/e6940d4a16410b2eb3dcc401478f8742.png.webp) ![image.png](https://oss-club.rt-thread.org/uploads/20220121/1a1629e8919fe1a567ddfae82758a83a.png) ## 配置SPI 主要涉及SPI通讯模式,最大通讯速率,数据模式等参数,具体怎么配置看从机是什么模式,讲究个主从匹配。 挂载 SPI 设备到 SPI 总线后需要配置 SPI 设备的传输参数。 函数原型: ```java rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) ``` | 参数 | 描述 | | :--------- | :--: | -----------: | | device | SPI 设备句柄 | | cfg | SPI 配置参数指针 | | 返回 | --- | | RT_EOK | 成功 | 这部分就类似于我们的cubemx中关于SPI的配置,参数在struct rt_spi_configuration 结构体中原型如下: ``` java struct rt_spi_configuration { rt_uint8_t mode; /* 模式 */ rt_uint8_t data_width; /* 数据宽度,可取8位、16位、32位 */ rt_uint16_t reserved; /* 保留 */ rt_uint32_t max_hz; /* 最大频率 */ }; ``` 模式: 包含 MSB/LSB、主从模式、 时序模式等,可取宏组合如下: ```java /* 设置数据传输顺序是MSB位在前还是LSB位在前 */ #define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */ #define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */ /* 设置SPI的主从模式 */ #define RT_SPI_MASTER (0<<3) /* SPI master device */ #define RT_SPI_SLAVE (1<<3) /* SPI slave device */ /* 设置时钟极性和时钟相位 */ #define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */ #define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */ #define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */ #define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */ #define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */ #define RT_SPI_NO_CS (1<<5) /* No chipselect */ #define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */ #define RT_SPI_READY (1<<7) /* Slave pulls low to pause */ ``` - 数据宽度: 根据 SPI 主设备及 SPI 从设备可发送及接收的数据宽度格式设置为8位、16位或者32位。 - 最大频率: 设置数据传输的波特率,同样根据 SPI 主设备及 SPI 从设备工作的波特率范围设置。 配置示例如下所示(具体根据设备要求): ```java struct rt_spi_configuration cfg; cfg.data_width = 8; cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; cfg.max_hz = 20 * 1000 *1000; /* 20M */ rt_spi_configure(spi_dev, &cfg); ``` ## 访问 SPI 设备 一般情况下 MCU 的 SPI 器件都是作为主机和从机通讯,在 RT-Thread 中将 SPI 主机虚拟为 SPI 总线设备,应用程序使用 SPI 设备管理接口来访问 SPI 从机器件,主要接口如下所示: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/1a7c48aae03771d3e3f9bef211cfc6b4.png.webp) 发送、接收函数有好几个,但基础函数是send和receive两个,就不再占用篇幅,一一介绍了,后面会主要看下rt_spi_transfer_message() 自定义数据传输函数,一定要注意,SPI相关的函数不要再中断中调用 - 查找 SPI 设备 和前面章节:IIC设备驱动详解 一样,SPI也是要先查找SPI设备: rt_device_t rt_device_find(const char* name); | 参数 | 描述 | | :--------- | :--: | -----------: | | name | 设备名称 | | 返回 | --- | | 设备句柄 | 查找到对应设备将返回相应的设备句柄 | | RT_NULL | 没有找到相应的设备对象 | 结合LD3320,定义SPI名称为SPI4,看代码: ```java struct rt_spi_device *spi_dev_ld3320; spi_dev_ld3320 = (struct rt_spi_device *)rt_device_find(name); if (!spi_dev_ld3320) { rt_kprintf("spi sample run failed! can't find %s device!\n", name); } else { rt_kprintf("spi sample run success!find %s device!\n", name); } ``` ## 自定义传输数据 ```java struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,struct rt_spi_message *message); ``` ![image.png](https://oss-club.rt-thread.org/uploads/20220121/168ee898e6daccc2d8e167ce01850404.png) 此函数可以传输一连串消息,用户可以自定义每个待传输的 message 结构体各参数的数值,从而可以很方便的控制数据传输方式。struct rt_spi_message 原型如下: ```java struct rt_spi_message { const void *send_buf; /* 发送缓冲区指针 */ void *recv_buf; /* 接收缓冲区指针 */ rt_size_t length; /* 发送 / 接收 数据字节数 */ struct rt_spi_message *next; /* 指向继续发送的下一条消息的指针 */ unsigned cs_take : 1; /* 片选选中 */ unsigned cs_release : 1; /* 释放片选 */ }; ``` sendbuf 为发送缓冲区指针,其值为 RT_NULL 时,表示本次传输为只接收状态,不需要发送数据。 recvbuf 为接收缓冲区指针,其值为 RT_NULL 时,表示本次传输为只发送状态,不需要保存接收到的数据,所以收到的数据直接丢弃。 length 的单位为 word,即数据长度为 8 位时,每个 length 占用 1 个字节;当数据长度为 16 位时,每个 length 占用 2 个字节。 参数 next 是指向继续发送的下一条消息的指针,若只发送一条消息,则此指针值为 RT_NULL。多个待传输的消息通过 next 指针以单向链表的形式连接在一起。 cs_take 值为 1 时,表示在传输数据前,设置对应的 CS 为有效状态。cs_release 值为 1 时,表示在数据传输结束后,释放对应的 CS。 注:* 当 send_buf 或 recv_buf 不为空时,两者的可用空间都不得小于 length。 * 若使用此函数传输消息,传输的第一条消息 cs_take 需置为 1,设置片选为有效,最后一条消息的 cs_release 需置 1,释放片选。 具体如何使用,在后面详细介绍 SPI的原理以及一些基本的函数、参数介绍就这样了,小伙伴们也可以去rt-thread官网查看 # LD3320简介 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/c9c10a6cd30eff96c7d467e3699b45dc.png.webp) 某宝买的,价格挺贵,功能也挺多,语音播放、录放音、语音识别等等,关于LD3320的资料介绍实在是太多了,下面主要介绍几点: ## 芯片介绍 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/95ad867fa14b98e5e2fcd7d03f66f820.png.webp) ## 主要特色功能 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/d7d0fcdf9e37b4d1d9673a68b537bfc2.png.webp) ## 主要技术参数 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/21e0f8f2847a02d75b75f2ea2ece4e09.png.webp) ## 模块原理图 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/16f385d1de517db5b940070d20e72195.png.webp) ## 串行驱动时序 标准的SPI协议,看一下即可... 写时序: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/d9b05befef89921379042d574617dec7.png.webp) 读时序: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/857ffb0a15b00f5282c388c6d8fe6461.png.webp) # 代码编写 硬件连接: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/3f67a1aca66b27a389d31544f56d44d5.png.webp) rt studio配置: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/3e8eb12ce922371a30439595d657b585.png) 先来对WR、RST、IRQ引脚进行定义 ```java #define LD3320_WR GET_PIN(H, 2) #define LD3320_RST GET_PIN(H, 3) #define LD3320_IRQ GET_PIN(B, 0) #define LD3320_CS GET_PIN(E, 4) #define LD3320_WR_Func(x) x ? rt_pin_write(LD3320_WR, PIN_HIGH) : rt_pin_write(LD3320_WR, PIN_LOW) #define LD3320_RST_Func(x) x ? rt_pin_write(LD3320_RST, PIN_HIGH) : rt_pin_write(LD3320_RST, PIN_LOW) #define LD3320_CS_Func(x) x ? rt_pin_write(LD3320_CS, PIN_HIGH) : rt_pin_write(LD3320_CS, PIN_LOW) ``` IRQ需要注册中断: ```java void rt_ld3320_irq_pin_init() { rt_pin_mode(LD3320_IRQ, PIN_MODE_INPUT_PULLUP); rt_pin_attach_irq(LD3320_IRQ, PIN_IRQ_MODE_FALLING, rt_ld3320_isr, RT_NULL); rt_pin_irq_enable(LD3320_IRQ, PIN_IRQ_ENABLE); } ``` SPI设备注册初始化: ``` java void rt_ld3320_init(int argc, char *argv[]) { char name[RT_NAME_MAX]; if (argc == 2) { rt_strncpy(name, argv[1], RT_NAME_MAX); } else { rt_strncpy(name, LD3320_SPI_DEVICE_NAME, RT_NAME_MAX); } rt_hw_ld3320_init(); spi_dev_ld3320 = (struct rt_spi_device *)rt_device_find(name); if (!spi_dev_ld3320) { rt_kprintf("spi sample run failed! can't find %s device!\n", name); } else { rt_kprintf("spi sample run success!find %s device!\n", name); } struct rt_spi_configuration cfg; cfg.data_width = 8; cfg.mode = RT_SPI_MSB | RT_SPI_MASTER | RT_SPI_MODE_2; cfg.max_hz = 6 * 1000 * 100; /* 6M */ if (rt_spi_configure(spi_dev_ld3320, &cfg) == RT_ERROR) { rt_kprintf("spi4 config error\r\n"); } rt_kprintf("spi4 config success\r\n"); rt_LD_init(); } ``` 老规矩。不出意外的话,SPI初始化OK的,来看: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/1ac4eb007f1c381f4bde4f412b855e9e.png.webp) ![image.png](https://oss-club.rt-thread.org/uploads/20220121/eb5d14397f98e1a3d96c6147ad0a48b6.png) 接下来编写读写寄存器的基本函数: 写寄存器: 结合上面对自定义数据传输函数的介绍,使用自定义数据实现SPI数据传输 ```java /******************************************************************************** function: Write data to LD3320 reg ********************************************************************************/ static void rt_LD_WriteReg(rt_uint8_t data1, rt_uint8_t data2) { rt_uint8_t buf[3]; struct rt_spi_message msg; LD3320_WR_Func(0); buf[0] = 0x04; buf[1] = data1; buf[2] = data2; msg.send_buf = buf; msg.recv_buf = RT_NULL; msg.length = 3; msg.cs_take = 1; msg.cs_release = 1; msg.next = RT_NULL; rt_spi_transfer_message(spi_dev_ld3320, &msg); } ``` 读取寄存器: ```java /******************************************************************************** function: Read data from LD3320 reg ********************************************************************************/ static rt_uint8_t rt_LD_ReadReg(rt_uint8_t reg_addr) { rt_uint8_t ret_byte; LD3320_WR_Func(0); struct rt_spi_message msg1, msg2; rt_uint8_t buf[3]; buf[0] = 0x05; buf[1] = reg_addr; buf[2] = 0x00; msg1.send_buf = buf; msg1.recv_buf = RT_NULL; msg1.length = 2; msg1.cs_take = 1; msg1.cs_release = 0; msg1.next = &msg2; msg2.send_buf = RT_NULL; msg2.recv_buf = &ret_byte; msg2.length = 1; msg2.cs_take = 0; msg2.cs_release = 1; msg2.next = RT_NULL; rt_spi_transfer_message(spi_dev_ld3320, &msg1); return (ret_byte); } ``` 关于语音播放,录放音的本章先不做介绍,重点看看语音识别如何实现: 模块在开始工作之前,要先对其进行复位操作,就是对芯片的第 47 腿( RSTB*)发送低电平,然后需要对片选 CS 做一次 拉低→拉高的操作,以激活内部 DSP。可按照以下顺序: ```java /******************************************************************************** function: LD3320 hardware init ********************************************************************************/ void rt_LD_init(void) { LD3320_RST_Func(1); rt_thread_delay(100); LD3320_RST_Func(0); rt_thread_delay(100); LD3320_RST_Func(1); rt_thread_delay(100); LD3320_CS_Func(0); rt_thread_delay(100); LD3320_CS_Func(1); rt_thread_delay(100); } ``` 语音识别的操作顺序是: 语音识别用初始化(包括通用初始化)→写入识别列表→开始识别, 并准备好中断响应函数,打开中断允许位。 这里需要说明一下,如果不用中断方式,也可以通过查询方式工作。在“开 始识别”后,读取寄存器 B2H 的值,如果为 21H 就表示有识别结果产生。 在此之后读取候选项等操作与中断方式相同。 - 通用初始化 ```java /******************************************************************************** function: Common init ********************************************************************************/ static void rt_LD_Init_Common(void) { bMp3Play = 0; rt_LD_ReadReg(0x06); rt_LD_WriteReg(0x17, 0x35); rt_thread_delay(20); rt_LD_ReadReg(0x06); rt_LD_WriteReg(0x89, 0x03); rt_thread_delay(20); rt_LD_WriteReg(0xCF, 0x43); rt_thread_delay(20); rt_LD_WriteReg(0xCB, 0x02); /*PLL setting*/ rt_LD_WriteReg(0x11, LD_PLL_11); if (nLD_Mode == LD_MODE_MP3) { rt_LD_WriteReg(0x1E, 0x00); rt_LD_WriteReg(0x19, LD_PLL_MP3_19); rt_LD_WriteReg(0x1B, LD_PLL_MP3_1B); rt_LD_WriteReg(0x1D, LD_PLL_MP3_1D); } else { rt_LD_WriteReg(0x1E, 0x00); rt_LD_WriteReg(0x19, LD_PLL_ASR_19); rt_LD_WriteReg(0x1B, LD_PLL_ASR_1B); rt_LD_WriteReg(0x1D, LD_PLL_ASR_1D); } rt_thread_delay(20); rt_LD_WriteReg(0xCD, 0x04); rt_LD_WriteReg(0x17, 0x4c); rt_thread_delay(20); rt_LD_WriteReg(0xB9, 0x00); rt_LD_WriteReg(0xCF, 0x4F); rt_LD_WriteReg(0x6F, 0xFF); } ``` - 语音识别初始化 ```java /******************************************************************************** function: ASR init ********************************************************************************/ static void rt_LD_Init_ASR(void) { nLD_Mode = LD_MODE_ASR_RUN; rt_LD_Init_Common(); rt_LD_WriteReg(0xBD, 0x00); rt_LD_WriteReg(0x17, 0x48); rt_thread_delay(20); rt_LD_WriteReg(0x3C, 0x80); rt_LD_WriteReg(0x3E, 0x07); rt_LD_WriteReg(0x38, 0xff); rt_LD_WriteReg(0x3A, 0x07); rt_thread_delay(20); rt_LD_WriteReg(0x40, 0); rt_LD_WriteReg(0x42, 8); rt_LD_WriteReg(0x44, 0); rt_LD_WriteReg(0x46, 8); rt_thread_delay(20); } ``` - 写入识别表 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/5c1283a6d6485aac01172648dd6a37de.png) 先介绍一个读取 0xB2 寄存器的函数,如果在以后的 ASR 命令函数前不能够 读取到正确 Idle 状态,说明芯片内部可能出错了。经拷机测试,当使用的电 源电压/电流出现不稳定有较大波动时,有小概率会出现这种情况。出现这种 情况时,建议 Reset LD3320 芯片,重新启动设置芯片。 ```java /******************************************************************************** function: Check ASR state ********************************************************************************/ static rt_uint8_t rt_LD_Check_ASRBusyFlag(void) { rt_uint8_t j; rt_uint8_t flag = 0; for (j = 0; j < 10; j++) { if (rt_LD_ReadReg(0xb2) == 0x21) { flag = 1; //rt_kprintf("success!!! ASR OK\r\n"); break; } rt_thread_delay(10); //printf("ERROR!!! ASR Busy\r\n"); } return flag; } /******************************************************************************** function: Add ASR Keyword ********************************************************************************/ static rt_uint8_t rt_LD_AsrAddKey(void) { rt_uint8_t k, flag; rt_uint8_t nAsrAddLength; #define DATE_A 5 #define DATE_B 20 rt_uint8_t sRecog[DATE_A][DATE_B] = { //add commond,use pinying "qian jin", "hou tui", "zuo zhuan", "you zhuan" }; rt_uint8_t pCode[DATE_A] = { //add commond code to do the commond CODE_RUN, CODE_KEY, CODE_FLASH, CODE_PLAY, }; flag = 1; for (k = 0; k < DATE_A; k++) { //write data to LD3320 if (rt_LD_Check_ASRBusyFlag() == 0) { flag = 0; break; } rt_LD_WriteReg(0xc1, pCode[k]); rt_LD_WriteReg(0xc3, 0); rt_LD_WriteReg(0x08, 0x04); rt_thread_delay(1); rt_LD_WriteReg(0x08, 0x00); rt_thread_delay(1); for (nAsrAddLength = 0; nAsrAddLength < DATE_B; nAsrAddLength++) { if (sRecog[k][nAsrAddLength] == 0) break; rt_LD_WriteReg(0x5, sRecog[k][nAsrAddLength]); } rt_LD_WriteReg(0xb9, nAsrAddLength); rt_LD_WriteReg(0xb2, 0xff); rt_LD_WriteReg(0x37, 0x04); } return flag; } ``` - 开始识别 ```java /******************************************************************************** function: Begin to ASR ********************************************************************************/ static rt_uint8_t rt_LD_AsrRun(void) { rt_LD_WriteReg(0x35, MIC_VOL); rt_LD_WriteReg(0x1C, 0x09); rt_LD_WriteReg(0xBD, 0x20); rt_LD_WriteReg(0x08, 0x01); rt_thread_delay(20); rt_LD_WriteReg(0x08, 0x00); rt_thread_delay(20); if (rt_LD_Check_ASRBusyFlag() == 0) return 0; rt_LD_WriteReg(0xB2, 0xff); rt_LD_WriteReg(0x37, 0x06); rt_LD_WriteReg(0x37, 0x06); rt_thread_delay(20); rt_LD_WriteReg(0x1C, 0x0b); rt_LD_WriteReg(0x29, 0x10); rt_LD_WriteReg(0xBD, 0x00); return 1; } /******************************************************************************** function: Run ASR ********************************************************************************/ rt_uint8_t rt_LD_ASR(void) { rt_uint8_t i = 0; rt_uint8_t asrflag = 0; for (i = 0; i < 5; i++) { //run ASR try 5 times rt_LD_Init_ASR(); //init ASR rt_thread_delay(100); if (rt_LD_AsrAddKey() == 0) { //Add fixed to LD3320 rt_kprintf("ERROR!!! LD_AsrAddKey\r\n"); rt_LD_init(); //ERROR,Reset LD3320 rt_thread_delay(50); continue; } rt_thread_delay(10); if (rt_LD_AsrRun() == 0) { //start ASR rt_kprintf("ERROR!!! LD_AsrRun\r\n"); rt_LD_init(); //ERROR,Reset LD3320 rt_thread_delay(50); continue; } asrflag = 1; break; } //rt_kprintf("RunASR\r\n"); return asrflag; } ``` - 响应中断 如果麦克风采集到声音,不管是否识别出正常结果,都会产生一个 中断信号。而中断程序要根据寄存器的值分析结果。 读取 BA 寄存器的值,可以知道有几个候选答案,而 C5 寄存器里的 答案是得分最高、最可能正确的答案。 例如发音为“上海”并被成功识别(无其他候选),那么 BA 寄存器 里的数值是 1,而 C5 寄存器里的值是对应的编码 3。 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/b7fcaeff1c939e4f5b8230b931f6e432.png.webp) 中断到来我们置一标志即可,逻辑处理函数放在main中执行 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/db64dc1530f6bc2d772b9191c8fea5ab.png) - 根据识别内容执行对应用户代码 ```java void rt_ProcessInt() { rt_uint8_t nAsrResCount = 0; ucRegVal = rt_LD_ReadReg(0x2B); ucHighInt = rt_LD_ReadReg(0x29); // interrupt enable flag ucLowInt = rt_LD_ReadReg(0x02); // interrupt enable flag rt_LD_WriteReg(0x29, 0);// interrupt disenable rt_LD_WriteReg(0x02, 0);// interrupt disenable if(nLD_Mode == LD_MODE_ASR_RUN) { //rt_kprintf("---------------ASR---------------\r\n"); //The interruption caused by speech recognition //(There is sound input, and there is interruption whether the recognition is successful or failed) if((ucRegVal & 0x10) && rt_LD_ReadReg(0xb2)==0x21 && rt_LD_ReadReg(0xbf)==0x35) { nAsrResCount = rt_LD_ReadReg(0xba); if(nAsrResCount>0 && nAsrResCount<=4) { rt_kprintf("ASR SUCCESSFUL \r\n"); nAsrStatus = LD_ASR_FOUNDOK; } else { rt_kprintf("ASR UNSUCCESSFUL \r\n"); nAsrStatus = LD_ASR_FOUNDZERO; } } else { //rt_kprintf("No ASR \r\n"); nAsrStatus = LD_ASR_FOUNDZERO; } rt_LD_WriteReg(0x2b, 0); rt_LD_WriteReg(0x1c, 0); return; } rt_kprintf("--------------PLAY MP3--------------\r\n"); // Play MP3 to produce 3 kinkd of intterupt // A. play over // B. data send over // C. Data will be used up and sent if(rt_LD_ReadReg(0xBA) & CAUSE_MP3_SONG_END) { // A. play over rt_LD_WriteReg(0x2B, 0); rt_LD_WriteReg(0xBA, 0); rt_LD_WriteReg(0xBC, 0); rt_LD_WriteReg(0x08, 1); rt_LD_WriteReg(0x08, 0); rt_LD_WriteReg(0x33, 0); rt_kprintf("play over \r\n"); bMp3Play = 0; // play status return; } if(nMp3Pos >= nMp3Size) { // B. data send over rt_LD_WriteReg(0xBC, 0x01);//data voer rt_LD_WriteReg(0x29, 0x10); return; } // C. Data will be used up and sent rt_LD_ReloadMp3Data(); rt_LD_WriteReg(0x29, ucHighInt); rt_LD_WriteReg(0x02, ucLowInt); } ``` 至此,语音识别流程就算完了,来看看如何调用: 创建两个线程,关于如何创建,紧跟小飞哥步伐,小飞哥会在后面的实战中详细介绍 ```java int ld3320_sample(void) { static rt_thread_t tid1 = RT_NULL,tid2 = RT_NULL; rt_ld3320_irq_pin_init(); /* 创建线程 */ tid1=rt_thread_create( "thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); /* 如果获得线程控制块,启动这个线程 */ if (tid1 != RT_NULL) rt_thread_startup(tid1); /* 创建线程 */ tid2=rt_thread_create( "thread2", thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); /* 如果获得线程控制块,启动这个线程 */ if (tid2 != RT_NULL) rt_thread_startup(tid2); return 0; } /* 导出到 msh 命令列表中 */ //MSH_CMD_EXPORT(ld3320_sample, ld3320 sample); INIT_COMPONENT_EXPORT(ld3320_sample); ``` 任务函数: ```java /* 线程入口 */ static void thread1_entry(void* parameter) { int i; while(1) { /* rt_kprintf("success!!! thread1_entry OK\r\n"); rt_thread_delay(500);*/ if(ld3320_flag) { ld3320_flag=0; rt_ProcessInt(); } else { ld3320_flag=0; i++; if(i%500==0){ rt_kprintf("\r\n\rvoice checking,please speak...\r\n"); i=0; } rt_thread_delay(1); } } } /* 线程入口 */ static void thread2_entry(void* parameter) { rt_uint8_t nAsrRes = 0; while (1) { if (bMp3Play) { rt_kprintf("*********playing*********\r\n"); continue; } switch (nAsrStatus) { case LD_ASR_RUNING: case LD_ASR_ERROR: break; case LD_ASR_NONE: nAsrStatus = LD_ASR_RUNING; if (rt_LD_ASR() == 0) //Start the ASR process once nAsrStatus = LD_ASR_ERROR; break; case LD_ASR_FOUNDOK: nAsrRes = rt_LD_GetResult(); //once ASR process end, get the result switch (nAsrRes) { //show the commond case CODE_RUN: rt_kprintf("voice control system is runing.....\r\n"); break; case CODE_KEY: rt_kprintf("voice control system is runing.....\r\n"); break; case CODE_FLASH: rt_kprintf("voice control system is runing.....\r\n"); break; case CODE_PLAY: rt_kprintf("voice control system is runing.....\r\n"); break; default: break; } nAsrStatus = LD_ASR_NONE; break; case LD_ASR_FOUNDZERO: default: nAsrStatus = LD_ASR_NONE; break; } rt_Board_text(nAsrRes); //do the commond nAsrRes = 0; } /* rt_kprintf("success!!! thread2_entry OK\r\n"); rt_thread_delay(500);*/ } ``` 至此,完成,ending # 效果演示 既然能够语音识别了,来,咱们一起和它对对话,分别对他说:你好,你来自哪里,背一首诗,世界这么大 识别之后分别对应输出相应的内容: ![image.png](https://oss-club.rt-thread.org/uploads/20220121/4d58f7cc002ddc22629923ee568c4804.png) ![image.png](https://oss-club.rt-thread.org/uploads/20220121/d3f36ac77834ef94bcc874925bdfa1cf.png) # 资料获取 关注公众号,后台回复“LD3320”即可获取源码资料,欢迎添加小飞哥好友,一起交流 ![image.png](https://oss-club.rt-thread.org/uploads/20220121/52d1cfd7c7b60583380fa76334235d70.jpg)
1
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Enthusiasm
公众号:小飞哥玩嵌入式
文章
8
回答
7
被采纳
0
关注TA
发私信
相关文章
1
RT-Thread Studio 编译ART-Pi 的SDK报错 求助!-
2
ART-Pi 在 Studio 中使用TouchGFX Library 编译报错
3
RT-Thread Studio 更新错误
4
基于ART-Pi开发板创建的工程,RT-Thread Settings打不开
5
在 ART-Pi 平台使用 openocd 直接烧录到外部 spi flash?
6
ART-Pi工程用mdk5打开后提示文件找不到
7
ART-PI demo是否能够增加demo流程框图或者详细设计文档
8
ART-Pi 的ST_Link无法识别
9
请把ART-Pi仓库放到gitee上一份。
10
仅有ART-Pi开发包时,RTT Studio无法创建ART-Pi工程
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
cubemx
freemodbus
PWM
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
C++_cpp
at_device
本月问答贡献
踩姑娘的小蘑菇
5
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
3
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
5
个答案
1
次被采纳
本月文章贡献
出出啊
1
篇文章
2
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
4
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部