如果你对硬件了解不是很深,看了标题可能会一脸懵逼,PWM是什么?别急,在回答这个问题之前,先看看PWM+一个普通的LED灯能实现什么效果:

(动图有点大,请稍等…)

从结果来看可能第一反应是这样的,普通的LED灯只能控制开/关,既然PWM可以控制LED的亮和暗,那它应该是一种电压控制器!其实根本不是那么回事,和电压毛关系都没有。它背后的原理恰恰是它最有意思的地方,它的本领可不仅仅是拿来控制个灯的明暗那么low。

PWM基础

PWM——脉冲宽度调制,顾名思义,就是一个脉冲信号输出源,且方波的周期(period)以及高电平持续时间(duty)是可调的。从软件角度来看,主要关注两个地方:

period,脉冲周期,就是发送一次1和0交替的完整时间

duty,占空比,就是一个脉冲周期内1占了多长时间。

有时候,还需要关注1秒钟发送多少个脉冲信号,即频率,不过对于本文要控制的led灯亮度而言,频率几乎可以忽略。关于PWM原理,如果还是云里雾里,就看一下这个视频介绍,非常简单。

不过先提醒以下,PWM的实际应用非常广,有红外遥控、音频控制、通信等,最常见的要数直流电机控制,以及手机上的背光灯亮度。总之,掌握pwm的原理及应用是灰常有必要滴。

树莓派上的PWM

树莓派上的PWM比较坑,明明很简单的东西我调了两天,网上99%的教程都是各种应用层的脚本或writingPi的函数调用,和Linux内核的边都沾不上,而且其中绝大多数还是相互复制粘贴。所以真正有用的资料寥寥无几,一直卡在pwm_request却获取不到pwm资源,当然最主要原因还是我对技术细节的不理解,好在发现了一个老外的博客:https://jumpnowtek.com/rpi/Using-the-Raspberry-Pi-Hardware-PWM-timers.html

在官方的《BCM2835外围指南》第9章说得很清楚,处理器内部集成了两个独立的PWM控制器,理论上可以达到100MHz的脉冲频率。树莓派扩展接口共有4个GPIO引出PWM,具体为:

PWM通道

GPIO号

物理引脚

复用功能

PWM0

GPIO12

32

Alt Fun 0

PWM1

GPIO13

33

Alt Fun 0

PWM0

GPIO18

12

Alt Fun 5

PWM1

GPIO19

35

Alt Fun 5

第一步,启用pwm(默认情况下未启用)

简而言之,你无法通过Linux内核API获取到PWM资源,因为在树莓派官方的设备树配置(/boot/config.txt)里并没有通知内核要启用pwm。因此第一步自然是让内核支持pwm驱动,使用如下命令:

1

2

3

4

5

6

7

8

9

10

11

12

13# vim打开/boot/config.txt

# 在最后一行加入: dtoverlay=pwm

# 保存退出,重启

philon@rpi:~ $ sudo vim /boot/config.txt

philon@rpi:~ $ sudo reboot

# 重启之后,有两种方式确认pwm已启用

philon@rpi:~ $ lsmod | grep pwm

pwm_bcm2835 16384 1 # 方式1: 加载了官方pwm驱动

philon@rpi:~ $ ls /sys/class/pwm/

pwmchip0 # 方式2: sysfs里可以看到pwmchip0目录

第二步,搭建硬件环境

非常简单,LED的正极拉到一个PWM通道,负极随便找一个GND接上。

如图所示,将三色LED的绿灯脚接到GPIO18,也就是PWM0通道,再随便找一个GND接上即可,和第一章的点亮一个LED一样,关键看软件如何操作了。

使用命令行控制PWM

根据之前的硬件接线,LED与树莓派的PWM0通道相连,所以使能pwm0即可点亮led,大体步骤为:

请求pwm0资源

设置脉冲周期

设置占空比

打开pwm0

命令行控制pwm其实和gpio大同小异,都是通过sysfs这个虚拟文件系统完成的。

1

2

3

4

5

6

7

8

9

10

11philon@rpi:~ $ cd /sys/class/pwm/pwmchip0/ # 进入pwm资源目录

philon@rpi:~ $ echo 0 > export # 加载pwm0资源

philon@rpi:~ $ echo 10000000 > pwm0/period # 设置脉冲周期为10ms(100Hz)

philon@rpi:~ $ echo 8000000 > pwm0/duty_cycle # 设置占空比为8ms

philon@rpi:~ $ echo 1 > pwm0/enable # 开始输出

# 可以自行调整脉冲周期和占空比,得到不同的亮度

# 如果玩够了,记得释放资源

philon@rpi:~ $ echo 0 > pwm0/enable # 关闭输出

philon@rpi:~ $ echo 0 > unexport # 卸载pwm0资源

经过上面这番犀利操作,只要你够虔诚,就会看见一束绿光闯入你的眼里,心里,脑海里。

Linux驱动控制PWM

在实现pwm调光led之前,需要先交代清楚:

由于本驱动仅仅是调节led的亮度,关于pwm的占空比和脉冲周期两个参数,其实只调节占空比就够了。你想一下,脉冲周期长短,无非就是led闪烁频率,只要人眼看着不闪,再快有个毛用。因此本驱动将脉冲周期固定为1ms,即1KHz,而占空比的取值为0~1000us,说白了,就是led从最暗到最亮一共分为一千个档位。

内核为pwm提供了标准的API接口,需要掌握这几个:

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// PWM channel object

struct pwm_device {

trueconst char *label; // name of the PWM device

trueunsigned long flags; // flags associated with the PWM device

trueunsigned int hwpwm; // per-chip relative index of the PWM device

trueunsigned int pwm; // global index of the PWM device

truestruct pwm_chip *chip; // PWM chip providing this PWM device

truevoid *chip_data; // chip-private data associated with the PWM device

truestruct pwm_args args; // PWM arguments

truestruct pwm_state state; // curent PWM channel state

};

/**

* 通过pwm通道号获取pwm通道对象

* @pwm_id 通道号

* @label pwm通道别名

*/

struct pwm_device *pwm_request(int pwm_id, const char *label);

/**

* 释放pwm通道对象

*/

void pwm_free(struct pwm_device *pwm);

/**

* 设置pwm通道的相关参数

* @duty_ns 以纳秒为单位的占空比

* @period_ns 以纳秒为单位的脉冲周期

*/

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)

/**

* 打开pwm通道,开始输出脉冲

*/

int pwm_enable(struct pwm_device *pwm)

/**

* 关闭pwm通道,停止输出脉冲

*/

void pwm_disable(struct pwm_device *pwm)

接着,实现pwmled的驱动吧:

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

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85/********************* pwmled.h *********************/

#define PWMLED_MAX_BRIGHTNESS 1000

typedef enum {

PWMLED_CMD_SET_BRIGHTNESS = 0x1,

PWMLED_CMD_GET_BRIGHTNESS,

} pwmled_cmd_t;

/********************* pwmled.c *********************/

#include

#include

#include

#include

#include "pwmled.h"

MODULE_LICENSE("Dual MIT/GPL");

MODULE_AUTHOR("Phlon | https://ixx.life");

#define PWMLED_PERIOD 1000000 // 脉冲周期固定为1ms

static struct {

struct pwm_device* pwm;

unsigned int brightness;

} pwmled;

long pwmled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){

switch (cmd) {

case PWMLED_CMD_SET_BRIGHTNESS:

// 所谓调节亮度,就是配置占空比,然后使能pwm0

pwmled.brightness = arg < PWMLED_MAX_BRIGHTNESS ? arg : PWMLED_MAX_BRIGHTNESS;

pwm_config(pwmled.pwm, pwmled.brightness * 1000, PWMLED_PERIOD);

if (pwmled.brightness > 0) {

pwm_enable(pwmled.pwm);

} else {

pwm_disable(pwmled.pwm);

}

case PWMLED_CMD_GET_BRIGHTNESS:

return pwmled.brightness;

default:

return -EINVAL;

}

return pwmled.brightness;

}

static struct file_operations fops = {

.owner = THIS_MODULE,

.unlocked_ioctl = pwmled_ioctl,

};

static struct miscdevice dev = {

.minor = 0,

.name = "pwmled",

.fops = &fops,

.nodename = "pwmled",

.mode = 0666,

};

int __init pwmled_init(void){

// 请求PWM0通道

struct pwm_device* pwm = pwm_request(0, "pwm0");

if (IS_ERR_OR_NULL(pwm)) {

printk(KERN_ERR "failed to request pwm\n");

return PTR_ERR(pwm);

}

pwmled.pwm = pwm;

pwmled.brightness = 0;

misc_register(&dev);

return 0;

}

module_init(pwmled_init);

void __exit pwmled_exit(void){

misc_deregister(&dev);

// 停止并释放PWM0通道

pwm_disable(pwmled.pwm);

pwm_free(pwmled.pwm);

}

module_exit(pwmled_exit);

该驱动会自动创建/dev/pwmled设备节点,然后应用层通过ioctl即可设置和获取灯的亮度等级。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24int main(int argc, char* argv[]){

int fd = open("/dev/pwmled", O_RDWR);

int brightness = 0;

char key = 0;

while ((key = getchar()) != 'q') {

switch (key) {

case '=':

brightness += brightness < PWMLED_MAX_BRIGHTNESS ? 10 : 0;

break;

case '-':

brightness -= brightness > 0 ? 10 : 0;

break;

}

if (ioctl(fd, PWMLED_CMD_SET_BRIGHTNESS, brightness) < 0) {

perror("ioctl");

break;

}

}

close(fd);

return 0;

}

正如本文开头那张动图一样,用户只需要按+/-即可调节led的亮度,so easy~

小结PWM即脉冲宽度调制,可以实时改变脉冲源的占空比和周期时长

树莓派Linux内核默认不加载pwm通道,需要在/boot/config.txt中增加一行dtoverlay=pwm

pwm可以像gpio一样通过命令行对sysfs虚拟文件系统操作,即可控制pwm资源

内核提供了标准的pwm接口,注意通道值的配置是以纳秒为单位

pwm技术应用范围非常广,务必牢牢掌握

树莓派 linux pwm,树莓派驱动开发实战04:PWM呼吸灯相关推荐

  1. 嵌入式linux驱动开发实战教程,嵌入式Linux驱动开发实战视频教程

    嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 咨 ...

  2. Linux驱动开发:字符设备驱动开发实战

    Linux驱动开发:字符设备驱动开发实战 一.工程创建 VSCode 创建工程,设置 C/C++ 配置,导入 linux kernel 源码目录,方便 vscode 写代码自动补全,vscode 配置 ...

  3. IMX6ULL驱动开发实战连载-01搭建开发环境

    哈喽,大家好.我是小仲.板子在3.31号就收到了,但是,由于最近一直很忙,拖到了现在才开始搭建环境.接下来的一段时间,会基于野火IMX6ULL开发板写一系列教程,主要侧重于驱动和内核的调试技巧.这方面 ...

  4. 《Linux嵌入式实时应用开发实战(原书第3版)》——第2章 安装Linux2.1 发行版...

    本节书摘来自华章计算机<Linux嵌入式实时应用开发实战(原书第3版)>一书中的第2章,第2.1节,作者:(美)Doug Abbott 更多章节内容可以访问云栖社区"华章计算机& ...

  5. 《Linux嵌入式实时应用开发实战(原书第3版)》——3.5 Linux文件系统

    本节书摘来自华章计算机<Linux嵌入式实时应用开发实战(原书第3版)>一书中的第3章,第3.5节,作者:(美)Doug Abbott 更多章节内容可以访问云栖社区"华章计算机& ...

  6. 《Linux嵌入式实时应用开发实战(原书第3版)》——1.6 资源

    本节书摘来自华章计算机<Linux嵌入式实时应用开发实战(原书第3版)>一书中的第1章,第1.6节,作者:(美)Doug Abbott 更多章节内容可以访问云栖社区"华章计算机& ...

  7. Linux 中的驱动开发的初学者体会

    Linux 中的驱动开发的初学者体会 很多年前,心里就存下这样一个愿望.就是把Linux 的驱动开发搞清楚. 但是一开始上上这样的开发难度天大了,对着一堆的寄存器发愁. 于是就从简单的STM8,PIC ...

  8. Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析

    SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转 ...

  9. Linux SD卡驱动开发(五) —— SD 卡驱动分析Core补充篇

    Core层中有两个重要函数 mmc_alloc_host 用于构造host,前面已经学习过,这里不再阐述:另一个就是 mmc_add_host,用于注册host 前面探测函数s3cmci_probe, ...

  10. Android 系统(4)---Android HAL层与Linux Kernel层驱动开发简介

    Android HAL层与Linux Kernel层驱动开发简介 近日稍微对Android中的驱动开发做了一些简要的了解,稍稍理清了一下Android驱动开发的套路,总结一下笔记. HAL:Hardw ...

最新文章

  1. 启明云端分享| ESP32-S2直驱USB摄像头
  2. python集合类型是一种具体的数据类型_Python3基础语法之集合类型
  3. Remove Duplicates from Sorted Array II leetcode java
  4. 程序员一定要提升技术之外的能力
  5. 平时有没有使用xml和json
  6. 【转】随机函数 rand() srand() 以及seed的原理
  7. PHPWAMP开机自启异常,服务器重启后Apache等服务不会自启的原因分析
  8. java地图 热力图,Spring Boot+高德地图热力图静态数据展示
  9. linux ram构架 C#编译器,如何使用imdisk和C#创建RAM磁盘?
  10. i春秋Web渗透测试工程师(初级)学习笔记(第三章)
  11. CCF关于举办CSP-J1 CSP-S1 初赛的报名通知
  12. Rxjava+retrofit+okHttp+mvp网络请求数据
  13. Linux系统引导过程及排除启动故障
  14. 25个常用的防火墙规则
  15. STM8的C语言编程(14)--+PWM
  16. 足球运动员的数据分析实战(python)
  17. VSCode 代码块/全文 折叠/展开 快捷键
  18. [转]PHP编码规范
  19. 我的世界服务器聊天显示坐标,我的世界端游怎么显示坐标
  20. AIMD 为什么收敛(tcp reno/cubic 为什么好)

热门文章

  1. flex的dataGrid:用checkbook和弹出窗口修改,返回修改本行
  2. PWM的占空比、分辨率
  3. Frogger(图论,最短路径)
  4. java 502错误_nginx 502 超时错误解决(java版本)
  5. PS模仿欢乐颂电视剧海报的水彩效果
  6. 【转】地址线和数据线的计算
  7. 皮克公式 Peake‘s theorem
  8. 华为云虚拟主机的防火墙设置
  9. 运维工程师遇到的运维事件_运维、运维工程师的相关知识随记
  10. 程序设计中的基本概念