title: "Linux RTC与PWM驱动开发实战" tags:
RTC(Real Time Clock,实时时钟)是一种专用外设,用于在系统断电后继续维持时间计数。其核心是一个低功耗计数器,通常以秒为单位递增。
┌─────────────────────────────────────────────────┐
│ RTC 工作原理 │
├─────────────────────────────────────────────────┤
│ │
│ 晶振(32.768kHz) ──→ 分频器 ──→ 秒计数器 │
│ │
│ 秒计数器 ──→ 秒/分/时/日/月/年 寄存器 │
│ │
│ CPU ──→ 总线接口 ──→ 读/写时间寄存器 │
│ │
│ 电池(VBAT) ──→ 持续供电(断电不丢失) │
│ │
└─────────────────────────────────────────────────┘
RTC的典型供电方式:
| 供电模式 | 说明 |
|---|---|
| VBAT引脚 | 外接纽扣电池(CR2032),系统断电后维持RTC运行 |
| 内部电源切换 | SOC内置电源切换电路,主电源存在时充电,断电时自动切换到电池 |
| 超低功耗 | 典型功耗<1μA,一颗CR2032可维持数年 |
RTC硬件通常以秒为单位计数(Unix时间戳),需要转换为人类可读时间:
// Unix时间戳 → rtc_time结构体
// 时间戳: 从1970-01-01 00:00:00 UTC起的秒数
struct rtc_time {
int tm_sec; // 秒 [0, 59]
int tm_min; // 分 [0, 59]
int tm_hour; // 时 [0, 23]
int tm_mday; // 日 [1, 31]
int tm_mon; // 月 [0, 11] 注意:0=一月
int tm_year; // 年 [从1900起算]
int tm_wday; // 星期 [0, 6] 0=周日
int tm_yday; // 一年中的第几天 [0, 365]
int tm_isdst; // 夏令时标志
};
转换函数:rtc_time_to_tm(unsigned long time, struct rtc_time *tm) 和 rtc_tm_to_time(struct rtc_time *tm, unsigned long *time)。
┌──────────────────────────────────────────────────────┐
│ 应用层 │
│ open() / read() / ioctl() / close() │
├──────────────────────────────────────────────────────┤
│ VFS层 │
│ /dev/rtc0 字符设备 │
├──────────────────────────────────────────────────────┤
│ RTC通用驱动层 (rtc-dev.c) │
│ rtc_dev_fops → rtc_dev_ioctl → rtc_read_time │
│ → rtc_set_time → rtc_read_alarm │
├──────────────────────────────────────────────────────┤
│ RTC核心层 (rtc-core.c) │
│ rtc_device_register() / rtc_device_unregister() │
├──────────────────────────────────────────────────────┤
│ RTC设备驱动层 │
│ rtc_class_ops: read_time / set_time / ... │
├──────────────────────────────────────────────────────┤
│ RTC硬件 │
│ I.MX6ULL SNVS-RTC / DS1302 / RX8025T 等 │
└──────────────────────────────────────────────────────┘
驱动开发者需要实现的核心操作集,定义在include/linux/rtc.h:
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); // 64位秒数设置
int (*set_mmss)(struct device *, unsigned long secs);
int (*read_callback)(struct device *, int data);
int (*alarm_irq_enable)(struct device *, unsigned int enabled); // 闹钟中断使能
};
关键回调函数说明:
| 回调函数 | 功能 | 必须实现 |
|---|---|---|
read_time |
从硬件读取当前时间 | 是 |
set_time |
向硬件写入新时间 | 是 |
read_alarm |
读取闹钟值 | 可选 |
set_alarm |
设置闹钟值 | 可选 |
alarm_irq_enable |
使能/关闭闹钟中断 | 可选 |
// RTC设备结构体 (include/linux/rtc.h)
struct rtc_device {
struct device dev; // 设备
struct module *owner;
int id; // ID
char name[RTC_DEVICE_NAME_SIZE]; // 名字
const struct rtc_class_ops *ops; // RTC底层操作函数
struct mutex ops_lock;
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;
struct rtc_task *irq_task;
spinlock_t irq_task_lock;
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;
int uie_unsupported;
};
Linux内核在drivers/rtc/rtc-dev.c中提供了所有RTC设备共用的字符设备操作集:
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,
};
// 注册RTC设备
struct rtc_device *rtc_device_register(const char *name,
struct device *dev,
const struct rtc_class_ops *ops,
struct module *owner);
// 注销RTC设备
void rtc_device_unregister(struct rtc_device *rtc);
// 设备管理版本(推荐,自动释放)
struct rtc_device *devm_rtc_device_register(struct device *dev,
const char *name,
const struct rtc_class_ops *ops,
struct module *owner);
void devm_rtc_device_unregister(struct device *dev, struct rtc_device *rtc);
RTC设备在设备树中通常作为平台设备节点存在,关键属性:
| 属性 | 说明 |
|---|---|
compatible |
匹配字符串,用于匹配驱动 |
reg |
寄存器地址和长度 |
interrupts |
中断号和触发方式 |
clocks |
时钟源 |
I.MX6ULL内部RTC(SNVS-RTC)在imx6ull.dtsi中的定义:
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" |
匹配驱动rtc-snvs.c |
regmap = <&snvs> |
引用SNVS系统控制regmap |
offset = <0x34> |
SNVS中RTC寄存器偏移 |
interrupts |
两个中断:秒中断(19)和闹钟中断(20) |
ds1302: rtc@68 {
compatible = "maxim,ds1302";
reg = <0x68>;
#clock-cells = <0>;
};
驱动文件:drivers/rtc/rtc-snvs.c
// 设备树ID表
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,
};
module_platform_driver(snvs_rtc_driver);
static int snvs_rtc_probe(struct platform_device *pdev)
{
struct snvs_rtc_data *data;
struct resource *res;
int ret;
void __iomem *mmio;
// 1. 申请并初始化snvs_rtc_data结构体
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 2. 获取regmap(用于寄存器读写)
data->regmap =
syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
if (IS_ERR(data->regmap)) {
// 旧DTS兼容:直接映射寄存器
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mmio = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mmio))
return PTR_ERR(mmio);
data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio,
&snvs_rtc_config);
} else {
// 新DTS:使用regmap偏移
data->offset = SNVS_LPREGISTER_OFFSET;
of_property_read_u32(pdev->dev.of_node, "offset",
&data->offset);
}
if (!data->regmap) {
dev_err(&pdev->dev, "Can't find snvs syscon\n");
return -ENODEV;
}
// 3. 获取中断号
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0)
return data->irq;
platform_set_drvdata(pdev, data);
// 4. 初始化毛刺检测寄存器
regmap_write(data->regmap, data->offset + SNVS_LPPGDR,
SNVS_LPPGDR_INIT);
// 5. 清除中断状态
regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff);
// 6. 使能RTC
snvs_rtc_enable(data, true);
// 7. 使能唤醒功能
device_init_wakeup(&pdev->dev, true);
// 8. 注册中断处理函数
ret = devm_request_irq(&pdev->dev, data->irq,
snvs_rtc_irq_handler,
IRQF_SHARED, "rtc alarm", &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to request irq %d: %d\n",
data->irq, ret);
goto error_rtc_device_register;
}
// 9. 注册RTC设备
data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
&snvs_rtc_ops, THIS_MODULE);
if (IS_ERR(data->rtc)) {
ret = PTR_ERR(data->rtc);
dev_err(&pdev->dev, "failed to register rtc: %d\n", ret);
goto error_rtc_device_register;
}
return 0;
error_rtc_device_register:
if (data->clk)
clk_disable_unprepare(data->clk);
return ret;
}
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,
};
static int snvs_rtc_read_time(struct device *dev,
struct rtc_time *tm)
{
struct snvs_rtc_data *data = dev_get_drvdata(dev);
unsigned long time = rtc_read_lp_counter(data);
rtc_time_to_tm(time, tm);
return 0;
}
static u32 rtc_read_lp_counter(struct snvs_rtc_data *data)
{
u64 read1, read2;
u32 val;
do {
// 读取高32位
regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
read1 = val;
read1 <<= 32;
// 读取低32位
regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
read1 |= val;
// 再次读取,用于校验
regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
read2 = val;
read2 <<= 32;
regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val);
read2 |= val;
} while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH));
// 将47位计数器转换为32位秒数
return (u32)(read1 >> CNTR_TO_SECS_SH);
}
读取策略说明:由于RTC是47位计数器,需要分两次读取高低32位。为防止读取过程中计数器更新导致高低位不匹配,采用连续读取两次并比较秒位的策略。
# 查看RTC设备信息
dmesg | grep rtc
# 输出: snvs-rtc-lp supply vsnvs not found, using dummy regulator
# snvs-rtc-lp rtc0: setting system clock to 1970-01-01 00:06:11 UTC (371)
# 查看系统时间
date
# 输出: Wed Jan 1 00:06:11 UTC 1970
# 设置系统时间(仅写入内存,重启丢失)
date -s "2025-01-15 14:30:00"
# 验证
date
# 输出: Wed Jan 15 14:30:00 CST 2025
# 将系统时间写入RTC(断电不丢失)
hwclock -w
# 从RTC读取时间
hwclock -r
# 输出: 2025-01-15 14:30:xx.123456+08:00
# 从RTC设置系统时间
hwclock -s
# 1. 设置正确时间
date -s "2025-01-15 14:30:00"
# 2. 同步到RTC硬件
hwclock -w
# 3. 重启验证
reboot
# 4. 启动后查看时间是否保持
date
# 如果电池正常,时间应保持为设置值
# 查看RTC设备列表
ls /sys/class/rtc/
# 输出: rtc0
# 查看RTC详细信息
cat /sys/class/rtc/rtc0/name
# 输出: snvs-rtc-lp
cat /sys/class/rtc/rtc0/hctosys
# 输出: 1 (表示已作为系统时钟源)
cat /sys/class/rtc/rtc0/since_epoch
# 输出: 秒数(从1970-01-01起)
PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过数字信号模拟模拟信号的技术。
┌──────────────────────────────────────────────────────┐
│ PWM 波形示意 │
├──────────────────────────────────────────────────────┤
│ │
│ 周期 T = 1/f │
│ ←─────────────────→ │
│ │
│ 高电平 ┌────────┐ ┌────────┐ │
│ │ │ │ │ │
│ ─────────┘ └──────┘ └────── │
│ │
│ ←── Ton ──→ │
│ │
│ 占空比 D = Ton / T × 100% │
│ │
└──────────────────────────────────────────────────────┘
| 参数 | 定义 | 公式 |
|---|---|---|
| 周期 T | 一个完整PWM波形的时间 | T = 1/f |
| 频率 f | 每秒脉冲数 | f = 1/T |
| 占空比 D | 高电平时间占周期的比例 | D = Ton / T × 100% |
| 分辨率 | 占空比的最小调节步进 | 取决于计数器位数 |
| 应用 | 说明 |
|---|---|
| LED调光 | 改变占空比调节亮度 |
| 电机调速 | 控制直流电机转速 |
| 舵机控制 | 位置伺服(50Hz, 0.5-2.5ms脉宽) |
| LCD背光 | 调节屏幕亮度 |
| 音频输出 | 蜂鸣器发声 |
| 电源转换 | 开关电源PWM控制 |
┌──────────────────────────────────────────────────────┐
│ 应用层 │
│ sysfs: /sys/class/pwm/pwmchipX/pwmY/ │
│ ioctl: PWM_REQUEST / PWM_CONFIG / PWM_ENABLE │
├──────────────────────────────────────────────────────┤
│ PWM核心层 (drivers/pwm/core.c) │
│ pwm_request() / pwm_config() / pwm_enable() │
│ pwmchip_add() / pwmchip_remove() │
├──────────────────────────────────────────────────────┤
│ PWM设备驱动层 │
│ pwm_ops: request / free / config / enable / disable│
├──────────────────────────────────────────────────────┤
│ PWM硬件控制器 │
│ I.MX6ULL PWM1~PWM8 / STM32 TIM / RK3568 PWM │
└──────────────────────────────────────────────────────┘
PWM子系统的核心结构,定义在include/linux/pwm.h:
struct pwm_chip {
struct device *dev; // 设备
struct list_head list; // 链表
const struct pwm_ops *ops; // PWM操作函数集
int base; // PWM通道基编号
unsigned int npwm; // PWM通道数量
struct pwm_device *pwms; // PWM设备数组
struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;
bool can_sleep; // 是否可以睡眠
};
struct pwm_ops {
int (*request)(struct pwm_chip *chip, // 请求PWM
struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip, // 释放PWM
struct pwm_device *pwm);
int (*config)(struct pwm_chip *chip, // 配置周期和占空比
struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip, // 设置极性
struct pwm_device *pwm,
enum pwm_polarity polarity);
int (*enable)(struct pwm_chip *chip, // 使能PWM
struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip, // 关闭PWM
struct pwm_device *pwm);
struct module *owner;
};
// 注册/注销PWM芯片
int pwmchip_add(struct pwm_chip *chip);
int pwmchip_remove(struct pwm_chip *chip);
// PWM操作API
struct pwm_device *pwm_request(int pwm_num, const char *label);
void pwm_free(struct pwm_device *pwm);
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
int pwm_enable(struct pwm_device *pwm);
void pwm_disable(struct pwm_device *pwm);
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);
pwm3: pwm@02088000 {
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
reg = <0x02088000 0x4000>;
interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_PWM3>,
<&clks IMX6UL_CLK_PWM3>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
};
#pwm-cells = <2> 表示引用此PWM控制器时需要2个参数:
| 参数 | 说明 |
|---|---|
| 第1个 | PWM通道索引(I.MX6ULL只有1个通道,填0) |
| 第2个 | PWM标志位(极性等) |
引用示例:
// 引用pwm1,通道0,周期5000000ns(5ms, 200Hz)
pwms = <&pwm1 0 5000000>;
// 在iomuxc节点下添加引脚定义
pinctrl_pwm3: pwm3grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
>;
};
// 在pwm3节点中引用
&pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
clocks = <&clks IMX6UL_CLK_PWM3>,
<&clks IMX6UL_CLK_PWM3>;
status = "okay";
};
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>; // PWM1, 通道0, 200Hz
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <7>; // 默认最大亮度
status = "okay";
};
属性解析:
| 属性 | 说明 |
|---|---|
compatible |
必须为"pwm-backlight" |
pwms |
指定PWM通道和周期 |
brightness-levels |
背光等级数组(0~255) |
default-brightness-level |
默认等级索引 |
驱动文件:drivers/pwm/pwm-imx.c
// 设备树ID表
static const struct of_device_id imx_pwm_dt_ids[] = {
{ .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
{ .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
{ /* sentinel */ }
};
// 平台驱动结构体
static struct platform_driver imx_pwm_driver = {
.driver = {
.name = "imx-pwm",
.of_match_table = imx_pwm_dt_ids,
},
.probe = imx_pwm_probe,
.remove = imx_pwm_remove,
};
module_platform_driver(imx_pwm_driver);
// v2版本(I.MX6ULL使用)
static struct imx_pwm_data imx_pwm_data_v2 = {
.config = imx_pwm_config_v2,
.set_enable = imx_pwm_set_enable_v2,
};
static int imx_pwm_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(imx_pwm_dt_ids, &pdev->dev);
const struct imx_pwm_data *data;
struct imx_chip *imx;
struct resource *r;
int ret = 0;
if (!of_id)
return -ENODEV;
// 1. 申请imx_chip结构体内存
imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
if (imx == NULL)
return -ENOMEM;
// 2. 初始化pwm_chip
imx->chip.ops = &imx_pwm_ops;
imx->chip.dev = &pdev->dev;
imx->chip.base = -1;
imx->chip.npwm = 1; // I.MX6ULL每个控制器1个通道
imx->chip.can_sleep = true;
// 3. 获取并映射PWM寄存器基地址
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
if (IS_ERR(imx->mmio_base))
return PTR_ERR(imx->mmio_base);
// 4. 获取版本特定的操作函数
data = of_id->data;
imx->config = data->config;
imx->set_enable = data->set_enable;
// 5. 向内核注册PWM芯片
ret = pwmchip_add(&imx->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, imx);
return 0;
}
static struct pwm_ops imx_pwm_ops = {
.enable = imx_pwm_enable,
.disable = imx_pwm_disable,
.config = imx_pwm_config,
.owner = THIS_MODULE,
};
static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
{
struct imx_chip *imx = to_imx_chip(chip);
u32 val;
// 读取PWMCR寄存器
val = readl(imx->mmio_base + MX3_PWMCR);
if (enable)
val |= MX3_PWMCR_EN; // bit0置1,使能PWM
else
val &= ~MX3_PWMCR_EN; // bit0清0,关闭PWM
// 写回PWMCR寄存器
writel(val, imx->mmio_base + MX3_PWMCR);
}
static int imx_pwm_config_v2(struct pwm_chip *chip,
struct pwm_device *pwm,
int duty_ns, int period_ns)
{
struct imx_chip *imx = to_imx_chip(chip);
struct device *dev = chip->dev;
unsigned long long c;
unsigned long period_cycles, duty_cycles, prescale;
bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
u32 cr;
// 1. 计算周期计数值
c = clk_get_rate(imx->clk_per); // 获取外设时钟频率
c = c * period_ns; // 时钟周期数 = 频率 × 周期(ns)
do_div(c, 1000000000); // 转换为计数值
period_cycles = c;
// 2. 计算预分频值
prescale = period_cycles / 0x10000 + 1;
// 3. 计算占空比计数值
period_cycles /= prescale;
c = (unsigned long long)period_cycles * duty_ns;
do_div(c, period_ns);
duty_cycles = c;
// 4. 周期值减2(硬件要求)
if (period_cycles > 2)
period_cycles -= 2;
else
period_cycles = 0;
// 5. 写入寄存器
writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); // 占空比
writel(period_cycles, imx->mmio_base + MX3_PWMPR); // 周期
// 6. 配置PWMCR寄存器
cr = MX3_PWMCR_PRESCALER(prescale) |
MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
if (enable)
cr |= MX3_PWMCR_EN;
writel(cr, imx->mmio_base + MX3_PWMCR);
return 0;
}
计算流程:
period_cycles = clk_rate × period_ns / 1e9
prescale = period_cycles / 65536 + 1
period_cycles = period_cycles / prescale
duty_cycles = period_cycles × duty_ns / period_ns
最终写入: PWMSAR = duty_cycles, PWMPR = period_cycles - 2
Device Drivers
-> Pulse-Width Modulation (PWM) Support
-> <*> i.MX PWM support
# 1. 查看可用的PWM芯片
ls /sys/class/pwm/
# 输出: pwmchip0 pwmchip1 pwmchip2 ... pwmchip7
# pwmchip2 对应 PWM3
# 2. 导出PWM通道
echo 0 > /sys/class/pwm/pwmchip2/export
# 导出后生成 pwm0 子目录
# 3. 设置周期(频率20KHz = 周期50000ns)
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period
# 4. 设置占空比(10000ns = 20%占空比)
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
# 5. 使能PWM
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
# 6. 查看当前配置
cat /sys/class/pwm/pwmchip2/pwm0/period
cat /sys/class/pwm/pwmchip2/pwm0/duty_cycle
cat /sys/class/pwm/pwmchip2/pwm0/enable
# 7. 关闭PWM
echo 0 > /sys/class/pwm/pwmchip2/pwm0/enable
# 8. 取消导出
echo 0 > /sys/class/pwm/pwmchip2/unexport
使用示波器连接GPIO1_IO04引脚,验证输出波形:
| 设置值 | 预期波形 |
|---|---|
| period=50000ns, duty_cycle=10000ns | 20KHz, 20%占空比 |
| period=50000ns, duty_cycle=25000ns | 20KHz, 50%占空比 |
| period=100000ns, duty_cycle=50000ns | 10KHz, 50%占空比 |
#!/bin/sh
# PWM测试脚本 - 验证不同频率和占空比
PWM_PATH="/sys/class/pwm/pwmchip2"
# 导出PWM
echo 0 > ${PWM_PATH}/export
sleep 1
# 测试1: 20KHz, 50%占空比
echo 50000 > ${PWM_PATH}/pwm0/period
echo 25000 > ${PWM_PATH}/pwm0/duty_cycle
echo 1 > ${PWM_PATH}/pwm0/enable
echo "Test 1: 20KHz, 50% - check scope"
sleep 3
# 测试2: 1KHz, 30%占空比
echo 1000000 > ${PWM_PATH}/pwm0/period
echo 300000 > ${PWM_PATH}/pwm0/duty_cycle
echo "Test 2: 1KHz, 30% - check scope"
sleep 3
# 测试3: 10KHz, 80%占空比
echo 100000 > ${PWM_PATH}/pwm0/period
echo 80000 > ${PWM_PATH}/pwm0/duty_cycle
echo "Test 3: 10KHz, 80% - check scope"
sleep 3
# 清理
echo 0 > ${PWM_PATH}/pwm0/enable
echo 0 > ${PWM_PATH}/unexport
echo "Tests complete"
| 特性 | I.MX6ULL (SNVS-RTC) | STM32F103 (RTC) | RK3568 (RTC) |
|---|---|---|---|
| RTC位置 | SNVS域,独立供电 | 备份域(BDR) | 独立PMU域 |
| 电池接口 | 内置电源切换 | VBAT引脚 | VBAT引脚 |
| 计数精度 | 32.768kHz晶振 | 32.768kHz晶振 | 32.768kHz晶振 |
| 驱动文件 | rtc-snvs.c |
rtc-stm32.c |
rtc-rk808.c |
| compatible | fsl,sec-v4.0-mon-rtc-lp |
st,stm32-rtc |
rockchip,rk808-rtc |
| 特殊机制 | 47位计数器,两次读取校验 | 预分频器 | 外部PMIC RTC |
| 寄存器访问 | regmap机制 | 直接MMIO | I2C/SPI总线 |
| 特性 | I.MX6ULL | STM32F103 | RK3568 |
|---|---|---|---|
| PWM数量 | 8路 | 多路(TIM) | 多路 |
| 驱动文件 | pwm-imx.c |
pwm-stm32.c |
pwm-rk.c |
| compatible | fsl,imx6ul-pwm |
st,stm32-pwm |
rockchip,rk3568-pwm |
| 最小分辨率 | 取决于时钟源 | 取决于ARR | 取决于时钟源 |
| 配置方式 | sysfs + API | sysfs + API | sysfs + API |
| 特殊功能 | 背光控制 | 高级定时器 | 多路独立控制 |
三个平台的RTC和PWM驱动都遵循相同的Linux子系统框架:
compatible属性匹配驱动platform_driver + probe函数rtc_class_ops和file_operations的关系是什么?答案:file_operations是字符设备的通用操作集,提供给用户空间的open/read/write/ioctl接口。rtc_class_ops是RTC硬件的底层操作集,包括read_time/set_time等。
RTC驱动采用两层封装:用户空间调用ioctl(RTC_RD_TIME) → rtc-dev.c中的rtc_dev_ioctl → 核心层rtc_read_time → 驱动的rtc_class_ops.read_time。这种设计使得所有RTC设备共享同一套用户空间接口,驱动只需实现硬件操作。
答案:SNVS-RTC是47位计数器,需要分两次读取高低32位寄存器(LPSRTCMR和LPSRTCLR)。由于总线读取存在延迟,读取高位后到读取低位之间,低位可能已经进位(例如从0x0000FFFF变为0x00010000),导致高低位不匹配。
解决方案是连续读取两次,比较秒位(>> CNTR_TO_SECS_SH后的值)是否一致。如果两次秒值相同,说明读取期间没有发生进位,数据有效。
#pwm-cells属性的含义是什么?如何在设备树中引用PWM?答案:#pwm-cells定义了引用PWM控制器时需要的参数个数。例如#pwm-cells = <2>表示需要2个参数:第1个是通道索引,第2个是标志位。
引用示例:pwms = <&pwm1 0 5000000> 表示使用pwm1控制器的通道0,周期为5000000ns。核心层会根据#pwm-cells解析参数,调用驱动的of_xlate回调转换为具体的PWM设备。
答案:RTC通过VBAT引脚连接纽扣电池(如CR2032),主电源断电时自动切换到电池供电。SNVS-RTC功耗极低(<1μA),一颗CR2032可维持数年。
如果电池没电,RTC计数器停止工作,时间丢失。重启后RTC从默认值(通常是0,即1970-01-01)开始计数。驱动会通过rtc_valid_tm检查时间有效性,无效时间会返回错误。此时需要通过NTP同步或手动设置时间。
答案:周期(period)决定PWM频率,占空比(duty_cycle)决定高电平时间占比。占空比必须小于等于周期。
寄存器值计算:I.MX6ULL使用IPG时钟,period_cycles = clk_rate × period_ns / 1e9,duty_cycles = period_cycles × duty_ns / period_ns。由于硬件要求周期值需减2(PWMPR寄存器),最终写入PWMPR = period_cycles - 2。预分频器用于将大周期值分频到16位计数器范围内。