電視頻道記憶功能,交通燈倒計時時間的設(shè)定,戶外 LED 廣告的記憶功能,都有可能用到 EEPROM 這類存儲器件。這類器件的優(yōu)勢是存儲的數(shù)據(jù)不僅可以改變,而且掉電后數(shù)據(jù)保存不丟失,因此大量應(yīng)用在各種電子產(chǎn)品上。
我們這節(jié)課的例程,有點類似廣告屏。上電后,1602 的第一行顯示 EEPROM 從 0x20 地址開始的16個字符,第二行顯示 EERPOM 從 0x40 開始的16個字符。我們可以通過 UART 串口通信來改變 EEPROM 內(nèi)部的這個數(shù)據(jù),并且同時也改變了 1602 顯示的內(nèi)容,下次上電的時候,直接會顯示我們更新過的內(nèi)容。
這個程序所有的相關(guān)內(nèi)容,前面都已經(jīng)講過了。但是這個程序體現(xiàn)在了一個綜合應(yīng)用能力上。這個程序用到了 1602 液晶、UART 串口通信、EEPROM 讀寫操作等多個功能的綜合應(yīng)用。寫個點亮小燈好簡單,但是我們想真正學(xué)好單片機,必須得學(xué)會這種綜合程序的應(yīng)用,實現(xiàn)多個模塊同時參與工作。因此同學(xué)們,要認(rèn)認(rèn)真真的把工程建立起來,一行一行的把程序編寫起來,最終鞏固下來。
/*I2C.c 文件程序源代碼***/ (此處省略,可參考之前章節(jié)的代碼) /*Lcd1602.c 文件程序源代碼***/ (此處省略,可參考之前章節(jié)的代碼) /****eeprom.c 文件程序源代碼/ (此處省略,可參考之前章節(jié)的代碼) /Uart.c 文件程序源代碼*****/ (此處省略,可參考之前章節(jié)的代碼)
/*****************************main.c 文件程序源代碼******************************/
#include <reg52.h>
unsigned char T0RH = 0; //T0 重載值的高字節(jié)
unsigned char T0RL = 0; //T0 重載值的低字節(jié)
void InitShowStr();
void ConfigTimer0(unsigned int ms);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
extern void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);
void main(){
EA = 1; //開總中斷
ConfigTimer0(1); //配置 T0 定時 1ms
ConfigUART(9600); //配置波特率為 9600
InitLcd1602(); //初始化液晶
InitShowStr(); //初始顯示內(nèi)容
while (1){
UartDriver(); //調(diào)用串口驅(qū)動
}
}
/* 處理液晶屏初始顯示內(nèi)容 */
void InitShowStr(){
unsigned char str[17];
str[16] = '\0';//在最后添加字符串結(jié)束符,確保字符串可以結(jié)束
E2Read(str, 0x20, 16); //讀取第一行字符串,其 E2 起始地址為 0x20
LcdShowStr(0, 0, str); //顯示到液晶屏
E2Read(str, 0x40, 16); //讀取第二行字符串,其 E2 起始地址為 0x40
LcdShowStr(0, 1, str); //顯示到液晶屏
}
/* 內(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
}
/* 將一字符串整理成 16 字節(jié)的固定長度字符串,不足部分補空格
out-整理后的字符串輸出指針,in-待整理字符串指針 */
void TrimString16(unsigned char *out, unsigned char *in){
unsigned char i = 0;
while (*in != '\0'){ //拷貝字符串直到輸入字符串結(jié)束
*out++ = *in++;
i++;
if (i >= 16){ //當(dāng)拷貝長度已達到 16 字節(jié)時,強制跳出循環(huán)
break;
}
}
for ( ; i<16; i++){ //如不足 16 個字節(jié)則用空格補齊
*out++ = ' ';
}
*out = '\0'; //最后添加結(jié)束符
}
/* 串口動作函數(shù),根據(jù)接收到的命令幀執(zhí)行響應(yīng)的動作
buf-接收到的命令幀指針,len-命令幀長度 */
void UartAction(unsigned char *buf, unsigned char len){
unsigned char i;
unsigned char str[17];
unsigned char code cmd0[] = "showstr1 "; //第一行字符顯示命令
unsigned char code cmd1[] = "showstr2 "; //第二行字符顯示命令
unsigned char code cmdLen[] = { //命令長度匯總表
sizeof(cmd0)-1, sizeof(cmd1)-1,
};
unsigned char code *cmdPtr[] = { //命令指針匯總表
&cmd0[0], &cmd1[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){ //根據(jù)比較結(jié)果執(zhí)行相應(yīng)命令
case 0:
buf[len] = '\0'; //為接收到的字符串添加結(jié)束符
TrimString16(str, buf+cmdLen[0]); //整理成 16 字節(jié)固定長度字符串
LcdShowStr(0, 0, str); //顯示字符串 1
E2Write(str, 0x20, sizeof(str)); //保存字符串 1,起始地址為 0x20
break;
case 1:
buf[len] = '\0'; //為接收到的字符串添加結(jié)束符
TrimString16(str, buf+cmdLen[1]); //整理成 16 字節(jié)固定長度字符串
LcdShowStr(0, 1, str); //顯示字符串 1
E2Write(str, 0x40, sizeof(str)); //保存字符串 2,起始地址為 0x40
break;
default: //未找到相符命令時,給上機發(fā)送“錯誤命令”的提示
UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
return;
}
buf[len++] = '\r'; //有效命令被執(zhí)行后,在原命令幀之后添加
buf[len++] = '\n'; //回車換行符后返回給上位機,表示已執(zhí)行
UartWrite(buf, len);
}
/* 配置并啟動 T0,ms-T0 定時時間 */
void ConfigTimer0(unsigned int ms){
unsigned long tmp; //臨時變量
tmp = 11059200 / 12; //定時器計數(shù)頻率
tmp = (tmp * ms) / 1000; //計算所需的計數(shù)值
tmp = 65536 - tmp; //計算定時器重載值
tmp = tmp + 33; //補償中斷響應(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;
UartRxMonitor(1); //串口接收監(jiān)控
}
我們在學(xué)習(xí) UART 通信的時候,剛開始也是用的 IO 口去模擬 UART 通信過程,最終實現(xiàn)和電腦的通信,而后因為 STC89C52 內(nèi)部具備 UART 硬件通信模塊,所以我們直接可以通過配置寄存器就可以很輕松的實現(xiàn)單片機的 UART 通信。同樣的道理,這個 I2C 通信,如果單片機內(nèi)部有硬件模塊的話,單片機可以直接自動實現(xiàn) I2C 通信了,就不需要我們再進行 IO口模擬起始、模擬發(fā)送、模擬結(jié)束,配置好寄存器,單片機就會把這些工作全部做了。
不過我們的 STC89C52 單片機內(nèi)部不具備 I2C 的硬件模塊,所以我們使用 STC89C52 進行 I2C 通信的話必須用 IO 口來模擬。使用 IO 口模擬 I2C 實際上更有利于我們徹底理解透徹 I2C 通信的實質(zhì)。當(dāng)然了,通過學(xué)習(xí) IO 口模擬通信,今后如果遇到內(nèi)部帶 I2C 模塊的單片機,也應(yīng)該很輕松的搞定,使用內(nèi)部的硬件模塊,可以提高程序的執(zhí)行效率。