Linux RTC 驱动框架
目录
- 一、实时时钟(RTC)介绍
- 1.1 概述
- 1.2 功能
- 1.3 应用场景
- 1.4 工作原理
- 1.5 对外接口
- 1.6 常见 RTC 芯片
- 1.7 在 Linux 系统中的应用
- 1.8 注意事项
- 二、Linux 内核 RTC 驱动框架
- 2.1 相关源码文件介绍
- 2.2 核心数据结构
- 2.2.1 struct rtc_device
- 2.2.2 rtc_class_ops
- 2.2.3 struct rtc_timer
- 2.4 RTC驱动实例分析
- 2.5 提供带有中文注释的drivers/rtc/源码
- 2.6 APP层操作RTC
- 三、参考资料
一、实时时钟(RTC)介绍
1.1 概述
实时时钟(Real-Time Clock,简称 RTC)是一种能够持续记录时间的电子设备。它通常用于计算机、嵌入式系统和其他需要准确时间记录的设备中。RTC 可以在系统关机或断电的情况下继续运行,因此即使在系统重启后也能保持准确的时间。
1.2 功能
时间记录
: 记录当前的日期和时间。闹钟功能
: 可以设置特定的时间点触发中断,用于唤醒系统或执行特定任务。周期性中断
:可以设置周期性的中断,用于定时任务。电源管理
: 通常由电池供电,确保在主电源断开时仍能正常工作。
1.3 应用场景
- 个人电脑: 用于记录 BIOS/UEFI 中的时间。
- 嵌入式系统: 用于工业控制、医疗设备、汽车电子等需要高精度时间的应用。
- 服务器:用于日志记录、定时任务调度等。 物联网设备: 用于时间同步和定时任务。
1.4 工作原理
时钟源
: RTC 通常使用低频晶体振荡器(如 32.768 kHz)作为时钟源,这种频率的振荡器功耗低且精度高。计数器
:内部计数器根据时钟源的脉冲进行计数,记录秒、分钟、小时、日、月和年。寄存器
: 时间和日期信息存储在寄存器中,可以通过 I2C、SPI 或其他接口读取和写入。中断
: 当设置的闹钟时间到达或周期性中断条件满足时,RTC 会触发中断信号。
1.5 对外接口
I2C
: 常见的通信接口,用于与主控制器通信。SPI
: 另一种常见的通信接口,适用于高速通信。GPIO
: 一些简单的 RTC设备可能使用 GPIO 进行通信。
1.6 常见 RTC 芯片
DS1307
: 由 Maxim 生产,广泛用于各种嵌入式系统.PCF8563
: 由 NXP 生产,具有低功耗特性。MCP79410
:由 Microchip 生产,集成了 EEPROM 和时钟功能。RV-1805
: 由 Epson 生产,具有高精度和低功耗特性。
1.7 在 Linux 系统中的应用
- RTC 驱动: Linux 内核提供了 RTC 驱动框架,用于管理和操作 RTC 设备。
- 用户空间工具: hwclock命令用于读取和设置硬件时钟,date 命令用于读取和设置系统时间。
- 系统启动: 在系统启动时,通常会从 RTC 读取时间并设置系统时间。
1.8 注意事项
- 电池寿命: RTC 通常由纽扣电池供电,需要注意电池的寿命和更换。
- 精度校准: 由于环境温度等因素的影响,RTC 的时间可能会有偏差,需要定期校准。
- 中断处理: 闹钟和周期性中断需要正确处理,避免影响系统的正常运行。
二、Linux 内核 RTC 驱动框架
在内核源码中的路径:drivers/rtc
2.1 相关源码文件介绍
class.c
:为底层驱动提供 register 与 unregister 接口用于 RTC 设备的注册/注销。初始化 RTC设备结构、sysfs、proc;interface.c
:提供用户程序与 RTC 的接口函数;dev.c
:将 RTC设备抽象为通用的字符设备,提供文件操作函数(struct file_operations rtc_dev_fops
的成员),可认为是一个字符设备驱动实现;sysfs.c
:管理 RTC 设备的 sysfs 属性,获取 RTC 设备名、日期、时间等;proc.c
:管理 RTC 设备的 procfs 属性,提供中断状态和标志查询;lib.c
:提供 RTC、Data 和 Time之间的转换函数;rtc-xxx.c
:不同 RTC 芯片的实际驱动;rtc.h
: 定义了 RTC 设备的数据结构和操作接口。
2.2 核心数据结构
2.2.1 struct rtc_device
/*** struct rtc_device - 实时时钟设备结构体** 该结构体表示实时时钟 (RTC) 设备的信息和状态。* 包括设备初始化、操作函数指针、中断处理和定时器相关信息。*/
struct rtc_device {// 基本设备结构体struct device dev;// 设备所属模块的所有者struct module *owner;// 设备标识号int id;// 指向包含 RTC 设备操作函数的结构体的指针const struct rtc_class_ops *ops;// 保护操作函数的互斥锁,确保线程安全struct mutex ops_lock;// RTC 字符设备结构体struct cdev char_dev;// 设备标志位unsigned long flags;// 中断相关数据unsigned long irq_data;// 保护中断相关数据的自旋锁spinlock_t irq_lock;// 中断处理等待队列wait_queue_head_t irq_queue;// 异步通知结构体,用于中断struct fasync_struct *async_queue;// 中断频率int irq_freq;// 用户空间允许的最大频率int max_user_freq;// 定时器队列,用于管理各种定时器struct timerqueue_head timerqueue;// 报警定时器struct rtc_timer aie_timer;// 更新中断定时器struct rtc_timer uie_rtctimer;// 高分辨率定时器,适用于亚秒精度的周期性中断struct hrtimer pie_timer; // 标记是否启用了周期性中断功能int pie_enabled;// 工作结构体,用于处理中断struct work_struct irqwork;// 有些硬件不支持 UIE 模式int uie_unsupported;// 设置 RTC 时钟所需的时间(纳秒)。这会影响设置操作的调用时间。偏移量:// - 0.5 秒会在墙上时间 10.0 秒时在 9.5 秒调用 RTC 设置// - 1.5 秒会在墙上时间 10.0 秒时在 8.5 秒调用 RTC 设置// - -0.5 秒会在墙上时间 10.0 秒时在 10.5 秒调用 RTC 设置long set_offset_nsec;// 标记设备是否已注册bool registered;// 旧 ABI 支持bool nvram_old_abi;struct bin_attribute *nvram;// RTC 范围的最小值time64_t range_min;// RTC 范围的最大值timeu64_t range_max;// 开始秒数time64_t start_secs;// 偏移秒数time64_t offset_secs;// 标记是否设置了开始时间bool set_start_time;#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL// 工作结构体,用于处理 UIE 任务struct work_struct uie_task;// UIE 定时器struct timer_list uie_timer;// 这些字段受 rtc->irq_lock 保护unsigned int oldsecs;unsigned int uie_irq_active:1;unsigned int stop_uie_polling:1;unsigned int uie_task_active:1;unsigned int uie_timer_active:1;
#endifANDROID_KABI_RESERVE(1);
};
UIE 是 “Update Interrupt Enable” 的缩写,它是一种实时时钟(RTC)设备的功能。具体来说,UIE 模式用于在 RTC 时间更新时生成中断。以下是 UIE 模式的详细解释:
-
时间更新中断:
- 当 RTC 的时间每秒钟更新时,会触发一个中断。这个中断可以被用户空间程序捕获和处理。
- 例如,某些应用程序可能需要在每一秒的边界上执行特定的操作,UIE 模式可以提供这种精确的时间同步。
-
应用场景:
- 日志记录:在需要精确时间戳的日志记录系统中,UIE 中断可以确保日志条目的时间戳非常准确。
- 定时任务:需要在固定时间间隔内执行的任务,可以通过 UIE 中断来触发。
-
实现细节:
- 在
struct rtc_device
结构体中,有几个与 UIE 相关的字段:int uie_unsupported
:标记某些硬件是否不支持 UIE 模式。long set_offset_nsec
:设置 RTC 时钟所需的时间偏移量,影响中断的触发时间。struct work_struct uie_task
和struct timer_list uie_timer
:用于处理 UIE 中断任务和定时器。unsigned int uie_irq_active:1
:标记 UIE 中断是否激活。unsigned int stop_uie_polling:1
:标记是否停止 UIE 轮询。unsigned int uie_task_active:1
:标记 UIE 任务是否激活。unsigned int uie_timer_active:1
:标记 UIE 定时器是否激活。
- 在
-
配置和启用:
- UIE 模式通常需要在内核配置中启用,例如通过
CONFIG_RTC_INTF_DEV_UIE_EMUL
配置选项。 - 应用程序可以通过 RTC 设备文件接口(如
/dev/rtc0
)来启用或禁用 UIE 模式。
- UIE 模式通常需要在内核配置中启用,例如通过
总结来说,UIE 模式是为了在 RTC 时间更新时生成中断,以便应用程序能够精确地响应时间变化。这对于需要高精度时间同步的应用非常有用。
2.2.2 rtc_class_ops
/** RTC 类操作结构体,定义了与 RTC 设备交互的各种方法。* 这些方法中的 `device` 参数是指物理设备,该设备位于硬件所在的总线上(如 I2C、Platform、SPI 等),* 并且已传递给 `rtc_device_register()` 函数。通常,`driver_data` 包含设备状态,包括 RTC 的 `rtc_device` 指针。** 大多数这些方法在调用时会持有 `rtc_device.ops_lock` 锁,通过 `rtc_*(struct rtc_device *, ...)` 调用。** 当前的例外情况主要是文件系统钩子:* - `proc()` 钩子用于 procfs*/
struct rtc_class_ops {/** ioctl 方法,用于处理 RTC 设备的 I/O 控制命令。* @param dev: RTC 设备* @param cmd: 命令码* @param arg: 命令参数* @return: 成功返回 0,失败返回负错误码*/int (*ioctl)(struct device *dev, unsigned int cmd, unsigned long arg);/** read_time 方法,用于读取 RTC 设备的时间。* @param dev: RTC 设备* @param tm: 存储读取时间的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*read_time)(struct device *dev, struct rtc_time *tm);/** set_time 方法,用于设置 RTC 设备的时间。* @param dev: RTC 设备* @param tm: 包含要设置时间的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*set_time)(struct device *dev, struct rtc_time *tm);/** read_alarm 方法,用于读取 RTC 设备的闹钟设置。* @param dev: RTC 设备* @param alrm: 存储读取闹钟设置的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*read_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** set_alarm 方法,用于设置 RTC 设备的闹钟。* @param dev: RTC 设备* @param alrm: 包含要设置闹钟的结构体指针* @return: 成功返回 0,失败返回负错误码*/int (*set_alarm)(struct device *dev, struct rtc_wkalrm *alrm);/** proc 方法,用于处理 procfs 文件系统的请求。* @param dev: RTC 设备* @param seq: 序列文件指针* @return: 成功返回 0,失败返回负错误码*/int (*proc)(struct device *dev, struct seq_file *seq);/** alarm_irq_enable 方法,用于启用或禁用 RTC 设备的闹钟中断。* @param dev: RTC 设备* @param enabled: 启用或禁用标志* @return: 成功返回 0,失败返回负错误码*/int (*alarm_irq_enable)(struct device *dev, unsigned int enabled);/** read_offset 方法,用于读取 RTC 设备的时间偏移量。* @param dev: RTC 设备* @param offset: 存储读取时间偏移量的指针* @return: 成功返回 0,失败返回负错误码*/int (*read_offset)(struct device *dev, long *offset);/** set_offset 方法,用于设置 RTC 设备的时间偏移量。* @param dev: RTC 设备* @param offset: 要设置的时间偏移量* @return: 成功返回 0,失败返回负错误码*/int (*set_offset)(struct device *dev, long offset);ANDROID_KABI_RESERVE(1);
};
2.2.3 struct rtc_timer
/*** @brief RTC定时器结构体* * 该结构体用于表示RTC(实时时钟)定时器,包含了定时器所需的信息和配置,如定时周期、回调函数等。*/
struct rtc_timer {/*** @brief 定时器队列节点* * 该字段用于将定时器插入到定时器队列中,以管理多个定时器的到期时间。*/struct timerqueue_node node;/*** @brief 定时周期* * 该字段表示定时器的周期时间,使用ktime_t类型来存储时间间隔。*/ktime_t period;/*** @brief 定时器回调函数指针* * 当定时器到期时,将调用此字段指向的函数。该函数将接收一个指向RTC设备的指针作为参数。*/void (*func)(struct rtc_device *rtc);/*** @brief 指向RTC设备的指针* * 该字段用于关联定时器和特定的RTC设备,使得定时器可以操作或访问该设备。*/struct rtc_device *rtc;/*** @brief 定时器启用状态* * 该字段用于指示定时器是否已启用。当定时器被禁用时,其值为0;当定时器被启用时,其值为非0。*/int enabled;
};
2.4 RTC驱动实例分析
drivers/rtc/rtc-rk808.c
// SPDX-License-Identifier: GPL-2.0-only
/** RTC driver for Rockchip RK808** Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd** Author: Chris Zhong <zyw@rock-chips.com>* Author: Zhang Qing <zhangqing@rock-chips.com>*/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/mfd/rk808.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>/* RTC_CTRL_REG bitfields */
#define BIT_RTC_CTRL_REG_STOP_RTC_M BIT(0)/* RK808 has a shadowed register for saving a "frozen" RTC time.* When user setting "GET_TIME" to 1, the time will save in this shadowed* register. If set "READSEL" to 1, user read rtc time register, actually* get the time of that moment. If we need the real time, clr this bit.*/
#define BIT_RTC_CTRL_REG_RTC_GET_TIME BIT(6)
#define BIT_RTC_CTRL_REG_RTC_READSEL_M BIT(7)
#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M BIT(3)
#define RTC_STATUS_MASK 0xFE
#define RTC_ALARM_STATUS BIT(6)#define SECONDS_REG_MSK 0x7F
#define MINUTES_REG_MAK 0x7F
#define HOURS_REG_MSK 0x3F
#define DAYS_REG_MSK 0x3F
#define MONTHS_REG_MSK 0x1F
#define YEARS_REG_MSK 0xFF
#define WEEKS_REG_MSK 0x7#define RTC_NEED_TRANSITIONS BIT(0)
/* REG_SECONDS_REG through REG_YEARS_REG is how many registers? */#define NUM_TIME_REGS (RK808_WEEKS_REG - RK808_SECONDS_REG + 1)
#define NUM_ALARM_REGS (RK808_ALARM_YEARS_REG - RK808_ALARM_SECONDS_REG + 1)struct rk_rtc_compat_reg {unsigned int ctrl_reg;unsigned int status_reg;unsigned int alarm_seconds_reg;unsigned int int_reg;unsigned int seconds_reg;
};struct rk808_rtc {struct rk808 *rk808;struct rtc_device *rtc;struct rk_rtc_compat_reg *creg;int irq;unsigned int flag;
};/** 该函数用于处理 Rockchip 日历与公历(Gregorian calendar)之间的转换。* Rockchip 日历在 RK808 中将 11 月视为有 31 天。我们定义 2016 年 1 月 1 日为两个日历同步的基准日期,* 并根据该日期进行其他日期的相对转换。* 注意:其他系统软件(例如固件)在读取相同硬件时,必须实现完全相同的转换算法,并使用相同的基准日期。** @param tm 指向 rtc_time 结构体的指针,包含待转换的时间信息** @return 返回一个 time64_t 类型的值,表示从基准日期(2016年1月1日)开始的年份偏移量,* 如果当前月份大于11月,则额外加1,以补偿11月多出的一天。*/
static time64_t nov2dec_transitions(struct rtc_time *tm)
{// 计算年份偏移量,并检查是否需要补偿11月多出的一天return (tm->tm_year + 1900) - 2016 + (tm->tm_mon + 1 > 11 ? 1 : 0);
}/*** 将 Rockchip 日期表示转换为公历(Gregorian)日期。* * 此函数用于处理从 Rockchip 特定日期表示法转换为标准公历的逻辑。* 特别地,它处理从11月31日转换为12月1日的特殊情况。* 转换过程分为两个主要步骤:* 1. 首先,使用 `rtc_tm_to_time64` 函数将输入的 Rockchip 日期转换为 Unix 时间戳。* 2. 然后,根据自输入日期以来发生的11月31日到12月1日的转换次数调整时间戳。* 这种调整是必要的,因为在 Rockchip 表示法中,11月31日被视为12月1日。* 调整后的时间戳再使用 `rtc_time64_to_tm` 函数转换回公历格式。* * @param tm 指向 `rtc_time` 结构的指针,该结构包含 Rockchip 格式的日期和时间信息。* 经过转换后,此结构将更新为对应的公历日期和时间。*/
static void rockchip_to_gregorian(struct rtc_time *tm)
{// 将 Rockchip 日期和时间转换为 Unix 时间戳time64_t time = rtc_tm_to_time64(tm);// 根据11月31日到12月1日的转换次数调整时间戳rtc_time64_to_tm(time + nov2dec_transitions(tm) * 86400, tm);
}/*** 将公历日期转换为Rockchip格式的日期* 此函数旨在处理特定的日期转换问题,即将公历日期转换为Rockchip硬件时钟可以理解的格式* 其中包括处理从11月到12月的过渡,这是Rockchip硬件时钟处理日期的一种特殊需求* * @param tm 指向RTC时间结构的指针,该结构包含日期和时间信息*/
static void gregorian_to_rockchip(struct rtc_time *tm)
{// 计算从11月到12月的过渡天数time64_t extra_days = nov2dec_transitions(tm);// 将RTC时间结构转换为自1970年1月1日以来的秒数time64_t time = rtc_tm_to_time64(tm);// 根据过渡天数调整时间,并将结果转换回RTC时间结构rtc_time64_to_tm(time - extra_days * 86400, tm);/* * 如果调整后的日期导致我们回到了11月(这可能发生在特定的年份),则进行补偿* 这种补偿机制可以确保日期正确地向前推进,即使在复杂的闰年情况下也是如此* (该补偿机制将在2381年之前有效)*/if (nov2dec_transitions(tm) < extra_days) {// 如果当前月份是11月,则简单地将日期推进一天if (tm->tm_mon + 1 == 11)tm->tm_mday++; /* This may result in 31! */// 否则,重新计算时间,确保日期正确地反映了从11月到12月的过渡elsertc_time64_to_tm(time - (extra_days - 1) * 86400, tm);}
}/* Read current time and date in RTC */
/*** 从RTC设备读取当前时间。** 此函数通过I2C总线与RTC芯片通信,读取当前的时间和日期,并将读取的数据转换为可使用的格式。** @param dev RTC设备的device结构指针。* @param tm 用于存储读取到的时间和日期信息的rtc_time结构指针。** @return 返回0表示成功,返回负值表示失败。*/static int rk808_rtc_readtime(struct device *dev, struct rtc_time *tm)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);struct rk808 *rk808 = rk808_rtc->rk808;u8 rtc_data[NUM_TIME_REGS];int ret;/* 强制立即更新影子寄存器 */ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_RTC_GET_TIME,BIT_RTC_CTRL_REG_RTC_GET_TIME);if (ret) {dev_err(dev, "Failed to update bits rtc_ctrl: %d\n", ret);return ret;}/** 设置GET_TIME位后,不能立即读取RTC时间。需要等待大约31.25微秒,* 这是32kHz时钟的一个周期。如果在这里清除GET_TIME位,则I2C传输时间* 肯定超过31.25微秒:在400kHz总线频率下为16 * 2.5微秒。*/ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_RTC_GET_TIME, 0);if (ret) {dev_err(dev, "Failed to update bits rtc_ctrl: %d\n", ret);return ret;}/* 批量读取RTC数据 */ret = regmap_bulk_read(rk808->regmap, rk808_rtc->creg->seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, "Failed to bulk read rtc_data: %d\n", ret);return ret;}/* 将BCD编码的时间数据转换为二进制并填充到tm结构中 */tm->tm_sec = bcd2bin(rtc_data[0] & SECONDS_REG_MSK);tm->tm_min = bcd2bin(rtc_data[1] & MINUTES_REG_MAK);tm->tm_hour = bcd2bin(rtc_data[2] & HOURS_REG_MSK);tm->tm_mday = bcd2bin(rtc_data[3] & DAYS_REG_MSK);tm->tm_mon = (bcd2bin(rtc_data[4] & MONTHS_REG_MSK)) - 1;tm->tm_year = (bcd2bin(rtc_data[5] & YEARS_REG_MSK)) + 100;tm->tm_wday = bcd2bin(rtc_data[6] & WEEKS_REG_MSK);/* 如果需要转换,调用rockchip_to_gregorian进行转换 */if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)rockchip_to_gregorian(tm);/* 打印调试信息 */dev_dbg(dev, "RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n",1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_wday,tm->tm_hour, tm->tm_min, tm->tm_sec);return ret;
}/*** rk808_rtc_set_time - 设置RTC(实时时钟)的时间和日期* @dev: 设备结构体指针,代表RTC设备* @tm: 指向rtc_time结构体的指针,包含要设置的日期和时间信息* * 此函数将给定的日期和时间信息写入到RTC芯片中,以更新RTC的当前时间和日期设置* 它首先将日期和时间信息转换为BCD格式,然后通过regmap接口将这些信息写入到RTC的相应寄存器中* * 返回值:* 成功时返回0,失败时返回负的错误代码*/
static int rk808_rtc_set_time(struct device *dev, struct rtc_time *tm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取RK808芯片的数据struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储RTC数据u8 rtc_data[NUM_TIME_REGS];// 定义返回值变量int ret;// 调试信息,显示正在设置的日期和时间dev_dbg(dev, "set RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n",1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_wday,tm->tm_hour, tm->tm_min, tm->tm_sec);// 如果需要转换,则将格里高利日期转换为适合RTC的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(tm);// 将时间数据从二进制转换为BCD格式,并存储到rtc_data数组中rtc_data[0] = bin2bcd(tm->tm_sec);rtc_data[1] = bin2bcd(tm->tm_min);rtc_data[2] = bin2bcd(tm->tm_hour);rtc_data[3] = bin2bcd(tm->tm_mday);rtc_data[4] = bin2bcd(tm->tm_mon + 1);rtc_data[5] = bin2bcd(tm->tm_year - 100);rtc_data[6] = bin2bcd(tm->tm_wday);// 停止RTC,以便更新RTC寄存器ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M,BIT_RTC_CTRL_REG_STOP_RTC_M);if (ret) {dev_err(dev, "Failed to update RTC control: %d\n", ret);return ret;}// 将rtc_data数组中的数据批量写入到RTC寄存器中ret = regmap_bulk_write(rk808->regmap, rk808_rtc->creg->seconds_reg,rtc_data, NUM_TIME_REGS);if (ret) {dev_err(dev, "Failed to bull write rtc_data: %d\n", ret);return ret;}// 再次启动RTCret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M, 0);if (ret) {dev_err(dev, "Failed to update RTC control: %d\n", ret);return ret;}return 0;
}/*** rk808_rtc_readalarm - 读取RTC报警时间* @dev: 设备结构体指针* @alrm: RTC报警时间结构体指针** 此函数从RTC中读取报警时间,并将其填充到alrm参数中。它首先读取报警时间寄存器,* 然后根据寄存器的值更新alrm结构体中的时间字段。此外,它还会读取中断寄存器以确定* 报警是否已启用。** 返回值: 成功时返回0,失败时返回负错误代码*/
static int rk808_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的驱动数据struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取RK808芯片结构体指针struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储报警时间寄存器的值u8 alrm_data[NUM_ALARM_REGS];// 定义一个变量来存储中断寄存器的值uint32_t int_reg;// 定义一个变量来存储函数执行结果int ret;// 从RTC中读取报警时间寄存器的值ret = regmap_bulk_read(rk808->regmap,rk808_rtc->creg->alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果读取失败,打印错误信息并返回错误代码dev_err(dev, "Failed to read RTC alarm date REG: %d\n", ret);return ret;}// 将读取的寄存器值转换为报警时间alrm->time.tm_sec = bcd2bin(alrm_data[0] & SECONDS_REG_MSK);alrm->time.tm_min = bcd2bin(alrm_data[1] & MINUTES_REG_MAK);alrm->time.tm_hour = bcd2bin(alrm_data[2] & HOURS_REG_MSK);alrm->time.tm_mday = bcd2bin(alrm_data[3] & DAYS_REG_MSK);alrm->time.tm_mon = (bcd2bin(alrm_data[4] & MONTHS_REG_MSK)) - 1;alrm->time.tm_year = (bcd2bin(alrm_data[5] & YEARS_REG_MSK)) + 100;// 如果需要转换,将Rockchip日历时间转换为公历时间if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)rockchip_to_gregorian(&alrm->time);// 读取中断寄存器的值以确定报警是否已启用ret = regmap_read(rk808->regmap, rk808_rtc->creg->int_reg, &int_reg);if (ret) {// 如果读取失败,打印错误信息并返回错误代码dev_err(dev, "Failed to read RTC INT REG: %d\n", ret);return ret;}// 打印调试信息,显示读取的报警时间dev_dbg(dev, "alrm read RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 根据中断寄存器的值设置报警启用状态alrm->enabled = (int_reg & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) ? 1 : 0;// 函数执行成功,返回0return 0;
}/*** 停止RTC闹钟。* * 该函数通过清除相应的中断使能位和闹钟状态位来停止RTC的闹钟功能。主要用于禁用RTC的闹钟功能。* * @param rk808_rtc 指向rk808_rtc结构的指针,包含RTC操作所需的信息。* @return 成功返回0,失败返回负的错误码。*/static int rk808_rtc_stop_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 = rk808_rtc->rk808;int ret;/* 禁用RTC闹钟中断 */ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M, 0);/** 必须在闹钟触发1秒后或禁用闹钟后清除RTC闹钟状态(BIT(6))。*/ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_ALARM_STATUS);return ret;
}static int rk808_rtc_start_alarm(struct rk808_rtc *rk808_rtc)
{struct rk808 *rk808 = rk808_rtc->rk808;int ret;ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->int_reg,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M,BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);return ret;
}/*** rk808_rtc_setalarm - 设置RTC闹钟* @dev: 设备结构体指针* @alrm: 闹钟数据结构指针,包含闹钟时间和是否启用闹钟的信息* * 此函数负责将给定的闹钟时间设置到RTC芯片中,并根据alrm->enabled决定是否启用闹钟。* 它首先停止当前的闹钟,然后将时间数据从二进制转换为BCD格式,并写入到RTC的相关寄存器中。* 如果需要转换,会将时间从格里高利历转换为适合RTC芯片的格式。* * 返回值: 0表示成功,负值表示出错*/
static int rk808_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{// 获取RTC设备的私有数据结构struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);// 获取主设备结构体指针struct rk808 *rk808 = rk808_rtc->rk808;// 定义一个数组来存储闹钟数据u8 alrm_data[NUM_ALARM_REGS];int ret;// 停止当前的闹钟ret = rk808_rtc_stop_alarm(rk808_rtc);if (ret) {// 如果停止闹钟失败,打印错误信息并返回错误码dev_err(dev, "Failed to stop alarm: %d\n", ret);return ret;}// 打印设置的闹钟时间信息dev_dbg(dev, "alrm set RTC date/time %ptRd(%d) %ptRt\n", &alrm->time,alrm->time.tm_wday, &alrm->time);// 如果需要转换,将时间从格里高利历转换为适合RTC芯片的格式if (rk808_rtc->flag & RTC_NEED_TRANSITIONS)gregorian_to_rockchip(&alrm->time);// 将时间数据从二进制转换为BCD格式,并存储到数组中alrm_data[0] = bin2bcd(alrm->time.tm_sec);alrm_data[1] = bin2bcd(alrm->time.tm_min);alrm_data[2] = bin2bcd(alrm->time.tm_hour);alrm_data[3] = bin2bcd(alrm->time.tm_mday);alrm_data[4] = bin2bcd(alrm->time.tm_mon + 1);alrm_data[5] = bin2bcd(alrm->time.tm_year - 100);// 将闹钟数据写入到RTC的相关寄存器中ret = regmap_bulk_write(rk808->regmap,rk808_rtc->creg->alarm_seconds_reg, alrm_data,NUM_ALARM_REGS);if (ret) {// 如果写入失败,打印错误信息并返回错误码dev_err(dev, "Failed to bulk write: %d\n", ret);return ret;}// 如果闹钟被启用,启动闹钟if (alrm->enabled) {ret = rk808_rtc_start_alarm(rk808_rtc);if (ret) {// 如果启动闹钟失败,打印错误信息并返回错误码dev_err(dev, "Failed to start alarm: %d\n", ret);return ret;}}// 返回成功return 0;
}static int rk808_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (enabled)return rk808_rtc_start_alarm(rk808_rtc);return rk808_rtc_stop_alarm(rk808_rtc);
}/** We will just handle setting the frequency and make use the framework for* reading the periodic interupts.** @freq: Current periodic IRQ freq:* bit 0: every second* bit 1: every minute* bit 2: every hour* bit 3: every day*/
static irqreturn_t rk808_alarm_irq(int irq, void *data)
{struct rk808_rtc *rk808_rtc = data;struct rk808 *rk808 = rk808_rtc->rk808;struct i2c_client *client = rk808->i2c;int ret;ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_STATUS_MASK);if (ret) {dev_err(&client->dev, "%s:Failed to update RTC status: %d\n",__func__, ret);return ret;}rtc_update_irq(rk808_rtc->rtc, 1, RTC_IRQF | RTC_AF);dev_dbg(&client->dev, "%s:irq=%d\n", __func__, irq);return IRQ_HANDLED;
}static const struct rtc_class_ops rk808_rtc_ops = {.read_time = rk808_rtc_readtime,.set_time = rk808_rtc_set_time,.read_alarm = rk808_rtc_readalarm,.set_alarm = rk808_rtc_setalarm,.alarm_irq_enable = rk808_rtc_alarm_irq_enable,
};#ifdef CONFIG_PM_SLEEP
/* Turn off the alarm if it should not be a wake source. */
static int rk808_rtc_suspend(struct device *dev)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (device_may_wakeup(dev))enable_irq_wake(rk808_rtc->irq);return 0;
}/* Enable the alarm if it should be enabled (in case it was disabled to* prevent use as a wake source).*/
static int rk808_rtc_resume(struct device *dev)
{struct rk808_rtc *rk808_rtc = dev_get_drvdata(dev);if (device_may_wakeup(dev))disable_irq_wake(rk808_rtc->irq);return 0;
}
#endifstatic SIMPLE_DEV_PM_OPS(rk808_rtc_pm_ops, rk808_rtc_suspend, rk808_rtc_resume);static struct rk_rtc_compat_reg rk808_creg = {.ctrl_reg = RK808_RTC_CTRL_REG,.status_reg = RK808_RTC_STATUS_REG,.alarm_seconds_reg = RK808_ALARM_SECONDS_REG,.int_reg = RK808_RTC_INT_REG,.seconds_reg = RK808_SECONDS_REG,
};static struct rk_rtc_compat_reg rk817_creg = {.ctrl_reg = RK817_RTC_CTRL_REG,.status_reg = RK817_RTC_STATUS_REG,.alarm_seconds_reg = RK817_ALARM_SECONDS_REG,.int_reg = RK817_RTC_INT_REG,.seconds_reg = RK817_SECONDS_REG,
};/*** @brief 实现 RK808 芯片的 RTC 设备探测功能。* * 此函数在加载相应的驱动程序时初始化 RTC 设备。* 主要任务包括:* - 检查设备树中是否启用了 RTC 设备。* - 为 RTC 设备结构分配内存。* - 根据不同的芯片变体设置 RTC 控制寄存器。* - 启动 RTC 并启用影子计时器。* - 注册 RTC 设备并请求报警中断。* * @param pdev 平台设备指针* @return 成功返回 0,失败返回负的错误码*/
static int rk808_rtc_probe(struct platform_device *pdev)
{struct rk808 *rk808 = dev_get_drvdata(pdev->dev.parent);struct rk808_rtc *rk808_rtc;struct device_node *np;int ret;// 根据芯片变体检查 RTC 设备是否启用switch (rk808->variant) {case RK805_ID:case RK808_ID:case RK816_ID:case RK818_ID:np = of_get_child_by_name(pdev->dev.parent->of_node, "rtc");if (np && !of_device_is_available(np)) {dev_info(&pdev->dev, "设备已禁用\n");return -EINVAL;}break;default:break;}// 为 RTC 设备结构分配内存rk808_rtc = devm_kzalloc(&pdev->dev, sizeof(*rk808_rtc), GFP_KERNEL);if (rk808_rtc == NULL)return -ENOMEM;// 根据不同的芯片变体设置控制寄存器switch (rk808->variant) {case RK808_ID:case RK818_ID:rk808_rtc->creg = &rk808_creg;rk808_rtc->flag |= RTC_NEED_TRANSITIONS;break;case RK805_ID:case RK816_ID:rk808_rtc->creg = &rk808_creg;break;case RK809_ID:case RK817_ID:rk808_rtc->creg = &rk817_creg;break;default:rk808_rtc->creg = &rk808_creg;break;}// 设置平台设备数据platform_set_drvdata(pdev, rk808_rtc);rk808_rtc->rk808 = rk808;// 启动 RTC 并启用影子计时器ret = regmap_update_bits(rk808->regmap, rk808_rtc->creg->ctrl_reg,BIT_RTC_CTRL_REG_STOP_RTC_M |BIT_RTC_CTRL_REG_RTC_READSEL_M,BIT_RTC_CTRL_REG_RTC_READSEL_M);if (ret) {dev_err(&pdev->dev, "Failed to update RTC control: %d\n", ret);return ret;}ret = regmap_write(rk808->regmap, rk808_rtc->creg->status_reg,RTC_STATUS_MASK);if (ret) {dev_err(&pdev->dev, "Failed to write RTC status: %d\n", ret);return ret;}// 启用设备唤醒功能device_init_wakeup(&pdev->dev, 1);// 分配 RTC 设备rk808_rtc->rtc = devm_rtc_allocate_device(&pdev->dev);if (IS_ERR(rk808_rtc->rtc))return PTR_ERR(rk808_rtc->rtc);// 设置 RTC 操作函数rk808_rtc->rtc->ops = &rk808_rtc_ops;// 获取 RTC 中断号rk808_rtc->irq = platform_get_irq(pdev, 0);if (rk808_rtc->irq < 0)return rk808_rtc->irq;// 请求报警中断ret = devm_request_threaded_irq(&pdev->dev, rk808_rtc->irq, NULL,rk808_alarm_irq, 0, "RTC alarm",rk808_rtc);if (ret) {dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",rk808_rtc->irq, ret);return ret;}// 注册 RTC 设备return rtc_register_device(rk808_rtc->rtc);
}static struct platform_driver rk808_rtc_driver = {.probe = rk808_rtc_probe,.driver = {.name = "rk808-rtc",.pm = &rk808_rtc_pm_ops,},
};module_platform_driver(rk808_rtc_driver);MODULE_DESCRIPTION("RTC driver for the rk808 series PMICs");
MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rk808-rtc");
2.5 提供带有中文注释的drivers/rtc/源码
在本文章的附带绑定资源中!
2.6 APP层操作RTC
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <time.h>
#include <string.h>
#include <errno.h>#define RTC_DEVICE "/dev/rtc0"void print_time(struct rtc_time *rtc_tm)
{printf("RTC date/time: %04d-%02d-%02d %02d:%02d:%02d\n",rtc_tm->tm_year + 1900, rtc_tm->tm_mon + 1, rtc_tm->tm_mday,rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
}int main(int argc, char *argv[])
{int fd;struct rtc_time rtc_tm;time_t rawtime;struct tm *timeinfo;// 打开RTC设备fd = open(RTC_DEVICE, O_RDWR);if (fd == -1) {perror("打开RTC设备失败");return errno;}// 读取当前时间if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) {perror("读取RTC时间失败");close(fd);return errno;}printf("当前");print_time(&rtc_tm);// 设置新的时间time(&rawtime);timeinfo = localtime(&rawtime);rtc_tm.tm_year = timeinfo->tm_year;rtc_tm.tm_mon = timeinfo->tm_mon;rtc_tm.tm_mday = timeinfo->tm_mday;rtc_tm.tm_hour = timeinfo->tm_hour;rtc_tm.tm_min = timeinfo->tm_min;rtc_tm.tm_sec = timeinfo->tm_sec;if (ioctl(fd, RTC_SET_TIME, &rtc_tm) == -1) {perror("设置RTC时间失败");close(fd);return errno;}printf("设置后的");print_time(&rtc_tm);// 关闭RTC设备close(fd);return 0;
}
三、参考资料
- Linux下RTC子系统驱动
- Linux NVMEM子系统:概述以及RK3588 OTP实例
相关文章:

Linux RTC 驱动框架
目录 一、实时时钟(RTC)介绍1.1 概述1.2 功能1.3 应用场景1.4 工作原理1.5 对外接口1.6 常见 RTC 芯片1.7 在 Linux 系统中的应用1.8 注意事项 二、Linux 内核 RTC 驱动框架2.1 相关源码文件介绍2.2 核心数据结构2.2.1 struct rtc_device2.2.2 rtc_class…...

msyql数据库读写分离搭建
一.mysql读写分离:缓解主服务器的压力1.概念:主服务器写数据,从服务器读数据2.实现方法:客户端分离: 开发手动分离地址服务端分离: 数据库与应用之间加一个中间件,分离读写请求mysql-proxy,mysql-route,maxscaleamoeba,cobar,mycat2atlas,kingshard,vitees3.mycat配置方法:冷配…...

WWW23-多行为级联|级联图卷积网络的多行为推荐
论文:https://arxiv.org/abs/2303.15720 代码:https://github.com/SS-00-SS/MBCGCN 这篇论文MB-CGCN和上一篇CRGCN是同一个团队的,都是级联的方式。一个用了残差,一个用了特征转换,文章最后有discussion讨论了两者的不…...

【EthIf-14】EthIfGeneral容器配置-02
1.实际EthIfGeneral的配置实例 关闭DET接口开启发送确认中断开启接收中断主周期接收timeout主周期 2. 代码实例参考 阅读此部分代码,搞清楚代码分为几个section,大概瞄一眼就好,不用深究其含义,只需有一个宏观的层次结构的映像即可。 //Appl/GenData/EthIf_Cfg.h #...

近实时”(NRT)搜索、倒排索引
近实时(Near Real-Time, NRT)搜索 近实时(NRT)搜索是 Elasticsearch 的核心特性之一,指的是数据在被写入到系统后,可以几乎立即被搜索和查询到。虽然它不像传统数据库那样完全实时,但它的延迟通…...

Ubuntu20.04安装openMVS<成功>.colmap<成功>和openMVG<失败(已成功)>
一、安装openMVS 参考官方文档 sudo apt-get -y install git mercurial cmake libpng-dev libjpeg-dev libtiff-dev libglu1-mesa-dev eigen git clone https://gitlab.com/libeigen/eigen --branch 3.4 mkdir eigen_build cd eigen_build &&\cmake . ../eigen -…...

从测试服务器手动热部署到生产环境的实现
为了实现从测试服务器(192.168.0.255)手动热部署到生产环境(172.168.20.100),可以采用多种方法。以下是详细的步骤和最佳实践,帮助你实现这一目标。 1. 准备生产环境 确保生产环境上的 Docker 和 Docker …...

【c++高阶DS】图
🔥个人主页:Quitecoder 🔥专栏:c笔记仓 目录 01.并查集02.图的介绍03.图的存储结构03.1.邻接矩阵03.2.邻接表03.3.矩阵版本代码实现03.4.邻接表版本代码实现 完整代码: 01.并查集 在一些应用问题中,需要将…...

React第十八节 useEffect 用法使用技巧注意事项详解
1、概述 useEffect 是React中一个用于 将组件与外部系统同步的 Hook;在函数式组件中处理副作用函数的 Hook,用于替代类式组件中的生命周期函数; 可以在副作用函数中 实现以下操作: a、请求接口,获取后台提供数据 b、操…...

C++ 指针基础:开启内存操控之门
1. 指针为何如此重要 在 C 编程领域,指针堪称一项极为关键的特性。它赋予了程序员直接访问和操控内存的能力,这使得程序在处理复杂数据结构与优化性能时具有更高的灵活性。想象一下,在编写大型程序时,高效地管理内存资源是多么重要…...

Nginx的stream模块代理四层协议TCP的流量转发
Nginx的stream模块是一个功能强大的工具,专门用于处理四层协议(即网络层和传输层,如TCP和UDP)的流量。以下是对Nginx stream模块的详细解析: 一、基本功能 Nginx的stream模块主要用于实现TCP和UDP数据流的代理、转发…...

UE5 渲染管线 学习笔记
兰伯特 SSS为散射的意思 带Bias的可以根据距离自动切换mip的卷积值 而带Level的值mipmaps的定值 #define A8_SAMPLE_MASK .a 这样应该就很好理解了 这个只采样a通道 带Level的参考上面的 朝左上和右下进行模糊 带Bias参考上面 随机数 4D 3D 2D 1D...

Echarts连接数据库,实时绘制图表详解
文章目录 Echarts连接数据库,实时绘制图表详解一、引言二、步骤一:环境准备与数据库连接1、环境搭建2、数据库连接 三、步骤二:数据获取与处理1、查询数据库2、数据处理 四、步骤三:ECharts图表配置与渲染1、配置ECharts选项2、动…...

Electron 学习笔记
目录 一、安装和启动electron 1. 官网链接 2. 根据文档在控制台输入 3. 打包必填 4. 安装electron开发依赖 5. 在开发的情况下打开应用 6. 修改main为main.js,然后创建main.js 7.启动 二、启动一个窗口 1. main.js 2. index.html 3. 隐藏菜单栏 三、其他…...

Debian 12 安装配置 fail2ban 保护 SSH 访问
背景介绍 双十一的时候薅羊毛租了台腾讯云的虚机, 是真便宜, 只是没想到才跑了一个月, 系统里面就收集到了巨多的 SSH 恶意登录失败记录. 只能说, 互联网真的是太不安全了. 之前有用过 fail2ban 在 CentOS 7 上面做过防护, 不过那已经是好久好久之前的故事了, 好多方法已经不…...

http反向代理
通过反向代理实现访问biying,目前访问一些网站需要绕过cloudfare还没有解决,代码如下: from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse import httpx import uvicorn import logging# 设置日志 logging.basicConfig(level=logging.…...

java12.24日记
运算符: 算术运算符: 顾名思义进行算数运算的 多为:四则运算,加一个取余 ,-,*,/以及 %(取余) 而外的:自增 以及自减--,对原数进行1或者-1 i…...

vue中proxy代理配置(测试一)
接口地址:http://jsonplaceholder.typicode.com/posts 1、配置一(代理没起作用) (1)设置baseURL为http://jsonplaceholder.typicode.com (2)proxy为 ‘/api’:’ ’ (3&a…...

[OpenGL]使用TransformFeedback实现粒子效果
一、简介 本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果,最终可以实现下图的效果: 本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。 二、使用 TransformFeed…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台
GoIoT 是基于Gin 的开源分布式物联网(IoT)开发平台,用于快速开发,部署物联设备接入项目,是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台,它是一个企业级物联网平台解决方案ÿ…...

用 gdbserver 调试 arm-linux 上的 AWTK 应用程序
很多嵌入式 linux 开发者都能熟练的使用 gdb/lldb 调试应用程序,但是还有不少朋友在调试开发板上的程序时,仍然在使用原始的 printf。本文介绍一下使用 gdbserver 通过网络调试开发板上的 AWTK 应用程序的方法,供有需要的朋友参考。 1. 下载 …...

攻防世界web第一题
最近开始学习网络安全的相关知识,开启刷题,当前第一题 题目为攻防世界web新手题 这是题目 翻译:在这个训练挑战中,您将了解 Robots_exclusion_standard。网络爬虫使用 robots.txt 文件来检查是否允许它们对您的网站或仅网站的一部…...

轮播图带详情插件,插件
超级好用的轮播图 介绍访问地址参数介绍使用方法(简单使用,参数结构点击链接查看详情)图片展示 介绍 video(15) 带有底部物品介绍以及价格的轮播图组件,持续维护,uniApp插件,直接下载填充数据就可以在项目里…...

gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接
gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接 题目描述 一个字符串是回文串,当且仅当该字符串从前往后读和从后往前读是一样的,例如, aabaa \texttt{aabaa} aabaa 和...

ISO17025最新认证消息
ISO17025认证是国际上广泛认可的实验室管理标准,全称为《检测和校准实验室能力的通用要求》,由国际标准化组织(ISO)和国际电工委员会(IEC)联合发布。以下是对ISO17025最新认证消息及相关内容的归纳…...

ASP.NET Core Web API 控制器
文章目录 一、基类:ControllerBase二、API 控制器类属性三、使用 Get() 方法提供天气预报结果 在深入探讨如何编写自己的 PizzaController 类之前,让我们先看一下 WeatherController 示例中的代码,了解它的工作原理。 在本单元中,…...

RAID5原理简介和相关问题
1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗? 3、RAID5单块硬盘存储的是原始数据,还是异或后的数据? 4、RAID5的分块大小 RAID5的分块大小一般选择4KB到64KB之间较为合适。选择合适的分块大小主要取决于以下几个考量因素࿱…...

Axure RP 8安装(内带安装包)
通过网盘分享的文件:Axure8.0.zip 链接: https://pan.baidu.com/s/195_qy2iiDIcYG4puAudScA 提取码: 6xt8 --来自百度网盘超级会员v1的分享 勾选I Agree 安装完成...

stm32定时器输出比较----驱动步进电机
定时器输出比较理论 OC(Output Compare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形每个高级定时器和通用定时器都拥有4个输出比较通道高级定时器的前3个通道额外拥有死区生成和互补输出…...

关于鸿蒙架构feature
鸿蒙feature层模块架构 model:定义数据类型,进行接口请求 view:视图层 写UI viewModel:控制层 关于逻辑和请求调用 page页...