在8051单片机项目中,DS1302是一款比较经典的实时时钟芯片,可以用于保存和读取当前的秒、分、时、日、月、星期和年份信息。
本文使用HRTOS作为系统运行环境,通过DS1302读取当前时间,再使用4位数码管显示当前的“时”和“分”,最终实现一个简单的8051电子时钟。
本文重点不是复杂的硬件电路,而是介绍在HRTOS中,如何把一个实际的8051应用拆分成多个任务。
一、实现效果
本例最终实现的功能比较简单:
DS1302 │ │ 读取时间 ▼ HRTOS任务 │ ▼ 4位数码管 │ ▼ 12:35数码管显示当前的小时和分钟。
例如:
12:35实际4位数码管显示:
1235如果当前时间为:
08:06则显示:
0806DS1302中的时间数据仍然按照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实际应用案例。