基于Arduino IDE开发的LD3320语音识别模块

基于Arduino的LD3320语音识别模块设计详解

前言

本文章为记录本人的学习过程,最终目的是设计一款IIC通讯方式的语音识别模块,该模块的主要功能,就是识别程序中设定的指令词,并返回识别结果,指令词和对应的返回编号可在程序中任意修改,不需要去给语音识别模块烧录固件,支持命令词的动态编辑。
模块硬件包含一块单片机芯片,一块LD3320芯片,以及外围电路。PCB工程已开源,网上到处都有。
基础功能:
1.动态编辑命令词,不需要烧录模块的固件
2.识别成功后返回对应结果
文章将会记录我从零开始的调试步骤以及遇到的问题,还有解决问题的详细思路。
接下来我们开始。
工程已上传(PCB工程+模块固件+Demo),有需要的可以先下载,链接挂在末尾
PCB如上所示
原理图如上所示

一、LD3320驱动编写

根据ICroute官方文档所写,想要驱动LD3320进行语音识别,可以使用软硬并口通讯方式对LD3320的寄存器进行读写从而驱动LD3320,也可以使用软硬SPI串行通讯方式对LD3320的寄存器进行读写,也可以驱动LD3320。需要注意的是,模拟并口驱动方式官方并不推荐,不仅速度慢,也会造成通讯不稳定等情况。推荐使用硬件SPI进行驱动,如果MCU没有SPI的硬件接口,也可以使用普通I/O口模拟SPI进行通讯,较为稳定。
回到正题,我手头正好有一块网上买的LD3320模块,是不带单片机的那种,板子上只有LD3320的那种。
在这里插入图片描述

直接先拿Arduino UNO开发板和这款LD3320进行原理测试,Let’s go。
直接用杜邦线,按如下方式连接:

5V – VCC
GND – GND
MISO – D12
MOSI – D11
SCK – D13
NSS – D4
RST – D9
IRQ – D2
WR – GND

官方文档所说,LD3320的电压为3V3,通讯引脚耐压也是3V3,超过3V3会使模块使用不稳定,但是在使用Arduino的实际测试中,未使用电压转换模块,反应良好。千万别学我,最好还是用3V3该转压还是得转请注意,引脚电平可以超过3v3,但是!电源只能是3v3
本部分我分为三小步来完成:

step 1.0 使用Arduino的SPI库,通过硬件SPI和LD3320通讯,读写寄存器
setp 1.1 访问LD3320的三个指定寄存器,检查硬件连接的可靠性
setp 1.2 进行驱动程序的编写,驱动LD3320进行语音识别

step 1.0 使用Arduino的SPI库,通过硬件SPI和LD3320通讯,读写寄存器

这一步的主要目的,是确定和LD3320能否进行正常的通讯,众所周知,SPI是一种串行通讯方式,需要至少连接五根线,为什么是五而不是四,因为要公地。
【SCK】 》》同步主机产生的数据传输的时钟脉冲。
【MOSI】 》》用于向外设发送数据的线,主机从这个引脚发送,从机从这个引脚接收。
【MISO】《《用于向主设备发送数据的线,主机从这个引脚接收,从机从这个引脚发送。
【CS】 》》片选信号,主机控制这个引脚来开启和禁用从机,低电平使能。
【GND】 祖传公地。
好了,SPI相关的知识我们就了解这么多,我们只需要知道他是全双工串行通讯,还有这些引脚是干啥的就可以了。继续下一步。

我们要和LD3320进行通讯,首先要让LD3320工作,我们需要先复位芯片,激活内部DSP。
只需要将LD3320芯片的47脚【RSTB*】发一个低电平,就可以复位LD3320到初始状态。然后需要激活DSP,就需要对片选【CS】做一次拉低再拉高的操作。

复位LD3320激活内部dsp的子函数

void LD_reset()//对LD3320复位  对47脚发送低电平 然后反转片选一次 激活DSP:
{
  digitalWrite(RSTB, HIGH);
  delay(1);
  digitalWrite(RSTB, LOW);
  delay(1);
  digitalWrite(RSTB, HIGH);
  delay(1);
  cSLow();
  delay(1);
  cSHigh();
  delay(1);
  //writeReg(0xb9, 0x00);  //寄存器 0xB9  当前添加识别语句的字符串长度   初始化时写入0x00
}

激活DSP之后,就可以进行LD3320的寄存器读写了,我们使用的是SPI串行方式,根据官方的文档描述,SPI的通讯参数需要设置如下:
SPI没有官方标准,所以要好好看芯片的datasheet
在这里插入图片描述
在这里插入图片描述

在Arduino的setup里对SPI参数进行设置

void setup() {
	......
  SPI.setClockDivider(SPI_CLOCK_DIV16);  //16分频
  SPI.setDataMode(SPI_MODE2);  //时钟极性CPOL 1   时钟相位CPHA  0
  SPI.setBitOrder(MSBFIRST);  //高位在前
	......
}

接下来写一个用于读寄存器的程序,根据官方文档描述,要读寄存器,需要先发送0x05,然后再发送十六进制的寄存器地址,然后再读取芯片返回过来的十六进制数据。

读寄存器的子函数

unsigned char LD_readReg(unsigned char address) 
{
  unsigned char result;//局部变量 保存读取的数据
  cSLow();   //片选使能 低电平有效
  delay(10);
  SPI.transfer(0x05);
  SPI.transfer(address);  //发送 寄存器地址
  result = SPI.transfer(0x00);  //读取数据
  cSHigh();  //片选去使能
  return (result);   //返回数据
}

接下来写一个用于写寄存器的程序,根据官方文档描述,要写取寄存器,需要先发送0x04,然后再发送十六进制的寄存器地址,最后发送十六进制数据。

写寄存器的子函数

void LD_writeReg(unsigned char address, unsigned char value) 
{
  cSLow();//片选使能
  delay(10);
  SPI.transfer(0x04);//发送0x04  是写入模式
  SPI.transfer(address);  //发送 寄存器地址
  SPI.transfer(value);  //发送 数据
  cSHigh();//片选去使能
}

接下来,进行对LD3320寄存器的读写测试。
如果,我们先向可读写的寄存器写入某个数值,再读出来,用来检查寄存器读写是否正常。
每次先向一个寄存器写,再读出来,内容是完全正确,但是在接下来的语音识别操作中,发现LD3320芯片并不会鸟你,这是为什么呢,因为可能你的数据存在SPI的总线上,但是没有Touch到寄存器里面
所以我建议读写寄存器的序列如下:

对寄存器写入再读取的操作顺序

void setup() {
......
  LD_reset();//先复位激活LD3320
  delay(1);
  LD_readReg(0x06);//读取一次0X06寄存器
  delay(1);
  LD_writeReg(0x35, 0x33);//对寄存器0x35写入0x33
  LD_writeReg(0x1b, 0x55);//对寄存器0x1b写入0x55
  LD_writeReg(0xb3, 0xaa);//对寄存器0xb3写入0xaa
  Serial.print(LD_readReg(0x35),HEX); Serial.print(" ");//读取并打印寄存器0x35的值
  Serial.print(LD_readReg(0x1b),HEX); Serial.print(" ");//读取并打印寄存器0x1b的值
  Serial.print(LD_readReg(0xb3),HEX); Serial.println(" ");//读取并打印寄存器0xb3的值
}

就是向 3 个寄存器先依次写数据,再依次读数据出来。作为比较。
这样可以有效地验证读写寄存器是否正常。
如果结果是 33 55 aa 那么恭喜你,读写寄存器正常,可以进行下一步操作了。
这一步基本不会出错,如果出错了,请详细比对SPI的参数设置
step1.0 完整代码,在工程文档中,有需要的直接下载即可

setp 1.1 访问LD3320的三个指定寄存器,检查硬件连接的可靠性

既然已经可以正常读写LD3320的寄存器了,那为了保证LD3320稳定工作,再来检查一下硬件之间连接的可靠性。
我们在复位LD3320之后,读取寄存器0x06两次,再读取寄存器0x35和0xb3,将值打印出来。

访问三个指定寄存器的操作

void setup() {
......
  LD_reset();//先复位激活LD3320
  delay(1);
  Serial.print(LD_readReg(0x06),HEX); Serial.print(" "); //读取并打印寄存器0x06的值
  Serial.print(LD_readReg(0x06),HEX); Serial.print(" "); //读取并打印寄存器0x06的值
  Serial.print(LD_readReg(0x35),HEX); Serial.print(" "); //读取并打印寄存器0x35的值
  Serial.print(LD_readReg(0xb3),HEX); Serial.println(" ");//读取并打印寄存器0xb3的值
  }

如果打印出来的值,是 87 87 80 FF 或者 00 87 80 FF 则正常。不要把LD3320断电,复位Arduino UNO ,多读取几次,看看值是否稳定,如果稳定,则连接非常稳定。就可以进行语音识别的操作了。

setp 1.2 进行驱动程序的编写,驱动LD3320进行语音识别

现在我们进行完整驱动的编写。首先,先介绍驱动LD3320进行语音识别的流程。
ASR初始化→写入识别列表→开始识别→准备好中断函数→打开中断
然后每次识别到语音,无论是否识别到命令词,就会触发中断,运行你准备好的中断函数,正常情况是只运行一次就结束了,但是你可以在中断函数中添加ASR初始化→开始识别,继续进行下一轮识别,达到循环识别的目的。
接下来,我们按照流程来编写驱动程序。先贴两张寄存器地址的图表。
在这里插入图片描述
在这里插入图片描述

通用初始化函数,直接抄作业就可以了,都是官方例程,唯一需要注意的,就是晶振频率相关的四个变量

void LD_Init_Common()//通用初始化
{
  LD_readReg(0x06);
  LD_writeReg(0x17, 0x35);
  delay(10);
  LD_readReg(0x06);
  LD_writeReg(0x89, 0x03);
  delay(5);
  LD_writeReg(0xcf, 0x43);
  delay(5);
  LD_writeReg(0xcb, 0x02);
  LD_writeReg(0x11, PLL_11);//和晶振频率有关,注意调整
  LD_writeReg(0x1e, 0x00);
  LD_writeReg(0x19, PLL_ASR_19);//和晶振频率有关,注意调整
  LD_writeReg(0x1b, PLL_ASR_1B);//和晶振频率有关,注意调整
  LD_writeReg(0x1d, PLL_ASR_1D);//和晶振频率有关,注意调整
  delay(10);
  LD_writeReg(0xcd, 0x04);
  LD_writeReg(0x17, 0x4c);
  delay(5);
  LD_writeReg(0xb9, 0x00);
  LD_writeReg(0xcf, 0x4f);
  LD_writeReg(0x6f, 0xff);
}

ASR初始化函数,一样的,抄作业就完事了,没什么需要注意的

void LD_Init_ASR() /**语音识别初始化**/
{
  LD_Init_Common();
  LD_writeReg(0xbd, 0x00);
  LD_writeReg(0x17, 0x48);
  delay(10);
  LD_writeReg(0x3c, 0x80);
  LD_writeReg(0x3e, 0x07);
  LD_writeReg(0x38, 0xff);
  LD_writeReg(0x3a, 0x07);
  LD_writeReg(0x40, 0);
  LD_writeReg(0x42, 8);
  LD_writeReg(0x44, 0);
  LD_writeReg(0x46, 8);
  delay(1);
}

接下来,写入识别列表,也就是把自定义的命令词写入到LD3320里面。需要注意的是,我们在写入识别列表之前,要先读取0xB2寄存器的值,查看芯片的状态是否正常,如果不正常,则需要先复位一次芯片,再重新初始化,再写入。为什么会出现不正常的情况,主要原因,是电压和电流不稳定造成的。

检查0xB2寄存器的函数,返回1则正常,返回0失败

int LD_Check_ASRbusyFlag_b2()/**检查芯片状态**/
{
  for (int j = 0; j < 10; j++)
  {
    if (LD_readReg(0xb2) == 0x21)//寄存器值0x21为空闲
    {
      return 1;
    }
    delay(10);
  }
  return 0;
}

如果检查寄存器结果是正常,那么就可以写入识别词了。

写入识别列表的函数,char *pass也可以用string pass代替

int LD_ASRAddFixed(char *pass, int num) /**添加命令词**/
{
  int i; //局部变量 用来循环写入识别字的数据,并记录字符的长度
  int flag;//返回添加成功或者失败
  flag = 1;
  for (int j = 0; j < 5; j++) {//这个循环没有任何意义
    if (LD_Check_ASRbusyFlag_b2() == 0) {
      flag = 0;
      break;//如果检查到不支持,则退出,并返回添加失败
    }
    LD_writeReg(0xc1, num);//添加命令的编号
    LD_writeReg(0xc3, 0);//识别字添加时写入0x00
    LD_writeReg(0x08, 0x04);//清除数据缓存器内容
    delay(1);
    LD_writeReg(0x08, 0x00);//清除后再次写入0x00
    delay(1);
    for (i = 0; i <80; i++)//为什么i<80,因为单个命令词最多支持80字节,最好只写入79字节
    {
      if (pass[i] == 0)break;//如果读取到空字符则退出循环写入
      LD_writeReg(0x5, pass[i]);///将识别字写入寄存器0x05
    }
    LD_writeReg(0xb9, i);//在0xb9寄存器写入当前字符长度
    LD_writeReg(0xb2, 0xff);
    LD_writeReg(0x37, 0x04);//在0x37寄存器写入 0x04通知DSP 我要写入一条识别词
    break;//写入完成,退出
  }
  return flag;//返回添加成功或者失败
}

接下来,就要启动识别了。

启动识别函数,照抄就行,这些变量都在程序头部进行宏定义

int LD_AsrRun()/**启动ASR**/
{
  LD_writeReg(0x35, MIC_VOL);//在0x35寄存器 写入识别灵敏度
  LD_writeReg(0xb3, speech_endpoint);//在0xb3寄存器 写入语音端点检测功能的灵敏度  0关闭 数值越小越灵敏
  LD_writeReg(0xb4, speech_start_time);//在0xb4寄存器 写入判断语句开始的时间
  LD_writeReg(0xb5, speech_end_time);//在0xb5寄存器 写入判断语句结束的时间
  LD_writeReg(0xb6, voice_max_length);//在0xb6寄存器 写入识别语句的最长长度
  LD_writeReg(0xb7, noise_time);//在0xb7寄存器 写入初始忽略掉的底噪时间
  LD_writeReg(0x1c, 0x09);//保留命令字
  LD_writeReg(0xbd, 0x20);//保留命令字
  LD_writeReg(0x08, 0x01);//清除缓存器
  delay( 1);
  LD_writeReg(0x08, 0x00);//再次写入00
  delay( 1);
  if (LD_Check_ASRbusyFlag_b2() == 0) //查看寄存器0xb2的值是否正常
  {
    return 0;//不正常返回0 ,启动失败
  }
  LD_writeReg(0xb2, 0xff);//更新寄存器0xb2的值
  LD_writeReg(0x37, 0x06);//通知dsp开始语音识别
  delay( 5 );
  LD_writeReg(0x1c, 0x0b);//选择声音输入方式,因为我用双极电容麦,直接写入0x0B,
  LD_writeReg(0x29, 0x10);//中断开启
  LD_writeReg(0xbd, 0x00);//启动ASR模块
  return 1;//成功
}

启动之后,我们准备好中断函数,用来响应LD3320发送的中断信号,查询返回值并启动下一轮识别。

中断响应函数

void LD_ASRget()/**中断执行程序**/
{
  /****以下程序对LD3320的运算结果进行分析,取其运算结果****/
  uint8_t Asr_Count = 0;
  LD_writeReg(0x29, 0) ;
  LD_writeReg(0x02, 0) ;
  if ((LD_readReg(0x2b) & 0x10) && LD_readReg(0xb2) == 0x21 && LD_readReg(0xbf) == 0x35)
  {

    Asr_Count = LD_readReg(0xba); //读取有几个候选值
    if (Asr_Count > 0 && Asr_Count < 4)
    {
      readnum = LD_readReg(0xc5); //得到最佳选项 如果你需要其他选项,可以去另外三个寄存器掏
      readflag = 1;
    }
    else {
      Serial.println("运算结果是无法识别");
    }
    LD_writeReg(0x2b, 0);
    LD_writeReg(0x1C, 0);
  }
  else {
    Serial.println("运算结果是无法识别");
  }
  /****以下程序对LD3320进行初始化,预备进行下一次识别****/
    LD_readReg(0x06);
    delay(10);
    LD_readReg(0x06);
    LD_writeReg(0x89, 0x03);
    delay(5);
    LD_writeReg(0xcf, 0x43);
    delay(5);
    LD_writeReg(0xcb, 0x02);
    LD_writeReg(0x11, PLL_11);
    LD_writeReg(0x1e, 0x00);
    LD_writeReg(0x19, PLL_ASR_19);
    LD_writeReg(0x1b, PLL_ASR_1B);
    LD_writeReg(0x1d, PLL_ASR_1D);
    delay(10);
    LD_writeReg(0xcd, 0x04);
    LD_writeReg(0x17, 0x4c);
    delay(5);
    LD_writeReg(0xcf, 0x4f);
    LD_writeReg(0xbd, 0x00);
    LD_writeReg(0x17, 0x48);
    delay(10);
    LD_writeReg(0x3c, 0x80);
    LD_writeReg(0x3e, 0x07);
    LD_writeReg(0x38, 0xff);
    LD_writeReg(0x3a, 0x07);
    LD_writeReg(0x40, 0);
    LD_writeReg(0x42, 8);
    LD_writeReg(0x44, 0);
    LD_writeReg(0x46, 8);
    delay(1);
    LD_writeReg(0x1c, 0x09);
    LD_writeReg(0xbd, 0x20);
    LD_writeReg(0x08, 0x01);
    delay( 1);
    LD_writeReg(0x08, 0x00);
    delay( 1);
    LD_writeReg(0xb2, 0xff);
    LD_writeReg(0x37, 0x06);
    delay( 5 );
    LD_writeReg(0x1c, 0x0b);
    LD_writeReg(0x29, 0x10);
    LD_writeReg(0xbd, 0x00);
}

把这个丢在LOOP里面循环运行

int LD_Read()
{
  if (readflag == 1)
  {
    readflag = 0;
    return readnum;
  }
  return -1;
}

所有的子函数都准备完毕了,接下来,开始识别!
Let’s Go!!!

再来个运行语音识别的子函数,我保证是最后一个了

int RunASR()
{
  int asrflag = 0;
  for (int z = 0; z < 5; z++) {//循环尝试五轮
    LD_Init_ASR();//进行ASR初始化
    delay(100);//延时时间自己控制,可以改5ms
    if (LD_ASRAddFixed("kai deng", 1) == 0) {//添加命令词kai deng,编号是1,注意,两个字的拼音用空格隔开
      LD_reset();                   //如果失败了,复位芯片
      delay(100);
      continue;                     //进行下一轮尝试
    }
    if (LD_ASRAddFixed("guan deng", 2) == 0) {//添加命令词guan deng,编号是2
      LD_reset();//如果失败了,复位芯片
      delay(100);
      continue;//进行下一轮尝试
    }
    if (LD_AsrRun() == 0) {//启动识别
      LD_reset();//如果失败了,复位芯片
      delay(100);
      continue;//进行下一轮尝试
    }
    asrflag = 1;//运行到这里了,说明上面的步骤都成功了
    break;//退出循环
  }
  return asrflag;//返回状态,成功或者失败
}

接下来调用RunASR()函数就可以启动语音识别了,当然你也可以这样写

if(RunASR()==0){
	Serial.println("ASR启动失败");
}
else 
	Serial.println("ASR启动成功");

然后就可以愉快得尝试了。

二、第二部分 用模拟SPI编写LD3320的驱动程序

模拟SPI通讯程序例程

时隔不知多少日,我又来更新了~~~~~~~~~~
这部分内容来介绍一下模拟SPI通讯,为什么要这样做呢,因为部分的单片机没有硬件SPI的支持,只能通过普通引脚来模拟SPI,达到通讯的目的,不过这样实现的通讯,往往速度较硬件SPI慢的不止一点点,也不够稳定,只能说,勉强够用。
先附上时序图,其实和硬件SPI的时序一毛一样。
在这里插入图片描述
在这里插入图片描述
和常规的 SPI通讯一样,查看时序图可知这四个引脚的状态。CS引脚(片选引脚SCS),低电平有效,也就是说在开始通讯之前,要先将CS引脚置为低电平,结束通讯之后,将CS引脚置为高电平。CLK引脚(时钟信号引脚SDCK),在开始通讯之后,要送出稳定的时钟信号,下降沿有效,在CLK送出下降沿的时候,MOSI(SDI)或者MISO(SDO)线上的电平信号才被记录为一个位数据。
写寄存器的操作和之前一样,要先写入一个字节0x04(写入寄存器的指令,读出寄存器为0x05),再写入一个字节(地址的值),最后写入数据。如图所示的SDI线上的时序,分为了三个字节位置,分别是指令字节、地址字节、数据字节,每个四节各八位。众所周知,0b1111 1111是0xFF,写寄存器的图中指令字节是0b 0000 0100,也就是0x04,这是8421-BCD码的知识点,不会的同学自己百度哦。

废话不多说,直接上程序。

头部宏定义
nop根据单片机的运行周期自行调整,没有很严格的要求

#define nop 0.07
#define CS D4 
#define SPI_MOSI_PIN  D11
#define SPI_SCK_PIN   D13
#define SPI_MISO_PIN  D12

模拟SPI写寄存器的部分程序
程序分成三个部分,分别为写入指令,写入地址,写入数据,三个流程结束,就成功完成了写寄存器的操作

void LD_writeReg(unsigned char address, unsigned char dataout)
{
  unsigned char i = 0;
  unsigned char Command = 0x04;//写寄存器的指令
  cSLow();//片选使能
  delayMicroseconds(nop * 3);//延时三个机器周期,如果通讯不正常,调整这里的延时时间可以解决问题
  //写入 指令
  for ( i = 0; i < 8; i++)//循环八次  循环结束就完整得写入了 Command 
  {
    if ((Command & 0x80) == 0x80)//按位与 Command & 0b 1000 0000  判断最高位是否为1
      digitalWrite(SPI_MOSI_PIN, HIGH);//置为高
    else
      digitalWrite(SPI_MOSI_PIN, LOW);//置为低
    delayMicroseconds(nop * 3);//延时三个机器周期
    digitalWrite(SPI_SCK_PIN, LOW);//时钟信号拉低
    Command = (Command << 1);//将Command 左移一位 
    delayMicroseconds(nop * 3);//延时三个机器周期
    digitalWrite(SPI_SCK_PIN, HIGH);//时钟信号拉高
  }
  //写入 地址 (和写入指令的操作一样)
  for (i = 0; i < 8; i++)
  {
    if ((address & 0x80) == 0x80)
      digitalWrite(SPI_MOSI_PIN, HIGH);
    else
      digitalWrite(SPI_MOSI_PIN, LOW);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, LOW);
    address = (address << 1);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, HIGH);
  }
  delayMicroseconds(nop * 3);
  //写入 数据(和写入指令的操作一样)
  for (i = 0; i < 8; i++)
  {
    if ((dataout & 0x80) == 0x80)
      digitalWrite(SPI_MOSI_PIN, HIGH);
    else
      digitalWrite(SPI_MOSI_PIN, LOW);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, LOW);
    dataout = (dataout << 1);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, HIGH);
  }
  delayMicroseconds(nop * 3);//延时三个系统周期
  cSHigh();//片选去使能
}

模拟SPI读寄存器的部分程序
程序分成三个部分,分别为写入指令,写入地址,读取数据,三个流程结束,就成功完成了读寄存器的操作

unsigned char LD_readReg(unsigned char address)
{
  unsigned char i = 0;
  unsigned char datain = 0 ;//用于存储读取到字节完整数据
  unsigned char Temp = 0;//用于存储读取到的字节单个位的数据
  unsigned char Command = 0x05;//读寄存器的指令
  cSLow();//片选使能
  delayMicroseconds(nop * 3);//延时三个系统周期
  //写入 指令(和之前一样的操作,就不打备注了)
  for ( i = 0; i < 8; i++)
  {
    if ((Command & 0x80) == 0x80)
      digitalWrite(SPI_MOSI_PIN, HIGH);
    else
      digitalWrite(SPI_MOSI_PIN, LOW);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, LOW);
    Command = (Command << 1);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, HIGH);
  }
  //写入 地址(和之前一样的操作,就不打备注了)
  for (i = 0; i < 8; i++)
  {
    if ((address & 0x80) == 0x80)
      digitalWrite(SPI_MOSI_PIN, HIGH);
    else
      digitalWrite(SPI_MOSI_PIN, LOW);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, LOW);
    address = (address << 1);
    delayMicroseconds(nop * 3);
    digitalWrite(SPI_SCK_PIN, HIGH);
  }
  delayMicroseconds(nop * 3);
  //读取数据
  for (i = 0; i < 8; i++)
  {
    datain = (datain << 1);//将datain 左移一位
    Temp = digitalRead(SPI_MISO_PIN);//读取到一位数据
    delayMicroseconds(nop * 3);//延时三个系统周期
    digitalWrite(SPI_SCK_PIN, LOW);//时钟信号拉低
    if (Temp == 1) {//如果Temp 为1
      datain |= 0x01;//或等于  将datain 的最低位变为1
    }
    delayMicroseconds(nop * 3);//延时三个系统周期
    digitalWrite(SPI_SCK_PIN, HIGH);//时钟信号拉高
  }
  delayMicroseconds(nop * 3);//延时三个系统周期
  cSHigh();//片选去使能
  return datain;//返回最后结果
}

这里给不懂 & 和 | 的同学稍微解释一下

& 在C语言里面是位与运算符,| 在C语言里面是位或运算符,例如:

int a=0x03;//0b 0000 0011
int b=0x02;//0b 0000 0010
int c,d;
c=a&b;
d=a|b;
//c的值为0x02  0&1=0  1&1=1  0&0=0
//d的值为0x03  0|1=1  1|1=1  0|0=0

&= 和 |= 的意思就是与等于和或等于,例如:
a&=b 的意思就是,先将a和b按位与,再将结果赋值给a
a|=b 的意思就是,先将a和b按位或,再将结果赋值给a

好了跑题了,回到正题,上述通过模拟SPI读写寄存器的操作就完成了,后面的驱动部分,按照第一部分硬件SPI驱动LD3320的方法移植过来就好了。
补充一点,驱动程序里面的外部中断响应操作不规范,正常情况下,读取到外部中断后,应该先关闭该外部中断,然后去执行一系列中断触发的程序,执行结束后重新打开该外部中断。建议修改。
文章结尾附上源码和PCB的下载链接
https://download.csdn.net/download/qq_40532525/85002112?spm=1001.2014.3001.5503

三、IIC修改命令词

原理很简单,就是通过自己拟定的代码命令,来操控语音识别模块的初始化等等进程,唯一需要注意的是,Arduino的IIC通讯缓存区只有32个字节,所以这边建议将命令词进行切割分块发送,最后发送一个自拟定的结束命令,通知模块我发送完了,你可以装载命令词了,具体方法查看例程3.1。例程2.3里面有切割字符串并载入到3320里面的例程。
祝大家玩的愉快,LD3320的内容就告一段落了。

  • 8
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
在我之前的项目中,我展示了如何使用Arduino开发板和BitVoicer服务器控制几个LED 。在这个项目中,我将使事情变得更加复杂。我还将使用Arduino DUE数模转换器(DAC)合成语音。如果您没有Arduino DUE,则可以使用其他Arduino板,但是您将需要一个外部DAC和一些其他代码来操作DAC(BVSSpeaker库将无法帮助您)。 在下面的视频中,您可以看到我还让Arduino播放了一首歌曲,并使LED闪烁,就像它们是钢琴键一样。对不起,我的钢琴技巧,但这是我能做到的最好的:)。LED实际上以与真实C,D和E键相同的顺序和时序闪烁,因此,如果您周围有钢琴,则可以跟随LED并播放同一首歌曲。这是一个不再存在的老零售商(Mappin)的叮当声。 将执行以下过程将语音命令转换为LED活动和合成语音: 1. Sparkfun Electret Breakout板将捕获并放大音频波; 2.放大后的信号将通过Arduino的模数转换器(ADC)进行数字化和缓冲; 3.音频样本将使用Arduino串行端口传输到BitVoicer服务器; 4. BitVoicer服务器将处理音频流并识别其包含的语音; 5.识别的语音将映射到预定义的命令,这些命令将发送回Arduino。如果其中一个命令用于合成语音,则BitVoicer Server将准备音频流并将其发送到Arduino; 6. Arduino将识别命令并执行适当的操作。如果接收到音频流,它将被排队到BVSSpeaker类中,并使用DUE DAC和DMA播放。 7. SparkFun单声道音频放大器会放大DAC信号,因此可以驱动8欧姆扬声器。 第一步是将Arduino和面包板与组件连接,如下图所示。我必须在扬声器下方放置一个小的橡胶垫,因为它会振动很多,而没有橡胶垫的话,音频质量会受到很大影响。 在这里,与我以前的项目相比,有一个小但重要的区别。大多数Arduino板均以5V运行,但DUE以3.3V运行。因为在3.3V下运行Sparkfun驻极体突破效果更好,所以如果您使用5V Arduino板,建议您在3.3V引脚和AREF引脚之间添加一个跳线。DUE已经使用了3.3V模拟基准,因此您不需要AREF引脚的跳线。实际上,DUE上的AREF引脚通过电阻桥连接到微控制器。要使用AREF引脚,必须从PCB上拆下电阻器BR1。
LD3320识别芯片介绍: LD3320 是一颗基于非特定人语音识别 (SI-ASR:Speaker-Independent Automatic Speech Recognition)技术的语音识别/声控芯片。提供了真正的单芯片语音识别解决方案。 LD3320 芯片上集成了高精度的 A/D 和 D/A 接口,不再需要外接辅助的Flash 和 RAM,即可以实现语音识别/声控/人机对话功能。并且,识别的关键词语列表是可以动态编辑的。 基于 LD3320,可以在任何的电子产品中,甚至包括简单的 51 作为主控芯片的系统中,轻松实现语音识别/声控/人机对话功能。为所有的电子产品增加 VUI(Voice User Interface)语音用户操作界面。 LD3320语音识别模块视频演示: 语音识别LD3320模块主要特色功能: 非特定人语音识别技术:不需要用户进行录音训练 可动态编辑的识别关键词语列表:只需要把识别的关键词语以字符串的形式传送进芯片,即可以在下次识别中立即生效。比如,用户在 51 等 MCU 的编程中,简单地通过设置芯片的寄存器,把诸如&ldquo;你好”这样的识别关键词的内容动态地传入芯片中,芯片就可以识别这样设定的关键词语了。 真正单芯片解决方案:不需要任何外接的辅助 Flash 和 RAM,真正降低系统成本。 内置高精度 A/D和D/A通道:不需要外接 AD 芯片,只需要把麦克风接在芯片的AD 引脚上;可以播放声音文件,并提供 550mW 的内置放大器。 高准确度和实用的语音识别效果。 支持用户自由编辑 50 条关键词语条:在同一时刻,最多在 50 条关键词语中进行识别,终端用户可以根据场景需要,随时编辑和更新这 50 条关键词语的内容。 LD3320与Arduino实物连接图: 相关链接:LD3320 在Arduino上的应用 LD3320模块主要技术参数内置单声道mono 16-bit A/D 模数转换 内置双声道stereo 16-bit D/A 数模转换 内置 20mW 双声道耳机放大器输出 内置 550mW 单声道扬声器放大器输出 支持并行接口或者 SPI 接口 内置锁相电路 PLL,输入主控时钟频率为 2MHz - 34MHz 工作电压:(VDD: for internal core) 3.3V 48pin 的 QFN 7*7 标准封装 省电模式耗电:1uA 内置单声道mono 16-bit A/D 模数转换 内置双声道stereo 16-bit D/A 数模转换 内置 20mW 双声道耳机放大器输出 内置 550mW 单声道扬声器放大器输出 支持并行接口或者 SPI 接口 内置锁相电路 PLL,输入主控时钟频率为 2MHz - 34MHz 工作电压:(VDD: for internal core) 3.3V 48pin 的 QFN 7*7 标准封装 省电模式耗电:1uA LD3320示例程序截图(具体的演示详见附件内容): 技术文档截图: 实物购买链接:https://www.waveshare.net/shop/LD3320-Board.htm
### 回答1: ld3320语音识别模块是一种基于语音识别技术的模块,可以与Arduino开发板进行连接,实现语音控制等功能。它可以识别多种语音指令,并输出相应的控制信号,非常适合用于智能家居、智能机器人等领域。同时,该模块还具有较高的识别准确率和稳定性,可以满足各种应用场景的需求。 ### 回答2: LD3320是一种语音识别模块,能够与Arduino开发板进行连接使用。它是基于ARM Cortex-M3架构并集成了一定的语音识别算法。它通过自适应的噪音抑制算法和环境适应算法,能够在高噪音环境下进行语音识别,并且还具有语音合成的功能。除此之外,LD3320还支持在线语音识别和离线语音识别两种方式。 LD3320的使用比较简单,只需要将模块通过串口连接到Arduino板上,然后通过Arduino的软件串口进行控制即可。在使用之前,需要先将模块进行一定的配置,比如设置识别语言、设置识别模式等。在使用中,可以通过Arduino的代码进行语音的录入、识别和合成,并且还能够根据识别结果进行一定的控制,比如控制LED灯的开关、控制舵机的旋转等等。 LD3320的应用范围比较广泛,可以用于智能家居、智能车、语音机器人等场景。比如,在智能家居场景下,可以通过LD3320实现语音控制家电的开关或者调节家居环境的亮度、温度等功能;在智能车场景下,可以通过LD3320实现语音控制小车的行驶方向或者控制小车的避障;在语音机器人场景下,可以通过LD3320实现语音交互,让机器人能够识别用户的语音指令,并根据指令进行一定的动作或者回答用户的问题。 总的来说,LD3320语音识别模块是一种功能强大、使用简单的语音识别设备,能够为智能化场景带来更加人性化的交互方式,具有非常广阔的应用前景。 ### 回答3: LD3320语音识别模块是一款基于语音识别技术的Arduino模块,它具有高度的可扩展性和开放性。该模块支持多国语言的语音识别和语音合成,并且可以通过串口或I2C接口与单片机、电脑等设备连接。 该模块内置了高性能的DSP芯片和自适应算法,可以实现高质量、准确、稳定的语音识别和语音合成。同时,该模块还支持唤醒功能,可以通过语音或外部信号来唤醒系统。 在使用该模块时,需要先对其进行初始化和配置,然后再进行语音识别和语音合成。可以通过Arduino IDE或其他串口工具来实现模块的配置和控制。 在实际应用中,该模块可以广泛应用于智能家居、智能机器人、语音导航、语音控制等场景,为用户提供更加便捷、智能的交互方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值