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


文章目录

  • 基于Arduino的LD3320语音识别模块设计详解
  • 前言
  • 一、LD3320驱动编写
  • step 1.0 使用Arduino的SPI库,通过硬件SPI和LD3320通讯,读写寄存器
  • setp 1.1 访问LD3320的三个指定寄存器,检查硬件连接的可靠性
  • setp 1.2 进行驱动程序的编写,驱动LD3320进行语音识别
  • 二、第二部分 用模拟SPI编写LD3320的驱动程序
  • 模拟SPI通讯程序例程
  • 三、IIC修改命令词


前言

本文章为记录本人的学习过程,最终目的是设计一款IIC通讯方式的语音识别模块,该模块的主要功能,就是识别程序中设定的指令词,并返回识别结果,指令词和对应的返回编号可在程序中任意修改,不需要去给语音识别模块烧录固件,支持命令词的动态编辑。

模块硬件包含一块单片机芯片,一块LD3320芯片,以及外围电路。PCB工程已开源,网上到处都有。

基础功能:

1.动态编辑命令词,不需要烧录模块的固件

2.识别成功后返回对应结果

文章将会记录我从零开始的调试步骤以及遇到的问题,还有解决问题的详细思路。

接下来我们开始。

工程已上传(PCB工程+模块固件+Demo),有需要的可以先下载,链接挂在末尾

基于Arduino的智能家居语音识别系统 arduino做语音识别_stm32


基于Arduino的智能家居语音识别系统 arduino做语音识别_单片机_02

一、LD3320驱动编写

根据ICroute官方文档所写,想要驱动LD3320进行语音识别,可以使用软硬并口通讯方式对LD3320的寄存器进行读写从而驱动LD3320,也可以使用软硬SPI串行通讯方式对LD3320的寄存器进行读写,也可以驱动LD3320。需要注意的是,模拟并口驱动方式官方并不推荐,不仅速度慢,也会造成通讯不稳定等情况。推荐使用硬件SPI进行驱动,如果MCU没有SPI的硬件接口,也可以使用普通I/O口模拟SPI进行通讯,较为稳定。

回到正题,我手头正好有一块网上买的LD3320模块,是不带单片机的那种,板子上只有LD3320的那种。

基于Arduino的智能家居语音识别系统 arduino做语音识别_stm32_03

直接先拿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的智能家居语音识别系统 arduino做语音识别_单片机_04


基于Arduino的智能家居语音识别系统 arduino做语音识别_寄存器_05

在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初始化→开始识别,继续进行下一轮识别,达到循环识别的目的。

接下来,我们按照流程来编写驱动程序。先贴两张寄存器地址的图表。

基于Arduino的智能家居语音识别系统 arduino做语音识别_单片机_06


基于Arduino的智能家居语音识别系统 arduino做语音识别_寄存器_07

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

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的时序一毛一样。

基于Arduino的智能家居语音识别系统 arduino做语音识别_寄存器_08


基于Arduino的智能家居语音识别系统 arduino做语音识别_51单片机_09


和常规的 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的下载链接

三、IIC修改命令词

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