鍍金池/ 教程/ 數(shù)據(jù)庫(kù)/ 13.3 多個(gè) .c 文件的初步認(rèn)識(shí)
18. RS485 通信與 Modbus 協(xié)議
17.5 A/D 差分輸入信號(hào)
15.8 C 語(yǔ)言復(fù)合數(shù)據(jù)類型(結(jié)構(gòu)體,共用體,枚舉類型)
16.3 NEC 協(xié)議紅外遙控器
13.1 單片機(jī)通信時(shí)序解析
14.4 單片機(jī) EEPROM 單字節(jié)讀寫(xiě)操作時(shí)序
13.3 多個(gè) .c 文件的初步認(rèn)識(shí)
18.2 Modbus 通信協(xié)議介紹
15.1 BCD 碼介紹
18.3 單片機(jī) Modbus 多機(jī)通信程序設(shè)計(jì)
18.1 單片機(jī) RS485 通信接口、控制線、原理圖及程序?qū)嵗?/span>
15. 實(shí)時(shí)時(shí)鐘 DS1302
14.7 單片機(jī) I2C 和 EEPROM 的綜合編程
17. 模數(shù)轉(zhuǎn)換與數(shù)模轉(zhuǎn)換
16.2 紅外遙控通信原理
13.2 1602 液晶整屏移動(dòng)程序
17.6 D/A 輸出
17.7 單片機(jī)信號(hào)發(fā)生器程序
16.4 溫度傳感器 DS18B20
14.6 單片機(jī)EEPROM的頁(yè)寫(xiě)入
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 簡(jiǎn)介
13.5 單片機(jī)串口通信原理和控制程序
15.5 DS1302 寄存器介紹
15.2 單片機(jī) SPI 通信接口
15.6 DS1302 通信時(shí)序介紹
14.5 單片機(jī) EEPROM 多字節(jié)讀寫(xiě)操作時(shí)序
16. 紅外通信與 DS18B20 溫度傳感器
14.1 單片機(jī) I2C 時(shí)序介紹
15.3 實(shí)時(shí)時(shí)鐘芯片 DS1302 介紹
15.9 單片機(jī)電子時(shí)鐘程序設(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.3 多個(gè) .c 文件的初步認(rèn)識(shí)

我們上一節(jié)的這個(gè)液晶滾屏移動(dòng)程序,大概有160行左右。隨著我們硬件模塊使用的增多,程序量的增大,我們往往要把程序?qū)懙蕉鄠€(gè)文件里,方便代碼的編寫(xiě)、維護(hù)和移植。

比如這個(gè)液晶滾屏程序,我們就可以把 1602 底層的功能函數(shù)專門(mén)寫(xiě)到一個(gè) .c 文件內(nèi),如LcdWaitReady、LcdWriteCmd、LcdWriteDat、LcdShowStr、LcdSetCursor、InitLcd1602 這些函數(shù),都是屬于液晶底層驅(qū)動(dòng)的程序代碼,我們要使用液晶功能的時(shí)候,只有兩個(gè)函數(shù)對(duì)我們實(shí)際功能實(shí)現(xiàn)部分有用,一個(gè)是 InitLcd1602 這個(gè)函數(shù),因?yàn)樾枰瘸跏蓟壕?,另外一個(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>
sbit LED = P0^0;
void main(){
    extern unsigned int i;
    while(1){
        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)用。

多 .c 文件的編程方式,大家不要想象的太復(fù)雜。首先新建一個(gè)工程,一個(gè)工程代表一個(gè)完整的單片機(jī)程序,只能生成一個(gè) hex,但是一個(gè)工程可以有很多個(gè) .c 源文件組成共同參與編譯。工程建立好之后,新建文件并且保存取名為 main.c 文件,再新建一個(gè)文件并且保存取名為 Lcd1602.c 文件,下面我們就可以在兩個(gè)不同文件中分別編寫(xiě)代碼了。當(dāng)然,在編寫(xiě)程序的過(guò)程中,不是說(shuō)我們要先把 main.c 的文件全部寫(xiě)完,再進(jìn)行 1602.c 程序的編寫(xiě),而往往是交互的。比如我們先寫(xiě) Lcd1602.c 文件中部分 Lcd1602 液晶的底層函數(shù) LcdWaitReady、LcdWriteCmd、LcdWriteDat、InitLcd1602,然后編寫(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í)際應(yīng)用中如果對(duì)程序邏輯需求了解透徹,根據(jù)自己的理解去寫(xiě)程序即可。那我們把 1602 整屏移動(dòng)的程序改造成為多文件的程序,大家先初步認(rèn)識(shí)一下。

/***************************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;
    //bit7 等于 1 表示液晶正忙,重復(fù)檢測(cè)直到其等于 0 為止
    }while (sta & 0x80);
}
/* 向 LCD1602 液晶寫(xiě)入一字節(jié)命令,cmd-待寫(xiě)入命令值 */
void LcdWriteCmd(unsigned char cmd){
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* 向 LCD1602 液晶寫(xiě)入一字節(jié)數(shù)據(jù),dat-待寫(xiě)入數(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)-對(duì)應(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)-對(duì)應(yīng)屏幕上的起始坐標(biāo),
    str-字符串指針,len-需顯示的字符長(zhǎng)度 */
void LcdShowStr(unsigned char x, unsigned char y,
        unsigned char *str, unsigned char len){
    LcdSetCursor(x, y); //設(shè)置起始地址
    while (len--){ //連續(xù)寫(xiě)入 len 個(gè)字符數(shù)據(jù)
        LcdWriteDat(*str++);
    }
}
/* 初始化 1602 液晶 */
void InitLcd1602(){
    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); //清屏
}
/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
bit flag500ms = 0; //500ms 定時(shí)標(biāo)志
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)
//待顯示的第一行字符串
unsigned char code str1[] = "Kingst Studio";
//待顯示的第二行字符串,需保持與第一行字符串等長(zhǎng),較短的行可用空格補(bǔ)齊
unsigned char code str2[] = "Let's move...";

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

void main(){
    unsigned char i;
    unsigned char index = 0; //移動(dòng)索引
    unsigned char pdata bufMove1[16+sizeof(str1)+16]; //移動(dòng)顯示緩沖區(qū) 1
    unsigned char pdata bufMove2[16+sizeof(str2)+16]; //移動(dòng)顯示緩沖區(qū) 2
    EA = 1; //開(kāi)總中斷
    ConfigTimer0(10); //配置 T0 定時(shí) 10ms
    InitLcd1602(); //初始化液晶

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

    while (1){
        if (flag500ms){ //每 500ms 移動(dòng)一次屏幕
            flag500ms = 0;
            //從緩沖區(qū)抽出需顯示的一段字符顯示到液晶上
            LcdShowStr(0, 0, bufMove1+index, 16);
            LcdShowStr(0, 1, bufMove2+index, 16);
            //移動(dòng)索引遞增,實(shí)現(xiàn)左移
            index++;
            if (index >= (16+sizeof(str1)-1)){ //起始位置達(dá)到字符串尾部后即返回從頭開(kāi)始
                index = 0;
            }
        }
    }
}
/* 配置并啟動(dòng) T0,ms-T0 定時(shí)時(shí)間 */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //臨時(shí)變量
    tmp = 11059200 / 12; //定時(shí)器計(jì)數(shù)頻率
    tmp = (tmp * ms) / 1000; //計(jì)算所需的計(jì)數(shù)值
    tmp = 65536 - tmp; //計(jì)算定時(shí)器重載值
    tmp = tmp + 12; //補(bǔ)償中斷響應(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
}
/* T0 中斷服務(wù)函數(shù),定時(shí) 500ms */
void InterruptTimer0() interrupt 1{
    static unsigned char tmr500ms = 0;
    TH0 = T0RH; //重新加載重載值
    TL0 = T0RL;
    tmr500ms++;
    if (tmr500ms >= 50){
        tmr500ms = 0;
        flag500ms = 1;
    }
}

我們?cè)?main.c 中要調(diào)用 Lcd1602.c 文件中的 InitLcd1602()和 LcdShowStr 這兩個(gè)函數(shù),只需要在 main.c 中進(jìn)行 extern 聲明即可。大家用 Keil 軟件編程試試,真正的感覺(jué)一下多 .c 源文件的好處。如果這個(gè)程序給你的感覺(jué)還不深刻,那下面我們來(lái)做一個(gè)稍微大點(diǎn)的程序來(lái)體會(huì)一下。