当前位置:   article > 正文

【正点原子Linux连载】第二十三章 Linux PWM驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

【正点原子Linux连载】第二十三章 Linux PWM驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第二十三章 Linux PWM驱动实验

PWM是很常用到功能,我们可以通过PWM来控制电机速度,也可以使用PWM来控制LCD的背光亮度。本章我们就来学习一下如何在Linux下进行PWM驱动开发。

23.1 PWM驱动简析
PWM全称是Pulse Width Modulation,也就是脉冲宽度调制,PWM信号如图23.1.1所示:
在这里插入图片描述

图23.1.1 PWM信号
PWM信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是1秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分之表示,如果一个周期内全是低电平那么占空比就是0%,如果一个周期内全是高电平那么占空比就是100%。
我们给LCD的背光引脚输入一个PWM信号,这样就可以通过调整占空比的方式来调整LCD背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度,重点就在于PWM信号的产生和占空比的控制。
23.1.1 设备树下的PWM控制器节点
1、PWM通道与引脚
RK3568有4个PWM模块,每个PWM模块有4个通道,因此一共有16路PWM:PWM0~PWM15,PWM模块与PWM通道以及对应的引脚关系如表23.1.1.1所示:
在这里插入图片描述

图23.1.1.1 RK3568 PWM通道与引脚
其中PWM3,PWM7,PWM11和PWM15可用用于IR。
2、PWM简介
RK3568有4个PWM模块,每个PWM模块的功能基本相同,这些PWM通道的特性如下:
①、每个PWM模块有4个通道。
②、支持捕获模式:
—测量输入波形高低电平的有效周期。
—输入波形极性变化的时候产生中断信号。
—32位高电平捕获寄存器。
—32位低电平捕获寄存器。
—32位当前值寄存器。
—捕获结果可以保存到FIFO中,FIFO深度为8,FIFO中的数据可以通过CPU或DMA读取。
③、支持连续以及单次模式:
— 32位的周期计数寄存器。
— 32位的占空比寄存器。
— 32位当前值寄存器。
— 输出PWM极性以及占空比可调。
— 可配置中央对齐或左对齐模式。
3、PWM设备节点
接下来看一下PWM的设备树,RK3568的PWM设备树绑定信息文档为:Documentation/devicetree/bindings/pwm/pwm-rockchip.txt,我们简单总结一下PWM节点信息。
①、必须的参数:
compatible:必须是“rockchip,-pwm”形式的,比如:rockchip,rk3288-pwm、rockchip,rk3568-pwm等。对于RK3568而言,有效的是rockchip,rk3288-pwm。
reg:PWM控制器物理寄存器基地址,比如对于PWM0来说,这个地址为0XFDD70000,这个可以在RK3568的数据手册上找到。
clocks:时钟源。
#pwm-cells:瑞芯微的芯片必须是2或者3,对于RK3568来说是3。
了解完PWM的绑定文档以后,我们来看一下RK3568实际的定时器节点,打开rk3568.dtsi,找到名为“pwm15”的设备节点,内容如下:
示例代码23.1.1.1 PWM15节点

1 pwm15: pwm@fe700030 {
2         compatible = "rockchip,rk3568-pwm", "rockchip,rk3328-pwm";
3         reg = <0x0 0xfe700030 0x0 0x10>;
4         interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>,
5                      <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
6         #pwm-cells = <3>;
7         pinctrl-names = "active";
8         pinctrl-0 = <&pwm15m0_pins>;
9         clocks = <&cru CLK_PWM3>, <&cru PCLK_PWM3>;
10        clock-names = "pwm", "pclk";
11        status = "disabled";
12};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
RK3568的PWM节点的compatible属性为“rockchip,rk3568-pwm”和“”rockchip,rk3328-pwm,我们可以在linux内核源码中搜索这两个字符串就可以找到PWM驱动文件,这个文件为:drivers/pwm/pwm-rockchip.c。
  • 1

23.1.2 PWM子系统
Linux内核提供了个PWM子系统框架,编写PWM驱动的时候一定要符合这个框架。PWM子系统的核心是pwm_chip结构体,定义在文件include/linux/pwm.h中,定义如下:

示例代码23.1.2.1 pwm_chip结构体
1  struct pwm_chip {
2  		struct device       	*dev;
3   	struct list_head    	list;
4   	const struct pwm_ops 	*ops;
5   	int         			base;
6   	unsigned int        	npwm;
7  
8   	struct pwm_device   	*pwms;
9  
10  	struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
11                      const struct of_phandle_args *args);
12  	unsigned int        	of_pwm_n_cells;
13  	bool            		can_sleep;
14 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

第4行,pwm_ops结构体就是PWM外设的各种操作函数集合,编写PWM外设驱动的时候需要开发人员实现。pwm_ops结构体也定义在pwm.h头文件中,定义如下:
示例代码23.1.2.2 pwm_ops结构体

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 #ifdef CONFIG_DEBUG_FS
17  	void (*dbg_show)(struct pwm_chip *chip,
18                      struct seq_file *s);
19 #endif
20  	struct module       *owner;
21 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

pwm_ops中的这些函数不一定全部实现,但是像config、enable和disable这些肯定是需要实现的,否则的话打开/关闭PWM,设置PWM的占空比这些就没操作了。
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 成功;负数 失败。
23.1.3 PWM驱动源码分析
我们简单分析一下Linux内核自带的RK3568 PWM驱动,驱动文件前面都说了,是pwm-rockchip.c这个文件。打开这个文件,可以看到,这是一个标准的平台设备驱动文件,有如下所示:
示例代码23.1.3.1 RK3568 PWM平台驱动

1  static const struct of_device_id rockchip_pwm_dt_ids[] = {
2   	{ .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
3   	{ .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
4   	{ .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
5   	{ /* sentinel */ }
6  };
7  ......
8  static struct platform_driver rockchip_pwm_driver = {
9   	.driver = {
10      	.name = "rockchip-pwm",
11      	.of_match_table = rockchip_pwm_dt_ids,
12  	},
13  	.probe = rockchip_pwm_probe,
14  	.remove = rockchip_pwm_remove,
15 };
16 module_platform_driver(rockchip_pwm_driver);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

第2~4行,当设备树PWM节点的compatible属性值为“rockchip,rk2928-pwm”、“rockchip,rk3288-pwm”和“rockchip,vop-pwm”中的某一个的话就会匹配此驱动。
第13行,当设备树节点和驱动匹配以后rockchip_pwm_probe函数就会执行。
在看rockchip_pwm_probe函数之前先来看下rockchip_pwm_chip stm32_pwm结构体,这个结构体是瑞芯微官方创建的针对瑞芯微芯片的PWM结构体,这个结构体会贯穿整个PWM驱动,起到灵魂的作用。rockchip_pwm_chip结构体内容如下:
示例代码23.1.3.2 rockchip_pwm_chip结构体

1 struct rockchip_pwm_chip {
2   	struct pwm_chip chip;
3   	struct clk *clk;
4   	const struct rockchip_pwm_data *data;
5   	void __iomem *base;
6 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
重点看一下第2行,这是一个pwm_chip结构体成员变量chip,前面说了,PWM子系统的核心就是pwm_chip。
  • 1

rockchip_pwm_probe函数如下(有缩减):
示例代码23.1.3.3 rockchip_pwm_probe函数

1  static int rockchip_pwm_probe(struct platform_device *pdev)
2  {
3   const struct of_device_id *id;
4   struct rockchip_pwm_chip *pc;
5   struct resource *r;
6   int ret;
7  
8   id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
9   if (!id)
10      return -EINVAL;
11 
12  pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
13  if (!pc)
14      return -ENOMEM;
15 
16  r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
17  pc->base = devm_ioremap_resource(&pdev->dev, r);
18  if (IS_ERR(pc->base))
19      return PTR_ERR(pc->base);
20 
21  pc->clk = devm_clk_get(&pdev->dev, NULL);
22  if (IS_ERR(pc->clk))
23      return PTR_ERR(pc->clk);
24 
25  ret = clk_prepare(pc->clk);
26  if (ret)
27      return ret;
28 
29  platform_set_drvdata(pdev, pc);
30 
31  pc->data = id->data;
32  pc->chip.dev = &pdev->dev;
33  pc->chip.ops = pc->data->ops;
34  pc->chip.base = -1;
35  pc->chip.npwm = 1;
36 
37  if (pc->data->ops->set_polarity) {
38      pc->chip.of_xlate = of_pwm_xlate_with_flags;
39      pc->chip.of_pwm_n_cells = 3;
40  }
41 
42  ret = pwmchip_add(&pc->chip);
43  if (ret < 0) {
44      clk_unprepare(pc->clk);
45      dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
46  }
47 
48  return ret;
49 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

第12行,pc是一个rockchip_pwm_chip类型的结构体指针变量,这里为其申请内存。rockchip_pwm_chip结构体有个重要的成员变量chip,chip是pwm_chip类型的。所以这一行就引出了PWM子系统核心部件pwm_chip,稍后的重点就是初始化chip。
第32~35行,初始化pc的chip成员变量,也就是初始化pwm_chip!第33行设置pwm_chip的ops操作集为pc->data->ops。根据示例代码23.1.3.1中的compatible属性可以,RK3568对应的data是pwm_data_v2,因此pwm_data_v2源码如下:
示例代码23.1.3.4 pwm_data_v2源码

1  static const struct rockchip_pwm_data pwm_data_v2 = {
2   	.regs = {
3       		.duty = 0x08,
4       		.period = 0x04,
5       		.cntr = 0x00,
6       		.ctrl = 0x0c,
7   	},
8   	.prescaler = 1,
9   	.ops = &rockchip_pwm_ops_v2,
10  	.set_enable = rockchip_pwm_set_enable_v2,
11 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
第9行,rockchip_pwm_ops_v2就是pwm_chip的ops函数,也就是RK3568的ops函数。
继续回到示例代码23.1.3.3中,第49行使用pwmchip_add函数向内核添加pwm_chip,这个就是rockchip_pwm_probe函数的主要工作。
我们重点来看一下rockchip_pwm_ops_v2,定义如下:
  • 1
  • 2
  • 3

示例代码23.1.3.5 rockchip_pwm_ops_v2操作集合

1 static const struct pwm_ops rockchip_pwm_ops_v2 = {
2   	.config = rockchip_pwm_config,
3   	.set_polarity = rockchip_pwm_set_polarity,
4   	.enable = rockchip_pwm_enable,
5   	.disable = rockchip_pwm_disable,
6   	.owner = THIS_MODULE,
7 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
第2行rockchip_pwm_config就是最终的PWM设置函数,我们在应用中设置的PWM频率和占空比最终就是由rockchip_pwm_config函数来完成的,此函数会最终操作RK3568相关的寄存器。
rockchip_pwm_config函数源码如下:
  • 1
  • 2

示例代码23.1.3.6 rockchip_pwm_config函数

1  static int rockchip_pwm_config(struct pwm_chip *chip, 
2             struct pwm_device *pwm,int duty_ns, int period_ns)
3  {
4   struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
5   unsigned long period, duty;
6   u64 clk_rate, div;
7   int ret;
8  
9   clk_rate = clk_get_rate(pc->clk);
10 
11  /*
12   * Since period and duty cycle registers have a width of 32
13   * bits, every possible input period can be obtained using the
14   * default prescaler value for all practical clock rate values.
15   */
16  div = clk_rate * period_ns;
17  do_div(div, pc->data->prescaler * NSEC_PER_SEC);
18  period = div;
19 
20  div = clk_rate * duty_ns;
21  do_div(div, pc->data->prescaler * NSEC_PER_SEC);
22  duty = div;
23 
24  ret = clk_enable(pc->clk);
25  if (ret)
26      return ret;
27 
28  writel(period, pc->base + pc->data->regs.period);
29  writel(duty, pc->base + pc->data->regs.duty);
30  writel(0, pc->base + pc->data->regs.cntr);
31 
32  clk_disable(pc->clk);
33 
34  return 0;
35 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
第16~18行,根据设置的频率,计算出RK3568 PWM的PERIOD寄存器。
第20~22行,根据设置的占空比,计算出DUTY寄存器的值。
第28~30行,设置PWM外设的PERIOD、DUTY和CNT寄存器。
至此,RK3568的PWM驱动基本分析到这里。
  • 1
  • 2
  • 3
  • 4

23.2 PWM驱动编写
23.2.1 修改设备树
PWM驱动就不需要我们再编写了,瑞芯微已经写好了,前面我们也已经详细的分析过这个驱动源码了。我们在实际使用的时候只需要修改设备树即可,ATK-DLRK3568开发板上的JP11排针引出了GPIO3_C5这个引脚,这个引脚可以用作PWM15_IR_M0,如图23.2.1.1所示:
在这里插入图片描述

图23.2.1.1 GPIO3_C5引脚
GPIO3_C5可以作为PWM3的通道3的PWM输出引脚,也就是PWM15_IR_M0,所以我们需要在设备树里面添加GPIO3_C5的引脚信息以及PWM15外设信息。
1、添加GPIO3_C5引脚信息
PWM的引脚配置瑞芯微已经帮我们做好了,打开rk3568-pinctrl.dtsi文件,找到如下所示内容:
示例代码23.2.1.1 TIM1 PWM引脚信息

1 pwm15 {
2         /omit-if-no-ref/
3         pwm15m0_pins: pwm15m0-pins {
4                 rockchip,pins =
5                         /* pwm15_irm0 */
6                         <3 RK_PC5 1 &pcfg_pull_down>;
7         };
8 
9         /omit-if-no-ref/
10        pwm15m1_pins: pwm15m1-pins {
11                rockchip,pins =
12                        /* pwm15_irm1 */
13                        <4 RK_PC3 1 &pcfg_pull_none>;
14        };
15};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
可以看出瑞芯微官方对GPIO3_C5写了两个配置,第6行是把GPIO3_C5配置为PWM15_IR_M0引脚,但是默认没有上下拉!大家根据实际情况修改为上下拉配置即可。本章实验,我们选择pcfg_pull_down,也就是将GPIO3_C5配置为PWM15_IR_M0,默认下拉。
2、向pwm15节点追加信息
前面已经讲过了,rk3568.dtsi文件中已经有了“pwm15”节点,但是这个节点默认是disable的,还不能直接使用,所以需要在rk3568-atk-evb1-ddr4-v10.dtsi文件中向打开pwm15节点。
  • 1
  • 2
  • 3

示例代码23.2.1.3 向timers1添加的内容

1&pwm15 {
2        status = "okay";
3}
  • 1
  • 2
  • 3
第2行,status改为okay,也就是使能PWM15。
  • 1

23.2.2 使能PWM驱动
瑞芯微官方的Linux内核已经默认使能了PWM驱动,所以不需要我们修改,但是为了学习,我们还是需要知道怎么使能。打开Linux内核配置界面,按照如下路径找到配置项:
-> Device Drivers │
-> Pulse-Width Modulation (PWM) Support (PWM [=y])
-> <*> Rockchip PWM support //选中
配置如图23.2.2.1所示:
在这里插入图片描述

图23.2.2.1 PWM配置项
23.3 PWM驱动测试
1、确定PWM15对应的pwmchipX文件
使用新的设备树启动系统,然后将开发板上的GPIO3_C5引脚连接到示波器上,通过示波器来查看PWM波形图。我们可以直接在用户层来配置PWM,进入目录/sys/class/pwm中,如图23.3.1所示:
在这里插入图片描述

图23.3.1 当前系统下的PWM外设
注意!图23.3.1中有个pwmchip0~ pwmchip3,但是我们并不知道哪个是PWM15的。我们可以通过查看pwmchip0~pwmchip3的外设基地址哪个和PWM15一样,那么哪个就是PWM15的。
ls -l
进入到pwm执行上面的ls -l指令,以后会打印出其链接的路径,如图23.3.2所示:
在这里插入图片描述

图23.3.2 pwmchip1路径名称
从图23.3.2可以看出pwmchip3对应的定时器寄存器起始地址为0XFE700030,根据示例代码23.1.1.1中的pwm15节点,可以知道PWM15这个定时器的寄存器起始地址就是0XFE700030。因此,pwmchip1就是PWM15对应的文件。
为什么要用这么复杂的方式来确定定时器对应的pwmchip文件呢?因为当RK3568开启多个PWM以后,其pwmchip文件就会变!
2、调出pwmchip15的pwm0子目录
输入如下命令打开pwmchip15的pwm0子目录
echo 0 > /sys/class/pwm/pwmchip3/export
执行完成会在pwmchip3目录下生成一个名为“pwm0”的子目录,如图23.3.3所示:
在这里插入图片描述

图23.3.3 新生成的pwm0子目录
2、设置PWM的频率
注意,这里设置的是周期值,单位为ns,比如20KHz频率的周期就是50000ns,输入如下命令:
echo 50000 > /sys/class/pwm/pwmchip3/pwm0/period
3、设置PWM的占空比
这里不能直接设置占空比,而是设置的一个周期的ON时间,也就是高电平时间,比如20KHz频率下20%占空比的ON时间就是10000,输入如下命令:
echo 10000 > /sys/class/pwm/pwmchip3/pwm0/duty_cycle
4、设置PWM极性
设置一下PWM波形的极性,输入如下命令:
echo normal > /sys/class/pwm/pwmchip3/pwm0/polarity
极性设置为normal,也就是duty_cycle为高电平时间。如果要将极性反过来,可以设置为inversed。
5、使能PWM
一定要先设置频率和波特率,最后在开启PWM,否则会提示参数错误!输入如下命令使能PWM:
echo 1 > /sys/class/pwm/pwmchip3/pwm0/enable
设置完成使用示波器查看波形是否正确,正确的话如图23.3.4所示:
在这里插入图片描述

图23.3.4 PWM波形图
从图23.3.4可以看出,此时PWM频率为20KHz,占空比为20%,与我们设置的一致。如果要修改频率或者占空比的话一定要注意这两者时间值,比如20KHz频率的周期值为50000ns,那么你在调整占空比的时候ON时间就不能设置大于50000,否则就会提示你参数无效。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/478962
推荐阅读
相关标签
  

闽ICP备14008679号