Linux内核驱动学习(八)GPIO驱动模拟输出PWM
前言
上一篇的学习中介绍了如何在用户空间直接操作GPIO,并写了一个脚本可以产生PWM
。本篇的学习会将写一个驱动操作GPIO
,同样的也可以发生PWM
,因此这里还需要部分的硬件配合,需要一块开发板,当然可能还需要一台示波器。
原理图
和上一篇相同,引脚依然是GPIO3_D0
,具体硬件肯定会不同,注意参考soc
的datasheet
和硬件原理图,先定位正确需要操作的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_value
和gpio_direction_output
的区别
如果使用该GPIO时,不会动态地切换输入输出
,建议在开始时就设置好GPIO 输出方向,后面拉高拉低时使用gpio_set_value()接口
,而不建议使用gpio_direction_output()
, 因为gpio_direction_output
接口里面有mutex锁
,对中断上下文调用会有错误异常,且相比gpio_set_value
,gpio_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的更多相关文章
- LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程
LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...
- LINUX内核分析第八周学习总结
LINUX内核分析第八周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...
- Linux 内核分析第八周学习笔记
Linux 内核分析第八周学习笔记 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10 ...
- Linux 内核协议栈 学习资料
终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...
- Linux内核设计第八周 ——进程的切换和系统的一般执行过程
Linux内核设计第八周 ——进程的切换和系统的一般执行过程 第一部分 知识点总结 第二部分 实验部分 1.配置实验环境,确保menu内核可以正常启动 2.进入gdb调试,在shedule和conte ...
- Linux内核分析第八周——进程的切换和系统的一般执行过程
Linux内核分析第八周--进程的切换和系统的一般执行过程 李雪琦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cou ...
- Linux 内核list_head 学习
Linux 内核list_head 学习(一) http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html 在Linux内核中,提 ...
- Linux内核调用SPI平台级驱动_实现OLED的显示功能
Linux内核调用SPI驱动_实现OLED显示功能 0. 导语 进入Linux的世界,发现真的是无比的有趣,也发现搞Linux驱动从底层嵌入式搞起真的是很有益处.我们在单片机.DSP这些无操作系统的裸 ...
- LINUX内核分析第八周学习总结:进程的切换和系统的一般执行过程
韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.进程切换的关 ...
- Linux内核分析——第八周学习笔记
实验作业:进程调度时机跟踪分析进程调度与进程切换的过程 20135313吴子怡.北京电子科技学院 [第一部分]理解Linux系统中进程调度的时机 1.Linux的调度程序是一个叫schedule()的 ...
随机推荐
- 如何可视化深度学习网络中Attention层
前言 在训练深度学习模型时,常想一窥网络结构中的attention层权重分布,观察序列输入的哪些词或者词组合是网络比较care的.在小论文中主要研究了关于词性POS对输入序列的注意力机制.同时对比实验 ...
- 排序算法代码实现-Java
前言 为了准备面试,从2月开始将排序算法认认真真得刷了一遍,通过看书看视频,实践打代码,还有一部分的leetcode题,自己感觉也有点进步,将笔记记录总结发出来. 冒泡排序 该排序就是一种像泡泡浮到水 ...
- bluecms v1.6 sp1 代码审计学习
前言 正式开始代码审计的学习,拓宽自己的知识面.代码审计学习的动力也是来自团队里的王叹之师傅,向王叹之师傅学习. 这里参考了一些前辈,师傅的复现经验和bluecms审计的心得 安装 install.p ...
- API加密框架monkey-api-encrypt发布1.2版本
框架介绍 monkey-api-encrypt 是我之前写的一个API加密的框架,主要是将加密/解密的逻辑交给框架实现,等数据到达Controller后自动解密了,让开发人员不需要关注数据的加解密操作 ...
- 权威的国际敏捷认证Certified Scrum Master (CSM)
权威的国际敏捷认证Certified Scrum Master (CSM) A. 认证前 在学习Certified Scrum Master (CSM)之前,你需要了解: 什么是CSM CSM认证与其 ...
- tensorflow1.0 placeholder占位符
import tensorflow as tf #(tf.float32,[2,2]) input1 = tf.placeholder(tf.float32) input2 = tf.placehol ...
- Java 多线程--ThreadLocal Timer ExecutorService
ThreadLocal /** * ThreadLocal:每个线程自身的存储本地.局部区域 * @author xzlf * */ public class ThreadLocalTest01 { ...
- 认证与授权】Spring Security系列之认证流程解析
上面我们一起开始了Spring Security的初体验,并通过简单的配置甚至零配置就可以完成一个简单的认证流程.可能我们都有很大的疑惑,这中间到底发生了什么,为什么简单的配置就可以完成一个认证流程啊 ...
- Python之numpy,pandas实践
Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言. Jupyter Notebook 的本质是一个 Web 应用程序,便 ...
- 这是那些大佬程序员常用的学习java网站,这就是别人薪资上万的原因
大学四年,看课本是不可能一直看课本的了,对于学习,特别是自学,善于搜索网上的一些资源来辅助,还是非常有必要的,下面我就把这几年私藏的各种资源,网站贡献出来给你们.主要有:电子书搜索.实用工具.在线视频 ...