语音识别芯片LD3320驱动程序
1、芯片复位
复位就是对LD3320芯片的第47腿(RSTB*)发送低电平,然后需要对片选CS做一次拉低→拉 高的操作,以激活内部DSP。按照以下顺序:
void LD_reset()
{
RSTB=1;delay(1);RSTB=0;delay(1);RSTB=1;delay(1);
CSB=0;delay(1);CSB=1;delay(1);
}
delay(1)是为了更稳定地工作。 初始化一般在程序的开始进行,如果有时芯片的反应不太正常,也可用这个方法恢复芯片初始状态。
(1)通用初始化
按照以下序列设置寄存器。
void LD_Init_Common()
{
bMp3Play=0;
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);
/*PLL setting*/
LD_WriteReg(0x11,LD_PLL_11);
if(nLD_Mode==LD_MODE_MP3)
{
LD_WriteReg(0x1E,0x00);
//!!注意,下面三个寄存器,会随晶振频率变化而设置不同
//!!注意,请根据使用的晶振频率修改参考程序中的CLK_IN
LD_WriteReg(0x19,LD_PLL_MP3_19);
LD_WriteReg(0x1B,LD_PLL_MP3_1B);
LD_WriteReg(0x1D,LD_PLL_MP3_1D);
}
else
{
LD_WriteReg(0x1E,0x00);
//!!注意,下面三个寄存器,会随晶振频率变化而设置不同
//!!注意,请根据使用的晶振频率修改参考程序中的CLK_IN
LD_WriteReg(0x19,LD_PLL_ASR_19);
LD_WriteReg(0x1B,LD_PLL_ASR_1B);
LD_WriteReg(0x1D,LD_PLL_ASR_1D);
}
LD_WriteReg(0xCD,0x04);LD_WriteReg(0x17,0x4c);delay(5);
LD_WriteReg(0xB9,0x00);LD_WriteReg(0xCF,0x4f);
LD_WriteReg(0x6F,0xFF);
}
(2)语音识别用初始化
按照以下序列设置寄存器。
void LD_Init_ASR()
{
nLD_Mode=LD_MODE_ASR_RUN;
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,0x00);LD_WriteReg(0x42,0x08);
LD_WriteReg(0x44,0x00);LD_WriteReg(0x46,0x08);delay(1);
}
(3)写入识别列表
列表的规则是:每个识别条目对应一个特定的编号(1个字节),不同的识别条目的编号可以相同,而且不用连续。本芯片最多支持50个识别条目,每个识别条目是标准普通话的汉语拼音(小写),每2个字(汉语拼音)之间用一个空格间隔。下面是一个简单的例子:
编号可以相同,可以不连续,但是数值要小于256(00H~FFH)。例子中的“北京”和“首都”对应同一编号,说这两个词会有相同的结果返回。简单流程图如下:
代码如下:
#define CODE_DEFAULT 0
#define CODE_BEIJING 1
#define CODE_SHANGHAI 3
#define CODE_TIANJIN 7
#define CODE_CHONGQING 8
先介绍一个读取0xB2寄存器的函数,如果在以后的ASR命令函数前不能够读取到正确Idle状态,说明芯片内部可能出错了。当使用的电源电压/电流出现不稳定有较大波动时,有小概率会出现这种情况。出现这种情况时,建议Reset LD3320芯片,重新启动设置芯片。
// Return 1: success.
uint8 LD_Check_ASRBusyFlag_b2()
{
uint8 j;uint8 flag=0;
for(j=0;j<10;j++)
{
if(LD_ReadReg(0xb2)= 0x21)
{flag=1;break;}
delay(10);
}
return flag;
}
// Return 1: success.
uint8 LD_AsrAddFixed()
{
uint8 k, flag;
uint8 nAsrAddLength;
const char sRecog[5][13]={"bei jing","shou du","shang hai","tian jin", "chong qing"};
const uint8 pCode[5]={CODE_BEIJING,CODE_BEIJING,CODE_SHANGHAI, CODE_TIANJIN,CODE_CHONGQING};
flag=1;
for(k=0;k<5;k++)
{
if(LD_Check_ASRBusyFlag_b2()==0)
{flag=0;break;}
LD_WriteReg(0xc1, pCode[k] );
LD_WriteReg(0xc3, 0 );
LD_WriteReg(0x08, 0x04); Delay(1);
LD_WriteReg(0x08, 0x00); Delay(1);
for(nAsrAddLength=0;nAsrAddLength<20;nAsrAddLength++)
{
if(sRecog[k][nAsrAddLength]==0) break;
LD_WriteReg(0x5,sRecog[k][nAsrAddLength]);
}
LD_WriteReg(0xb9, nAsrAddLength);
LD_WriteReg(0xb2, 0xff);
LD_WriteReg(0x37, 0x04);
}
return flag;
}
(4)开始识别
设置几个相关的寄存器,就可以控制LD3320芯片开始语音识别。 值得注意:单片机程序中,一般会用一个全局变量控制当前状态,(例如:LD_ASR_RUNING状态或者LD_ASR_FOUNDOK状态),在编程时一定要把对该状态的设置放在正式LD3320芯片开始识别以前,例如下面例程中的语句nAsrStatus=LD_ASR_RUNING;便是设置控制变量。需要把这句语句放置在LD3320正式开始识别之前调用:
{
。。。
nAsrStatus=LD_ASR_RUNING;LD_AsrRun();
}
参考代码如下:
// Return 1: success.
uint8 LD_AsrRun()
{
nAsrStatus=LD_ASR_RUNING;
LD_WriteReg(0x35,MIC_VOL);
LD_WriteReg(0x1C,0x09);
LD_WriteReg(0xBD,0x20);
LD_WriteReg(0x08,0x01);delay(1);
LD_WriteReg(0x08,0x00);delay(1);
if(LD_Check_ASRBusyFlag_b2()==0)
{return 0;}
LD_WriteReg(0xB2,0xff);
LD_WriteReg(0x37,0x06);delay(5);
LD_WriteReg(0x1C,0x0b);
LD_WriteReg(0x29,0x10);
LD_WriteReg(0xBD,0x00);
EX0=1;
return 1;
}
综上所述:语音识别的流程可以总结成如下的参考代码,可以以此为参考,根据使用流程进行合理的改动。
uint8 RunASR()
{
uint8 i=0;uint8 asrflag=0;
for(i=0;i<5;i++)
{
LD_Init_ASR();delay(100);
if(LD_AsrAddFixed()==0)
{LD_reset();delay(100);continue;}
delay(10);
if(LD_AsrRun()==0)
{LD_reset();delay(100);continue;}
asrflag=1;
break;
}
return asrflag;
}
对上述函数的调用参考代码:
文件main.c 函数main()
case LD_ASR_NONE:
{
nAsrStatus=LD_ASR_RUNING;
if (RunASR()==0)
{nAsrStatus=LD_ASR_ERROR;LED1=0;LED2=0;}
break;
}
(5)响应中断
如果麦克风采集到声音,不管是否识别出正常结果,都会产生一个中断信号。而中断程序要根据寄存器的值分析结果。
读取BA寄存器的值,可以知道有几个候选答案,而C5寄存器里的答案是得分最高、最可能正确的答案。
例如发音为“上海”并被成功识别(无其他候选),那么BA寄存器里的数值是1,而C5寄存器里的值是对应的编码3。
以下为简单流程图:
程序代码如下:
void ExtInt0Handler(void) interrupt 0 {ProcessInt0();}
void ProcessInt0()
{
uint8 nAsrResCount=0;EX0=0;ET0=0;
ucRegVal=LD_ReadReg(0x2B);
ucHighInt=LD_ReadReg(0x29);
LD_WriteReg(0x29,0);
ucLowInt=LD_ReadReg(0x02);
LD_WriteReg(0x02,0);
if(nLD_Mode==LD_MODE_ASR_RUN)
{
if((ucRegVal&0x10)&&LD_ReadReg(0xbf)==0x35&&LD_ReadReg(0xb2)==0x21)
{
nAsrResCount=LD_ReadReg(0xba);
if(nAsrResCount>0&&nAsrResCount<4) {nAsrStatus=LD_ASR_FOUNDOK;}
else{nAsrStatus=LD_ASR_FOUNDZERO;}
}
else{nAsrStatus=LD_ASR_FOUNDZERO;}
LD_WriteReg(0x2b,0);
LD_WriteReg(0x1C,0);
ET0=1;
return;
}
}
uint8 LD_GetResult() {return LD_ReadReg(0xc5);}
值得注意:获取识别结果 LD_ReadReg(0xba); 多少条候选识别结果,值1~4说明是有正确的识别结果。
4个候选结果的读取:根据0xba决定读取几个识别结果。
LD_ReadReg(0xc5);
LD_ReadReg(0xc7);
LD_ReadReg(0xc9);
LD_ReadReg(0xcb);
在目前的Demo程序中,只读取了最优候选。在其他使用场合,如果需要读取其他候选,用户可以自己编程实现。
3、声音播放
播放声音的操作顺序是:
MP3播放用初始化(包括通用初始化)→调节播放音量→开始播放声音, 并准备好中断响应函数,打开中断允许位。
(1)通用初始化
和语音识别部分一样,按指定序列设置寄存器。
(2)声音播放用初始化 请参照源代码设置寄存器。
void LD_Init_MP3()
{
nLD_Mode = LD_MODE_MP3;
LD_Init_Common();
LD_WriteReg(0xBD,0x02);
LD_WriteReg(0x17, 0x48); delay(10);
LD_WriteReg(0x85,0x52);
LD_WriteReg(0x8F,0x00);
LD_WriteReg(0x81,0x00);
LD_WriteReg(0x83,0x00);
LD_WriteReg(0x8E,0xff);
LD_WriteReg(0x8D,0xff); delay(1);
LD_WriteReg(0x87,0xff);
LD_WriteReg(0x89,0xff); delay(1);
LD_WriteReg(0x22,0x00);
LD_WriteReg(0x23,0x00);
LD_WriteReg(0x20,0xef);
LD_WriteReg(0x21,0x07);
LD_WriteReg(0x24, 0x77);
LD_WriteReg(0x25,0x03);
LD_WriteReg(0x26, 0xbb);
LD_WriteReg(0x27,0x01);
}
(3)调节播放音量
这里需要修改寄存器8E。 音量分为16级,用4位二进制表示,范围是0-15。 设置音量时,将(15-音量值) 设给寄存器8E的第2-5位。
源代码如下:
void LD_AdjustMIX2SPVolume(uint8 val)
{
uint8 ucTmp;
ucSPVol=val;
val=((15-val)&0x0f)<<2;
LD_WriteReg(0x8E,val|0xc3);
}
这个函数只调节喇叭(Speaker)的音量,和耳机等其他输出无关。而且实验板上只有喇叭连接了输出。
耳机音量是可以调节的,使用寄存器81(左耳音量)和83(右耳音量)。 2个寄存器都是只有第1-5位有效,共32级,而且这5位都为0(00000B)的时候是音量最大的,都为1(11111B)的时候音量最小。
(4)开始播放声音
开始播放位置清零(自定义变量Mp3Pos=0)
寄存器1B的第3位设为1(按位或0x08)
循环执行:
while(【播放条件】=true)
{
顺序将MP3数据放入寄存器01(每次一个字节);
Mp3Pos增加1
}
【播放条件】为下面条件都成立,有一个不满足就跳出循环:
读取寄存器06,第3位=0
Mp3Pos < MP3文件的总长度。
修改BA 、17等寄存器。(参照源代码)
开放中断允许。例如,EX0=1。
开始播放的简单流程图如下:
源代码如下:
void LD_play()
{
nMp3Pos=0;bMp3Play=1;
if(nMp3Pos>=nMp3Size)return ;
LD_ReloadMp3Data();
LD_WriteReg(0xBA,0x00);
LD_WriteReg(0x17,0x48);
LD_WriteReg(0x33,0x01);
LD_WriteReg(0x29,0x04);
LD_WriteReg(0x02,0x01);
LD_WriteReg(0x85,0x5A);
EX0=1;
}
void LD_ReloadMp3Data()
{
uint32 nCurMp3Pos;uint8 val;uint8 k;
nCurMp3Pos=nMp3StartPos+nMp3Pos;
FLASH_CS=1;FLASH_CLK=0;FLASH_CS=0;
IO_Send_Byte(W25P_FastReadData); /* read command */
IO_Send_Byte(((nCurMp3Pos&0xFFFFFF)>>16)); /* send 3 address bytes */
IO_Send_Byte(((nCurMp3Pos&0xFFFF)>>8));
IO_Send_Byte(nCurMp3Pos&0xFF);
IO_Send_Byte(0xFF);
ucStatus=LD_ReadReg(0x06);
while(!(ucStatus&MASK_FIFO_STATUS_AFULL)&&(nMp3Pos<nMp3Size))
{
val=0;
for(k=0;k<8;k++){FLASH_CLK=0;val<<=1;FLASH_CLK=1;val|=FLASH_DO;}
LD_WriteReg(0x01,val);nMp3Pos++;ucStatus=LD_ReadReg(0x06);
}
FLASH_CS=1;FLASH_CLK=0;
}
LD_ReloadMp3Data()函数的功能是送入数据,不同的硬件结构可能需要改写这一部分。例如有的系统可能使用大容量的RAM,取数据就会很方便。这里是根据串行FLASH存储器的接口写的函数,使用的是SPI协议。
(5)中断响应。
开始播放可以把声音数据的最初部分送入芯片,等到芯片播放这一段后会发出中断请求。而中断函数里会不断的送入数据,直到FIFO_DATA装满或声音数据结束。这一段程序和开始播放比较类似,都是通过LD_ReloadMp3Data()函数送入数据。
由于LD3320芯片只有一只管脚负责中断请求输出,所以一般情况下用一个中断响应函数处理2种中断。这里为了简明,将中断函数分开书写。
中断处理函数里,播放声音部分流程图如下:
要注意的是,寄存器操作2)和寄存器操作3) 并不会恢复寄存器02和29的内容。因为这时播放已经结束了,应该让02,29清零。 而数据重载结束后(达到了继续播放的条件),需要恢复寄存器02,29。 以便于继续出中断,连续播放。
源代码是:
void ExtInt0Handler(void) interrupt 0{ProcessInt0();}
void ProcessInt0()
{
uint8 nAsrResCount=0;
EX0=0;ET0=0;
ucRegVal=LD_ReadReg(0x2B);
ucHighInt=LD_ReadReg(0x29);
ucLowInt=LD_ReadReg(0x02);
LD_WriteReg(0x29,0);
LD_WriteReg(0x02,0);
if(nLD_Mode==LD_MODE_MP3)
{
if(LD_ReadReg(0xBA)&CAUSE_MP3_SONG_END)
{
LD_WriteReg(0x2B,0);
LD_WriteReg(0xBA,0x00);
LD_WriteReg(0xBC,0x00);
bMp3Play=0;
LD_WriteReg(0x08,0x01);
LD_WriteReg(0x08,0x00);
LD_WriteReg(0x33,0x00);
return ;
}
if(nMp3Pos>=nMp3Size)
{
LD_WriteReg(0xBC,0x01);
LD_WriteReg(0x29,0x10);
EX0=1;
return;
}
LD_ReloadMp3Data();
LD_WriteReg(0x29,ucHighInt);
LD_WriteReg(0x02,ucLowInt) ;
EX0=1;
}
}
补充说明
1、此芯片的特色是兼有语音识别和MP3播放的两项功能,但是由于这两项功能会使用一些公用的资源,所以为了使芯片稳定地工作,在功能切换的时候,必须从最“通用初始化”开始,对芯片进行一系列的设置。
2、当芯片长时间没有响应时,可能是应用程序的设置不合理或者是电源的电压/电流有比较大的波动造成。这时应使用芯片复位的功能(对芯片的RTSB*发送低电平),使芯片重新开始工作。