当前位置: 首页 > news >正文

第19章_瑞萨MCU零基础入门系列教程之RTC

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总: https://blog.csdn.net/qq_35181236/article/details/132779862


第19章 RTC

本章目标

  • 了解瑞萨处理器RTC模块的使用

19.1 RTC模块的使用

19.1.1 RTC简介

RA6M5的RTC(Real Time Clock)外设,实质是一个掉电后还继续运行的定时器。RA6M5的实时时钟(RTC)有两种计数模式:日历计数模式、二进制计数模式,可以通过寄存器的设置来切换模式。对于日历计数模式,RTC具有从2000年到2099年的100年日历,并自动调整闰年的日期。对于二进制计数模式,RTC计数秒,并保留信息作为串行值。二进制计数模式可用于公历(西历)以外的日历。

可以选择作子时钟振荡器或LOCO作为时间计数器的计数源。RTC使用128Hz的时钟,通过将计数源除以预分频器的值获得。

19.1.2 RTC特征

瑞萨处理器的RTC有如下特征:

  • 计数模式:日历计数模式和二进制计数模式。
  • 时钟源:子时钟或LOCO。
  • l 日历计数模式:年,月,日,星期,小时,分钟,秒计数。
  • 二进制计数模式:32位二进制计数器。
  • 闹钟中断:在日历计数模式下,可以与年,月,日,星期,小时,分钟和秒进行比较。在二进制计数模式下则与32位2进制计数器进行对比。
  • 周期性中断:可以选择2秒,1秒,1/2秒,1/4秒,1/8秒,1/16秒,1/32秒,1/64秒、1/128秒或1/256秒作为中断周期。
  • 进位中断:当从64HZ计数器到二进制计数器的进位时和当改变64Hz计数器和R64同时读取CNT寄存器时进行中断。
  • 输入捕获:当检测到捕获时间输入引脚的电平发生跳变时(上升沿或者下降沿时),可以进行输入捕获。 该输入捕获可以用日历计数或者二进制计数。电平跳变时可以产生中断。与GPT相同的是,该输入捕获也能使用噪声滤波器。
  • 事件关联:周期性输出事件。
  • TrustZone过滤器:可以设置安全属性。

19.1.3 RTC的系统框图

瑞萨处理器的RTC系统框图大致分为以下几部分:

  1. 外部引脚:
  • XCIN/XCOUT,连接到外部32.768kHz晶振;
  • RTCOUT:输出1Hz或64Hz的方波,无法在待机模式下使用。
  • RTCICn(n = 0,1,2):输入捕获引脚。
  1. 计数器

这个计数器可以在VBAT供电下使用,由以下寄存器(同时也是计数器)构成:

  • R64CNT:R64CNT是一个8位的计数器,由128Hz的时钟脉冲驱动,实际只使用[6:0]位,故其最大计数值为128次, 也就是1秒就会产生一次进位,并驱动RSECCNT/BCNT计数器+1。
  • RSECCNT:用于统计R64CNT每秒产生的进位信号,表示“秒”,设置范围为0-59,如果设置其他值,会导致RTC工作异常。
  • RMINCNT:用于统计RSECCNT每分钟产生的进位信号,表示“分”,设置范围为0-59,如果设置其他值,会导致RTC工作异常。
  • RHRCNT:用于统计RMINCNT每小时产生的进位信号,表示“时”,当RTC设置为24小时制,则设置范围时0-23。 当RTC设置为12小时制,则设置范围时0-11。如果设置其他值,会导致RTC工作异常。
  • RDAYCNT:用于统计RHRCNT的每天时产生进位信号,表示“日”,计数范围取决于月份以及这一年是否为闰年。设置范围为1-31,如果设置其他值,会导致RTC工作异常。
  • RWKCNT:用于统计RHRCNT的每天产生的进位信号,表示“星期”,计数范围为0-6,如果设置其他值,会导致RTC工作异常。
  • RMONCNT:用于统计RDAYCNT每个月产生的进位信号,表示“月”,计数范围为1-12,如果设置其他值,会导致RTC工作异常。
  • RYRCNT:用于统计RMONCNT每个月产生的进位信号,表示“年”,计数范围为0-99,如果设置其他值,会导致RTC工作异常。

在二进制模式下,RSECCNT、RMINCNT、RHRCNT、RWKCNT共同构成BCNT(Binary Counter),是一个32位向上递增的计数器。通过统计R64CNT的进位次数进行计数。

  1. 闹钟功能

在日历计数模式下,闹钟可以按年、月、日、周、时、分、秒或它们的任意组合来设置。在设置闹钟时,往涉及的寄存器的ENB位(都是最高位)写1, 并在低位设置比较值;对于不涉及的寄存器,往它的ENB位写入0。例如,设置闹钟为每天的8点30分, 则在RMINAR(分闹钟寄存器)的最高位写入1,同时低位为30。RHRAR(时闹钟寄存器)最高位写入1,同时低位写8。其他寄存器则在最高位写0。

在二进制计数模式下,时间值是32位的数值,设置闹钟时,要比较哪些位?在BCNTnAER中,将需要比较的位写1,不需要比较的位写0。这些位的比较值是什么?BCNTnAR中设置比较值。

需要注意的是,在日历计数模式下,闹钟寄存器的比较值使用BCD码。

  1. 中断控制

RTC支持的中断请求有以下三种:

  • 闹钟中断请求
  • 周期计数中断请求
  • 捕获比较中断请求
  1. 输入捕获控制单元

整体框图如下图所示:

19.1.4 配置RTC模块

配置RTC时,需要配置引脚和Stack模块。

  1. 配置RTC引脚

在RASC的配置界面中去“Pins”里的“Peripherals”找到“Timers:RTC”,选择RTC0后,在右侧使能它的操作模式,如果不输出RTC时钟或者捕获采样信号,就不需要选择引脚,如下图所示:

  1. 加配置RTC的Stack模块

首先需要去“Stacks”中添加RTC的Stack,如下图所示:

然后去配置RTC Stack的参数。

RTC的Stack参数分为3个板块:

  1. Common:配置RTC的通用参数,所有的RTC模块都会使用到这个板块的参数配置;
  2. Module:指定RTC模块的定制参数,包括模块名称、时钟源、中断回调函数、中断优先级等;
  3. Pins:RTC的引脚;

RTC的Common板块中比较重要的参数是“Set Source Clock in Open”,如果使能了这个参数且后续使用的时钟源是子时钟,那么用户在代码中就不需要使用函数手动设置时钟源;如果不使能,就需要在代码中调用函数手动设置RTC的时钟源。默认是“Enabled”。

而RTC的Module板块需要配置的参数比较多,详见下表:

下图是设置万年历实验的RTC Stack模块参数配置图:

在万年历实验中,选择的时钟源是子时钟系统,使能了周期计数中断,它的优先级选择为比较低的10级优先级,中断回调函数的函数名设置为rtc_callback。

19.1.5 配置信息解读

因为没有使用RTC的输入/输出引脚,因而使用RASC配置好RTC并生成工程后,只会在hal_data.c中生成RTC设备对象的配置信息代码。

在hal_data.c中,定义了一个rtc_instance_t类型的全局变量g_rtc0,它含有控制参数、配置信息、接口信息:

const rtc_instance_t g_rtc0 =
{.p_ctrl        = &g_rtc0_ctrl,.p_cfg         = &g_rtc0_cfg,.p_api         = &g_rtc_on_rtc
};
  • p_ctrl:RTC控制参数,它指向结构体变量g_rtc0_ctrl,g_rtc0_ctrl结构体原型如下:
typedef struct st_rtc_ctrl
{uint32_t          open;                     ///< Whether or not driver is openconst rtc_cfg_t * p_cfg;                    ///< Pointer to initial configurationsvolatile bool     carry_isr_triggered;      ///< Was the carry isr triggeredvoid (* p_callback)(rtc_callback_args_t *); // Pointer to callback that is called when a rtc_event_t occurs.rtc_callback_args_t * p_callback_memory;    // Pointer to non-secure memory that can be used to pass arguments to a callback in non-secure memory.void const * p_context;                     // Pointer to context to be passed into callback function
} rtc_instance_ctrl_t;
  • p_cfg:RTC配置参数,它指向全局常量g_rtc0_cfg,此常量成员的取值来自用户在RASC中对RTC的配置;以万年历的配置为例,此常量的赋值代码如下:
const rtc_cfg_t g_rtc0_cfg =
{.clock_source            = RTC_CLOCK_SOURCE_SUBCLK,.freq_compare_value_loco = 255,.p_err_cfg               = &g_rtc0_err_cfg,.p_callback              = rtc_callback,
......(省略内容)
};
  • p_api:RTC操作函数的封装,指向结构体g_rtc_on_rtc,后面讲解。

19.1.6 中断回调函数

在RASC中设置了RTC的中断回调函数名字,会在hal_data.h中声明此函数:

#ifndef rtc_callback
void rtc_callback(rtc_callback_args_t * p_args);
#endif

用户需要在自己的程序中实现此函数,例如:

void rtc_callback(rtc_callback_args_t * p_args)
{if(RTC_EVENT_PERIODIC_IRQ == p_args->event){}
}

用户可以根据参数p_args的event成员判断是什么触发了RTC的中断,RTC的中断事件有以下这些:

/** Events that can trigger a callback function */
typedef enum e_rtc_event
{RTC_EVENT_ALARM_IRQ,               ///< Real Time Clock ALARM IRQRTC_EVENT_PERIODIC_IRQ,            ///< Real Time Clock PERIODIC IRQ
} rtc_event_t;

只有2种原因:闹铃中断、周期计数中断。

19.1.7 API接口及其用法

RTC模块的操作方法封装到了结构体rtc_api_t中,此结构体的原型如下:

typedef struct st_rtc_api
{fsp_err_t (* open)(rtc_ctrl_t * const p_ctrl, rtc_cfg_t const * const p_cfg);fsp_err_t (* close)(rtc_ctrl_t * const p_ctrl);fsp_err_t (* clockSourceSet)(rtc_ctrl_t * const p_ctrl);fsp_err_t (* calendarTimeSet)(rtc_ctrl_t * const p_ctrl, rtc_time_t * const p_time);fsp_err_t (* calendarTimeGet)(rtc_ctrl_t * const p_ctrl, rtc_time_t * const p_time);fsp_err_t (* calendarAlarmSet)(rtc_ctrl_t * const p_ctrl, rtc_alarm_time_t * const p_alarm);fsp_err_t (* calendarAlarmGet)(rtc_ctrl_t * const p_ctrl, rtc_alarm_time_t * const p_alarm);fsp_err_t (* periodicIrqRateSet)(rtc_ctrl_t * const p_ctrl, rtc_periodic_irq_select_t const rate);fsp_err_t (* errorAdjustmentSet)(rtc_ctrl_t * const p_ctrl, rtc_error_adjustment_cfg_t const * const err_adj_cfg);fsp_err_t (* callbackSet)(rtc_ctrl_t * const p_ctrl, void (* p_callback)(rtc_callback_args_t *),void const * const p_context, rtc_callback_args_t * const p_callback_memory);fsp_err_t (* infoGet)(rtc_ctrl_t * const p_ctrl, rtc_info_t * const p_rtc_info);
} rtc_api_t;

瑞萨在r_rtc.c中实现了一个rtc_api_t结构体,里面填充了各个函数指针,如下:

const rtc_api_t g_rtc_on_rtc =
{.open               = R_RTC_Open,.close              = R_RTC_Close,.clockSourceSet     = R_RTC_ClockSourceSet,.calendarTimeGet    = R_RTC_CalendarTimeGet,.calendarTimeSet    = R_RTC_CalendarTimeSet,.calendarAlarmGet   = R_RTC_CalendarAlarmGet,.calendarAlarmSet   = R_RTC_CalendarAlarmSet,.periodicIrqRateSet = R_RTC_PeriodicIrqRateSet,.infoGet            = R_RTC_InfoGet,.errorAdjustmentSet = R_RTC_ErrorAdjustmentSet,.callbackSet        = R_RTC_CallbackSet,
};

接下来介绍这些操作函数。

  1. 打开RTC设备
fsp_err_t (* open)(rtc_ctrl_t * const p_ctrl, rtc_cfg_t const * const p_cfg);
  • p_ctrl:rtc_ctrl_t结构体类型,用来记录一些状态信息;
  • p_cfg:rtc_cfg_t结构体类型,含有RTC的配置信息;

用户可以参考以下代码调用此函数初始化RTC的配置:

fsp_err_t err = g_rtc0.p_api->open(g_rtc0.p_ctrl, g_rtc0.p_cfg);
if(FSP_SUCCESS != err)
{printf("Error in %s on %d\r\n", __FUNCTION__, __LINE__);
}
  1. 关闭RTC设备
fsp_err_t (* close)(rtc_ctrl_t * const p_ctrl);

关闭RTC设备只需要传入RTC的控制参数即可,此函数会将控制参数p_ctrl中的状态标志位open成员改变位CLOSED。

用户可以参考以下代码关闭RTC设备:

fsp_err_t err = g_rtc0.p_api->close(g_rtc0.p_ctrl);
if(FSP_SUCCESS != err)
{printf("Error in %s on %d\r\n", __FUNCTION__, __LINE__);
}
  1. 设置RTC的时钟源
fsp_err_t (* clockSourceSet)(rtc_ctrl_t * const p_ctrl);

如果用户在RASC的配置中没有将“Set Source Clock in Open”设置为“Enabled”,就需要在代码中调用此函数手动设置使能RTC的时钟源。

用户可以参考以下代码手动设置使能RTC的时钟源:

fsp_err_t err = g_rtc0.p_api->clockSourceSet(g_rtc0.p_ctrl);
if(FSP_SUCCESS != err)
{printf("Error in %s on %d\r\n", __FUNCTION__, __LINE__);
}
  1. 设置RTC的计数起始时间
fsp_err_t (* calendarTimeSet)(rtc_ctrl_t * const p_ctrl, rtc_time_t * const p_time);
  • p_time:rtc_time_t结构体类型,此结构体成员表示日常的时间值(年月日,时分秒和星期),原型如下:
struct tm {int tm_sec;   /* seconds after the minute, 0 to 60(0 - 60 allows for the occasional leap second) */int tm_min;   /* minutes after the hour, 0 to 59 */int tm_hour;  /* hours since midnight, 0 to 23 */int tm_mday;  /* day of the month, 1 to 31 */int tm_mon;   /* months since January, 0 to 11 */int tm_year;  /* years since 1900 */int tm_wday;  /* days since Sunday, 0 to 6 */int tm_yday;  /* days since January 1, 0 to 365 */int tm_isdst; /* Daylight Savings Time flag */
};
typedef struct tm rtc_time_t;

在注释中已经解释了这些成员的取值范围。

用户需要先定义一个rtc_time_t结构体,将其各成员赋值后再调用函数calendarTimeSet函数,它会设置RTC从这个时间开始计数。

用户可以参考以下代码来设置RTC的初始计数时间:

rtc_time_t SetTime = {.tm_sec = 0,  //秒.tm_min = 0,  //分.tm_hour = 0,  //小时.tm_mday = 29,  //日(一个月中).tm_wday = 1,   //星期.tm_mon = 5,   //月份.tm_year = 2023-1900, //年份(如今年是2008,则这里输入2008-1900=108)
};
fsp_err_t err = g_rtc0.p_api->calendarTimeSet(g_rtc0.p_ctrl, &SetTime);
FSP_Assert(FSP_SUCCESS == err);

此段代码将RTC的初始时间设置为2023年5月29日星期一的0时0分0秒。

  1. 获取RTC的当前计数时间
fsp_err_t (* calendarTimeGet)(rtc_ctrl_t * const p_ctrl, rtc_time_t * const p_time);

此函数是获取RTC当前计数的时间,和设置时间使用的是同一个结构体。用户可以参考以下代码获取当前时间:

void rtc_callback(rtc_callback_args_t * p_args)
{if(RTC_EVENT_PERIODIC_IRQ == p_args->event){/*若是周期中断,获取日期*/gRtcPeriodFlag = true;g_rtc0.p_api->calendarTimeGet(g_rtc0.p_ctrl, (rtc_time_t*)&gCurTime);}
}

上述代码是在RTC的中断回调函数中,更新一个全局变量gCurTime,它被用来记录时间值。

可以使用如下函数返回gCurTime,获得当前时间:

rtc_time_t RTCDrvGetTime(void)
{RTCDrvWaitPeriodInt();return gCurTime;
}
  1. 设置RTC的闹铃时间
fsp_err_t (* calendarAlarmSet)(rtc_ctrl_t * const p_ctrl, rtc_alarm_time_t * const p_alarm);
  • p_alarm:rtc_alarm_time_t结构体类型,用于指定闹铃匹配规则、匹配值,此结构体原型如下:
typedef struct st_rtc_alarm_time
{rtc_time_t time;                   ///< Time structurebool       sec_match;  ///< Enable the alarm based on a match of the seconds fieldbool       min_match;  ///< Enable the alarm based on a match of the minutes fieldbool       hour_match; ///< Enable the alarm based on a match of the hours fieldbool       mday_match; ///< Enable the alarm based on a match of the days fieldbool       mon_match;  ///< Enable the alarm based on a match of the months fieldbool       year_match; ///< Enable the alarm based on a match of the years fieldbool dayofweek_match; ///< Enable the alarm based on a match of the dayofweek field
} rtc_alarm_time_t;
  • 第03行:闹铃匹配的时间,结构体内容见前文;
  • 第04~10行:RTC闹铃匹配规则,根据需求决定匹配哪个时间点,true-匹配,false-不匹配;

用户需要在程序中设定好匹配规则、匹配值后,再调用calendarAlarmSet函数进行设置,例如:

rtc_alarm_time_t AlarmTime = {.time = {.tm_sec = 30,  //秒.tm_min = 0,  //分.tm_hour = 0,  //小时.tm_mday = 29,  //日(一个月中).tm_wday = 1,   //星期.tm_mon = 5,   //月份.tm_year = 2023-1900, //年份(如今年是2023,则这里输入2023-1900=123)},.year_match = 0,.mon_match = 0,.mday_match = 0,.dayofweek_match = 0,.hour_match = 0,.min_match = 0,.sec_match = 1,
};
err = g_rtc0.p_api->calendarAlarmSet(g_rtc0.p_ctrl, &AlarmTime);
assert(FSP_SUCCESS == err);
  • 第02~10行:将闹铃时间设置为2023-5-29.0:0:30;
  • 第11~17行:匹配规则为仅匹配秒时间段,即每分钟的第30秒会匹配一次闹铃;
  • 第19行:调用函数完成闹铃设置;
  1. 获取RTC的闹铃时间
fsp_err_t (* calendarAlarmGet)(rtc_ctrl_t * const p_ctrl, rtc_alarm_time_t * const p_alarm);

获取闹铃设置信息的使用比较简单,仅是帮助用户获知当前RTC设置的闹铃的匹配规则、匹配值。参数和设置闹铃时间函数的参数是同一结构体类型。

  1. 设置RTC的中断周期
fsp_err_t (* periodicIrqRateSet)(rtc_ctrl_t * const p_ctrl, rtc_periodic_irq_select_t const rate);

rate:rtc_periodic_irq_select_t结构体类型,表示触发RTC周期中断的间隔时间,支持的时间如下:

typedef enum e_rtc_periodic_irq_select
{RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_256_SECOND = 6,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_128_SECOND = 7,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_64_SECOND  = 8,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_32_SECOND  = 9,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_16_SECOND  = 10,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_8_SECOND   = 11,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_4_SECOND   = 12,RTC_PERIODIC_IRQ_SELECT_1_DIV_BY_2_SECOND   = 13,RTC_PERIODIC_IRQ_SELECT_1_SECOND            = 14,RTC_PERIODIC_IRQ_SELECT_2_SECOND            = 15,
} rtc_periodic_irq_select_t;

根据枚举成员的命名可以知道,周期间隔中断时间是1/256秒、1/128秒……1秒、2秒,通常选用1秒的间隔,例如:

err 
= g_rtc0.p_api->periodicIrqRateSet(g_rtc0.p_ctrl, RTC_PERIODIC_IRQ_SELECT_1_SECOND);
assert(FSP_SUCCESS == err);
  1. 获取RTC的配置信息
fsp_err_t (* infoGet)(rtc_ctrl_t * const p_ctrl, rtc_info_t * const p_rtc_info);

p_rtc_info:RTC的配置信息和运行状态信息,配置信息指时钟源信息,运行状态则是指RTC是处于停滞状态还是运行状态:

typedef struct st_rtc_info
{rtc_clock_source_t clock_source;   ///< Clock source for the RTC blockrtc_status_t       status;         ///< RTC run status
} rtc_info_t;

运行状态的枚举成员如下:

typedef enum e_rtc_status
{RTC_STATUS_STOPPED = 0,            ///< RTC counter is stoppedRTC_STATUS_RUNNING = 1             ///< RTC counter is running
} rtc_status_t;

19.2 万年历实验

19.2.1 设计目的

学会使用瑞萨处理器的RTC FSP库函数接口,获取RTC的计数时间并且将之打印出来观察。

19.2.2 硬件连接

作为例程实验,本书没有连接外部电池电源VBAT,断电后无法保存维持RTC时钟,但是不影响本实验。

19.2.3 驱动程序

  1. 初始化RTC

需要设置RTC的起始计数时间,并设置周期性中断的时间值,代码如下:

int RTCDrvInit(void)
{rtc_time_t SetTime = {.tm_sec = 0,  //秒.tm_min = 0,  //分.tm_hour = 0,  //小时.tm_mday = 29,  //日(一个月中).tm_wday = 1,   //星期.tm_mon = 5,   //月份.tm_year = 2023-1900, //年份(如今年是2008,则这里输入2008-1900=108)};fsp_err_t err = g_rtc0.p_api->open(g_rtc0.p_ctrl, g_rtc0.p_cfg);assert(FSP_SUCCESS == err);err = g_rtc0.p_api->calendarTimeSet(g_rtc0.p_ctrl, &SetTime);assert(FSP_SUCCESS == err);err = g_rtc0.p_api->periodicIrqRateSet(g_rtc0.p_ctrl, RTC_PERIODIC_IRQ_SELECT_1_SECOND);assert(FSP_SUCCESS == err);return true;
}
  1. 中断回调函数和周期等待函数

本次实验,先在中断回调函数中判断触发中断的原因是否为“周期中断”,在全局变量gCurTime中更新当前时间值:

void rtc_callback(rtc_callback_args_t * p_args)
{if(RTC_EVENT_PERIODIC_IRQ == p_args->event){/*若是周期中断,获取日期*/gRtcPeriodFlag = true;g_rtc0.p_api->calendarTimeGet(g_rtc0.p_ctrl, (rtc_time_t*)&gCurTime);}
}

在此基础上封装一个周期中断的等待函数:

static int RTCDrvWaitPeriodInt(void)
{int ret = (int)gRtcPeriodFlag;gRtcPeriodFlag = false;return ret;
}

此函数如果返回true则表明时间已经更新。

3.时间获取函数

实际上,这个函数可以直接读取当前时间gCurTime,下面的代码中先判断是否发生了RTC周期性中断,只是想得到更新后的时间值(以免多次调用都得到同样的时间值):

int RTCDrvGetTime(rtc_time_t *time)
{if(RTCDrvWaitPeriodInt()){*time = gCurTime;return true;}return false;
}

此函数返回true才表明获取到了最新一次更新的时间,调用者根据返回值和输出参数time做处理。

19.2.4 测试程序

本次实验测试方法很简单,就是获取更新的时间再打印出来,代码如下:

void RTCAppTest(void)
{UARTDrvInit();RTCDrvInit();printf("RTC Test!\r\n");while(1){rtc_time_t time;if(RTCDrvGetTime(&time)){printf ("%.4d-%.2d-%.2d-%.2d:%.2d:%.2d\r", time.tm_year + 1900, time.tm_mon, time.tm_mday,time.tm_hour, time.tm_min, time.tm_sec);}}
}

为了对齐以便观察,打印的年份使用4个数字,而月日时分秒使用2个数字表示。

19.2.5 测试结果

在hal_entry()函数中调用测试函数,将编译出来的二进制文件烧录到处理器中运行可以看到一直在计数的时间:

image8

上图只是观察变化,因为RTC的起始计数时刻并未和网络时间统一校准,会出现一定的偏差。

19.3 闹铃实验

19.3.1设计目的

FSP库的RTC函数,设计一个简单的闹铃匹配程序。

19.3.2 RTC模块配置

和万年历实验相比,本节实验的RTC模块需要使能RTC的Alarm中断:

19.3.3驱动程序

1.初始化RTC

本次实验初始化RTC的时候除了设置RTC的起始计数时间以外,还要设置RTC的闹铃匹配规则和匹配值:

int RTCDrvInit(void)
{rtc_time_t SetTime = {.tm_sec = 0,  //秒.tm_min = 0,  //分.tm_hour = 0,  //小时.tm_mday = 29,  //日(一个月中).tm_wday = 1,   //星期.tm_mon = 5,   //月份.tm_year = 2023-1900, //年份(如今年是2023,则这里输入2023-1900=123)};rtc_alarm_time_t AlarmTime = {.time = {.tm_sec = 30,  //秒.tm_min = 0,  //分.tm_hour = 0,  //小时.tm_mday = 29,  //日(一个月中).tm_wday = 1,   //星期.tm_mon = 5,   //月份.tm_year = 2023-1900, //年份(如今年是2023,则这里输入2023-1900=123)},.year_match = 0,.mon_match = 0,.mday_match = 0,.dayofweek_match = 0,.hour_match = 0,.min_match = 0,.sec_match = 1,};fsp_err_t err = g_rtc0.p_api->open(g_rtc0.p_ctrl, g_rtc0.p_cfg);assert(FSP_SUCCESS == err);/* 设置起始计数时间 */err = g_rtc0.p_api->calendarTimeSet(g_rtc0.p_ctrl, &SetTime);assert(FSP_SUCCESS == err);/* 设置闹铃匹配时间和匹配规则 */err = g_rtc0.p_api->calendarAlarmSet(g_rtc0.p_ctrl, &AlarmTime);assert(FSP_SUCCESS == err);/* 设置触发周期中断的时间间隔 */err = g_rtc0.p_api->periodicIrqRateSet(g_rtc0.p_ctrl, RTC_PERIODIC_IRQ_SELECT_1_SECOND);assert(FSP_SUCCESS == err);return true;
}
  1. 中断回调函数和闹铃中断等待函数

本次实验的中断回调函数除了处理周期性中断外,还要处理闹铃中断,在闹铃中断事件中将闹铃事件标志置true:

void rtc_callback(rtc_callback_args_t * p_args)
{switch(p_args->event){case (RTC_EVENT_ALARM_IRQ):{/* 如果是闹铃事件,则闹铃标志设置true */gRtcAlarmFlag = true;break;}case (RTC_EVENT_PERIODIC_IRQ):{/*若是周期中断,获取日期*/gRtcPeriodFlag = true;g_rtc0.p_api->calendarTimeGet(g_rtc0.p_ctrl, (rtc_time_t*)&gCurTime);break;}default:break;} 
}

然后再封装一个闹铃事件等待函数:

static int RTCDrvAlarmEvent(void)
{if(gRtcAlarmFlag){gRtcAlarmFlag = false;return true;}return false;
}

用户通过调用此函数来判断是否触发闹铃事件。

  1. 时间获取函数

此函数就是移植的上一小节万年历实验的驱动函数,此处不再说明。

19.3.4 测试程序

本节实验既要实时获取并打印RTC的计数时间,也要实时判断是否发生了闹铃匹配,如果发生了闹铃匹配那么就打印信息:

void RTCAppTest(void)
{UARTDrvInit();RTCDrvInit();while(1){rtc_time_t time;if(RTCDrvGetTime(&time)){printf ("%.4d-%.2d-%.2d-%.2d:%.2d:%.2d\r", time.tm_year + 1900, time.tm_mon, time.tm_mday,time.tm_hour, time.tm_min, time.tm_sec);}if(RTCDrvAlarmEvent()){printf("\r\nAlarm Time!\r\n");}}
}

19.3.5 测试结果

在hal_entry()函数中调用测试函数,将编译出来的二进制文件烧录到处理器中运行观察可以发现,RTC计数时间的每个分钟的第30秒都会触发一次闹铃事件:


本章完

相关文章:

第19章_瑞萨MCU零基础入门系列教程之RTC

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…...

6、Spring之依赖注入源码解析(上)

依赖注入底层原理流程图: Spring中Bean的依赖注入原理| ProcessOn免费在线作图,在线流程图,在线思维导图 Spring中到底有几种依赖注入的方式? 首先分两种: 手动注入自动注入手动注入 在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值。 <bean n…...

vscode各种配置的方法

一. vscode配置 vscode 是微软公司提供的一个 代码编辑器。是做C/C常用的编辑器。 在安装后&#xff0c;可以根据自己需要自行安装常用的配置插件。同时&#xff0c;也可以在设置栏设置自己需要的功能&#xff0c;以方便使用。 下面学习 vscode的几种常见的设置。 二. vsco…...

每天几道面试题(第一天)

目录 第一幕 、第一场&#xff09;某大厦楼下大门前第二场&#xff09;电梯中第三场&#xff09;走廊中 友情提醒 背面试题很枯燥&#xff0c;加入一些戏剧场景故事人物来加深记忆。PS:点击文章目录可直接跳转到文章指定位置。 第一幕 、 第一场&#xff09;某大厦楼下大门前…...

[paddle]paddlepaddle官方安装命令合集

官方最新安装命令&#xff1a; https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/windows-pip.html 历史命令&#xff1a; V2.4 环境支持 Python 版本 3.6/3.7/3.8/3.9/3.10 PIP安装方式 Windows 安装 GPU版本支持CUDA 10.2/11.…...

使用JS实现一个简单的观察者模式(Observer)

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 手撸Observer⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领…...

智能井盖传感器:高效守护城市道路安全

近年来&#xff0c;井盖出问题导致事故的报道时有发生&#xff0c;但却容易被公众所忽视。井盖作为城市基础设施的一部分&#xff0c;主要用于保护下方的供水管道、下水道以及电信线缆等。然而&#xff0c;由于长时间使用、缺乏维护、设计不合理等原因&#xff0c;井盖出现问题…...

pycharm创建py文件时自动添加基础信息--模板

在图片中加入下面基本信息&#xff0c;这些基本信息可以自己定义&#xff1a; #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : ${DATE} ${TIME} # Author : supermps # File : ${NAME}.py # Software : ${PRODUCT_NAME} import logging import math import w…...

Notpad++常用正则表达式替换案例集锦

1、在每行的开头加上单引号 2、在每行的结尾加上单引号 3、“删除”某个关键字之前字符串 原始字符串&#xff1a; 注&#xff1a;仅保留含有"[条件日志]:"之后的内容&#xff0c;“日志:”前面的内容“删除”掉&#xff0c;即替换为“”。 4、“删除”某个关键字…...

DGA行为转变引发了对网络安全的担忧

Akamai的研究人员发现&#xff0c;在域名系统(DNS)流量数据中&#xff0c;动态种子域生成算法(DGA)家族的行为发生了令人担忧的变化。这一发现揭示了恶意行为者如何调整他们的策略来延长他们的指挥与控制(C2)通信通道的寿命&#xff0c;以保护他们的僵尸网络。 从技术角度来看…...

微信小程序开发---页面导航

目录 一、页面导航的概念 二、页面导航的实现 &#xff08;1&#xff09;声明式导航 1、概念 2、导航到tabBar页面 3、导航非tabBar页面 4、后退导航 &#xff08;2&#xff09;编程式导航 1、导航到tabBar页面 2、导航到非tabBar页面 3、后退导航 三、导航传参 &…...

torch.nn中的L1Loss和MSELoss

我们打开Pytorch官网&#xff0c;找到torch.nn中的loss function&#xff0c;进去如下图所示。 L1LOSS 我们先来看看 L1LOSS 损失函数的使用。下图是官网给出的描述。 L1loss有两种方式&#xff0c;一种是将所有误差累加作为总损失&#xff0c;另一种是将所有误差累加之后求平…...

Speech | 语音处理,分割一段音频(python)

本文主要是关于语音数据在处理过程中的一些脚本文件以及实例&#xff0c;所有代码只需要更改所需处理的文件路径&#xff0c;输出路径等&#xff0c;全部可运行。 目录 所需环境 方法1&#xff1a;将一整段音频按时间批量切成一个一个音频 方法2&#xff1a;将一整段音频按…...

【深度学习】 Python 和 NumPy 系列教程(三):Python容器:1、列表List详解(初始化、索引、切片、更新、删除、常用函数、拆包、遍历)

目录 一、前言 二、实验环境 三、Python容器&#xff08;Containers&#xff09; 0、容器介绍 1、列表&#xff08;List&#xff09; 1. 初始化 a. 创建空列表 b. 使用现有元素初始化列表 c. 使用列表生成式 d. 复制列表 2. 索引和切片 a. 索引 b. 负数索引 c. 切…...

【C++笔记】C++string类模拟实现

【C笔记】Cstring类模拟实现 一、实现模型和基本接口1.1、各种构造和析构1.2、迭代器 二、各种插入和删除接口2.1、插入接口2.2、删除接口2.3、resize接口 三、各种运算符重载3.1、方括号运算符重载3.2、各种比较运算符重载 四、查找接口4.1、查找字符4.2、查找子串 五、流插入…...

操作系统之课后习题——引论

&#xff08;一&#xff09;简答题 1.在计算机系统上配置OS的目标是什么&#xff1f;作用主要表现在哪几个方面&#xff1f; 答&#xff1a; 在计算机系统上配置OS&#xff0c;主要目标是实现&#xff1a;方便性、有效性、可扩充性和开放性&#xff1b; OS的作用主要表现在以下…...

【PHP代码审计】反序列化漏洞实战

文章目录 概述资源下载地址Typecho代码审计-漏洞原理call_user_func()_applyFilter()、get()与__get__toString()__construct()install.php POC利用漏洞利用复现利用链执行phpinfo()GET利用POST利用 getshell生成payload漏洞利用蚁剑连接 总结 概述 序列化&#xff0c;“将对象…...

Socks5 与 HTTP 代理在网络安全中的应用

目录 Socks5和HTTP代理在网络安全中的应用。 Socks5代理和HTTP代理的优点和缺点。 选择合适的代理IP需要考虑的因素&#xff1a; 总结 在网络安全领域中&#xff0c;Socks5和HTTP代理都扮演着重要的角色。作为两种不同的代理技术&#xff0c;它们在网络安全中的应用各有特点…...

进阶C语言-指针的进阶(中)

指针的进阶 &#x1f4d6;5.函数指针&#x1f4d6;6.函数指针数组&#x1f4d6;7.指向函数指针数组的指针&#x1f4d6;8.回调函数 &#x1f4d6;5.函数指针 数组指针 - 指向数组的指针 - 存放的是数组的地址 - &数组名就是数组的地址。 函数指针 - 指向函数的指针 - 存放的…...

保姆级-微信小程序开发教程

一&#xff0c;注册微信小程序 如果你还没有微信公众平台的账号&#xff0c;请先进入微信公众平台首页&#xff0c;点击 “立即注册” 按钮进行注册。注册的账号类型可以是订阅号、服务号、小程序以及企业微信&#xff0c;我们选择 “小程序” 即可。 接着填写账号信息&#x…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 &#xff09; 缓存工作原理分析 在了解了本地缓存和远程缓存之后&#xff0c;我们来探究缓存是如何工作的。以计算文件的哈希串为例&#xff0c;若后续运行任务时文件哈希串未变&#xff0c;系统会直接使用对应的输出和制品文件。 2 …...