一、前言
電子鐘是一種能夠準(zhǔn)確顯示時(shí)間的設(shè)備,廣泛應(yīng)用于家庭、辦公場(chǎng)所和公共場(chǎng)所,為人們提供了方便和準(zhǔn)確的時(shí)間信息。本項(xiàng)目設(shè)計(jì)一個(gè)基于51單片機(jī)的電子鐘,使用DS1302作為RTC時(shí)鐘芯片,LCD1602作為顯示屏,并通過串口方式連接上位機(jī)進(jìn)行時(shí)間設(shè)置和鬧鐘設(shè)置。
STC89C52作為主控芯片,具有較高的性能和穩(wěn)定性,可完成對(duì)外設(shè)的控制和數(shù)據(jù)處理。DS1302是一款低功耗的實(shí)時(shí)時(shí)鐘芯片,能夠提供準(zhǔn)確的時(shí)間計(jì)數(shù)和日期功能。LCD1602是一款常用的字符型液晶顯示屏,具有兩行16列的顯示區(qū)域,能夠清晰顯示時(shí)間和其他相關(guān)信息。
通過串口連接上位機(jī),用戶可以方便地設(shè)置電子鐘的時(shí)間和鬧鐘時(shí)間,實(shí)現(xiàn)個(gè)性化需求。此外,電子鐘還帶有一個(gè)蜂鳴器,可以根據(jù)設(shè)置的鬧鐘時(shí)間進(jìn)行響鈴,提醒用戶。
電子鐘具有以下功能:
(1)顯示當(dāng)前時(shí)間和日期:LCD1602顯示屏將實(shí)時(shí)更新并顯示當(dāng)前的時(shí)間和日期信息。
(2)時(shí)間設(shè)置:通過串口連接上位機(jī),用戶可以進(jìn)行時(shí)間的設(shè)置,包括小時(shí)、分鐘和秒。
(3)日期設(shè)置:用戶可以通過上位機(jī)設(shè)置當(dāng)前的年、月和日。
(4)鬧鐘設(shè)置:用戶可以設(shè)置鬧鐘的時(shí)間,包括小時(shí)和分鐘。到達(dá)設(shè)定時(shí)間時(shí),蜂鳴器將響鈴提醒用戶。
(5)整點(diǎn)報(bào)時(shí):每到整點(diǎn),蜂鳴器將發(fā)出短促的提示音,提醒用戶當(dāng)前時(shí)間。
(6)鬧鐘響鈴:當(dāng)鬧鐘時(shí)間到達(dá)時(shí),蜂鳴器將持續(xù)響鈴,直到用戶停止。
(7)該項(xiàng)目將借助STC89C52單片機(jī)的控制能力和串口通信功能,結(jié)合DS1302時(shí)鐘芯片和LCD1602顯示屏,實(shí)現(xiàn)一個(gè)簡單而實(shí)用的電子鐘。用戶可以根據(jù)自己的
(8)需求進(jìn)行時(shí)間設(shè)置和鬧鐘設(shè)置,方便實(shí)用,并且具有較高的準(zhǔn)確性和穩(wěn)定性。
二、項(xiàng)目的設(shè)計(jì)思路
項(xiàng)目的設(shè)計(jì)思路分為硬件設(shè)計(jì)和軟件設(shè)計(jì)兩部分。
2.1 硬件設(shè)計(jì)思路
(1)主控芯片選擇:選擇STC89C52作為主控芯片,由于其較高的性能和穩(wěn)定性,適合用于控制和數(shù)據(jù)處理。
(2)RTC時(shí)鐘芯片選擇:選擇DS1302作為RTC時(shí)鐘芯片,具有低功耗、精確計(jì)時(shí)和日期功能。
(3)顯示屏選擇:選擇LCD1602作為顯示屏,它具有兩行16列的字符顯示區(qū)域,能夠清晰顯示時(shí)間和其他相關(guān)信息。
(4)串口連接:設(shè)計(jì)串口連接電路,實(shí)現(xiàn)與上位機(jī)的通信,用于時(shí)間設(shè)置和鬧鐘設(shè)置。
(5)蜂鳴器:添加蜂鳴器模塊,用于整點(diǎn)報(bào)時(shí)和鬧鐘響鈴功能。
(6)按鍵輸入:添加按鍵輸入模塊,用于用戶操作,如切換設(shè)置模式、調(diào)整時(shí)間和設(shè)置鬧鐘。
2.2 軟件設(shè)計(jì)思路
(1)初始化設(shè)置:在程序啟動(dòng)時(shí),進(jìn)行硬件初始化,包括配置主控芯片的引腳、初始化DS1302時(shí)鐘芯片和LCD1602顯示屏。
(2)時(shí)間獲取與顯示:通過DS1302時(shí)鐘芯片獲取當(dāng)前的時(shí)間和日期,并將其顯示在LCD1602顯示屏上。
(3)串口通信:通過串口與上位機(jī)進(jìn)行通信,接收上位機(jī)發(fā)送的時(shí)間設(shè)置和鬧鐘設(shè)置指令,并進(jìn)行相應(yīng)的處理
(4)時(shí)間設(shè)置:根據(jù)上位機(jī)發(fā)送的時(shí)間設(shè)置指令,更新DS1302時(shí)鐘芯片的時(shí)間計(jì)數(shù)器。
(5)日期設(shè)置:根據(jù)上位機(jī)發(fā)送的日期設(shè)置指令,更新DS1302時(shí)鐘芯片的日期計(jì)數(shù)器。
(6)鬧鐘設(shè)置:根據(jù)上位機(jī)發(fā)送的鬧鐘設(shè)置指令,設(shè)置鬧鐘時(shí)間,并將其保存在主控芯片的內(nèi)部存儲(chǔ)器中。
(7)整點(diǎn)報(bào)時(shí):通過檢測(cè)DS1302時(shí)鐘芯片的小時(shí)計(jì)數(shù)器,當(dāng)小時(shí)值變化時(shí),觸發(fā)蜂鳴器發(fā)出短促的提示音。
(8)鬧鐘響鈴:通過比較當(dāng)前時(shí)間和保存的鬧鐘時(shí)間,當(dāng)達(dá)到鬧鐘時(shí)間時(shí),觸發(fā)蜂鳴器持續(xù)響鈴,直到用戶停止或設(shè)定的時(shí)間段結(jié)束。
三、項(xiàng)目硬件接線
(1)STC89C52與DS1302:
STC89C52的P2.0口連接到DS1302的SCLK(時(shí)鐘)引腳,用于提供時(shí)鐘信號(hào)。
STC89C52的P2.1口連接到DS1302的IO(數(shù)據(jù))引腳,用于數(shù)據(jù)傳輸。
STC89C52的P2.2口連接到DS1302的RST(復(fù)位)引腳,用于對(duì)DS1302進(jìn)行復(fù)位操作。
(2)STC89C52與LCD1602:
STC89C52的P0口連接到LCD1602的D0-D7引腳,用于傳輸字符數(shù)據(jù)和控制信號(hào)。
STC89C52的P2.3口連接到LCD1602的RS(寄存器選擇)引腳,用于選擇數(shù)據(jù)或命令寄存器。
STC89C52的P2.4口連接到LCD1602的RW(讀寫選擇)引腳,用于選擇讀或?qū)懖僮鳌?/p>
STC89C52的P2.5口連接到LCD1602的E(使能)引腳,用于啟動(dòng)傳輸。
(3)STC89C52與蜂鳴器模塊:
STC89C52的P3.7口連接到蜂鳴器模塊的信號(hào)引腳,用于觸發(fā)蜂鳴器響鈴。
(4)串口通信接口。在STC89C52單片機(jī)上,串口引腳如下:
UART接收線(RXD):連接至外部設(shè)備的發(fā)送線。
STC89C52的P3.0口(RXD)用于接收串口數(shù)據(jù)。
UART發(fā)送線(TXD):連接至外部設(shè)備的接收線。
STC89C52的P3.1口(TXD)用于發(fā)送串口數(shù)據(jù)。
四、項(xiàng)目代碼
4.1 DS1302時(shí)鐘讀取、設(shè)置
下面代碼實(shí)現(xiàn)了,STC89C52讀取DS1302時(shí)鐘信息打印到串口,以及設(shè)置鬧鐘、時(shí)間讀取、打印到串口的功能。其中,采用了UART通信進(jìn)行與上位機(jī)交互,可以接收上位機(jī)發(fā)送過來的時(shí)間字符串,并據(jù)此設(shè)置鬧鐘和時(shí)間。
#include <reg52.h>
#include <stdio.h>
#define uchar unsigned char
#define uint unsigned int
// 定義DS1302時(shí)鐘寄存器地址
#define DS1302_SEC_REG 0x80
#define DS1302_MIN_REG 0x82
#define DS1302_HR_REG 0x84
#define DS1302_DAY_REG 0x86
#define DS1302_MONTH_REG 0x88
#define DS1302_YEAR_REG 0x8C
// 定義DS1302控制寄存器命令
#define DS1302_CMD_WRITE 0x80
#define DS1302_CMD_READ 0x81
// 定義串口波特率為9600
#define BAUDRATE 9600
#define FOSC 11059200L
#define TIMER_INTERVAL (65536 - FOSC / 12 / BAUDRATE)
// 聲明全局變量
uchar time_buffer[20]; // 存放時(shí)間字符串
uchar alarm_buffer[20]; // 存放鬧鐘時(shí)間字符串
uint i;
bit flag; // 標(biāo)記是否接收到上位機(jī)的時(shí)間字符串
// 初始化UART模塊
void InitUart() {
TMOD &= 0x0F;
TMOD |= 0x20;
TH1 = TIMER_INTERVAL / 256;
TL1 = TIMER_INTERVAL % 256;
PCON |= 0x80;
SCON = 0x50;
ES = 1;
TR1 = 1;
EA = 1;
}
// 將單個(gè)字節(jié)發(fā)送到串口
void SendData(uchar dat) {
SBUF = dat;
while (!TI);
TI = 0;
}
// 將字符串發(fā)送到串口
void SendString(uchar *s) {
while (*s != '?') {
SendData(*s++);
}
}
// 初始化DS1302時(shí)鐘芯片
void InitDS1302() {
uchar i;
// 使能DS1302寫保護(hù)功能
DS1302_CE = 0;
DS1302_SCL = 0;
DS1302_CE = 1;
Write_DS1302(DS1302_CMD_WRITE | 0x8e, 0x80);
// 關(guān)閉時(shí)鐘允許,準(zhǔn)備寫入數(shù)據(jù)
Write_DS1302(DS1302_CMD_WRITE | 0x90, 0x00);
// 寫入年月日時(shí)分秒周
Write_DS1302(DS1302_SEC_REG, 0x00);
Write_DS1302(DS1302_MIN_REG, 0x30);
Write_DS1302(DS1302_HR_REG, 0x11);
Write_DS1302(DS1302_DAY_REG, 0x08);
Write_DS1302(DS1302_MONTH_REG, 0x09);
Write_DS1302(DS1302_YEAR_REG, 0x21);
Write_DS1302(0x8e, 0x00);
// 初始化鬧鐘時(shí)間
for (i = 0; i < 20; i++) {
alarm_buffer[i] = 0;
}
}
// 向DS1302寫入數(shù)據(jù)
void Write_DS1302(uchar addr, uchar dat) {
uchar i;
DS1302_CE = 0;
DS1302_SCL = 0;
// 發(fā)送起始信號(hào)
DS1302_CE = 1;
DS1302_SCL = 1;
DS1302_CE = 0;
// 發(fā)送命令字節(jié)地址
DS1302_WriteByte(addr);
// 發(fā)送數(shù)據(jù)字節(jié)
DS1302_WriteByte(dat);
// 停止信號(hào)
DS1302_SCL = 0;
DS1302_CE = 1;
// 延時(shí)至少1us
for (i = 0; i < 10; i++);
}
// 向DS1302讀取數(shù)據(jù)
uchar Read_DS1302(uchar addr) {
uchar dat;
uchar i;
DS1302_CE = 0;
DS1302_SCL = 0;
// 發(fā)送起始信號(hào)
DS1302_CE = 1;
DS1302_SCL = 1;
DS1302_CE = 0;
// 發(fā)送命令字節(jié)地址
DS1302_WriteByte(addr | 0x01);
// 讀取數(shù)據(jù)字節(jié)
dat = DS1302_ReadByte();
// 停止信號(hào)
DS1302_SCL = 0;
DS1302_CE = 1;
// 延時(shí)至少1us
for (i = 0; i < 10; i++);
return dat;
}
// 讀取DS1302時(shí)間并打印到串口
void ReadTime() {
uchar sec, min, hour, day, month, year;
sprintf(time_buffer, "Time: ");
sec = Read_DS1302(DS1302_SEC_REG);
min = Read_DS1302(DS1302_MIN_REG);
hour = Read_DS1302(DS1302_HR_REG);
day = Read_DS1302(DS1302_DAY_REG);
month = Read_DS1302(DS1302_MONTH_REG);
year = Read_DS1302(DS1302_YEAR_REG);
sprintf(time_buffer + 6, "%02d:%02d:%02d %02d/%02d/%02drn", hour, min, sec, day, month, year);
SendString(time_buffer);
}
// 向DS1302寫入鬧鐘時(shí)間
void SetAlarm(uchar *str) {
uint i = 0;
// 將字符串轉(zhuǎn)換為數(shù)字
while (str[i] != '?') {
alarm_buffer[i] = str[i] - '0';
i++;
if (i > 19) // 防止溢出
break;
}
// 寫入鬧鐘時(shí)間
Write_DS1302(DS1302_CMD_WRITE | 0x81, alarm_buffer[10] << 4 | alarm_buffer[11]);
Write_DS1302(DS1302_CMD_WRITE | 0x83, alarm_buffer[8] << 4 | alarm_buffer[9]);
Write_DS1302(DS1302_CMD_WRITE | 0x85, alarm_buffer[6] << 4 | alarm_buffer[7]);
}
// 從串口接收數(shù)據(jù)中解析出時(shí)間信息
void ParseTime() {
uchar i, j;
uchar temp;
for (i = 0; i < 20; i++) {
time_buffer[i] = 0;
}
// 接收字符串格式為:hh:mm:ss dd/mm/yy
for (i = 0; i < 8; i++) {
temp = 0;
for (j = 0; j < 2; j++) {
temp *= 10;
temp += (SBUF - '0');
while (!RI); // 等待接收完成
RI = 0;
}
time_buffer[i] = temp;
if (i == 2 || i == 4) {
while (SBUF != ' '); // 跳過空格字符
while (!RI); // 等待接收完成
RI = 0;
}
}
flag = 1; // 標(biāo)記已經(jīng)接收到字符串
}
// 主函數(shù)
void main() {
InitUart();
InitDS1302();
flag = 0;
while (1) {
if (flag) { // 接收到時(shí)間字符串,設(shè)置鬧鐘和時(shí)間
SetAlarm(time_buffer);
Write_DS1302(DS1302_CMD_WRITE | 0x80, time_buffer[6] << 4 | time_buffer[7]);
Write_DS1302(DS1302_CMD_WRITE | 0x82, time_buffer[3] << 4 | time_buffer[4]);
Write_DS1302(DS1302_CMD_WRITE | 0x84, time_buffer[0] << 4 | time_buffer[1]);
flag = 0;
}
ReadTime(); // 讀取當(dāng)前時(shí)間并發(fā)送到串口
}
}
// UART接收中斷函數(shù)
void UartIsr() interrupt 4 {
if (RI) { // 接收到數(shù)據(jù)
ParseTime(); // 解析時(shí)間字符串
}
RI = 0;
}
4.2 LCD1602顯示時(shí)鐘
基于STC89C52控制LCD1602顯示時(shí)間字符串的實(shí)現(xiàn)代碼。
#include <reg52.h>
#include <stdio.h>
// 定義Data和Command寄存器選擇端口
sbit LCD_RS = P2^0; // RS引腳(寄存器選擇)
sbit LCD_RW = P2^1; // RW引腳(讀寫選擇)
sbit LCD_EN = P2^2; // EN引腳(使能)
// 定義數(shù)據(jù)總線端口
#define LCD_DATA P0
void DelayMs(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 120; j++);
}
void WriteCommand(unsigned char cmd) {
LCD_RS = 0; // 選擇指令寄存器
LCD_RW = 0; // 寫模式
LCD_EN = 0; // 低電平使能
LCD_DATA = cmd; // 發(fā)送指令
DelayMs(1); // 延時(shí)等待指令寫入
LCD_EN = 1; // 高電平使能
DelayMs(1); // 持續(xù)一段時(shí)間
LCD_EN = 0; // 結(jié)束使能
}
void WriteData(unsigned char dat) {
LCD_RS = 1; // 選擇數(shù)據(jù)寄存器
LCD_RW = 0; // 寫模式
LCD_EN = 0; // 低電平使能
LCD_DATA = dat; // 發(fā)送數(shù)據(jù)
DelayMs(1); // 延時(shí)等待數(shù)據(jù)寫入
LCD_EN = 1; // 高電平使能
DelayMs(1); // 持續(xù)一段時(shí)間
LCD_EN = 0; // 結(jié)束使能
}
void LCDInit() {
WriteCommand(0x38); // 設(shè)置顯示模式為2行、5x8點(diǎn)陣字符
WriteCommand(0x0C); // 顯示器開,光標(biāo)關(guān)閉
WriteCommand(0x06); // 光標(biāo)右移,整屏不移動(dòng)
WriteCommand(0x01); // 清除顯示并設(shè)置光標(biāo)回到初始位置
}
void LCDDisplayTime(char* time) {
int i;
WriteCommand(0x80); // 設(shè)置光標(biāo)位置為第一行的起始位置
for (i = 0; i < 16; i++) {
WriteData(time[i]); // 在第一行顯示時(shí)間字符串
}
WriteCommand(0xC0); // 設(shè)置光標(biāo)位置為第二行的起始位置
for (i = 0; i < 16; i++) {
WriteData(time[16 + i]); // 在第二行顯示時(shí)間字符串
}
}
void main() {
char time_buffer[32] = "Current Time: 00:00:00"; // 時(shí)間字符串
unsigned char sec = 0, min = 0, hour = 0; // 當(dāng)前時(shí)間變量
LCDInit(); // 初始化LCD顯示器
while (1) {
// 更新時(shí)間變量
sec++;
if (sec >= 60) {
sec = 0;
min++;
if (min >= 60) {
min = 0;
hour++;
if (hour >= 24) {
hour = 0;
}
}
}
// 格式化時(shí)間字符串
sprintf(time_buffer + 14, "%02d:%02d:%02d", hour, min, sec);
// 顯示時(shí)間字符串
LCDDisplayTime(time_buffer);
DelayMs(1000); // 延時(shí)1秒
}
}
代碼使用LCD_RS
、LCD_RW
和LCD_EN
分別表示LCD1602的RS、RW和EN引腳。數(shù)據(jù)總線通過LCD_DATA
定義,連接到P0端口。先初始化LCD顯示器,在一個(gè)無限循環(huán)中更新時(shí)間變量并格式化時(shí)間字符串,最后在LCD上顯示時(shí)間字符串。