4.3.11. PWM 驱动调试指南
4.3.11.1. 概述
PWM(脉宽调制, Pulse Width Modulation)是
参数
PWM 默认
支持 频率 范围 为 0.05Hz - 1MHz,占空比 寄存器 RATIO 精度 为 16bit,周期 有效 时间 为 1us 到 20s,占空比 有效 时间 为 10ns 到 20s。
4.3.11.2. 特点
芯片
1. 两个
2. 每个 PWM 通道
3. 每个
4. 每个
5. 八位
4.3.11.3. 功能描述
典型应用
PWM 信号
0.5ms: 舵机
转动 到 0 度 1.0ms: 舵机
转动 到 45 度 1.5ms: 舵机
转动 到 90 度 2.0ms: 舵机
转动 到 135 度 2.5ms: 舵机
转动 到 180 度

PWM 信号
周期:从
上升 沿到 下 一个 上升 沿 的 时间 ,100HZ 表示 1 秒钟 内有 100 个 时钟 周期。 占空比( Duty Cycle):占空比
是 指 PWM 信号 在 一个 周期 内“高电平”持续 的 时间 比例。占空比 通常 以 百分比 表示。例如,占空比 为 50% 时,信号 的 一半 时间 为 高电平,另一半 时间 为 低电平。 频率( Frequency):频率
是 指 PWM 信号 每秒钟 重复 的 次数,频率 和 周期 成反比,频率 = 1 / 周期。
功能原理
下图

工作方式
PWM 信号
设备
初始化:驱动程序 通过 设备树 或者 平台 代码 初始化 PWM 硬件,并 配置 硬件 寄存器。 PWM 配置:通过
drobot_pwm_apply或用户 空间 接口,设置 PWM 的 周期(频率)和 占空比。 启用 PWM :通过
调用 pwm_enable()来启动 PWM 输出。当 PWM 被 启用 时,硬件 控制器 将 按 指定 的 频率 和 占空比 生成 PWM 波形。 运行
期间 调节:可以 通过 系统 接口 实时 调整 PWM 的 占空比 和 频率。 关闭 PWM:当
不再 需要 PWM 输出 时,可以 通过 pwm_disable()来停止 PWM 输出。
具体

4.3.11.4. 驱动代码
PWM 代码说明
drivers/pwm/pwm-drobot.c
内核配置
/* arch/arm64/configs/hobot_x5_soc_defconfig */
...
CONFIG_PWM_DROBOT=y
...
DTS 节点配置
X5 PWM 控制器arch/arm64/boot/dts/hobot/x5.dtsi 文件x5-evb.dts 为例,使能 pwm3 ch0-1。
备注:
x5.dtsi 中
/* arch/arm64/boot/dts/hobot/x5-evb.dts */
...
&pwm3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3_0 &pinctrl_pwm3_1>;
};
...
pwm 设备树
&pwm3:这是 另 一个 PWM 控制器, PWM3 。 status = "okay"启用 PWM3 控制器。pinctrl-names = "default":指定使用 默认 的 引脚 控制 设置,这里 打开 了 PWM3 的 两个 channel。 pinctrl-0 = <&pinctrl_pwm3_0 &pinctrl_pwm3_1>: 这里打开 了 PWM3 两个 channel。 指定
了 PWM3 控制器 的 引脚 配置。这是 一个 指针 数组,指向 一系列 引脚 控制 配置(通常 是 由 pinctrl 子系统 管理 的),每个 pinctrl_pwm3_X 表示 一个 预定 义 的 引脚 配置,用于 控制 PWM 信号 在 物理 引脚 上 的 行为,例如 引脚 号、复用 功能、电源 域 配置 等。
4.3.11.5. 功能使用
kernel space
drobot_pwm_probe 函数
static int drobot_pwm_probe(struct platform_device *pdev)
{
struct drobot_pwm_chip *drobot_pwm = NULL;
struct resource *res = NULL;
int ret = 0;
drobot_pwm = devm_kzalloc(&pdev->dev, sizeof(*drobot_pwm), GFP_KERNEL);
if (!drobot_pwm)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
drobot_pwm->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(drobot_pwm->base))
return PTR_ERR(drobot_pwm->base);
drobot_pwm->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(drobot_pwm->clk))
return PTR_ERR(drobot_pwm->clk);
ret = clk_prepare_enable(drobot_pwm->clk);
if (ret < 0) {
clk_disable_unprepare(drobot_pwm->clk);
dev_err(&pdev->dev, "failed to enable pwm clock, error %d\n", ret);
return ret;
}
drobot_pwm->reset = devm_reset_control_get_exclusive(&pdev->dev,
NULL);
if (IS_ERR(drobot_pwm->reset))
return PTR_ERR(drobot_pwm->reset);
reset_control_assert(drobot_pwm->reset);
usleep_range(1, 2);
reset_control_deassert(drobot_pwm->reset);
drobot_pwm->chip.dev = &pdev->dev;
drobot_pwm->chip.ops = &drobot_pwm_ops;
drobot_pwm->chip.npwm = 2;
drobot_pwm->chip.base = -1;
ret = pwmchip_add(&drobot_pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret);
return ret;
}
/* When PWM is disable, configure the output to the default value */
platform_set_drvdata(pdev, drobot_pwm);
pm_runtime_enable(&pdev->dev);
dev_info(&pdev->dev, "D-Robotics PWM register done!\n");
return 0;
}
驱动代码关键接口
1.devm_kzalloc
作用:为 drobot_pwm_chip 结构
体 分配内存,并 使用 内核 的 内存 管理 方式 (devm) 进行 资源管理。 参数:第一个
参数 为 设备 对象 (&pdev->dev),第二个 参数 是 要 分配 的 内存大小,第三个 参数 为 分配 标志 (GFP_KERNEL)。 返回值:返回
分配 的 内存地址,若 分配 失败 返回 NULL。
2.platform_get_resource
作用:平台
设备 中 提取 指定 类型 的 资源,这里 提取 的 是 内存 资源 (IORESOURCE_MEM)。 参数:第一个
参数 为 平台 设备 对象 pdev,第二个 参数 是 资源类型(内存 类型),第三个 参数 是 资源 的 索引(通常 为 0 )。 返回值:返回 struct resource 类型
的 资源 结构 体,若 没有 该 资源 返回 NULL。
3.devm_ioremap_resource
作用:为 drobot_pwm_chip 结构
体中 的 base 成员 赋值,将 设备 的 物理 内存 地址映射 为 虚拟地址。 参数:第一个
参数 为 设备 对象,第二个 参数 为 要 映射 的 资源。 返回值:返回
映射 后 的 虚拟地址,若 失败 返回 ERR_PTR。
4.devm_clk_get
作用:获取
与 设备 相关 的 时钟 源,并 为 设备 提供 时钟 支持。 参数:第一个
参数 为 设备 对象,第二个 参数 为 时钟 名称(此处 为 NULL,表示 获取 默认 时钟)。 返回值:返回
时钟 的 句柄,若 获取 失败 返回 ERR_PTR。
5.clk_prepare_enable
作用:在
启用 设备 时钟 之前,准备 时钟 并 确保 它 处于 活动状态。 参数:时钟
句柄。 返回值:返回
时钟 启用 操作 的 结果,若 失败 返回 负值。
6.devm_reset_control_get_exclusive
作用:获取
并 管理 与 设备 相关 的 复位 控制 资源。 参数:第一个
参数 为 设备 对象,第二个 参数 为 复位 控制器 的 名称(此处 为 NULL,表示 获取 默认 复位 控制器)。 返回值:返回
复位 控制器 的 句柄,若 获取 失败 返回 ERR_PTR。
7.pwmchip_add
作用:将 drobot_pwm 设备
的 PWM 配置 结构 体 添加 到 内核 中,供 内核 管理 PWM 输出。 参数: PWM 配置
结构 体 (drobot_pwm->chip)。 返回值:返回
负值 表示 添加 失败,返回 0 表示 成功
8.platform_set_drvdata
作用:将 drobot_pwm 设备
的 指针 保存 到 平台 设备 中,以便 后续 访问。 参数:平台
设备 对象 pdev,驱动 数据(此处 是 drobot_pwm)。 返回值:无
返回值。
drobot_pwm_probe 函数drobot_pwm->chip.ops 为 drobot_pwm_ops,即该
static const struct pwm_ops drobot_pwm_ops = {
.apply = drobot_pwm_apply,
.owner = THIS_MODULE,
};
通过 drobot_pwm_apply 来robot_pwm_enable 来
user space
sysfs 节点调试
在
cat /sys/class/pwm/pwmchip0/device/uevent
DRIVER=drobot-pwm
OF_NAME=pwm
OF_FULLNAME=/soc/a55_apb0/pwm@34160000
OF_COMPATIBLE_0=d-robotics,pwm
OF_COMPATIBLE_N=1
MODALIAS=of:NpwmT(null)Cd-robotics,pwm
查看 x5.dtsi 中 关于 PWM 控制器
pwm2: pwm@34160000 {
compatible = "d-robotics,pwm";
status = "disabled";
reg = <0x34160000 0x10000>;
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&hpsclks X5_LSIO_PWM2_PCLK>;
#pwm-cells = <2>;
resets = <&socrst LSIO_PWM2_APB_RESET>;
};
可以
root@buildroot:/sys/class/pwm# ls
pwmchip0 pwmchip2 pwmchip6
以上
root@buildroot:/sys/class/pwm# cd pwmchip0
root@buildroot:/sys/class/pwm/pwmchip0# ls
device export npwm power subsystem uevent unexport
device: 这是
一个 指向 设备 的 符号 链接,它 通常 链接 到 实际 的 硬件 设备 节点。通过 访问 该 文件,系统 可以 获取 或 修改 与 该 设备 相关 的 属性。 npwm: 指
当前 PWM 所 包含 的 channel 数量, pwm 通道 数为 2 。 export:用户
可以 通过 echo 向 该 文件 写入 PWM 通道 的 编号,将 通道 导出 到 /sys/class/pwm/pwmchip0/pwmX 目录(其中 X 为 PWM 通道 的 编号)。通过 这种 方式,系统 允许 用户 对 该 通道 进行 配置 和 控制。 power:该
文件 提供 了 与 设备 电源 管理 相关 的 信息。它 可以 包含 关于 设备 的 电源 状态 的 详细信息,比如 是否 启用 节能 模式、是否 开启 了 电源 等。 subsystem:该
目录 是 指向 该 PWM 设备 所属 的 子系统 的 符号 链接。子系统 是 Linux 中 的 设备 管理 层次结构 的 一部分,表示 该 设备 所属 的 类别(如 PWM 设备、 I2C 设备 等)。 uevent:该
文件 用于 管理 udev(设备 管理器)事件,它 通常 用于 向 系统 通知 设备 的 变化。 unexport:该
文件 允许 用户 通过 写入 来 取消 导出 某个 PWM 通道,如果 某个 PWM 通道 已经 被 导出(通过 export 文件),则 用户 可以 通过 向 unexport 文件 写入 该 通道 的 编号 来 取消 导出,从而 使该 PWM 通道 不再 可用。
可以
申请
注册 PWM0 通道:
root@buildroot:/sys/class/pwm/pwmchip0# echo 0 > export
root@buildroot:/sys/class/pwm/pwmchip0# cd pwm0
设置 周期 为 100us:
root@buildroot:/sys/class/pwm/pwmchip0/pwm0# echo 100000 > period
设置 占空比 为 50%:
root@buildroot:/sys/class/pwm/pwmchip0/pwm0# echo 50000 > duty_cycle
使能 PWM 输出
或 关闭
root@buildroot:/sys/class/pwm/pwmchip0/pwm0# echo 1 > enable
root@buildroot:/sys/class/pwm/pwmchip0/pwm0# echo 0 > enable
用户
#!/bin/bash
set -e
target_chip="pwmchip0"
target_ch="0"
chip_sysfs_path="/sys/class/pwm/${target_chip}"
ch_sysfs_path="${chip_sysfs_path}/pwm${target_ch}"
cd "$chip_sysfs_path" || { echo "$chip_sysfs_path not found! Abort!"; exit 1; }
if [ ! -d "$ch_sysfs_path" ];then
echo "$target_ch" > export
fi
cd "pwm${target_ch}"
# 配置周期为 100us
echo 100000 > period
# 配置占空比为 50% = 100us * 0.5 = 50us
echo 50000 > duty_cycle
# 使能 PWM 输出
echo 1 > enable
# 以下是进行寄存器读取
chip_reg="0x$(cat ${chip_sysfs_path}/device/uevent | grep OF_FULLNAME | awk -F'@' '{print $2}')"
echo "Regs of ${target_chip}:"
echo "PWM_EN `devmem $(printf "0x%X" $((chip_reg + 0x00))) 32`"
echo "PWM_INT_CTRL `devmem $(printf "0x%X" $((chip_reg + 0x04))) 32`"
echo "PWMCH0_CTRL `devmem $(printf "0x%X" $((chip_reg + 0x10))) 32`"
echo "PWMCH0_CLK `devmem $(printf "0x%X" $((chip_reg + 0x14))) 32`"
echo "PWMCH0_PERIOD `devmem $(printf "0x%X" $((chip_reg + 0x20))) 32`"
echo "PWMCH0_STATUS `devmem $(printf "0x%X" $((chip_reg + 0x28))) 32`"
echo "PWMCH1_CTRL `devmem $(printf "0x%X" $((chip_reg + 0x30))) 32`"
echo "PWMCH1_CLK `devmem $(printf "0x%X" $((chip_reg + 0x34))) 32`"
echo "PWMCH1_PERIOD `devmem $(printf "0x%X" $((chip_reg + 0x40))) 32`"
echo "PWMCH1_STATUS `devmem $(printf "0x%X" $((chip_reg + 0x48))) 32`"