前言

上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM。本篇的学习会将写一个驱动操作GPIO,同样的也可以发生PWM,因此这里还需要部分的硬件配合,需要一块开发板,当然可能还需要一台示波器。

原理图

和上一篇相同,引脚依然是GPIO3_D0,具体硬件肯定会不同,注意参考socdatasheet和硬件原理图,先定位正确需要操作的GPIO

IO模拟输出PWM

这里驱动实现的方式是先创建一个内核线程,如何创建内核线程可以参考Linux内核驱动学习(五)KThread学习总结,然后在线程函数一直循环反转IO口的输出。这里的目的单纯是为了学习操作GPIO,不建议项目中通过这种IO口模拟的方式去实现PWM的输出,而应该直接使用自带PWM功能的引脚。

设备树

	gpio-demo {
compatible = "gpio-demo";
gpios = <&gpio3 0 GPIO_ACTIVE_LOW>;
};

驱动源码中通过of_get_gpio接口去解析gpio

驱动端

驱动源码中of_device_id结构体变量中的成员.compatible的值必须和设备树的设备节点兼容属性compatible的值相同;

static struct of_device_id gpio_demo_of_match[] = {
{ .compatible = "gpio-demo"},
{},
} MODULE_DEVICE_TABLE(of,gpio_demo_of_match); static struct platform_driver gpio_demo_driver = {
.probe = gpio_demo_probe,
.driver = {
.name = "gpio-demo-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_demo_of_match),
}
};

probe函数实现对设备树节点的解析,of_get_gpio对应gpio-demo节点下的gpios属性;

然后ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name)语句初始化GPIO为输出引脚;

static int gpio_demo_probe(struct platform_device *pdev){

	int ret,i;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node; if (!node)
return -EINVAL;
ret = of_gpio_count(node);
if (ret == 0){
return -EINVAL;
}
priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(int) * ret, GFP_KERNEL);
if (!priv){
return -ENOMEM;
}
priv->count = ret;
mutex_init(&priv->mtx);
for (i = 0; i < priv->count; i++) {
unsigned int gpio;
gpio = of_get_gpio(node, i);
if (gpio < 0) {
dev_warn(dev, "Unable to get gpio #%d\n", i);
continue;
}
ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
priv->gpio[i] = gpio;
if (ret < 0) { dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
gpio, ret);
continue;
}
printk(KERN_INFO "success request gpio %d\n",gpio); gpio_direction_output(gpio, 1); //设置输出的电平 }
return 0;
}

线程执行函数中通过gpio_set_value设置GPIO的输出值,然后休眠50毫秒,最终PWM的周期应该是100毫秒左右。

static int thread_func(void *data) {	

	int i, count;
while (1){
count++;
mutex_lock(&priv->mtx);
for ( i = 0; i < priv->count; i++){
gpio_set_value(priv->gpio[i], count%2);
}
mutex_unlock(&priv->mtx);
msleep(50);
printk(KERN_INFO "thread count %d\n", count);
}
return 0;
}

gpio_set_valuegpio_direction_output的区别

如果使用该GPIO时,不会动态地切换输入输出,建议在开始时就设置好GPIO 输出方向,后面拉高拉低时使用gpio_set_value()接口,而不建议使用gpio_direction_output(), 因为gpio_direction_output接口里面有mutex锁,对中断上下文调用会有错误异常,且相比 gpio_set_valuegpio_direction_output 所做事情更多,浪费。

调试信息

先通过debugfs查看相应的GPIO已经成功加载到内核了;但是我们目前没有留用户层调用的接口,这个有悖于我们的初衷,但是目前为止已经实现了自己想要的效果。

实验结果

附录

#include <linux/module.h>
#include <linux/init.h> #include <linux/platform_device.h>
//API for libgpio
#include <linux/gpio.h>
//API for malloc
#include <linux/slab.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/mutex.h> static struct task_struct *thread_body;
struct gpio_demo_priv{
int count;
int gpio[0];
struct mutex mtx;
int mode;
}; struct gpio_demo_priv *priv; static int thread_func(void *data) { int i, count;
while (1){
count++;
mutex_lock(&priv->mtx);
for ( i = 0; i < priv->count; i++){
gpio_set_value(priv->gpio[i], count%2);
}
mutex_unlock(&priv->mtx);
msleep(50);
printk(KERN_INFO "thread count %d\n", count);
}
return 0;
} static int gpio_demo_probe(struct platform_device *pdev){ int ret,i;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node; if (!node)
return -EINVAL; ret = of_gpio_count(node);
if (ret == 0){
return -EINVAL;
} priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(int) * ret, GFP_KERNEL); if (!priv){
return -ENOMEM;
} priv->count = ret;
mutex_init(&priv->mtx);
for (i = 0; i < priv->count; i++) {
unsigned int gpio;
gpio = of_get_gpio(node, i);
if (gpio < 0) {
dev_warn(dev, "Unable to get gpio #%d\n", i);
continue;
}
ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
priv->gpio[i] = gpio;
if (ret < 0) { dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
gpio, ret);
continue;
}
printk(KERN_INFO "success request gpio %d\n",gpio); gpio_direction_output(gpio, 1); //设置输出的电平 }
platform_set_drvdata(pdev,priv); thread_body = kthread_create(thread_func, NULL, "thread_pwm");
if((thread_body))
{
wake_up_process(thread_body);
} return 0;
} static struct of_device_id gpio_demo_of_match[] = {
{ .compatible = "gpio-demo"},
{},
} MODULE_DEVICE_TABLE(of,gpio_demo_of_match); static struct platform_driver gpio_demo_driver = {
.probe = gpio_demo_probe,
.driver = {
.name = "gpio-demo-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_demo_of_match),
}
}; static int __init gpio_demo_init(void){
return platform_driver_register(&gpio_demo_driver);
} static void __exit gpio_demo_exit(void){
platform_driver_unregister(&gpio_demo_driver);
} late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gpio demo Driver");
MODULE_ALIAS("platform:gpio-demo");

Linux内核驱动学习(八)GPIO驱动模拟输出PWM的更多相关文章

  1. LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

    LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

  2. LINUX内核分析第八周学习总结

    LINUX内核分析第八周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

  3. Linux 内核分析第八周学习笔记

    Linux 内核分析第八周学习笔记 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...

  4. Linux 内核协议栈 学习资料

    终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...

  5. Linux内核设计第八周 ——进程的切换和系统的一般执行过程

    Linux内核设计第八周 ——进程的切换和系统的一般执行过程 第一部分 知识点总结 第二部分 实验部分 1.配置实验环境,确保menu内核可以正常启动 2.进入gdb调试,在shedule和conte ...

  6. Linux内核分析第八周——进程的切换和系统的一般执行过程

    Linux内核分析第八周--进程的切换和系统的一般执行过程 李雪琦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cou ...

  7. Linux 内核list_head 学习

    Linux 内核list_head 学习(一) http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html 在Linux内核中,提 ...

  8. Linux内核调用SPI平台级驱动_实现OLED的显示功能

    Linux内核调用SPI驱动_实现OLED显示功能 0. 导语 进入Linux的世界,发现真的是无比的有趣,也发现搞Linux驱动从底层嵌入式搞起真的是很有益处.我们在单片机.DSP这些无操作系统的裸 ...

  9. LINUX内核分析第八周学习总结:进程的切换和系统的一般执行过程

    韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程切换的关 ...

  10. Linux内核分析——第八周学习笔记

    实验作业:进程调度时机跟踪分析进程调度与进程切换的过程 20135313吴子怡.北京电子科技学院 [第一部分]理解Linux系统中进程调度的时机 1.Linux的调度程序是一个叫schedule()的 ...

随机推荐

  1. numpy basic sheatsheet

    NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库.NumPy 通常与 SciPy(Scien ...

  2. [Laravel] 自带分页实现以及links方法不存在错误

    自带分页实现其实挺简单的,但是我在实现的时候报错!找了很久才找出原因! 废话不说上码 控制器LeeController.php层 <?php namespace App\Http\control ...

  3. 基于TextRank算法的文本摘要

    本文介绍TextRank算法及其在多篇单领域文本数据中抽取句子组成摘要中的应用. TextRank 算法是一种用于文本的基于图的排序算法,通过把文本分割成若干组成单元(句子),构建节点连接图,用句子之 ...

  4. PostMan接口测试(很全面的接口测试教程)

    一:理论部分 1. 前言 在前后端分离开发时,后端工作人员完成系统接口开发后,需要与前端人员对接,测试调试接口,验证接口的正确性可用性.而这要求前端开发进度和后端进度保持基本一致,任何一方的进度跟不上 ...

  5. Python初学者常见错误问题汇总

    1.在客户端和服务端如何传递数组? 答:在客户端和服务端可以使用json进行数据传输.在客户端把数据转换成json字符串,然后使用POST方法发送给服务端. 服务端收集到数据之后,使用json.loa ...

  6. PG TO Oracle 增量同步-外部表

    背景 最近在负责公司数据Oracle转PG:老平台数据库:Oracle11g:新平台数据库:PostgreSQL12.由于平台统计规则有变动:所以正在推广的游戏数据无法全部迁移过来:只能在老平台上运行 ...

  7. Android--sos闪光灯

    Camera camera = null; Parameters parameters = null; Handler handler = new Handler() { @Override publ ...

  8. 详解 Discuz 的 PHP经典加密解密函数 authcode

    函数注释: // $string: 明文 或 密文 // $operation:DECODE表示解密,其它表示加密 // $key: 密匙 // $expiry:密文有效期 function auth ...

  9. Inno Setup [Run] Section 双引号嵌套

    https://stackoverflow.com/questions/26257808/inno-setup-how-to-run-a-code-procedure-in-run-section-o ...

  10. Docker基本使用(一)

    一.为什么使用容器? 1. 上线流程繁琐开发->测试->申请资源->审批->部署->测试等环节2. 资源利用率低普遍服务器利用率低,造成过多浪费3. 扩容/缩容不及时业务 ...