pwm驱动十分常见,这篇博文就以pca9685为例,简单分析一下pwm驱动的常用套路,本文注重重代码逻辑,轻寄存器hack,寄存器hack,请参考datasheet。

代码解析

源码路径drivers/pwm/pwm-pca9685.c

首先看驱动的probe函数

static int pca9685_pwm_probe(struct i2c_client *client,const struct i2c_device_id *id)
{struct pca9685 *pca;//驱动结构体pcaint ret;int mode2;pca = devm_kzalloc(&client->dev, sizeof(*pca), GFP_KERNEL);//动态分配if (!pca)return -ENOMEM;pca->regmap = devm_regmap_init_i2c(client, &pca9685_regmap_i2c_config);//regmap初始化if (IS_ERR(pca->regmap)) {ret = PTR_ERR(pca->regmap);dev_err(&client->dev, "Failed to initialize register map: %d\n",ret);return ret;}pca->duty_ns = 0;pca->period_ns = PCA9685_DEFAULT_PERIOD;//默认值 5000000nsi2c_set_clientdata(client, pca); // client->dev.drvdata = pca;regmap_read(pca->regmap, PCA9685_MODE2, &mode2); //读取mode2寄存器if (device_property_read_bool(&client->dev, "invert"))//读取设备树,配置mode2寄存器。mode2 |= MODE2_INVRT;elsemode2 &= ~MODE2_INVRT;if (device_property_read_bool(&client->dev, "open-drain"))mode2 &= ~MODE2_OUTDRV;elsemode2 |= MODE2_OUTDRV;regmap_write(pca->regmap, PCA9685_MODE2, mode2);//设置mode2寄存器/* clear all "full off" bits */regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0);//LED清零regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0);pca->chip.ops = &pca9685_pwm_ops;        //设置pwm回调函数/* add an extra channel for ALL_LED */pca->chip.npwm = PCA9685_MAXCHAN + 1;    //设置pwm数量pca->chip.dev = &client->dev;pca->chip.base = -1;ret = pwmchip_add(&pca->chip);           //向pwm子系统注册当前pwm chipif (ret < 0)return ret;ret = pca9685_pwm_gpio_probe(pca);       //gpio初始化相关if (ret < 0) {pwmchip_remove(&pca->chip);return ret;}/* the chip comes out of power-up in the active state */pm_runtime_set_active(&client->dev);    //电源管理pm open/** enable will put the chip into suspend, which is what we* want as all outputs are disabled at this point*/pm_runtime_enable(&client->dev);        //电源管理pm enablereturn 0;
}

probe函数主要做了:

1.驱动结构体pca的动态分配,初始化。

2.regmap的初始化,用于提供读写i2c寄存器的底层接口。

3.芯片初始化的一些寄存器配置。

4.pwm回调函数注册。

5.gpio回调函数注册。

6.电源管理初始化。

编写pwm驱动,最核心的就是编写pwm_ops回调函数,这些回调函数会在,sys文件系统的相应文件读写时,被pwm子系统调用,让我们来看看:

默认echo路径:

/sys/class/pwm/pwmchipX/pwmX
static const struct pwm_ops pca9685_pwm_ops = { //pwm ops回调.enable = pca9685_pwm_enable,     //echo 1 >enable时调用.disable = pca9685_pwm_disable,   //echo 0 > enable时调用 .config = pca9685_pwm_config,     //echo xx >period; echo xx > duty_cycle时调用.request = pca9685_pwm_request,   //echo xx > export时调用.free = pca9685_pwm_free,         //echo xx >  unexport时调用.owner = THIS_MODULE,
};

pca9685_pwm_config回调函数是这个pwm驱动的核心,用于配置pwm的周期,占空比,简单注释一下。

static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,//pwm 配置int duty_ns, int period_ns)
{struct pca9685 *pca = to_pca(chip);unsigned long long duty;unsigned int reg;int prescale;if (period_ns != pca->period_ns) {//设置新的pwm周期prescale = DIV_ROUND_CLOSEST(PCA9685_OSC_CLOCK_MHZ * period_ns,//通过新的周期,算出prescalePCA9685_COUNTER_RANGE * 1000) - 1;if (prescale >= PCA9685_PRESCALE_MIN &&prescale <= PCA9685_PRESCALE_MAX) {/** putting the chip briefly into SLEEP mode* at this point won't interfere with the* pm_runtime framework, because the pm_runtime* state is guaranteed active here.*//* Put chip into sleep mode */pca9685_set_sleep_mode(pca, true);//设置为sleep mode/* Change the chip-wide output frequency */regmap_write(pca->regmap, PCA9685_PRESCALE, prescale);//设置除数/* Wake the chip up */pca9685_set_sleep_mode(pca, false);//wakeuppca->period_ns = period_ns;//新周期设置完成} else {dev_err(chip->dev,"prescaler not set: period out of bounds!\n");return -EINVAL;}}pca->duty_ns = duty_ns;//设置duty时间(一个周期内pwm为高的时间)if (duty_ns < 1) {if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_H;elsereg = LED_N_OFF_H(pwm->hwpwm);regmap_write(pca->regmap, reg, LED_FULL);//小于1ns,就认为输出一直为低return 0;}if (duty_ns == period_ns) {//如果两个时间相同,就认为是一直为高电平/* Clear both OFF registers */if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_L;elsereg = LED_N_OFF_L(pwm->hwpwm);regmap_write(pca->regmap, reg, 0x0);//full off 位清零if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_H;elsereg = LED_N_OFF_H(pwm->hwpwm);regmap_write(pca->regmap, reg, 0x0);/* Set the full ON bit */if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_ON_H;elsereg = LED_N_ON_H(pwm->hwpwm);regmap_write(pca->regmap, reg, LED_FULL);//full on位 置1return 0;}duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns;duty = DIV_ROUND_UP_ULL(duty, period_ns);//计算占空比 duty = (duty/period) * 4096 if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_L;elsereg = LED_N_OFF_L(pwm->hwpwm);regmap_write(pca->regmap, reg, (int)duty & 0xff);//设置占空比if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_H;elsereg = LED_N_OFF_H(pwm->hwpwm);regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf);//设置占空比的高4位/* Clear the full ON bit, otherwise the set OFF time has no effect */if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_ON_H;elsereg = LED_N_ON_H(pwm->hwpwm);regmap_write(pca->regmap, reg, 0);//full on 位清零,这样pwm才会生效return 0;
}

其他pwm回调函数的简单注释:

static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体unsigned int reg;/** The PWM subsystem does not support a pre-delay.* So, set the ON-timeout to 0*/if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_ON_L;elsereg = LED_N_ON_L(pwm->hwpwm);regmap_write(pca->regmap, reg, 0);//清除LED_ON_L寄存器if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_ON_H;elsereg = LED_N_ON_H(pwm->hwpwm);regmap_write(pca->regmap, reg, 0);//清除LED_ON_H寄存器,led full on/** Clear the full-off bit.* It has precedence over the others and must be off.*/if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_H;elsereg = LED_N_OFF_H(pwm->hwpwm);//清除LED_OFF寄存器full off位regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0);//return 0;
}static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体unsigned int reg;if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_H;elsereg = LED_N_OFF_H(pwm->hwpwm);regmap_write(pca->regmap, reg, LED_FULL);//led off full位置1,所有输出禁能。/* Clear the LED_OFF counter. */if (pwm->hwpwm >= PCA9685_MAXCHAN)reg = PCA9685_ALL_LED_OFF_L;elsereg = LED_N_OFF_L(pwm->hwpwm);regmap_write(pca->regmap, reg, 0x0);//
}static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
{struct pca9685 *pca = to_pca(chip);//获得pca驱动结构体if (pca9685_pwm_is_gpio(pca, pwm))//判断该port是否用作gpio, 如果用作gpio,则不能再申请该port作为pwm。return -EBUSY;pm_runtime_get_sync(chip->dev);//电源管理引用计数加1return 0;
}static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
{pca9685_pwm_disable(chip, pwm);//pwm禁能pm_runtime_put(chip->dev);//电源管理引用计数减1
}

电源管理的回调函数:

static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable)
{regmap_update_bits(pca->regmap, PCA9685_MODE1,//设置寄存器位MODE1_SLEEPMODE1_SLEEP, enable ? MODE1_SLEEP : 0);if (!enable) {/* Wait 500us for the oscillator to be back up */udelay(500);}
}static int pca9685_pwm_runtime_suspend(struct device *dev)//进入休眠时调用
{struct i2c_client *client = to_i2c_client(dev);struct pca9685 *pca = i2c_get_clientdata(client);pca9685_set_sleep_mode(pca, true);//芯片进入休眠return 0;
}static int pca9685_pwm_runtime_resume(struct device *dev)//从休眠恢复时调用
{struct i2c_client *client = to_i2c_client(dev);struct pca9685 *pca = i2c_get_clientdata(client);pca9685_set_sleep_mode(pca, false);//芯片唤醒return 0;
}static const struct dev_pm_ops pca9685_pwm_pm = {SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend,pca9685_pwm_runtime_resume, NULL)
};

gpio相关的配置暂不分析了。

简单总结一下

写pwm驱动的核心套路,那就是pwm_ops回调函数的编写。

主要实现底层功能:配置pwm周期,使能pwm,禁能pwm,即可。其他事情pwm子系统已经帮我们完成了。

pwm驱动 pca9685 代码简析相关推荐

  1. [2021-CVPR] Jigsaw Clustering for Unsupervised Visual Representation Learning 论文简析及关键代码简析

    [2021-CVPR] Jigsaw Clustering for Unsupervised Visual Representation Learning 论文简析及关键代码简析 论文:https:/ ...

  2. 关于php车服务论文,「PHP」行车服务app后端代码简析

    之前发布了一篇关于我的 行车服务 app iOS 端代码简析的文章:文章地址. 此篇是对这个项目后端 iOS端代码地址: iOS代码,PHP代码.如果你觉得有帮助,希望能够点个 Star ,感谢~ 笔 ...

  3. YOLOv5Face YOLO5Face人脸检测论文及代码简析

    YOLO5face人脸检测模型论文和代码简析 YOLO5Face模型分析 论文及源码下载 论文创新点 实验结果 下载代码跑起来 调整数据集 训练完成之后检验结果 一点点代码简析 文件结构 data m ...

  4. 智能机器人编程游戏robocode的运行代码简析

    智能机器人编程游戏robocode的运行代码简析 金庆 2007.6.1 阅读robocode1.3的源代码,查看运行的原理. (转载请注明来源于 金庆的专栏) 主线程Battle.run() --- ...

  5. Learning with Noisy Correspondence for Cross-modal Matching 文献翻译 代码简析

    Learning with Noisy Correspondence for Cross-modal Matching 基于噪声对应的跨模态匹配学习 Learning with Noisy Corre ...

  6. ruoyi框架默认的导出Excel功能代码简析

    ruoyi框架默认导出Excel功能 项目使用的是RuoYi Bootstrap多模块版本4.7.2,启动项目后会有默认的导出功能.包括使用ruoyi自带代码生成器,都会有导出功能的附带.接下就讲解一 ...

  7. WinForm 自动完成控件实例代码简析

    在Web的应用方面有js的插件实现自动完成(或叫智能提示)功能,但在WinForm窗体应用方面就没那么好了. TextBox控件本身是提供了一个自动提示功能,只要用上这三个属性: AutoComple ...

  8. Linux LED驱动源码简析

    驱动的加载与卸载函数 驱动加载服务函数 int major; static int first_drv_init(void) {major = register_chrdev(0, "fir ...

  9. django的manage.py代码简析

    django中创建一个project之后,就会创建一个以项目名称命名的文件中,文件夹中包含了一个同名文件夹和一个manage.py文件.比如: 之后如果要启动服务.同步数据库等操作,都是用python ...

最新文章

  1. .NET BitmapImage 内存释放问题解决方案
  2. mongodb常用命令
  3. SurfaceView-----------------转
  4. 服务器巡检文档,服务器巡检工具
  5. union和union all有什么区别
  6. vue模拟加载更多功能(数据追加)
  7. C语言怎样编程分子变化,C语言经典编程(一)
  8. Spring框架整合MyBatis框架
  9. windows 搭建kms服务器激活_windows下搭建MQTT服务器
  10. 风控人必知必会的征信知识
  11. visitor设计模式记录
  12. 2021年计算机网络常见面试题
  13. dw中html5快捷键,Adobe Dreamweaver(dw)常用快捷键--系统之家
  14. 【数字IC验证】1-systemverilog数据类型
  15. oracle rac实现,炼数成金Oracle 12C RAC集群原理与管理实战 16课
  16. [Lorg/openxmlformats/schemas/spreadsheetml/x2006/main/CTPhoneticRun报错
  17. vscode提示:“An SSH installation couldn‘t be found”
  18. 使用 pdb 进行调试
  19. golang 获取文件的MD5值
  20. 系统接口日志记录-AOP

热门文章

  1. MyBatis-01 初始+环境搭建
  2. 湖南安院•美和易思校企共建专业PPT制作与演讲大赛圆满落幕
  3. 蓝桥杯嵌入式第十二届省赛第一场
  4. 弘辽科技:春节应该怎么去运营店铺。
  5. JQuery Ajax局部刷新功能
  6. 计算机毕业设计python+django摄影作品相册分享系统(源码+系统+mysql数据库+Lw文档)
  7. wifi boombox android,15天+19项 15款蓝牙音响史上最严苛横评
  8. 分布式数据库 TiDB
  9. HCIE-双点双向引入
  10. Win10系统发生卡顿故障如何重装系统