基于pico rtc设备驱动开发
- I/O设备框架
- RTC设备
- 功能配置——启用Soft RTC
- 功能配置——启用NTP时间自动同步
- 功能配置——启用硬件RTC
RT-Thread 的 RTC (实时时钟)设备为操作系统的时间系统提供了基础服务。应用层对于 RTC 设备一般不存在直接调用的 API ,使用者中间接通过设备的 control 接口完成交互。
I/O设备框架
I/O 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分为 I/O 设备管理层、设备驱动框架层、设备驱动层。
I/O 设备管理层实现了对设备驱动程序的封装。应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。应用层通过该层提供的标准接口访问底层设备,只需关注功能,无需考虑底层的变更,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。
设备驱动框架层是对同类硬件设备驱动的抽象,为I/O设备管理层提供功能实现调用接口。
设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。
从源码层面解释各层之间的联系,如下图所示。图中,横向表示分层,纵向表示各类的继承派生关系。从下到上不断抽象、屏蔽下层差异,体现了面向对象的抽象的思想。上层为基类,下层为派生类。派生类各自实现父类提供的统一接口。这样,驱动层的不同厂商的相同硬件模块可创建各自的派生类对象,然后对接到框架层同一的基类接口,从而形成多对一的面向抽象的关系。同理,从设备驱动框架层到IO设备管理接口层,又是上层对下层的一次抽象。
在RT-Thread中,I/O设备管理层和驱动设备框架层已完成封装,若新增设备驱动到I/O设备框架时,一般只需要结合硬件设备提供的SDK对设备驱动层进行驱动开发。
详细可参考:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/device
RTC设备
在开启 RTC 设备框架以及 RTC 驱动之后,用户可以 #include <sys/time.h>
用来引用标准的时间操作函数,对软件或硬件RTC设备进行访问和操作。如:
设置日期:rt_err_t set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day)
设置时间:rt_err_t set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second)
获取当前时间:time_t time(time_t *t)
功能配置——启用Soft RTC
在rt-thread 源码下的pico设备目录下(如rt-thread-master/bsp/raspberry-pico/),构建dist项目框架。source ~/.env/env.sh 将env工具引入该路径,启动工具,在 menuconfig 中可以启用使用软件模拟 RTC 的功能。
scons编译构建后,将生成的rtthread-pico.uf2文件拷贝至pico设备(RPI-RP2 disk)中。通过串口连接访问设备。在Finsh命令行,执行date命令,查看效果如下图。实际验证中,Soft RTC的时间比实际要快很多,精度不高。
ps: C语言项目的裁剪配置本质上通过条件编译和宏的展开进行的。RT-Thread借助Kconfig机制实现该功能。Env在根目录下执行menuconfig命令后会递归解析各级Kconfig文件,然后提供如下配置界面,完成相应的配置后并保存,根目录下会存在一份.config文件保存当前选择的配置项,并将.config文件转为RT-Thread的系统配置文件rtconfig.h。详细内容可参考:https://bbs.elecfans.com/jishu_2279148_1_1.html
功能配置——启用NTP时间自动同步
若 RT-Thread 已接入互联网,可启用 NTP 时间自动同步功能,定期同步本地时间。
首先在 menuconfig 中按照如下选项开启 NTP 功能:
开启 NTP 后 RTC 的自动同步功能将会自动开启,还可以设置同步周期和首次同步的延时时间:
生成执行执行文件,在Finsh命令行,输入 date
即可查看当前当地时区时间,大致效果如下:
功能配置——启用硬件RTC
查看PICO RP2040设备现有驱动。查看UART和GPIO的驱动源码所在路径,后续将在该路径下添加RTC驱动相关的源码。
硬件RTC驱动源码开发,参考现有的UART及GPIO的驱动源码,了解其结构及逻辑调用关系。参考PICO RP2040设备SDK下的…/pico-sdk/src/rp2_common/hardware_rtc下的RTC源码,了解当前PICO 设备下所能与硬件操作相关的函数实现。参考/rt-thread/components/drivers/rtc/下的RTC的设备驱动框架源码,了解RTC设备类封装的函数及相关接口体。综上,针对某一操作,传递至设备框架层的函数能够有效调用驱动层的函数,驱动层的函数调用设备SDK衍生出的函数,进而实现对硬件的操作。
参考看门狗设备使用时序图,RTC设备驱动核心代码如下:
创建RTC设备:
根据设备框架层rtc.h下的结构体,创建设备驱动层drv_rtc.c下的_rtc_ops对象。
接下来,依次实现RTC设备初始化函数(pico_rtc_init)、设置时间函数(pico_rtc_set_secs)、获取时间函数(pico_rtc_get_secs),使得RTC具备硬件访问能力。
RTC设备驱动程序根据RTC设备模型定义,创建出具备硬件访问能力的RTC设备实例后,将RTC设备通过 rt_hw_rtc_register()
接口注册到RTC设备驱动框架中。
硬件RTC验证:
ps:设备驱动开发中所涉及到的其他源文件或头文件,可在当前项目下的libraries中的SConscript进行添加。
rtc_driver.c:
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <stdio.h>
#include <time.h>
#include <string.h>#include "board.h"
#include "drv_rtc.h"
#include <sys/time.h>#include "pico.h"
#include "hardware/rtc.h"
#include "hardware/irq.h"
#include "hardware/resets.h"
#include "hardware/clocks.h"struct rtc_device_object
{rt_rtc_dev_t rtc_dev;
#ifdef RT_USING_ALARMstruct rt_rtc_wkalarm wkalarm;
#endif
};static struct rtc_device_object rtc_device;bool rtc_running(void) {return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS);
}static bool valid_datetime(datetime_t *t) {// Valid ranges taken from RTC doc. Note when setting an RTC alarm// these values are allowed to be -1 to say "don't match this value"if (!(t->year >= 0 && t->year <= 4095)) return false;if (!(t->month >= 1 && t->month <= 12)) return false;if (!(t->day >= 1 && t->day <= 31)) return false;if (!(t->hour >= 0 && t->hour <= 23)) return false;if (!(t->min >= 0 && t->min <= 59)) return false;if (!(t->sec >= 0 && t->sec <= 59)) return false;return true;
}time_t datetime2sec(int year, int mon, int day, int hour, int min, int sec)
{struct tm tt;memset(&tt, 0, sizeof(tt));tt.tm_year = year;tt.tm_mon = mon;tt.tm_mday = day;tt.tm_hour = hour;tt.tm_min = min;tt.tm_sec = sec;return mktime(&tt);
}static rt_err_t pico_rtc_init(void){// Get clk_rtc freq and make sure it is runninguint rtc_freq = clock_get_hz(clk_rtc);assert(rtc_freq != 0);// Take rtc out of reset now that we know clk_rtc is runningreset_block(RESETS_RESET_RTC_BITS);unreset_block_wait(RESETS_RESET_RTC_BITS);// Set up the 1 second divider.// If rtc_freq is 400 then clkdiv_m1 should be 399rtc_freq -= 1;// Check the freq is not too big to divideassert(rtc_freq <= RTC_CLKDIV_M1_BITS);// Write divide valuertc_hw->clkdiv_m1 = rtc_freq;return RT_EOK;
}bool rtc_get_datetime(datetime_t *t) {// Make sure RTC is runningif (!rtc_running()) {return false;}// Note: RTC_0 should be read before RTC_1uint32_t rtc_0 = rtc_hw->rtc_0;uint32_t rtc_1 = rtc_hw->rtc_1;t->dotw = (int8_t) ((rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB);t->hour = (int8_t) ((rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB);t->min = (int8_t) ((rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB);t->sec = (int8_t) ((rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB);t->year = (int16_t) ((rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB);t->month = (int8_t) ((rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB);t->day = (int8_t) ((rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB);return true;
}static rt_err_t pico_rtc_get_secs(time_t *sec)
{struct timeval tv;datetime_t t;rtc_get_datetime(&t);tv.tv_sec = datetime2sec(t.year,t.month,t.day,t.hour,t.min,t.sec);*(time_t *) sec = tv.tv_sec;return RT_EOK;
}bool rtc_set_datetime(datetime_t *t) {if (!valid_datetime(t)) {return false;}// Disable RTCrtc_hw->ctrl = 0;// Wait while it is still activewhile (rtc_running()) {tight_loop_contents();}// Write to setup registersrtc_hw->setup_0 = (((uint32_t)t->year) << RTC_SETUP_0_YEAR_LSB ) |(((uint32_t)t->month) << RTC_SETUP_0_MONTH_LSB) |(((uint32_t)t->day) << RTC_SETUP_0_DAY_LSB);rtc_hw->setup_1 = (((uint32_t)t->dotw) << RTC_SETUP_1_DOTW_LSB) |(((uint32_t)t->hour) << RTC_SETUP_1_HOUR_LSB) |(((uint32_t)t->min) << RTC_SETUP_1_MIN_LSB) |(((uint32_t)t->sec) << RTC_SETUP_1_SEC_LSB);// Load setup values into rtc clock domainrtc_hw->ctrl = RTC_CTRL_LOAD_BITS;// Enable RTC and wait for it to be runningrtc_hw->ctrl = RTC_CTRL_RTC_ENABLE_BITS;while (!rtc_running()) {tight_loop_contents();}return true;
}static rt_err_t pico_rtc_set_secs(time_t *sec)
{datetime_t t;struct tm *local;local = localtime(sec);t.year = local->tm_year;t.month = local->tm_mon;t.day = local->tm_mday;t.hour = local->tm_hour;t.min = local->tm_min;t.sec = local->tm_sec;rtc_set_datetime(&t);return RT_EOK;
}const static struct rt_rtc_ops _rtc_ops =
{pico_rtc_init,pico_rtc_get_secs,pico_rtc_set_secs,RT_NULL,RT_NULL,RT_NULL,RT_NULL,
};int rt_hw_rtc_init(void)
{rt_err_t ret = RT_EOK;rtc_device.rtc_dev.ops = &_rtc_ops;ret = rt_hw_rtc_register(&rtc_device.rtc_dev, "rtc", RT_DEVICE_FLAG_RDWR, RT_NULL);if(ret != RT_EOK){return ret;}
#ifdef RT_USING_ALARMrt_rtc_alarm_init();
#endif
}
INIT_DEVICE_EXPORT(rt_hw_rtc_init);