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
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值