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

mem 设备控制 GPIO - C程序通过sysfs文件系统使用GPIO中断

提示:通过 mem 设备控制 GPIO 知识点;使用C程序通过sysfs文件系统使用GPIO中断

文章目录

  • 前言
  • 参考资料
  • 一、通过 mem 设备控制 GPIO
    • /dev/mem 设备 知识点
    • 控制源码程序
      • map_base 函数分析
        • 参数详解
          • addr - 映射起始地址
          • length - 映射区域长度
          • prot - 内存保护标志
          • flags - 映射类型和选项
          • fd - 文件描述符
          • offset - 文件偏移量
      • mem 控制业务分析和步骤
      • 控制GPIO方案 mmap-sysfs-字符设备 比较
  • 二、使用C程序通过sysfs文件系统使用GPIO中断
    • 控制源码程序
    • 基础知识
      • memset 结构体
      • pollfd 结构体
        • 结构体定义
        • 事件标志说明
        • 基本使用
    • 监听中断流程
      • gpio_interrupt 监听中断分析
        • 为什么需要调用read()
      • 配置硬件测试环境
  • 总结


前言

通过io 操作实现点亮LED灯操作。 但是IO操作的都是寄存器地址,所以先搞清楚寄存器知识点。

参考资料

GPIO 控制和操作-使用命令通过sysfs文件系统控制GPIO
RK3568驱动指南|第十二篇 GPIO子系统-第129章 GPIO控制和操作实验
使用C程序通过sysfs文件系统控制gpio
rk3588写入寄存器
linux IO指令 读写GPIO口电平实例
操作寄存器来控制GPIO-点亮LED灯

  • 这里目标是LED灯点亮实验,在寄存器控制GPIO基础上进一步底层抽象,用mem设备来控制GPIO
  • 在C程序控制sysfs文件系统控制GPIO基础上,进一步了解、掌握 C程序通过sysfs文件系统,使用GPIO中断

这本身是两个知识点,但都很重要,有必要进一步理解学习。

一、通过 mem 设备控制 GPIO

/dev/mem 设备 知识点

/dev/mem 是一个特殊的字符设备文件,它提供了对整个物理内存的直接访问。通过这个设备,用户空间程序可以读写任意的物理内存地址。

  • 在某些情况下, 我们可能无法直接使用 IO 命令来访问 GPIO 寄存器, 或者希望使用更高级的抽象来控制硬件。 这时, 可以使用/dev/mem 设备来操作物理内存, 以实现对 GPIO
    寄存器的访问。

  • 通过打开/dev/mem 设备文件, 并将其映射到用户空间的内存中, 我们可以直接读写物理内存地址, 从而实现对 GPIO 寄存器的控制。 这种方法相对于 IO 命令更加灵活, 可以使用更高级的编程语言(如 C/C++) 来编写控制逻辑。

控制源码程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>#define GPIO_REG_BASE 0xFDD60000                                     // GPIO base address
#define GPIO_SWPORT_DDR_L_OFFSET 0x0008                   // DDR   direction mode   offset
#define GPIO_SWPORT_DR_L_OFFSET 0x0000                     // DR   direction mode   offset
#define SIZE_MAP 0x1000// 打开LED灯
void LED_ON(unsigned char *base)
{// 设置LED灯的方向为输出*(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;// 将LED灯打开*(volatile unsigned int *)(base + GPIO_SWPORT_DR_L_OFFSET) = 0x80008044;
}// 关闭LED灯
void LED_OFF(unsigned char *base)
{// 设置LED灯的方向为输出*(volatile unsigned int *)(base + GPIO_SWPORT_DDR_L_OFFSET) = 0x80008044;// 将LED灯关闭*(volatile unsigned int *)(base + GPIO_SWPORT_DR_L_OFFSET) = 0x80000044;
}int main(int argc, char *argv[])
{int fd;unsigned char *map_base;// 打开/dev/mem设备fd = open("/dev/mem", O_RDWR);if (fd < 0){printf("open /dev/mem error \n");return -1;}// 将物理地址映射到用户空间map_base = (unsigned char *)mmap(NULL, SIZE_MAP, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_REG_BASE);if (map_base == MAP_FAILED){printf("map_base error \n");return -2;}while (1){// 打开LED灯LED_ON(map_base);// 等待1秒usleep(1000000);// 关闭LED灯LED_OFF(map_base);// 等待1秒usleep(1000000);}// 解除映射munmap(map_base, SIZE_MAP);// 关闭文件描述符close(fd);return 0; // 返回0表示程序正常退出
}

将 控制程序编译成可执行文件,然后拷贝到开发板上,执行即可实现LED的亮灭闪烁:

  ./gpioctrl_by_mem  15

map_base 函数分析

mmap(memory map)函数用于将设备内存或文件映射到进程的地址空间,从而允许应用程序直接通过内存访问来操作硬件寄存器。

参数详解
addr - 映射起始地址
  • 建议设为 NULL,由内核自动选择合适地址

  • 如果指定地址,通常需要页对齐(sysconf(_SC_PAGE_SIZE))

  • 非 NULL 时,内核会将其作为提示,但不保证使用该地址

length - 映射区域长度
  • 映射的字节数

  • 实际会向上取整到系统的页大小整数倍

  • 可以通过 sysconf(_SC_PAGE_SIZE) 获取页大小

prot - 内存保护标志

控制对映射内存的访问权限,可以组合使用:

标志说明
PROT_READ页面可读0x1
PROT_WRITE页面可写0x2
PROT_EXEC页面可执行0x4
PROT_NONE页面不可访问0x0

常用组合:

PROT_READ | PROT_WRITE    // 可读写
PROT_READ                 // 只读
PROT_READ | PROT_EXEC     // 可读可执行
flags - 映射类型和选项

控制映射的行为和特性:

映射类型(必选其一):

  • MAP_SHARED:共享映射,修改对其他进程可见,会写回文件

  • MAP_PRIVATE:私有映射,修改不会写回文件,采用写时复制

其他选项(可选):

  • MAP_FIXED:必须使用指定的 addr,如果不可用则失败

  • MAP_ANONYMOUS:创建匿名映射,不关联文件(此时 fd 应为 -1)

  • MAP_LOCKED:锁定页面在内存中,防止被换出

  • MAP_NORESERVE:不为交换空间预留空间

fd - 文件描述符
  • 要映射的文件描述符
  • 对于匿名映射,设为 -1
offset - 文件偏移量
  • 从文件开头算起的偏移量
  • 必须是系统页大小的整数倍

mem 控制业务分析和步骤

如上对于mem源码控制代码中,核心流程其实就是通过寄存器直接控制的程序代码化

  • 假使已经实现类 IO口的GPIO复用,那么第一步 mmap 函数实现 将物理地址映射到用户空间
  • 提前通过操作手册,找到 GPIO方向GPIO_SWPORT_DDR_L_OFFSET 和 GPIO 高低电平的 GPIO_SWPORT_DR_L_OFFSET 偏移量
  • 最后通过 映射mmap函数拿到基地址到用户空间,然后用户空间拼接方向模式、高低电平具体的值,赋值实际控制的值即可 立刻同步到内核空间,进而通过mem 机制实现了控制GPIO功能

控制GPIO方案 mmap-sysfs-字符设备 比较

替代方案比较

方法性能复杂度灵活性适用场景
mmap高性能、实时控制
sysfs简单应用、调试
字符设备生产环境

通过 mmap 直接映射 GPIO 寄存器内存,可以实现最高性能的 GPIO 控制,特别适合需要快速响应和精确时序控制的嵌入式应用。

二、使用C程序通过sysfs文件系统使用GPIO中断

通过 GPIO 的输入中断程序, 将中断触发方式设置为边沿触发, 每当触发中断会打印 value的值。

在使用C程序通过sysfs文件系统控制gpio 篇中其实已经了解过 通过C程序控制 GPIO。

这里其实就是通过物理硬件环境模拟中断,然后程序能够检测到。
如何检测:就是通过poll机制,检测。

控制源码程序


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>int fd;              // 文件描述符
int ret;             // 返回值
char gpio_path[100]; // GPIO路径
int len;             // 字符串长度
char file_path[100]; // 文件路径
char buf[2];         // 缓冲区struct pollfd fds[1]; // poll结构体数组// 导出GPIO引脚
int gpio_export(char *argv)
{fd = open("/sys/class/gpio/export", O_WRONLY); // 打开export文件if (fd < 0){printf("open /sys/class/gpio/export error \n"); // 打开文件失败return -1;}len = strlen(argv);         // 获取字符串长度ret = write(fd, argv, len); // 写入引脚号到export文件if (ret < 0){printf("write /sys/class/gpio/export error \n"); // 写入失败return -2;}close(fd); // 关闭文件
}// 取消导出GPIO引脚
int gpio_unexport(char *argv)
{fd = open("/sys/class/gpio/unexport", O_WRONLY); // 打开unexport文件if (fd < 0){printf("open /sys/class/gpio/unexport error \n"); // 打开文件失败return -1;}len = strlen(argv);        // 获取字符串长度ret = write(fd, argv, len); // 写入引脚号到unexport文件if (ret < 0){printf("write /sys/class/gpio/unexport error \n"); // 写入失败return -2;}close(fd); // 关闭文件
}// 控制GPIO引脚的属性
int gpio_ctrl(char *arg, char *val)
{sprintf(file_path, "%s/%s", gpio_path, arg); // 构建属性文件的路径fd = open(file_path, O_WRONLY);              // 打开属性文件if (fd < 0){printf("open file_path error \n"); // 打开文件失败return -1;}len = strlen(val);         // 获取字符串长度ret = write(fd, val, len); // 写入属性值到属性文件if (ret < 0){printf("write file_path error\n"); // 写入失败return -2;}close(fd); // 关闭文件
}// 监听GPIO引脚的中断事件
int gpio_interrupt(char *arg)
{sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径fd = open(file_path, O_RDONLY);              // 打开文件if (fd < 0){printf("open file_path error \n"); // 打开文件失败return -1;}memset((void *)fds, 0, sizeof(fds)); // 清空poll结构体数组fds[0].fd = fd;                      // 设置poll结构体的文件描述符fds[0].events = POLLPRI;             // 设置poll结构体的事件类型为POLLPRI,表示有紧急数据可读read(fd, buf, 2); // 读取文件内容,清除中断事件ret = poll(fds, 1, -1); // 调用poll函数等待中断事件发生,阻塞直到事件发生if (ret <= 0){printf("poll error \n"); // 调用poll失败或超时return -1;}if(fds[0].revents & POLLPRI){lseek(fd, 0, SEEK_SET); // 重新定位文件指针到文件开头read(fd, buf, 2);       // 读取文件内容,获取中断事件的值buf[1] = '\0';printf("value is %s\n", buf); // 输出中断事件的值}
}// 读取GPIO引脚的值
int gpio_read_value(char *arg)
{sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径fd = open(file_path, O_WRONLY); // 打开文件,以只写模式打开是一个错误,应该使用只读模式if (fd < 0){printf("open file_path error\n"); // 打开文件失败return -1;}ret = read(fd, buf, 1); // 读取文件内容,获取引脚的值if (!strcmp(buf, "1")){printf("The value is high\n"); // 引脚值为高电平return 1;}else if (!strcmp(buf, "0")){printf("The value is low\n"); // 引脚值为低电平return 0;}return -1; // 这里应该返回读取到的引脚值(0或1),而不是返回固定的-1close(fd); // 关闭文件(这行代码无法执行到,应该放在read之前)
}int main(int argc, char *argv[]) // 主函数
{int value;sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建GPIO路径if (access(gpio_path, F_OK))                            // 检查GPIO路径是否存在{gpio_export(argv[1]); // 不存在则导出GPIO引脚}else{gpio_unexport(argv[1]); // 存在则取消导出GPIO引脚}gpio_ctrl("direction", "in"); // 设置GPIO引脚为输入模式gpio_ctrl("edge", "both");    // 设置GPIO引脚的中断触发方式为上升沿和下降沿gpio_interrupt("value");      // 监听GPIO引脚的中断事件gpio_unexport(argv[1]); // 最后取消导出GPIO引脚return 0; // 返回0表示程序正常退出
}

基础知识

memset 结构体

在 C/C++ 中,使用 memset 初始化结构体是一种常见的做法。以下是详细的使用方法和注意事项:

 struct Person p1;// 将结构体所有字节设置为0memset(&p1, 0, sizeof(struct Person));

pollfd 结构体

结构体定义
#include <poll.h>struct pollfd {int   fd;         /* 文件描述符 */short events;     /* 等待的事件 */short revents;    /* 实际发生的事件 */
};
事件标志说明
事件标志说明方向
POLLIN有数据可读输入
POLLPRI有紧急数据可读输入
POLLOUT可写,不会阻塞输出
POLLRDHUP对端关闭连接输入
POLLERR发生错误输出
POLLHUP挂起输出
POLLNVAL无效请求输出
基本使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>#define TIMEOUT_MS 5000  // 5秒超时int main() {struct pollfd fds[2];// 初始化 pollfd 结构体memset(fds, 0, sizeof(fds));// 监视标准输入 (fd = 0)fds[0].fd = STDIN_FILENO;fds[0].events = POLLIN;// 假设有另一个文件描述符fds[1].fd = -1;  // 无效的文件描述符fds[1].events = POLLIN;printf("等待输入(5秒超时)...\n");int ret = poll(fds, 2, TIMEOUT_MS);if (ret == -1) {perror("poll");return 1;} else if (ret == 0) {printf("超时!\n");} else {// 检查哪些文件描述符就绪if (fds[0].revents & POLLIN) {printf("标准输入有数据可读\n");}if (fds[1].revents & POLLIN) {printf("文件描述符 %d 有数据可读\n", fds[1].fd);}// 检查错误条件if (fds[0].revents & POLLERR) {printf("标准输入发生错误\n");}if (fds[1].revents & POLLNVAL) {printf("文件描述符 %d 无效\n", fds[1].fd);}}return 0;
}

监听中断流程

有了前面的知识点,其实代码业务流程很清晰的,如下:

  • 导出GPIO gpio_export
  • gpio_ctrl(“direction”, “in”); // 设置GPIO引脚为输入模式
  • gpio_ctrl(“edge”, “both”); // 设置GPIO引脚的中断触发方式为上升沿和下降沿
  • gpio_interrupt(“value”); // 监听GPIO引脚的中断事件

在上面分析 pollfd 后,我们再仔细看看我们程序里面的监听中断到底如何试下的,如下:

gpio_interrupt 监听中断分析

int gpio_interrupt(char *arg)
{sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径fd = open(file_path, O_RDONLY);              // 打开文件if (fd < 0){printf("open file_path error \n"); // 打开文件失败return -1;}memset((void *)fds, 0, sizeof(fds)); // 清空poll结构体数组fds[0].fd = fd;                      // 设置poll结构体的文件描述符fds[0].events = POLLPRI;             // 设置poll结构体的事件类型为POLLPRI,表示有紧急数据可读read(fd, buf, 2); // 读取文件内容,清除中断事件ret = poll(fds, 1, -1); // 调用poll函数等待中断事件发生,阻塞直到事件发生if (ret <= 0){printf("poll error \n"); // 调用poll失败或超时return -1;}if(fds[0].revents & POLLPRI){lseek(fd, 0, SEEK_SET); // 重新定位文件指针到文件开头read(fd, buf, 2);       // 读取文件内容,获取中断事件的值buf[1] = '\0';printf("value is %s\n", buf); // 输出中断事件的值}
}
  • 打开节点路径 open,获取到文件描述符 fd
  • 通过memset 方法,每次运行时候清空pollfd 结构体
  • read 消费一次中断
  • poll(fds, 1, -1); // 调用poll函数等待中断事件发生,阻塞直到事件发生
  • 阻塞,等待返回结果,直到有中断产生。
为什么需要调用read()
// 如果不调用read(),会发生什么?
// poll()会立即返回,因为中断事件没有被消费
// 导致程序进入忙等待状态

配置硬件测试环境

当可执行文件执行后 ./gpioctrl 42& ,模拟实际测试环境:

由于中断并没有被触发, 所以程序会阻塞, 等待中断的进行, 然后使用杜邦线的另一端将GPIO 底座的 3.3V 接到 GPIO1_PB2 pin 脚, 进行中断的测试

总结

  • 了解 mem 控制GPIO的 这种机制
  • C程序如何监听GPIO中断
http://www.dtcms.com/a/471718.html

相关文章:

  • 简约风格装修seo排名如何
  • 有关使用AVX,EIGEN等加速方法过程中cmake选项的说明
  • 二手书交易网站开发背景WordPress发邮件4.4.1
  • 【项目开发Trip第2站】casbin库与身份权限划分
  • POET 宣布投资7500万美元
  • wordpress底部插件郑州seo顾问热狗网
  • 韩国网站免费模板美丽定制 网站模板
  • 栾城网站制作产品推广策划案
  • 如何做网站联盟营销网站设计中的js
  • Wazuh vs. 安全洋葱:开源SOC核心平台用哪个呢?
  • 容桂网站制作价位晋江论坛手机版
  • 有做网站看病的吗用vs2010做网站应用程序脱机
  • 如何评价一个网站的网站建设林和西网站建设
  • 云服务器的应用场景
  • 网站开发需要什么工程师软装工作室
  • 怎么建立一个网站好文创产品设计方案模板
  • 提高网站排名怎么做小程序定制开发公司前十名
  • ins做甜品网站手车做网课网站
  • 【AI图片生成】图片生成,这里特别注意根据实际需要换成对应适合自己需求的图片大小和尺寸
  • 框架--Lombok
  • 要找人做公司网站应该怎么做开发工具控件属性怎么打开
  • Ubuntu 22.04 安装 AppImage
  • 电商网站开发流程图百度有刷排名软件
  • 昆明百度搜索排名优化如何做网站的优化
  • 业主验证超时问题解决方案
  • 新乡网站开发的公司电话亚马逊图书官网
  • 贵州省建设局八大员报名网站管理咨询行业
  • [GazeTracking] 摄像头交互与显示 | OpenCV
  • 车载互联系统网站建设佛山网站建设哪里有
  • 红酒营销 网站建设南京短视频制作公司