基于51单片机的万年历设计 读取ds1302时钟芯片里面的数据,并将时间显示在LCD1602上面 可以通过按键设置当前时间 可以通过按键设置闹钟,当闹钟到了之后,蜂鸣器响 读取ds18b20的数据,并将温度显示在LCD1602上面 包括程序代码+protues仿真

在嵌入式系统的学习与实践中,基于 51 单片机打造一个多功能万年历是个很有趣且实用的项目。它融合了时钟读取、温度测量、显示以及用户交互等多个功能模块。下面咱们就一步步来剖析这个项目。

1. 硬件模块介绍

1.1 DS1302 时钟芯片

DS1302 是一款能提供秒、分、时、日、月、年等信息的时钟芯片,还自带 31 字节静态 RAM 用于数据存储。它通过简单的三线接口(CE、I/O、SCLK)与单片机通信。

1.2 DS18B20 温度传感器

DS18B20 是常用的数字温度传感器,仅需一根数据线就能与单片机进行通信,可直接读出被测温度,使用极为方便。

1.3 LCD1602 显示屏

LCD1602 是一种字符型液晶显示器,能显示两行,每行 16 个字符。通过它可直观展示时间、温度等信息。

1.4 按键与蜂鸣器

按键用于设置时间、闹钟等参数,蜂鸣器则在闹钟时间到达时发出声响。

2. 软件实现

2.1 读取 DS1302 时钟数据

// DS1302 端口定义
sbit DS1302_SCLK = P1^0;
sbit DS1302_IO = P1^1;
sbit DS1302_CE = P1^2;

// 向 DS1302 写一个字节数据
void DS1302_WriteByte(unsigned char dat) {
    unsigned char i;
    for (i = 0; i < 8; i++) {
        DS1302_IO = dat & 0x01;
        DS1302_SCLK = 1;
        dat >>= 1;
        DS1302_SCLK = 0;
    }
}

// 从 DS1302 读一个字节数据
unsigned char DS1302_ReadByte() {
    unsigned char i, dat = 0;
    for (i = 0; i < 8; i++) {
        dat >>= 1;
        if (DS1302_IO)
            dat |= 0x80;
        DS1302_SCLK = 1;
        DS1302_SCLK = 0;
    }
    return dat;
}

// 读取 DS1302 时间
void DS1302_ReadTime(unsigned char *time) {
    unsigned char i;
    DS1302_CE = 1;
    DS1302_WriteByte(0x81); // 读秒寄存器
    time[0] = DS1302_ReadByte();
    DS1302_WriteByte(0x83); // 读分寄存器
    time[1] = DS1302_ReadByte();
    DS1302_WriteByte(0x85); // 读时寄存器
    time[2] = DS1302_ReadByte();
    DS1302_WriteByte(0x87); // 读日寄存器
    time[3] = DS1302_ReadByte();
    DS1302_WriteByte(0x89); // 读月寄存器
    time[4] = DS1302_ReadByte();
    DS1302_WriteByte(0x8B); // 读年寄存器
    time[5] = DS1302_ReadByte();
    DS1302_CE = 0;
}

代码分析:首先定义了 DS1302 与单片机连接的端口。DS1302WriteByte 函数通过移位操作将一个字节数据逐位写入 DS1302,DS1302ReadByte 函数则从 DS1302 逐位读取数据并返回。DS1302_ReadTime 函数通过向特定寄存器地址写入命令,依次读取秒、分、时、日、月、年的数据,并存储在数组 time 中。

2.2 读取 DS18B20 温度数据

// DS18B20 端口定义
sbit DS18B20_DQ = P3^7;

// DS18B20 初始化
bit DS18B20_Init() {
    bit ack;
    DS18B20_DQ = 1;
    _nop_();
    DS18B20_DQ = 0;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    DS18B20_DQ = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    ack = DS18B20_DQ;
    while (!DS18B20_DQ);
    return ack;
}

// 向 DS18B20 写一个字节
void DS18B20_WriteByte(unsigned char dat) {
    unsigned char i;
    for (i = 0; i < 8; i++) {
        DS18B20_DQ = 0;
        _nop_();
        _nop_();
        if (dat & 0x01)
            DS18B20_DQ = 1;
        else
            DS18B20_DQ = 0;
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        DS18B20_DQ = 1;
        dat >>= 1;
    }
}

// 从 DS18B20 读一个字节
unsigned char DS18B20_ReadByte() {
    unsigned char i, dat = 0;
    for (i = 0; i < 8; i++) {
        DS18B20_DQ = 0;
        _nop_();
        _nop_();
        dat >>= 1;
        DS18B20_DQ = 1;
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        if (DS18B20_DQ)
            dat |= 0x80;
    }
    return dat;
}

// 读取 DS18B20 温度
float DS18B20_ReadTemp() {
    unsigned char low, high;
    float temp;
    DS18B20_Init();
    DS18B20_WriteByte(0xCC); // 跳过 ROM 操作
    DS18B20_WriteByte(0x44); // 启动温度转换
    while (!DS18B20_DQ);
    DS18B20_Init();
    DS18B20_WriteByte(0xCC);
    DS18B20_WriteByte(0xBE); // 读取温度寄存器
    low = DS18B20_ReadByte();
    high = DS18B20_ReadByte();
    temp = (high << 8) | low;
    temp = temp * 0.0625;
    return temp;
}

代码分析:先定义了 DS18B20 与单片机连接的端口。DS18B20Init 函数通过拉低总线再拉高,等待 DS18B20 响应来完成初始化,并返回初始化是否成功的标志。DS18B20WriteByteDS18B20ReadByte 函数分别实现向 DS18B20 写和读一个字节数据的操作。DS18B20ReadTemp 函数先启动温度转换,然后读取温度寄存器的高低字节,将其合成温度值并转换为实际温度。

2.3 LCD1602 显示

// LCD1602 端口定义
#define LCD1602_DATA P2
sbit LCD1602_RS = P3^0;
sbit LCD1602_RW = P3^1;
sbit LCD1602_EN = P3^2;

// 延时函数
void delay(unsigned int ms) {
    unsigned int i, j;
    for (i = ms; i > 0; i--)
        for (j = 110; j > 0; j--);
}

// LCD1602 写命令
void LCD1602_WriteCmd(unsigned char cmd) {
    LCD1602_DATA = cmd;
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_EN = 1;
    _nop_();
    _nop_();
    LCD1602_EN = 0;
    delay(2);
}

// LCD1602 写数据
void LCD1602_WriteData(unsigned char dat) {
    LCD1602_DATA = dat;
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_EN = 1;
    _nop_();
    _nop_();
    LCD1602_EN = 0;
    delay(2);
}

// LCD1602 初始化
void LCD1602_Init() {
    LCD1602_WriteCmd(0x38); // 8 位模式,2 行显示,5x7 点阵
    delay(5);
    LCD1602_WriteCmd(0x0C); // 开显示,关光标
    delay(5);
    LCD1602_WriteCmd(0x06); // 光标移动方向
    delay(5);
    LCD1602_WriteCmd(0x01); // 清屏
    delay(5);
}

// 在 LCD1602 特定位置显示字符串
void LCD1602_ShowString(unsigned char x, unsigned char y, char *str) {
    if (y == 0)
        LCD1602_WriteCmd(0x80 + x);
    else
        LCD1602_WriteCmd(0xC0 + x);
    while (*str) {
        LCD1602_WriteData(*str++);
    }
}

代码分析:定义了 LCD1602 与单片机连接的端口。delay 函数用于延时,这在与 LCD1602 通信中很关键,因为 LCD1602 处理命令和数据需要一定时间。LCD1602WriteCmdLCD1602WriteData 函数分别实现向 LCD1602 写命令和数据的操作。LCD1602Init 函数对 LCD1602 进行初始化设置,LCD1602ShowString 函数则用于在指定位置显示字符串。

2.4 按键处理与闹钟实现

// 按键端口定义
sbit KEY1 = P3^3;
sbit KEY2 = P3^4;
sbit KEY3 = P3^5;

// 蜂鸣器端口定义
sbit BEEP = P3^6;

// 闹钟时间设置与比较
unsigned char alarm_time[3];
void SetAlarm() {
    // 按键处理逻辑,这里简化为示例,实际需更完善
    if (KEY1 == 0) {
        delay(20);
        if (KEY1 == 0) {
            // 增加小时设置逻辑
            alarm_time[0]++;
            if (alarm_time[0] > 23)
                alarm_time[0] = 0;
        }
        while (!KEY1);
    }
    if (KEY2 == 0) {
        delay(20);
        if (KEY2 == 0) {
            // 增加分钟设置逻辑
            alarm_time[1]++;
            if (alarm_time[1] > 59)
                alarm_time[1] = 0;
        }
        while (!KEY2);
    }
    if (KEY3 == 0) {
        delay(20);
        if (KEY3 == 0) {
            // 确认设置
            // 这里可添加存储设置到 EEPROM 等操作
        }
        while (!KEY3);
    }
}

void CheckAlarm(unsigned char *time) {
    if (time[2] == alarm_time[0] && time[1] == alarm_time[1]) {
        BEEP = 1; // 蜂鸣器响
    } else {
        BEEP = 0;
    }
}

代码分析:定义了按键和蜂鸣器的端口。SetAlarm 函数通过检测按键状态来设置闹钟时间,虽然这里只是简单示例,实际应用中可加入更完善的按键消抖和设置逻辑。CheckAlarm 函数将当前时间与闹钟时间进行比较,若时间匹配则使蜂鸣器响起。

2.5 主函数

void main() {
    unsigned char time[6];
    float temp;
    LCD1602_Init();
    while (1) {
        DS1302_ReadTime(time);
        temp = DS18B20_ReadTemp();
        // 显示时间
        char time_str[17];
        sprintf(time_str, "%02d:%02d:%02d", time[2], time[1], time[0]);
        LCD1602_ShowString(0, 0, time_str);
        // 显示温度
        char temp_str[17];
        sprintf(temp_str, "Temp: %.2f C", temp);
        LCD1602_ShowString(0, 1, temp_str);
        SetAlarm();
        CheckAlarm(time);
        delay(1000);
    }
}

代码分析:在主函数中,先初始化 LCD1602。然后进入无限循环,在循环中不断读取 DS1302 的时间和 DS18B20 的温度,将其格式化后显示在 LCD1602 上。同时调用按键设置闹钟函数 SetAlarm 和闹钟检测函数 CheckAlarm,并延时 1 秒以控制循环节奏。

3. Proteus 仿真

在 Proteus 软件中搭建电路,将 51 单片机、DS1302、DS18B20、LCD1602、按键、蜂鸣器等元件按实际连接关系连接好。然后将编写好的程序编译生成 hex 文件,加载到单片机中,运行仿真即可看到万年历各项功能的实现效果。通过仿真可以在实际制作硬件之前验证程序逻辑和电路设计的正确性,大大提高开发效率。

以上就是基于 51 单片机的万年历设计的详细实现过程,希望对大家在嵌入式开发学习中有所帮助。大家可以根据实际需求进一步优化和扩展这个项目,比如增加更多功能或者优化用户交互体验等。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐