《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
《Linux驱动开发(十四)—USB驱动开发学习(键盘+鼠标)》
《Linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》
《Linux驱动开发(十六)—块设备驱动》
今天来学习一下PWM,这个东西在控制电动机方面还是挺有作用,万一以后用到的上呢。

脉宽调制(PWM,Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
PWM值是在一个周期内,开关管导通时间长短相加的平均值。导通时间越长,则直流输出的平均值越大;
PWM占空比是一个周期内,导通时间与周期时间的一个比值。

根据面积等效法则,可以通过对改变脉冲的时间宽度,来等效的获得所需要合成的相应幅值和频率的波形;
例如模拟正弦波,就是不停改变占空比,让每一时刻的等效电压按照正弦波的波形变化。


pwm的频率:
是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
也就是说一秒钟PWM有多少个周期
单位: Hz
表示方式: 50Hz 100Hz
pwm的周期:
T=1/f
周期=1/频率
50Hz = 20ms 一个周期
如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
占空比:
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%
摘自《PWM原理 PWM频率与占空比详解》。
里面还介绍了如何控制LED产生呼吸效果和舵机的转动角度。后续我们要深入学习。

树莓派是支持PWM的,看了一下补充文档中的内容,给了例子。
BCM2835 PWM controller (Raspberry Pi controller)
Required properties:
- compatible: should be "brcm,bcm2835-pwm"
- reg: physical base address and length of the controller's registers
- clocks: This clock defines the base clock frequency of the PWM hardware
system, the period and the duty_cycle of the PWM signal is a multiple of
the base period.
- #pwm-cells: Should be 3. See pwm.yaml in this directory for a description of
the cells format.
Examples:
pwm@2020c000 {
compatible = "brcm,bcm2835-pwm";
reg = <0x2020c000 0x28>;
clocks = <&clk_pwm>;
#pwm-cells = <3>;
};
clocks {
....
clk_pwm: pwm {
compatible = "fixed-clock";
reg = <3>;
#clock-cells = <0>;
clock-frequency = <9200000>;
};
....
};
其实这个配置,树莓派已经给配置好了,只需要打开就可以。

并且开发板上已经带了驱动了
root@raspberrypi:/sys/class/pwm# ls /lib/modules/5.15.55-v7+/kernel/drivers/pwm/
pwm-bcm2835.ko pwm-pca9685.ko pwm-raspberrypi-poe.ko
修改一下设备树,看看能否加载驱动。
这个设备树如何修改,其实很简单,只需要加上下面
&pwm {
status = "okay";
};
至于这个调用关系,我也是找了很久,根据bcm2837的数据手册,找到这个地址

然后我们看一下bcm283x.dtsi被谁包含

再看bcm2837.dtsi被谁包含

正好这个bcm2710.dtsi就在我们使用的设备树中包含。
所以直接打开pwm就可以了。

其实在原始定义部分是这样的

然后更新dts,重启设备
root@raspberrypi:/home/pgg# cd /sys/class/pwm/
root@raspberrypi:/sys/class/pwm# ls
pwmchip0

操作方法,参考《linux操作PWM命令》
生成目录
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 1 > /sys/class/pwm/pwmchip0/export
会生成
下面有几个文件可以配置
使用例子:配置一个50%占空比pwm。
设置PWM一个周期的时间,单位为ns
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period
设置PWM一个周期中“ON”的时间,单位为ns,占空比为50%
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle
使能 PWM
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
关闭 PWM
root@raspberrypi:/sys/class/pwm/pwmchip0# echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable

之前就一直在思考,如果两个驱动怎么传递数据,难道还需要经过用户空间吗?那感觉好傻啊。事实证明,我们可以在驱动中调用其他的驱动,简单的例子就是,用PWM控制led灯。
部分内容参考自《Linux驱动 | LED驱动(使用PWM子系统)》

led是我要驱动的设备,这个设备中用到了pwm,而pwm是用默认的驱动。这里需要先修改设备树,如何定义自己的LED设备,包含pwm设备。
阅读Documentation/devicetree/bindings/pwm/pwm.txt

我们就可以修改一个我们的PWM驱动的LED设备

我们自己写一个驱动。内部在使用PWM子系统。形成了包含驱动的驱动。

驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
//#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#define LED_PWM_CMD_SET_DUTY 0x01
#define LED_PWM_CMD_SET_PERIOD 0x02
#define LED_PWM_CMD_SET_BOTH 0x03
#define LED_PWM_CMD_ENABLE 0x04
#define LED_PWM_CMD_DISABLE 0x05
struct my_pwm_param_struct
{
int duty_ns;
int period_ns;
};
struct my_pwm_led_dev_struct
{
dev_t dev_no;
struct cdev chrdev;
struct class *led_class;
struct device_node *dev_node;
struct pwm_device *led_pwm;
};
static struct my_pwm_param_struct my_pwm_para;
static struct my_pwm_led_dev_struct my_pwm_led_dev;
static int red_led_drv_open (struct inode *node, struct file *file)
{
int ret = 0;
//pwm_set_polarity(my_pwm_led_dev.led_pwm, PWM_POLARITY_INVERSED);
pwm_enable(my_pwm_led_dev.led_pwm);
printk("led_pwm open\r\n");
return ret;
}
static ssize_t red_led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
if (size != sizeof(my_pwm_para)) return -EINVAL;
err = copy_from_user(&my_pwm_para, buf, size);
if (err > 0) return -EFAULT;
pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
return 1;
}
static long _drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
void __user *my_user_space = (void __user *)arg;
switch (cmd)
{
case LED_PWM_CMD_SET_DUTY:
ret = copy_from_user(&my_pwm_para.duty_ns, my_user_space, sizeof(my_pwm_para.duty_ns));
if (ret > 0)
return -EFAULT;
pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
break;
case LED_PWM_CMD_SET_PERIOD:
ret = copy_from_user(&my_pwm_para.period_ns, my_user_space, sizeof(my_pwm_para.period_ns));
if (ret > 0)
return -EFAULT;
pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
break;
case LED_PWM_CMD_SET_BOTH:
ret = copy_from_user(&my_pwm_para, my_user_space, sizeof(my_pwm_para));
if (ret > 0)
return -EFAULT;
pwm_config(my_pwm_led_dev.led_pwm, my_pwm_para.duty_ns, my_pwm_para.period_ns);
break;
case LED_PWM_CMD_ENABLE:
pwm_enable(my_pwm_led_dev.led_pwm);
break;
case LED_PWM_CMD_DISABLE:
pwm_disable(my_pwm_led_dev.led_pwm);
break;
}
return 0;
}
static int red_led_drv_release(struct inode *node, struct file *filp)
{
int ret = 0;
pwm_config(my_pwm_led_dev.led_pwm, 0, 5000);
printk("led pwm dev close\r\n");
// pwm_disable(my_pwm_led_dev.led_pwm);
return ret;
}
static struct file_operations red_led_drv = {
.owner = THIS_MODULE,
.open = red_led_drv_open,
.write = red_led_drv_write,
.unlocked_ioctl = _drv_ioctl,
.release = red_led_drv_release,
};
static int led_red_driver_probe(struct platform_device *pdev)
{
int err;
int ret;
struct device *tdev;
struct device_node *child;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
tdev = &pdev->dev;
child = of_get_next_child(tdev->of_node, NULL); /* 获取设备树子节点 */
if (!child)
{
return -EINVAL;
}
my_pwm_led_dev.led_pwm = devm_of_pwm_get(tdev, child, NULL); /* 从子节点中获取PWM设备 */
if (IS_ERR(my_pwm_led_dev.led_pwm))
{
printk(KERN_ERR"can't get led_pwm!!\n");
return -EFAULT;
}
ret = alloc_chrdev_region(&my_pwm_led_dev.dev_no, 0, 1, "led_pwm");
if (ret < 0)
{
pr_err("Error: failed to register mbochs_dev, err: %d\n", ret);
return ret;
}
cdev_init(&my_pwm_led_dev.chrdev, &red_led_drv);
cdev_add(&my_pwm_led_dev.chrdev, my_pwm_led_dev.dev_no, 1);
my_pwm_led_dev.led_class = class_create(THIS_MODULE, "led_pwm");
err = PTR_ERR(my_pwm_led_dev.led_class);
if (IS_ERR(my_pwm_led_dev.led_class))
{
goto failed1;
}
tdev = device_create(my_pwm_led_dev.led_class , NULL, my_pwm_led_dev.dev_no, NULL, "my_pwm_led");
if (IS_ERR(tdev))
{
ret = -EINVAL;
goto failed2;
}
printk(KERN_INFO"%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
failed2:
device_destroy(my_pwm_led_dev.led_class, my_pwm_led_dev.dev_no);
class_destroy(my_pwm_led_dev.led_class);
failed1:
cdev_del(&my_pwm_led_dev.chrdev);
unregister_chrdev_region(my_pwm_led_dev.dev_no, 1);
return ret;
}
int led_red_driver_remove(struct platform_device *dev)
{
// pwm_disable(my_pwm_led_dev.led_pwm);
// pwm_free(my_pwm_led_dev.led_pwm);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(my_pwm_led_dev.led_class, my_pwm_led_dev.dev_no);
class_destroy(my_pwm_led_dev.led_class);
unregister_chrdev_region(my_pwm_led_dev.dev_no, 1);
cdev_del(&my_pwm_led_dev.chrdev);
return 0;
}
static struct of_device_id dts_match_table[] = {
{.compatible = "pgg,my_pwm_led", },
{},
};
static struct platform_driver my_pwm_led_platform_driver =
{
.probe = led_red_driver_probe,
.remove = led_red_driver_remove,
.driver =
{
.name = "pgg,my_pwm_led",
.owner = THIS_MODULE,
.of_match_table = dts_match_table,//通过设备树匹配
},
};
module_platform_driver(my_pwm_led_platform_driver);
MODULE_AUTHOR("PGG");
MODULE_LICENSE("GPL");
这里要注意的就是,我们不需要注册pwm设备,而是从设备树获取这个设备就可以
my_pwm_led_dev.led_pwm = devm_of_pwm_get(tdev, child, NULL); /* 从子节点中获取PWM设备 */
用户侧程序
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
#define DEV_NAME "/dev/my_pwm_led"
#define LED_PWM_CMD_SET_DUTY 0x01
#define LED_PWM_CMD_SET_PERIOD 0x02
#define LED_PWM_CMD_SET_BOTH 0x03
#define LED_PWM_CMD_ENABLE 0x04
#define LED_PWM_CMD_DISABLE 0x05
struct led_pwm_param {
int duty_ns;
int period_ns;
};
void sleep_ms(unsigned int ms)
{
struct timeval delay;
delay.tv_sec = 0;
delay.tv_usec = ms * 1000;
select(0, NULL, NULL, NULL, &delay);
}
int main(int argc, char **argv)
{
int fd;
int ret;
/* 2. 打开文件 */
fd = open(DEV_NAME, O_RDWR | O_NONBLOCK); // | O_NONBLOCK
if (fd < 0)
{
printf("can not open file %s, %d\n", DEV_NAME, fd);
return -1;
}
int buf = 3;
struct led_pwm_param led_pwm;
led_pwm.duty_ns = 500;
led_pwm.period_ns = 5000;
write(fd, &led_pwm, sizeof(led_pwm));
sleep_ms(3000);
for (int i = 0; i < 5; i++)
{
led_pwm.duty_ns += 300;
if (led_pwm.duty_ns < led_pwm.period_ns)
ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
sleep_ms(1000);
}
// led_pwm.duty_ns = 0;
// ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
sleep_ms(3000);
close(fd);
return 0;
}
别以为到这里就结束了,其实上面的代码都有问题。

第一步调用shell的方式,就发现和PWM相关的那些引脚,都没有反应。翻看了一遍设备树,也没有找到怎么配置PWM输出到哪个引脚上,这感觉像是缺少点配置。于是经过搜索,发现了一个类似的博客
《LINUX驱动开发11:【设备树】NANOPI的PWM驱动》,在配置设备树的时候,似乎要配置使用的引脚。

所以我这里也需要增加一个引脚,但这个引脚怎么写呢
于是我反编译了生成的dtb,查看到了树莓派中pwm引脚的定义

那么灵感来了,设备树修改成这样,感觉八九不离十了

继续上机调试,shell命令行的方式,成功了!!

然后就是调试led驱动,结果发现灯一直是灭的。突然又来灵感了,配置pwm,必须要先关停pwm,所以,修改了驱动中的代码,在配置前后都加上了禁用和启用

然后用户侧的程序,进行了优化,连续让灯变亮变暗
while(1)
{
if(led_pwm.duty_ns<=500)
{
while(led_pwm.duty_ns<led_pwm.period_ns)
{
ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
sleep_ms(50);
led_pwm.duty_ns += 300;
}
}
else
{
while(led_pwm.duty_ns > 500)
{
ioctl(fd, LED_PWM_CMD_SET_DUTY, &led_pwm.duty_ns);
sleep_ms(50);
led_pwm.duty_ns -= 300;
}
}
}
测试起来!

在搜索dts文件的时候,常用下面命令,用来找到包含关键内容的文件,及在文件第几行。
grep "内容" * -nr
前面搜索pwm配置的时候,就用到了这种命令。

昨天看到夫人给发的孩子照片,突然感觉像一个大孩子了。结果下午出去玩,上厕所出来走丢了,也知道告诉热心人父母的电话,帮忙联系父母。
我很欣慰啊,就算被人拐卖走了,应该也能找回来了。

夫人说得克制一下脾气了,孩子前天晚上说:我只有一个烦恼,就是妈妈爱发火。害,人哪能没有烦恼呢,没有烦恼,那不就光剩快乐了吗,这不符合我们社会主义初级阶段的国情。

今天上午公司有试用期员工转正大会,赶紧收工,一会去看热闹。

我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道rubyonrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现