本文实际是对ld3320(spi通信版)的个人理解,如果单论代码和开发板的资料而言,其实当你购买ld3320的时候,卖家已然提供了很多资料。我在大学期间曾经多次使用ld3320芯片的开发板用于设计系统,我在我的毕业设计作品中也有添加这个系统功能,用于添加整个系统的趣味性。本文的初衷也是为了总结学习内容,供大家参考学习。如果我的理解有误,也希望读者可以在评论中指出,不胜感激。
附上我的工程代码:工程代码
目录
目录
关于ld3320的通俗理解
使用ld3320的好处是它直接免去了你训练模型的过程,换言之,它是将已经训练好的一套语音识别系统集成到ld3320芯片上了,我们只需要会“调用”ld3320芯片即可,就像搭积木一样,它已然是个整体模块,我们甚至不需要提供多余的操作,只需要堆上去,它就已经可以使用。那么如何“调用”它,就是我们需要解决的最大难点。
目前市面上的ld3320有两种型号:其一是spi版的,如下图所示,这个是我们今天的主角,我们可以将其视为外围电路的一个模块(就是一块积木),需要外接一个单片机去“调用”这个模块从而实现智能语音识别。

其二是串口版本的,如图所示。

那么ld3320串口版和spi版本的有什么区别呢?
ld3320语音识别芯片的spi版本和串口版本的区别在于它们的通信接口不同。spi版本是通过spi接口进行通信,而串口版本是通过串口接口进行通信。
具体而言,spi版本可以直接和mcu或其他支持spi通信的设备进行连接,并通过spi接口传输数据。而串口版本则需要通过串口与mcu进行通信,通常需要在mcu上编写一定的串口通信协议来实现与芯片的数据交换。
另外,由于通信接口不同,spi版本和串口版本的应用场景也略有不同。spi版本的芯片适合于对通信速度有要求、需要高速传输数据的场合,而串口版本则适合于一些通信速度要求不高、需要适应不同串口的应用场合。
通俗而言,就是spi版本的通信速度快,且可以是视为一个正真独立的模块;而串口版的可以视为一个具有信息处理能力的51mcu。
ld3320的实现原理
ld3320语音识别芯片的实现原理主要可以分为以下几个步骤:
1. 音频采集:ld3320内置一个麦克风放大器,它可以对周围的声音进行采集,并将采集到的音频信号送入芯片内部的语音信号处理器。
2. 信号处理:ld3320内置一个语音信号处理器,它可以对音频信号进行预处理、降噪、滤波等处理,以提高识别的准确率。
3. 特征提取:经过信号处理后,ld3320将音频信号转换成数字信号,并提取出其中的语音特征。ld3320采用了一种叫做mel频率倒谱系数(mel-frequency cepstral coefficients, mfcc)的特征提取算法。
4. 模型匹配:ld3320内置了一些语音识别的模型,比如说中文数字、英文数字、中文拼音等。ld3320将提取出的语音特征与这些模型进行匹配,以识别出用户说的话。
5. 输出结果:一旦ld3320识别出了用户的话,它会将识别结果输出到外部的mcu或dsp芯片中,以供后续的应用程序使用。
总的来说,ld3320的实现智能语音识别的原理就是通过内置的麦克风放大器进行音频采集,经过信号处理和特征提取后,将提取出的语音特征与内置的模型进行匹配,从而实现对用户语音指令的识别和理解。ld3320语音识别芯片通过识别语音信号中的特定频率和时域特征来实现语音识别。它使用的是端点检测技术,即在语音信号的开始和结束处检测到信号的存在,并将其传递给后续处理模块进行识别。ld3320芯片可以进行多语言识别,包括中文、英文、日文、韩文等。不过需要注意的是,其语音识别的准确率可能会受到环境噪声等因素的影响。
功能实现(demo)
1、实现功能
语音控制开关卧室、客厅灯;开关风扇等功能
2、实物图

3、接线图

4、spi协议的详细介绍和解读
spi(serial peripheral interface)是一种同步的串行通信协议,用于在微控制器或其他数字集成电路(ic)之间传输数据。它是一种全双工、点对点的通信协议,使用四根线进行通信:主机发送数据时,从机收到并返回响应数据。
spi通信协议使用一个主机和一个或多个从机之间进行数据传输,其中一个设备是主机,其他设备是从机。主机负责协调整个通信过程,从机只有在被主机选择时才会响应。通常情况下,spi总线上只会有一个主机,但可以同时连接多个从机,每个从机都有一个独立的从机选择信号线。
1)spi协议的通信过程如下:
1. 主机将从机选择信号线拉低,选中要进行通信的从机。
2. 主机向从机发送一个时钟信号,时钟信号将数据的传输分为若干个时钟周期。
3. 主机通过一个数据线将数据发送给从机,并通过另一个数据线接收从机返回的数据。
4. 在时钟的最后一个周期,主机将从机选择信号线拉高,表示通信结束。
2)spi协议的数据传输方式分为两种模式:模式0和模式3
模式0的时钟极性(cpol)和相位(cpha)都为0,意味着时钟线在空闲状态下为低电平,数据在上升沿进行采样。模式3的时钟极性(cpol)和相位(cpha)都为1,意味着时钟线在空闲状态下为高电平,数据在下降沿进行采样。
3)spi协议有四根线:sclk、mosi、miso、ss
其中,sclk是时钟线,由主设备控制,用于同步数据传输;mosi是主设备输出、从设备输入的数据线;miso是从设备输出、主设备输入的数据线;ss是片选信号线,用于选择与主设备通信的从设备。通信过程中,主设备通过sclk线向从设备发送时钟信号,同时将数据通过mosi线发送给从设备,从设备则通过miso线返回响应的数据。ss线控制从设备的选中与释放,可以支持多从设备的通信。
spi协议的优点是高速、简单、成本低廉,通信线路简单,但需要额外的从机选择信号线。spi协议被广泛应用于各种数字集成电路之间的通信,如闪存、eeprom、数字信号处理器等。总而言之,spi协议的传输速率快,适合于数据量小、实时性高的应用场合。
代码层面解析(需要修改)
综上所述,我们不难看出。我们只需要学会“调用”ld3320就行,因为它就是一块“积木”,我们只需要学会使用就行,至于语音识别的难题,芯片已经帮你解决了。在“调用”芯片时需要使用到spi协议,我们使用stm32c8t6作为控制芯片
1)main.c文件
我们这里只需要注意ld3320_main()函数,至于while中的两个函数,你不要被它唬到了,它其实就是通过定时器和中断来实现开关(客厅和卧室)灯。难点实际在ld3320_main()
int main()
{
systick_init(72);
nvic_prioritygroupconfig(nvic_prioritygroup_2);
usart1_init(115200);
//tim3_int_init(5000-1,72-1);
tim4_int_init(999,72-1);
led_init();
//tim_cmd(tim3,enable);
tim_cmd(tim4,enable);
printf("初始化完成\r\n");
ld3320_main(); //ld3320 主函数函数
while(1)
{
timertreat();
ledshow();
}
}
2)ld3320_main.c文件及其头文件(重点)
(1)ld3320_main.c文件
这里主要修改:static void board_text(uint8 code_val)函数,具体操作结合头文件分析即可。注意,头文件也需要修改。这个非常的简单,只需要有基本的c代码阅读能力即可修改,不做过多解释啦
#include "ldchip.h"
#include "reg_rw.h"
#include "system.h"
#include "systick.h"
#include "usart.h"
#include <stdio.h>
#include "led.h"
/*************端口信息********************
* 接线说明
ld3320接口 stm32接口
* rst pb6
* cs pb8
* wr/spis pb9
* p2/sdck pb13
* p1/sdo pb14
* p0/sdi pb15
* irq pa3
* a0 pb7
* rd pa0
*****************************************/
/************************************************************************************
// nasrstatus 用来在main主程序中表示程序运行的状态,不是ld3320芯片内部的状态寄存器
// ld_asr_none: 表示没有在作asr识别
// ld_asr_runing: 表示ld3320正在作asr识别中
// ld_asr_foundok: 表示一次识别流程结束后,有一个识别结果
// ld_asr_foundzero: 表示一次识别流程结束后,没有识别结果
// ld_asr_error: 表示一次识别流程中ld3320芯片内部出现不正确的状态
*********************************************************************************/
uint8 nasrstatus=0;
void ld3320_init(void);
uint8 runasr(void);
void processint0(void);
void ld3320_exti_cfg(void);
void ld3320_spi_cfg(void);
void ld3320_gpio_cfg(void);
void led_gpio_cfg(void);
static void board_text(uint8 code_val);
extern void printcombit(usart_typedef* usartx, uint8_t data);
/***********************************************************
* 名 称: ld3320_main(void)
* 功 能: 主函数ld3320程序入口
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_main(void)
{
uint8 nasrres=0;
ld3320_init();
while(1)
{
switch(nasrstatus)
{
case ld_asr_runing:
case ld_asr_error:
break;
case ld_asr_none:
nasrstatus=ld_asr_runing;
if (runasr()==0) // 启动一次asr识别流程:asr初始化,asr添加关键词语,启动asr运算
{
nasrstatus = ld_asr_error;
}
break;
case ld_asr_foundok:
nasrres = ld_getresult( ); //识别成功自动 获取识别码,识别码在ldchip.h文件中,自行定义的数据
printcombit(usart1,nasrres ); //串口输出识别码
//board_text(nasrres );//开发板测试演示部分---对识别码的进行判断做出动作 (用户亦可以添加自己的功能,例如控制io口输出,串口数据输出等)
nasrstatus = ld_asr_none;
break;
case ld_asr_foundzero:
default: nasrstatus = ld_asr_none;
break;
}//switch
//开发板测试
board_text(nasrres );
}// while
}
static void board_text(uint8 code_val)
{
switch(code_val) //对结果执行相关操作
{
case code_dd: //命令“打开客厅灯”
led_kt=1;
printf("打开客厅灯\r\n");
break;
case code_gd: //命令“关闭客厅灯”
led_kt=0;
printf("关闭客厅灯\r\n");
break;
case code_qdd: //命令“打开卧室灯”
//打开卧室灯
led_ws = 1;
printf("打开卧室灯\r\n");
break;
case code_dg: //命令“关闭卧室灯”
//关闭卧室灯
led_ws = 0;
printf("关闭卧室灯\r\n");
break;
case code_lsd: //命令“全部打开”
//全部打开
led_kt=1;
led_ws = 1;
printf("全部打开\r\n");
break;
case code_ssd: //命令“全部关闭”
//全部关闭
led_kt=0;
led_ws = 0;
printf("全部关闭\r\n");
break;
case code_ddr: //命令“打开风扇”
//打开风扇
printf("打开风扇\r\n");
jdy_fs = 0;
break;
case code_rdd: //命令“关闭风扇”
//关闭风扇
jdy_fs = 1;
printf("关闭风扇\r\n");
break;
default:break;
}
}
/***********************************************************
* 名 称:ld3320_init(void)
* 功 能:模块驱动端口初始配置
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_init(void)
{
ld3320_gpio_cfg();
ld3320_exti_cfg();
ld3320_spi_cfg();
ld_reset();
}
/***********************************************************
* 名 称: runasr(void)
* 功 能: 运行asr
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
uint8 runasr(void)
{
uint8 i=0;
uint8 asrflag=0;
for (i=0; i<5; i++) // 防止由于硬件原因导致ld3320芯片工作不正常,所以一共尝试5次启动asr识别流程
{
ld_asrstart(); //初始化asr
ld3320_delay(100);
if (ld_asraddfixed()==0) //添加关键词语到ld3320芯片中
{
ld_reset(); // ld3320芯片内部出现不正常,立即重启ld3320芯片
ld3320_delay(50); // 并从初始化开始重新asr识别流程
continue;
}
ld3320_delay(10);
if (ld_asrrun() == 0)
{
ld_reset(); // ld3320芯片内部出现不正常,立即重启ld3320芯片
ld3320_delay(50); // 并从初始化开始重新asr识别流程
continue;
}
asrflag=1;
break; // asr流程启动成功,退出当前for循环。开始等待ld3320送出的中断信号
}
return asrflag;
}
/***********************************************************
* 名 称: void delay_( int i)
* 功 能: 短延时
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void delay_( int i)
{
while( i--)
{
}
}
/***********************************************************
* 名 称: ld3320_delay(unsigned long uldata)
* 功 能: 长延时函数
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_delay(unsigned long uldata)
{
unsigned int j = 0;
unsigned int g = 0;
for (j=0;j<5;j++)
{
for (g=0;g<uldata;g++)
{
delay_(120);
}
}
}
/***********************************************************
* 名 称:ld3320_gpio_cfg(void)
* 功 能:初始化需要用到的io口
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_gpio_cfg(void)
{
gpio_inittypedef gpio_initstructure;
// 配置pa8 输出 8m 波形
//定义rst/a0/cs/wr端口
{
rcc_apb2periphclockcmd(rcc_apb2periph_gpioa | rcc_apb2periph_gpiob ,enable);
//ld_cs /rset
gpio_initstructure.gpio_pin =gpio_pin_6|gpio_pin_7|gpio_pin_8|gpio_pin_9;
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_initstructure.gpio_mode = gpio_mode_out_pp;
gpio_init(gpiob,&gpio_initstructure);
gpio_setbits(gpiob,gpio_pin_7); /*a0默认拉高*/
}
}
/***********************************************************
* 名 称:ld3320_spi_cfg(void)
* 功 能:配置spi功能和端口初始化
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_spi_cfg(void)
{
gpio_inittypedef gpio_initstructure;
spi_inittypedef spi_initstructure;
rcc_apb2periphclockcmd( rcc_apb2periph_gpiob, enable );//portb时钟使能
rcc_apb1periphclockcmd( rcc_apb1periph_spi2, enable );//spi2时钟使能
gpio_initstructure.gpio_pin = gpio_pin_13 | gpio_pin_14 | gpio_pin_15;
gpio_initstructure.gpio_mode = gpio_mode_af_pp; //pb13/14/15复用推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpiob, &gpio_initstructure);
ld_cs_h();
gpio_setbits(gpiob,gpio_pin_13|gpio_pin_14|gpio_pin_15); //pb13/14/15上拉
spi_initstructure.spi_direction = spi_direction_2lines_fullduplex; //设置spi单向或者双向的数据模式:spi设置为双线双向全双工
spi_initstructure.spi_mode = spi_mode_master; //设置spi工作模式:设置为主spi
spi_initstructure.spi_datasize = spi_datasize_8b; //设置spi的数据大小:spi发送接收8位帧结构
spi_initstructure.spi_cpol = spi_cpol_high; //选择了串行时钟的稳态:时钟悬空高
spi_initstructure.spi_cpha = spi_cpha_1edge; //数据捕获于第二个时钟沿
spi_initstructure.spi_nss = spi_nss_soft; //nss信号由硬件(nss管脚)还是软件(使用ssi位)管理:内部nss信号有ssi位控制
spi_initstructure.spi_baudrateprescaler = spi_baudrateprescaler_64; //定义波特率预分频的值:波特率预分频值为16
spi_initstructure.spi_firstbit = spi_firstbit_msb; //指定数据传输从msb位还是lsb位开始:数据传输从msb位开始
spi_initstructure.spi_crcpolynomial = 7; //crc值计算的多项式
spi_init(spi2, &spi_initstructure); //根据spi_initstruct中指定的参数初始化外设spix寄存器
spi_cmd(spi2, enable); //使能spi外设
}
/***********************************************************
* 名 称: ld3320_exti_cfg(void)
* 功 能: 外部中断功能配置和相关端口配置
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void ld3320_exti_cfg(void)
{
exti_inittypedef exti_initstructure;
nvic_inittypedef nvic_initstructure;
gpio_inittypedef gpio_initstructure;
//定义irq中断引脚配置
rcc_apb2periphclockcmd(rcc_apb2periph_gpioa, enable);
gpio_initstructure.gpio_pin =gpio_pin_3;
gpio_initstructure.gpio_mode = gpio_mode_in_floating;
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpioa, &gpio_initstructure);
//外部中断线配置
gpio_extilineconfig(gpio_portsourcegpioa, gpio_pinsource3);
exti_initstructure.exti_line = exti_line3;
exti_initstructure.exti_mode = exti_mode_interrupt;
exti_initstructure.exti_trigger =exti_trigger_falling;
exti_initstructure.exti_linecmd = enable;
exti_init(&exti_initstructure);
exti_generateswinterrupt(exti_line3);
gpio_setbits(gpioa,gpio_pin_3); //默认拉高中断引脚
exti_clearflag(exti_line3);
exti_clearitpendingbit(exti_line3);
//中断嵌套配置
nvic_initstructure.nvic_irqchannel = exti3_irqn;
nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
nvic_initstructure.nvic_irqchannelsubpriority = 0;
nvic_initstructure.nvic_irqchannelcmd = enable;
nvic_init(&nvic_initstructure);
}
/***********************************************************
* 名 称: exti1_irqhandler(void)
* 功 能: 外部中断函数
* 入口参数:
* 出口参数:
* 说 明:
* 调用方法:
**********************************************************/
void exti3_irqhandler(void)
{
if(exti_getitstatus(exti_line3)!= reset )
{
processint0();
exti_clearflag(exti_line3);
exti_clearitpendingbit(exti_line3);
}
}
(2)ldchip.h文件(不做过多解释,详细见注释)
#ifndef ld_chip_h
#define ld_chip_h
#define uint8 unsigned char
#define uint16 unsigned int
#define uint32 unsigned long
// 以下三个状态定义用来记录程序是在运行asr识别还是在运行mp3播放
#define ld_mode_idle 0x00
#define ld_mode_asr_run 0x08
#define ld_mode_mp3 0x40
// 以下五个状态定义用来记录程序是在运行asr识别过程中的哪个状态
#define ld_asr_none 0x00 // 表示没有在作asr识别
#define ld_asr_runing 0x01 // 表示ld3320正在作asr识别中
#define ld_asr_foundok 0x10 // 表示一次识别流程结束后,有一个识别结果
#define ld_asr_foundzero 0x11 // 表示一次识别流程结束后,没有识别结果
#define ld_asr_error 0x31 // 表示一次识别流程中ld3320芯片内部出现不正确的状态
#define clk_in 22 /* user need modify this value according to clock in */
#define ld_pll_11 (uint8)((clk_in/2.0)-1)
#define ld_pll_mp3_19 0x0f
#define ld_pll_mp3_1b 0x18
#define ld_pll_mp3_1d (uint8)(((90.0*((ld_pll_11)+1))/(clk_in))-1)
#define ld_pll_asr_19 (uint8)(clk_in*32.0/(ld_pll_11+1) - 0.51)
#define ld_pll_asr_1b 0x48
#define ld_pll_asr_1d 0x1f
// ld chip fixed values.
#define resum_of_music 0x01
#define cause_mp3_song_end 0x20
#define mask_int_sync 0x10
#define mask_int_fifo 0x04
#define mask_afifo_int 0x01
#define mask_fifo_status_afull 0x08
void ld_reset(void);
uint8 runasr(void);
void ld_init_common(void);
void ld_init_asr(void);
void ld_reloadmp3data(void);
void ld_reloadmp3data_2(void);
uint8 ld_processasr(uint32 recogaddr);
void ld_asrstart(void);
uint8 ld_asrrun(void);
uint8 ld_asraddfixed(void);
uint8 ld_getresult(void);
void ld_readmemoryblock(uint8 dev, uint8 * ptr, uint32 addr, uint8 count);
void ld_writememoryblock(uint8 dev, uint8 * ptr, uint32 addr, uint8 count);
extern uint8 nld_mode;
//以下为识别码的宏定义无特别意义,0-ff可自行修改值,不分顺序不要重复。
#define code_dd 0x01 /*打开客厅灯*/
#define code_gd 0x02 /*关闭客厅灯*/
#define code_qdd 0x03 /*打开卧室灯*/
#define code_dg 0x04 /*关闭卧室灯*/
#define code_lsd 0x05 /*全部打开*/
#define code_ssd 0x06 /*全部关闭*/
#define code_ddr 0x07 /*打开继电器*/
#define code_rdd 0x08 /*继电器点动*/
//#define code_play 0x09 /*播放歌曲*/
//#define code_name 0x0a /*你叫什么名字*/
//#define code_do 0x0b /*你会做什么*/
void ld3320_delay(unsigned long uldata);
#define mic_vol 0x43 //麦克风灵敏度条件,值越大越灵敏也容易误识别
#endif
3)关于用电器的io口配置(led.c)
这里的代码的是对用电器引脚口的一个配置
#include "led.h"
#include "timer.h"
void led_init()
{
gpio_inittypedef gpio_initstructure;
rcc_apb2periphclockcmd(rcc_apb2periph_gpiob, enable);
gpio_initstructure.gpio_pin = gpio_pin_12|gpio_pin_0|gpio_pin_1;
gpio_initstructure.gpio_mode = gpio_mode_out_pp; //推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz; //io口速度为50mhz
gpio_init(gpiob, &gpio_initstructure);
led_kt = 0;
led_ws = 0;//灯光初始为关闭状态
jdy_fs = 1;
}
void ledshow(void)
{
if(g_sttimertickflag.btimer500ms == 1)
{
led_kt = !led_kt;
led_ws = !led_ws;
}
}
代码是不是也非常简单,它就是一个搭积木的过程。总而言之很有趣的一个小系统,用于智能家居等一些场景也是十分不错的。但是目前而言,ld3320语音识别的板子对杂音的过滤十分有限,大概只能做到1m-1.5m的有效识别吧,具体的没测过。感兴趣的同学可以试试哈哈哈哈哈哈哈
哦,对了,需要说明一下,本代码只供学习哦,这代码本身也是我通过商家代码改的。如果想跟我一样,给自己的毕设或者系统添加一点小功能和一些趣味性,代码自取哈!
发表评论