STM32MP157驱动开发——外置RTC芯片RCF8563驱动


参考文章:【正点原子】STM32MP1嵌入式Linux驱动开发——外置RTC实验

一、简介

  在一般的应用场合,使用 SOC 内置的 RTC 就可以了,而且成本也低。但是在一些对于时间精度要求比较高的场合,SOC 内置的 RTC 就不适用了。这时需要根据自己的应用要求选择合适的外置 RTC 芯片。正点原子 STM32MP1 开发板上板载了一个 RTC 芯片:PCF8563,这是一个 IIC 接口的外置 RTC 芯片。
  PCF8563 是一个 CMOS RTC 芯片,支持时间和日历功能,使用 IIC 接口来传输时间信息,最大传输速度为 400Kbit/S,在读写寄存器的时候地址自增。其一些特性如下:

  • 提供年、月、日、星期,时、分、秒计时,使用外置 32.768Khz 晶振
  • 低后备电流:0.25uA ,VDD=3.0V,温度 25℃
  • IIC 接口,速度最高 400KHz
  • 可编程时钟输出,可以供其他设备使用,可输出的时钟频率有 32.768kHz、1.024kHz、32Hz 和 1Hz
  • 支持闹钟和定时功能
  • IIC 读地址为 0XA3,写地址为 0XA2,也就是 IIC 器件地址为: 0X51
  • 有一个开漏输出的中断引脚

二、驱动开发

  总体来说,PCF8563 还是很简单的,这是一个 IIC 接口的 RTC 芯片,因此在 Linux 系统下就涉及到两类驱动:①IIC 驱动,需要 IIC 驱动框架来读写 PCF8563 芯片。②RTC 驱动,因为这是一个 RTC 芯片,因此要用到 RTC 驱动框架。如果要使用中断功能,还需要使用中断子系统。中断子系统的使用也比较简单,在设备树中添加相关的中断引脚描述即可。
  另外,在Linux系统中默认集成了PCF8563驱动,所以直接修改设备树并使能驱动即可。

原理图:
在这里插入图片描述
PCF8563 连接到了 STM32MP157 的 I2C4 接口上,引脚为 PZ5、PZ4。INT 引脚连接到 STM32MP157 的 PI3 引脚。

1.修改设备树

在stm32mp15-pinctrl.dtsi中,找到 i2c4 节点:
在这里插入图片描述
可以看出,I2C4 默认引脚就是 PZ4 和 PZ5,所以不需要修改。

在设备树文件stm32mp157d-atk.dts中,添加 i2c4 节点并追加 PCF8563 子节点:

&i2c4 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c4_pins_a>;
	pinctrl-1 = <&i2c4_pins_sleep_a>;
	status = "okay";

	pcf8563@51 {
		compatible = "nxp,pcf8563";
		irq_gpio = <&gpioi 3 IRQ_TYPE_EDGE_FALLING>;
		reg = <0x51>;
	};
};

首先设置 IO 要使用的 pinmux 配置,然后设置pcf8563子节点,compatible 为“nxp,pcf8563”,中断引脚为 PI3,下降沿触发。I2C 地址为 0X51,因此 reg 为 0X51。

2.驱动使能

在使用外部RTC时,需要先关闭上一节的内部RTC配置。在 Linux 内核的menuconfig中关闭以下选项:
在这里插入图片描述
然后使能 Linux 自带的PCF8563驱动:
在这里插入图片描述
配置完成后重新编译出新的设备树和uImage即可启动开发板。

三、运行测试

第一次启动时会提示以下内容:
在这里插入图片描述
在开机后使用以下命令进行设置:

date -s "2022-12-26 17:58:00" //设置时间
hwclock -w //保存

然后就不会有错误提示了。

四、驱动分析

pcf8563的驱动文件为 drivers/rtc/rtc-pcf8563.c,其基础框架为 I2C,驱动的部分内容如下:

1 static const struct i2c_device_id pcf8563_id[] = {
2 	{ "pcf8563", 0 },
3 	{ "rtc8564", 0 },
4 	{}
5 };
6 MODULE_DEVICE_TABLE(i2c, pcf8563_id);
7
8 #ifdef CONFIG_OF
9 static const struct of_device_id pcf8563_of_match[] = {
10 	{ .compatible = "nxp,pcf8563" },
11 	{ .compatible = "epson,rtc8564" },
12 	{ .compatible = "microcrystal,rv8564" },
13 	{}
14 };
15 MODULE_DEVICE_TABLE(of, pcf8563_of_match);
16 #endif
17
18 static struct i2c_driver pcf8563_driver = {
19 	 .driver = {
20 		.name = "rtc-pcf8563",
21 		.of_match_table = of_match_ptr(pcf8563_of_match),
22 	 },
23 	 .probe = pcf8563_probe,
24   .id_table = pcf8563_id,
25 };
26
27 module_i2c_driver(pcf8563_driver);

是个标准的 I2C 驱动框架,pcf8563_of_match 结构体数组就是设备树匹配数组。
pcf8563_probe 函数:

1 static int pcf8563_probe(struct i2c_client *client, const struct i2c_device_id *id)
3 {
4 		struct pcf8563 *pcf8563;
5 		int err;
6 		unsigned char buf;
......
13 		pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563), GFP_KERNEL);
15 		if (!pcf8563)
16 			return -ENOMEM;
17
18 		i2c_set_clientdata(client, pcf8563); pcf8563->client = client;
20 		device_set_wakeup_capable(&client->dev, 1);
21
22 		/* Set timer to lowest frequency to save power */
23 		buf = PCF8563_TMRC_1_60;
24 		err = pcf8563_write_block_data(client, PCF8563_REG_TMRC, 1, &buf);
25 		if (err < 0) {
26 			dev_err(&client->dev, "%s: write error\n", __func__);
27 		return err;
28 		}
29
30 		/* Clear flags and disable interrupts */
31 		buf = 0;
32 		err = pcf8563_write_block_data(client, PCF8563_REG_ST2, 1, &buf);
33 		if (err < 0) {
34 			dev_err(&client->dev, "%s: write error\n", __func__);
35 			return err;
36 		}
37
38 		pcf8563->rtc = devm_rtc_allocate_device(&client->dev);
39 		if (IS_ERR(pcf8563->rtc))
40 			return PTR_ERR(pcf8563->rtc);
41
42 		pcf8563->rtc->ops = &pcf8563_rtc_ops;
43 		/* the pcf8563 alarm only supports a minute accuracy */
44 		pcf8563->rtc->uie_unsupported = 1;
45 		pcf8563->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
46 		pcf8563->rtc->range_max = RTC_TIMESTAMP_END_2099;
47 		pcf8563->rtc->set_start_time = true;
48
49 		if (client->irq > 0) {
50 			err = devm_request_threaded_irq(&client->dev, client->irq, NULL, pcf8563_irq,
52 			IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
53 			pcf8563_driver.driver.name, client);
54 			if (err) {
55 				dev_err(&client->dev, "unable to request IRQ %d\n", client->irq);
57 				return err;
58 			}
59 		}
60
61 		err = rtc_register_device(pcf8563->rtc);
62 		if (err)
63 			return err;
......
70 		return 0;
71 }

第 13 行,申请内存内存,rtc-pcf8563.c 定义了一个 pcf8563 结构体来描述 PCF8563 芯片,所以这里就是申请一个 pcf8563 实例。
第 23~36 行,初始化 PCF8563。
第 38 行,pcf8563 结构体里有个 rtc 成员变量,此成员变量是个 rtc_device 结构体指针。这个就是 RTC 驱动框架最核心的 rtc_device。 这
里需要对这个 rtc 指针分配内存。
第42行,设置rtc_device的ops成员变量为pcf8563_rtc_ops,pcf8563_rtc_ops包含了PCF8563的具体操作,包括设置时间、读取时间、设置闹钟等。
第 44~47 行,继续初始化 rtc 的其他成员变量。
第 49~59 行,中断初始化,PCF8563 有个中断引脚 INT,因此可以使用中断功能。这里使用 devm_request_threaded_irq 函数完成中断申请,已经初始化,中断函数为 pcf8563_irq。
第 61 行,调用 rtc_register_device 函数向系统注册 rtc_device,也就是 pcf8563。

设备操作函数集pcf8563_rtc_ops:

1 static const struct rtc_class_ops pcf8563_rtc_ops = {
2 		.ioctl = pcf8563_rtc_ioctl,
3 		.read_time = pcf8563_rtc_read_time,
4 		.set_time = pcf8563_rtc_set_time,
5 		.read_alarm = pcf8563_rtc_read_alarm,
6 		.set_alarm = pcf8563_rtc_set_alarm,
7 		.alarm_irq_enable = pcf8563_irq_enable,
8 };

以 read 读取操作为例:

1 static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
2 {
3 		struct i2c_client *client = to_i2c_client(dev);
4 		struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
5 		unsigned char buf[9];
6 		int err;
7 
8		err = pcf8563_read_block_data(client, PCF8563_REG_ST1, 9, buf);
9 		if (err)
10 			return err;
11
12 		if (buf[PCF8563_REG_SC] & PCF8563_SC_LV) {
13 			pcf8563->voltage_low = 1;
14 			dev_err(&client->dev, "low voltage detected, date/time is not reliable.\n");
16 			return -EINVAL;
17 		}
......
28 		tm->tm_sec = bcd2bin(buf[PCF8563_REG_SC] & 0x7F);
29 		tm->tm_min = bcd2bin(buf[PCF8563_REG_MN] & 0x7F);
30 		tm->tm_hour = bcd2bin(buf[PCF8563_REG_HR] & 0x3F);
31 		tm->tm_mday = bcd2bin(buf[PCF8563_REG_DM] & 0x3F);
32		 tm->tm_wday = buf[PCF8563_REG_DW] & 0x07;
33 		tm->tm_mon = bcd2bin(buf[PCF8563_REG_MO] & 0x1F) - 1;34 		tm->tm_year = bcd2bin(buf[PCF8563_REG_YR]) + 100;
35 		/* detect the polarity heuristically. see note above. */
36 		pcf8563->c_polarity = (buf[PCF8563_REG_MO] & PCF8563_MO_C) ? (tm->tm_year >= 100) : (tm->tm_year < 100);
......
45 		return 0;
46 }

第 8 行,使用 pcf8563_read_block_data 函数从 PCF8563_REG_ST1 寄存器(地址为 0X00)开始,连续读取 9 个寄存器的数据。 这样就可以得到 PCF8563 的控制与状态寄存器 1 和 2,以及事件与日期寄存器的值。
第 12 行,判断 PCF8563 的 0X02 寄存器 VL 位是否为 1,也就是检查 PCF8563 是否处于低电压模式,事件和日期是否有效。
第 28~34 行,依次获取 PCF8563 中的时间和日期值,这里使用 bcd2bin 函数将原始的 BCD值转换为时间值。将获取到的时间和日期打包到参数 tm 中,tm 是个 rtc_time 结构体指针变量。
第 36 行,判断 0X07 寄存器的 C 位(bit7)的值,此位为 1 的话表示 20xx 年,为 0 的话就是19xx 年。
其他的RTC驱动也可以仿照此驱动进行开发。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是基于STM32F103系列驱动RTC实时时钟的示例代码: ```c #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_rtc.h" void RTC_Configuration(void); int main(void) { RTC_Configuration(); //初始化RTC while(1) { RTC_TimeTypeDef RTC_TimeStruct; RTC_DateTypeDef RTC_DateStruct; RTC_GetTime(RTC_Format_BIN, &RTC_TimeStruct); //获取当前时间 RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct); //获取当前日期 //在这里进行实时时钟的应用,例如在LCD显示时间和日期等 } } void RTC_Configuration(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 RCC_LSEConfig(RCC_LSE_ON); //打开LSE外部晶振 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); //等待LSE晶振稳定 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //将RTC时钟源改为LSE外部晶振 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 RTC_WaitForSynchro(); //等待RTC寄存器同步 RTC_InitTypeDef RTC_InitStructure; RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; //时间格式为24小时制 RTC_InitStructure.RTC_AsynchPrediv = 0x7F; //RTC异步分频系数为0x7F+1=128 RTC_InitStructure.RTC_SynchPrediv = 0xFF; //RTC同步分频系数为0xFF+1=256 RTC_Init(&RTC_InitStructure); //初始化RTC RTC_TimeTypeDef RTC_TimeStruct; RTC_TimeStruct.RTC_Hours = 0x00; //设置RTC小时数 RTC_TimeStruct.RTC_Minutes = 0x00; //设置RTC分钟数 RTC_TimeStruct.RTC_Seconds = 0x00; //设置RTC秒数 RTC_SetTime(RTC_Format_BIN, &RTC_TimeStruct); //设置RTC时间 RTC_DateTypeDef RTC_DateStruct; RTC_DateStruct.RTC_Year = 0x20; //设置RTC年份 RTC_DateStruct.RTC_Month = RTC_Month_January; //设置RTC月份 RTC_DateStruct.RTC_Date = 0x01; //设置RTC日数 RTC_DateStruct.RTC_WeekDay = RTC_Weekday_Thursday; //设置RTC星期几 RTC_SetDate(RTC_Format_BIN, &RTC_DateStruct); //设置RTC日期 } ``` 这段代码中,我们首先在`main()`函数中调用`RTC_Configuration()`函数,来初始化RTC。然后我们通过`RTC_GetTime()`和`RTC_GetDate()`函数获取当前的时间和日期,并进行实时时钟的应用。在`RTC_Configuration()`函数中,我们首先使能了PWR和BKP外设时钟,然后使能了RTC和后备寄存器访问。接着我们打开了LSE外部晶振,并等待其稳定。然后我们将RTC时钟源改为LSE外部晶振,并使能了RTC时钟。最后我们等待RTC寄存器同步,然后初始化RTC时间和日期。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值