久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 30993|回復(fù): 23
打印 上一主題 下一主題
收起左側(cè)

第13章 1602液晶與串口實(shí)用例程

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:1 發(fā)表于 2013-9-28 15:11 | 只看該作者 |只看大圖 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
  本教材現(xiàn)以連載的方式由網(wǎng)絡(luò)發(fā)布,并將于2014年由清華大學(xué)出版社出版最終完整版,版權(quán)歸作者和清華大學(xué)出版社所有。本著開(kāi)源、分享的理念,本教材可以自由傳播及學(xué)習(xí)使用,但是務(wù)必請(qǐng)注明出處來(lái)自金沙灘工作室
        理論上的內(nèi)容要想逐步消化掌握,必須得通過(guò)大量的實(shí)踐進(jìn)行鞏固,否則時(shí)間一長(zhǎng),極容易忘掉。尤其是一些編程的算法相關(guān)的技巧,就是靠不停的寫(xiě)程序,不停的參考別人的程序慢慢積累成長(zhǎng)起來(lái)的。這節(jié)課帶著大家學(xué)習(xí)一下1602的例程和實(shí)際開(kāi)發(fā)中比較實(shí)用的串口程序。
13.1 通信時(shí)序解析        隨著我們對(duì)通信技術(shù)的深入學(xué)習(xí),大家要逐漸在頭腦中建立起時(shí)序這種概念。所謂“時(shí)序”從字面意義上來(lái)理解,一是“時(shí)間問(wèn)題”,二是“順序問(wèn)題”。
        先說(shuō)“順序問(wèn)題”,這個(gè)相對(duì)簡(jiǎn)單一些。我們?cè)趯W(xué)UART串口通信的時(shí)候,先1位起始位,再8位數(shù)據(jù)位,最后1位停止位,這個(gè)先后順序不能錯(cuò)。我們?cè)趯W(xué) 1602液晶的時(shí)候,比如寫(xiě)指令,RS=L,R/W=L,D0~D7=指令碼,這三者的順序是無(wú)所謂的,但是最終的E=高脈沖,必須是在這三條程序之后,這個(gè)順序一旦錯(cuò)誤,寫(xiě)的數(shù)據(jù)也可能出錯(cuò)。
        “時(shí)間問(wèn)題”內(nèi)容相對(duì)復(fù)雜。比如UART通信,每一位的時(shí)間寬度是1/baud。我們初中就學(xué)過(guò)一個(gè)概念,世界上沒(méi)有絕對(duì)的準(zhǔn)確。那我們這個(gè)每一位的時(shí)間寬度1/baud要求精確到什么范圍內(nèi)呢?
前邊教程我提到過(guò),單片機(jī)讀取UART的RXD引腳數(shù)據(jù)的時(shí)候,一位數(shù)據(jù),單片機(jī)平均分成了16份,取其中的7、8、9三次讀到的結(jié)果,這三次中有2次是高電平那這一位就是1,有2次是低電平,那這一次就是0。如果我們的波特率稍微有些偏差,只要累計(jì)下來(lái)到最后一位停止位,這7、8、9還在范圍內(nèi)即可。如圖 13-1所示。



        圖13-1 UART信號(hào)采集時(shí)序圖


     我用三個(gè)箭頭來(lái)表示7、8、9這三次的采集位置,大家可以注意到,當(dāng)采集到D7的時(shí)候,已經(jīng)有一次采集偏差出去了,但是我們采集到的數(shù)據(jù)還是不會(huì)錯(cuò),因?yàn)橛?2次采集正確。至于這個(gè)偏差允許多大,大家自己可以詳細(xì)算一下。實(shí)際上UART通信的波特率是允許一定范圍內(nèi)誤差存在的,但是不能過(guò)大,否則就會(huì)采集錯(cuò)誤。大家在計(jì)算波特率的時(shí)候,發(fā)現(xiàn)沒(méi)有整除,有小數(shù)部分的時(shí)候,就要特別小心了,因?yàn)樾?shù)部分是一概被舍掉的,于是計(jì)算誤差就產(chǎn)生了。 我們用 11.0592M晶振計(jì)算的過(guò)程中,11059200/12/32/9600得到的是一個(gè)整數(shù),如果用12M晶振計(jì)算12000000/12/32 /9600就會(huì)得到一個(gè)小數(shù),大家可以算一下誤差多少,是否在誤差范圍內(nèi)。
        1602的時(shí)序問(wèn)題,大家要學(xué)會(huì)通過(guò)LCD1602的數(shù)據(jù)手冊(cè)提供的時(shí)序圖和時(shí)序參數(shù)表格來(lái)進(jìn)行研究,而且看懂時(shí)序圖是學(xué)習(xí)單片機(jī)必須學(xué)會(huì)的一項(xiàng)技能,如圖12-2所示。



        圖13-2 1602時(shí)序圖


        大家看到這種圖的時(shí)候,不要覺(jué)得害怕。說(shuō)句不過(guò)分的話,單片機(jī)這些邏輯上的問(wèn)題,只要小學(xué)畢業(yè)就可以理解的,很多時(shí)候是因?yàn)榇蠹野褑?wèn)題想象的太難才學(xué)不下去的。
        我們先來(lái)看一下讀操作時(shí)序的RS引腳和R/W引腳,這兩個(gè)引腳先進(jìn)行變化,因?yàn)槭亲x操作,所以R/W引腳首先要置為高電平,而不管他原來(lái)是什么。讀指令還是讀數(shù)據(jù),都是讀操作,而且都有可能,所以RS引腳既有可能是置為高電平,也有可能是置為低電平,大家注意圖上的畫(huà)法。而RS和R/W變化了經(jīng)過(guò) Tsp1這么長(zhǎng)時(shí)間后,使能引腳E才能從低電平到高電平發(fā)生變化。
而使能引腳E拉高了經(jīng)過(guò)了tD這么長(zhǎng)時(shí)間后,LCD1602輸出DB的數(shù)據(jù)就是有效數(shù)據(jù)了,我們就可以來(lái)讀取DB的數(shù)據(jù)了。讀完了之后,我們要先把使能E拉低,經(jīng)過(guò)一段時(shí)間后RS、R/W和DB才可以變化繼續(xù)為下一次讀寫(xiě)做準(zhǔn)備了。
        而寫(xiě)操作時(shí)序和讀操作時(shí)序的差別,就是寫(xiě)操作時(shí)序,DB的改變是我們單片機(jī)來(lái)完成的,因此要放到使能引腳E的變化之前進(jìn)行操作,其他區(qū)別大家可以自行對(duì)比一下。
        細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),這個(gè)時(shí)序圖上還有很多時(shí)間標(biāo)簽。比如E的上升時(shí)間tR,下降時(shí)間時(shí)間tF,使能引腳E從一個(gè)上升沿到下一個(gè)上升沿之間的長(zhǎng)度周期 tC,使能E下降沿后,R/W和RS變化時(shí)間間隔tHD1等等很多時(shí)間要求,這些要求怎么看呢?放心,只要是正規(guī)的數(shù)據(jù)手冊(cè),都會(huì)把這些時(shí)間要求給大家標(biāo)記出來(lái)的。我們來(lái)看一下表13-1所示。

        表13-1 1602時(shí)序參數(shù)


時(shí)序參數(shù)
符號(hào)
極限值
單位
測(cè)試條件
最小值
典型值
最大值
E信號(hào)周期
tC
400
--
--
ns
引腳E
E脈沖寬度
tPW
150
--
--
ns
E上升沿/下降沿時(shí)間
tR, tF
--
--
25
ns
地址建立時(shí)間
tSP1
30
--
--
ns
引腳E、RS、R/W
地址保持時(shí)間
tHD1
10
--
--
ns
數(shù)據(jù)建立時(shí)間(讀)
tD
--
--
100
ns
引腳DB0~DB7
數(shù)據(jù)保持時(shí)間(讀)
tHD2
20
--
--
ns
數(shù)據(jù)建立時(shí)間(寫(xiě))
tSP2
40
--
--
ns
數(shù)據(jù)保持時(shí)間(寫(xiě))
tHD2
10
--
--
ns
        大家要善于把手冊(cè)中的這個(gè)表格和時(shí)序圖結(jié)合起來(lái)看。表12-1中的數(shù)據(jù),都是時(shí)序參數(shù),本節(jié)課的所有的時(shí)序參數(shù),我都一點(diǎn)點(diǎn)的給大家講出來(lái),以后遇到同類時(shí)序圖,我就不再講了,只是提一下,但是大家務(wù)必要學(xué)會(huì)自己看時(shí)序圖,這個(gè)很重要,此外,看以下解釋需要結(jié)合圖13-2來(lái)看。
        tC:指的是使能引腳E從本次上升沿到下次上升沿的最短時(shí)間是400ns,而我們單片機(jī)因?yàn)樗俣容^慢,一個(gè)機(jī)器周期就是1us多,而一條C語(yǔ)言指令肯定是一個(gè)或者幾個(gè)機(jī)器周期的,所以這個(gè)條件完全滿足。
        tPW:指的是使能引腳E高電平的持續(xù)時(shí)間最短是150ns,由于我們的單片機(jī)比較慢,這個(gè)條件也完全滿足。
        tR, tF:指的是使能引腳E的上升沿時(shí)間和下降沿時(shí)間,不能超過(guò)25ns,這個(gè)時(shí)間限值空間很大,我們用示波器測(cè)了一下我們開(kāi)發(fā)板的這個(gè)引腳上升沿和下降沿時(shí)間大概是10ns到15ns之間,完全滿足。
        tSP1:指的是RS和R/W引腳使能后至少保持30ns,使能引腳E才可以變成高電平,這個(gè)條件完全滿足。
        tHD1:指的是使能引腳E變成低電平后,至少保持10ns之后,RS和R/W才能進(jìn)行變化,這個(gè)條件完全滿足。
        tD:指的是我們的使能引腳E變成高電平后,最多100ns后,1602就把數(shù)據(jù)送出來(lái)了,那么我們就可以正常去讀取狀態(tài)或者數(shù)據(jù)了。
        tHD2:指的是讀操作過(guò)程中,使能引腳E變成低電平后,至少保持20ns,DB數(shù)據(jù)總線才可以進(jìn)行變化,這個(gè)條件完全滿足。
        tSP2:指的是DB數(shù)據(jù)總線準(zhǔn)備好后,至少保持40ns,使能引腳E才可以從低到高進(jìn)行使能變化,這個(gè)條件完全滿足。
        tHD2:指的是寫(xiě)操作過(guò)程中,只能引腳E變成低電平后,至少保持10ns,DB數(shù)據(jù)總線才可以變化,這個(gè)條件完全滿足。
        好了,表13-1這個(gè)LCD1602的時(shí)序參數(shù)表已經(jīng)解析完成了,看完之后,是不是感覺(jué)比你想象的要簡(jiǎn)單,沒(méi)有你想的那么困難。大家自己也得慢慢學(xué)會(huì)看這種時(shí)序圖和表格,在今后的學(xué)習(xí)中,這方面的能力尤為重要。如果以后換用了其它型號(hào)的單片機(jī),那么就根據(jù)單片機(jī)的執(zhí)行速度來(lái)評(píng)估你的程序是否滿足時(shí)序要求,整體上來(lái)說(shuō)器件都是有一個(gè)最快速度的限制,而沒(méi)有最慢限制,所以當(dāng)換用高速的單片機(jī)后通常都是靠在各步驟間插入軟件延時(shí)來(lái)滿足較慢的時(shí)序要求。
13.2 1602整屏移動(dòng)       我們前邊學(xué)第七章點(diǎn)陣LED的時(shí)候,可以實(shí)現(xiàn)上下移動(dòng),左右移動(dòng)等。而對(duì)于1602液晶來(lái)說(shuō),也可以進(jìn)行屏幕移動(dòng),實(shí)現(xiàn)我們想要的一些效果,那我們來(lái)用一個(gè)例程實(shí)現(xiàn)字符串在1602液晶上的左移。每個(gè)人都不要只瞪著眼看,一定要認(rèn)真抄下來(lái),甚至抄幾遍,邊抄遍理解,要想真正學(xué)好,一定要根據(jù)我的方法來(lái)做。

#include <reg52.h>

#define LCD1602_DB   P0     

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

bit flagT0 = 0;            //T0中斷產(chǎn)生標(biāo)志
unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)

unsigned char code str1[] = "Kingst Studio"; //待顯示的第一行字符串
unsigned char code str2[] = "Let's move..."; //待顯示的第二行字符串,需保持與第一行字符串等長(zhǎng),較短的行可用空格補(bǔ)齊

void ConfigTimer0(unsigned int ms);
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str, unsigned char len);
void LcdInit();

void main ()
{
    unsigned char i;
    unsigned char iMove = 0; //移動(dòng)索引
    unsigned int  tmrMove = 0; //屏幕移動(dòng)定時(shí)器
    unsigned char pdata bufMove1[16 + sizeof(str1) + 16]; //移動(dòng)顯示的緩沖區(qū)
    unsigned char pdata bufMove2[16 + sizeof(str1) + 16]; //移動(dòng)顯示的緩沖區(qū)

    EA = 1;            //開(kāi)總中斷
    ConfigTimer0(10);  //配置T0定時(shí)10ms
    LcdInit();         //初始化液晶

    for (i=0; i<16; i++)  //緩沖區(qū)開(kāi)頭一段填充為空格
    {
        bufMove1[ i] = ' ';
        bufMove2[ i] = ' ';
    }
    for (i=0; i<(sizeof(str1)-1); i++) //待顯示字符串拷貝到緩沖區(qū)中間位置
    {
        bufMove1[16+i] = str1[ i];
        bufMove2[16+i] = str2[ i];
    }
    for (i=(16+sizeof(str1)-1); i<sizeof(bufMove1); i++)  //緩沖區(qū)結(jié)尾一段也填充為空格
    {
        bufMove1[ i] = ' ';
        bufMove2[ i] = ' ';
    }

    while(1)
    {
        if (flagT0)
        {
            flagT0 = 0;
            tmrMove += 10;
            if (tmrMove >= 500) //每500ms移動(dòng)一次屏幕
            {
                tmrMove = 0;
                LcdShowStr(0, 0, bufMove1+iMove, 16); //從緩沖區(qū)抽出需顯示的一段字符顯示到液晶上
                LcdShowStr(0, 1, bufMove2+iMove, 16);
                iMove++;                              //移動(dòng)索引遞增,實(shí)現(xiàn)左移
                if (iMove >= (16+sizeof(str1)-1))     //起始位置達(dá)到字符串尾部后即返回從頭開(kāi)始
                {
                    iMove = 0;
                }
            }
        }
    }
}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;

    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 12;           //修正中斷響應(yīng)延時(shí)造成的誤差

    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
void LcdWaitReady()  //等待液晶準(zhǔn)備好
{
    unsigned char sta;

    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重復(fù)檢測(cè)直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd)  //寫(xiě)入命令函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_E  = 1;
    LCD1602_DB = cmd;
    LCD1602_E  = 0;
}
void LcdWriteDat(unsigned char dat)  //寫(xiě)入數(shù)據(jù)函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_E  = 1;
    LCD1602_DB = dat;
    LCD1602_E  = 0;
}
void LcdInit()  //液晶初始化函數(shù)
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
    LcdWriteCmd(0x0C);  //顯示器開(kāi),光標(biāo)關(guān)閉
    LcdWriteCmd(0x06);  //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01);  //清屏
}
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str, unsigned char len)  // 顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str,需顯示長(zhǎng)度len
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
    {
        addr = 0x00 + x; //第一行字符地址從0x00起始
    }
    else
    {
        addr = 0x40 + x; //第二行字符地址從0x40起始
    }

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (len--)       //連續(xù)寫(xiě)入字符串?dāng)?shù)據(jù)
    {
        LcdWriteDat(*str);
        str++;
    }
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    flagT0 = 1;
}
        通過(guò)這個(gè)程序,大家首先要學(xué)會(huì)for語(yǔ)句在數(shù)組中的靈活應(yīng)用,這個(gè)其實(shí)在數(shù)碼管顯示有效位的例程中已經(jīng)有所體現(xiàn)了。其次,隨著我們后邊程序量的增大,大家得學(xué)會(huì)多個(gè)函數(shù)之間的相互調(diào)用的靈活應(yīng)用,體會(huì)其中的奧妙。
13.3 多.c文件的初步認(rèn)識(shí)        我們上一節(jié)的這個(gè)液晶滾屏移動(dòng)程序,大概有150行左右。隨著我們硬件模塊使用的增多,程序量的增大,我們往往要把程序?qū)懙蕉鄠(gè)文件里,方便代碼的編寫(xiě)、維護(hù)和移植。
        比如這個(gè)液晶滾屏程序,我們就可以把1602底層的功能函數(shù)專門寫(xiě)到一個(gè).C文件內(nèi),如LcdWaitReady、LcdWriteCmd、 LcdWriteDat、LcdInit、LcdShowStr 這幾個(gè)函數(shù),都是屬于液晶底層驅(qū)動(dòng)的程序代碼,我們要使用液晶功能的時(shí)候,只有兩個(gè)函數(shù)對(duì)我們實(shí)際功能實(shí)現(xiàn)部分有用,一個(gè)是LcdInit這個(gè)函數(shù),需要先初始化液晶,另外一個(gè)就是LcdShowStr這個(gè)函數(shù),我們只需要把我們要顯示的內(nèi)容通過(guò)參數(shù)傳遞給這個(gè)函數(shù),這個(gè)函數(shù)就可以實(shí)現(xiàn)我們想要的顯示效果,所以我們把這幾個(gè)底層的液晶驅(qū)動(dòng)程序都放到另外一個(gè)文件Lcd1602.c文件中,而我們想實(shí)現(xiàn)的一些比如滾動(dòng)實(shí)現(xiàn)、中斷等上層功能程序全部都放到main.c中,但是main.c文件如何調(diào)用Lcd1602.c文件中的函數(shù)呢?
C語(yǔ)言中,有一個(gè)extern關(guān)鍵字,他有兩個(gè)基本作用。
        1、當(dāng)一個(gè)變量的聲明不在文件的開(kāi)頭,在它聲明之前的函數(shù)想要引用的話,則應(yīng)該用extern進(jìn)行“外部變量”聲明。用一個(gè)簡(jiǎn)單的程序給大家介紹一下,知道這么回事,能看懂別人寫(xiě)的就行,自己寫(xiě)就別這么用了。
#include<reg52.h>               //包含寄存器的庫(kù)文件                  
sbit  LED = P0 ^ 0;             //位地址聲明  注意:sbit必須小寫(xiě)!
void  main()               
{   
    extern unsigned  int  i;  

    while(1)                  //程序死循環(huán)  
    {                                   
        LED = 0;                 //點(diǎn)亮小燈
        for(i=0;i<30000;i++);  //延時(shí)
        LED = 1;   //熄滅小燈
        for(i=0;i<30000;i++);  //延時(shí)
    }
}
unsigned  int  i = 0;
... ...

        我們變量的作用域,是從聲明這個(gè)變量開(kāi)始往后所有的程序,如果我們調(diào)用在前,聲明在后,那么就是這么用。但是實(shí)際開(kāi)發(fā)過(guò)程中,我們一般都不會(huì)這樣做,所以僅僅是表達(dá)一下extern的這個(gè)用法,但它并不實(shí)用。
        2、在一個(gè)工程中,我們?yōu)榱朔奖愎芾砭S護(hù)代碼,所以用了多個(gè).C源文件,如果其中一個(gè)main.c文件要調(diào)用Lcd1602.c文件里的變量或者函數(shù)的時(shí)候,我們就必須得在main.c里邊進(jìn)行以下外部聲明,告訴編譯器這個(gè)變量或者函數(shù)是在其他文件中定義的,可以直接在這個(gè)文件中進(jìn)行調(diào)用,我們用上一節(jié)的程序代碼試試看。
        多.c文件的編程方式,大家不要想象的太復(fù)雜。首先新建一個(gè)工程,一個(gè)工程代表一個(gè)完整的單片機(jī)程序,只能生成一個(gè)hex,但是一個(gè)工程可以有很多個(gè).c源文件組成共同參與編譯。工程建立好之后,新建文件并且保存稱為main.c文件,再新建一個(gè)文件并且保存稱為L(zhǎng)cd1602.c文件,下面我們就可以在兩個(gè)不同文件中分別編寫(xiě)代碼了。當(dāng)然,在編寫(xiě)程序的過(guò)程中,不是說(shuō)我們要先把main.c的文件全部寫(xiě)完,再進(jìn)行1602.c程序的編寫(xiě),而往往是交互的。比如我們先寫(xiě)Lcd1602.c文件中部分Lcd1602液晶的底層函數(shù)LcdWaitReady、LcdWriteCmd、 LcdWriteDat、LcdInit,然后編寫(xiě)main.c文件中的功能程序,在編寫(xiě)main.c文件中程序時(shí),又有對(duì)Lcd1602.c底層程序的綜合調(diào)用,這個(gè)時(shí)候需要Lcd1602.c文件提供一個(gè)被調(diào)用的函數(shù)比如LcdShowStr,我們就可以再到Lcd1602.c中把這個(gè)函數(shù)完成。當(dāng)然了,這僅僅是一個(gè)說(shuō)明例子而已,順序完全是沒(méi)有一個(gè)標(biāo)準(zhǔn)的,實(shí)際過(guò)程我們?nèi)绻麑?duì)程序邏輯需求了解透徹,根據(jù)我們自己的理解去寫(xiě)程序即可。那我們把1602 整屏移動(dòng)的程序改造成為多文件的程序,大家先初步認(rèn)識(shí)一下。
/*************************1602.c文件程序源代碼**************************/

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

void LcdWaitReady()  //等待液晶準(zhǔn)備好
{
    unsigned char sta;

    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重復(fù)檢測(cè)直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd)  //寫(xiě)入命令函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_E  = 1;
    LCD1602_DB = cmd;
    LCD1602_E  = 0;
}
void LcdWriteDat(unsigned char dat)  //寫(xiě)入數(shù)據(jù)函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdInit()  //液晶初始化函數(shù)
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
    LcdWriteCmd(0x0C);  //顯示器開(kāi),光標(biāo)關(guān)閉
    LcdWriteCmd(0x06);  //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01);  //清屏
}
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str, unsigned char len)  // 顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str,需顯示長(zhǎng)度len
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
    {
        addr = 0x00 + x; //第一行字符地址從0x00起始
    }
    else
    {
        addr = 0x40 + x; //第二行字符地址從0x40起始
    }

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (len--)       //連續(xù)寫(xiě)入字符串?dāng)?shù)據(jù)
    {
        LcdWriteDat(*str);
        str++;
    }
}

/*************************main.c文件程序源代碼**************************/

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

bit flagT0 = 0;
unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)

unsigned char code str1[] = "Kingst Studio"; //待顯示的第一行字符串
unsigned char code str2[] = "Let's move..."; //待顯示的第二行字符串,需保持與第一行字符串等長(zhǎng),較短的行可用空格補(bǔ)齊

void ConfigTimer0(unsigned int ms);
extern void LcdInit();
extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str, unsigned char len);

void main ()
{
    unsigned char i;
    unsigned char iMove = 0; //移動(dòng)索引
    unsigned int  tmrMove = 0; //屏幕移動(dòng)定時(shí)器
    unsigned char pdata bufMove1[16 + sizeof(str1) + 16]; //移動(dòng)顯示的緩沖區(qū)
    unsigned char pdata bufMove2[16 + sizeof(str1) + 16]; //移動(dòng)顯示的緩沖區(qū)

    EA = 1;            //開(kāi)總中斷
    ConfigTimer0(10);  //配置T0定時(shí)10ms
    LcdInit();         //初始化液晶

    for (i=0; i<16; i++)  //緩沖區(qū)開(kāi)頭一段填充為空格
    {
        bufMove1[ i] = ' ';
        bufMove2[ i] = ' ';
    }
    for (i=0; i<(sizeof(str1)-1); i++) //待顯示字符串拷貝到緩沖區(qū)中間位置
    {
        bufMove1[16+i] = str1[ i];
        bufMove2[16+i] = str2[ i];
    }
    for (i=(16+sizeof(str1)-1); i<sizeof(bufMove1); i++)  //緩沖區(qū)結(jié)尾一段也填充為空格
    {
        bufMove1[ i] = ' ';
        bufMove2[ i] = ' ';
    }

    while(1)
    {
        if (flagT0)
        {
            flagT0 = 0;
            tmrMove += 10;
            if (tmrMove >= 500) //每500ms移動(dòng)一次屏幕
            {
                tmrMove = 0;
                LcdShowStr(0, 0, bufMove1+iMove, 16); //從緩沖區(qū)抽出需顯示的一段字符顯示到液晶上
                LcdShowStr(0, 1, bufMove2+iMove, 16);
                iMove++;                              //移動(dòng)索引遞增,實(shí)現(xiàn)左移
                if (iMove >= (16+sizeof(str1)-1))     //起始位置達(dá)到字符串尾部后即返回從頭開(kāi)始
                {
                    iMove = 0;
                }
            }
        }
    }
}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;

    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 12;           //修正中斷響應(yīng)延時(shí)造成的誤差

    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    flagT0 = 1;
}

        我們?cè)趍ain.c要調(diào)用Lcd1602.c文件中的LcdInit()和LcdShowStr這兩個(gè)函數(shù),只需要在main.c中進(jìn)行extern 聲明即可。大家用keil軟件編程試試,真正的感覺(jué)一下多.c源文件的好處。如果這個(gè)你的感覺(jué)還不夠深刻,那下面我們來(lái)做一個(gè)稍微大點(diǎn)的程序來(lái)體會(huì)一下。
13.4 計(jì)算器程序        按鍵和液晶,可以組成我們最簡(jiǎn)易的計(jì)算器。下面我們來(lái)寫(xiě)一個(gè)簡(jiǎn)易整數(shù)計(jì)算器提供給大家學(xué)習(xí)。為了讓程序不過(guò)于復(fù)雜,我們這個(gè)計(jì)算器不考慮連加,連減等連續(xù)計(jì)算,不考慮小數(shù)情況。加減乘除分別用上下左右來(lái)替代,回車表示等于,ESC表示歸0。程序共分為三部分,一部分是1602液晶顯示,一部分是按鍵動(dòng)作和掃描,一部分是主函數(shù)功能。
/*************************1602.c文件程序源代碼**************************/

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

void LcdWaitReady()  //等待液晶準(zhǔn)備好
{
    unsigned char sta;

    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重復(fù)檢測(cè)直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd)  //寫(xiě)入命令函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdWriteDat(unsigned char dat)  //寫(xiě)入數(shù)據(jù)函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str)  //顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
        addr = 0x00 + x; //第一行字符地址從0x00起始
    else
        addr = 0x40 + x; //第二行字符地址從0x40起始

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (*str != '\0')      //連續(xù)寫(xiě)入字符串?dāng)?shù)據(jù),直到檢測(cè)到結(jié)束符
    {
        LcdWriteDat(*str);
        str++;
    }
}
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len)  //區(qū)域清除,清除從(x,y)坐標(biāo)起始的len個(gè)字符位
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
        addr = 0x00 + x; //第一行字符地址從0x00起始
    else
        addr = 0x40 + x; //第二行字符地址從0x40起始

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (len--)             //連續(xù)寫(xiě)入空格
    {
        LcdWriteDat(' ');
    }
}
void LcdFullClear()
{
    LcdWriteCmd(0x01);  //清屏
}
void LcdInit()  //液晶初始化函數(shù)
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
    LcdWriteCmd(0x0C);  //顯示器開(kāi),光標(biāo)關(guān)閉
    LcdWriteCmd(0x06);  //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01);  //清屏
    LcdShowStr(15, 1, "0");
}

/***********************keyboard.c文件程序源代碼************************/

#include <reg52.h>

sbit KEY_IN_1  = P2^4;  //矩陣按鍵的掃描輸入引腳1
sbit KEY_IN_2  = P2^5;  //矩陣按鍵的掃描輸入引腳2
sbit KEY_IN_3  = P2^6;  //矩陣按鍵的掃描輸入引腳3
sbit KEY_IN_4  = P2^7;  //矩陣按鍵的掃描輸入引腳4
sbit KEY_OUT_1 = P2^3;  //矩陣按鍵的掃描輸出引腳1
sbit KEY_OUT_2 = P2^2;  //矩陣按鍵的掃描輸出引腳2
sbit KEY_OUT_3 = P2^1;  //矩陣按鍵的掃描輸出引腳3
sbit KEY_OUT_4 = P2^0;  //矩陣按鍵的掃描輸出引腳4

const unsigned char code KeyCodeMap[4][4] = { //矩陣按鍵編號(hào)到PC標(biāo)準(zhǔn)鍵盤(pán)鍵碼的映射表
    { '1',  '2',  '3', 0x26 }, //數(shù)字鍵1、數(shù)字鍵2、數(shù)字鍵3、向上鍵
    { '4',  '5',  '6', 0x25 }, //數(shù)字鍵4、數(shù)字鍵5、數(shù)字鍵6、向左鍵
    { '7',  '8',  '9', 0x28 }, //數(shù)字鍵7、數(shù)字鍵8、數(shù)字鍵9、向下鍵
    { '0', 0x1B, 0x0D, 0x27 }  //數(shù)字鍵0、ESC鍵、  回車鍵、 向右鍵
};
unsigned char pdata KeySta[4][4] = {  //全部矩陣按鍵的當(dāng)前狀態(tài)
    {1, 1, 1, 1},
    {1, 1, 1, 1},
    {1, 1, 1, 1},
    {1, 1, 1, 1}
};
unsigned char step = 0;  //操作步驟
unsigned char oprt = 0;  //運(yùn)算類型
signed long num1 = 0;    //操作數(shù)1
signed long num2 = 0;    //操作數(shù)2
signed long result = 0;  //運(yùn)算結(jié)果

extern void LcdFullClear();
extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);

unsigned char NumToString(unsigned char *str, signed long num) //整型數(shù)轉(zhuǎn)換為字符串,字符串指針str,待轉(zhuǎn)換數(shù)num,返回值為字符串長(zhǎng)度
{
    unsigned char i, len;
    unsigned char buf[12];

    if (num < 0)  //如果為負(fù)數(shù),則首先輸出符號(hào)到指針上,并取其絕對(duì)值
    {
        *str = '-';
        str++;
        num = -num;
    }
    i = 0;        //先轉(zhuǎn)換為低位在前的十進(jìn)制數(shù)組
    do {
        buf[ i] = num % 10;
        num /= 10;
        i++;
    } while (num > 0);
    len = i;       //i最后的值就是有效字符的個(gè)數(shù)
    while (i > 0)  //然后將數(shù)組值轉(zhuǎn)換為ASCII碼反向拷貝到接收指針上
    {
        i--;
        *str = buf[ i] + '0';
        str++;
    }

    return len;   //返回轉(zhuǎn)換后的字符串長(zhǎng)度
}
void ShowOprt(unsigned char y, unsigned char type) //顯示運(yùn)算符,顯示位置y,運(yùn)算符類型type
{
    switch (type)
    {
        case 0: LcdShowStr(0, y, "+"); break;
        case 1: LcdShowStr(0, y, "-"); break;
        case 2: LcdShowStr(0, y, "*"); break;
        case 3: LcdShowStr(0, y, "/"); break;
        default: break;
    }
}
void Reset()  //計(jì)算器復(fù)位函數(shù)
{
    num1 = 0;
    num2 = 0;
    step = 0;
    LcdFullClear();
}
void NumKeyAction(unsigned char n) //數(shù)字鍵動(dòng)作函數(shù),按鍵輸入的數(shù)值n
{
    unsigned char len;
    unsigned char str[12];

    if (step > 1)  //如計(jì)算已完成,則重新開(kāi)始新的計(jì)算
    {
        Reset();
    }
    if (step == 0)  //輸入第一操作數(shù)
    {
        num1 = num1*10 + n;           //輸入數(shù)值累加到原操作數(shù)上
        len = NumToString(str, num1); //新數(shù)值轉(zhuǎn)換為字符串
        LcdShowStr(16-len, 1, str);   //顯示到液晶第二行上
    }
    else            //輸入第二操作數(shù)
    {
        num2 = num2*10 + n;
        len = NumToString(str, num2);
        LcdShowStr(16-len, 1, str);
    }
}
void OprtKeyAction(unsigned char type) //運(yùn)算符按鍵動(dòng)作函數(shù),運(yùn)算符類型type
{
    unsigned char len;
    unsigned char str[12];

    if (step == 0)  //第二操作數(shù)尚未輸入時(shí)響應(yīng),即不支持連續(xù)操作
    {
        len = NumToString(str, num1); //第一操作數(shù)轉(zhuǎn)換為字符串
        LcdAreaClear(0, 0, 16-len);   //清除第一行左邊的字符位
        LcdShowStr(16-len, 0, str);   //字符串靠右顯示在第一行
        ShowOprt(1, type);            //在第二行顯示操作符
        LcdAreaClear(1, 1, 14);       //清除第二行中間的字符位
        LcdShowStr(15, 1, "0");       //在第二行最右端顯示0
        oprt = type;                  //記錄操作類型
        step = 1;
    }
}
void GetResult() //計(jì)算結(jié)果
{
    unsigned char len;
    unsigned char str[12];

    if (step == 1) //第二操作數(shù)已輸入時(shí)才執(zhí)行計(jì)算
    {
        step = 2;
        switch (oprt)  //根據(jù)運(yùn)算符類型計(jì)算結(jié)果,未考慮溢出問(wèn)題
        {
            case 0: result = num1 + num2; break;
            case 1: result = num1 - num2; break;
            case 2: result = num1 * num2; break;
            case 3: result = num1 / num2; break;
            default: break;
        }
        len = NumToString(str, num2);   //原第二操作數(shù)和運(yùn)算符顯示在第一行
        ShowOprt(0, oprt);
        LcdAreaClear(1, 0, 16-1-len);
        LcdShowStr(16-len, 0, str);
        len = NumToString(str, result); //計(jì)算結(jié)果和等號(hào)顯示在第二行
        LcdShowStr(0, 1, "=");
        LcdAreaClear(1, 1, 16-1-len);
        LcdShowStr(16-len, 1, str);
    }
}

void KeyAction(unsigned char keycode)  //按鍵動(dòng)作函數(shù),根據(jù)鍵碼執(zhí)行相應(yīng)動(dòng)作
{
    if  ((keycode>='0') && (keycode<='9'))  //顯示輸入的字符
    {
        NumKeyAction(keycode - '0');
    }
    else if (keycode == 0x26)  //向上鍵,+
    {
        OprtKeyAction(0);
    }
    else if (keycode == 0x28)  //向下鍵,-
    {
        OprtKeyAction(1);
    }
    else if (keycode == 0x25)  //向左鍵,*
    {
        OprtKeyAction(2);
    }
    else if (keycode == 0x27)  //向右鍵,÷
    {
        OprtKeyAction(3);
    }
    else if (keycode == 0x0D)  //回車鍵,計(jì)算結(jié)果
    {
        GetResult();
    }
    else if (keycode == 0x1B)  //Esc鍵,清除
    {
        Reset();
        LcdShowStr(15, 1, "0");
    }
}
void KeyDrive()  //按鍵動(dòng)作驅(qū)動(dòng)函數(shù)
{
    unsigned char i, j;
    static unsigned char pdata backup[4][4] = {  //按鍵值備份,保存前一次的值
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1},
        {1, 1, 1, 1}
    };

    for (i=0; i<4; i++)  //循環(huán)掃描4*4的矩陣按鍵
    {
        for (j=0; j<4; j++)
        {
            if (backup[ i][j] != KeySta[ i][j])  //檢測(cè)按鍵動(dòng)作
            {
                if (backup[ i][j] != 0)  //按鍵按下時(shí)執(zhí)行動(dòng)作
                {
                    KeyAction(KeyCodeMap[ i][j]);  //調(diào)用按鍵動(dòng)作函數(shù)
                }
                backup[ i][j] = KeySta[ i][j];
            }
        }
    }
}
void KeyScan()  //按鍵掃描函數(shù)
{
    unsigned char i;
    static unsigned char keyout = 0;  //矩陣按鍵掃描輸出計(jì)數(shù)器
    static unsigned char keybuf[4][4] = {  //按鍵掃描緩沖區(qū),保存一段時(shí)間內(nèi)的掃描值
        {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //將一行的4個(gè)按鍵值移入緩沖區(qū)
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

    //消抖后更新按鍵狀態(tài)
    for (i=0; i<4; i++)  //每行4個(gè)按鍵,所以循環(huán)4次
    {
        if ((keybuf[keyout][ i] & 0x0F) == 0x00)
        {   //連續(xù)4次掃描值為0,即16ms(4*4ms)內(nèi)都只檢測(cè)到按下?tīng)顟B(tài)時(shí),可認(rèn)為按鍵已按下
            KeySta[keyout][ i] = 0;
        }
        else if ((keybuf[keyout][ i] & 0x0F) == 0x0F)
        {   //連續(xù)4次掃描值為1,即16ms(4*4ms)內(nèi)都只檢測(cè)到彈起狀態(tài)時(shí),可認(rèn)為按鍵已彈起
            KeySta[keyout][ i] = 1;
        }
    }

    //執(zhí)行下一次的掃描輸出
    keyout++;
    keyout &= 0x03;
    switch (keyout)
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}

/*************************main.c文件程序源代碼**************************/

#include <reg52.h>

void ConfigTimer0(unsigned int ms);
extern void KeyScan();
extern void KeyDrive();
extern void LcdInit();

unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)

void main(void)
{
    EA = 1;          //開(kāi)總中斷
    ConfigTimer0(1); //配置T0定時(shí)1ms
    LcdInit();       //初始化液晶

    while(1)
    {
        KeyDrive();
    }
}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;

    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 18;           //修正中斷響應(yīng)延時(shí)造成的誤差

    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}

void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    KeyScan();   //按鍵掃描
}
        通過(guò)這樣一個(gè)程序,大家一方面學(xué)習(xí)如何進(jìn)行多個(gè).c文件編程,另外一個(gè)方面學(xué)會(huì)多個(gè)函數(shù)之間的靈活調(diào)用。可以把這個(gè)程序看成是一個(gè)簡(jiǎn)單的小項(xiàng)目,學(xué)習(xí)一下項(xiàng)目編程都是如何進(jìn)行和布局的。不要把項(xiàng)目想象的太難,再?gòu)?fù)雜的項(xiàng)目也是這種簡(jiǎn)單程序的組合而已。
13.5 串口通信機(jī)制和實(shí)用的串口例程        我們前邊學(xué)串口通信的時(shí)候,比較注重的是串口底層時(shí)序上的操作過(guò)程,所以例程都是簡(jiǎn)單的收發(fā)字符或者字符串。在我們實(shí)際應(yīng)用中,往往串口還要和電腦上的上位機(jī)軟件進(jìn)行交互,實(shí)現(xiàn)電腦軟件發(fā)送不同的指令,單片機(jī)可以對(duì)應(yīng)執(zhí)行不同的操作,這就要求我們組織一個(gè)比較合理的通信機(jī)制邏輯關(guān)系,用來(lái)實(shí)現(xiàn)我們想要的結(jié)果。
        程序的功能是,通過(guò)我們電腦的串口調(diào)試助手下發(fā)三個(gè)不同的命令,第一條指令:buzz on可以讓蜂鳴器響;第二條指令:buzz off可以讓蜂鳴器不響;第三條指令:showstr ,這個(gè)命令空格后邊,可以添加任何字符串,讓后邊的字符串在1602液晶上顯示出來(lái),同時(shí)不管發(fā)送什么命令,單片機(jī)收到后把命令原封不動(dòng)的再通過(guò)串口發(fā)送給電腦,以表示“我收到了……你可以檢查下對(duì)不對(duì)”。這樣的感覺(jué)是不是更像是一個(gè)小項(xiàng)目了呢?
        對(duì)于串口通信部分來(lái)說(shuō),單片機(jī)給電腦發(fā)字符串好說(shuō),有多大的數(shù)組,我們就發(fā)送多少個(gè)字節(jié)即可,但是單片機(jī)接收數(shù)據(jù),接收多少個(gè)才應(yīng)該是一幀數(shù)據(jù)呢?數(shù)據(jù)接收起始頭在哪里,結(jié)束在哪里?這些我們?cè)诮邮盏綌?shù)據(jù)前都是無(wú)從得知的。那怎么辦呢?
        我們的編程思路基于這樣一種通常的事實(shí):當(dāng)需要發(fā)送一幀(多個(gè)字節(jié))數(shù)據(jù)時(shí),這些數(shù)據(jù)都是連續(xù)不斷的發(fā)送的,即發(fā)送完一個(gè)字節(jié)后會(huì)緊接著發(fā)送下一個(gè)字節(jié),期間沒(méi)有間隔或間隔很短,而當(dāng)這一幀數(shù)據(jù)都發(fā)送完畢后,就會(huì)間隔很長(zhǎng)一段時(shí)間(相對(duì)于連續(xù)發(fā)送時(shí)的間隔來(lái)講)不再發(fā)送數(shù)據(jù),也就是通信總線上會(huì)空閑一段較長(zhǎng)的時(shí)間。于是我們就建立這樣一種程序機(jī)制:設(shè)置一個(gè)軟件的總線空閑定時(shí)器,這個(gè)定時(shí)器在有數(shù)據(jù)傳輸時(shí)(從單片機(jī)接收角度來(lái)說(shuō)就是接收到數(shù)據(jù)時(shí))清零,而在總線空閑時(shí)(也就是沒(méi)有接收到數(shù)據(jù)時(shí))時(shí)累加,當(dāng)它累加到一定時(shí)間(例程里是30ms)后,我們就可以認(rèn)定一幀完整的數(shù)據(jù)已經(jīng)傳輸完畢了,于是告訴其它程序可以來(lái)處理數(shù)據(jù)了,本次的數(shù)據(jù)處理完后就恢復(fù)到初始狀態(tài),再準(zhǔn)備下一次的接收。那么這個(gè)用于判定一幀結(jié)束的空閑時(shí)間取多少合適呢?它取決于多個(gè)條件,并沒(méi)有一個(gè)固定值,我們這里介紹幾個(gè)需要考慮的原則:第一,這個(gè)時(shí)間必須大于波特率周期,很明顯我們的單片機(jī)接收中斷產(chǎn)生是在一個(gè)字節(jié)接收完畢后,也就是一個(gè)時(shí)刻點(diǎn),而其接收過(guò)程我們的程序是無(wú)從知曉的,因此在至少一個(gè)波特率周期內(nèi)你覺(jué)不能認(rèn)為空閑已經(jīng)時(shí)間達(dá)到了。第二,要考慮發(fā)送方的系統(tǒng)延時(shí),因?yàn)椴皇撬械陌l(fā)送方都能讓數(shù)據(jù)嚴(yán)格無(wú)間隔的發(fā)送,因?yàn)檐浖憫?yīng)、關(guān)中斷、系統(tǒng)臨界區(qū)等等操作都會(huì)引起延時(shí),所以還得再附加幾個(gè)到十幾個(gè)ms的時(shí)間。我們選取的30ms是一個(gè)折中的經(jīng)驗(yàn)值,它能適應(yīng)大部分的波特率(大于1200)和大部分的系統(tǒng)延時(shí)(PC機(jī)或其它單片機(jī)系統(tǒng))情況。
我先把這個(gè)程序最重要的UART.c文件中的程序貼出來(lái),一點(diǎn)點(diǎn)給大家解析,這個(gè)是實(shí)際項(xiàng)目開(kāi)發(fā)常用的用法,大家一定要認(rèn)真弄明白。

#include <reg52.h>

bit flagOnceTxd = 0;  //單次發(fā)送完成標(biāo)志,即發(fā)送完一個(gè)字節(jié)
bit cmdArrived = 0;  //命令到達(dá)標(biāo)志,即接收到上位機(jī)下發(fā)的命令
unsigned char cntRxd = 0;
unsigned char pdata bufRxd[40]; //串口接收緩沖區(qū)

extern bit flagBuzzOn;

extern void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);

void ConfigUART(unsigned int baud)  //串口配置函數(shù),baud為波特率
{
    SCON = 0x50;   //配置串口為模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1為模式2
    TH1 = 256 - (11059200/12/32) / baud;  //計(jì)算T1重載值
    TL1 = TH1;     //初值等于重載值
    ET1 = 0;       //禁止T1中斷
    ES  = 1;       //使能串口中斷
    TR1 = 1;       //啟動(dòng)T1
}
unsigned char UartRead(unsigned char *buf, unsigned char len) //串口數(shù)據(jù)讀取函數(shù),數(shù)據(jù)接收指針buf,讀取數(shù)據(jù)長(zhǎng)度len,返回值為實(shí)際讀取到的數(shù)據(jù)長(zhǎng)度
{
    unsigned char i;

    if (len > cntRxd) //讀取長(zhǎng)度大于接收到的數(shù)據(jù)長(zhǎng)度時(shí),
    {
        len = cntRxd; //讀取長(zhǎng)度設(shè)置為實(shí)際接收到的數(shù)據(jù)長(zhǎng)度
    }
    for (i=0; i<len; i++) //拷貝接收到的數(shù)據(jù)
    {
        *buf = bufRxd[ i];
        buf++;
    }
    cntRxd = 0;  //清零接收計(jì)數(shù)器

    return len;  //返回實(shí)際讀取長(zhǎng)度
}
void UartWrite(unsigned char *buf, unsigned char len) //串口數(shù)據(jù)寫(xiě)入函數(shù),即串口發(fā)送函數(shù),待發(fā)送數(shù)據(jù)指針buf,數(shù)據(jù)長(zhǎng)度len
{
    while (len--)
    {
        flagOnceTxd = 0;
        SBUF = *buf;
        buf++;
        while (!flagOnceTxd);
    }
}

bit CmdCompare(unsigned char *buf, const unsigned char *cmd) //命令比較函數(shù),緩沖區(qū)數(shù)據(jù)與指定命令比較,相同返回1,不同返回0
{
    while (*cmd != '\0')
    {
        if (*cmd != *buf) //遇到不相同字符時(shí)即刻返回0
        {
            return 0;
        }
        else //當(dāng)前字符相等時(shí),指針遞增準(zhǔn)備比較下一字符
        {
            cmd++;
            buf++;
        }
    }
    return 1; //到命令字符串結(jié)束時(shí)字符都相等則返回1
}
void UartDriver() //串口驅(qū)動(dòng)函數(shù),檢測(cè)接收到的命令并執(zhí)行相應(yīng)動(dòng)作
{
    unsigned char i;
    unsigned char len;
    unsigned char buf[30];
    const unsigned char code cmd0[] = "buzz on";
    const unsigned char code cmd1[] = "buzz off";
    const unsigned char code cmd2[] = "showstr ";
    const unsigned char code *cmdList[] = {cmd0, cmd1, cmd2};

    if (cmdArrived) //有命令到達(dá)時(shí),讀取處理該命令
    {
        cmdArrived = 0;
        for (i=0; i<sizeof(buf); i++) //清零命令接收緩沖區(qū)
        {
            buf[ i] = 0;
        }
        len = UartRead(buf, sizeof(buf)); //將接收到的命令讀取到緩沖區(qū)中
        for (i=0; i<sizeof(cmdList)/sizeof(cmdList[0]); i++) //與所支持的命令列表逐一進(jìn)行比較
        {
            if (CmdCompare(buf, cmdList[ i]) == 1) //檢測(cè)到相符命令時(shí)退出循環(huán),此時(shí)的i值就是該命令在列表中的下標(biāo)值
            {
                break;
            }
        }
        switch (i) //根據(jù)比較結(jié)果執(zhí)行相應(yīng)命令
        {
            case 0:
                flagBuzzOn = 1; //開(kāi)啟蜂鳴器
                break;
            case 1:
                flagBuzzOn = 0; //關(guān)閉蜂鳴器
                break;
            case 2:
                buf[len] = '\0'; //為接收到的字符串添加結(jié)束符
                i = sizeof(cmd2) - 1;
                LcdShowStr(0, 0, buf+i);  //顯示字符串
                i = len - i;              //計(jì)算有效字符個(gè)數(shù)
                if (i < 16) //有效字符少于16時(shí),清楚液晶上的后續(xù)字符位
                {
                    LcdAreaClear(i, 0, 16-i);
                }
                break;
            default:  //i大于命令列表最大下標(biāo)時(shí),即表示沒(méi)有相符的命令,給上機(jī)發(fā)送“錯(cuò)誤命令”的提示
                UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
                return;
        }
        buf[len++] = '\r';  //有效命令被執(zhí)行后,在原命令幀之后添加回車換行符后返回給上位機(jī),表示已執(zhí)行
        buf[len++] = '\n';
        UartWrite(buf, len);
    }
}

void UartRxMonitor(unsigned char ms)  //串口接收監(jiān)控函數(shù)
{
    static unsigned char cntbkp = 0;
    static unsigned char idletmr = 0;

    if (cntRxd > 0)  //接收計(jì)數(shù)器大于零時(shí),監(jiān)控總線空閑時(shí)間
    {
        if (cntbkp != cntRxd)  //接收計(jì)數(shù)器改變,即剛接收到數(shù)據(jù)時(shí),清零空閑計(jì)時(shí)
        {
            cntbkp = cntRxd;
            idletmr = 0;
        }
        else
        {
            if (idletmr < 30)  //接收計(jì)數(shù)器未改變,即總線空閑時(shí),累積空閑時(shí)間
            {
                idletmr += ms;
                if (idletmr >= 30)  //空閑時(shí)間超過(guò)30ms即認(rèn)為一幀命令接收完畢
                {
                    cmdArrived = 1; //設(shè)置命令到達(dá)標(biāo)志
                }
            }
        }
    }
    else
    {
        cntbkp = 0;
    }
}
void InterruptUART() interrupt 4  //UART中斷服務(wù)函數(shù)
{
if (RI)  //接收到字節(jié)
    {
        RI = 0;   //手動(dòng)清零接收中斷標(biāo)志位
        if (cntRxd < sizeof(bufRxd)) //接收緩沖區(qū)尚未用完時(shí),
        {
            bufRxd[cntRxd++] = SBUF; //保存接收字節(jié),并遞增計(jì)數(shù)器
        }
    }
if (TI)  //字節(jié)發(fā)送完畢
    {
        TI = 0;   //手動(dòng)清零發(fā)送中斷標(biāo)志位
        flagOnceTxd = 1;  //設(shè)置單次發(fā)送完成標(biāo)志
    }
}

        我對(duì)照著程序,把重點(diǎn)部分給大家分析一下。
        bit變量flagOnceTxd:?jiǎn)纹瑱C(jī)接收到串口下發(fā)命令后回發(fā)給調(diào)試助手程序中所用到的變量。
        bit變量cmdArrived:?jiǎn)纹瑱C(jī)接收完整一幀數(shù)據(jù)后,通過(guò)這個(gè)變量指示接收一個(gè)命令完畢。
        變量cntRxd:用來(lái)記錄一幀數(shù)據(jù)中,實(shí)際接收了多少個(gè)字節(jié)。
        數(shù)組bufRxd[40]:用來(lái)存放接收到的數(shù)據(jù)。
        聲明外部Bit變量flagBuzzOn:蜂鳴器響和不響的標(biāo)志位,串口接收指令,讓蜂鳴器響,那就flagBuzzOn=1,如果讓它不響,那就flagBuzzOn=0。主程序main.c里進(jìn)行判斷,如果是1則蜂鳴器響,如果是0則蜂鳴器不響。
        聲明外部函數(shù)LcdShowStr:串口接收到讓液晶顯示字符的程序,調(diào)用此函數(shù)。
        聲明外部函數(shù)LcdAreaClear:串口接收到讓液晶顯示字符的程序后,顯示有效字符,后面的液晶顯示清空。
        下面對(duì)函數(shù)逐個(gè)進(jìn)行分析。
        ConfigUART函數(shù)不需要多說(shuō),配置串口波特率的。
       函數(shù)unsigned char UartRead(unsigned char *buf, unsigned char len):
       串口數(shù)據(jù)讀取函數(shù),數(shù)據(jù)接收指針buf,讀取數(shù)據(jù)長(zhǎng)度len,返回值為實(shí)際讀取到的數(shù)據(jù)長(zhǎng)度。當(dāng)其他函數(shù)要調(diào)用這個(gè)函數(shù)的時(shí)候,調(diào)用之前是不知道串口讀到字節(jié)長(zhǎng)度,所以調(diào)用之前定義一個(gè)足夠大的緩沖數(shù)組比如buf[30],調(diào)用這個(gè)函數(shù)后,通過(guò)這個(gè)函數(shù)把串口讀到的數(shù)據(jù)全部復(fù)制到buf[30]這個(gè)數(shù)組中。這里有兩個(gè)長(zhǎng)度,第一個(gè)長(zhǎng)度是調(diào)用UartRead函數(shù)的形參len,這個(gè)長(zhǎng)度用的是buf數(shù)組的長(zhǎng)度(實(shí)際上是30);第二個(gè)長(zhǎng)度是接收到的字符串的實(shí)際長(zhǎng)度,UartRead函數(shù)不再是void類型,而是unsigned char類型,因此有一個(gè)返回值,返回值就是實(shí)際接收到的字符串的長(zhǎng)度。
        進(jìn)入這個(gè)函數(shù)后,我們首先判斷一下30是否比接收到的字符串大,如果大的話,則獲取實(shí)際長(zhǎng)度,如果不大于的話,則直接返回30。然后用一個(gè)for循環(huán),通過(guò)數(shù)組元素的指針,把串口接收緩沖區(qū)的所有的數(shù)據(jù)全部傳遞到調(diào)用UartRead的那個(gè)函數(shù)的數(shù)組buf里。最后清掉UART接收數(shù)據(jù)個(gè)數(shù)的計(jì)數(shù)器,返回剛才這一幀的數(shù)據(jù)長(zhǎng)度。
        也許你會(huì)說(shuō),既然數(shù)據(jù)都已經(jīng)接收到bufRxd[40]中了,那我直接從這里面拿出來(lái)用不就行了嘛,何必還得再拷貝到另一個(gè)地方去呢?我們?cè)O(shè)計(jì)這種雙緩沖的機(jī)制,主要是為了提高串口接收到響應(yīng)效率:首先如果你在bufRxd[40]中處理數(shù)據(jù),那么這時(shí)機(jī)不能再接收任何數(shù)據(jù),因?yàn)樾陆邮盏臄?shù)據(jù)會(huì)破壞原來(lái)的數(shù)據(jù),造成其不完整和混亂;其次,你這個(gè)處理過(guò)程可能會(huì)耗費(fèi)較長(zhǎng)的時(shí)間,比如說(shuō)上位機(jī)現(xiàn)在就給你發(fā)來(lái)一個(gè)延時(shí)顯示的命令,那么在這個(gè)延時(shí)的過(guò)程中你都無(wú)法去接收新的命令,在上位機(jī)看來(lái)就是你暫時(shí)失去響應(yīng)了。而使用這種雙緩沖機(jī)制就可以大大改善這個(gè)問(wèn)題,因?yàn)閿?shù)據(jù)拷貝所需的時(shí)間是相當(dāng)短的,而只要拷貝出去后,bufRxd[40]就可以馬上準(zhǔn)備去接收新數(shù)據(jù)了。
         函數(shù)void UartWrite(unsigned char *buf, unsigned char len) :
        串口數(shù)據(jù)寫(xiě)入函數(shù),即串口發(fā)送函數(shù),待發(fā)送數(shù)據(jù)指針buf,數(shù)據(jù)長(zhǎng)度len。這個(gè)函數(shù)要和串口中斷函數(shù)結(jié)合來(lái)看,每次進(jìn)入串口中斷后,把變量 flagOnceTxd置1,其實(shí)相當(dāng)于把flagOnceTxd認(rèn)為和TI一樣的效果,只是TI是給串口中斷用的,而flagOnceTxd是給 UartWrite函數(shù)作為標(biāo)志位用的。當(dāng)我們接收到電腦下發(fā)下來(lái)的指令后,我們通過(guò)這個(gè)函數(shù)把指令再返回到電腦串口調(diào)試助手。
       函數(shù)bit CmdCompare(unsigned char *buf, const unsigned char *cmd) :
       命令比較函數(shù),緩沖區(qū)數(shù)據(jù)與指定命令比較,相同返回1,不同返回0。這是字符串比較函數(shù),傳遞進(jìn)來(lái)的是2個(gè)字符串的指針,因?yàn)槲覀儐纹瑱C(jī)中的命令是完整的字符串格式,都擁有一個(gè)結(jié)束符——’\0’,而上位機(jī)下發(fā)的字符串是沒(méi)有結(jié)束符的,所以我們以單片機(jī)中的cmd為基準(zhǔn),檢測(cè)cmd直到遇到’\0’為止,對(duì)2個(gè)字符串進(jìn)行比較,如果遇到不同的字符,則返回0,比較到最后都沒(méi)有發(fā)現(xiàn)不同的,則認(rèn)定相同就返回1。
        函數(shù)void UartDriver() :
        串口驅(qū)動(dòng)函數(shù),檢測(cè)接收到的命令并執(zhí)行相應(yīng)動(dòng)作。這個(gè)函數(shù)是放在主循環(huán)里檢測(cè)掃描的函數(shù),要講這個(gè)函數(shù)之前,先來(lái)了解一下指針數(shù)組。
        因?yàn)橹羔?指針變量)本身也是個(gè)變量,因此用指向同一種數(shù)據(jù)類型的幾個(gè)指針來(lái)構(gòu)成一個(gè)數(shù)組,就形成了指針數(shù)組。我們程序之前,只講了RAM里邊的變量的地址,寫(xiě)到FLASH里邊的數(shù)組以及所有的程序代碼,實(shí)際上都是有地址的,都可以使用指針訪問(wèn)。那這個(gè)函數(shù)我們定義到FLASH里三個(gè)字符串?dāng)?shù)組,并且定義了一個(gè)指針數(shù)組,我寫(xiě)出來(lái),大家重點(diǎn)再認(rèn)識(shí)一下。
const unsigned char code cmd0[] = "buzz on";
const unsigned char code cmd1[] = "buzz off";
const unsigned char code cmd2[] = "showstr ";
const unsigned char code *cmdList[] = {cmd0, cmd1, cmd2};
        我們這個(gè)程序,字符串命令數(shù)越多,指針數(shù)組的優(yōu)勢(shì)會(huì)越明顯,這就是我前邊提到的,指針的意義體現(xiàn)在復(fù)雜程序上,而且越復(fù)雜,指針的優(yōu)勢(shì)越明顯。
        這個(gè)函數(shù)判斷到了命令到達(dá)標(biāo)志位變1后,首先將命令從串口接收緩沖區(qū)中把接收到的數(shù)據(jù)全部讀過(guò)來(lái),然后和我們之前協(xié)議好的指令一一進(jìn)行對(duì)比。這段程序的技巧,大家要學(xué)會(huì)。尤其這一句i<sizeof(cmdList)/sizeof(cmdList[0]),是我們求一個(gè)數(shù)組元素個(gè)數(shù)的一個(gè)常用方法。sizeof(cmdList)是數(shù)組所占的總空間大小,sizeof(cmdList[0])是第0個(gè)元素所占的空間大小,因?yàn)閿?shù)組元素類型一致,所以每個(gè)元素所占空間大小肯定是一樣的。我們程序現(xiàn)在用了3個(gè)命令,這個(gè)位置我們不寫(xiě)成3,而寫(xiě)成這樣的表達(dá)式,后邊如果你想添加更多的命令,只需要添加一個(gè)命令數(shù)組cmd3[],并把它同時(shí)添加到cmdList[]中去就行了,而程序主體中不需要做任何改動(dòng),這就是程序易維護(hù)性的一種體現(xiàn)!
        讀到指令后,根據(jù)判斷到的指令執(zhí)行相應(yīng)的動(dòng)作,最后在字符串后加個(gè)尾巴,再發(fā)到串口調(diào)試助手,以便于調(diào)試助手把一條命令顯示為一行,而不是連續(xù)的不分行的顯示。
        函數(shù):void UartRxMonitor(unsigned char ms):
       這是串口接收的監(jiān)控程序,我們把它放在了1ms定時(shí)器中斷里,即每隔1ms調(diào)用一次,如果你使用的定時(shí)不是1ms,那么只需要修改調(diào)用時(shí)的參數(shù)就行了。這個(gè)函數(shù)就用來(lái)完成我們上面說(shuō)的通過(guò)總線空閑定時(shí)器來(lái)判定一幀數(shù)據(jù)接收完畢的任務(wù),所以它必須被不停的固定的間隔來(lái)調(diào)用。
        剩下的程序相信大家都可以獨(dú)立研究明白,不懂的多和同學(xué)討論一下,我把剩下的程序貼出來(lái)。
/*************************main.c文件程序源代碼**************************/

#include <reg52.h>

sbit BUZZ = P1^6;  //蜂鳴器控制引腳

bit flagBuzzOn = 0;   //蜂鳴器啟動(dòng)標(biāo)志

unsigned char T0RH = 0;  //T0重載值的高字節(jié)
unsigned char T0RL = 0;  //T0重載值的低字節(jié)

void ConfigTimer0(unsigned int ms);
extern void LcdInit();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartDriver();

void main ()
{
    EA = 1;           //開(kāi)總中斷
    ConfigTimer0(1);  //配置T0定時(shí)1ms
    ConfigUART(9600); //配置波特率為9600
    LcdInit();        //初始化液晶

    while(1)
    {
        UartDriver();
    }
}

void ConfigTimer0(unsigned int ms)  //T0配置函數(shù)
{
    unsigned long tmp;

    tmp = 11059200 / 12;      //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000;  //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp;        //計(jì)算定時(shí)器重載值
    tmp = tmp + 18;           //修正中斷響應(yīng)延時(shí)造成的誤差

    T0RH = (unsigned char)(tmp >> 8);  //定時(shí)器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0為模式1
    TH0 = T0RH;     //加載T0重載值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中斷
    TR0 = 1;        //啟動(dòng)T0
}
void InterruptTimer0() interrupt 1  //T0中斷服務(wù)函數(shù)
{
    TH0 = T0RH;  //定時(shí)器重新加載重載值
    TL0 = T0RL;
    if (flagBuzzOn)  //執(zhí)行蜂鳴器鳴叫或關(guān)閉
        BUZZ = ~BUZZ;
    else
        BUZZ = 1;
    UartRxMonitor(1);  //串口接收監(jiān)控
}
/***********************lcd1602.c文件程序源代碼*************************/

#include <reg52.h>

#define LCD1602_DB   P0

sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

void LcdWaitReady()  //等待液晶準(zhǔn)備好
{
    unsigned char sta;

    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do
    {
        LCD1602_E = 1;
        sta = LCD1602_DB; //讀取狀態(tài)字
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重復(fù)檢測(cè)直到其等于0為止
}
void LcdWriteCmd(unsigned char cmd)  //寫(xiě)入命令函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_E  = 1;
    LCD1602_DB = cmd;
    LCD1602_E  = 0;
}
void LcdWriteDat(unsigned char dat)  //寫(xiě)入數(shù)據(jù)函數(shù)
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
void LcdShowStr(unsigned char x, unsigned char y, const unsigned char *str)  //顯示字符串,屏幕起始坐標(biāo)(x,y),字符串指針str
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
        addr = 0x00 + x; //第一行字符地址從0x00起始
    else
        addr = 0x40 + x; //第二行字符地址從0x40起始

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (*str != '\0')      //連續(xù)寫(xiě)入字符串?dāng)?shù)據(jù),直到檢測(cè)到結(jié)束符
    {
        LcdWriteDat(*str);
        str++;
    }
}
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len)  //區(qū)域清除,清除從(x,y)坐標(biāo)起始的len個(gè)字符位
{
    unsigned char addr;

    //由輸入的顯示坐標(biāo)計(jì)算顯示RAM的地址
    if (y == 0)
        addr = 0x00 + x; //第一行字符地址從0x00起始
    else
        addr = 0x40 + x; //第二行字符地址從0x40起始

    //由起始顯示RAM地址連續(xù)寫(xiě)入字符串
    LcdWriteCmd(addr | 0x80); //寫(xiě)入起始地址
    while (len--)             //連續(xù)寫(xiě)入空格
    {
        LcdWriteDat(' ');
    }
}
void LcdInit()  //液晶初始化函數(shù)
{
    LcdWriteCmd(0x38);  //16*2顯示,5*7點(diǎn)陣,8位數(shù)據(jù)接口
    LcdWriteCmd(0x0C);  //顯示器開(kāi),光標(biāo)關(guān)閉
    LcdWriteCmd(0x06);  //文字不動(dòng),地址自動(dòng)+1
    LcdWriteCmd(0x01);  //清屏
}
        大家是否發(fā)現(xiàn),現(xiàn)在模塊化編程后,很多函數(shù),甚至.c文件,如果我們有需要,都可以直接復(fù)制添加到我們新的工程中來(lái)用,非常方便功能程序移植,這樣隨著實(shí)踐積累的增加,你會(huì)發(fā)現(xiàn)工作效率變得越來(lái)越高了。
13.6 作業(yè)1、將通信時(shí)序的邏輯完全理解透徹,并且能夠自己獨(dú)立看懂其他器件的時(shí)序圖。
2、根據(jù)1602整屏移動(dòng)程序,改寫(xiě)成右移以及先左移后右移的程序。
3、掌握多.c源文件編寫(xiě)代碼的方法以及調(diào)用其他文件中變量和函數(shù)的用法。
4、徹底理解比較實(shí)用的串口通信機(jī)制程序,能夠完全解析明白實(shí)用串口通信例程,為今后自己獨(dú)立編寫(xiě)類似程序打下基礎(chǔ)。

評(píng)分

參與人數(shù) 1黑幣 +3 收起 理由
++___aa + 3 贊一個(gè)!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏7 分享淘帖 頂3 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:72292 發(fā)表于 2015-1-18 19:23 | 只看該作者
這么好的章節(jié),竟然沒(méi)有評(píng)論
回復(fù)

使用道具 舉報(bào)

板凳
ID:73216 發(fā)表于 2015-2-3 18:33 | 只看該作者
受教了,51hei有你更精彩
回復(fù)

使用道具 舉報(bào)

地板
ID:43559 發(fā)表于 2015-3-4 23:00 | 只看該作者
學(xué)習(xí)了,感謝分享。
回復(fù)

使用道具 舉報(bào)

5#
ID:75246 發(fā)表于 2015-3-29 10:43 | 只看該作者
老師好,我按照您的例程做了實(shí)驗(yàn),用串口助手沒(méi)問(wèn)題,回傳的字符串正常顯示,可是我自己用易語(yǔ)言寫(xiě)了個(gè)類似串口助手的實(shí)驗(yàn)程序,發(fā)送正常,LCD順序顯示,可是回傳到我的軟件里顯示的字符順序混款,每次都不一樣,您能幫我解答一下嗎?如何能讓我的易語(yǔ)言程序正確顯示您的程序回傳的字符串?謝謝老師!
附件在這個(gè)網(wǎng)址里 http://www.zg4o1577.cn/bbs/dpj-32219-1.html
回復(fù)

使用道具 舉報(bào)

6#
ID:75246 發(fā)表于 2015-3-29 13:54 | 只看該作者
老師好:
貌似解決了!
去掉那個(gè)“處理事件”就沒(méi)問(wèn)題了。
謝謝老師!
回復(fù)

使用道具 舉報(bào)

7#
ID:80135 發(fā)表于 2015-5-16 07:37 | 只看該作者
很精彩,學(xué)習(xí)了,謝謝樓主!
回復(fù)

使用道具 舉報(bào)

8#
ID:82036 發(fā)表于 2015-10-18 16:13 | 只看該作者
很精彩,學(xué)習(xí)了,謝謝樓主!
回復(fù)

使用道具 舉報(bào)

9#
ID:102050 發(fā)表于 2016-1-6 11:25 | 只看該作者
想問(wèn)一下 液晶屏上下移動(dòng)當(dāng)如何
回復(fù)

使用道具 舉報(bào)

10#
ID:102050 發(fā)表于 2016-1-6 11:28 | 只看該作者
1602液晶屏 上下移動(dòng)當(dāng)如何
回復(fù)

使用道具 舉報(bào)

11#
ID:162190 發(fā)表于 2017-3-9 10:01 | 只看該作者
本帖最后由 tlone51hei 于 2017-3-22 16:27 編輯

看明白了,需要duo看幾遍
回復(fù)

使用道具 舉報(bào)

12#
ID:163931 發(fā)表于 2017-3-26 23:35 | 只看該作者
不錯(cuò)!
回復(fù)

使用道具 舉報(bào)

13#
ID:204609 發(fā)表于 2017-8-8 15:44 | 只看該作者
我在做整屏左移方針的時(shí)候顯示的都是黑格啊
回復(fù)

使用道具 舉報(bào)

14#
ID:204609 發(fā)表于 2017-8-9 15:20 | 只看該作者
我按照你的實(shí)例做出的仿真為什么不顯示字符啊 一直都是黑格啊  請(qǐng)老師看一下
回復(fù)

使用道具 舉報(bào)

15#
ID:204609 發(fā)表于 2017-8-9 16:06 | 只看該作者
不好意思圖片不顯示 我發(fā)一下我帖子的地址:http://www.zg4o1577.cn/bbs/dpj-92146-1.html
回復(fù)

使用道具 舉報(bào)

16#
ID:204609 發(fā)表于 2017-8-15 14:31 | 只看該作者
我發(fā)現(xiàn)我真懷疑自己的能力:第一個(gè)不顯示字符顯示黑方塊;第二個(gè)計(jì)算器按鍵不起作用
回復(fù)

使用道具 舉報(bào)

17#
ID:68429 發(fā)表于 2017-10-9 13:58 來(lái)自手機(jī) | 只看該作者
很好的資料
回復(fù)

使用道具 舉報(bào)

18#
ID:304089 發(fā)表于 2018-4-8 23:27 來(lái)自手機(jī) | 只看該作者
學(xué)習(xí)了
回復(fù)

使用道具 舉報(bào)

19#
ID:351047 發(fā)表于 2018-6-13 13:33 | 只看該作者
謝謝樓主
回復(fù)

使用道具 舉報(bào)

20#
ID:93625 發(fā)表于 2018-7-16 10:53 | 只看該作者
繼續(xù)學(xué)習(xí),充分利用時(shí)間。還是要感謝樓主發(fā)的教程!
回復(fù)

使用道具 舉報(bào)

21#
ID:618030 發(fā)表于 2019-12-1 22:52 | 只看該作者
夠詳細(xì)..好樣的
回復(fù)

使用道具 舉報(bào)

22#
ID:702974 發(fā)表于 2020-3-22 09:45 | 只看該作者
對(duì)實(shí)際工程實(shí)施很有用
回復(fù)

使用道具 舉報(bào)

23#
ID:837558 發(fā)表于 2020-11-2 23:47 | 只看該作者
請(qǐng)問(wèn)怎么編寫(xiě)一個(gè)簡(jiǎn)單的時(shí)鐘呢
回復(fù)

使用道具 舉報(bào)

24#
ID:1042173 發(fā)表于 2022-9-2 10:10 | 只看該作者
這是什么書(shū)來(lái)的???
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表
主站蜘蛛池模板: 国产精品无码专区在线观看 | 草草网 | 亚洲乱码国产乱码精品精98午夜 | 国产999精品久久久久久 | 最新黄色在线观看 | 高清久久久 | 欧美日韩中文字幕在线 | 国产欧美日韩一区 | 久久久青草 | 欧美在线观看网站 | 日韩欧美第一页 | 日本福利在线观看 | 亚洲精品电影 | 国产精品免费视频一区 | 免费午夜视频在线观看 | 91免费在线看 | 日本中文字幕一区 | 久久久久久亚洲精品 | 免费av播放 | 人人cao| 国产一级片在线观看视频 | 久久久久亚洲精品国产 | 91久久久久久久 | 免费视频一区二区 | 午夜久久久| 精品久久伊人 | 成人影院网站ww555久久精品 | 成人免费在线小视频 | 黄色国产视频 | 久久精品天堂 | 久久久久综合 | 精品国产一区二区三区观看不卡 | 亚洲精品观看 | 午夜精品久久久久久久久久久久 | 国产 欧美 日韩 一区 | 亚洲成av人片在线观看 | 免费在线看黄 | 午夜a v电影| 日本欧美在线 | 亚洲一区久久 | 亚州激情|