STM32MP157驱动开发——Linux PWM驱动
0.前言
PWM 是日常生活中的常用功能,可以通过 PWM 来控制电机速度、LCD背光亮度等。这一节就学习 Linux 下的 PWM 驱动开发。
一、PWM驱动
1.PWM 简介
PWM 全称是 Pulse Width Modulation,也就是脉冲宽度调制, PWM 信号如下图所示:
PWM 信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是 1 秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分数表示,如果一个周期内全是低电平那么占空比就是 0%,如果一个周期内全是高电平那么占空比就是 100%。
假如给 LCD 的背光引脚输入一个 PWM 信号,就可以通过调整占空比的方式来调整 LCD 背光亮度。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度,重点就在于PWM 信号的产生和占空比的控制。
2.设备树下的 PWM 控制器节点
1)定时器节点
STM32MP157 有很多路 PWM,这些 PWM 都是由定时器产生的:
TIM1/TIM8: 这 2 个是 16 位高级定时器,主要用于电机控制。这两个定时器每个支持 4 通道 PWM 信号。
TIM2/TIM3/TIM4/TIM5: 这 4 个是通用定时器,TIM3/TIM4 是 16 位定时器,TIM2/TIM5 是 32 位定时器。这 4 个定时器也支持 PWM 输出,每个定时器支持 4 通道 PWM 信号。
TIM12/TIM13/TIM14:这 3 个都是 16 位的通用定时器,TIM12 支持 2 通道的 PWM 信号,TIM13/TIM14 这两个定时器每个只支持 1 个通道的 PWM 信号。
TIM15/TIM16/TIM17:这 3 个也都是 16 位的通用定时器,TIM15 支持 2 通道的 PWM 信号,TIM16/TIM17 每个定时器支持 1 通道的 PWM 信号。
可以看出,STM32MP157 的 PWM 通道非常多,不同的 PWM 通道功能也不同,可以根据实际情况选择合适的 PWM 通道。本节使用 PA10 引脚来实现 PWM 功能。
注:PA10 这个引脚也可以被用作 USB 的 ID 引脚,如果所使用的开发板使用了 PA10 作为 USB OTG 的 ID 引脚,那么在做本实验时开发板的 USB OTG 接口不能连接到电脑上!正点原子 STM32MP157 开发板的 USB 接口采用了 TypeC 接口,因此没有用到 PA10 作为 ID 引脚。
打开 STM32MP157 的数据手册,可以看到 PA10 可以作为 TIM1 的通道 3:
2)TIM1 简介
TIM1 通道 3 这一路 PWM 属于 TIM1 定时器,TIM 1 为高级定时器,因为 TIM1 的功能非常强大,主要用于电机驱动。TIM1 主要特性如下:
① 16 位的向上、向下自动加载计数器
② 16 位可编程的预分频器
③ 6 个独立的通道,这些通道的功能如下:
— 输入捕获(只有通道 5 和 6 支持)
— 输出比较
— PWM 波形生成(边缘和中间对齐模式)
— 单脉冲模式
④ 带有死区的可编程互补输出
⑤ 以下事件可以生成中断或者 DMA:
— 更新事件,计数器溢出
— 触发事件,计数器开始、停止、初始化等
— 输入捕获
— 输出比较
3)TIM 1 设备节点
STM32 定时器设备树绑定信息文档为:Documentation/devicetree/bindings/mfd/stm32-timers.txt,其节点信息如下:
① 必须的参数:
compatible:必须是“st,stm32-timers”
reg:定时器控制器物理寄存器基地址,对于 TIM1 来说,这个地址为 0x44000000,这个可以在 STM32MP157 的数据手册上找到
clock-names:时钟源名字,设置为“int”
clocks:时钟源
② 可选的参数:
resets:复位句柄,用来复位定时器控制器,可以参考文档 reset/st,stm32-rcc.txt
dmas:DMA 通道,最多 7 通道的 DMA
dma-names:DMA 名字列表,必须和“dmas”属性匹配,可选的名字有:“ch1”、“ch2”、“ch3”、“ch4”、“up”、“trig”、“com”
③ 可选的子节点:
STM32 定时器有多种功能,比如及时、PWM、计数器等,不同的功能需要用不同的子节点来表示,可选子节点有三种,分别对应不同的功能:
pwm:pwm 子节点描述定时器的 PWM 功能,关于 PWM 的详细信息请参考绑定文档 pwm/pwm-stm32.txt
timer::timer 子节点描述定时器的定时功能,定时相关信息请参考绑定文档 iio/timer/stm32-timer-trigger.txt。
counter:counter 子节点描述定时器的计数功能,相关信息请参考绑定文档 counter/stm32-timer-cnt.txt
在设备树文件 stm32mp151.dtsi 中,有一个名为 “timers1” 的节点,这个就是 TIM1 定时器:
timers1: timer@44000000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "st,stm32-timers";
reg = <0x44000000 0x400>;
clocks = <&rcc TIM1_K>;
clock-names = "int";
dmas = <&dmamux1 11 0x400 0x80000001>,
<&dmamux1 12 0x400 0x80000001>,
<&dmamux1 13 0x400 0x80000001>,
<&dmamux1 14 0x400 0x80000001>,
<&dmamux1 15 0x400 0x80000001>,
<&dmamux1 16 0x400 0x80000001>,
<&dmamux1 17 0x400 0x80000001>;
dma-names = "ch1", "ch2", "ch3", "ch4", "up", "trig", "com";
status = "disabled";
pwm {
compatible = "st,stm32-pwm";
#pwm-cells = <3>;
status = "disabled";
};
timer@0 {
compatible = "st,stm32h7-timer-trigger";
reg = <0>;
status = "disabled";
};
counter {
compatible = "st,stm32-timer-counter";
status = "disabled";
};
};
其中的 pwm 子节点就是本节需要重点关注的。
4)PWM 设备子节点
PWM 子节点绑定文档:Documentation/devicetree/bindings/pwm/pwm-stm32.txt。在此文件中描述 PWM 子节点的属性:
compatible:必须为“st,stm32-pwm”
pinctrl-names:设置为“default”,也可以添加“sleep”,这样当进入低功耗时 PWM 引脚引入 sleep 模式
pinctrl-n:PWM 引脚 pinctrl 句柄,用来指定 PWM 信号输出引脚
#pwm-cells:应该设置为 3
STM32MP157 的 PWM 节点的 compatible 属性为“st,stm32-pwm”,可以在 linux 内核源码中搜索这个字符串找到 PWM 驱动文件,这个文件为:drivers/pwm/pwm-stm32.c。
3.PWM 子系统
Linux 内核提供了个 PWM 子系统框架,编写 PWM 驱动的时候一定要符合这个框架。PWM子系统的核心是 pwm_chip 结构体,定义在文件 include/linux/pwm.h 中,内容如下:
struct pwm_chip {
struct device *dev;
const struct pwm_ops *ops;
int base;
unsigned int npwm;
struct pwm_device * (*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;
/* only used internally by the PWM framework */
struct list_head list;
struct pwm_device *pwms;
};
其中的 pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动时需要开发人员实现。pwm_ops 结构体也定义在 pwm.h 头文件中,定义如下:
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 (*capture)(struct pwm_chip *chip, /* 捕获 PWM 信号 */
struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout);
int (*apply)(struct pwm_chip *chip, /* 新的 PWM 配置方法,配置 PWM 周期和占空比 */
struct pwm_device *pwm,
const struct pwm_state *state);
void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state);
struct module *owner;
/* Only used by legacy drivers */
int (*config)(struct pwm_chip *chip, /* 配置 PWM 周期和占空比 */
struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip, /* 设置 PWM 极性 */
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);
};
pwm_ops 中的这些函数不一定全部实现,但是配置 PWM 的函数必须实现,比如 apply 或者 config。apply 函数是最新的 PWM 配置函数,通过此函数来配置 PWM 的周期以及占空比,老的内核里面会使用config 函数来配置 PWM。其中 config、set_polarity、enable 和 disable 都是老版本内核所使用的函数。
PWM 子系统驱动的核心初始化 pwm_chip 结构体,然后向内核注册初始化完成以后的 pwm_chip。这里就要用到 pwmchip_add 函数,此函数定义在 drivers/pwm/core.c 文件中:
原型:
int pwmchip_add(struct pwm_chip *chip)
参数:
chip:要向内核注册的 pwm_chip。
返回值:
0:成功
负数:失败
卸载 PWM 驱动的时候需要将前面注册的 pwm_chip 从内核移除掉,这里要用到 pwmchip_remove 函数:
原型:
int pwmchip_remove(struct pwm_chip *chip)
参数:
chip:要移除的 pwm_chip。
返回值:
0:成功
负数:失败
4.源码分析
Linux 内核自带的 STM32MP157 PWM 驱动,驱动文件为 pwm-stm32,是一个标准的平台设备驱动文件:
pwn-stm32.c:
static const struct of_device_id stm32_pwm_of_match[] = {
{ .compatible = "st,stm32-pwm", },
{},
};
MODULE_DEVICE_TABLE(of, stm32_pwm_of_match);
static struct platform_driver stm32_pwm_driver = {
.probe = stm32_pwm_probe,
.remove = stm32_pwm_remove,
.driver = {
.name = "stm32-pwm",
.of_match_table = stm32_pwm_of_match,
.pm = &stm32_pwm_pm_ops,
},
};
①节点的 compatible 属性值为“st,stm32-pwm”时就会匹配此驱动
②设备树节点和驱动匹配以后 stm32_pwm_probe 函数就会执行
在看 stm32_pwm_probe 函数之前先看下 stm32_pwm 结构体,这个结构体是 ST 官方创建的 STM32 PWM 结构体,会贯穿整个 PWM 驱动,起到灵魂的作用。stm32_pwm 结构体定义在 pwm-stm32.c 文件中:
struct stm32_pwm {
struct pwm_chip chip;
......
u32 capture[4] ____cacheline_aligned; /* DMA'able buffer */
};
chip 为 pwm_chip 结构体成员变量,也就是 PWM 子系统的核心。
stm32_pwm_probe 函数部分内容如下:
static int stm32_pwm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
struct stm32_pwm *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->lock);
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
priv->max_arr = ddata->max_arr;
priv->chip.of_xlate = of_pwm_xlate_with_flags;
priv->chip.of_pwm_n_cells = 3;
if (!priv->regmap || !priv->clk)
return -EINVAL;
ret = stm32_pwm_probe_breakinputs(priv, np);
if (ret)
return ret;
stm32_pwm_detect_complementary(priv);
priv->chip.base = -1;
priv->chip.dev = dev;
priv->chip.ops = &stm32pwm_ops;
priv->chip.npwm = stm32_pwm_detect_channels(priv);
ret = pwmchip_add(&priv->chip);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, priv);
return 0;
}
①priv 是一个 stm32_pwm 类型的结构体指针变量,使用 devm_kzalloc 为其申请内存。stm32_pwm 结构体有个重要的成员变量 chip,chip 是 pwm_chip 类型的。所以这一行就引出了 PWM 子系统核心部件 pwm_chip,稍后的重点就是初始化 chip
②mutex_init 初始化相关的锁,下方的代码初始化 priv 的各个成员变量,其中还初始化了 pwm_chip 的 of_xlate和 of_pwm_n_cells 这两个成员变量
③调用 stm32_pwm_probe_breakinputs 函数来读取“st,breakinput”属性,设置 break输入,本节例程用不到
④调用 stm32_pwm_detect_complementary 函数来检测是否使能 TIM1 的互补输出功能
⑤初始化 pwm_chip 的各个成员变量,其中包括设置 pwm_chip 的 ops 函数为 stm32pwm_ops,stm32pwm_ops 里面包含了 PWM 的具体操作;设 置 pwm_chip 的 npwm,也就是设置当前打开多少路 PWM。这里直接使用stm32_pwm_detect_channels 函数来读取 TIM1 的 CCER 寄存器,CCER 寄存器的 CC1E(bit0)、CC2E(bit4)、CC3E(bit8)和 CC4E(bit12)这 4 个位用于开启 TIM1 的 4 个通道的 PWM,如果为 1 就表示对应的 PWM 通道打开。所以 stm32_pwm_detect_channels 函数就会直接读取这 4 个位来判断对应的 PWM 通道是否打开
stm32pwm_ops 设备操作函数集:
static const struct pwm_ops stm32pwm_ops = {
.owner = THIS_MODULE,
.apply = stm32_pwm_apply_locked,
.capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL,
};
stm32_pwm_apply_locked 就是最终的 PWM 设置函数,在应用中设置的 PWM 频率和占空比最终就是由 stm32_pwm_apply_locked 函数来完成的,此函数会最终操作 STM32相关的寄存器。
stm32_pwm_apply_locked 函数源码如下:
static int stm32_pwm_apply_locked(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *state)
{
struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
int ret;
/* protect common prescaler for all active channels */
mutex_lock(&priv->lock);
ret = stm32_pwm_apply(chip, pwm, state);
mutex_unlock(&priv->lock);
return ret;
}
①加互斥锁,防止竞争的产生。一次只有一个应用可以设置 PWM
②调用 stm32_pwm_apply 函数来设置 PWM
stm32_pwm_apply 函数内容如下:
static int stm32_pwm_apply(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *state)
{
bool enabled;
struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
int ret;
enabled = pwm->state.enabled;
if (enabled && !state->enabled) {
stm32_pwm_disable(priv, pwm->hwpwm);
return 0;
}
if (state->polarity != pwm->state.polarity)
stm32_pwm_set_polarity(priv, pwm->hwpwm, state->polarity);
ret = stm32_pwm_config(priv, pwm->hwpwm, state->duty_cycle, state->period);
if (ret)
return ret;
if (!enabled && state->enabled)
ret = stm32_pwm_enable(priv, pwm->hwpwm);
return ret;
}
①在设置 PWM 之前,先调用 stm32_pwm_disable 函数关闭 PWM
②调用 stm32_pwm_set_polarity 函数设置指定 PWM 通道的极性
③调用 stm32_pwm_config 来设置 PWM 的频率以及占空比
④PWM 设置完成以后调用 stm32_pwm_enable 函数使能 PWM
stm32_pwm_config 函数内容如下:
static int stm32_pwm_config(struct stm32_pwm *priv, int ch, int duty_ns, int period_ns)
{
unsigned long long prd, div, dty;
unsigned int prescaler = 0;
u32 ccmr, mask, shift;
/* Period and prescaler values depends on clock rate */
div = (unsigned long long)clk_get_rate(priv->clk) * period_ns;
do_div(div, NSEC_PER_SEC);
prd = div;
while (div > priv->max_arr) {
prescaler++;
div = prd;
do_div(div, prescaler + 1);
}
prd = div;
if (prescaler > MAX_TIM_PSC)
return -EINVAL;
/*
* All channels share the same prescaler and counter so when two
* channels are active at the same time we can't change them
*/
if (active_channels(priv) & ~(1 << ch * 4)) {
u32 psc, arr;
regmap_read(priv->regmap, TIM_PSC, &psc);
regmap_read(priv->regmap, TIM_ARR, &arr);
if ((psc != prescaler) || (arr != prd - 1))
return -EBUSY;
}
regmap_write(priv->regmap, TIM_PSC, prescaler);
regmap_write(priv->regmap, TIM_ARR, prd - 1);
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, TIM_CR1_ARPE);
/* Calculate the duty cycles */
dty = prd* duty_ns;
do_div(dty, period_ns);
write_ccrx(priv, ch, dty);
/* Configure output mode */
shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
mask = CCMR_CHANNEL_MASK << shift;
if (ch < 2)
regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
else
regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
regmap_update_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE, TIM_BDTR_MOE);
return 0;
}
PWM 的设置主要就是两方面:频率和占空比。
div
设置部分即为设置频率,函数参数 period_ns 为周期值,也就是 PWM 的频率。TIM 的 PSC 寄存器用来设置定时器分频值,当 TIM 时钟源确定以后,设置 PSC 分频值即可得到 TIM 最终的时钟频率。TIM 的 ARR 寄存器是自动加载寄存器,将 TIM 设置为向下计数器,定时器开启以后每个时钟周期,计数器减一,直到计数器减为 0。这个时候再将 ARR 里面的值加载到计数器里面,计数器就会重新开始倒计时,如此一直重复。因此 PSC 和 ARR 这两个寄存器就决定了 PWM 的周期值。
注:由于一个定时器有 4 通道的 PWM,而这 4 路 PWM 只能设置成同一个周期,如果想要多路周期不同的 PWM 信号,那就要使用多个不同的 TIM。
和dty
有关的部分则是用来设置占空比。参数 duty_ns 表示占空比。一个定时器下的 4 路 PWM 可以设置不同的占空比,相当于一个定时器下的 4 路 PWM 信号,周期是绝对一样的,但是占空比可以不同。占空比的设计原理比较简单,前面我们已经知道当定时器时钟频率确定以后(PSC 分频值不变),ARR 寄存器里面的值就决定了 PWM 周期,比如当 ARR 为 100 时,当 100 减为 0,那么一个周期就结束了。假如 PWM 信号默认是高电平,这个时候如果让计数器里面的值从 100 减少到 50 的时候,输出的波形改变一下方向,也就是从高电平变为低电平,当计数器减少到 0 的时候重新回到原来的高电平,这样 PWM 波形就出来了。而占空比就是 50%;如果让计数器减少到 70 时改变电平方向,此时占空比不就是 30%了,这个数值专业数据就叫比较值,改变比较值就可以改变 PWM 的占空比。STM32MP157 一个定时器有 4
路 PWM 通道,每个通道都有个用来存放比较值的寄存器,因此一共有 4 个寄存器 CCR1~CCR4,这 4 个寄存器就叫做比较寄存器。所以do_div
函数就是根据参数 duty_ns 算出对应的CCRx(x=1 ~ 4)寄存器对应的值,然后通过 write_ccrx 函数将相应的值写入到对应的CCRx 寄存器中。
/* Configure output mode */
部分是设置 PWM 输出模式,通道 1 和通道 2 使用 CCMR1 寄存器,通道 3 和通道 4 使用 CCMR2 寄存器。最后用 regmap_update_bits 设置 BDTR 寄存器,这个寄存器是 break 和死区控制相关的,本小节用不到。
二、PWM驱动开发
1.修改设备树
PWM 驱动已经由 ST 写好了,前面也已经详细的分析过这个驱动源码。在实际使用的时候只需要修改设备树即可,STM32MP157 开发板上的 JP1 排针
引出了 PA10 这个引脚,如图所示:
PA10 可以作为 TIM1 通道 3 的 PWM 输出引脚,所以需要在设备树里面添加 PA10的引脚信息以及 TIM1 通道 3 的 PWM 信息:
在设备树文件 stm32mp15-pinctrl.dtsi 中的 pinctrl
根节点下添加GPIO1_IO04 的引脚信息:
注:此部分与原子教程不太相同,我使用的版本中没有 iomuxc 节点,如果有的话修改该节点下的信息。
在原文件中已经设置好了 pwm1 的相关信息,在本节中,将通道 3 的引脚修改为 PA10。
2.向 timers1 节点追加信息
stm32mp151.dtsi 文件中已经有了“timers1”节点,但是这个节点默认是 disable 的,而且还不能直接使用。需要在 stm32mp157d-atk.dts 文件中向 timers1 节点追加一些内容,在 stm32mp157d-atk.dts 文件中加入如下所示内容:
&timers1 {
status = "okay";
/* spare all DMA channels since they are not needed for PWM output */
/delete-property/dmas;
/delete-property/dma-names;
pwm1: pwm {
pinctrl-0 = <&pwm1_pins_a>;
pinctrl-1 = <&pwm1_sleep_pins_a>;
pinctrl-names = "default", "sleep";
#pwm-cells = <2>;
status = "okay";
};
};
①关闭dma功能,PWM 输出不需要 DMA
②pinctrl-0 属性指定 TIM1 的 CH3 所使用的输出引脚对应的 pinctrl 节点,设置为之前添加的 pwm1_pins_a
3.屏蔽掉其他复用的 IO
检查设备树 stm32mp157d-atk.dts 中有没有其他外设用到 PA10,如果有的话需要屏蔽掉。注:不能只屏蔽掉 PA10 的 pinctrl 配置信息,也要搜索一下“gpioa 10”。
修改完后重新编译出设备树文件,用作开发板启动。
4.使能 PWM 驱动
ST 官方的 Linux 内核已经默认使能了 PWM 驱动,选中 menuconfig 中的 Device Drivers --> Pulse-Width Modulation (PWM) Support --> STMicroelectronics STM32 PWM
,然后重新编译出内核镜像,用作开发板启动。
三、PWM 驱动测试
1.确定 TIM1 对应的 pwmchipX 文件
使用新的设备树启动系统,然后将开发板上的 PA10 引脚连接到示波器上,通过示波器来查看 PWM 波形图。可以直接在用户层来配置 PWM,进入目录 /sys/class/pwm 中,有个 pwmchip0,但是目前并不知道这个 pwmchip0 是否为 TIM1 对应的文件。我们可以通过查看 pwmchip0 对应的地址和 TIM1 定时器寄存器起始地址是否一致来确定其是否属于 TIM1。
可以看出 pwmchip0 对应的定时器寄存器起始地址为 0X44000000,因此, pwmchip0 就是 TIM1 对应的文件。
2.调出 pwmchip0 的 pwm2 子目录
pwmchip0 是整个 TIM1 的总目录,而 TIM1 有 4 路 PWM,每路都可以独立打开或关闭。CH1 ~ CH4 对应的编号为 0~3,因此打开 TIM1 的 CH3 输入如下命令:
echo 2 > /sys/class/pwm/pwmchip0/export
上述命令中 2 就是 TIM1_CH3,如果要打开 TIM1 的CH1,那就是 0。执行完成会在 pwmchip0 目录下生成一个名为“pwm2”的子目录:
3.设置 PWM 的频率
注意,这里设置的是周期值,单位为 ns,比如 20KHz 频率的周期就是 50000ns,输入如下命令:
echo 50000 > /sys/class/pwm/pwmchip0/pwm2/period
4.设置 PWM 的占空比
这里不能直接设置占空比,而是设置的一个周期的 ON 时间,也就是高电平时间,比如 20KHz 频率下 20%占空比的 ON 时间就是 10000,输入如下命令:
echo 10000 > /sys/class/pwm/pwmchip0/pwm2/duty_cycle
5.使能 TIM1 的通道 3
一定要先设置频率和波特率,最后在开启定时器,否则会提示参数错误!输入如下命令使能 TIM1 的通道 3 这路 PWM:
echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable
设置完成后即可使用示波器查看波形是否正确,这里笔者没有示波器,使用原子教程中的图片,有能力的小伙伴可以将频率设置低一点,使用单片机制作一个 AD 采样工具进行验证。
可以看出,此时 PWM 频率为 20KHz,占空比为 20%,与设置的一致。如果要修改频率或者占空比一定要注意这两者时间值,比如 20KHz 频率的周期值为 50000ns,那么在调整占空比时 ON 时间就不能设置大于 50000,否则就会提示参数无效。
6.极性翻转
PWM 的极性也可以修改,上面设置的 PWM 占空比为 20%,只需要修改极性就可以将占空比变为 80%。向/pwmchip0/pwm2/polarity 文件写入“inversed”即可反转极性,命令如下:
echo "inversed" > /sys/class/pwm/pwmchip0/pwm2/polarity
极性反转以后占空比就变为了 80%,如果要恢复回原来的极性,向 /pwmchip0/pwm2/polarity文件写入“normal”即可,命令如下:
echo "normal" > /sys/class/pwm/pwmchip0/pwm2/polarity