STM32F4 RTC-Alarm 的使用(RT-Thread操作系统)

2023-11-03


相关文章
1 STM32F4 RTC-Alarm 的使用
2 STM32F4 PM组件 DeepSleep 模式的使用
3 STM32F4 PM组件 StandBy 模式的使用


  本次测试使用的开发板为正点原子的 STM32F429IGT6 阿波罗开发板,RT-Thread 的 RTC 驱动没有实现闹钟设置的部分,在测试时花了一些时间查找修改办法,在此记录一下。

1 工程的创建和配置

  本次测试创建的工程是基于芯片创建的 RT-Thread 的标准工程,使用的开发环境是 RT-Thread Studio,各软件包的版本如下

类型 对应的版本
内核 4.0.4
芯片支持包 0.2.2
RTT Studio 2.2.3
STM32 CubeMX 6.2.1

1.1 CubeMX 的配置

  创建工程后首先对时钟和RTC的功能进行配置,在这里我们使用 CubeMX 进行图形化的配置,双击工程中的“CubeMX Settings”打开图形化配置软件 CubeMX 进行配置。

1.1.1 时钟源的选择

  RT-Thread Studio创建好的工程默认使用的是内部的低速晶振作为时钟源,因此我们首先进行时钟源的选择配置,本次使用的开发板用的是无源的晶振,HSE为25MHz,LSE为32.768KHZ。在配置界面选择 BYPASS Clock Source 表示使用的是有源晶振,选择 Crystal 表示使用的是无源晶振。时钟源选择之后在右侧的引脚描述图中可以看到对应的引脚已被配置,配置完成的界面如下图所示。
1.png

1.1.2 Debug 引脚配置

  本次使用四线的STLINK进行程序的烧写和调试,相应的引脚按照下图所示进行配置。需要注意的是在裸机代码中如果Debug引脚不进行配置的话,烧写一次程序后板子将不能进行第二次烧写,原因是Debug引脚没有进行配置复用成其他功能了,此时应该调整BOOT0和BOOT1引脚的电平,使用SRAM启动,然后烧写一次程序后再将BOOT0接地,就能正常烧写程序。
2.png

1.1.3 控制台串口的配置

  控制台串口的配置如下,我们使用的是串口1作为控制台串口,串口工作模式是异步通信,串口的收发引脚是PA9和PA10。配置好串口后可以在右侧的引脚图中看到PA9和PA10已经复用为串口1的功能。串口的通讯参数按照默认的115200 8n1进行配置。
  因为CubeMX配置后的工程使用的是文件 cubemx/Inc/stm32f4xx_hal_conf.h 里面的宏定义来确定外设的使能,所以在这里需要配置一个串口,以便自动打开宏 HAL_UART_MODULE_ENABLED,否则编译会报错。
3.png

1.1.4 RTC的配置

  本次测试的目的是测试RTC的闹钟功能,因此需要对RTC进行配置。F429有Alarm A和Alarm B两个闹钟,本次以Alarm A为例进行测试,配置界面如下图所示。初始的时间可以按照实际的进行设置,也可以不进行设置。
4.png

  RTC外设没有独立的中断,是通过触发EXTI来产生RTC外设中断。因此我们还需要对中断进行配置,配置界面如下所示,我们使能了RTC Alarm中断,中断优先级测试时没有对其进行修改,按照默认进行配置。
5.png

1.1.5 时钟树配置

  配置完了时钟源和外设,我们需要对时钟树进行配置,如下图所示。
6.png

1.1.6 代码生成

  所有选项配置好之后我们点击CubeMX右上角的“GENERATE CODE”生成配置代码,生成之后关闭CubeMX软件,然后查看Studio软件,弹出如下图所示的对话框表示生成成功。
7.png

1.2 RT-Thread Settings 的配置

  我们需要配置 RT-Thread 的设备驱动,使能 RTC 外设的驱动和 Alarm 的功能,如下所示。
8.png

  除此之外还能进行时区的设置,如下所示,默认为东八区
9.png

1.3 board.h 的修改

  上述配置完成之后需要在文件 drivers/board.h 中开启宏 BSP_USING_ONCHIP_RTC,以使能 RTC 硬件配置,如下所示。
image.png

2 编译及错误修改

  上述的配置全部完成之后,点击编译按钮,对配置好的功能进行编译。  RTT Source Code v4.0.4 版本的 RTC 在移植时会有函数重复定义和缺少头文件的问题,需要注意一下。错误信息如下

../drivers/drv_rtc.c:38:8: error: unknown type name 'time_t'
 static time_t get_rtc_timestamp(void)

../drivers/drv_rtc.c:211:17: error: conflicting types for 'rt_hw_rtc_register'
 static rt_err_t rt_hw_rtc_register(rt_device_t device, const char *name, rt_uint32_t flag)
                 ^
In file included from F:\HotNet_Project\HN1000_GCU\hn1000_gcu_app\rt-thread\components\drivers\include/rtdevice.h:33:0,
                 from ../drivers/drv_rtc.c:13:
F:\HotNet_Project\HN1000_GCU\hn1000_gcu_app\rt-thread\components\drivers\include/drivers/rtc.h:42:10: note: previous declaration of 'rt_hw_rtc_register' was here
 rt_err_t rt_hw_rtc_register(rt_rtc_dev_t  *rtc,

  解决方法为
  1. 在 drivers/drv_rtc.c 中增加头文件 #include <sys/time.h>
  2. 文件 rt-thread/components/drivers/include/drivers/rtc.h 中注释掉 rt_hw_rtc_register() 函数的声明,如下图所示。
10.png

3 源码的修改

  使用时发现源码有一些问题导致不能正确的产生 RTC 闹钟的中断,将修改的源码部分记录如下。

3.1 alarm.c 的修改

  alarm.c 文件位于路径 rt-thread/components/drivers/rtc/alarm.c。参考本文的第 6.1 小节的解释,将 alarm.c 中的所有的 gmtime_r 替换为 localtime_r

3.2 drv_rtc.c 的修改

  drv_rtc.c 文件位于路径drivers/drv_rtc.c。本部分内容参考文章 drv_rtc.c alarm 的实现。在 RT-Thread 源码提供的drv_rtc.c文件是缺少 RT_DEVICE_CTRL_RTC_SET_ALARMRT_DEVICE_CTRL_RTC_GET_ALARM 的实现,参考论坛中的 @mii 的文章对其进行修改,主要添加了函数 set_rtc_alarm_stamp()get_rtc_alarm_stamp(),修改的详细步骤参考上面的文章链接,修改后的 drv_rtc.c 如下,添加的内容均有 2022-06-08 时间标记。

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date         Author        Notes
 * 2018-12-04   balanceTWK    first version
 */

#include "board.h"
#include<rtthread.h>
#include<rtdevice.h>

#ifdef BSP_USING_ONCHIP_RTC

#ifndef HAL_RTCEx_BKUPRead
#define HAL_RTCEx_BKUPRead(x1, x2) (~BKUP_REG_DATA)
#endif
#ifndef HAL_RTCEx_BKUPWrite
#define HAL_RTCEx_BKUPWrite(x1, x2, x3)
#endif
#ifndef RTC_BKP_DR1
#define RTC_BKP_DR1 RT_NULL
#endif

//#define DRV_DEBUG
#define LOG_TAG             "drv.rtc"
#include <drv_log.h>

#define BKUP_REG_DATA 0xA5A5

static struct rt_device rtc;

static RTC_HandleTypeDef RTC_Handler;

static time_t get_rtc_timestamp(void)
{
    RTC_TimeTypeDef RTC_TimeStruct = { 0 };
    RTC_DateTypeDef RTC_DateStruct = { 0 };
    struct tm tm_new;

    HAL_RTC_GetTime(&RTC_Handler, &RTC_TimeStruct, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&RTC_Handler, &RTC_DateStruct, RTC_FORMAT_BIN);

    tm_new.tm_sec = RTC_TimeStruct.Seconds;
    tm_new.tm_min = RTC_TimeStruct.Minutes;
    tm_new.tm_hour = RTC_TimeStruct.Hours;
    tm_new.tm_mday = RTC_DateStruct.Date;
    tm_new.tm_mon = RTC_DateStruct.Month - 1;
    tm_new.tm_year = RTC_DateStruct.Year + 100;

    LOG_D("get rtc time.");
    return mktime(&tm_new);
}

static rt_err_t set_rtc_time_stamp(time_t time_stamp)
{
    RTC_TimeTypeDef RTC_TimeStruct = { 0 };
    RTC_DateTypeDef RTC_DateStruct = { 0 };
    struct tm *p_tm;

    p_tm = localtime(&time_stamp);
    if (p_tm->tm_year < 100)
    {
        return -RT_ERROR;
    }

    RTC_TimeStruct.Seconds = p_tm->tm_sec;
    RTC_TimeStruct.Minutes = p_tm->tm_min;
    RTC_TimeStruct.Hours = p_tm->tm_hour;
    RTC_DateStruct.Date = p_tm->tm_mday;
    RTC_DateStruct.Month = p_tm->tm_mon + 1;
    RTC_DateStruct.Year = p_tm->tm_year - 100;
    RTC_DateStruct.WeekDay = p_tm->tm_wday + 1;

    if (HAL_RTC_SetTime(&RTC_Handler, &RTC_TimeStruct, RTC_FORMAT_BIN) != HAL_OK)
    {
        return -RT_ERROR;
    }
    if (HAL_RTC_SetDate(&RTC_Handler, &RTC_DateStruct, RTC_FORMAT_BIN) != HAL_OK)
    {
        return -RT_ERROR;
    }

    LOG_D("set rtc time.");HAL_RTCEx_BKUPWrite(&RTC_Handler, RTC_BKP_DR1, BKUP_REG_DATA);
    return RT_EOK;
}

/* 后添加的设置闹钟的接口 2022-06-08 */
static rt_err_t set_rtc_alarm_stamp(struct rt_rtc_wkalarm wkalarm)
{
    RTC_AlarmTypeDef sAlarm = { 0 };

    if (wkalarm.enable == RT_FALSE)
    {
        if (HAL_RTC_DeactivateAlarm(&RTC_Handler, RTC_ALARM_A) != HAL_OK)
        {
            return -RT_ERROR;
        }

        LOG_I("stop rtc alarm.");
    }
    else
    {
        /* Enable the Alarm A */
        sAlarm.AlarmTime.Hours = wkalarm.tm_hour;
        sAlarm.AlarmTime.Minutes = wkalarm.tm_min;
        sAlarm.AlarmTime.Seconds = wkalarm.tm_sec;
        sAlarm.AlarmTime.SubSeconds = 0x0;
        sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
        sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
        sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;   // 日期或者星期无效
        sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
        sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
        sAlarm.AlarmDateWeekDay = 1;
        sAlarm.Alarm = RTC_ALARM_A;     // 这里要和 CubeMX 配置的一致,均为 Alarm-A,修改的话两个都要修改
        if (HAL_RTC_SetAlarm_IT(&RTC_Handler, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
        {
            return -RT_ERROR;
        }

        LOG_I("set rtc alarm.");
    }

    return RT_EOK;

    /*
     * sAlarm.AlarmMask  闹钟掩码字段选择,即选择闹钟时间哪些字段无效。
     *     RTC_ALARMMASK_NONE                (全部有效)
     *     RTC_ALARMMASK_DATEWEEKDAY         (日期或星期无效)
     *     RTC_ALARMMASK_HOURS               (小时无效)
     *     RTC_ALARMMASK_MINUTES             (分钟无效)
     *     RTC_ALARMMASK_SECONDS             (秒钟无效)
     *     RTC_ALARMMASK_ALL                 (全部无效)
     *
     * sAlarm.AlarmDateWeekDaySel  闹钟日期或者星期选择。要想这个配置有效,AlarmMask 不能配置为 RTC_AlarmMask_DateWeekDay,否则会被 MASK 掉
     *     RTC_ALARMDATEWEEKDAYSEL_DATE      (日期)
     *     RTC_ALARMDATEWEEKDAYSEL_WEEKDAY   (星期)
     *
     * sAlarm.AlarmDateWeekDay  具体的日期或者日期
     *     当 AlarmDateWeekDaySel 设置成星期时,该成员 取值范围:1~7
     *     当 AlarmDateWeekDaySel 设置成日期,该成员取值范围:1~31
     */
}

/* 后添加的获取闹钟时间的接口 2022-06-08 */
struct rt_rtc_wkalarm get_rtc_alarm_stamp(void)
{
    RTC_AlarmTypeDef sAlarm = { 0 };

    struct rt_rtc_wkalarm wkalarm;

    if (HAL_RTC_GetAlarm(&RTC_Handler, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN) != HAL_OK)
    {
        LOG_E("get rtc alarm fail!.");
    }

    wkalarm.tm_sec = sAlarm.AlarmTime.Seconds;
    wkalarm.tm_min = sAlarm.AlarmTime.Minutes;
    wkalarm.tm_hour = sAlarm.AlarmTime.Hours;

    LOG_I("get rtc alarm.");

    return wkalarm;
}

/* 后添加的设置闹钟中断的接口 2022-06-08 */
void RTC_Alarm_IRQHandler(void)
{
    /* USER CODE BEGIN RTC_Alarm_IRQn 0 */

    /* USER CODE END RTC_Alarm_IRQn 0 */
    HAL_RTC_AlarmIRQHandler(&RTC_Handler);
    /* USER CODE BEGIN RTC_Alarm_IRQn 1 */

    /* USER CODE END RTC_Alarm_IRQn 1 */
}

/* 后添加的设置闹钟中断的回调函数接口 2022-06-08 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    rt_alarm_update(&rtc, 1);   // 该函数的两个形参没有用到,传什么都可以,作用是发送一个事件集,唤醒 alarmsvc 线程
}

static void rt_rtc_init(void)
{
#ifndef SOC_SERIES_STM32H7
    __HAL_RCC_PWR_CLK_ENABLE();
#endif

    RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
#ifdef BSP_RTC_USING_LSI
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    RCC_OscInitStruct.LSEState = RCC_LSE_OFF;
    RCC_OscInitStruct.LSIState = RCC_LSI_ON;
#else
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    RCC_OscInitStruct.LSEState = RCC_LSE_ON;
    RCC_OscInitStruct.LSIState = RCC_LSI_OFF;
#endif
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
}

static rt_err_t rt_rtc_config(struct rt_device *dev)
{
    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = { 0 };

    HAL_PWR_EnableBkUpAccess();
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
#ifdef BSP_RTC_USING_LSI
    PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
#else
    PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
#endif
    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);

    /* Enable RTC Clock */
    __HAL_RCC_RTC_ENABLE();

    RTC_Handler.Instance = RTC;
    if (HAL_RTCEx_BKUPRead(&RTC_Handler, RTC_BKP_DR1) != BKUP_REG_DATA)
    {
        LOG_I("RTC hasn't been configured, please use <date> command to config.");

#if defined(SOC_SERIES_STM32F1)
        RTC_Handler.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
        RTC_Handler.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
#elif defined(SOC_SERIES_STM32F0)

        /* set the frequency division */
#ifdef BSP_RTC_USING_LSI
        RTC_Handler.Init.AsynchPrediv = 0XA0;
        RTC_Handler.Init.SynchPrediv = 0xFA;
#else
        RTC_Handler.Init.AsynchPrediv = 0X7F;
        RTC_Handler.Init.SynchPrediv = 0x0130;
#endif /* BSP_RTC_USING_LSI */

        RTC_Handler.Init.HourFormat = RTC_HOURFORMAT_24;
        RTC_Handler.Init.OutPut = RTC_OUTPUT_DISABLE;
        RTC_Handler.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
        RTC_Handler.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
#elif defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32H7)

        /* set the frequency division */
#ifdef BSP_RTC_USING_LSI
        RTC_Handler.Init.AsynchPrediv = 0X7D;
#else
        RTC_Handler.Init.AsynchPrediv = 0X7F;
#endif /* BSP_RTC_USING_LSI */
        RTC_Handler.Init.SynchPrediv = 0XFF;

        RTC_Handler.Init.HourFormat = RTC_HOURFORMAT_24;
        RTC_Handler.Init.OutPut = RTC_OUTPUT_DISABLE;
        RTC_Handler.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
        RTC_Handler.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
#endif
        if (HAL_RTC_Init(&RTC_Handler) != HAL_OK)
        {
            return -RT_ERROR;
        }
    }
    return RT_EOK;
}

static rt_err_t rt_rtc_control(rt_device_t dev, int cmd, void *args)
{
    rt_err_t result = RT_EOK;
    RT_ASSERT(dev != RT_NULL);
    switch (cmd)
    {
    case RT_DEVICE_CTRL_RTC_GET_TIME:
        *(rt_uint32_t *) args = get_rtc_timestamp();
        LOG_D("RTC: get rtc_time %x\n", *(rt_uint32_t * )args);
        break;

    case RT_DEVICE_CTRL_RTC_SET_TIME:
        if (set_rtc_time_stamp(*(rt_uint32_t *) args))
        {
            result = -RT_ERROR;
        }
        LOG_D("RTC: set rtc_time %x\n", *(rt_uint32_t * )args);
        break;
    case RT_DEVICE_CTRL_RTC_SET_ALARM:
        if (set_rtc_alarm_stamp(*(struct rt_rtc_wkalarm *) args))
        {
            result = -RT_ERROR;
        }
        LOG_D("RTC: set rtc_alarm tme : hour: %d , min: %d , sec:  %d \n", *(struct rt_rtc_wkalarm * )args->tm_hour,
                *(struct rt_rtc_wkalarm * )args->tm_min, *(struct rt_rtc_wkalarm * )args->tm_sec);
        break;

    case RT_DEVICE_CTRL_RTC_GET_ALARM:
        *(struct rt_rtc_wkalarm *) args = get_rtc_alarm_stamp();
        LOG_D("RTC: get rtc_alarm time : hour: %d , min: %d , sec:  %d \n", *(struct rt_rtc_wkalarm * )args->tm_hour,
                *(struct rt_rtc_wkalarm * )args->tm_min, *(struct rt_rtc_wkalarm * )args->tm_sec);
        break;
    }

    return result;
}

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops rtc_ops =
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    rt_rtc_control
};
#endif

static rt_err_t rt_hw_rtc_register(rt_device_t device, const char *name, rt_uint32_t flag)
{
    RT_ASSERT(device != RT_NULL);

    rt_rtc_init();
    if (rt_rtc_config(device) != RT_EOK)
    {
        return -RT_ERROR;
    }
#ifdef RT_USING_DEVICE_OPS
    device->ops = &rtc_ops;
#else
    device->init = RT_NULL;
    device->open = RT_NULL;
    device->close = RT_NULL;
    device->read = RT_NULL;
    device->write = RT_NULL;
    device->control = rt_rtc_control;
#endif
    device->type = RT_Device_Class_RTC;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;
    device->user_data = RT_NULL;

    /* register a character device */
    return rt_device_register(device, name, flag);
}

int rt_hw_rtc_init(void)
{
    rt_err_t result;
    result = rt_hw_rtc_register(&rtc, "rtc", RT_DEVICE_FLAG_RDWR);
    if (result != RT_EOK)
    {
        LOG_E("rtc register err code: %d", result);
        return result;
    }
    LOG_D("rtc init success");
    return RT_EOK;
}
INIT_DEVICE_EXPORT(rt_hw_rtc_init);

#endif /* BSP_USING_ONCHIP_RTC */

4 测试用例和测试结果

4.1 测试用例

  测试用例如下,本次定义了一个执行闹钟测试命令 5 秒的闹钟,然后通过日志信息来查看闹钟的响应,测试代码如下。

static rt_alarm_t alarm = RT_NULL;

/* 闹钟的用户回调函数 */
void user_alarm_callback(rt_alarm_t alarm, time_t timestamp)
{
    struct tm p_tm;
    time_t now = timestamp;
	
    localtime_r(&now, &p_tm); // 时间戳转换 

    LOG_D("user alarm callback function.");
    LOG_D("curr time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec);  // 打印闹钟中断产生时的时间,和设定的闹钟时间比对,以确定得到的是否是想要的结果
}

/* 闹钟示例 */
void alarm_sample(void)
{
    time_t curr_time;
    struct tm p_tm;
    struct rt_alarm_setup setup;

    curr_time = time(NULL) + 5;     // 将闹钟的时间设置为当前时间的往后的 5 秒
    localtime_r(&curr_time, &p_tm); // 将时间戳转换为本地时间,localtime_r 是线程安全的
    LOG_D("now time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour,
            p_tm.tm_min, p_tm.tm_sec - 5);  // 打印当前时间,其中秒应该减去 5,因为前面加了 5

    setup.flag = RT_ALARM_ONESHOT;  // 单次闹钟
    setup.wktime.tm_year = p_tm.tm_year;
    setup.wktime.tm_mon = p_tm.tm_mon;
    setup.wktime.tm_mday = p_tm.tm_mday;
    setup.wktime.tm_wday = p_tm.tm_wday;
    setup.wktime.tm_hour = p_tm.tm_hour;
    setup.wktime.tm_min = p_tm.tm_min;
    setup.wktime.tm_sec = p_tm.tm_sec;

    alarm = rt_alarm_create(user_alarm_callback, &setup);   // 创建一个闹钟并设置回调函数
    if (RT_NULL != alarm)
    {
        rt_alarm_start(alarm);  // 启动闹钟
    }
    else
    {
        LOG_E("rtc alarm create failed");
    }

    rt_alarm_dump();    // 打印闹钟的信息
}
MSH_CMD_EXPORT(alarm_sample, alarm sample);

/* 删除闹钟测试 */
void delete_alarm_sample(void)
{
    rt_alarm_delete(alarm); // 删除闹钟
    rt_alarm_dump();        // 打印闹钟的信息
}
MSH_CMD_EXPORT(delete_alarm_sample, delete alarm sample);

4.2 测试结果

  测试结果如下,测试结果的分析在下面的结果中以注释的内容呈现。

msh >alarm_sample                           // 命令行输入命令进行闹钟功能的测试
[D/main] now time: 2022-06-08 22:26:32      // 打印要设置闹钟时的当前时间
[I/drv.rtc] set rtc alarm.  
[I/drv.rtc] get rtc alarm.
| id | YYYY-MM-DD hh:mm:ss | week | flag | en |
+----+---------------------+------+------+----+
|  0 | 2022-06-08 22:26:37 |   3  |   O  |  1 |
+----+---------------------+------+------+----+
msh >
msh >[I/drv.rtc] stop rtc alarm.    
[D/main] user alarm callback function.      // 执行了闹钟的回调函数
[D/main] curr time: 2022-06-08 22:26:37     // 在闹钟的回调函数中打印当前的时间,验证闹钟时间是否正确
[I/drv.rtc] stop rtc alarm.

msh >rt_alarm_dump
| id | YYYY-MM-DD hh:mm:ss | week | flag | en |
+----+---------------------+------+------+----+
|  0 | 2022-06-08 22:26:37 |   3  |   O  |  0 |
+----+---------------------+------+------+----+
msh >   
msh >alarm_sample                           // 再次实验闹钟功能
[D/main] now time: 2022-06-08 22:26:51
[I/drv.rtc] set rtc alarm.
[I/drv.rtc] get rtc alarm.
| id | YYYY-MM-DD hh:mm:ss | week | flag | en |
+----+---------------------+------+------+----+
|  0 | 2022-06-08 22:26:56 |   3  |   O  |  1 |
|  1 | 2022-06-08 22:26:37 |   3  |   O  |  0 |
+----+---------------------+------+------+----+ 
msh >
msh >[I/drv.rtc] stop rtc alarm.
[D/main] user alarm callback function.
[D/main] curr time: 2022-06-08 22:26:56
[I/drv.rtc] stop rtc alarm.

msh >
msh >rt_alarm_dump                          // 有两个闹钟
| id | YYYY-MM-DD hh:mm:ss | week | flag | en |
+----+---------------------+------+------+----+
|  0 | 2022-06-08 22:26:56 |   3  |   O  |  0 |
|  1 | 2022-06-08 22:26:37 |   3  |   O  |  0 |
+----+---------------------+------+------+----+
msh >
msh >delete_alarm_sample                    // 删除闹钟测试
[I/drv.rtc] stop rtc alarm.                 // 删除后只有一个闹钟
| id | YYYY-MM-DD hh:mm:ss | week | flag | en |
+----+---------------------+------+------+----+
|  0 | 2022-06-08 22:26:37 |   3  |   O  |  0 |
+----+---------------------+------+------+----+
msh >

5 源码分析

5.1 RTC设备初始化源码分析

  RTC 设备注册和初始化的过程如下所示。

rt_hw_rtc_init                  // RTC硬件初始化 (drv_rtc.c)
    |-> rt_hw_rtc_register      // RTC硬件注册 (drv_rtc.c)
        |-> rt_rtc_init         // RTC时钟源初始化,默认使用LSE (drv_rtc.c)
        |-> rt_rtc_config       // 配置了分频因子、信号输出极性和类型等参数 (drv_rtc.c)
            |-> HAL_RTC_Init    // RTC外设的初始化
                |-> HAL_RTC_MspInit     // RTC MSP 初始化,由 CubeMX 配置生成
        |-> rt_device_register  // RTC设备注册
            |-> rt_object_init

  从函数的调用流程和分析我们可以得出,其实在 CubeMX 配置 RTC 的参数时只有生成的函数 HAL_RTC_MspInit(cubemx/Src/stm32f4xx_hal_msp.c) 被调用了,该函数的具体内容如下,也就是说只有配置的 RTC 的中断初始化被调用。
  在 CubeMX 中配置的分频因子,时间日志和闹钟的初始值等参数均没有被使用,因此可以不用在 CubeMX 中配置相关的参数。分频因子的配置由 RT-Thread 源码默认设定了,在函数 rt_rtc_config 里面实现,开发板的时间需要手动的使用 date 命令或者 set_time() 函数进行设置。

void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  if(hrtc->Instance==RTC)
  {
  /* USER CODE BEGIN RTC_MspInit 0 */

  /* USER CODE END RTC_MspInit 0 */
  /** Initializes the peripherals clock
  */
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
      Error_Handler();
    }

    /* Peripheral clock enable */
    __HAL_RCC_RTC_ENABLE();
    /* RTC interrupt Init */
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspInit 1 */

  /* USER CODE END RTC_MspInit 1 */
  }
}

5.2 RTC闹钟源码分析

  RTC闹钟源码调用过程如下。
  在闹钟的启动函数中我们可以看到该函数调用了在文件 drv_rtc.c 中添加的函数 set_rtc_alarm_stamp() 将设定的闹钟值写入到对应的寄存器中
  闹钟的处理流程可以概括为创建一个名字为 alarmsvc 的线程,该线程一直在等待一个闹钟事件集,该事件集有闹钟的中断处理函数中进行发送。alarmsvc 线程获取到该闹钟事件集后就更新闹钟事件,然后进行调用函数 alarm_wakeup() 进行闹钟的唤醒操作,最终调用了用户使用函数 rt_alarm_create() 在创建闹钟时设定的回调函数,执行用户设定的操作。

// 启动闹钟的流程分析            
rt_alarm_start   // 启动闹钟 (alarm.c)
    |-> alarm_setup // 根据不同的闹钟模式对闹钟值进行设置 (alarm.c)
    |-> alarm_set   // 将设置的闹钟值写入对应的寄存器中 (alarm.c)
    |-> rt_device_control(RT_DEVICE_CTRL_RTC_SET_ALARM) // (device.c)
        |-> device_control      // (device.c)
            |-> rt_rtc_control  // (drv_rtc.c)
                |-> set_rtc_alarm_stamp     // 自己添加的设置闹钟的函数 (drv_rtc.c)
                    |-> HAL_RTC_SetAlarm_IT // (stm32f4xx_hal_rtc.c)
                    
                    
// 闹钟处理流程分析
rt_alarm_system_init    // 闹钟系统初始化 (alarm.c)
    |-> rt_thread_create("alarmsvc", rt_alarmsvc_thread_init)   // 创建闹钟处理线程 (alarm.c)

rt_alarmsvc_thread_init // 闹钟处理线程 (alarm.c)
    |-> rt_event_recv(&_container.event)    // 等待闹钟事件集 (alarm.c)
        |-> alarm_update        // 更新闹钟事件 (alarm.c)
            |-> alarm_wakeup    // 闹钟唤醒的操作 (alarm.c)
                |-> switch (alarm->flag & 0xFF00)       // 根据闹钟的不同模式对闹钟的唤醒时间进行不同的操作 (alarm.c)
                |-> alarm->callback(alarm, timestamp);  // 执行用户定义的闹钟中断的回调函数 (alarm.c)
            

// 闹钟中断的处理流程
RTC_Alarm_IRQHandler    // 闹钟中断的处理函数 (drv_rtc.c)
    |-> HAL_RTC_AlarmIRQHandler // HAL库提供的中断处理函数 (stm32f4xx_hal_rtc.c)
        |-> HAL_RTC_AlarmAEventCallback // 自己重写的闹钟中断回调函数 (drv_rtc.c)
            |-> rt_alarm_update // RTT提供的更新闹钟时间,实际是发用一个闹钟事件集 (alarm.c)
                |-> rt_event_send   // 发送闹钟事件集,线程 alarmsvc 在移植阻塞等待该事件集 (ipc.c)

6 问题记录

6.1 时区问题

  参考官方文档 时间函数 说底层驱动不应当使用带有时区的时间,而应该使用格林威治时间,即 UTC+0,但是在使用函数 set_time(rt-thread/components/drivers/rtc/rtc.c) 设置RTC的时间时却使用了函数 localtime_r,也就是说写入到寄存器的时间确实是带时区的时间,查看相应的寄存器写入的也是带时区的时间,这部分代码和文档的说法有矛盾
  在 alarm.c 的函数 alarm_update() 获取的却是不带时区的时间,然后在函数 alarm_wakeup() 中对不带时区的现在时间和带时区的闹钟时间进行对比,结果就是 sec_now 永远小于 sec_alarm,导致变量 wakeup 的值永远为 FALSE,也就是永远不能执行用户定义的alarm回调函数 alarm->callback。解决办法是将 alarm.c 中的所有的 gmtime_r 替换为 localtime_r
11.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32F4 RTC-Alarm 的使用(RT-Thread操作系统) 的相关文章

  • 在 MCU 内部 FLASH 中从一个固件跳转到另一个固件

    我目前正在开发针对 STM32F030C8 的引导加载程序固件应用程序 我在分散文件中指定引导加载程序应用程序将占用主内存位置 0x08000000 到 0x08002FFF 扇区 0 到扇区 2 我还编写了一个主固件应用程序 存储在0x0
  • 139-基于stm32单片机老人居家监护报警系统Proteus仿真+源程序

    资料编号 139 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 MQ4传感器 电位器模拟 MQ2传感器 电位器模拟 蜂鸣器 电机 制作一个基于stm32单片机老人居家监护报警系统Proteus仿真 2 通过MQ2传
  • 137-基于stm32单片机智能保温杯控制装置Proteus仿真+源程序

    资料编号 137 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DS18B20传感器 电机 制作一个基于stm32单片机智能保温杯控制装置Proteus仿真 2 通过DS18b20传感器检测当前保温杯水的温度 并且
  • STM32用一个定时器执行多任务写法

    文章目录 main c include stm32f4xx h uint32 t Power check times 电量检测周期 uint32 t RFID Init Check times RFID检测周期 int main Timer
  • 中断管理学习

    中断管理 什么是中断 简单的解释就是系统正在处理某一个正常事件 忽然被另一个需要马上处理的紧急事件打断 系统转而处理这个紧急事件 待处理完毕 再恢复运行刚才被打断的事件 生活中 我们经常会遇到这样的场景 当你正在专心看书的时候 忽然来了一个
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • [屏驱相关]【SWM166-SPI-Y1.28C1测评】+ 有点惊艳的开箱

    耳闻华芯微特许久了 看到论坛得评测活动赶紧上了末班车 毕竟对有屏幕得板子也是很喜欢得 京东快递小哥客客气气 微笑着把快递给了我 好评 直接拆了包 在此之前没看过视频号 所以这个圆盘盘得模具还是有点惊喜的 正面照如下 开机有灯光秀 还有动画
  • SHT10温湿度传感器——STM32驱动

    实验效果 硬件外观 接线 3 3V供电 IIC通讯 代码获取 查看下方 END
  • 硬件基础-电容

    电容 本质 电容两端电压不能激变 所以可以起到稳定电压作用 充放电 电容量的大小 想使电容容量大 使用介电常数高的介质 增大极板间的面积 减小极板间的距离 品牌 国外 村田 muRata 松下 PANASONIC 三星 SAMSUNG 太诱
  • 跟着野火学FreeRTOS:第一段(任务定义,切换以及临界段)

    在裸机系统中 系统的主体就是 C P U CPU CP U 按照预先设定的程序逻辑在 m a i n
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • STM32的HAL中实现单按、长按和双按功能

    我正在尝试实现单击 双击和长按功能来执行不同的功能 到目前为止 我已经理解了单击和长按的逻辑 但我不知道如何检测双击 至于代码 我使用计数器实现了单击和长按 但代码仅停留在第一个 if 条件上 bool single press false
  • Freertos低功耗管理

    空闲任务中的低功耗Tickless处理 在整个系统运行得过程中 其中大部分时间都是在执行空闲任务的 空闲任务之所以执行 因为在系统中的其他任务处于阻塞或者被挂起时才会执行 因此可以将空闲任务的执行时间转换成低功耗模式 在其他任务解除阻塞而准
  • CMSIS & STM32,如何开始? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想在 STM32 上使用 CMSIS 启动项目 网上一搜 没找到具体的教程 有些使用 SPL 开始项
  • STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

    文章目录 一 前言 二 问题1 数值超过4096 三 问题1的排错过程 四 问题2 右对齐模式设置失败 五 问题2的解决方法 5 1 将ADC ExternalTrigConv设置为0 5 2 使用ADC StructInit 函数 一 前
  • STM32H5 Nucleo-144 board开箱

    文章目录 开发板资料下载 目标 点亮LD1 绿 LD2 黄 和LD3 红 三个LED灯 开箱过程 博主使用的是STM32CubeMX配置生成代码 具体操作如下 打开STM32CubeMX File gt New project 选择开发板型
  • 特殊寄存器

    特殊寄存器 文章目录 前言 一 背景 二 2 1 2 2 总结 前言 前期疑问 STM32特殊寄存器到底是什么 特殊寄存器怎么查看和调试代码 本文目标 记录和理解特殊寄存器 一 背景 最近在看ucosIII文章是 里面提到特殊寄存器 这就进
  • 嵌入式 C++11 代码 — 我需要 volatile 吗?

    采用 Cortex M3 MCU STM32F1 的嵌入式设备 它具有嵌入式闪存 64K MCU固件可以在运行时重新编程闪存扇区 这是由闪存控制器 FMC 寄存器完成的 所以它不像a b那么简单 FMC 获取缓冲区指针并将数据刻录到某个闪存
  • PWM DMA 到整个 GPIO

    我有一个 STM32F4 我想对一个已与掩码进行 或 运算的 GPIO 端口进行 PWM 处理 所以 也许我们想要 PWM0b00100010一段时间为 200khz 但随后 10khz 后 我们现在想要 PWM0b00010001 然后
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d

随机推荐

  • ChatGPT的奇思妙想

    ChatGPT的奇思妙想 最近 一个名叫 ChatGPT 通用人工智能 的人工智能系统引起了大家的关注 该系统可以通过对话生成文本和回答问题 在谷歌公司公布的一份新研究报告中 该系统已经具备了 与人类进行自然语言交流 的能力 虽然 Chat
  • Hungarian method 匈牙利算法 解决指派问题

    这个也讲得不错 https blog csdn net Wonz5130 article details 80678410 from scipy optimize import linear sum assignment import nu
  • javascript XMLHttpRequest 对象的open() 方法参数说明

    下文是从w3c上摘录下来的 其中参数 method 说明的很简短 不是很理解 所以又找了些资料作为补充 文中带括号部分 XMLHttpRequest open 初始化 HTTP 请求参数 语法 open method url async u
  • vscode设置第三方库路径和自动代码补全

    1 打开VSCode gt 文件 gt 首选项 gt 设置 gt 用户 gt 扩展 gt Python gt Auto Complete Extra Paths gt 在settings json中编辑 在settings json中添加代
  • vue工程可视化大屏 自适应问题

    目录 三大常用方式 vw vh方案 scale方案 rem vw vh方案 最新方式 调用autofit js包 vue工程可视化大屏 自适应问题 可视化大屏的适配是一个老生常谈的话题了 现在其实不乏一些大佬开源的自适应插件 工具但是我为什
  • Java 的使用习惯

    定义配置文件信息 有时候我们为了统一管理会把一些变量放到 yml 配置文件中 例如 用 ConfigurationProperties 代替 Value 使用方法 定义对应字段的实体 Data 指定前缀 ConfigurationPrope
  • Windows两台服务器之间实现文件共享

    1 检查工作 1 win r 输入services msc检查TCP IP NetBIOS Helper服务是否已经开启 2 两台服务器之间需要开放139 445端口 或者关闭防火墙 2 共享文件夹配置 1 共享设置 新建share文件夹
  • Java基础——参数传参(基本类型,引用类型传参)

    文章目录 一 方法 参数 返回值 二 参数传递过程中的底层原理 1 创建对象过程中发生的事 1 对象创建并存储 2 基本类型作为形参传递 值传递 3 引用类型作为形参传递 本质也是值传递 4 Java中的引用与C 中引用 指针的区别 三 特
  • scala Stack可变栈

    import scala collection mutable 栈 先进后出 后进先出 top 获取栈顶元素 但是不会把这个元素从栈顶移除 push 表示入栈操作 相当于把元素压入栈顶 pop 移除栈顶元素 并返回此元素 clear 清除集
  • 测试gpt的function函数功能

    官网API 科学上网查看 1 我对该功能的理解 利用gpt的上下文理解能力 在执行方法run conversation xx 时 目标锁定在 提取出functions里每个function下required属性对应的值 而真正的functi
  • 2020泰迪杯数据挖掘挑战赛总结(A题)

    写在开始 转眼间 泰迪杯数据挖掘挑战赛已经过去了10多天 趁着结果还没出来 还有这股热乎劲 写篇文章总结下 文章目录 最初 学习阶段 解题阶段 最终 最初 最初的参赛原因简单粗暴 后来也渐渐发现 白嫖也的确学到不少 刚开始组队 选好队友很重
  • 字节对齐

    一 什么是字节对齐 为什么要对齐 现代计算机中内存空间都是按照byte划分的 从理论上讲似乎对任何类型的变量的访问可以从任何地址开始 但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问 这就需要各种类型数据按照一定的规则在空间上
  • Hbase解决ERROR: KeeperErrorCode = ConnectionLoss for /hbase/master报错

    1 在单机模式中 要先修改一个文件 usr local hbase conf hbase site xml hbase site xml内容
  • element ui + vue项目,修改el-divider默认样式

    修改el divider 垂直分割线的样式 以修改margin为例 其他样式改变同理
  • 实验2-动态规划编程题4. 01背包问题

    问题描述 给定一个容量为C的背包 现有n个物品 每个物品的体积分别为s1 s2 sn 价值分别为v1 v2 vn 每个物品只能放入一次 背包最多能装入价值为多少的物品 输入形式 输入的第1行包含2个整数C和n 分别表示背包容量和物品个数 接
  • gdb--设置断点的方法

    package utils import fmt github com gin gonic gin net http type Album struct ID string json id Title string json title A
  • 一个赛马算法

    原题 25匹马 5条跑道 怎样能用最快方式 得到最快的三匹马 假设每匹马的体力保持不变 速度固定 解法 堆排序 如下 package org algorithm search import java util ArrayList impor
  • 【vue】vue2 获取本地IP地址

    具体代码
  • 如何挖掘关键词

    挖掘关键词是SEO的基本功 借助的工具有 百度下拉框 百度相关搜索 搜搜问问 百度知道 百度推广助手 百度指数等 1 百度下拉框和相关搜索 通过下拉框和相关搜索搜集长尾词的方法是一级一级搜集 比如搜索SEO 然后再搜索SEO的下拉框里面的S
  • STM32F4 RTC-Alarm 的使用(RT-Thread操作系统)

    文章目录 1 工程的创建和配置 1 1 CubeMX 的配置 1 1 1 时钟源的选择 1 1 2 Debug 引脚配置 1 1 3 控制台串口的配置 1 1 4 RTC的配置 1 1 5 时钟树配置 1 1 6 代码生成 1 2 RT T