资料:《【正点原子】I.MX6U开发指南V1.81.pdf》七十三章
6. PWM子系统 --- [野火]嵌入式Linux驱动开发实战指南
一、相关结构体
以下代码均来自include/linux/pwm.h
1.1 pwm_chip
cpp
/**
* struct pwm_chip - abstract a PWM controller
* @dev: device providing the PWMs
* @list: list node for internal use
* @ops: callbacks for this PWM controller PWM外设的各种操作函数集合
* @base: number of first PWM controlled by this chip 芯片控制的第一个 PWM 的编号
* @npwm: number of PWMs controlled by this chip 此芯片的PWM控制器数量
* @pwms: array of PWM devices allocated by the framework
* @can_sleep: must be true if the .config(), .enable() or .disable()
* operations may sleep
*/
struct pwm_chip {
struct device *dev;
struct list_head list;
const struct pwm_ops *ops;
int base;
unsigned int npwm;
struct pwm_device *pwms;
struct pwm_device * (*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args);
unsigned int of_pwm_n_cells;
bool can_sleep;
};
1.2 pwm_ops
其中,struct pwm_ops *ops就是PWM设备的操作集,写代码时需要由我们来实现。其结构体定义如下:
cpp
/**
* struct pwm_ops - PWM controller operations
* @request: optional hook for requesting a PWM 申请
* @free: optional hook for freeing a PWM 释放
* @config: configure duty cycles and period length for this PWM 配置 PWM 周期和占空比
* @set_polarity: configure the polarity of this PWM 设置 PWM 极性
* @enable: enable PWM output toggling 使能
* @disable: disable PWM output toggling 关闭
* @dbg_show: optional routine to show contents in debugfs
* @owner: helps prevent removal of modules exporting active PWMs
*/
struct pwm_ops {
int (*request)(struct pwm_chip *chip,
struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip,
struct pwm_device *pwm);
int (*config)(struct pwm_chip *chip,
struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity);
int (*enable)(struct pwm_chip *chip,
struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip,
struct pwm_device *pwm);
#ifdef CONFIG_DEBUG_FS
void (*dbg_show)(struct pwm_chip *chip,
struct seq_file *s);
#endif
struct module *owner;
};
1.3 pwm_device
cpp
struct pwm_device {
const char *label; // pwm设备名
unsigned long flags; // 标志位
unsigned int hwpwm; //
unsigned int pwm; // pwm设备索引
struct pwm_chip *chip; // pwm控制器
void *chip_data;
unsigned int period; // pwm周期,单位ns
unsigned int duty_cycle; // 占空比,单位ns
// 如果是正常输出极性,表示一个周期内高电平持续时间
// 如果设置非输出反相,则是一个周期内低电平持续时间
enum pwm_polarity polarity; // 枚举,输出类型,具体如下↓
};
enum pwm_polarity {
PWM_POLARITY_NORMAL, // 正常
PWM_POLARITY_INVERSED, // 反相
};
二、相关函数
drivers/pwm/core.c
2.1 注册 / 注销pwm_chip
用于向内核注册一个新的pwm_chip。
cpp
int pwmchip_add(struct pwm_chip *chip)
// chip:要添加的pwm_chip
// return:0则成功,负值失败
// 如果chip->base < 0,会动态分配一个pwm编号
注销:
cpp
/**
* pwmchip_remove() - remove a PWM chip 移除一个pwm_chip
* @chip: the PWM chip to remove 要移除的pwm_chip
*
* Removes a PWM chip. This function may return busy if the PWM chip provides
* a PWM device that is still requested.
* 如果pwm_chip提供的PWM设备仍被请求,此函数可能会return BUSY。
* return 0则成功,负值失败
*/
int pwmchip_remove(struct pwm_chip *chip)
2.2 申请 / 释放 pwm_device
申请:
cpp
// 根据dev找pwm
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
// dev:当前这个驱动对应的设备对象,对应pwm-names,可以传NULL
// con_id:消费者名/连接名
// OF函数,通过设备树节点找pwm
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, const char *con_id)
// np:设备树节点,使用OF函数获取
// con_id:消费者名/连接名,对应pwm-names,可以传NULL
// dev:devm版会有这个参数,因为自动释放 需要和设备生命周期绑定
释放:
cpp
void pwm_put(struct pwm_device *pwm)
void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
// pwm:要释放的pwm device
// dev:devm版会有这个参数,因为自动释放 需要和设备生命周期绑定
2.3 使能 / 停用
配置频率和占空比:
cpp
/**
* pwm_config() - change a PWM device configuration
* @pwm: PWM device
* @duty_ns: "on" time (in nanoseconds) 高电平时间ns(正常) 低电平时间ns(反相)
* @period_ns: duration (in nanoseconds) of one cycle 周期ns
*/
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
//return:0成功,负数:错误码
用于设置PWM极性:
cpp
/**
* pwm_set_polarity() - configure the polarity of a PWM signal
* @pwm: PWM device
* @polarity: new polarity of the PWM signal 极性,具体类型详见1.3
*
* Note that the polarity cannot be configured while the PWM device is enabled
* 如果PWM device已经被使能,不能修改极性
*/
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
//return:0成功,负数:错误码
使能:
cpp
/**
* pwm_enable() - start a PWM output toggling
* @pwm: PWM device
*/
int pwm_enable(struct pwm_device *pwm)
//return:0成功,负数:错误码
关闭:
cpp
/**
* pwm_disable() - stop a PWM output toggling
* @pwm: PWM device
*/
void pwm_disable(struct pwm_device *pwm)
三、代码
3.1 设备树
IMX6ULL有8路PWM输出,都属于AIPS1域。在imx6ull.dtsi文件中的aips1中可以找到pwm1~8节点。依据《指南pdf》,本次实验使用PWM3,对应GPIO1_IO04。底板原理图中没有写,但在【正点原子】阿尔法Linux开发板(A盘)-基础资料/07、I.MX6U参考资料/02、I.MX6ULL芯片资料/IMX6ULL参考手册.pdf中的2475页有写:


imx6ull-alientek-emmc.dts文件中,在&iomuxc的imx6ul-evk下新增下面的代码,添加gpio1 io04引脚:
cpp
pinctrl_pwm3: pwm3grp {
fsl,pins = <MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0>;
};
imx6ull-alientek-emmc.dts增加以下代码,向imx6ull.dtsi的pwm节点追加控制器信息。直接写到最外面:
cpp
&pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
status = "okay";
};
最后在根目录下新增sg90的设备节点:
cpp
sg90{
compatible = "sg90";
pwms = <&pwm3 0 20000000>;// pwm1 id=0 周期20000000ns->20ms,这个20ms是sg90统一的周期
status = "okay";
};
然后检查一下有没有其他地方使用了pwm3、gpio1 io04,有的话注释掉。比如这个↓

编译,然后将生成的文件复制到tftproot下,然后重启开发板:
cpp
cd /...../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make dtbs
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dts /...../tftpboot/
3.2 文件结构
bash
PWM (工作区)
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── pwm.code-workspace
├── Makefile
├── pwm.c
└── pwmAPP.c
3.3 Makefile
bash
CFLAGS_MODULE += -w
KERNELDIR := /....../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径
CURRENT_PATH := $(shell pwd) # 当前路径
obj-m := pwm.o # 编译文件
build: kernel_modules # 编译模块
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3.4 pwm.c驱动代码
3.4.1 设备结构体
cpp
#define PLATFORM_NAME "sg90" // 设备名
#define PLATFORM_COUNT 1 // 设备数量
/////////////////////////////////////////////////////
/* 设备结构体 */
struct sg90_struct{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 此设备号 */
struct cdev cdev;
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd;
struct pwm_device *pwm_dev;
};
struct sg90_struct sg90dev;
3.4.2 file_operations
cpp
static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int ret,angle,duty_ns;
char databuf[32];
size_t len = min(count, sizeof(databuf)-1);
struct sg90_struct *dev = filp->private_data;
memset(databuf, 0, sizeof(databuf));
if(copy_from_user(databuf, buf, len))return -EFAULT;
databuf[len]='\0';
ret = kstrtoint(databuf, 10, &angle); // string -> int
if(ret)return -EINVAL;
if(angle < 0) angle = 0;
else if(angle > 180) angle = 180;
duty_ns = 500000 + (2500000 - 500000) * angle / 180;
ret = pwm_config(dev->pwm_dev, duty_ns, 20000000);
if(ret < 0){
pr_err("fild_pwm_config!\r\n");
return ret;
}
return len;
}
static int sg90_open(struct inode *inode, struct file *filp){
filp->private_data = &sg90dev;
return 0;
}
static int sg90_release(struct inode *inode, struct file *filp){
return 0;
}
/* 字符设备操作集合 */
static const struct file_operations sg90_fops = {
.owner = THIS_MODULE,
.write = sg90_write,
.open = sg90_open,
.release = sg90_release,
};
3.4.3 platform结构体 + 匹配表
cpp
static const struct of_device_id sg90_of_match[] = {
{.compatible = "sg90"}, // 要和新增的设备数节点中的compatible匹配
{ /* sentinel */ }, // 哨兵元素
};
// platform驱动结构体
static struct platform_driver sg90_driver = {
.driver = {
.name = "sg90", // 无设备树时使用name与设备进行匹配
.of_match_table = sg90_of_match, // 有设备树时进行匹配
},
.probe = sg90_probe,
.remove = sg90_remove,
};
3.4.4 probe + remove
cpp
static int sg90_probe(struct platform_device *dev){
pr_info("sg90 probe\r\n");
int ret = 0;
/* 1.注册字符设备驱动 */
sg90dev.devid = 0;
ret = alloc_chrdev_region(&sg90dev.devid, 0, PLATFORM_COUNT, PLATFORM_NAME);
if(ret<0){
pr_err("fail_chrdev!\n");
goto fail_chrdev;
}
sg90dev.major = MAJOR(sg90dev.devid);
sg90dev.minor = MINOR(sg90dev.devid);
/* 2.初始化cdev */
sg90dev.cdev.owner = THIS_MODULE;
cdev_init(&sg90dev.cdev, &sg90_fops);
/* 3.添加cdev */
ret = cdev_add(&sg90dev.cdev, sg90dev.devid, PLATFORM_COUNT);
if(ret<0){
pr_err("fail_add_cdev!\n");
goto fail_add_cdev;
}
/* 4.创建类 */
sg90dev.class = class_create(THIS_MODULE, PLATFORM_NAME);
if(IS_ERR(sg90dev.class)){
pr_err("fail_create_class!\n");
ret = PTR_ERR(sg90dev.class);
goto fail_create_class;
}
/* 5.创建设备 */
sg90dev.device = device_create(sg90dev.class, NULL, sg90dev.devid, NULL, PLATFORM_NAME);
if(IS_ERR(sg90dev.device)){
pr_err("fail_create_device!\n");
ret = PTR_ERR(sg90dev.device);
goto fail_create_device;
}
sg90dev.nd = dev->dev.of_node;
// platform_device类型中的device结构体成员中已经包括了of_node(device_node类型)
// 可以直接读取,不需要再手动寻找of_find_node_by_path
struct device_node *node = dev->dev.of_node; // 设备树
sg90dev.pwm_dev = devm_of_pwm_get(&dev->dev, node, NULL);
if (IS_ERR(sg90dev.pwm_dev)){
pr_err("fail_get_pwm!\n");
ret = PTR_ERR(sg90dev.pwm_dev);
goto fail_pwm;
}
ret = pwm_config(sg90dev.pwm_dev, 500000, 20000000); // 周期20ms,高电平时间0.5ms,舵机角度0
if(ret < 0){
pr_err("fail_config_pwm!\n");
goto fail_pwm;
}
ret = pwm_set_polarity(sg90dev.pwm_dev, PWM_POLARITY_NORMAL); // 正常极性
if(ret < 0){
pr_err("fail_set_polarity!\n");
goto fail_pwm;
}
ret = pwm_enable(sg90dev.pwm_dev);
if(ret < 0){
pr_err("fail_enable_pwm!\n");
goto fail_pwm;
}
return 0;
fail_pwm:
device_destroy(sg90dev.class,sg90dev.devid);
fail_create_device:
class_destroy(sg90dev.class);
fail_create_class:
cdev_del(&sg90dev.cdev);
fail_add_cdev:
unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
fail_chrdev:
return ret;
}
static int sg90_remove(struct platform_device * dev){
pr_info("sg90 remove\r\n");
pwm_disable(sg90dev.pwm_dev);
device_destroy(sg90dev.class, sg90dev.devid);
class_destroy(sg90dev.class);
cdev_del(&sg90dev.cdev);
unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
return 0;
}
这里有一个问题,在配置极性的时候回报错:
fail_set_polarity!
sg90: probe of servo failed with error -38
可能是控制器不支持这个功能?先注释掉了
3.4.5 驱动出入口
cpp
// 加载驱动
static int __init sg90driver_init(void){
return platform_driver_register(&sg90_driver);
}
// 卸载驱动
static void __exit sg90driver_exit(void){
platform_driver_unregister(&sg90_driver);
}
module_init(sg90driver_init);
module_exit(sg90driver_exit);
MODULE_LICENSE("GPL");
3.4.6 完整驱动代码
cpp
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mod_devicetable.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/pwm.h>
#define PLATFORM_NAME "sg90" // 设备名
#define PLATFORM_COUNT 1 // 设备数量
/////////////////////////////////////////////////////
/* 设备结构体 */
struct sg90_struct{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 此设备号 */
struct cdev cdev;
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd;
struct pwm_device *pwm_dev;
};
struct sg90_struct sg90dev;
/////////////////////////////////////////////////////
static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int ret,angle,duty_ns;
char databuf[32];
size_t len = min(count, sizeof(databuf)-1);
struct sg90_struct *dev = filp->private_data;
memset(databuf, 0, sizeof(databuf));
if(copy_from_user(databuf, buf, len))return -EFAULT;
databuf[len]='\0';
ret = kstrtoint(databuf, 10, &angle); // string -> int
if(ret)return -EINVAL;
if(angle < 0) angle = 0;
else if(angle > 180) angle = 180;
duty_ns = 500000 + (2500000 - 500000) * angle / 180;
ret = pwm_config(dev->pwm_dev, duty_ns, 20000000);
if(ret < 0){
pr_err("fild_pwm_config!\r\n");
return ret;
}
return len;
}
static int sg90_open(struct inode *inode, struct file *filp){
filp->private_data = &sg90dev;
return 0;
}
static int sg90_release(struct inode *inode, struct file *filp){
return 0;
}
/* 字符设备操作集合 */
static const struct file_operations sg90_fops = {
.owner = THIS_MODULE,
.write = sg90_write,
.open = sg90_open,
.release = sg90_release,
};
///////////////////////////////////////////////////////
static int sg90_probe(struct platform_device *dev){
pr_info("sg90 probe\r\n");
int ret = 0;
/* 1.注册字符设备驱动 */
sg90dev.devid = 0;
ret = alloc_chrdev_region(&sg90dev.devid, 0, PLATFORM_COUNT, PLATFORM_NAME);
if(ret<0){
pr_err("fail_chrdev!\n");
goto fail_chrdev;
}
sg90dev.major = MAJOR(sg90dev.devid);
sg90dev.minor = MINOR(sg90dev.devid);
/* 2.初始化cdev */
sg90dev.cdev.owner = THIS_MODULE;
cdev_init(&sg90dev.cdev, &sg90_fops);
/* 3.添加cdev */
ret = cdev_add(&sg90dev.cdev, sg90dev.devid, PLATFORM_COUNT);
if(ret<0){
pr_err("fail_add_cdev!\n");
goto fail_add_cdev;
}
/* 4.创建类 */
sg90dev.class = class_create(THIS_MODULE, PLATFORM_NAME);
if(IS_ERR(sg90dev.class)){
pr_err("fail_create_class!\n");
ret = PTR_ERR(sg90dev.class);
goto fail_create_class;
}
/* 5.创建设备 */
sg90dev.device = device_create(sg90dev.class, NULL, sg90dev.devid, NULL, PLATFORM_NAME);
if(IS_ERR(sg90dev.device)){
pr_err("fail_create_device!\n");
ret = PTR_ERR(sg90dev.device);
goto fail_create_device;
}
sg90dev.nd = dev->dev.of_node;
// platform_device类型中的device结构体成员中已经包括了of_node(device_node类型)
// 可以直接读取,不需要再手动寻找of_find_node_by_path
struct device_node *node = dev->dev.of_node; // 设备树
sg90dev.pwm_dev = devm_of_pwm_get(&dev->dev, node, NULL);
if (IS_ERR(sg90dev.pwm_dev)){
pr_err("fail_get_pwm!\n");
ret = PTR_ERR(sg90dev.pwm_dev);
goto fail_pwm;
}
ret = pwm_config(sg90dev.pwm_dev, 500000, 20000000); // 周期20ms,高电平时间0.5ms,舵机角度0
if(ret < 0){
pr_err("fail_config_pwm!\n");
goto fail_pwm;
}
// ret = pwm_set_polarity(sg90dev.pwm_dev, PWM_POLARITY_NORMAL); // 正常极性
// if(ret < 0){
// pr_err("fail_set_polarity!\n");
// goto fail_pwm;
// }
ret = pwm_enable(sg90dev.pwm_dev);
if(ret < 0){
pr_err("fail_enable_pwm!\n");
goto fail_pwm;
}
return 0;
fail_pwm:
device_destroy(sg90dev.class,sg90dev.devid);
fail_create_device:
class_destroy(sg90dev.class);
fail_create_class:
cdev_del(&sg90dev.cdev);
fail_add_cdev:
unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
fail_chrdev:
return ret;
}
static int sg90_remove(struct platform_device * dev){
pr_info("sg90 remove\r\n");
pwm_disable(sg90dev.pwm_dev);
device_destroy(sg90dev.class, sg90dev.devid);
class_destroy(sg90dev.class);
cdev_del(&sg90dev.cdev);
unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
return 0;
}
static const struct of_device_id sg90_of_match[] = {
{.compatible = "sg90"}, // 要和新增的设备数节点gpio中的compatible匹配
{ /* sentinel */ }, // 哨兵元素
// 后面可以接更多的{.compatible = "............"}
};
// platform驱动结构体
static struct platform_driver sg90_driver = {
.driver = {
.name = "sg90", // 无设备树时使用name与设备进行匹配
.of_match_table = sg90_of_match, // 有设备树时进行匹配
},
.probe = sg90_probe,
.remove = sg90_remove,
};
///////////////////////////////////////////////////////////////////
// 加载驱动
static int __init sg90driver_init(void){
return platform_driver_register(&sg90_driver);
}
// 卸载驱动
static void __exit sg90driver_exit(void){
platform_driver_unregister(&sg90_driver);
}
module_init(sg90driver_init);
module_exit(sg90driver_exit);
MODULE_LICENSE("GPL");
3.5 应用程序测试
cpp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]){
int fd;
char buf[32];
if (argc != 2) {
printf("Usage: %s <angle>\n", argv[0]);
return -1;
}
fd = open("/dev/sg90", O_WRONLY);
if (fd < 0) {
printf("open /dev/sg90 failed");
return -1;
}
if (write(fd, argv[1], strlen(argv[1])) < 0) {
printf("write failed");
close(fd);
return -1;
}
printf("set angle = %d\n", atoi(argv[1]));
close(fd);
return 0;
}
3.6 测试
bash
make
arm-linux-gnueabihf-gcc pwmAPP.c -o pwmAPP
sudo cp pwm.ko pwmAPP /home/for/linux/nfs/rootfs/lib/modules/4.1.15/
bash
cd /lib/modules/4.1.15
depmod
modprobe pwm.ko
./pwmAPP XXX # 输入角度