基于LD3320语音识别模块的智能照明系统
一、模块的使用
本次识别系统主要是使用了“全球鹰电子-LD3320+STC11”的模块作为核心模块,再加上以SSD1306的0.96寸OLED屏作为显示,普通LED灯做了照明的简易系统设置。
二、系统运行原理
因为LD3320语音识别模块内置了STC11系列单片机的芯片,所以本次系统以STC11作为数据处理以及收发的核心。当咪头接收到外界语音信号后通过LD3320芯片解析数据,在获取到正确的数据后通过之前定义的宏定义将获取到的数据的宏定义值传递到STC11芯片,芯片通过数据进行处理,同时通过操作IO口完成对LED灯的亮灭控制以及亮度控制,最终也会在OLED屏上显示出识别到的口令。具体操作流程如下图:
三、代码源码分析
3.1 系统初始化代码讲解
MCU_init();//单片机初始化
LD_Reset();//复位LD模块
Timer0_Init();//初始化定时器,用于模拟PWM波来控制LED灯的亮度
UartIni(); //串口初始化
OLED_Init();//初始化OLED
OLED_ColorTurn(0);//0正常显示,1 反色显示
OLED_DisplayTurn(0);//0正常显示 1 屏幕翻转显示
OLED_Display_On();//开启OLED显示
3.2 语音识别循环代码讲解
while(1)
{
switch(nAsrStatus)//nAsrStatus 用来在main主程序中表示程序运行的状态,不是LD3320芯片内部的状态寄存器
{
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: //一次ASR识别流程结束,去取ASR识别结果
{
nAsrRes = LD_GetResult(); //获取结果
User_handle(nAsrRes); //将获取到的结果传递到用户想执行的函数进行后续操作
nAsrStatus = LD_ASR_NONE; //复位nAsrStatus,等待下一次识别结果
break;
}
case LD_ASR_FOUNDZERO:
default:
{
nAsrStatus = LD_ASR_NONE;
break;
}
}
}
整个系统是在一个while循环中不断运行,当LD3320在中断函数中获取到一个有效值后会将nAsrStatus 的值
设置为LD_ASR_FOUNDOK,此时switch循环就会将获取到的数值传递到User_handle函数中进行下一步的处理。
3.3 LD3320语音识别模块中断服务函数
void ProcessInt0(void)
{
uint8 nAsrResCount=0;
EX0=0;
ucRegVal = LD_ReadReg(0x2B);
LD_WriteReg(0x29,0) ;
LD_WriteReg(0x02,0) ;
if((ucRegVal & 0x10) &&
LD_ReadReg(0xb2)==0x21 &&
LD_ReadReg(0xbf)==0x35) /*识别成功*/
{
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);/*写0:ADC不可用*/
LD_WriteReg(0x29,0) ;
LD_WriteReg(0x02,0) ;
LD_WriteReg(0x2B, 0);
LD_WriteReg(0xBA, 0);
LD_WriteReg(0xBC,0);
LD_WriteReg(0x08,1); /*清除FIFO_DATA*/
LD_WriteReg(0x08,0); /*清除FIFO_DATA后 再次写0*/
EX0=1;
}
此函数为LD3320语音模块厂商提供的中断服务函数,在中断读取特定寄存器并判断此时已经获取到一个较优结果
后会将nAsrStatus标志位置为LD_ASR_FOUNDOK,以便于前面while循环进行后续操作
3.4 OLED显示函数
/*
**********************************************************************************
* 函数功能: OLED屏显示字符串
* 函数形参: display_page 显示起始页
* display_column 显示起始列
* *display_memory 显示数据的首地址
* 函数返回值: None
* 备注: 字体大小要求16*16
**********************************************************************************
*/
void oled_display_arbitrary_string(uint8_t display_page, uint8_t display_column, uint8_t *display_string)
{
uint16_t chinese_characters_number = 0;
/* 如果当前字符不是'\0'说明需要显示字符 */
while(*display_string != '\0')
{
if(*display_string > 0X80) //当前需要显示的字符是中文
{
chinese_characters_number = 0;
while(1)//查找是第几个字
{
if((*display_string == chinese_character_code_list[2 * chinese_characters_number]) && (*(display_string + 1) == chinese_character_code_list[2 * chinese_characters_number + 1]))
{
break; //已经找到该字了
}
chinese_characters_number++;
if(chinese_character_code_list[2 * chinese_characters_number] == '\0') //当前没有这个中文字
{
break;
}
}
if(chinese_character_code_list[2 * chinese_characters_number] != '\0')//有这个字才显示
{
if((128 - display_column) < 16)//自动换行
{
display_column = 0;
display_page += 2;
}
oled_display_chinese_character(display_page,display_column,(u8 *)&chinese_character_buff[chinese_characters_number * 32]);
display_column += 16;
display_string += 2;
}
}
else //当前需要显示的是英文
{
if((128 - display_column) < 8)//自动换行
{
display_column = 0;
display_page += 2;
}
chinese_characters_number = *display_string - ' ';
oled_display_english_character(display_page,display_column,(u8 *)&english_character_buff[chinese_characters_number * 16]);
display_column += 8;
display_string += 1;
}
}
}
OLED显示函数主要是使用PCtolLCD生成特定的字库,然后在获取到需要显示数据后将数据通过添点的方式发送到OLED
的寄存器中将其显示出来,此处代码与网上大部分显示代码类似,故不做解释。
3.5 用户执行函数
用户执行函数会因为用户的需求不同的内容不同,本系统的用户执行函数主要是用于执行对LED的亮灭以及亮度的控制,在获取到内容后同时将内容通过OLED显示到屏幕上,由于STC11芯片存储空间有限,所以只能用ASCII来显示需要显示的内容。
控制灯的亮灭我使用了两种方式,一种是控制多盏LED灯,识别到增加亮度则打开多一盏LED灯,识别到降低亮度则关闭一盏LED灯,关闭则是关闭全部LED灯;第二种方式则是通过PWM波控制,由于我未在STC11上找到用于PWM波的IO口,所以我只能通过在定时器中不断的拉高拉低IO以达到在一定时间中IO口高点电平时长不同来达到LED灯亮度上的不同。但是由于OLED使用的IIC通信协议也是使用IO口继续模拟,在使用定时器模拟PWM波时会导致IIC通信受到很大的干扰从而导致OLED显示内容缓慢,此问题还未解决。
/***********************************************************
* 名 称:用户执行函数
* 功 能:当识别成功后可以根据识别到的内容进行相应的操作
* 入口参数:无
* 出口参数:无
* 说 明:
**********************************************************/
void User_handle(uint8 dat)
{
//UARTSendByte(dat);//串口识别码(十六进制)
static uint8_t open_flag = 0;
if(dat == CODE_CMD)
{
G0_flag=ENABLE;
LED=0;
PrintCom("在的,小策指令已收到\r\n");
OLED_Display_On();
OLED_Clear();
oled_display_arbitrary_string(2,4,"I'M READY!");
}
else if(ENABLE==G0_flag)
{
G0_flag=DISABLE;
switch(dat)
{
case CODE_OPEN:
PrintCom("“开灯”命令识别成功\r\n");
if(open_flag == 0)
{
open_flag = 1;
}
else
{
break;
}
#if USE_PWM
LCD_WWITE_ONE=0;
ET0=1;//开定时器0中断
TR0=1;//启动定时器0
#else
light = 1;
#endif
break;
case CODE_CLOSE:
PrintCom("“关灯”命令识别成功\r\n");
#if USE_PWM
ET0=0;//关闭时器0中断
TR0=0;//关闭定时器0
LCD_WWITE_ONE=1;
#else
light = 0;
open_flag = 0;
#endif
break;
case CODE_UP:
PrintCom("“增加亮度”命令识别成功\r\n");
light++;
if(light >= 3)//最大亮度三级
{
light = 3;
}
break;
case CODE_DOWN:
PrintCom("“降低亮度”命令识别成功\r\n");
light--;
if(light <= 1)//最小亮度一级
{
light = 1;
}
break;
case CODE_NIGHT:
PrintCom("“晚安”命令识别成功\r\n");
#if USE_PWM
LCD_WWITE_ONE=1;
ET0=0;//关闭时器0中断
TR0=0;//关闭定时器0
#else
light = 0;
open_flag = 0;
#endif
break;
case CODE_CLOSE_OLED:
OLED_Display_Off();
show_flag = 0;
break;
case CODE_OPEN_OLED:
OLED_Display_On();
show_flag = 1;
break;
default:
READ_DATA_ERR();
PrintCom("识别失败\r\n");
break;
}
switch(light)
{
case 0:
LCD_WWITE_ONE = 1;
LCD_WWITE_TWO = 1;
LCD_WWITE_THREE = 1;
break;
case 1: //亮度一级
#if USE_PWM
use_time = 1;
#else
LCD_WWITE_ONE = 0;
LCD_WWITE_TWO = 1;
LCD_WWITE_THREE = 1;
#endif
break;
case 2: //亮度二级
#if USE_PWM
use_time = 25;
#else
LCD_WWITE_ONE = 0;
LCD_WWITE_TWO = 0;
LCD_WWITE_THREE = 1;
#endif
break;
case 3: //亮度三级
#if USE_PWM
use_time = 50;
#else
LCD_WWITE_ONE = 0;
LCD_WWITE_TWO = 0;
LCD_WWITE_THREE = 0;
#endif
break;
default:
break;
}
//由于定时器模拟PWM波会对显示照成影响导致系统响应过慢,
//所以在执行完命令后再显示
if(show_flag)
{
switch(dat)
{
case CODE_OPEN:
OLED_Clear();
oled_display_arbitrary_string(1,4,"OK! OPEN THE LIGHT!");
break;
case CODE_CLOSE:
OLED_Clear();
oled_display_arbitrary_string(1,4,"OK! CLOSE THE LIGHT!");
break;
case CODE_UP:
OLED_Clear();
oled_display_arbitrary_string(1,4,"OK! ADD LEVEL!");
break;
case CODE_DOWN:
OLED_Clear();
oled_display_arbitrary_string(1,4,"OK! REDUCE LEVEL!");
break;
case CODE_NIGHT:
OLED_Clear();
oled_display_arbitrary_string(1,4,"GOOD NIGHT!");
OLED_Display_Off();
break;
case CODE_OPEN_OLED:
OLED_Clear();
oled_display_arbitrary_string(1,4,"OLED OPEN!");
break;
default:
OLED_Clear();
oled_display_arbitrary_string(1,4,"NOT COMMAND");
break;
}
}
}
else
{
READ_DATA_ERR();
PrintCom("请说出一级口令\r\n");
}
}