電子實訓課程實驗項目
????????????????????? --電子琴
【前言】
?????? 為進一步激發(fā)學生對于硬件編程的興趣而開展的課程“電子實訓”課程到目前為止已經要告一段落了。將近四周的時間,從電路板印刷、貼片參觀,到自己親手將原件焊接到電路板上,再到一步一步熟悉STC編程當中的技巧,我們漸漸對硬件編程有了初步的認識,雖然并不一定能完成什么高級的設計,但是對目前所涉及到的數(shù)碼管、LED、撥碼開關、中斷、定時器、傳感器的應用已經有了初步認識,也可以寫出一些簡單的應用。為驗證十幾天的學習成果,每個人都會根據(jù)給出的53個例程當中選擇自己比較熟悉或者擅長的方面進行一個創(chuàng)新設計或者擴展設計,也或者融合多個方面。
?? 我完成的項目是一個“電子琴”,基于工程“電子音樂”改編而來,不過也做出了很多的改變,也增加了一些東西,使得整個項目看起來還算不錯。
【實驗目的與要求】
1、???熟練掌握定時器的應用,以及如何通過定時器驅動蜂鳴器發(fā)出一定頻率的聲音,掌握簡譜與定時器重裝值之間的關系,能夠實現(xiàn)蜂鳴器譜樂;
2、???熟悉下位機編程,usb轉串口原理應用,相關寄存器的使用,并且實現(xiàn)該功能,為上、下位機通信奠定基礎;
3、???熟悉上位機編程方法,可以參考相關資料,設計并實現(xiàn)上位機;
【設計概要】
本案例的設計主要可以分為兩個部分,上位機部分的設計和下位機部分的設計
上位機:
?? 案例的上位機是用MFC以及MFC中的串口控件來完成的。上位機中共有8個按鍵,一個下拉框,三個復選框,七個按鍵完成對音符頻率的選擇,對應簡譜中的DO,RE,MI,FA,SO,LA,XI,每當一個按鍵被按下的時候,上位機會通過串口將一個字節(jié)發(fā)送到單片機,在發(fā)送的時候,上位機會檢測被選中的復選框,若選中的是“低八度”,則會發(fā)送低八度對應的音節(jié);若選中的是“中八度”,則會發(fā)送中八度對應的音節(jié),高八度也類似。下拉框控制的是串口號的選擇,列出了電腦主機所配置的串口號,串口號下面有一個“打開窗口”的按鈕,當點擊的時候,上位機會檢測下拉框中選擇的串口號,并將其對應的串口號打開。在點擊的同時,上位機會設置串口的相關屬性值,m_ctrlComm.SetSettings("9600,n,8,1");設置串口波特率為9600,無校驗位,8個數(shù)據(jù)位,1個停止位,并設置以二進制方式撿取數(shù)據(jù),清空緩沖區(qū)。
下位機:
? 下位機設計的重要部分在于串口和蜂鳴器兩個部分。串口部分使用的是串口1中斷,串口1中斷使用TH1和TL1作為串口中斷的計時器,使用TL1=(65536-(Machine_Focs/4/BAUD1));TH1=(65536-(Machine_Focs/4/BAUD1))>>8作為重裝值,當串口發(fā)生中斷的時候,會觸發(fā)中斷處理程序。中斷處理程序中會將已經轉換成1的RI端口和TI端口轉換成0,返回源程序,開始下一次中斷的歷程,并且會觸發(fā)playmusic函數(shù),將display中的音節(jié)在蜂鳴器中播放。
測試方法:
(1)打開“piano”工程文件,找到“hex”文件;
(2)打開ISP下載器,選中該“hex文件”,選中對應的端口,點擊下載;
(3)下載完成,打開上位機“test.exe”,選中對應的端口,點擊“打開串口”;
(4)串口打開,選中對應的音階,低八度,中八度,或者高八度,然后點擊按鈕,蜂鳴器發(fā)出聲音;
(5)測試完成;
?
【實驗原理】
1、實驗原理圖
(1)無源蜂鳴器電路原理圖
?????????????????????????????
(2)芯片相關引腳圖
?
(3)單片機下載電路
?
2、USB轉串口原理:單片機集成了USB轉串口模塊,對應使用RXD線接收數(shù)據(jù),用TXD發(fā)送數(shù)據(jù)。每個串口由2個數(shù)據(jù)緩沖器(相互獨立1收1發(fā))、一個移位寄存器(一字節(jié)數(shù)據(jù)一位一位發(fā)送出去)、一個串行控制器和一個波特率發(fā)生器(這個比較重要,結合相關的定時器)組成。對應發(fā)送、接收數(shù)據(jù)完成(RI、TI硬件置1)都會觸發(fā)串口中斷,但是無法確定是哪個觸發(fā)的,所以在串口中斷中我們要判斷是接收數(shù)據(jù)產生的中斷還是發(fā)送數(shù)據(jù)產生的中斷,對于發(fā)送數(shù)據(jù)產生的中斷,我們要軟件將TI清0,并將數(shù)據(jù)就緒標志清0,允許下一字節(jié)數(shù)據(jù)發(fā)送,發(fā)送數(shù)據(jù)函數(shù)中通過while循環(huán),等待發(fā)送數(shù)據(jù)準備就緒,完了將就緒的數(shù)據(jù)復制給SBUF;對于接收數(shù)據(jù)產生的中斷,我們要軟件將RI清0,并從SBUF中讀取數(shù)據(jù)。
3、蜂鳴器原理:
本實驗板采用的是無源蜂鳴器,無源內部不帶震蕩源,所以如果用直流信號無法令其鳴叫。必須用2K~5K的方波去驅動它。相比與有源蜂鳴器,無源蜂鳴器的優(yōu)點在于價格便宜,可以通過控制其振動頻率來改變發(fā)出的聲音,做出“多來米發(fā)索拉西”的效果。因此,無源蜂鳴器可以用于音樂的播放。而有源蜂鳴器的優(yōu)點在于使用簡單,不需要編寫“樂譜”。本實驗板使用的無源蜂鳴器是電磁式蜂鳴器,電磁式蜂鳴器由振蕩器、電磁線圈、磁鐵、振動膜片及外殼等組成。接通電源后,接收到的音頻信號電流通過電磁線圈,使電磁線圈產生磁場。振動膜片在電磁線圈和磁鐵的相互作用下,周期性地振動發(fā)聲。
蜂鳴器部分每個音符都有自己對應的頻率,每個頻率也多有自己對應的簡譜碼,每一個簡譜碼都對應了一個計時器重裝值,比如說低1DO頻率是262Hz,則振動周期T=1000ms/262Hz=3.817ms,要求定時器在3.817ms會產生一個周期振動,由于一個周期振動是兩次電平變化,而每一次定時器中斷只有一次電平變化,所以要將定時器設置成1.908ms震動一次,也就是1908us,因此可以得到定時器重裝值為65536-1908=63628。
?
【源碼展示與說明】
下位機部分:
/*文件名稱piano.c
通過串口用上位機控制單片機上面的蜂鳴器發(fā)出不同頻率的聲音
*/
#include"STC15F2K60S2.H"
//宏定義
#defineuchar unsigned char
#defineuint unsigned int
?
#defineMachine_Focs 11059200L? ?????????? //晶振頻率 11.0592MHz
#defineBAUD1 9600??????????????????????? //波特率,這里使用的是9600
?
sbitLED_SEL=P2^3;
sbitbeep=P3^4;????????? ? ??????????????? //蜂鳴器引腳
uchartimeh,timel;?????? ? ??????????????? //定時器的重裝值
?
/*收發(fā)數(shù)據(jù)相關*/
uchardisplay;??????????????????????????? //單片機上SBUF緩沖的數(shù)據(jù)
ucharflag;
intcount=0;????????????????????????????? //計數(shù)器,用來分頻
ucharduanxuan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,
0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
????????????????????????????????????????? //段選信號,選擇0-f
/*蜂鳴器振動頻率相關*/
ucharcode quzi[] ={ ? ??????????? ?????? //此數(shù)組作為各個音符在定時器中的重裝值
//低八度
0xf8,0x8c,0xf9,0x5b,0xfa,0x15,0xfa,0x67,0xfb,0x04,0xfb,0x90,0xfc,0x0c,
//中八度
0xfc,0x44,0xfc,0xac,0xfd,0x09,0xfd,0x34,0xfd,0x82,0xfd,0xc8,0xfe,0x06,
//高八度
0xfe,0x22,0xfe,0x56,0xfe,0x6e,0xfe,0x9a,0xfe,0xc1,0xfe,0xe4,0xff,0x03
};
/***************************************************************
曲調
****************************************************************/
ucharquyin(uchar tem)
{
??? uchar qudiao,jp,weizhi;???? ? //定義曲調,音符,位置
??? qudiao=tem/16;????????????? ? //高四位為是曲調值
??? jp=tem%16;?????????????? ? ???//低四位是音符
??? if(qudiao==1)??????????? ? ???//當曲調值為1的時候,低八度,低八度在quzi數(shù)組中的基址為0
??? qudiao=0;
??? else if(qudiao==2)????????? ? //當曲調值為2的時候,中八度,中八度在quzi數(shù)組中的基址為14
?????? qudiao=14;
??? else if(qudiao==3)????????? ? //當曲調值為3的時候,高八度,高八度在quzi數(shù)組中的基址為28
?
?????? qudiao=28;
??? weizhi=qudiao+(jp-1)*2;???? ? //基址加上偏移量得到音符對應數(shù)組中的位置
??? return weizhi;????????????? ? //返回位置值
}
/**********************
函數(shù)名稱:void delay(unsigned int xms)
功能描述:延時
入口參數(shù):xms:輸入需要延時的毫秒值
出口參數(shù):無
***********************/
voiddelay(uint xms)
{
??? uint i;
??? for(; xms>0; xms--)???????????????????
?????? for(i=114; i>0; i--)
?????? {}
}???
/**********************
函數(shù)名稱:Timer0
功能描述:定時器0的中斷響應函數(shù),用來控制蜂鳴器
***********************/
voidTimer0() interrupt 1?? ?
{
??? count++;
??? if (count==4)
??? {
?????? count=0;
??? }
??? TH0=timeh;
??? TL0=timel;
??? if(flag==1&&count==3)????????????? //四分頻
??? {
??? ?beep=~beep;
??? }
}
/**************************************************************
函數(shù)名稱:Uart1_Init
功能描述:初始化串口中斷
***************************************************************/
voidUart1_Init(void)
{
??? AUXR=0X80;??????????????????????????????? //輔助寄存器,使T0x12=1,此時不分頻
??? SCON|=0X50; ????????????????????????????? //SM0=0,SM1=1,串口以方式1工作,8位Uart,串行口1用定時器1作為其波特率發(fā)生器且定時器1工作于模式0;REN=1,開啟串口接收
??? TL1=(65536-(Machine_Focs/4/BAUD1));
??? TH1=(65536-(Machine_Focs/4/BAUD1))>>8;
??? AUXR|=0X40;?????????????????????????????? //使T0x12=1,此時不分頻
??? RI=0;????? ?????????????????????????????? //接收終端標志位
??? TI=0;????? ?????????????????????????????? //發(fā)送中斷標志位
??? TR1=1;???? ?????????????????????????????? //啟動的定時器1
??? ES=1;????? ?????????????????????????????? //串口中斷允許位
??? EA=1;????? ?????????????????????????????? //總中斷允許位
}
/**************************************************************
函數(shù)名稱:Init
功能描述:完成各部分功能模塊的初始化
***************************************************************/
voidInit()?????????????????????????????????? //初始化操作
{
??? P3M0=0x00;??????????????????????????????? //推挽模式
??? P3M1=0x00;
??? P2M0=0xff;
? ? P2M1=0x00;
? ? P0M0=0xff;
? ? P0M1=0x00;
??? TMOD=0x01;??????????????????????????????? //定時器0,方式1,要求每一次中斷之后手動重裝
??? ET0=1;??????????????????????????????????? //開啟定時器0中斷
??? EA=1;??????????????????????????????????? //開啟總中斷
??? TH0=0x00;
??? TL0=0x00;
??? TR0=1;???? ??????????????????????????????? //啟動定時器0
??? beep=0;?????????????????????????????????? //蜂鳴器初始化0
??? flag=0;
??? P0=0;
??? Uart1_Init();???????????????????????????? //串口中斷
??? display = 0x00;???????????????? ?????????? //初始化數(shù)據(jù)緩沖器
??? LED_SEL=0;??????????????????????????????? //設置數(shù)碼管顯示狀態(tài)
}
/**********************
函數(shù)名稱:void playmusic()
功能描述:播放音樂
***********************/
voidplaymusic(uchar p)?????????????????????? //p為音節(jié)
{
??? uchar tem;?????????????????????????????? ???
??? tem=quyin(p);???? ???????????????????????? //找到p音節(jié)在quzi數(shù)組中的位置
??? timeh=quzi[tem];? ?????? ????????????????? //音節(jié)重裝值的高八位
??? timel=quzi[tem+1];??????????????????????? //音節(jié)重裝值的低八位
??? TR0=1;???????????? ???????????????????????//開啟定時器0中斷
??? delay(0x10*180);??????????? ?????????? ???//延時一個節(jié)拍?
??? TR0=0;??????????????????????????????????? //關閉定時器0中斷
}
/*************************************************************
串口1中斷相應程序
**************************************************************/
voidUart1_fun() interrupt 4
{
??? if(RI)??????? ?????????????????????????? //接受完數(shù)據(jù)以后,RI自動轉1
??? {
?????? flag=1;
?????? RI=0;
?????? display=SBUF;
?????? //TR0=1;
?????? playmusic(display);
??? }
??? //if(TI)????? ??? ??????????????????????? //接受完數(shù)據(jù)以后,RI自動轉1
??? //{
?????? //TI=0;
?????? //Uart1_Sendbusy=0;
??? //}
}
/**************************************************************
主函數(shù)
***************************************************************/
voidmain()?????????????????????????????????????
{
??? Init();???
??? while(1){}??????????????????????????????? //執(zhí)行死循環(huán)
}
?
上位機部分:
int nIndex =m_chuankou_select.GetCurSel();
CString strCBText;
m_chuankou_select.GetLBText(nIndex,strCBText);
用來控制從下拉框m_chuankou_select中獲得已經選擇的串口號,并且將其存儲在字符串strCBText中;
if(strCBText=="")
?????? AfxMessageBox("請選擇串口");
??? else if(strCBText=="com1")
?????? m_ctrlComm.SetCommPort(1);
??? else if(strCBText=="com2")
?????? m_ctrlComm.SetCommPort(2);
。。。。。。
用來打開strCBText對應的串口號
m_ctrlComm.SetSettings("9600,n,8,1");//波特率9600,無校驗,8個數(shù)據(jù)位,1個停止位? ?m_ctrlComm.SetInputMode(1); //1:表示以二進制方式檢取數(shù)據(jù)?
m_ctrlComm.SetRThreshold(1);//參數(shù)1表示每當串口接收緩沖區(qū)中有多于或等于1個字符時將引發(fā)一個接收數(shù)據(jù)的OnComm事件?
m_ctrlComm.SetInputLen(0);//設置當前接收區(qū)數(shù)據(jù)長度為0?
m_ctrlComm.GetInput();//先預讀緩沖區(qū)以清除殘留數(shù)據(jù)?
設置串口屬性
UpdateData(TRUE); //讀取編輯框內容
??? if(flag==3)
?????? m_ctrlComm.SetOutput(COleVariant((CString)0x31));//發(fā)送數(shù)據(jù)
??? else if(flag==2)
?????? m_ctrlComm.SetOutput(COleVariant((CString)0x21));//發(fā)送數(shù)據(jù)
??? else if(flag==1)
?????? m_ctrlComm.SetOutput(COleVariant((CString)0x11));//發(fā)送數(shù)據(jù)
??? else
?????? AfxMessageBox("請選擇一個音階");?
控制發(fā)送高中低八度的DO,其他音節(jié)類似
CStringstr[]={"com1","com2","com3","com4","com5","com6","com7","com8","com9"};
??? ((CComboBox*)GetDlgItem(IDC_chuankou_select))->ResetContent();
??? CString defaultstr=str[0];
??? SetDlgItemText(IDC_chuankou_select,defaultstr);
??? for(int i=1;i
??? {
?????? ((CComboBox*)GetDlgItem(IDC_chuankou_select))->AddString(str[i]);
??? }
填充下拉框
【實驗總結】
1、???接收中斷自動置1的問題:在串口傳輸?shù)倪^程當中,使用RXD接收上位機傳過來的數(shù)據(jù),使用TXD傳出數(shù)據(jù),每當數(shù)據(jù)發(fā)送完成,TI會自動置位為1,請求接收中斷處理;每當數(shù)據(jù)接收完成,RI會自動置位為1,請求發(fā)送中斷處理,由于TI和RI以“或”邏輯關系向主機請求中斷,所以主機響應中斷時事先并不知道是TI還是RI請求的中斷,必須在中斷服務程序中查詢TI和RI進行判別,然后分別處理,因此,兩個中斷請求標志位均不能由硬件自動置位,必須通過軟件置零,否則將出現(xiàn)一次請求多次向英的錯誤。這里采用的是在中斷處理程序中進行軟件置位,也就是每一次中斷結束時檢測中斷的類型,進而對對應的中斷標志位進行置位。
2、???音節(jié)的節(jié)拍的完成:即控制某一頻率的震動在某一指定時間內完成。這一功能是通過控制定時器0的開關實現(xiàn)的,即當單片機接收到串口數(shù)據(jù)時,會執(zhí)行playmusic函數(shù),在默認情況下,定時器0開關TR0=0是關閉狀態(tài),執(zhí)行playmusic的同時,打開定時器開關TR0=1,中間延時0x10*180ms一個節(jié)拍,延時結束后將定時器控制TR0置0,等待下一次輸入。由于在之前的“電子音樂”工程當中是用數(shù)組存儲音節(jié)和節(jié)拍的,每兩個字節(jié)決定一個音階和一個節(jié)拍,在串口中一次只能傳輸一個字節(jié),所以如果要實現(xiàn)節(jié)拍功能就會給用戶帶來很多不便,所以在這里舍去了節(jié)拍的選擇,轉而采用固定的一個節(jié)拍。
【實驗心得】
1、???單片機使用c語言進行編程,這樣給嵌入式開發(fā)者帶來了很大的方便,畢竟我們最初接觸到的語言就是c語言,不過和之前的c語言編程相對比,現(xiàn)在編程語句結構相對分散或者說是獨立而不像之前的編程當中體現(xiàn)的非常強的邏輯,但是由于新加入的中斷定時器等會再各個結構之間產生影響,仍然需要非常注意,特別是對于中斷概念的理解,可以參考“深入理解計算機系統(tǒng)”這本書;
2、???要多編程,了解各個引腳,寄存器的用法、功能。雖說我們做硬件編程的時候可以隨時查找數(shù)據(jù)手冊尋找需要的知識,但是如果能記住這些寄存器、引腳的分布、用法,無疑會給硬件編程帶來非常大的方便,這就要求我們多編程,才能比較好的掌握;
3、???stc硬件編程入門很容易,也就是說很容易便可以掌握stc板的一些基本用法,像是數(shù)碼管、led、定時器等等,但是要編出比較強大好用的硬件程序,就必須多去了解各種中斷用法、編程技巧等,并且這些知識經驗只能靠自己在編程的過程中發(fā)現(xiàn),不是任何老師可以教授的,所以,學習單片機,要親力親為。