嵌入式驱动开发详解6(RTC)

文章目录

前言

实时时钟是很常用的一个外设,通过实时时钟我们就可以知道年、月、日和时间等信息。 因此在需要记录时间的场合就需要实时时钟,可以使用专用的实时时钟芯片来完成此功能,但 是现在大多数的 MCU 或者 MPU 内部就已经自带了实时时钟外设模块。

RTC简介

STM32 内部有一个 RTC 外设模块,这个模块需要一个 32.768KHz 的晶振,对这个 RTC 模块进行初始化就可以得到一个实时时钟。I.MX6U 内部也有 个 RTC 模块,但是不叫作"RTC",而是叫做"SNVS",这一点要注意!

SNVS 直译过来就是安全的非易性存储,SNVS 里面主要是一些低功耗的外设,包括一个 安全的实时计数器(RTC)、一个单调计数器(monotonic counter)和一些通用的寄存器。SNVS 里面的外设在芯片掉电以后由电池供电继续运行,这个纽扣电池就是在主电源关闭以后为 SNVS 供电的。

SNVS 分为两个子模块:SNVS_HP 和 SNVS_LP,也就是高功耗域(SNVS_HP)和低功耗域 (SNVS_LP),系统主电源断电以后SNVS_HP也会断电,但是在后备电源支持下,SNVS_LP 是不会断电的,而且SNVS_LP是和芯片复位隔离开的,因此SNVS_LP相关的寄存器的值会一直保存着。

①、VDD_HIGH_IN 是系统(芯片)主电源,这个电源会同时供给给 SNVS_HP 和 SNVS_LP。

②、VDD_SNVS_IN 是纽扣电池供电的电源,这个电源只会供给给 SNVS_LP,保证在系 统主电源 VDD_HIGH_IN 掉电以后 SNVS_LP 会继续运行。

③、SNVS_HP 部分。

④、SNVS_LP 部分,此部分有个 SRTC,这个就是我们本章要使用的 RTC。

其实不管是 SNVS_HP 还是 SNVS_LP,其内部都有一个 SRTC,但是因为 SNVS_HP 在系 统电源掉电以后就会关闭,所以我们使用的是 SNVS_LP内部的SRTC,不管是 SNVS_HP 里面的SRTC,还是 SNVS_LP 里面的 SRTC,其本质就是一个定时 器,SRTC需要外界提供一个32.768KHz 的时钟,寄存器 SNVS_LPSRTCMR 和 SNVS_LPSRTCLR 保存着秒数,直接读取这两个寄存 器的值就知道过了多长时间了。一般以 1970 年 1 月 1 日为起点,加上经过的秒数即可得到现在 的时间和日期,原理还是很简单的。SRTC 也是带有闹钟功能的,可以在寄存器 SNVS_LPAR 中 写入闹钟时间值,当时钟值和闹钟值匹配的时候就会产生闹钟中断,要使用时钟功能的话还需要进行一些设置。

RTC驱动分析

RTC驱动框架

Linux内核中的RTC设备不仅用于获取当前时间,还可以用于设置系统时间。内核提供了一套API,允许用户空间程序与RTC设备进行交互,实现时间的读取和设置。

RTC 设备驱动是一个标准的字符设备驱动,应用程序通过 open、release、read、write 和 ioctl 等函数完成对 RTC 设备的操作,如下图所示:

内核将 RTC 设备抽象为 rtc_device 结构体,定义在include/linux/rtc.h:

c 复制代码
struct rtc_device
{
	struct device dev;
	struct module *owner;

	int id;
	char name[RTC_DEVICE_NAME_SIZE];

	const struct rtc_class_ops *ops;
	struct mutex ops_lock;

	struct cdev char_dev;
	unsigned long flags;
	............
};

我们需要重点关注的是 ops 成员变量,这是一个 rtc_class_ops 类型的指针变量,定义在include/linux/rtc.h,rtc_class_ops 为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。

c 复制代码
struct rtc_class_ops {
	int (*open)(struct device *);
	void (*release)(struct device *);
	int (*ioctl)(struct device *, unsigned int, unsigned long);
	int (*read_time)(struct device *, struct rtc_time *);
	int (*set_time)(struct device *, struct rtc_time *);
	int (*read_alarm)(struct device *, struct rtc_wkalrm *);
	int (*set_alarm)(struct device *, struct rtc_wkalrm *);
	int (*proc)(struct device *, struct seq_file *);
	int (*set_mmss64)(struct device *, time64_t secs);
	int (*set_mmss)(struct device *, unsigned long secs);
	int (*read_callback)(struct device *, int data);
	int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

rtc_class_ops 中 的这 些 函数只最底层的RTC设备操作函数, 并不是提供给应用层的 file_operations 函数操作集。RTC 是个字符设备,那么肯定有字符设备的 file_operations 函数操作集,Linux 内核提供了一个 RTC 通用字符设备驱动文件 ,文件名为 drivers/rtc/rtc-dev.c,rtcdev.c 文件提供了所有 RTC 设备共用的 file_operations 函数操作集。之所以称之为通用,是因为二次开发的驱动是建立在这个通用字符设备驱动文件和linux内核的基础上开发的

c 复制代码
static const struct file_operations rtc_dev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= rtc_dev_read,
	.poll		= rtc_dev_poll,
	.unlocked_ioctl	= rtc_dev_ioctl,
	.open		= rtc_dev_open,
	.release	= rtc_dev_release,
	.fasync		= rtc_dev_fasync,
};

应用程序可以通过 ioctl 函 数来设置/读取时间、设置/读取闹钟的操作,那么对应的 rtc_dev_ioctl 函数就会执行, rtc_dev_ioctl 最终会通过操作 rtc_class_ops 中的 read_time、set_time 等函数来对具体 RTC 设备 的读写操作。具体的源码详细分析就不展示了,需要时可以自行查看。
字符设备的file_operations结构体设置好以后需要调用rtc_class_ops里的指针函数,但是rtc_class_ops的函数仅仅只是设置好了形式没有注册,因此需要将其注册到 Linux 内核中,这里我们可以使用 rtc_device_register 函数完成注册工作,rtc_device_unregister 函数来注销注册的 rtc_device。

c 复制代码
struct rtc_device *rtc_device_register(const char *name, struct device *dev, const struct rtc_class_ops *ops, struct module *owner)
void rtc_device_unregister(struct rtc_device *rtc)

还有另外一对rtc_device注册函数devm_rtc_device_register和devm_rtc_device_unregister, 分别为注册和注销 rtc_device。

RTC驱动实现

一般情况下,内部 RTC 驱动都不需要我们去编写,半导体厂商会编写好,这里就只学习原厂已经写好的驱动是怎么写的,以NXP的imx6ull为例,分析驱动,先从设备树入手,打开 imx6ull.dtsi,在里面找到如下 snvs_rtc 设备节点,节点 内容如下所示:

c 复制代码
/{
	soc{
		aips1: aips-bus@2000000{
				snvs: snvs@020cc000 {
				compatible = "fsl,sec-v4.0-mon", "syscon", "simple-mfd";
				reg = <0x020cc000 0x4000>;

				snvs_rtc: snvs-rtc-lp {
					compatible = "fsl,sec-v4.0-mon-rtc-lp";
					regmap = <&snvs>;
					offset = <0x34>;
					interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 20 		IRQ_TYPE_LEVEL_HIGH>;
				};
			};
		}
	};
};

通过兼容属性 compatible 的值"fsl,sec-v4.0-mon-rtc-lp"可以在Linux内核源码中搜索到对应的驱动文件,此文件为 drivers/rtc/rtc-snvs.c,如下所示,可以看出该驱动对应的也是platform架构:

c 复制代码
static const struct of_device_id snvs_dt_ids[] = {
	{ .compatible = "fsl,sec-v4.0-mon-rtc-lp", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, snvs_dt_ids);

static struct platform_driver snvs_rtc_driver = {
	.driver = {
		.name	= "snvs_rtc",
		.pm	= SNVS_RTC_PM_OPS,
		.of_match_table = snvs_dt_ids,
	},
	.probe		= snvs_rtc_probe,
};

在.snvs_rtc_probe函数中,主要做了以下几个事件,代码就不在展示了:

  • platform_get_resource 函数从设备树中获取到 RTC 外设寄存器基地址
  • devm_ioremap_resource 完成内存映射,得到 RTC 外设寄存器物理基 地址对应的虚拟地址
  • devm_regmap_init_mmio 函数将 RTC 的硬件寄存器转化为 regmap 形式,这样 regmap 机制的 regmap_write、regmap_read 等 API 函数才能操作寄存器。==具体有关regmap机制的说明后面会出相关笔记
  • 从设备树中获取 RTC 的中断号
  • 设置对应的寄存器
  • 用 snvs_rtc_enable 函数使能 RTC
  • devm_request_irq 函数请求 RTC 中断
  • devm_rtc_device_register 函数向系统注册 rtc_devcie,此函数会设置snvs_rtc_ops操作集
    最后需要原厂驱动开发人员把snvs_rtc_ops对应的函数完成:
c 复制代码
static const struct rtc_class_ops snvs_rtc_ops = {
	.read_time = snvs_rtc_read_time,
	.set_time = snvs_rtc_set_time,
	.read_alarm = snvs_rtc_read_alarm,
	.set_alarm = snvs_rtc_set_alarm,
	.alarm_irq_enable = snvs_rtc_alarm_irq_enable,
};

RTC应用

  • 如果要查看时间的话输入"date"命令即可
sh 复制代码
date
  • RTC 时间设置也是使用的date命令,输入"date --help"命令即可查看 date 命令如何设置系统 时间,设置当前时间为 2019 年 8 月 31 日 18:13:00,因此输入如下命令:
sh 复制代码
date -s "2019-08-31 18:13:00"
  • "date -s"命令仅仅是将当前系统时间设置了,此时间还没有写入到 I.MX6U 内部 RTC 里面或其他的 RTC 芯片里面,因此系统重启以后时间又会丢失。我们需要将 当前的时间写入到 RTC 里面,这里要用到 hwclock 命令,输入如下命令将系统时间写入到 RTC 里面:
sh 复制代码
hwclock -w //将当前系统时间写入到 RTC 里面

后续

到这里对RTC的笔记记录大致结束了,后面有新的相关的重要的内容会继续进行更新。

相关推荐
nopSled12 小时前
AlphaAvatar v0.6.4 架构升级:统一实时感知、RTC 解耦与在线 ENV Memory
人工智能·架构·实时音视频·agent·多模态
hz567891 天前
内网视频会议系统建设方案:适合政企单位的安全会议选择
安全·云计算·音视频·实时音视频·信息与通信
普普通通的一名码农1 天前
TPS6120芯片说明
驱动开发·笔记·芯片手册
hz567892 天前
电子远程评标系统哪家好?安全、合规、易用平台评测指南
安全·云计算·音视频·实时音视频·信息与通信
hz567892 天前
局域网视频会议解决方案,适配政企专网场景
音视频·实时音视频·信息与通信·安全架构
阿拉斯攀登2 天前
SPI 设备驱动开发
linux·驱动开发·嵌入式硬件·linux内核·嵌入式·linux驱动·spi
阿拉斯攀登3 天前
I2C 设备驱动开发
linux·驱动开发·嵌入式硬件·嵌入式·linux驱动·i2c·字符设备驱动
REDcker3 天前
远程实时操控技术详解
音视频·实时音视频·数据采集·遥控·遥操作·数据闭环·远程操控
johnny2333 天前
规范驱动开发SDD:Spec Kit、OpenSpec、BMAD-METHOD、Kiro Spec
驱动开发
美狐美颜SDK开放平台3 天前
娱乐直播APP平台开发实践:视频美颜SDK接入经验总结
php·音视频·实时音视频·娱乐·第三方美颜sdk·视频美颜sdk