基于STM32(F103ZE)的LD3320語音控制系統(tǒng)
暑假快完了也什么成果,就打算做點東西來讓這個暑假充實點帽撑。發(fā)現(xiàn)手頭有一個LD3320語音模塊叛薯,又剛學C#上位機,就想結(jié)合一個做一個小項目瞧掺。
第一次寫,多多包含凡傅。
我的目的是:可以語音控制一個LED燈 如果有條件就控制明亮度然后在LCD上顯示出來辟狈,并且在上位機上也可以顯示當前狀態(tài)和控制LED狀態(tài)。
材料
- STM32開發(fā)板(我用的是正點原子的戰(zhàn)艦V3 STM32F103ZET6大家也可以用自己的開發(fā)板做)
- LD3320語音模塊
- 杜邦線
- 嗯 還需要電腦軟件
- keil4 或 keil5 因為下載的例程文件是keil4寫的 而手頭LCD屏的頭文件都是keil5戰(zhàn)艦的 所以兩個都用到了
-VS2017
-串口調(diào)試助手 我用的是XCOME
然后就開始做了夏跷。
制作過程
- 硬件部分
想去找一些關于LD3320的資料哼转。
我先去淘寶找了這個模塊,發(fā)現(xiàn)有瀏覽了幾家商家之后發(fā)現(xiàn)了資料關于LD3320的槽华,很完整壹蔓。附上料地址http://www.waveshare.net/wiki/LD3320_Board
發(fā)現(xiàn)他有keil4已經(jīng)寫好的程序,我就下載下來猫态,然后又找到引腳圖
**雖然已經(jīng)有圖了佣蓉,但是還要提醒下VCC和GND千萬不要接反了E恪! **
這樣硬件部分就連接好了勇凭,接下來就是程序和上位機了-
- 程序部分
拿到例程后發(fā)現(xiàn)他的LED燈的端口不同疚膊,改成我們板子的LED
#define LED1_PIN GPIO_Pin_5
#define LED1_GPIO_PORT GPIOB
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED2_PIN GPIO_Pin_5
#define LED2_GPIO_PORT GPIOE
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOE
改之后,看他已經(jīng)寫的指令有哪些虾标,有“流水燈”寓盗、“閃爍”、“按鍵觸發(fā)”璧函、“全滅”傀蚌、“狀態(tài)”這幾個,試試管用不管 看串口部分配置是 115200 的波特率蘸吓,打開串口調(diào)試工具善炫。
設置好波特率之后打開串口 按復位鍵 對著模塊用清晰的普通話講“流水燈” (哈哈 開始說的幾次都沒識別出來 還以為有問題 慢點清晰后成功率明顯提高了。
附圖:
第一部分 嘗試就成功了 接下來就是 修改代碼 加上LCD 在LCD上也可以顯示當時命令和狀態(tài)库继。
還有就是把發(fā)送給上位機的串口數(shù)據(jù)做好 以便上位機能更好的識別命令和執(zhí)行命令销部。
找了半天,戰(zhàn)艦V3的LCD就只有正點原子哥寫好的制跟,是用keil5寫的,還是在sys的支持下(因為sys頭文件我在移植到keil4上時就有很多error)酱虎,所以只能在5上寫了雨膨。首先先創(chuàng)個工程把源代碼原封不動的移植到5上,執(zhí)行后結(jié)果正常读串,然后添加LCD的程序聊记,因為sys頭文件包含了delay、sys恢暖、usart所以串口部分也要替換掉排监,在主函數(shù)里添加頭文件 初始化等函數(shù);
//添加頭文集
#include "delay.h"
#include "sys.h"
//主函數(shù)下添加
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
uart_init(115200);
然后試試杰捂,也正常舆床。
接下來就是添加LCD程序
//添加頭文件
#include "lcd.h" LCD_Init();
//主函數(shù)里添加
LCD_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(72,100,200,16,16,"Hello STM32!");
除了在代碼里加這些 還要在工程項目里把lcd的.c文件添加進去 在路含 lcd文件夾
顯示正常 然后我發(fā)現(xiàn)這樣就只能寫英文 不能寫漢字 于是就添加了漢字顯示
//在lcd.c下添加
void LCD_ShowChinese(u16 x,u16 y,u8 num,u8 size,u8 mode)
{
u8 csize;
u8 temp,t1,t;
u16 y0=y;
csize=(2*(size/8+((size%8)?1:0))*(size/2)); //得到字體一個字符對應點陣集所占的字節(jié)數(shù)
for(t=0;t<csize;t++)
{
if(size==12)temp=chinese_12[num][t]; //調(diào)用1206字體
else if(size==16)temp=chinese_16[num][t]; //調(diào)用1608字體
else if(size==24)temp=chinese_24[num][t]; //調(diào)用2412字體
else return; //沒有字體庫
for(t1=0;t1<8;t1++)
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
temp<<=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
//在lcd.h下添加
void LCD_ShowChinese(u16 x,u16 y,u8 num,u8 size,u8 mode);//在指定位置顯示一個漢字
因為用的時16*16大小的字體 所以在字庫里添加字組時要添加到const unsigned char chinese_16[40][32]
里。
接下來就是字體取模 我用這個PCtoLCD2002軟件嫁佳,配置好后,添加我們要用到的字體挨队。
下邊就是我們要的字組,復制到剛才說的數(shù)組里蒿往,在程序中添加 就OK了盛垦,還有好多漢字 我就不一一列舉了,方法是一樣的瓤漏。 注意要把數(shù)字變大足以裝下字組腾夯。
//主函數(shù)下添加
LCD_ShowChinese(29,40,5,16,1); //開
LCD_ShowChinese(46,40,6,16,1); //始
LCD_ShowChinese(63,40,7,16,1); //運
LCD_ShowChinese(78,40,8,16,1); //行
LCD_ShowChinese(95,40,9,16,1); //L
LCD_ShowChinese(103,40,10,16,1); //D
LCD_ShowChinese(111,40,11,16,1); //3
LCD_ShowChinese(119,40,12,16,1); //3
LCD_ShowChinese(127,40,13,16,1); //2
LCD_ShowChinese(135,40,14,16,1); //0
LCD_ShowChinese(143,40,15,16,1); //測
LCD_ShowChinese(160,40,16,16,1); //試
LCD_ShowChinese(177,40,17,16,1); //程
LCD_ShowChinese(194,40,18,16,1); //序
這樣就可以正常顯示漢字了
然后找到LD3320.c文件下 語識別后要執(zhí)行的部分 添加要顯示的漢字 這樣就做好了颊埃。注意:要在顯示之前添加
LCD_ShowString(72,100,200,16,16," ");
這樣顯示其下次的會把上次的覆蓋掉,顯示的就是新的內(nèi)容蝶俱。 試試
添加或修改口令
在LD3320.c里
//在LD_AsrAddFixed函數(shù)中sRecog[DATE_A][DATE_B]數(shù)組里加上你要說的口令的拼音
//比如第二個閃爍
uint8 sRecog[DATE_A][DATE_B] = {
"liu shui deng",\
"shan shuo",\
"an jian chu fa",\
"quan mie",\
"zhuang tai"\
};
//和我的一樣即可
//在pCode[DATE_A]數(shù)組中添加指令班利,數(shù)組序號要對應比如上邊第二個是閃爍,這個數(shù)組中就要相應閃爍的函數(shù)
//比如第二個ss
uint8 pCode[DATE_A] = {
CODE_LSD, \
CODE_SS, \
CODE_AJCF,\
CODE_QM, \
CODE_JT \
};
//在Board_text函數(shù)中添加相應執(zhí)行的函數(shù)
//比如第二個Flicker_LED()
static void Board_text(uint8 Code_Val)
{
switch(Code_Val) //???á1??′DD?à1?2ù×÷
{
case CODE_LSD: //?üá??°á÷??μ??±
Glide_LED();
break;
case CODE_SS: //?üá??°éá???±
Flicker_LED();
break;
case CODE_AJCF: //?üá??°°′?ü′¥·¢?±
Key_LED();
break;
case CODE_QM: //?üá??°è??e?±
Off_LED();
break;
case CODE_JT: //?üá??°×′ì??±
Jt_LED();
break;
default:break;
}
}
//在下邊添加相應函數(shù)所要執(zhí)行的動作
//Flicker_LED函數(shù)
static void Flicker_LED(void)
{
LED1_ON();
LED2_ON();
Delayms(0XFFF);
LED1_OFF();
LED2_OFF();
Delayms(0XFFF);
}
這樣就添加了我們想要的命令了
完美跷乐!
接下來就是給上位機寫好數(shù)據(jù) 我們用串口1肥败。除了添加中斷是否接收完成,還要添加們要用指令愕提。
代碼如下:
//在usart.c或者stm32f10x_it.c里添加
void USART1_IRQHandler(void) //串口1中斷服務程序
{
u8 Res;
int i=0;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真馒稍,則需要支持OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收的數(shù)據(jù)必須是0x0d 0x0a結(jié)尾)
{
Res =USART_ReceiveData(USART1); //讀取接收到的數(shù)據(jù)
if((USART_RX_STA&0x8000)==0)//接受未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始
else USART_RX_STA|=0x8000; //接收完成了
if(USART_RX_STA&0x8000)
{
if(strcmp(USART_RX_BUF,"全亮")==0)
{
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
if(strcmp(USART_RX_BUF,"第一個")==0)
{
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
if(strcmp(USART_RX_BUF,"第二個")==0)
{
GPIO_SetBits(GPIOE,GPIO_Pin_5);
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
if(strcmp(USART_RX_BUF,"全滅")==0)
{
GPIO_SetBits(GPIOE,GPIO_Pin_5);
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
USART_RX_STA=0x00000;
for(i=0;i<USART_REC_LEN;i++)
{
USART_RX_BUF[i]=0;
}
}
}
else //還沒收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0; //接收數(shù)據(jù)錯誤重新開始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真浅侨,則需要支持OS.
OSIntExit();
#endif
}
#endif
- 軟件部分
我要做一個能控制LED燈 并且可以顯示當前LED狀態(tài)的上位機
經(jīng)過不斷變化 最后設計的形象是這樣的
在打開時自動掃描端口纽谒。
for (int i = 0; i < 20; i++)
{
try
{
string str = "COM" + i;
serialPort1.PortName = str;
serialPort1.Open();
comboBox1.Items.Add(str);
serialPort1.Close();
comboBox1.Text = str;
}
catch { }
}
打開軟件后自動監(jiān)測COM口,然后波特率是115200點擊連接如输,連接成功鼓黔,連接按鈕就會變成斷開,連接失敗就會彈出錯誤窗口不见。注意:如果在啟動軟件的時候沒有把板子插上并打開澳化,那么在打開軟件的時候就不會有COM口,這是就需要通電板子并重新打開軟件稳吮。
在連接之后就要監(jiān)測LED當前狀態(tài)缎谷,在keil5程序中,在監(jiān)測到正確命令后灶似,就會發(fā)送一個字符串printf("流水燈列林,指令識別成功")
的提示,我們需要做的就是監(jiān)測是否有”流水燈“字樣的字符酪惭。我想在一個窗口顯示接收到的數(shù)據(jù)希痴,一個窗口顯示目前的LED狀態(tài)。遇到的問題就是serialPort1.ReadExisting();
把接收代碼里的字符串直接去識別的話是識別不成功的春感,如果去識別serialPort1.ReadLine();
的話砌创,可以成功識別,但是會造成顯示窗口的卡死鲫懒,為了解決這個問題纺铭,我又加了一個textbox控件,但是這個控件并不顯示刀疙,只是拿到數(shù)據(jù)去做對比舶赔。這樣就解決了這個問題。代碼如下:
textBox2.Text=serialPort1.ReadExisting();
textBox1.AppendText(textBox2.Text);
if (textBox2.Text.Contains("流"))
textBox3.Text = "流水燈";
else if (textBox2.Text.Contains("閃"))
textBox3.Text = "閃爍";
else if (textBox2.Text.Contains("按"))
textBox3.Text = "按鍵觸發(fā)";
else if (textBox2.Text.Contains("全"))
textBox3.Text = "全滅";
else if (textBox2.Text.Contains("狀"))
textBox3.Text = "狀態(tài)";
這樣就可以顯示了谦秧。
附圖:
:
最后就是控制LED 竟纳。因為程序一直在中斷中運行撵溃,就需要不斷給板子發(fā)命令,他才可以一直保持這種狀態(tài)锥累。這里我用一個定時器控件缘挑,當按鍵觸發(fā)后,每隔很短的時間就給板子發(fā)送命令桶略。代碼如下:
private void button2_Click(object sender, EventArgs e)
{
if (button2.Text == "打開")
try
{
button2.Text = "關閉";
timer1.Start();
}
catch { }
else
{
button2.Text = "打開";
timer1.Stop();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
try
{
serialPort1.Write(comboBox3.Text + Environment + Environment.NewLine);
textBox3.Text = comboBox3.Text;
}
catch
{ }
}
成功了语淘!這樣這個項目就完成了!
最后附上完整的項目文件际歼。自行下載惶翻。
https://download.csdn.net/download/weixin_42320020/16594044
還有很多不足的地方,希望大佬可以指正鹅心。也希望和愛好者交流學習吕粗。
第一次寫,使用的很不熟練旭愧,寫的不好希望不要介意颅筋。以后還會寫。望多多交流學習输枯。
禁止轉(zhuǎn)載R楸谩!桃熄!
QQ:2039723308
VX:Shiboven