Linux学习第57天:Linux PWM驱动实验

Linux版本号4.1.15   芯片I.MX6ULL                                 大叔学Linux    品人间百味  思文短情长


本章的思维导图如下:

一、PWM驱动简析

1、设备树下的PWM控制节点

        8 路 PWM 都属于 I.MX6ULL 的 AIPS-1 域,分为了两部分, PWM1~PWM4 在一起, PWM5~PWM8 在一起。

        本章实验我们使用GPIO1_IO04 这个引脚来完成 PWM 实验,而 GPIO1_IO04 就是 PWM3 的输出引脚。

1 pwm3: pwm@02088000 {
2 compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";/* compatible 属性值有两个“fsl,imx6ul-pwm”和“fsl,imx27-pwm”,所以在整个 Linux
源码里面搜索这两个字符窜即可找到 I.MX6ULL 的 PWM 驱动文件,这个文件就是
drivers/pwm/pwm-imx.c。*/
3 reg = <0x02088000 0x4000>;
4 interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
5 clocks = <&clks IMX6UL_CLK_PWM3>,
6 <&clks IMX6UL_CLK_PWM3>;
7 clock-names = "ipg", "per";
8 #pwm-cells = <2>;
9 };

2、PWM子系统

        框架

        PWM子系统的核心是 pwm_chip 结构体,定义在文件 include/linux/pwm.h 中,定义如下:
 

1 struct pwm_chip {
2 struct device *dev;
3 struct list_head list;
4 const struct pwm_ops *ops;/*第 4 行, pwm_ops 结构体就是 PWM 外设的各种操作函数集合,编写 PWM 外设驱动的时
候需要开发人员实现。 */
5 int base;
6 unsigned int npwm;
7 struct pwm_device *pwms;
8 struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
9 const struct of_phandle_args *args);
10 unsigned int of_pwm_n_cells;
11 bool can_sleep;
12 };

        pwm_ops 结构体也定义在 pwm.h 头文件中,定义如下:

/*
pwm_ops 中的这些函数不一定全部实现,但是像 config、 enable 和 disable 这些肯定是需要
实现的,否则的话打开/关闭 PWM,设置 PWM 的占空比这些就没操作了。
*/

1 struct pwm_ops {
2 int (*request)(struct pwm_chip *chip, //请求 PWM
3 struct pwm_device *pwm);
4 void (*free)(struct pwm_chip *chip, //释放 PWM
5 struct pwm_device *pwm);
6 int (*config)(struct pwm_chip *chip, //配置 PWM 周期和占空比
7 struct pwm_device *pwm,
8 int duty_ns, int period_ns);
9 int (*set_polarity)(struct pwm_chip *chip, //设置 PWM 极性
10 struct pwm_device *pwm,
11 enum pwm_polarity polarity);
12 int (*enable)(struct pwm_chip *chip, //使能 PWM
13 struct pwm_device *pwm);
14 void (*disable)(struct pwm_chip *chip, //关闭 PWM
15 struct pwm_device *pwm);
16 struct module *owner;
17 };

         PWM 子系统驱动的核心就是初始化 pwm_chip 结构体各成员变量,然后向内核注册初始化完成以后的 pwm_chip。

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 成功;负数 失败。
*/

 3、PWM驱动源码分析

1 static const struct of_device_id imx_pwm_dt_ids[] = {
2 { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, },
3 { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, },
/*
第 3 行,当设备树 PWM 节点的 compatible 属性值为“fsl,imx27-pwm”的话就会匹配此驱
动
后面的.data 为 imx_pwm_data_v2,这是一个 imx_pwm_data 类型的结构体变量,内容
如下:
1 static struct imx_pwm_data imx_pwm_data_v2 = {
2 .config = imx_pwm_config_v2,
3 .set_enable = imx_pwm_set_enable_v2,
4 };

imx_pwm_config_v2 函数就是最终操作 I.MX6ULL 的 PWM 外设寄存器,进行实际配置的
函数。 imx_pwm_set_enable_v2 就是具体使能 PWM 的函数。
*/
4 { /* sentinel */ }
5 };
6 7
......
8 9
static struct platform_driver imx_pwm_driver = {
10 .driver = {
11 .name = "imx-pwm",
12 .of_match_table = imx_pwm_dt_ids,
13 },
14 .probe = imx_pwm_probe,/*第 14 行,当设备树节点和驱动匹配以后 imx_pwm_probe 函数就会执行。*/
15 .remove = imx_pwm_remove,
16 };
17
18 module_platform_driver(imx_pwm_driver);

        imx_pwm_probe 函数如下(有缩减):
 

1 static int imx_pwm_probe(struct platform_device *pdev)
2 {
3 const struct of_device_id *of_id =
4 of_match_device(imx_pwm_dt_ids, &pdev->dev);
5 const struct imx_pwm_data *data;
6 struct imx_chip *imx;
7 struct resource *r;
8 int ret = 0;
9
10 if (!of_id)
11 return -ENODEV;
12
13 imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL);
/*
第 13 行, imx 是一个 imx_chip 类型的结构体指针变量,这里为其申请内存。 imx_chip 结
构体有个重要的成员变量 chip, chip 是 pwm_chip 类型的。所以这一行就引出了 PWM 子系统
核心部件 pwm_chip,稍后的重点就是初始化 chip。
*/
14 if (imx == NULL)
15 return -ENOMEM;
......
/*
第 31~35 行,初始化 imx 的 chip 成员变量,也就是初始化 pwm_chip!
*/
31 imx->chip.ops = &imx_pwm_ops;
32 imx->chip.dev = &pdev->dev;
33 imx->chip.base = -1;
34 imx->chip.npwm = 1;
35 imx->chip.can_sleep = true;
36
/*
 37 和 38 行,从设备树中获取 PWM 节点中关于 PWM 控
制器的地址信息,然后再进行内存映射,这样我们就得到了 PWM 控制器的基地址。
*/
37 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
38 imx->mmio_base = devm_ioremap_resource(&pdev->dev, r);
39 if (IS_ERR(imx->mmio_base))
40 return PTR_ERR(imx->mmio_base);
41
42 data = of_id->data;

/*第 43 和 44 行,这两行设置 imx 的 config 和 set_enable 这两个成员变量为 data->config 和
data->set_enable,

imx_pwm_enable、 imx_pwm_disable 和 imx_pwm_config 这三个函数最终调用就是
imx_pwm_config_v2 和 imx_pwm_set_enable_
*/
43 imx->config = data->config;
44 imx->set_enable = data->set_enable;
45
46 ret = pwmchip_add(&imx->chip);
47 if (ret < 0)
48 return ret;
49
50 platform_set_drvdata(pdev, imx);
51 return 0;
52 }

        设置 pwm_chip的 ops 操作集为 imx_pwm_ops, imx_pwm_ops 定义如下:

1 static struct pwm_ops imx_pwm_ops = {
2 .enable = imx_pwm_enable,
3 .disable = imx_pwm_disable,
4 .config = imx_pwm_config,
5 .owner = THIS_MODULE,
6 };

        

imx_pwm_set_enable_v2:打开或关闭对应的PWM
1 static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable)
2 {
3 struct imx_chip *imx = to_imx_chip(chip);
4 u32 val;
5 
6 val = readl(imx->mmio_base + MX3_PWMCR);/*第 6 行,读取 PWMCR 寄存器的值。*/
7 
8 if (enable)
9 val |= MX3_PWMCR_EN;/*第 9 行,如果 enable 为真,表示使能 PWM,将 PWMCR 寄存器的 bit0 置 1 即可,宏
MX3_PWMCR_EN 为(1<<0)。*/
10 else
11 val &= ~MX3_PWMCR_EN;/*第 11 行,如果 enable 不为真,表示关闭 PWM,将 PWMCR 寄存器的 bit0 清 0 即可。*/
12
13 writel(val, imx->mmio_base + MX3_PWMCR);/*第 13 行,将新的 val 值写入到 PWMCR 寄存器中。*/
14 }

        imx_pwm_config_v2 函数用于设置 PWM 的频率和占空比,相关操作如下:

1 static int imx_pwm_config_v2(struct pwm_chip *chip,
2 struct pwm_device *pwm, int duty_ns, int period_ns)
3 {
4 struct imx_chip *imx = to_imx_chip(chip);
5 struct device *dev = chip->dev;
6 unsigned long long c;
7 unsigned long period_cycles, duty_cycles, prescale;
8 unsigned int period_ms;
9 bool enable = test_bit(PWMF_ENABLED, &pwm->flags);
10 int wait_count = 0, fifoav;
11 u32 cr, sr;
12
......
42

/*
第43~62行,根据参数duty_ns和period_ns来计算出应该写入到寄存器里面的值 duty_cycles
和 period_cycles。
*/
43 c = clk_get_rate(imx->clk_per);
44 c = c * period_ns;
45 do_div(c, 1000000000);
46 period_cycles = c;
47
48 prescale = period_cycles / 0x10000 + 1;
49
50 period_cycles /= prescale;
51 c = (unsigned long long)period_cycles * duty_ns;
52 do_div(c, period_ns);
53 duty_cycles = c;
54
55 /*
56 * according to imx pwm RM, the real period value should be
57 * PERIOD value in PWMPR plus 2.
58 */
59 if (period_cycles > 2)
60 period_cycles -= 2;
61 else
62 period_cycles = 0;
63
64 writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);/*第 64 行,将计算得到的 duty_cycles 写入到 PWMSAR 寄存器中,设置 PWM 的占空比*/
65 writel(period_cycles, imx->mmio_base + MX3_PWMPR);/*第 65 行,将计算得到的 period_cycles 写入到 PWMPR 寄存器中,设置 PWM 的频率。*/
66
67 cr = MX3_PWMCR_PRESCALER(prescale) |
68 MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
69 MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH;
70
71 if (enable)
72 cr |= MX3_PWMCR_EN;
73
74 writel(cr, imx->mmio_base + MX3_PWMCR);
75
76 return 0;
77 }

 二、PWM驱动编写

1、修改设备树

1)添加 GPIO1_IO04 引脚信息

在 iomuxc 节点下添加 GPIO1_IO04 的引脚信息,如下所示:

1 pinctrl_pwm3: pwm3grp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0
4 >;
5 };

2)向 pwm3 节点追加信息


        在 imx6ull-alientek-emmc.dts 文件中向 pwm3 节点追加一些内容,在 imx6ull-alientek-emmc.dts 文件中加入如下所示内容:

1 &pwm3 {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_pwm3>;/*第 3 行, pinctrl-0 属性指定 PWM3 所使用的输出引脚对应的 pinctrl 节点,*/
4 clocks = <&clks IMX6UL_CLK_PWM3>,/*第 4 和 5 行,设置时钟,第 4 行设置 ipg 时钟,第 5 行设置 per 时钟。*/
5 <&clks IMX6UL_CLK_PWM3>;
6 status = "okay";
7 };

         3)屏蔽掉其他复用的 IO 

2、使能PWM驱动

        -> Device Drivers

                -> Pulse-Width Modulation (PWM) Support

                        -> <*> i.MX PWM support

三、PWM驱动测试

        使用新的设备树启动系统,然后将开发板 JP2 排针上的 GPIO_4(GPIO1_IO04)引脚连接到示波器上,通过示波器来查看 PWM 波形图。

        直接在用户层来配置 PWM,进入目录/sys/class/pwm 中:

1、调出 pwmchip2 的 pwm0 子目录

echo 0 > /sys/class/pwm/pwmchip2/export

         执行完成会在 pwmchip2 目录下生成一个名为“pwm0”的子目录

2、使能 PWM3

echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable

3、设置 PWM3 的频率

echo 50000 > /sys/class/pwm/pwmchip2/pwm0/period

 4、设置 PWM3 的占空比

不能直接设置占空比,而是设置的一个周期的 ON 时间,也就是高电平时间:

echo 10000 > /sys/class/pwm/pwmchip2/pwm0/duty_cycle

5、PWM背光设置

必要的属性如下:

compatible: 内容必须为“pwm-backlight”,通过这个可以匹配到内核自带的 PWM 背光驱动,驱动文件为 drivers/video/backlight/pwm_bl.c,这里就不去分析驱动源码了。

pwms: 此属性指定背光使用哪一路 PWM,以及 PWM 相关的属性。

brightness-levels: 背光等级数组,范围 0~255,对应占空比为 0%~100%。数组内的值必须从 0 开始,也就是 0%占空比,最后一个值必须是 255,也就是 100%占空比。数组中间值的个数以及值大小可以自行定义。

default-brightness-level: 默认的背光等级,也就是 brightness-levels 属性中第几个值,注意这里是数索引编号,不是具体的数值!

power-supply: 支持的电压,此属性可以不需要。

        以正点原子 ALPHA 开发板为例,看一下 PWM 背光节点是如何设置的,打开 imx6ullalientek-emmc.dts,找到如下所示节点内容:

1 backlight {
2 compatible = "pwm-backlight";/*compatible 属性必须为“pwm-backlight”*/
3 pwms = <&pwm1 0 5000000>;
/*
pwms 属性指定背光所使用的 pwm 通道,第一个参数指定使用 pwm1,由于
I.MX6ULL 的 PWM 只有一个通道,因此这里为 0。最后一个参数是 PWM 周期,单位为 ns,这
里 PWM 周期为 5000000ns,频率为 200Hz。
*/
4 brightness-levels = <0 4 8 16 32 64 128 255>;/*第 4 行,背光等级数组,一共 8 个等级,索引编号从 0 到 7。*/
5 default-brightness-level = <7>;/*第 5 行,背光默认处于第 7 等级,也就是 255,为 100%占空比。*/
6 status = "okay";
7 };

本笔记为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,未经允许不得用于商业用途。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 当使用Linux时,PWM驱动程序是一种非常常见的驱动程序类型,用于控制数字信号,如LED的亮度,风扇的速度等等。 以下是一些在Linux中使用PWM驱动程序的步骤: 1. 确认您的系统支持PWM驱动程序。要检查此功能,请执行以下操作: $ ls /sys/class/pwm 如果输出结果为“pwmchip0”,则您的系统支持PWM驱动程序。 2. 选择一个PWM引脚。要选择PWM引脚,请执行以下操作: $ ls /sys/class/pwm/pwmchip0 这将显示您的系统中可用的所有PWM通道。选择一个通道,并记下其索引号。 3. 设置PWM参数。要设置PWM参数,请执行以下操作: $ cd /sys/class/pwm/pwmchip0/pwm0 $ echo 1000000 > period $ echo 500000 > duty_cycle $ echo 1 > enable 这将设置PWM周期为1秒(1000000微秒),占空比为50%(500000微秒)并启用PWM信号。 4. 您还可以使用PWM驱动程序的API来编写自己的PWM控制程序。这些API包括pwm_request,pwm_config和pwm_enable等。 这些API可用于从用户空间直接控制PWM驱动程序,以实现更高级的PWM控制功能。 希望这些步骤对您有所帮助! ### 回答2: PWM(Pulse Width Modulation)顾名思义是一种脉冲宽度调制技术,在电子工程领域中广泛应用于数字信号处理中。PWM技术可以通过改变脉冲信号的脉冲宽度,来控制电路中的电压、电流或功率大小。PWM控制技术在电机调速、LED亮度调节、UPS、DC-DC变换器等各种电子设备中均有较为广泛的应用。 在linux内核中,PWM是通过相应的PWM驱动程序来控制的。PWM驱动程序所做的工作主要包括以下几个方面: 第一,初始化PWM。在系统启动过程中,通过相应的平台设备驱动程序,向内核注册PWM设备,设置相应的参数,包括PWM的周期和脉冲宽度等信息。 第二,控制PWM输出。PWM输出可以通过向PWM设备文件写入相应的数值来完成。输出数值由占空比和周期组成,通过改变参数可以控制PWM输出的占空比和频率。 第三,提供用户接口。PWM驱动程序同时还提供相应的用户接口文件,用户可以通过读写PWM设备对应的文件来实现对PWM的控制。例如,在/sys/class/pwm目录下,可以找到相应的PWM设备子目录,通过读写相应的文件即可对PWM进行控制。 第四,支持中断处理。在PWM输出过程中,一般需要在到达周期末尾时触发中断,进行相应的处理。PWM驱动程序也需要对中断处理进行支持。 总之,PWM技术在现代电子领域中应用非常广泛,linux PWM驱动程序也是相应的关键组成部分之一。通过PWM驱动程序的实现,我们可以轻松地控制LED亮度、电机转速等各种应用场景。而PWM驱动程序在日后的不断发展和完善中,也将不断地被改进和优化,为现代电子技术的不断进步提供更为稳定可靠的支持。 ### 回答3: PWM(Pulse Width Modulation)或脉宽调制是一种控制电子器件的技术,通过调整脉冲的宽度和周期来达到控制电子设备的功率、速度和亮度的目的。Linux PWM驱动则是一种跨平台的PWM控制器,使用Linux的内核从驱动器中执行PWM协议操作。 Linux PWM驱动是基于Linux系统内核提供的高级驱动程序接口(API)实现对PWM设备的控制和管理。Linux PWM驱动使得PWM设备的编程接口更加简单和便捷,可以自由配置PWM信号的频率、占空比和周期等参数以控制输出信号。 Linux PWM驱动通常被用于控制一些设备,比如LED、电机、声音发生器和传感器等等。例如,我们可以通过PWM控制器驱动LED的亮度和颜色,也可以通过PWM控制器驱动电机来调节速度和方向。 要在Linux中使用PWM驱动程序,需要安装相应的内核模块并配置设备驱动程序参数。LinuxPWM API接口包括PWM子系统,该子系统提供了PWM设备的管理、底层硬件驱动和访问函数等。此外,Linux的设备树(Device Tree)也提供一种类似于BIOS的抽象层,用于描述硬件平台的配置,包括PWM控制器等设备的物理和逻辑地址。 总之,Linux PWM驱动是一种实现PWM控制的程序接口,可用于管理和控制PWM设备。通过LinuxPWM API和设备树,开发人员可以轻松地访问硬件PWM信号并实现有用的控制功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值