树莓派4B——PWM
设备树
bcm2711.dtsi
定义部分:
pwm1: pwm@7e20c800 {
compatible = "brcm,bcm2835-pwm";
reg = <0x7e20c800 0x28>;
clocks = <&clocks BCM2835_CLOCK_PWM>;
assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
assigned-clock-rates = <10000000>;
#pwm-cells = <2>;
status = "disabled";
};
compatible 字段表示匹配驱动的名字。
reg表示地址空间,阅读手册的话会发现其实0x7e20c800是PWM1的基地址,0x28表示地址长度,正是下面寄存器的地址范围大小。
下面还有一些和pwm有关的描述,不清楚具体的含义(应该是pinctrl子系统),但是和这个图有关
pwm0_0_gpio12: pwm0_0_gpio12 {
pin-pwm {
pins = "gpio12";
function = "alt0"; /* 功能 */
bias-disable; /* 配置状态 */
};
};
pwm0_0_gpio18: pwm0_0_gpio18 {
pin-pwm {
pins = "gpio18";
function = "alt5";
bias-disable;
};
};
pwm1_0_gpio40: pwm1_0_gpio40 {
pin-pwm {
pins = "gpio40";
function = "alt0";
bias-disable;
};
};
pwm0_1_gpio13: pwm0_1_gpio13 {
pin-pwm {
...
bcm2711-rpi-4-b.dts
&pwm1 {
pinctrl-names = "default"; /* 设备的状态,这里只有一种 */
pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>; /* 该状态的引脚定义 */
status = "okay";
};
&pwm1 {
status = "disabled";
};
这里也不清楚,status决定是否启用,后面的应该会覆盖前面的,将其改为okay。
编译
重新编译设备树:
sudo make dtbs
sudo cp arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb /boot
sudo reboot
重启后会在/sys/class/pwm下生成pwmchip0。
# 引出
echo 1 > /sys/class/pwm/pwmchip0/export
# 设置周期
echo 1000000 > /sys/class/pwm/pwmchip0/pwm1/period
# 设置占空比
echo 500000 > /sys/class/pwm/pwmchip0/pwm1/duty_cycle
# 使能pwm
echo 1 > /sys/class/pwm/pwmchip0/pwm1/enable
# 关闭pwm
echo 0 > /sys/class/pwm/pwmchip0/pwm1/enable
# pwm0
echo 0 > /sys/class/pwm/pwmchip0/export
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
找了半天,默认的管脚似乎没有引出来,改为gpio12试下。
设备树节点改了之后,测试的pwm还是没有效果,不知道啥原因(树莓派的pwm和音频audio似乎是共用的硬件,不知道是不是系统起来后干了啥事情)。
最后采用的方式是修改/boot/config.txt,在末尾添加:
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=2
这个方式可以正常的操控pwm管脚。
驱动编写(基于PWM子系统)
在设备树根节点下添加测试节点
/* pwm test */
my_pwm_led{
compatible = "pgg,my_pwm_led";
back{
pwms = <&pwm 0 5000000>;
pwm-names = "my_pwm_led";
};
};
内核驱动:
#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; /* 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); /* 使能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;
/* 接收用户空间拷贝过来的数据,也就是write函数的数据 */
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); /* 配置pwm */
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");
应用程序:
#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);
perror("open");
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);
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;
}
}
}
close(fd);
return 0;
}
总结
pwm子系统基于pinctrl子系统,需要了解pinctrl子系统及其设备树的写法。
内核态基于pwm子系统的驱动编写流程比较简单,获取设备后进行配置即可,要熟悉devm_of_pwm_get等API。
遗留问题
为什么只修改设备树的话,使用PWM无反应,而是要修改config.txt(后者应该也是动态修改了设备树) ?
看了一遍又发现设备树里面是只有PWM1的设备描述,还有一个问题是内核驱动的设备树,并没有找到pwm节点。