news 2026/9/27 7:40:08

8051单片机实战:使用HRTOS+DS1302+4位数码管实现电子时钟

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
8051单片机实战:使用HRTOS+DS1302+4位数码管实现电子时钟

在8051单片机项目中,DS1302是一款比较经典的实时时钟芯片,可以用于保存和读取当前的秒、分、时、日、月、星期和年份信息。

本文使用HRTOS作为系统运行环境,通过DS1302读取当前时间,再使用4位数码管显示当前的“时”和“分”,最终实现一个简单的8051电子时钟。

本文重点不是复杂的硬件电路,而是介绍在HRTOS中,如何把一个实际的8051应用拆分成多个任务。


一、实现效果

本例最终实现的功能比较简单:

DS1302 │ │ 读取时间 ▼ HRTOS任务 │ ▼ 4位数码管 │ ▼ 12:35

数码管显示当前的小时和分钟。

例如:

12:35

实际4位数码管显示:

1235

如果当前时间为:

08:06

则显示:

0806

DS1302中的时间数据仍然按照BCD格式保存。


二、DS1302时间数据

本例定义一个7字节数组保存DS1302读取到的时间:

unsigned char rtc_time[7];

各个字节对应的数据如下:

下标数据
time[0]秒
time[1]分
time[2]时
time[3]日
time[4]月
time[5]星期
time[6]年

例如当前时间为:

12:35:20

那么其中的小时和分钟分别保存在:

rtc_time[2] rtc_time[1]

三、DS1302使用BCD格式

DS1302读取出来的时间数据采用BCD格式。

例如:

25 -> 0x25 59 -> 0x59

因此,在显示之前需要先将BCD数据转换为普通十进制数据。

这里使用一个简单的BCD转换函数:

unsigned char bcd_to_dec(unsigned char bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); }

例如:

bcd_to_dec(0x25)

返回:

25

而:

bcd_to_dec(0x08)

返回:

8

这样就可以直接用于后面的十位和个位计算。


四、为什么使用两个任务

这个例子非常适合体现RTOS的一个基本思想:

不同功能可以由不同任务负责。

本例将程序拆成两个任务。

任务1:DS1302读取任务

负责:

读取DS1302 ↓ 更新rtc_time[] ↓ 等待 ↓ 再次读取

对应:

void task_ds1302_read(void) { while(1) { drv_ds1302_read_time(rtc_time); os_delay(10); } }

这个任务不负责数码管显示,只负责获取时间。


任务2:数码管显示任务

第二个任务负责:

读取rtc_time[] ↓ BCD转十进制 ↓ 提取小时、分钟 ↓ 刷新4位数码管

对应:

void task_seg_display(void) { unsigned char hour; unsigned char minute; while(1) { hour = bcd_to_dec(rtc_time[2]); minute = bcd_to_dec(rtc_time[1]); drv_seg_display(0, hour / 10); os_delay_ms(1); drv_seg_display(1, hour % 10); os_delay_ms(1); drv_seg_display(2, minute / 10); os_delay_ms(1); drv_seg_display(3, minute % 10); os_delay_ms(1); } }

这样以后如果需要修改显示方式,只需要修改显示任务即可。


五、数码管显示过程

假设DS1302当前时间为:

12:35

首先读取:

hour = 12; minute = 35;

然后拆分成4个数字:

hour / 10 = 1 hour % 10 = 2 minute / 10 = 3 minute % 10 = 5

最终分别显示:

第0位 → 1 第1位 → 2 第2位 → 3 第3位 → 5

因此数码管看到的就是:

1235

六、HRTOS中的任务创建

完成任务函数以后,在系统初始化函数中创建两个任务:

void hrtos_main(void) { drv_ds1302_init(); drv_seg_init(); os_task_create( task_ds1302_read, 2, 2, 4 ); os_task_create( task_seg_display, 3, 2, 4 ); }

首先初始化DS1302:

drv_ds1302_init();

然后初始化数码管:

drv_seg_init();

最后创建两个任务。


七、两个任务的职责

整个程序运行起来以后,可以简单理解成:

HRTOS │ ┌────────┴────────┐ │ │ ▼ ▼ DS1302读取任务 数码管显示任务 │ │ ▼ ▼ rtc_time[7] 读取时间数据 │ ▼ 数码管刷新

DS1302读取任务只负责“获取数据”。

数码管显示任务只负责“显示数据”。

这就是RTOS比较典型的任务划分方式。


八、为什么读取任务不需要一直运行

DS1302本身具有实时时钟功能,会自动进行计时。

因此,程序没有必要在CPU上不断执行:

drv_ds1302_read_time(rtc_time);

而是读取一次以后等待一段时间,再读取下一次。

本例中使用:

os_delay(10);

让任务进入延时状态。

任务延时期间,CPU可以运行其他任务。

这也是RTOS任务设计中一个比较重要的思想:

不需要执行的时候,就让任务主动让出CPU。

相比让任务一直占用CPU进行轮询,这种方式更适合多任务系统。


九、数码管任务为什么需要较短的延时

4位数码管通常采用动态扫描方式。

程序需要不断切换:

第0位 第1位 第2位 第3位 第0位 第1位 ……

因此显示任务不能像DS1302读取任务一样等待很长时间。

本例在每一位显示之后使用:

os_delay_ms(1);

例如:

drv_seg_display(0, hour / 10); os_delay_ms(1); drv_seg_display(1, hour % 10); os_delay_ms(1);

不断快速刷新4个数码管,就可以利用人眼视觉暂留形成稳定的显示效果。


十、完整程序

下面是本例的完整代码:

#include "hrtos.h" #include "hrtos_hal.h" /* * ========================================================= * DS1302 + 4位数码管时钟示例 * --------------------------------------------------------- * 功能: * 1. 初始化DS1302 * 2. 读取DS1302当前时间 * 3. 使用4位数码管显示时、分 * 4. 每秒更新一次时间 * * 显示格式: * * 12:35 * * DS1302时间数据采用BCD格式: * * time[0]:秒 * time[1]:分 * time[2]:时 * time[3]:日 * time[4]:月 * time[5]:星期 * time[6]:年 * ========================================================= */ /*------------------------------------------------ * DS1302时间数据 *------------------------------------------------*/ unsigned char rtc_time[7]; /*------------------------------------------------ * BCD转十进制 * * 例如: * 0x25 -> 25 * 0x59 -> 59 *------------------------------------------------*/ unsigned char bcd_to_dec(unsigned char bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); } /*------------------------------------------------ * DS1302读取任务 * * 每秒读取一次DS1302时间 *------------------------------------------------*/ void task_ds1302_read(void) { while(1) { /* 读取DS1302时间 */ drv_ds1302_read_time(rtc_time); /* * DS1302内部自动走时, * 因此这里周期性读取即可。 */ os_delay(10); } } /*------------------------------------------------ * 数码管显示任务 * * 显示: * * HHMM * * 例如: * 12:35 -> 1235 * 08:06 -> 0806 *------------------------------------------------*/ void task_seg_display(void) { unsigned char hour; unsigned char minute; while(1) { /* BCD转换为十进制 */ hour = bcd_to_dec(rtc_time[2]); minute = bcd_to_dec(rtc_time[1]); /* 显示小时 */ drv_seg_display(0, hour / 10); os_delay_ms(1); drv_seg_display(1, hour % 10); os_delay_ms(1); /* 显示分钟 */ drv_seg_display(2, minute / 10); os_delay_ms(1); drv_seg_display(3, minute % 10); os_delay_ms(1); } } /** * @brief DS1302时钟示例初始化 */ void hrtos_main(void) { /* 初始化DS1302 */ drv_ds1302_init(); /* 初始化数码管 */ drv_seg_init(); /* 创建DS1302读取任务 */ os_task_create( task_ds1302_read, 2, 2, 4 ); /* 创建数码管显示任务 */ os_task_create( task_seg_display, 3, 2, 4 ); }

十一、这个例子体现了什么

虽然这个电子时钟程序并不复杂,但已经包含了一个基本HRTOS应用的几个重要组成部分:

硬件初始化 ↓ 创建任务 ↓ 任务独立运行 ↓ 任务之间共享数据 ↓ 任务延时 ↓ 硬件驱动

其中:

DS1302任务

负责:

硬件 → 数据

数码管任务

负责:

数据 → 显示

HRTOS负责:

任务调度 任务延时 CPU资源管理

这样就把底层硬件、应用功能和操作系统调度进行了比较清晰的划分。


十二、传统方式与RTOS方式

如果不使用RTOS,也可以把所有代码都放在一个:

while(1)

中。

例如:

while(1) { 读取DS1302 刷新数码管 判断按键 处理传感器 处理通信 ... }

当功能越来越多以后,主循环会逐渐变得复杂。

而使用HRTOS以后,可以按照功能拆分:

DS1302任务 数码管任务 按键任务 传感器任务 通信任务 ...

每个任务负责自己的功能。

对于功能不断增加的8051项目,这种组织方式会更加清晰。


十三、总结

本文使用HRTOS、DS1302和4位数码管,实现了一个简单的8051电子时钟。

整个程序只有两个主要任务:

task_ds1302_read ↓ 读取RTC时间 task_seg_display ↓ 刷新4位数码管

通过这个例子,可以初步了解HRTOS应用程序的基本结构:

初始化硬件 ↓ 创建任务 ↓ 任务执行 ↓ 任务延时 ↓ HRTOS负责调度

后续还可以继续增加按键、温度传感器、蜂鸣器、串口通信等功能,并进一步使用HRTOS提供的信号量、消息队列、互斥锁等机制。

这也是使用RTOS构建8051应用程序时比较常见的一种思路。

HRTOS持续维护8051操作系统及相关驱动、示例和组件,后续将继续分享更多8051 RTOS实际应用案例。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 7:40:06

从批处理控制到着色器预编译,揭秘让帧率翻倍的底层黑科技

一、"反直觉"优化:移除"优化"反而性能暴涨2025 年,一位独立开发者在 Steam 上公开了自家游戏的优化全过程,揭示了一个令人意外的真相:某些"优化"其实是性能杀手。开发团队最初从主机版移植到 PC 时…

作者头像 李华
网站建设 2026/9/27 7:34:42

springboot request wrapper + request context holder

内容简述 目的 解决servlet request InputStream body只能读取一次的问题 设置请求级别的线程变量 ThreadLocal 注意:Fitler 执行循序,以免在wrapper 初始化前,body已经消费 Request wrapper wrapper 重写和 body 读取相关的 3 个核心方法&am…

作者头像 李华
网站建设 2026/9/27 7:31:32

MySQL复杂查询与Union操作

在日常开发中,数据查询是数据库操作的核心部分,尤其是在处理多表数据时,单一的查询方式无法满足业务的复杂需求。因此,掌握复杂查询技术变得至关重要。 本教程将介绍如何使用UNION操作进行多表数据合并查询,并通过CASE语句实现条件选择。通过对这些技巧的学习,可以轻松处…

作者头像 李华