rt-thread rtc设备驱动开发

基于pico 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:

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_ALARM
    struct 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 running
    uint rtc_freq = clock_get_hz(clk_rtc);
    assert(rtc_freq != 0);
    // Take rtc out of reset now that we know clk_rtc is running
    reset_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 399
    rtc_freq -= 1;
    // Check the freq is not too big to divide
    assert(rtc_freq <= RTC_CLKDIV_M1_BITS);
    // Write divide value
    rtc_hw->clkdiv_m1 = rtc_freq;
    return RT_EOK; 
}

bool rtc_get_datetime(datetime_t *t) {
    // Make sure RTC is running
    if (!rtc_running()) {
        return false;
    }

    // Note: RTC_0 should be read before RTC_1
    uint32_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 RTC
    rtc_hw->ctrl = 0;
    // Wait while it is still active
    while (rtc_running()) {
        tight_loop_contents();
    }

    // Write to setup registers
    rtc_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 domain
    rtc_hw->ctrl = RTC_CTRL_LOAD_BITS;

    // Enable RTC and wait for it to be running
    rtc_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_ALARM
    rt_rtc_alarm_init();
#endif
}
INIT_DEVICE_EXPORT(rt_hw_rtc_init);
相关推荐
hz5678913 小时前
内网视频会议系统建设方案:适合政企单位的安全会议选择
安全·云计算·音视频·实时音视频·信息与通信
普普通通的一名码农20 小时前
TPS6120芯片说明
驱动开发·笔记·芯片手册
hz567892 天前
电子远程评标系统哪家好?安全、合规、易用平台评测指南
安全·云计算·音视频·实时音视频·信息与通信
hz567892 天前
局域网视频会议解决方案,适配政企专网场景
音视频·实时音视频·信息与通信·安全架构
阿拉斯攀登2 天前
SPI 设备驱动开发
linux·驱动开发·嵌入式硬件·linux内核·嵌入式·linux驱动·spi
阿拉斯攀登2 天前
I2C 设备驱动开发
linux·驱动开发·嵌入式硬件·嵌入式·linux驱动·i2c·字符设备驱动
REDcker3 天前
远程实时操控技术详解
音视频·实时音视频·数据采集·遥控·遥操作·数据闭环·远程操控
johnny2333 天前
规范驱动开发SDD:Spec Kit、OpenSpec、BMAD-METHOD、Kiro Spec
驱动开发
美狐美颜SDK开放平台3 天前
娱乐直播APP平台开发实践:视频美颜SDK接入经验总结
php·音视频·实时音视频·娱乐·第三方美颜sdk·视频美颜sdk
阿拉斯攀登3 天前
RTSP 拉流与录制:IPC 摄像头本地录像完整方案
ffmpeg·音视频·webrtc·实时音视频·视频编解码