PWM子系统
文章目录
- 一、电路连接
- (一)蜂鸣器
- (二)风扇
- (三)马达
- 二、配置内核
- 三、设备树
- (一)timer定时器
- (二)PWM
- (三)设备树节点
- 四、驱动
- (一)相关API
- 1. 分配对象pwm_device
- 2. 配置PWM设备
- 3. 打开或者关闭PWM设备
- 4. 注销PWM设备
- (二)代码示例
实现用PWM子系统控制风扇、蜂鸣器、马达的速率
一、电路连接
(一)蜂鸣器
由图可知,使用了定时器4的通道1,该引脚连到了主控板的PB6引脚
(二)风扇
使用了定时器1的通道1,连接了PE9引脚
(三)马达
连接引脚PF6
二、配置内核
-
开启STM32定时器
Device Drivers ---> Multifunction device drivers ---> {*} Support for STM32 Timers
-
开启STM32 PWM控制器
Device Drivers ---> [*] Pulse-Width Modulation (PWM) Support ---> <*> STMicroelectronics STM32 PWM <*> STMicroelectronics STM32 PWM LP
-
开启基于PWM⼦系统的pwm-beeper驱动
如果不使用内核自带的pwm-beeper驱动,该配置不要选配,选nDevice Drivers ---> Input device support ---> [*] Miscellaneous devices ---> <*> PWM beeper support
三、设备树
(一)timer定时器
打开stm32mp151.dtsi文件,找到timers4节点
timers4: timer@40002000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "st,stm32-timers";
reg = <0x40002000 0x400>;
clocks = <&rcc TIM4_K>;
clock-names = "int";
dmas = <&dmamux1 29 0x400 0x80000001>,
<&dmamux1 30 0x400 0x80000001>,
<&dmamux1 31 0x400 0x80000001>,
<&dmamux1 32 0x400 0x80000001>;
dma-names = "ch1", "ch2", "ch3", "ch4";
status = "disabled";
};
打开内核帮助文档,找到相关示例
st,stm32-timer.yaml
36 examples:
37 - |
38 #include <dt-bindings/interrupt-controller/arm-gic.h>
39 #include <dt-bindings/clock/stm32mp1-clks.h>
40 timer: timer@40000c00 {
41 compatible = "st,stm32-timer";
42 reg = <0x40000c00 0x400>;
43 interrupts = <50>;
44 clocks = <&clk_pmtr1>;
45 };
对比可见,定时器基本配置已经配置完毕。因此只需要配置相关的引脚控制。
定时器4节点
&timers4 {
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
pwm4: pwm {
pinctrl-0 = <&pwm4_pins_c>;
pinctrl-1 = <&pwm4_sleep_pins_c>;
pinctrl-names = "default", "sleep";
#pwm-cells = <2>;
status = "okay";
};
timer@3 {
status = "disabled";
};
};
引脚配置
&pinctrl {
pwm4_pins_c: pwm4-0 {
pins {
pinmux = <STM32_PINMUX('B', 6, AF2)>;
/* TIM4_CH1 */
bias-pull-down; //下拉
drive-push-pull; //推挽
slew-rate = <0>;
};
};
pwm4_sleep_pins_c: pwm4-sleep-0 {
pins {
pinmux = <STM32_PINMUX('B', 6, ANALOG)>; /* TIM4_CH1 */
};
};
};
(二)PWM
然后打开PWM的帮助文档
pwm.txt ---- 介绍用户节点
pwm.yaml ---- 介绍控制器节点
在pwm.txt中可以获得关于pwm用户节点的介绍:
#pwm-cells = <2>;
使用两个元素来描述PWM设备
第一个元素描述PWM通道号,0表示使用PWM控制器的第一个通道。
第二个元素描述PWM频率,单位是赫兹(Hz)
pwms:这是一个必需的属性,用于列出设备将要使用的PWM设备。它的值是一个或多个pwm-list结构
pwm-list属性的列表来指定想要使用的PWM设备
蜂鸣器节点
/{
//蜂鸣器
beeper {
compatible = "pwm-beeper";
pwms = <&pwm4 0 4000000>;
};
};
(三)设备树节点
由上述类比,可得
\{
//蜂鸣器
beeper {
compatible = "zyx,pwm-beep";
pwms = <&pwm4 0 4000000>;
};
//风扇
fun {
compatible = "zyx,pwm-fun";
pwms = <&pwm1 0 4000000>;
};
//马达
motor {
compatible = "zyx,pwm-motor";
pwms = <&pwm16 0 4000000>;
};
};
/***蜂鸣器***/
&timers4 {
/* spare dmas for other usage */
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
pwm4: pwm {
pinctrl-0 = <&pwm4_pins_c>;
pinctrl-1 = <&pwm4_sleep_pins_c>;
pinctrl-names = "default", "sleep";
#pwm-cells = <2>;
status = "okay";
};
timer@3 {
status = "disabled";
};
};
&pinctrl {
pwm4_pins_c: pwm4-0 {
pins {
pinmux = <STM32_PINMUX('B', 6, AF2)>;
/* TIM4_CH1 */
bias-pull-down;
drive-push-pull;
slew-rate = <0>;
};
};
pwm4_sleep_pins_c: pwm4-sleep-0 {
pins {
pinmux = <STM32_PINMUX('B', 6, ANALOG)>; /* TIM4_CH1 */
};
};
};
/***风扇***/
&timers1{
/* spare dmas for other usage */
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
pwm1: pwm {
pinctrl-0 = <&pwm1_pins_b>;
pinctrl-1 = <&pwm1_sleep_pins_b>;
pinctrl-names = "default", "sleep";
#pwm-cells = <2>;
status = "okay";
};
};
//pinctl文件中提供的pwm1引脚会包含其他引脚,因此此处仍然自己实现引脚
&pinctrl{
pwm1_pins_b: pwm1-0 {
pins {
pinmux = <STM32_PINMUX('E', 9, AF1)>;/* TIM1_CH1 */
bias-pull-down;
drive-push-pull;
slew-rate = <0>;
};
};
pwm1_sleep_pins_b: pwm1-sleep-0 {
pins {
pinmux = <STM32_PINMUX('E', 9, ANALOG)>; /* TIM1_CH1 */
};
};
};
/***马达***/
&timers16{
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
pwm16: pwm {
pinctrl-0 = <&pwm16_pins_a>;
pinctrl-1 = <&pwm16_sleep_pins_a>;
pinctrl-names = "default", "sleep";
#pwm-cells = <2>;
status = "okay";
};
};
&pinctrl{
pwm16_pins_a: pwm16-0 {
pins {
pinmux = <STM32_PINMUX('F', 6, AF1)>;/* TIM1_CH1 */
bias-pull-down;
drive-push-pull;
slew-rate = <0>;
};
};
pwm16_sleep_pins_a: pwm16-sleep-0 {
pins {
pinmux = <STM32_PINMUX('F', 6, ANALOG)>; /* TIM1_CH1 */
};
};
};
四、驱动
(一)相关API
1. 分配对象pwm_device
//PWM通道对象
struct pwm_device {
const char *label; //PWM设备名
unsigned long flags; //标志位
unsigned int hwpwm; //PWM设备索引
unsigned int pwm; //全局索引
struct pwm_chip *chip; //PWM设备的PWM芯片
void *chip_data; //芯片的私有数据
struct pwm_args args; //PWM参数
struct pwm_state state; //PWM当前状态
struct pwm_state last; //最后一次尝试实现的状态,用于调试
};
申请对象内存空间:
struct pwm_device *pwm;
pwm = kzalloc(sizeof(*pwm),GFP_KERNEL);
分配对象:
struct pwm_device *pwm_get(struct device *dev, const char *con_id);
功能:
请求一个PWM设备
参数:
@dev:device结构体指针,父类
@con_id:指向一个字符串,用作连接标识符或别名来指定所需的PWM设备
返回值:
成功,返回pwm_device结构体指针
失败,返回错误码指针
使用IS_ERR()判断
使用PTR_ERR()打印错误码
2. 配置PWM设备
static inline int pwm_config(struct pwm_device *pwm, int duty_ns,int period_ns);
功能:
改变PWM设备的配置
参数:
@pwm: PWM设备
@duty_ns: 高电平的时长(单位:ns)
@period_ns: 周期时长(单位:ns)
返回值:
成功,返回0;
失败,返回错误码;
3. 打开或者关闭PWM设备
static inline int pwm_enable(struct pwm_device *pwm);
功能:开始PWM输出
参数:
@pwm: PWM设备
返回值:
成功,返回0
失败,返回错误码
static inline void pwm_disable(struct pwm_device *pwm);
功能:停止PWM输出
参数:
@pwm: PWM设备
返回值: 无
4. 注销PWM设备
void pwm_free(struct pwm_device *pwm);
功能:
注销PWM设备
参数:
@pwm:PWM设备
返回值: 无
(二)代码示例
注意:风扇的占空比需要相对较高,否则可能无法转动
本次尝试设置百分之五十的占空比,结果无法转动
此处以风扇为例
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/pwm.h>
#include <linux/kernel.h>
#include <linux/input-event-codes.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#define CNAME "fun_driver"
#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
//1s = 1000000000ns
struct cdev *fun_cdev; //字符设备指针
int major=0;
int minor=0;
dev_t devno; //设备号
struct class *cls;
struct pwm_fun {
struct pwm_device *pwm;
unsigned long period;
};
struct pwm_fun *fun;
int fun_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t fun_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
int value;
//从用户空间读取频率
ret = copy_from_user(&value,ubuf,sizeof(value));
if(ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
//配置频率
if(value == 0)
{
//第五步:关闭PWM设备
pwm_disable(fun->pwm);
}else{
//第三步:配置pwm设备
fun->period = HZ_TO_NANOSECONDS(value);
ret = pwm_config(fun->pwm,fun->period,fun->period);
if(ret){
printk("pwm config error\n");
return ret;
}
//第四步:使能pwm设备
ret = pwm_enable(fun->pwm);
if(ret){
printk("pwm enable error\n");
return ret;
}
}
return size;
}
int fun_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
pwm_disable(fun->pwm);
return 0;
}
const struct file_operations fops = {
.open = fun_open,
.write = fun_write,
.release = fun_close,
};
//设备树匹配成功后会进入probe函数
int fun_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
//第一步:分配PWM对象
fun = kzalloc(sizeof(*fun),GFP_KERNEL);
if(!fun)
{
printk("fun kzalloc error\n");
ret = -ENOMEM;
goto err0;
}
//第二步:申请PWM对象
fun->pwm = pwm_get(dev,NULL);
if(IS_ERR(fun->pwm))
{
printk("failed to request pwm device:%ld\n",PTR_ERR(fun->pwm));
ret = PTR_ERR(fun->pwm);
goto err1;
}
/***注册字符设备***/
//1.分配对象
fun_cdev = cdev_alloc();
if(NULL == fun_cdev){
printk("cdev alloc error\n");
ret = -ENOMEM;
goto err2;
}
//2. 对象部分初始化
cdev_init(fun_cdev,&fops);
//3. 申请设备号
if(major > 0){ //静态申请主设备号
ret = register_chrdev_region(MKDEV(major,minor),1,CNAME);
if(ret < 0){
pr_err("register_chrdev_region failed\n");
goto err3;
}
}else{//动态分配设备号
ret = alloc_chrdev_region(&devno,0,1,CNAME);
if(ret < 0){
pr_err("alloc_chrdev_region failed\n");
goto err3;
}
major = MAJOR(devno);
minor = MINOR(devno);
}
//4. 注册字符设备
ret = cdev_add(fun_cdev,devno,1);
if(ret < 0){
printk("cdev add error\n");
goto err4;
}
/***自动创建设备节点***/
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls))
{
printk("class create error\n");
ret = PTR_ERR(cls);
goto err5;
}
dev = device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);
if(IS_ERR(dev))
{
printk("device create error\n");
ret = PTR_ERR(dev);
goto err6;
}
return 0;
err6:
class_destroy(cls);
err5:
cdev_del(fun_cdev);
err4:
unregister_chrdev_region(MKDEV(major,minor),1);
err3:
kfree(fun_cdev);
err2:
pwm_free(fun->pwm);
err1:
kfree(fun);
err0:
return ret;
}
//卸载驱动会调用remove函数
int fun_remove(struct platform_device *pdev)
{
device_destroy(cls,MKDEV(major,minor));
class_destroy(cls);
cdev_del(fun_cdev);
unregister_chrdev_region(MKDEV(major,minor),1);
kfree(fun_cdev);
//第六步:关闭PWM设备,释放PWM设备
pwm_disable(fun->pwm);
pwm_free(fun->pwm);
kfree(fun);
return 0;
}
//使用设备树匹配
const struct of_device_id ofmatch[] = {
{.compatible = "zyx,pwm-fun",},
{}
};
struct platform_driver fun_pwm = {
.probe = fun_probe,
.remove = fun_remove,
.driver = {
.name = "fun",
.of_match_table = ofmatch,
}
};
MODULE_DEVICE_TABLE(of,ofmatch);
module_platform_driver(fun_pwm);
MODULE_LICENSE("GPL");