当前位置: 首页 > news >正文

12.DTS中增加GPIO信息

说明

  1. DTS里增加GPIO寄存器物理地址(硬件信息只放在设备树,C代码不硬编码地址)
  2. 驱动里用ioremap把物理地址转为内核虚拟地址,复刻STM32指针操作寄存器
  3. 分层结构不变:平台驱动负责资源获取,底层函数只操作寄存器,完全对标单片机写法
  4. 应用程序read()就可以读取GPIO引脚电平

1. 设备树DTS节点

myhello_gpio { compatible = "mycompany,hello_gpio"; status = "okay"; /* 物理基地址 + 寄存器区间长度,对应SOC的GPIO外设基地址 */ reg = <0x40020000 0x1000>; };

把这里的0x40020000为芯片真实GPIOA/GPIOB物理地址,和STM32手册一致。
重新编译dtb烧入开发板。


2. 驱动 hello_gpio.c

分层结构

  • 第一层(硬件层):纯寄存器读写函数,只接收基地址,和单片机代码几乎一模一样
  • 第二层(框架层):平台驱动从DTS取出地址,做地址映射,注册字符设备
#include <linux/module.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/io.h> #include <linux/err.h> #define DEV_NAME "hello_gpio" /* 字符设备全局变量 */ static dev_t devno; static struct cdev cdev; static struct class *dev_class; /* 寄存器虚拟基地址,代替单片机里的 volatile 指针 */ static void __iomem *gpio_base = NULL; /************************************************* * 【第一层:硬件寄存器操作层,对标STM32裸机代码】 * 只操作寄存器偏移,不写死物理地址 *************************************************/ #define GPIO_IDR 0x10 /* 输入数据寄存器偏移,和STM32寄存器表一致 */ /* 读取GPIO引脚电平,等价于:*GPIO_IDR & (1<<pin) */ static int gpio_read_pin(int pin) { u32 val = readl(gpio_base + GPIO_IDR); return (val >> pin) & 0x01; } /************************************************* * 文件操作接口:应用open/read调用到这里 *************************************************/ static int hello_open(struct file *file) { pr_info("gpio dev open\n"); return 0; } static ssize_t hello_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { char k_buf[16]; int level = gpio_read_pin(0); // 读取PIN0引脚电平 snprintf(k_buf, sizeof(k_buf), "%d\n", level); copy_to_user(buf, k_buf, strlen(k_buf)); return strlen(k_buf); } static int hello_release(struct file *file) { pr_info("gpio dev close\n"); return 0; } static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .read = hello_read, .release = hello_release, }; /************************************************* * 【第二层:平台驱动框架,从DTS获取硬件资源】 *************************************************/ static int hello_probe(struct platform_device *pdev) { struct resource res; int ret; /* 1. 从设备树reg中取出物理地址 */ if (platform_get_resource(pdev, IORESOURCE_MEM, 0, &res)) { pr_err("get mem resource failed\n"); return -ENODEV; } /* 2. 物理地址 → 内核虚拟地址,替代单片机直接指针访问 */ gpio_base = ioremap(res->start, resource_size(res)); if (IS_ERR(gpio_base)) { pr_err("ioremap failed\n"); return PTR_ERR(gpio_base); } /* 3. 注册字符设备 */ ret = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); if (ret < 0) goto err_iounmap; cdev_init(&cdev, &hello_fops); cdev_add(&cdev, devno, 1); dev_class = class_create(THIS_MODULE, DEV_NAME); device_create(dev_class, NULL, devno, NULL, DEV_NAME); pr_info("gpio driver probe ok, phy_addr=0x%pa\n", &res->start); return 0; err_iounmap: iounmap(gpio_base); return ret; } static int hello_remove(struct platform_device *pdev) { device_destroy(dev_class, devno); class_destroy(dev_class); cdev_del(&cdev); unregister_chrdev_region(devno, 1); /* 释放地址映射 */ iounmap(gpio_base); pr_info("gpio driver removed\n"); return 0; } /* 和DTS的compatible严格匹配 */ static const struct of_device_id hello_of_match[] = { {.compatible = "mycompany,hello_gpio"}, {} }; MODULE_DEVICE_TABLE(of, hello_of_match); static struct platform_driver hello_driver = { .probe = hello_probe, .remove = hello_remove, .driver = { .name = "hello_gpio_drv", .of_match_table = hello_of_match, }, }; module_platform_driver(hello_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("STM32-Linux"); MODULE_DESCRIPTION("GPIO read driver, same as STM32 register code");

3. Makefile

obj-m += hello_gpio.o KERNELDIR ?= /path/to/your/linux PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

4. 用户层测试程序 app.c

#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(void) { int fd = open("/dev/hello_gpio", O_RDONLY); if (fd < 0) { perror("open"); return -1; } char buf[8]; read(fd, buf, sizeof(buf)); printf("GPIO PIN0 level: %s", buf); close(fd); return 0; }

交叉编译:

arm-linux-gnueabihf-gcc app.c -o app

5. 开发板测试命令

# 加载驱动,自动匹配DTS节点 insmod hello_gpio.ko # 运行应用 ./app # 卸载驱动 rmmod hello_gpio

输出效果:

GPIO PIN0 level: 1

重点:和STM32单片机代码无缝对照

STM32裸机代码

#define GPIOA_IDR (*(volatile unsigned int*)0x40020010) int read_pin0(void) { return (GPIOA_IDR >> 0) & 1; }

Linux驱动对应代码

#define GPIO_IDR 0x10 u32 val = readl(gpio_base + GPIO_IDR); return (val >> 0) & 0x01;

唯一区别只有两点:

  1. Linux不能直接用物理地址指针,必须先用ioremap做虚拟地址映射;
  2. 使用内核函数readl/writel代替volatile指针,防止编译优化。

硬件地址全部放在DTS,C驱动没有硬编码任何数字,真正做到软硬件分离。


http://www.jsqmd.com/news/1093803/

相关文章:

  • 视频台词停顿太多?一键自动去除空白间隙
  • K8s 多 Master 重启:流程梳理与问题排查
  • 做了一个月Skills,我才理解Agent可靠性的本质
  • 三、Prometheus安装和配置node-exporter服务
  • LED 隧道灯老旧改造工程 功率测算与施工核验技术规范
  • OpenMontage全链路AI视频生成:从环境部署到生产实践指南
  • 好用的检测机DD马达哪家靠谱
  • 《VMware 安装 Ubuntu Linux 全过程 + 排错总结》
  • GaussDB(DWS)数据仓库性能压测与调优实战:从0到1全记录
  • 【从0到1构建一个ClaudeAg _
  • 为什么建议中小企业优先考虑开源ERP
  • 终极SPT-AKI存档编辑器:5分钟掌握逃离塔科夫离线版完整修改指南
  • 电商多店运维实战:解决云机长期挂机掉线、账号风控问题
  • AI编码代理实战:从网站克隆到Next.js项目生成的工程化指南
  • 企微多个渠道活码如何平均分配客户?员工模板“全轮流”机制的实现与配置
  • AI 赋能接口自动化测试系列(一):接口文档智能解析Agent Skill推荐
  • PG 日报|UUID 解析 SIMD 加速,AI 行业动态速览
  • 使用Composer导出高清模型图像,并导出带BOM表的模型视图!
  • OpenMontage:本地化AI视频全链路制作工具部署与实战指南
  • 计算机毕业设计之基于深度学习的花卉分类检测系统的设计与实现
  • 【每天认识一个国家 | 土耳其】
  • WhatsApp验证码收不到别砸手机!六大死穴七招破解
  • BambuStudio:从模型到成品的智能3D打印管家
  • NIKON 4S589-551酸浓度计装置
  • 2026算力避坑实测!主流GPU租赁平台稳定性深度评测,告别宕机与算力虚标
  • 基于PANDAS的QAbstractTableModel实现高级TableView详细解析(九、在TableView实现多重表头)
  • 智能门锁室内2寸-5寸屏幕驱动芯片模组方案
  • Paxos算法:如何解决分布式系统中的共识问题
  • 民意调查真伪辨别!四招看懂靠谱民调标准
  • 慢病时代中医养护新思路:糖尿病的系统化调理与健康管理