前言:野火原本的镜像中已开放pwm值的使用,基础配置是与RGB灯关联,如果需要使用其他路的pwm值需要配置,并把原本与RGB灯关联的设备树关闭。本文是源于控制各路pwm值需要过多的单独指令,比较繁琐,现把各路pwm值整合到一个驱动中,使用一个驱动去驱动各路pwm值,驱动是通过 ioctl 接口去控制多个 PWM 设备(本文用到pwm3、pwm5、pwm6、pwm7、pwm8这几路pwm值)。
1.设备树编写
设备树的编写是仿照野火中pwm控制RGB灯的设备树编写,每个pwm的引脚设置必须保证没有其他引脚的复用,因为是控制多路pwm,需要在设备树中为各路pwm中设置编号,具体体现在:pwm-num。
把设备树编译完成后,放入开发板指定位置(具体参考野火开发文档),并在uEnv.txt文档中使能自己的设备树。
/*
* Copyright (C) 2019 - All Rights Reserved by
* filename : imx-fire-mpu6050-overlay.dts
* brief : Device Tree overlay for EBF6ull Pro mpu6050 device
* author : embedfire
* date : 2019-11-26
* version : A001
*/
#include "../imx6ul-pinfunc.h"
#include "../imx6ull-pinfunc.h"
#include "../imx6ull-pinfunc-snvs.h"
#include "dt-bindings/input/linux-event-codes.h"
#include "dt-bindings/gpio/gpio.h"
#include <dt-bindings/clock/imx6ul-clock.h>
#include "dt-bindings/interrupt-controller/irq.h"
#include "dt-bindings/interrupt-controller/arm-gic.h"
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/";
__overlay__ {
sweep_pwm {
compatible = "sweep_pwm";
status = "okay";
pwm3 {
pwm-num = <3>;
pwms = <&pwm3 0 50000 100000>;
status = "okay";
};
pwm5 {
pwm-num = <5>;
pwms = <&pwm5 0 50000 100000>;
status = "okay";
};
pwm6 {
pwm-num = <6>;
pwms = <&pwm6 0 50000 100000>;
status = "okay";
};
pwm7 {
pwm-num = <7>;
pwms = <&pwm7 0 50000 100000>;
status = "okay";
};
pwm8 {
pwm-num = <8>;
pwms = <&pwm8 0 50000 100000>;
status = "okay";
};
};
};
};
fragment@1 {
target = <&iomuxc>;
__overlay__ {
pinctrl_pwm3:pwm3grp{
fsl,pins = < MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x000010B1 >;
};
pinctrl_pwm5:pwm5grp{
fsl,pins = < MX6UL_PAD_ENET1_TX_DATA1__PWM5_OUT 0x000010B1 >;
};
pinctrl_pwm6:pwm6grp{
fsl,pins = < MX6UL_PAD_ENET1_TX_EN__PWM6_OUT 0x000010B1 >;
};
pinctrl_pwm7:pwm7grp{
fsl,pins = < MX6UL_PAD_CSI_VSYNC__PWM7_OUT 0x000010B1 >;
};
pinctrl_pwm8:pwm8grp{
fsl,pins = < MX6UL_PAD_CSI_HSYNC__PWM8_OUT 0x000010B1 >;
};
};
};
fragment@2 {
target = <&pwm3>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
status = "okay";
};
};
fragment@3 {
target = <&pwm7>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm7>;
clocks = <&clks IMX6UL_CLK_PWM7>,
<&clks IMX6UL_CLK_PWM7>;
status = "okay";
};
};
fragment@4 {
target = <&pwm8>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm8>;
clocks = <&clks IMX6UL_CLK_PWM8>,
<&clks IMX6UL_CLK_PWM8>;
status = "okay";
};
};
fragment@5 {
target = <&pwm5>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm5>;
clocks = <&clks IMX6UL_CLK_PWM5>,
<&clks IMX6UL_CLK_PWM5>;
status = "okay";
};
};
fragment@6 {
target = <&pwm6>;
__overlay__ {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm6>;
clocks = <&clks IMX6UL_CLK_PWM6>,
<&clks IMX6UL_CLK_PWM6>;
status = "okay";
};
};
};
2.驱动的编写
驱动通过 ioctl 接口控制多个pwm设备。
注意:驱动的名字要与设备树中的匹配。
sweep_pwm.h
#ifndef INPUT_SWEEP_PWM_H
#define INPUT_SWEEP_PWM_H
#include <linux/types.h>
#endif
sweep_pwm.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/pwm.h>
#include <linux/err.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#define SWEEP_PWM_MAGIC 'p'
#define SET_PWM_PARAMS _IOW(SWEEP_PWM_MAGIC, 1, struct pwm_params)
struct pwm_params {
int pwm_num;
int duty_cycle;
int period;
};
struct pwm_device *sweep_pwm[10]; // 假设最多支持10个PWM设备
static struct platform_device *pdev_global;
static int sweep_pwm_probe_new(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *child;
int ret = 0;
printk(KERN_INFO "match success\n");
// 遍历子节点,获取 PWM 设备
for_each_child_of_node(dev->of_node, child) {
struct pwm_device *pwm;
int pwm_num;
if (of_property_read_u32(child, "pwm-num", &pwm_num)) {
printk(KERN_ERR "Failed to read pwm-num property\n");
continue;
}
pwm = devm_of_pwm_get(dev, child, NULL);
if (IS_ERR(pwm)) {
printk(KERN_ERR "Failed to get PWM device\n");
continue;
}
if (pwm_num >= 0 && pwm_num < 10) {
sweep_pwm[pwm_num] = pwm;
printk(KERN_INFO "PWM device found: %pOF, pwm_num = %d\n", child, pwm_num);
} else {
printk(KERN_ERR "Invalid pwm_num: %d\n", pwm_num);
}
}
pdev_global = pdev;
return 0;
}
static int sweep_pwm_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < 10; i++) {
if (sweep_pwm[i]) {
pwm_config(sweep_pwm[i], 0, 5000);
pwm_disable(sweep_pwm[i]);
}
}
return 0;
}
static long sweep_pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct pwm_params params;
int ret;
if (cmd != SET_PWM_PARAMS) {
return -ENOTTY;
}
if (copy_from_user(¶ms, (struct pwm_params *)arg, sizeof(params))) {
return -EFAULT;
}
if (params.pwm_num < 0 || params.pwm_num >= 10 || !sweep_pwm[params.pwm_num]) {
printk(KERN_ERR "Invalid PWM number\n");
return -EINVAL;
}
ret = pwm_config(sweep_pwm[params.pwm_num], params.duty_cycle, params.period);
if (ret) {
printk(KERN_ERR "Failed to configure PWM\n");
return ret;
}
pwm_enable(sweep_pwm[params.pwm_num]);
return 0;
}
static const struct of_device_id of_pwm_sweep_match[] = {
{.compatible = "sweep_pwm"},
{},
};
static const struct file_operations sweep_pwm_fops = {
.unlocked_ioctl = sweep_pwm_ioctl,
};
static struct miscdevice sweep_pwm_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sweep_pwm",
.fops = &sweep_pwm_fops,
};
static struct platform_driver sweep_pwm_driver = {
.probe = sweep_pwm_probe_new,
.remove = sweep_pwm_remove,
.driver = {
.name = "sweep_pwm",
.of_match_table = of_pwm_sweep_match,
},
};
static int __init sweep_pwm_platform_driver_init(void)
{
int ret;
ret = platform_driver_register(&sweep_pwm_driver);
if (ret) {
printk(KERN_ERR "Failed to register platform driver\n");
return ret;
}
ret = misc_register(&sweep_pwm_miscdev);
if (ret) {
printk(KERN_ERR "Failed to register misc device\n");
platform_driver_unregister(&sweep_pwm_driver);
return ret;
}
return 0;
}
static void __exit sweep_pwm_platform_driver_exit(void)
{
printk(KERN_INFO "sweep_pwm_exit\n");
misc_deregister(&sweep_pwm_miscdev);
platform_driver_unregister(&sweep_pwm_driver);
}
module_init(sweep_pwm_platform_driver_init);
module_exit(sweep_pwm_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("DOUBAO");
MODULE_DESCRIPTION("Dynamic PWM Control Driver using ioctl");
MODULE_VERSION("1.0");
3.测试程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
struct pwm_params {
int pwm_num;
int duty_cycle;
int period;
};
#define SET_PWM_PARAMS _IOW('p', 1, struct pwm_params)
int main(int argc, char *argv[])
{
int fd;
struct pwm_params params;
if (argc != 4) {
fprintf(stderr, "Usage: %s <pwm_num> <duty_cycle> <period>\n", argv[0]);
return -1;
}
fd = open("/dev/sweep_pwm", O_RDWR);
if (fd < 0) {
perror("Failed to open device file");
return -1;
}
params.pwm_num = atoi(argv[1]);
params.duty_cycle = atoi(argv[2]);
params.period = atoi(argv[3]);
if (ioctl(fd, SET_PWM_PARAMS, ¶ms) < 0) {
perror("Failed to set PWM parameters using ioctl");
close(fd);
return -1;
}
printf("PWM parameters set successfully\n");
close(fd);
return 0;
}
4.Makfile编写
KERNEL_DIR=../ebf_linux_kernel/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := sweep_pwm.o
out = sweep_pwm_test
all:
$(MAKE) EXTRA_CFLAGS=-fno-pic -C $(KERNEL_DIR) M=$(CURDIR) modules
$(CROSS_COMPILE)gcc -o $(out) sweep_pwm_test.c
.PHONY:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
rm $(out)
5.测试
a.加载驱动
insmod sweep_pwm.ko
b.测试指令
(运行测试程序后面依次接上:pwm编号,占空比,周期)
./sweep_pwm_test 3 1000000 500000
具体的pwm值变化可以通过万用表或示波器体现。