在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 數(shù)據(jù)庫/ 13.5 單片機(jī)串口通信原理和控制程序
18. RS485 通信與 Modbus 協(xié)議
17.5 A/D 差分輸入信號
15.8 C 語言復(fù)合數(shù)據(jù)類型(結(jié)構(gòu)體,共用體,枚舉類型)
16.3 NEC 協(xié)議紅外遙控器
13.1 單片機(jī)通信時序解析
14.4 單片機(jī) EEPROM 單字節(jié)讀寫操作時序
13.3 多個 .c 文件的初步認(rèn)識
18.2 Modbus 通信協(xié)議介紹
15.1 BCD 碼介紹
18.3 單片機(jī) Modbus 多機(jī)通信程序設(shè)計(jì)
18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/span>
15. 實(shí)時時鐘 DS1302
14.7 單片機(jī) I2C 和 EEPROM 的綜合編程
17. 模數(shù)轉(zhuǎn)換與數(shù)模轉(zhuǎn)換
16.2 紅外遙控通信原理
13.2 1602 液晶整屏移動程序
17.6 D/A 輸出
17.7 單片機(jī)信號發(fā)生器程序
16.4 溫度傳感器 DS18B20
14.6 單片機(jī)EEPROM的頁寫入
13.4 單片機(jī)計(jì)算器程序設(shè)計(jì)[詳細(xì)]
17.2 A/D(模數(shù)轉(zhuǎn)換)的主要指標(biāo)
17.4 PCF8591 應(yīng)用程序
17.1 A/D 和 D/A 的基本概念
17.3 PCF8591硬件接口(電路圖引腳圖)
14.3 單片機(jī) EEPROM 簡介
13.5 單片機(jī)串口通信原理和控制程序
15.5 DS1302 寄存器介紹
15.2 單片機(jī) SPI 通信接口
15.6 DS1302 通信時序介紹
14.5 單片機(jī) EEPROM 多字節(jié)讀寫操作時序
16. 紅外通信與 DS18B20 溫度傳感器
14.1 單片機(jī) I2C 時序介紹
15.3 實(shí)時時鐘芯片 DS1302 介紹
15.9 單片機(jī)電子時鐘程序設(shè)計(jì)
16.1 紅外光的基本原理
15.4 DS1302 的硬件信息
15.7 DS1302 的 BURST 模式
14.2 單片機(jī) I2C 尋址模式
14. 單片機(jī) I2C 總線與 EEPROM
13. 單片機(jī) 1602 液晶與串口的應(yīng)用實(shí)例

13.5 單片機(jī)串口通信原理和控制程序

我們前邊學(xué)串口通信的時候,比較注重的是串口底層時序上的操作過程,所以例程都是簡單的收發(fā)字符或者字符串。在實(shí)際應(yīng)用中,往往串口還要和電腦上的上位機(jī)軟件進(jìn)行交互,實(shí)現(xiàn)電腦軟件發(fā)送不同的指令,單片機(jī)對應(yīng)執(zhí)行不同操作的功能,這就要求我們組織一個比較合理的通信機(jī)制和邏輯關(guān)系,用來實(shí)現(xiàn)我們想要的結(jié)果。

本節(jié)所提供程序的功能是,通過電腦串口調(diào)試助手下發(fā)三個不同的命令,第一條指令:buzz on 可以讓蜂鳴器響;第二條指令:buzz off 可以讓蜂鳴器不響;第三條指令:showstr ,這個命令空格后邊,可以添加任何字符串,讓后邊的字符串在 1602 液晶上顯示出來,同時不管發(fā)送什么命令,單片機(jī)收到后把命令原封不動的再通過串口發(fā)送給電腦,以表示“我收到了??你可以檢查下對不對”。這樣的感覺是不是更像是一個小項(xiàng)目了呢?

對于串口通信部分來說,單片機(jī)給電腦發(fā)字符串好說,有多大的數(shù)組,我們就發(fā)送多少個字節(jié)即可,但是單片機(jī)接收數(shù)據(jù),接收多少個才應(yīng)該是一幀完整的數(shù)據(jù)呢?數(shù)據(jù)接收起始頭在哪里,結(jié)束在哪里?這些我們在接收到數(shù)據(jù)前都是無從得知的。那怎么辦呢?

我們的編程思路基于這樣一種通常的事實(shí):當(dāng)需要發(fā)送一幀(多個字節(jié))數(shù)據(jù)時,這些數(shù)據(jù)都是連續(xù)不斷的發(fā)送的,即發(fā)送完一個字節(jié)后會緊接著發(fā)送下一個字節(jié),期間沒有間隔或間隔很短,而當(dāng)這一幀數(shù)據(jù)都發(fā)送完畢后,就會間隔很長一段時間(相對于連續(xù)發(fā)送時的間隔來講)不再發(fā)送數(shù)據(jù),也就是通信總線上會空閑一段較長的時間。于是我們就建立這樣一種程序機(jī)制:設(shè)置一個軟件的總線空閑定時器,這個定時器在有數(shù)據(jù)傳輸時(從單片機(jī)接收角度來說就是接收到數(shù)據(jù)時)清零,而在總線空閑時(也就是沒有接收到數(shù)據(jù)時)時累加,當(dāng)它累加到一定時間(例程里是 30 ms)后,我們就可以認(rèn)定一幀完整的數(shù)據(jù)已經(jīng)傳輸完畢了,于是告訴其它程序可以來處理數(shù)據(jù)了,本次的數(shù)據(jù)處理完后就恢復(fù)到初始狀態(tài),再準(zhǔn)備下一次的接收。那么這個用于判定一幀結(jié)束的空閑時間取多少合適呢?它取決于多個條件,并沒有一個固定值,我們這里介紹幾個需要考慮的原則:第一,這個時間必須大于波特率周期,很明顯我們的單片機(jī)接收中斷產(chǎn)生是在一個字節(jié)接收完畢后,也就是一個時刻點(diǎn),而其接收過程我們的程序是無從知曉的,因此在至少一個波特率周期內(nèi)你絕不能認(rèn)為空閑已經(jīng)時間達(dá)到了。第二,要考慮發(fā)送方的系統(tǒng)延時,因?yàn)椴皇撬械陌l(fā)送方都能讓數(shù)據(jù)嚴(yán)格無間隔的發(fā)送,因?yàn)檐浖憫?yīng)、關(guān)中斷、系統(tǒng)臨界區(qū)等等操作都會引起延時,所以還得再附加幾個到十幾個 ms 的時間。我們選取的 30 ms 是一個折中的經(jīng)驗(yàn)值,它能適應(yīng)大部分的波特率(大于1200)和大部分的系統(tǒng)延時(PC 機(jī)或其它單片機(jī)系統(tǒng))情況。

我先把這個程序最重要的 UART.c 文件中的程序貼出來,一點(diǎn)點(diǎn)給大家解析,這個是實(shí)際項(xiàng)目開發(fā)常用的用法,大家一定要認(rèn)真弄明白。

/*****************************Uart.c 文件程序源代碼*****************************/
#include <reg52.h>

bit flagFrame = 0; //幀接收完成標(biāo)志,即接收到一幀新數(shù)據(jù)
bit flagTxd = 0; //單字節(jié)發(fā)送完成標(biāo)志,用來替代 TXD 中斷標(biāo)志位
unsigned char cntRxd = 0; //接收字節(jié)計(jì)數(shù)器
unsigned char pdata bufRxd[64]; //接收字節(jié)緩沖區(qū)

extern void UartAction(unsigned char *buf, unsigned char len);

/* 串口配置函數(shù),baud-通信波特率 */
void ConfigUART(unsigned int 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; //啟動 T1
}
/* 串口數(shù)據(jù)寫入,即串口發(fā)送函數(shù),buf-待發(fā)送數(shù)據(jù)的指針,len-指定的發(fā)送長度 */
void UartWrite(unsigned char *buf, unsigned char len){
    while (len--){ //循環(huán)發(fā)送所有字節(jié)
        flagTxd = 0; //清零發(fā)送標(biāo)志
        SBUF = *buf++; //發(fā)送一個字節(jié)數(shù)據(jù)
        while (!flagTxd); //等待該字節(jié)發(fā)送完成
    }
}
/* 串口數(shù)據(jù)讀取函數(shù),buf-接收指針,len-指定的讀取長度,返回值-實(shí)際讀到的長度 */
unsigned char UartRead(unsigned char *buf, unsigned char len){
    unsigned char i;
    //指定讀取長度大于實(shí)際接收到的數(shù)據(jù)長度時,
    //讀取長度設(shè)置為實(shí)際接收到的數(shù)據(jù)長度
    if (len > cntRxd){
        len = cntRxd;
    }
    for (i=0; i<len; i++){ //拷貝接收到的數(shù)據(jù)到接收指針上
        *buf++ = bufRxd[i];
    }
    cntRxd = 0; //接收計(jì)數(shù)器清零
    return len; //返回實(shí)際讀取長度
}
/* 串口接收監(jiān)控,由空閑時間判定幀結(jié)束,需在定時中斷中調(diào)用,ms-定時間隔 */
void UartRxMonitor(unsigned char ms){
    static unsigned char cntbkp = 0;
    static unsigned char idletmr = 0;

    if (cntRxd > 0){ //接收計(jì)數(shù)器大于零時,監(jiān)控總線空閑時間
        if (cntbkp != cntRxd){ //接收計(jì)數(shù)器改變,即剛接收到數(shù)據(jù)時,清零空閑計(jì)時
            cntbkp = cntRxd;
            idletmr = 0;
        }else{ //接收計(jì)數(shù)器未改變,即總線空閑時,累積空閑時間
            if (idletmr < 30){ //空閑計(jì)時小于 30ms 時,持續(xù)累加
                idletmr += ms;
                if (idletmr >= 30){ //空閑時間達(dá)到 30ms 時,即判定為一幀接收完畢
                    flagFrame = 1; //設(shè)置幀接收完成標(biāo)志
                }
            }
        }
    }else{
        cntbkp = 0;
    }
}
/* 串口驅(qū)動函數(shù),監(jiān)測數(shù)據(jù)幀的接收,調(diào)度功能函數(shù),需在主循環(huán)中調(diào)用 */
void UartDriver(){
    unsigned char len;
    unsigned char pdata buf[40];

    if (flagFrame){ //有命令到達(dá)時,讀取處理該命令
        flagFrame = 0;
        len = UartRead(buf, sizeof(buf)); //將接收到的命令讀取到緩沖區(qū)中
        UartAction(buf, len); //傳遞數(shù)據(jù)幀,調(diào)用動作執(zhí)行函數(shù)
    }
}
/* 串口中斷服務(wù)函數(shù) */
void InterruptUART() interrupt 4{
    if (RI){ //接收到新字節(jié)
        RI = 0; //清零接收中斷標(biāo)志位
        //接收緩沖區(qū)尚未用完時,保存接收字節(jié),并遞增計(jì)數(shù)器
        if (cntRxd < sizeof(bufRxd)){{
            bufRxd[cntRxd++] = SBUF;
        }
    }
    if (TI){ //字節(jié)發(fā)送完畢
        TI = 0; //清零發(fā)送中斷標(biāo)志位
        flagTxd = 1; //設(shè)置字節(jié)發(fā)送完成標(biāo)志
    }
}

大家可以對照注釋和前面的講解分析下這個 Uart.c 文件,在這里指出其中的兩個要點(diǎn)希望大家多注意下。

1、接收數(shù)據(jù)的處理,在串口中斷中,將接收到的字節(jié)都存入緩沖區(qū) bufRxd 中,同時利用另外的定時器中斷通過間隔調(diào)用 UartRxMonitor 來監(jiān)控一幀數(shù)據(jù)是否接收完畢,判定的原則就是我們前面介紹的空閑時間,當(dāng)判定一幀數(shù)據(jù)結(jié)束完畢時,設(shè)置 flagFrame 標(biāo)志,主循環(huán)中可以通過調(diào)用 UartDriver 來檢測該標(biāo)志,并處理接收到的數(shù)據(jù)。當(dāng)要處理接收到的數(shù)據(jù)時,先通過串口讀取函數(shù) UartRead 把接收緩沖區(qū) bufRxd 中的數(shù)據(jù)讀取出來,然后再對讀到的數(shù)據(jù)進(jìn)行判斷處理。也許你會說,既然數(shù)據(jù)都已經(jīng)接收到 bufRxd 中了,那我直接在這里面用不就行了嘛,何必還得再拷貝到另一個地方去呢?我們設(shè)計(jì)這種雙緩沖的機(jī)制,主要是為了提高串口接收到響應(yīng)效率:首先如果你在 bufRxd 中處理數(shù)據(jù),那么這時侯就不能再接收任何數(shù)據(jù),因?yàn)樾陆邮盏臄?shù)據(jù)會破壞原來的數(shù)據(jù),造成其不完整和混亂;其次,這個處理過程可能會耗費(fèi)較長的時間,比如說上位機(jī)現(xiàn)在就給你發(fā)來一個延時顯示的命令,那么在這個延時的過程中你都無法去接收新的命令,在上位機(jī)看來就是你暫時失去響應(yīng)了。而使用這種雙緩沖機(jī)制就可以大大改善這個問題,因?yàn)閿?shù)據(jù)拷貝所需的時間是相當(dāng)短的,而只要拷貝出去后,bufRxd 就可以馬上準(zhǔn)備去接收新數(shù)據(jù)了。

2、串口數(shù)據(jù)寫入函數(shù) UartWrite,它把數(shù)據(jù)指針 buf 指向的數(shù)據(jù)塊連續(xù)的由串口發(fā)送出去。雖然我們的串口程序啟用了中斷,但這里的發(fā)送功能卻沒有在中斷中完成,而是仍然靠查詢發(fā)送中斷標(biāo)志 flagTxd(因中斷函數(shù)內(nèi)必須清零 TI,否則中斷會重復(fù)進(jìn)入執(zhí)行,所以另置了一個 flagTxd 來代替 TI)來完成,當(dāng)然也可以采用先把發(fā)送數(shù)據(jù)拷貝到一個緩沖區(qū)中,然后再在中斷中發(fā)緩沖區(qū)數(shù)據(jù)發(fā)送出去的方式,但這樣一是要耗費(fèi)額外的內(nèi)存,二是使程序更復(fù)雜。這里也還是想告訴大家,簡單方式可以解決的問題就不要搞得更復(fù)雜。

/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
sbit BUZZ = P1^6; //蜂鳴器控制引腳
bit flagBuzzOn = 0; //蜂鳴器啟動標(biāo)志
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)

void ConfigTimer0(unsigned int ms);
extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);

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

    while (1){
        UartDriver(); //調(diào)用串口驅(qū)動
    }
}
/* 內(nèi)存比較函數(shù),比較兩個指針?biāo)赶虻膬?nèi)存數(shù)據(jù)是否相同,
ptr1-待比較指針 1,ptr2-待比較指針 2,len-待比較長度
返回值-兩段內(nèi)存數(shù)據(jù)完全相同時返回 1,不同返回 0 */
bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len){
    while (len--){
        if (*ptr1++ != *ptr2++){ //遇到不相等數(shù)據(jù)時即刻返回 0
            return 0;
        }
    }
    return 1; //比較完全部長度數(shù)據(jù)都相等則返回 1
}
/* 串口動作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動作
buf-接收到的命令幀指針,len-命令幀長度 */
void UartAction(unsigned char *buf, unsigned char len){
    unsigned char i;
    unsigned char code cmd0[] = "buzz on"; //開蜂鳴器命令
    unsigned char code cmd1[] = "buzz off"; //關(guān)蜂鳴器命令
    unsigned char code cmd2[] = "showstr "; //字符串顯示命令
    unsigned char code cmdLen[] = { //命令長度匯總表
        sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
    };

    unsigned char code *cmdPtr[] = { //命令指針匯總表
        &cmd0[0], &cmd1[0], &cmd2[0],
    };

    for (i=0; i<sizeof(cmdLen); i++){ //遍歷命令列表,查找相同命令
        if (len >= cmdLen[i]){ //首先接收到的數(shù)據(jù)長度要不小于命令長度
            if (CmpMemory(buf, cmdPtr[i], cmdLen[i])){ //比較相同時退出循環(huán)
                break;
            }
        }
    }
    switch (i){ //循環(huán)退出時 i 的值即是當(dāng)前命令的索引值
        case 0:
            flagBuzzOn = 1; //開啟蜂鳴器
            break;
        case 1:
            flagBuzzOn = 0; //關(guān)閉蜂鳴器
            break;
        case 2:
            buf[len] = '\0'; //為接收到的字符串添加結(jié)束符
            LcdShowStr(0, 0, buf+cmdLen[2]); //顯示命令后的字符串
            i = len - cmdLen[2]; //計(jì)算有效字符個數(shù)
            if (i < 16){ //有效字符少于 16 時,清除液晶上的后續(xù)字符位
                LcdAreaClear(i, 0, 16-i);
            }
            break;
        default: //未找到相符命令時,給上機(jī)發(fā)送“錯誤命令”的提示
            UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
            return;
    }
    buf[len++] = '\r'; //有效命令被執(zhí)行后,在原命令幀之后添加
    buf[len++] = '\n'; //回車換行符后返回給上位機(jī),表示已執(zhí)行
    UartWrite(buf, len);
}
/* 配置并啟動 T0,ms-T0 定時時間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時變量
    tmp = 11059200 / 12; //定時器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp; //計(jì)算定時器重載值
    tmp = tmp + 33; //補(bǔ)償中斷響應(yīng)延時造成的誤差
    T0RH = (unsigned char)(tmp>>8); //定時器重載值拆分為高低字節(jié)
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0; //清零 T0 的控制位
    TMOD |= 0x01; //配置 T0 為模式 1
    TH0 = T0RH; //加載 T0 重載值
    TL0 = T0RL;
    ET0 = 1; //使能 T0 中斷
    TR0 = 1; //啟動 T0
}
/* T0 中斷服務(wù)函數(shù),執(zhí)行串口接收監(jiān)控和蜂鳴器驅(qū)動 */
void InterruptTimer0() interrupt 1{
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    if (flagBuzzOn){ //執(zhí)行蜂鳴器鳴叫或關(guān)閉
        BUZZ = ~BUZZ;
    }else{
        BUZZ = 1;
    }
    UartRxMonitor(1); //串口接收監(jiān)控
}

main 函數(shù)和主循環(huán)的結(jié)構(gòu)我們已經(jīng)做過很多了,就不多說了,這里重點(diǎn)把串口接收數(shù)據(jù)的具體解析方法給大家分析一下,這種用法具有很強(qiáng)的普遍性,掌握并靈活運(yùn)用它可以使你將來的開發(fā)工作事半功倍。

首先來看 CmpMemory 函數(shù),這個函數(shù)很簡單,就是比較兩段內(nèi)存數(shù)據(jù),通常都是數(shù)組中的數(shù)據(jù),函數(shù)接收兩段數(shù)據(jù)的指針,然后逐個字節(jié)比較——if (ptr1++ != ptr2++),這行代碼既完成了兩個指針指向的數(shù)據(jù)的比較,又在比較完后把兩個指針都各自+1,從這里是不是也能領(lǐng)略到一點(diǎn) C 語言的簡潔高效的魅力呢。這個函數(shù)的用處自然就是用來比較我們接收到的數(shù)據(jù)和事先放在程序里的命令字符串是否相同,從而找出相符的命令了。

接下來是 UartAction 函數(shù)對接收數(shù)據(jù)的解析和處理方法,先把接收的數(shù)據(jù)與所支持的命令字符串逐條比較,這個比較中首先要確保接收的長度大于命令字符串的長度,然后再用上述的 CmpMemory 函數(shù)逐字節(jié)比較,如果比較相同就立即退出循環(huán),不同則繼續(xù)對比下一條命令。當(dāng)找到相符的命令字符串時,最終 i 的值就是該命令在其列表中的索引位置,當(dāng)遍歷完命令列表都沒有找到相符的命令時,最終 i 的值將等于命令總數(shù),那么接下來就用 switch語句根據(jù) i 的值來執(zhí)行具體的動作,這個就不需要再詳細(xì)說明了。

/***************************Lcd1602.c 文件程序源代碼*****************************/
#include <reg52.h>
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/* 等待液晶準(zhǔn)備好 */
void LcdWaitReady(){
    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ù)檢測直到其等于 0 為止
}
/* 向 LCD1602 液晶寫入一字節(jié)命令,cmd-待寫入命令值 */
void LcdWriteCmd(unsigned char cmd){
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫入一字節(jié)數(shù)據(jù),dat-待寫入數(shù)據(jù)值 */
void LcdWriteDat(unsigned char dat){
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 設(shè)置顯示 RAM 起始地址,亦即光標(biāo)位置,(x,y)-對應(yīng)屏幕上的字符坐標(biāo) */
void LcdSetCursor(unsigned char x, unsigned char y){
    unsigned char addr;
    if (y == 0){ //由輸入的屏幕坐標(biāo)計(jì)算顯示 RAM 的地址
        addr = 0x00 + x; //第一行字符地址從 0x00 起始
    }else{
        addr = 0x40 + x; //第二行字符地址從 0x40 起始
    }
    LcdWriteCmd(addr | 0x80); //設(shè)置 RAM 地址
}
/* 在液晶上顯示字符串,(x,y)-對應(yīng)屏幕上的起始坐標(biāo),str-字符串指針 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
    LcdSetCursor(x, y); //設(shè)置起始地址
    while (*str != '\0'){ //連續(xù)寫入字符串?dāng)?shù)據(jù),直到檢測到結(jié)束符
        LcdWriteDat(*str++);
    }
}
/* 區(qū)域清除,清除從(x,y)坐標(biāo)起始的 len 個字符位 */
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){
    LcdSetCursor(x, y); //設(shè)置起始地址
    while (len--){ //連續(xù)寫入空格
        LcdWriteDat(' ');
    }
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
    LcdWriteCmd(0x38); //16*2 顯示,5*7 點(diǎn)陣,8 位數(shù)據(jù)接口
    LcdWriteCmd(0x0C); //顯示器開,光標(biāo)關(guān)閉
    LcdWriteCmd(0x06); //文字不動,地址自動+1
    LcdWriteCmd(0x01); //清屏
}

液晶文件與上一個例程的液晶文件基本是一樣的,唯一的區(qū)別是刪掉了一個本例中用不到的全屏清屏函數(shù),其實(shí)留著這個函數(shù)也沒關(guān)系,只是 Keil 會提示一個警告,告訴你有未被調(diào)用的函數(shù)而已,可以不理會它。

經(jīng)過這幾個多文件工程的練習(xí)后,大家是否發(fā)現(xiàn),在采用多文件模塊化編程后,不光是某些函數(shù),甚至整個 c 文件,如有需要,我們都可以直接復(fù)制到其它的新工程中使用,非常方便功能程序的移植,這樣隨著實(shí)踐積累的增加,你會發(fā)現(xiàn)工作效率變得越來越高了。