rt-thread 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_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);
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值