52.Linux PWM子系统

52.Linux PWM子系统

结构体pwm_chip表示PWM的核心

imx6ull_alientek共有八路pwm信号

pwm1-pwm4都在一组、pwm5-pwm8在一组

而 GPIO1_IO04 可以是 PWM3 的输出引脚

c 复制代码
		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>;
			};

compatible 属性值有两个"fsl,imx6ul-pwm"和"fsl,imx27-pwm",所以在整个 Linux源码里面搜索这两个字符窜即可找到 I.MX6ULL 的 PWM 驱动文件,这个文件就是drivers/pwm/pwm-imx.c。

include/linux/pwm.h中定义了pwm_chip结构体

c 复制代码
struct pwm_chip {
	struct device		*dev;
	struct list_head	list;
	const struct pwm_ops	*ops;   //pwm驱动的设备操作集合
	int			base;
	unsigned int		npwm;

	struct pwm_device	*pwms;

	struct pwm_device *	(*of_xlate)(struct pwm_chip *pc,
					    const struct of_phandle_args *args);
	unsigned int		of_pwm_n_cells;
	bool			can_sleep;
};

pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动的时候需要开发人员实现。

pwm_ops如下

c 复制代码
struct pwm_ops {
	int			(*request)(struct pwm_chip *chip,struct pwm_device *pwm);  //请求PWM
	void			(*free)(struct pwm_chip *chip,struct pwm_device *pwm);	//释放PWM
	int			(*config)(struct pwm_chip *chip,struct pwm_device *pwm, int duty_ns, int period_ns);  //配置PWM周期和占空比
	int			(*set_polarity)(struct pwm_chip *chip,  struct pwm_device *pwm, enum pwm_polarity polarity);  //配置PWM极性
	int			(*enable)(struct pwm_chip *chip, struct pwm_device *pwm);		//使能PWM
	void			(*disable)(struct pwm_chip *chip,struct pwm_device *pwm);	//关闭PWM
	struct module		*owner;
};

config\enable\disable这个三个肯定是需要实现的,否则打开/关闭 PWM,设置 PWM 的占空比这些就没操作了。

使用该结构体的思路

  1. 初始化pwm_chip结构体各成员变量

  2. 然后向内核注册初始化完成以后的pwm_chip,使用pwmchip_add 函数

    此函数定义在 drivers/pwm/core.c 文件中,函数原型如下:

    c 复制代码
    int pwmchip_add(struct pwm_chip *chip)
    //返回值: 0 成功;负数 失败。  
        
    int pwmchip_remove(struct pwm_chip *chip)  
    //返回值: 0 成功;负数 失败。

PWM驱动源码分析

im6ull的 PWM 驱动文件是drivers/pwm/pwm-imx.c。

c 复制代码
static struct imx_pwm_data imx_pwm_data_v1 = {
	.config = imx_pwm_config_v1,
	.set_enable = imx_pwm_set_enable_v1,
};

static struct imx_pwm_data imx_pwm_data_v2 = {
	.config = imx_pwm_config_v2,
	.set_enable = imx_pwm_set_enable_v2,
};

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 int imx_pwm_probe(struct platform_device *pdev)
{
    
}
static int imx_pwm_remove(struct platform_device *pdev)
{
    
}
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);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");

是一个标准的platform平台设备驱动文件

当设备树 PWM 节点的 compatible 属性值为"fsl,imx27-pwm"的话就会匹配此驱动,注意后面的.data 为 imx_pwm_data_v2 型的结构体变量,内容如下:

imx_pwm_config_v2 函数就是最终操作 I.MX6ULL 的 PWM 外设寄存器,进行实际配置的函数。 imx_pwm_set_enable_v2 就是具体使能 PWM 的函数。

c 复制代码
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;  //如果为真使能PWM,将寄存器的bit0置1即可
	else
		val &= ~MX3_PWMCR_EN; 	//关闭PWM,将寄存器的bit0置0即可

	writel(val, imx->mmio_base + MX3_PWMCR); //将新的 val 值写入到 PWMCR 寄存器中。
}

static int imx_pwm_config(struct pwm_chip *chip,
		struct pwm_device *pwm, int duty_ns, int period_ns)
{
	struct imx_chip *imx = to_imx_chip(chip);
	int ret;

	ret = clk_prepare_enable(imx->clk_ipg);
	if (ret)
		return ret;

	ret = imx->config(chip, pwm, duty_ns, period_ns);

	clk_disable_unprepare(imx->clk_ipg);

	return ret;
}

static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct imx_chip *imx = to_imx_chip(chip);
	int ret;

	ret = clk_prepare_enable(imx->clk_per);
	if (ret)
		return ret;

	imx->set_enable(chip, true);

	return 0;
}

static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct imx_chip *imx = to_imx_chip(chip);

	imx->set_enable(chip, false);

	clk_disable_unprepare(imx->clk_per);
}

static struct pwm_ops imx_pwm_ops = {
	.enable = imx_pwm_enable,
	.disable = imx_pwm_disable,
	.config = imx_pwm_config,
	.owner = THIS_MODULE,
};

struct imx_pwm_data {
	int (*config)(struct pwm_chip *chip,
		struct pwm_device *pwm, int duty_ns, int period_ns);
	void (*set_enable)(struct pwm_chip *chip, bool enable);
};

static struct imx_pwm_data imx_pwm_data_v1 = {
	.config = imx_pwm_config_v1,
	.set_enable = imx_pwm_set_enable_v1,
};

static struct imx_pwm_data imx_pwm_data_v2 = {
	//这两个函数由于打开和关闭对应的PWM
	.config = imx_pwm_config_v2,
	.set_enable = imx_pwm_set_enable_v2,
};

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 */ }
};
MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids);

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;

	//申请内存
	imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
	if (imx == NULL)
		return -ENOMEM;

	imx->clk_per = devm_clk_get(&pdev->dev, "per");
	if (IS_ERR(imx->clk_per)) {
		dev_err(&pdev->dev, "getting per clock failed with %ld\n",
				PTR_ERR(imx->clk_per));
		return PTR_ERR(imx->clk_per);
	}

	imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
	if (IS_ERR(imx->clk_ipg)) {
		dev_err(&pdev->dev, "getting ipg clock failed with %ld\n",
				PTR_ERR(imx->clk_ipg));
		return PTR_ERR(imx->clk_ipg);
	}

	//初始化imx的chip(pwm_chip对象)

		//设置pwm_ops的操作集合
	imx->chip.ops = &imx_pwm_ops;
	imx->chip.dev = &pdev->dev;
	imx->chip.base = -1;
	imx->chip.npwm = 1;
	imx->chip.can_sleep = true;


	//获取设备树中的PWM节点关于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);

	//将imx_pwm_config_v2函数中的两个config和set_enable函数赋给imx的相同变量
	data = of_id->data;
	imx->config = data->config;
	imx->set_enable = data->set_enable;

	ret = pwmchip_add(&imx->chip);
	if (ret < 0)
		return ret;

	platform_set_drvdata(pdev, imx);
	return 0;
}

static int imx_pwm_remove(struct platform_device *pdev)
{
	struct imx_chip *imx;

	imx = platform_get_drvdata(pdev);
	if (imx == NULL)
		return -ENODEV;

	return pwmchip_remove(&imx->chip);
}

实战演练

设备树修改

c 复制代码
		
&pwm3 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm3>;
	clocks = <&clks IMX6UL_CLK_PWM3>,
			<&clks IMX6UL_CLK_PWM3>;
	status = "okay";
};

IOMUXC{
		pinctrl_pwm3: pwm3grp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO04__PWM3_OUT   0x110b0
			>;
		};
    
};

在imx6ull.dtsi中已经写好了pwm3的相关参数,需要添加其他的引脚信息和节点信息如上

内核使能支持PWM(默认已经使能)

c 复制代码
-> Device Drivers
		-> Pulse-Width Modulation (PWM) Support
				-> <*> i.MX PWM support

最后将设备树、zImage拷贝到启动文件中

c 复制代码
cd /sys/class/pwm
ls
pwmchip0  pwmchip2  pwmchip4  pwmchip6
pwmchip1  pwmchip3  pwmchip5  pwmchip7

pwmchip2就是刚添加的pwm3设备

导出该节点用于控制

c 复制代码
 echo 0 > /sys/class/pwm/pwmchip2/export
c 复制代码
/pwmchip2 # ls
device     npwm       pwm0       uevent
export     power      subsystem  unexport

pwm0就是导出的文件

使能 PWM3

c 复制代码
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable

设置 PWM3 的频率

c 复制代码
echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period

设置 PWM3 的占空比 = period/duty_cycle = 10000/50000 = 20%

c 复制代码
echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle

输出一个50HZ(20ms)的周期信号

设置周期:

priod : 20000000 ------> 20ms

设置占空比:

duty_cycle:

500000 ------> 0.5ms (2.5%) - 0度

1000000 ------> 1ms (5%) - 45度

1500000 ------> 1.5ms (5%) - 90度

不同占空比(2.5% - 12.5%,0.5ms ~ 2.5ms)控制舵机的转向角度为

红色线接电源(4.8V---7.2V), 棕色线接地, 橙色线接pwm

shell 复制代码
echo 0 > /sys/class/pwm/pwmchip2/export
echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable
echo 20000000 > /sys/class/pwm/pwmchip2/pwm0/period

//转到0度
echo 500000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle

//转到45度
echo 1000000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle
相关推荐
zwm_yy29 分钟前
mysql主从主备回顾
运维·mysql·adb
CheungChunChiu30 分钟前
Linux 总线模型与 bind/unbind 完整解析
linux·ubuntu·sys·bind/unbind
可可苏饼干36 分钟前
ELK(Elastic Stack)日志采集与分析
linux·运维·笔记·elk
wangmengxxw37 分钟前
微服务-服务配置
java·运维·微服务·云计算·服务配置
大柏怎么被偷了1 小时前
【Git】基本操作
linux·运维·git
小女孩真可爱1 小时前
大模型学习记录(八)---------RAG评估
linux·人工智能·python
纯粹的热爱1 小时前
Windows 10/11解决“无法访问共享文件夹—组织安全策略阻止未经身份验证的来宾访问”
运维
乾元1 小时前
AI + Jinja2/Ansible:从自然语义到可执行 Playbook 的完整流水线(工程级深度)
运维·网络·人工智能·网络协议·华为·自动化·ansible
我在人间贩卖青春1 小时前
查看文件相关命令
linux·查看文件