Linux 内核探秘:从零构建 GPIO 设备驱动程序实战指南

news2025/5/22 22:59:19

在嵌入式系统开发领域,GPIO(通用输入 / 输出)作为硬件与软件交互的桥梁,是实现设备控制与数据采集的基础。编写高效、稳定的 GPIO 设备驱动程序,对于发挥硬件性能至关重要。本文将深入剖析 Linux 内核中 GPIO 驱动开发的全流程,从原理到代码实现,带你一步步掌握 GPIO 设备驱动的核心技术。

一、GPIO 驱动基础理论

1.1 GPIO 工作原理

GPIO 引脚可以通过软件配置为输入或输出模式。作为输出时,可设置引脚的高低电平,控制外部设备,如 LED 灯的亮灭、继电器的开合;作为输入时,可读取引脚的电平状态,获取外部设备的信号,例如按钮的按下与释放。在 Linux 系统中,GPIO 的操作需要通过驱动程序来实现对硬件寄存器的读写控制。

1.2 Linux 驱动模型与 GPIO 子系统

Linux 采用统一的设备驱动模型,将设备分为字符设备、块设备和网络设备等类型。GPIO 设备通常作为字符设备进行驱动开发。Linux 内核提供了 GPIO 子系统,它封装了底层硬件操作,为驱动开发者提供了一套通用的 API,使得开发者无需直接操作硬件寄存器,降低了开发难度,提高了驱动的可移植性和通用性。

二、搭建开发环境

2.1 硬件准备

首先需要准备一块支持 GPIO 功能的开发板,如树莓派、STM32MP1 系列开发板等。确保开发板与主机能够正常通信,一般通过串口或 SSH 进行连接。

2.2 软件准备

在主机上安装交叉编译工具链,用于将驱动程序代码编译为目标开发板的可执行文件。例如,对于 ARM 架构的开发板,可能需要安装arm-linux-gnueabihf-gcc等工具链。同时,安装 Linux 内核源代码,可从官方网站下载对应开发板的内核版本,解压后作为驱动开发的基础。

三、编写 GPIO 驱动程序

3.1 驱动框架搭建

基于 Linux 字符设备驱动框架,编写 GPIO 驱动的基本结构。首先定义设备号、设备类和设备节点:

 

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/uaccess.h>

#include <linux/gpio.h>

#include <linux/of_gpio.h>

#define DEVICE_NAME "my_gpio_device"

static dev_t dev_num;

static struct cdev my_cdev;

static struct class *my_class;

然后实现驱动的初始化和退出函数:

 

static int __init my_gpio_driver_init(void)

{

// 申请设备号

int ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);

if (ret < 0) {

printk(KERN_ERR "Failed to allocate device number\n");

return ret;

}

// 初始化cdev结构

cdev_init(&my_cdev, &my_fops);

my_cdev.owner = THIS_MODULE;

// 添加cdev到系统

ret = cdev_add(&my_cdev, dev_num, 1);

if (ret < 0) {

printk(KERN_ERR "Failed to add cdev\n");

unregister_chrdev_region(dev_num, 1);

return ret;

}

// 创建设备类

my_class = class_create(THIS_MODULE, DEVICE_NAME);

if (IS_ERR(my_class)) {

printk(KERN_ERR "Failed to create class\n");

cdev_del(&my_cdev);

unregister_chrdev_region(dev_num, 1);

return PTR_ERR(my_class);

}

// 创建设备节点

device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);

return 0;

}

static void __exit my_gpio_driver_exit(void)

{

device_destroy(my_class, dev_num);

class_destroy(my_class);

cdev_del(&my_cdev);

unregister_chrdev_region(dev_num, 1);

}

module_init(my_gpio_driver_init);

module_exit(my_gpio_driver_exit);

3.2 GPIO 操作实现

从设备树获取 GPIO 引脚编号,并实现 GPIO 的输入输出操作。假设设备树中已定义好 GPIO 节点:

 

static int gpio_num;

static int __init get_gpio_num(void)

{

struct device_node *np = of_find_compatible_node(NULL, NULL, "my,gpio-device");

if (!np) {

printk(KERN_ERR "Failed to find device node\n");

return -ENODEV;

}

gpio_num = of_get_named_gpio(np, "gpio", 0);

if (gpio_num < 0) {

printk(KERN_ERR "Failed to get gpio number\n");

return gpio_num;

}

if (gpio_request(gpio_num, "my_gpio")) {

printk(KERN_ERR "Failed to request gpio\n");

return -EBUSY;

}

return 0;

}

实现文件操作接口函数,如open、read、write等:

 

static int my_gpio_open(struct inode *inode, struct file *filp)

{

if (get_gpio_num() < 0) {

return -EIO;

}

// 设置为输出模式

gpio_direction_output(gpio_num, 0);

return 0;

}

static ssize_t my_gpio_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)

{

int value;

if (count != sizeof(int)) {

return -EINVAL;

}

if (copy_from_user(&value, buf, sizeof(int))) {

return -EFAULT;

}

gpio_set_value(gpio_num, value);

return count;

}

static ssize_t my_gpio_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)

{

int value = gpio_get_value(gpio_num);

if (copy_to_user(buf, &value, sizeof(int))) {

return -EFAULT;

}

return count;

}

static int my_gpio_release(struct inode *inode, struct file *filp)

{

gpio_free(gpio_num);

return 0;

}

static const struct file_operations my_fops = {

.owner = THIS_MODULE,

.open = my_gpio_open,

.write = my_gpio_write,

.read = my_gpio_read,

.release = my_gpio_release,

};

四、编译与测试

4.1 编译驱动程序

编写 Makefile 文件,指定内核源代码路径和交叉编译工具链:

 

obj-m += my_gpio_driver.o

all:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

执行make命令编译驱动程序,生成.ko文件。

4.2 加载与测试

将编译好的驱动模块拷贝到开发板上,使用insmod命令加载驱动:

 

insmod my_gpio_driver.ko

加载成功后,会在/dev目录下生成设备节点my_gpio_device。编写测试程序,通过设备节点对 GPIO 进行读写操作:

 

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#include <unistd.h>

int main()

{

int fd = open("/dev/my_gpio_device", O_RDWR);

if (fd < 0) {

perror("open");

return -1;

}

int value = 1;

write(fd, &value, sizeof(int)); // 点亮LED

sleep(2);

value = 0;

write(fd, &value, sizeof(int)); // 熄灭LED

close(fd);

return 0;

}

编译并运行测试程序,观察 GPIO 控制的设备状态变化。

五、总结与进阶

通过以上步骤,我们完成了一个简单的 GPIO 设备驱动程序的编写、编译、加载与测试。但实际应用中,还需考虑更多问题,如中断处理、电源管理、错误处理优化等。后续可以深入研究 Linux 内核文档和优秀的开源驱动代码,进一步提升驱动开发能力,为更复杂的嵌入式系统开发打下坚实基础。

上述文章涵盖了 GPIO 驱动开发全流程,若你觉得某些部分需要更详细讲解,或想增加调试技巧等内容,随时告诉我。

  • 摩尔狮云计算每日课堂Top1-课程大纲:
    Linux系统管理-数据库与监控平台-网络安全与控制技术课程大纲:
  • 本课程聚焦 Linux 系统管理、数据库、监控平台、网络安全与控制四大核心板块。Linux 系统管理模块,涵盖系统安装配置、用户权限管理、进程服务维护、文件系统存储及网络管理等内容;数据库板块从基础理论切入,深入讲解关系数据库与 SQL 语言、数据库设计及主流数据库(如 MySQL、Oracle)的应用;监控平台部分,介绍开源工具(Zabbix、Prometheus 等)的使用,以及系统性能监控、数据可视化;网络安全与控制技术,剖析网络安全基础、常见攻击防范、防火墙配置、网络加密与 VPN 技术。教学采用理论讲授、实践操作、小组项目、案例分析及在线学习相结合的方式,考核通过平时成绩、期中期末笔试、小组项目综合评定,旨在培养学生系统运维、数据库管理、性能监控及网络安全防护等多方面能力。

云计算培训摩尔狮的独特优势助力解决问题 摩尔狮的课程不仅有理论知识和实践方法,还有强大的师资团队和教学服务。当遇到运维相关问题时,不要慌张。借助在摩尔狮学到的知识和技能,从理论分析到实践排查,多维度入手,就能精准定位并解决问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2383446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openlayer:10点击地图上某些省份利用Overlay实现提示省份名称

实现点击地图上的省份&#xff0c;在点击经纬度坐标位置附近利用Overlay实现提示框提示相关省份名称。本文介绍了如何通过OpenLayers库实现点击地图上的省份&#xff0c;并在点击的经纬度坐标位置附近显示提示框&#xff0c;提示相关省份名称。首先&#xff0c;定义了两个全局变…

upload-labs通关笔记-第13关 文件上传之白名单POST法

目录 一、白名单过滤 二、%00截断 1.截断原理 2、截断条件 &#xff08;1&#xff09;PHP版本 < 5.3.4 &#xff08;2&#xff09;magic_quotes_gpc配置为Off &#xff08;3&#xff09;代码逻辑存在缺陷 三、源码分析 1、代码审计 &#xff08;1&#xff09;文件…

数据库健康监测器(BHM)实战:如何通过 HTML 报告识别潜在问题

在数据库运维中,健康监测是保障系统稳定性与性能的关键环节。通过 HTML 报告,开发者可以直观查看数据库的运行状态、资源使用情况与潜在风险。 本文将围绕 数据库健康监测器(Database Health Monitor, BHM) 的核心功能展开分析,结合 Prometheus + Grafana + MySQL Export…

Oracle 11g 单实例使用+asm修改主机名导致ORA-29701 故障分析

解决 把服务器名修改为原来的&#xff0c;重启服务器。 故障 建表空间失败。 分析 查看告警日志 ORA-1119 signalled during: create tablespace splex datafile ‘DATA’ size 2000M… Tue May 20 18:04:28 2025 create tablespace splex datafile ‘DATA/option/dataf…

OpenCV CUDA模块图像过滤------用于创建一个最大值盒式滤波器(Max Box Filter)函数createBoxMaxFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 createBoxMaxFilter()函数创建的是一个 最大值滤波器&#xff08;Maximum Filter&#xff09;&#xff0c;它对图像中每个像素邻域内的像素值取最…

Redis数据库-消息队列

一、消息队列介绍 二、基于List结构模拟消息队列 总结&#xff1a; 三、基于PubSub实现消息队列 (1)PubSub介绍 PubSub是publish与subscribe两个单词的缩写&#xff0c;见明知意&#xff0c;PubSub就是发布与订阅的意思。 可以到Redis官网查看通配符的书写规则&#xff1a; …

破解充电安全难题:智能终端的多重防护体系构建

随着智能终端的普及&#xff0c;充电安全问题日益凸显。从电池过热到短路起火&#xff0c;充电过程中的安全隐患不仅威胁用户的生命财产安全&#xff0c;也制约了行业的发展。如何构建一套高效、可靠的多重防护体系&#xff0c;成为破解充电安全难题的关键。通过技术创新和系统…

apptrace 三大策略,助力电商 App 在 618 突围

随着 5 月 13 日 “618” 电商大促预售战的打响&#xff0c;各大平台纷纷祭出百亿补贴、消费券等大招&#xff0c;投入超百亿流量与数十亿现金&#xff0c;意图在这场年度商战中抢占先机。但这场流量争夺战远比想象中艰难&#xff0c;中国互联网络信息中心数据显示&#xff0c;…

SuperVINS:应对挑战性成像条件的实时视觉-惯性SLAM框架【全流程配置与测试!!!】【2025最新版!!!!】

一、项目背景及意义 SuperVINS是一个改进的视觉-惯性SLAM&#xff08;同时定位与地图构建&#xff09;框架&#xff0c;旨在解决在挑战性成像条件下的定位和地图构建问题。该项目基于经典的VINS-Fusion框架&#xff0c;但通过引入深度学习方法进行了显著改进。 视觉-惯性导航系…

Node-Red通过开疆智能Profinet转ModbusTCP采集西门子PLC数据配置案例

一、内容简介 本篇内容主要介绍Node-Red通过node-red-contrib-modbus插件与开疆智能ModbusTCP转Profinet设备进行通讯&#xff0c;这里Profinet转ModbusTCP网关作为从站设备&#xff0c;Node-Red作为主站分别从0地址开始读取10个线圈状态和10个保持寄存器&#xff0c;分别用Mo…

【性能测试】jvm监控

使用本地jvisualvm远程监控服务器 参考文章&#xff1a;https://blog.csdn.net/yeyuningzi/article/details/140261411 jvisualvm工具默认是监控本地jvm&#xff0c;如果需要监控远程就要修改配置参数 1、先查看是否打开 ps -ef|java 如果打开杀掉进程 2、进入项目服务路径下…

Uniapp开发鸿蒙应用时如何运行和调试项目

经过前几天的分享&#xff0c;大家应该应该对uniapp开发鸿蒙应用的开发语法有了一定的了解&#xff0c;可以进行一些简单的应用开发&#xff0c;今天分享一下在使用uniapp开发鸿蒙应用时怎么运行到鸿蒙设备&#xff0c;并且在开发中怎么调试程序。 运行 Uniapp项目支持运行到…

QT+RSVisa控制LXI仪器

1.下载并安装visa R&SVISA - Rohde & Schwarz China 2.安装后的目录说明 安装了64位visa会默认把32位的安装上&#xff1b; 64位库和头文件目录为&#xff1a;C:\Program Files\IVI Foundation 32位库和头文件目录为&#xff1a;C:\Program Files (x86)\IVI Foundation…

springboot3+vue3融合项目实战-大事件文章管理系统-文章分类也表查询(条件分页)

在pojo实体类中增加pagebean实体类 Data NoArgsConstructor AllArgsConstructor public class PageBean <T>{private Long total;//总条数private List<T> items;//当前页数据集合 }articlecontroller增加代码 GetMappingpublic Result<PageBean<Article&g…

Canvas进阶篇:鼠标交互动画

Canvas进阶篇&#xff1a;鼠标交互动画 前言获取鼠标坐标鼠标事件点击事件监听代码示例效果预览 拖动事件监听代码示例效果预览 结语 前言 在上一篇文章Canvas进阶篇&#xff1a;基本动画详解 中&#xff0c;我们讲述了在Canvas中实现动画的基本步骤和动画的绘制方法。本文将进…

【Node.js】Web开发框架

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. Node.js Web框架概述1.1 Web框架的作用1.2 Node.js主要Web框架生态1.3 框架选择考虑因素 2. Express.js2.1 Express.js概述2.2 基本用法2.2.1 安装Express2.2.2 创建基本服务器 2.3 路由2.4 中间件2.5 请求…

使用Vite创建一个动态网页的前端项目

1. 引言 虽然现在的前端更新换代的速度很快&#xff0c;IDE和工具一批批的换&#xff0c;但是我们始终要理解一点基本的程序构建的思维&#xff0c;这些环境和工具都是为了帮助我们更快的发布程序。笔者还记得以前写前端代码的时候&#xff0c;只使用文本编辑器&#xff0c;然…

系统架构设计师案例分析题——web篇

软考高项系统架构设计师&#xff0c;其中的科二案例分析题为5选3&#xff0c;总分75达到45分即合格。本贴来归纳web设计题目中常见的知识点即细节&#xff1a; 目录 一.核心知识 1.常见英文名词 2.私有云 3.面向对象三模型 4.计网相关——TCP和UDP的差异 5.MQTT和AMQP协…

MySQL--day5--多表查询

&#xff08;以下内容全部来自上述课程&#xff09; 多表查询 1. 为什么要用多表查询 # 如果不用多表查询 #查询员工名为Abel的人在哪个城市工作? SELECT* FROM employees WHERE last_name Abel;SELECT * FROM departments WHERE department_id 80;SELECT * FROM locati…

leetcode hot100刷题日记——7.最大子数组和

class Solution { public:int maxSubArray(vector<int>& nums) {//方法一&#xff1a;动态规划//dp[i]表示以i下标结尾的数组的最大子数组和//那么在i0时&#xff0c;dp[0]nums[0]//之后要考虑的就是我们要不要把下一个数加进来&#xff0c;如果下一个数加进来会使结…