一、PWM子系统简介
PWM(Pulse Width Modulation,脉宽调制)是一种通过调节高电平持续时间占整个周期的比例(即占空比)来控制模拟信号的方法。在嵌入式系统中,PWM常用于电机控制、LED调光、舵机控制等场景。
二、设备树配置(Device Tree)
2.1 PWM控制器节点(SoC内部定义)
以 pwm2
为例,其定义位于 imx6ull.dtsi
文件中:
pwm2: pwm@02084000 {
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
reg = <0x02084000 0x4000>;
interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_DUMMY>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "ipg", "per";
#pwm-cells = <2>;
};
2.2 引脚复用(pinctrl配置)
将 PWM2_OUT
映射到引脚 GPIO1_IO09
:
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_pwm2: pwm2grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__PWM2_OUT 0x110b0
>;
};
};
};
2.3 启用 PWM 控制器节点
在 pwm2
节点中启用 PWM,并引用引脚配置:
&pwm2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm2>;
clocks = <&clks IMX6UL_CLK_PWM2>,
<&clks IMX6UL_CLK_PWM2>;
status = "okay";
};
2.4 定义 SG90 伺服节点
在根节点添加 SG90 设备信息:
hc_sg90 {
compatible = "hc-sg90";
pwms = <&pwm2 0 20000000>; /* 周期为 20ms(20000000ns) */
status = "okay";
};
注解:
-
pwms = <&pwm2 0 20000000>
:&pwm2
:引用pwm控制器;0
:通道号;20000000
:周期为20ms,SG90标准周期。
-
pwm-names
:可选,用于命名。
三、驱动程序部分
3.1 驱动结构概述
- 使用 platform 驱动机制;
- 注册字符设备,提供
/dev/sg90
接口; - 利用
pwm_config()
等函数配置PWM波形。
3.2 驱动源码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/pwm.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static int major;
static struct class *sg90_class;
static struct pwm_device *pwm_dev;
static int sg90_open(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t sg90_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned char angle;
unsigned int duty_ns;
if (size != 1)
return -EINVAL;
if (copy_from_user(&angle, buf, 1))
return -EFAULT;
duty_ns = 500000 + angle * (1000000 / 90); // 0.5ms + angle * (1ms / 90)
pwm_config(pwm_dev, duty_ns, 20000000); // 周期 20ms
return 1;
}
static int sg90_release(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations sg90_fops = {
.owner = THIS_MODULE,
.open = sg90_open,
.write = sg90_write,
.release = sg90_release,
};
/* probe 函数:设备匹配后调用 */
static int sg90_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int ret;
printk("%s %s line %d: sg90 matched\n", __FILE__, __FUNCTION__, __LINE__);
/* 获取 PWM 设备 */
pwm_dev = devm_of_pwm_get(&pdev->dev, node, NULL);
if (IS_ERR(pwm_dev)) {
printk(KERN_ERR "Failed to get PWM device\n");
return PTR_ERR(pwm_dev);
}
/* 初始化 PWM */
pwm_config(pwm_dev, 1500000, 20000000); // 默认 90 度
pwm_set_polarity(pwm_dev, PWM_POLARITY_NORMAL);
pwm_enable(pwm_dev);
/* 注册字符设备 */
major = register_chrdev(0, "sg90", &sg90_fops);
if (major < 0) {
printk(KERN_ERR "register_chrdev failed\n");
return major;
}
sg90_class = class_create(THIS_MODULE, "sg90_class");
if (IS_ERR(sg90_class)) {
unregister_chrdev(major, "sg90");
return PTR_ERR(sg90_class);
}
device_create(sg90_class, NULL, MKDEV(major, 0), NULL, "sg90");
return 0;
}
/* remove 函数:卸载时调用 */
static int sg90_remove(struct platform_device *pdev)
{
pwm_disable(pwm_dev);
device_destroy(sg90_class, MKDEV(major, 0));
class_destroy(sg90_class);
unregister_chrdev(major, "sg90");
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id sg90_of_match[] = {
{ .compatible = "hc-sg90", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sg90_of_match);
/* 平台驱动结构体 */
static struct platform_driver sg90_driver = {
.driver = {
.name = "my_sg90",
.of_match_table = of_match_ptr(sg90_of_match),
},
.probe = sg90_probe,
.remove = sg90_remove,
};
/* 模块加载函数:仅注册 platform_driver */
static int __init sg90_init(void)
{
printk("%s %s line %d: module init\n", __FILE__, __FUNCTION__, __LINE__);
return platform_driver_register(&sg90_driver);
}
module_init(sg90_init);
/* 模块卸载函数 */
static void __exit sg90_exit(void)
{
platform_driver_unregister(&sg90_driver);
}
module_exit(sg90_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PWM Driver for SG90 Servo");
四、应用层测试程序
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
unsigned char angle;
if (argc != 2) {
printf("Usage: %s <angle: 0~180>\n", argv[0]);
return -1;
}
angle = atoi(argv[1]);
if (angle > 180) angle = 180;
fd = open("/dev/sg90", O_WRONLY);
if (fd < 0) {
perror("open");
return -1;
}
write(fd, &angle, 1);
close(fd);
return 0;
}
运行方式:
./sg90_test 90
五、PWM控制SG90舵机的原理解析
SG90舵机接受周期为20ms的PWM信号:
- 0度:高电平为0.5ms(500000ns)
- 90度:高电平为1.5ms(1500000ns)
- 180度:高电平为2.5ms(2500000ns)
占空比计算公式:
高电平时间(ns) = 500000 + angle * (1000000 / 90)
六、小结与拓展
驱动要点总结:
- 利用
devm_of_pwm_get()
从设备树获取 PWM; - 使用
pwm_config()
配置占空比; - 使用
pwm_set_polarity()
设置极性; - 使用
pwm_enable()
开启 PWM 输出; - 使用字符设备接口接收用户空间控制命令。