有时候根据需求需要外加时钟芯片,实现掉电保存的功能,linux已经为我们实现了一系列的rtc时钟芯片,所以我们在选择的时候一般就直接选择内核里面已有的芯片。有了rtc后,需要将网络时间同步到rtc里面,目前更多使用htpdate,不适用ntp。
1.驱动添加
rtc芯片一般使用i2c方式连接,很多arm的内部i2c总是会有时序不稳定的情况,所以会使用gpio模拟i2c的形式,linux内部也已经支持该部分。
1.1、内核driver—》CONFIG
内核需要开启两个驱动
i2c选项
CONFIG_PACKAGE_kmod-i2c-core=y
CONFIG_PACKAGE_kmod-i2c-algo-bit=y
CONFIG_PACKAGE_kmod-i2c-gpio=y
rtc相关
CONFIG_RTC_DRV_PCF85063=y
1.2、内核platform—》dts/board_info
platform一般两种方式,dts和board_info,目前主流的就是只用dts的方式
1.2.1 dts方式
dtsi里面添加宏
i2c: i2c@0 {
compatible = "i2c-gpio"; ---》使用gpio模拟i2c的方式
gpios = <&pio 14 GPIO_ACTIVE_HIGH>, --》指定好gpio
<&pio 15 GPIO_ACTIVE_HIGH>;
i2c-gpio,delay-us = <3>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
dts里面开启宏,设置好pinctrl和添加rtc
&i2c {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
pcf85063: rtc@51 {
status = "okay";
compatible = "nxp,pcf85063";
reg = <0x51>;
};
};
很多gpio是复用功能,所以需要在pinctrl里面将function设置成gpio,这个group的名称需要去看pinctrl里面对应芯片的定义。
&pio {
i2c0_pins: i2c0-pins {
mux {
function = "gpio";
group = "i2c0";
};
};
}
1.2.2 board_info方式
需要在/arch/mips/mtk/mt7621.c里面注册该i2c设备的i2c_board_info和i2c设备注册platform_device_register。
#define MT7621_GPIO_I2C_SDA 3
#define MT7621_GPIO_I2C_SCL 4
static struct i2c_gpio_platform_data mt7621_i2c_gpio_data = {
.sda_pin = MT7621_GPIO_I2C_SDA,
.scl_pin = MT7621_GPIO_I2C_SCL,
};
static struct platform_device mt7621_i2c_gpio_device = {
.name = "i2c-gpio",
.id = 0,
.dev = {
.platform_data = &mt7621_i2c_gpio_data,
}
};
static struct i2c_board_info mt7621_i2c_board_info[] __initdata = {
{
I2C_BOARD_INFO("pcf85063", 0x51),
},
};
void mt7621_common_init(void)
{
u32 gpio_mode;
gpio_mode = rt_sysc_r32(SYSC_REG_GPIO_MDOE);
/*
* i2c gpio to gpio mode; use i2c-gpio driver
*/
gpio_mode |= MT7621_GPIO_I2C_MODE;
gpio_mode &= ~MT7621_GPIO_WDT_MODE_MASK;
gpio_mode |= MT7621_GPIO_WDT_MODE;
gpio_mode &= ~MT7621_GPIO_UART2_MODE_MASK;
gpio_mode |= MT7621_GPIO_UART2_MODE;
gpio_mode &= ~MT7621_GPIO_UART3_MODE_MASK;
gpio_mode |= MT7621_GPIO_UART3_MODE;
gpio_mode |= MT7621_GPIO_JTAG_GPIO_MODE;
mt7621_gpio_init(gpio_mode);
i2c_register_board_info(0, mt7621_i2c_board_info,
ARRAY_SIZE(mt7621_i2c_board_info));
platform_device_register(&mt7621_i2c_gpio_device);
}
i2c_board_info里面将设备的型号和地址传进去,这个在/drivers/i2c/i2c-boardinfo.c里面使用到。
platform_device_register接口里面将I2C的GPIO脚,和要probe的i2c-gpio名字传进去,这个在/drivers/i2c/busses/i2c-gpio.c里面会用到。
内核调试打印流程
[ 3.560000] bus: 'platform': add driver rtc_cmos
[ 3.560000] bus: 'platform': remove driver rtc_cmos
[ 3.560000] driver: 'rtc_cmos': driver_release
[ 3.560000] bus: 'i2c': add driver rtc-ds3232
[ 3.560000] bus: 'i2c': driver_probe_device: matched device 0-0068 with driver rtc-ds3232
[ 3.560000] bus: 'i2c': really_probe: probing driver rtc-ds3232 with device 0-0068
[ 3.570000] device: 'rtc0': device_add
[ 3.570000] rtc-ds3232 0-0068: rtc core: registered ds3232 as rtc0
[ 3.580000] driver: '0-0068': driver_bound: bound to device 'rtc-ds3232'
[ 3.580000] bus: 'i2c': really_probe: bound device 0-0068 to driver rtc-ds3232
[ 3.580000] i2c /dev entries driver
[ 3.590000] device class 'i2c-dev': registering
[ 3.590000] device: 'i2c-0': device_add
2.i2c应用调试
只要上面的驱动配置都正常,i2c设备的调试先要有i2c适配器设备,即在/dev/下有i2c设备
root@openwrt:/dev# ls /dev/i2c-0
/dev/i2c-0
有了i2c驱动之后,就可以使用busybox提供的各种i2c工具直接测试i2c是否正常。
2.1、检测到adapter
使用i2c-detect工具可以检测到adapter
root@openwrt:/dev# i2cdetect -l
i2c-0 i2c 1e000000.palmbus:i2c@0 I2C adapter
2.2、寻找设备
root@openwrt:/dev# i2cdetect -r -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
2.3、读取全部寄存器值
root@openwrt:/# i2cdump -f -y 0 0x51
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
10: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
20: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
30: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
40: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
50: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
60: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
70: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
80: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
90: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
a0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
b0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
c0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
d0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??..............
e0: 00 05 00 00 21 14 08 26 03 10 22 80 80 80 80 80 .?..!??&??"?????
f0: b7 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ??.............
2.4、读取单个寄存器值
root@openwrt:/# i2cget -fy 0 0x51 0x07
0x26
root@openwrt:/# i2cget -fy 0 0x51 0x09
0x10
http://www.atmcu.com/2341.html
2.5、设置寄存器值
root@openwrt:/# i2cset -fy 0 0x51 0x12 0x04 b
3. “系统时间”与“硬件时间”
系统时间: 一般说来就是我们执行 date 命令看到的时间,linux系统下所有的时间调用(除了直接访问硬件时间的命令)都是使用的这个时间。
root@openwrt:/# date
Tue May 21 15:05:26 CST 2019
时间设置
date -s "2020-03-23 11:22:50"
settimeofday
硬件时间: 外部RTC时钟,由电池供电来维持运行,所以适配器掉电了时间也可以正常运行
root@openwrt:/# hwclock
Tue May 21 15:06:53 2019 0.000000 seconds
- -r, --show 读取并打印硬件时钟(read hardware clock and print result)
- -s, --hctosys 将硬件时钟同步到系统时钟(set the system time from the hardware clock)
- -w, --systohc 将系统时钟同步到硬件时钟(set the hardware clock to the current system time)
drivers/rtc/下面有很多的时钟芯片驱动,一般用的都是Maxim/Dallas的I2C芯片,所以我们只需要添加i2c驱动即可。
原理如下
hwclock -w
-> xioctl(RTC_SET_TIME);
-> rtc_dev_ioctl()
-> rtc_set_time()
hwclock是busybox下面的一个程序,内部会调用xioctl函数,改函数会对驱动设备进行写数据。
busybox-1.22.1$ vim ./libbb/rtc.c
int FAST_FUNC rtc_xopen(const char **default_rtc, int flags)
{
int rtc;
if (!*default_rtc) {
*default_rtc = "/dev/rtc";
rtc = open(*default_rtc, flags);
if (rtc >= 0)
return rtc;
*default_rtc = "/dev/rtc0";
rtc = open(*default_rtc, flags);
if (rtc >= 0)
return rtc;
*default_rtc = "/dev/misc/rtc";
}
return xopen(*default_rtc, flags);
}
rtc驱动里面会初始化字符设备信息
cdev_init(&rtc->char_dev, &rtc_dev_fops);
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,
};
4.互联网时间htpdate
4.1 htpdate逻辑
htpdate启动参数
参数
| 含义
|
-l
| |
-s
| 立即设置时间
|
-t
| |
-d
| 开启日志
|
-D
| 后台运行
|
-m
| 请求失败间隔时间
|
-M
| |
将htpdata的日志打开-d
htpdate启动脚本位于/etc/init.d/htpdate
root@openwrt:/# cat /etc/init.d/htpdate
#!/bin/sh /etc/rc.common
# Copyright (C) 2006 OpenWrt.org
START=10
STOP=91
BIN=htpdate
DEFAULT=/etc/default/$BIN
RUN_D=/var/run
PID_F=$RUN_D/$BIN.pid
EXTRA_COMMANDS='save'
start() {
local disabled
config_load htpdate
config_get_bool disabled htpdate disabled 0
htpdate_stop
[ "$disabled" -gt 0 ] || {
[ -f $DEFAULT ] && . $DEFAULT
mkdir -p $RUN_D
$BIN -l -s -t -m 300 -M 600 -D $OPTIONS
}
}
htpdate_stop() {
[ -f $PID_F ] && {
kill -9 $(cat $PID_F)
rm -rf $PID_F
}
}
stop() {
htpdate_stop
}
域名配置文件
cat /etc/default/htpdate
OPTIONS="www.baidu.com www.taobao.com www.jd.com www.youku.com"
启动后进程内容如下:
htpdate -l -s -t -d -m 300 -M 600 -D www.baidu.com www.taobao.com www.jd.com www.youku.com
打开debug日志后,logread内容大概如下
Tue Oct 25 16:00:56 2022 user.info : htpdate version 1.1.1 started
Tue Oct 25 16:00:56 2022 user.info : burst: 1 try: 1 when: 200000
Tue Oct 25 16:00:57 2022 user.info : www.baidu.com 25 Oct 2022 08:00:57 GMT (0.055) => 0
Tue Oct 25 16:02:12 2022 user.info : burst: 1 try: 1 when: 400000
Tue Oct 25 16:02:12 2022 user.info : www.taobao.com 25 Oct 2022 08:02:13 GMT (0.048) => 1
Tue Oct 25 16:02:12 2022 user.info : burst: 1 try: 2 when: 400000
Tue Oct 25 16:02:13 2022 user.info : www.taobao.com 25 Oct 2022 08:02:14 GMT (0.047) => 1
Tue Oct 25 16:02:13 2022 user.info : burst: 1 try: 1 when: 600000
Tue Oct 25 16:02:13 2022 user.info : www.jd.com 25 Oct 2022 08:02:14 GMT (0.053) => 1
Tue Oct 25 16:02:13 2022 user.info : burst: 1 try: 2 when: 600000
Tue Oct 25 16:02:14 2022 user.info : www.jd.com 25 Oct 2022 08:02:15 GMT (0.054) => 1
Tue Oct 25 16:02:14 2022 user.info : burst: 1 try: 1 when: 800000
Tue Oct 25 16:02:14 2022 user.info : www.youku.com 25 Oct 2022 08:02:15 GMT (0.057) => 1
Tue Oct 25 16:02:14 2022 user.info : burst: 1 try: 2 when: 800000
Tue Oct 25 16:02:15 2022 user.info : www.youku.com 25 Oct 2022 08:02:16 GMT (0.059) => 1
Tue Oct 25 16:02:15 2022 user.info : #: 4 mean: 1 average: 0.750
Tue Oct 25 16:02:15 2022 user.info : Timezone: GMT+8 (CST,)
Tue Oct 25 16:02:15 2022 user.info : Setting 0.750 seconds
Tue Oct 25 16:02:16 2022 user.info : Set: Tue Oct 25 16:02:16 2022
htpdate逻辑1:默认如果时间同步成功后,会等待30min后再次请求
/* Sleep for 30 minutes after a time adjust or set */
sleep( DEFAULT_MIN_SLEEP );
30分钟后再次请求:
Tue Oct 25 16:32:16 2022 user.info : burst: 1 try: 1 when: 200000
Tue Oct 25 16:32:16 2022 user.info : www.baidu.com 25 Oct 2022 08:32:16 GMT (0.053) => 0
Tue Oct 25 16:33:31 2022 user.info : burst: 1 try: 1 when: 400000
Tue Oct 25 16:33:31 2022 user.info : www.taobao.com 25 Oct 2022 08:33:31 GMT (0.046) => 0
Tue Oct 25 16:34:46 2022 user.info : burst: 1 try: 1 when: 600000
Tue Oct 25 16:34:46 2022 user.info : www.jd.com 25 Oct 2022 08:34:47 GMT (0.054) => 1
Tue Oct 25 16:34:46 2022 user.info : burst: 1 try: 2 when: 600000
Tue Oct 25 16:34:47 2022 user.info : www.jd.com 25 Oct 2022 08:34:48 GMT (0.053) => 1
Tue Oct 25 16:34:47 2022 user.info : burst: 1 try: 1 when: 800000
Tue Oct 25 16:34:47 2022 user.info : www.youku.com 25 Oct 2022 08:34:48 GMT (0.056) => 1
Tue Oct 25 16:34:47 2022 user.info : burst: 1 try: 2 when: 800000
Tue Oct 25 16:34:48 2022 user.info : www.youku.com 25 Oct 2022 08:34:49 GMT (0.056) => 1
Tue Oct 25 16:34:48 2022 user.info : #: 4 mean: 1 average: 0.500
Tue Oct 25 16:34:48 2022 user.info : Timezone: GMT+8 (CST,)
Tue Oct 25 16:34:48 2022 user.info : Adjusting 0.500 seconds
Tue Oct 25 16:34:48 2022 user.info : Drift 256.15 PPM, 22.13 s/day
htpdate逻辑2:就算入参有-s立即设置时间,但是这个也只生效一次,第二次就变成adjust time了
/* After first poll cycle do not step through time, only adjust */
if ( setmode != 3 ) {
setmode = 1;
}
设置时间用asctime()
函数,调整时间用adjtime()
函数,还有一个调整内核时间adjtimex()
函数
4.2 htp时间同步到rtc
修改htpdate的源码,在htpdate更新时间的位置,添加调用时间同步到rtc脚本,如下:
static void htpdate_save() {
system("/etc/init.d/htpdate save 0");
}
脚本内如也位于/etc/init.d/htpdate,内容如下:
save()
{
local local_time=$(date '+%s')
date -k
# set to rtc
hwclock -w
uci set htpdate.htpdate.sync_time=$local_time
uci commit htpdate
if [ "$1" != "1" ]; then
# call hotplug
time-hotplug sync
fi
}
完成htp时间同步到rtc中的实现。