armsom-W3单板计算机之小白学习笔记(六)

前言:

本篇内容我们学习W3开发板40pin上的GPIO ,我们使用子系统框架来辅助学习GPIO。

一.什么是GPIO?

GPIO是通用输入输出(General Purpose Input/Output)的缩写,它是一种通用的硬件接口标准,常见于嵌入式系统和单片机中。GPIO允许处理器或控制器与外部设备(如传感器、执行器或其他电子组件)进行通信和交互。

具体来说,GPIO引脚可以通过软件配置为输入模式或输出模式。在输入模式下,GPIO引脚可以接收来自外部设备的电信号或数字信号,以便处理器读取该信号进行相应的操作。在输出模式下,处理器可以控制GPIO引脚的电平,以向外部设备发送信号或操控其他电子组件。

GPIO接口通常使用数字逻辑电平(通常是0V和3.3V或5V),用于表示逻辑上的“低”(0)或“高”,通过适当的编程和配置,可以利用GPIO接口实现各种控制和通信功能,使得嵌入式系统具有更大的灵活性和可扩展性。

 二.GPIO的操作方法

1.使用命令通过sysfs文件系统控制GPIO

1.1GPIO编程方式有很多种:

  • 可以写驱动程序调用GPIO函数操作GPIO
  • 也可以直接通过直接操作寄存器的方式操作GPIO(不通用,不同开发板的寄存器是不一样的)
  • 还可以通过sysfs的方式实现对GPIO的控制

使用sysfs方式控制GPIO,首先需要底层驱动的支持,连接好开发板之后,在内核目录下输入export ARCH=arm64去设置平台为arm64,然后在make menuconfig图形化配置界面中加入配置Device Drivers>GPIO Support>sys/class /gpio/...(sysfs interface),就是把这个选项勾选上,然后重新编译好,烧录进开发板。

1.2sysfs控制接口

sys/class/gpio/export 将gpio控制从内核空间导出到用户空间,只写sys/class/gpio/unexport 取消GPIO控制从内核空间到用户空间的导出,只写Gpiochipx(x=0,1,2...)代表控制器。

1.3gpio控制接口

在sys/class/gpio/gpioN(N=0,1,2...)下有多种属性,可以通过这些属性(一些属性可以把内核空间和用户空间进行数据交换)进行控制,从而实现对GPIO的控制:

  • direction属性:值为in或者out,可以通过对该属性写入in或者out设置该gpio为输入或者输出。
  • value属性:用汉语读取输入电平或者看控制输出电平,写入或者读取到1表示高电平,写入或者读取到0表示低电平。
  • edge属性:设置触发电平,再将GPIO设置为中断时候才会出现和使用这个属性。

1.4GPIO编号确定

计算公式:

GPIOpin脚计算公式:pin=bank*32 + number   //bank为组号,number为小组编号。

GPIO小组编号计算公式:number = group *8 + X  //group(A=0,B=1,C=2,D=3)、

比如:gpio5 RK_PA3:5 * 32 +(0*8 + 3) = 163

1.5详细步骤

在内核目录下使用以下命令设置平台是arm64

export ARCH=arm64

 然后再使用make menuconfig命令打开图形化配置界面 ,按照下面的步骤依次打开

make menuconfig

 

 配置好之后我们使用串口在mobaxterm连接好开发板。

找到这个目录sys/class/gpio

 接下来我们使用以下命令来控制打开LED灯,一步一步操作就可以完成实验。

1. echo 96 > /sys/class/gpio/export                  //gpio_request          申请导出相应的gpio
2. echo out > /sys/class/gpio/gpio96/direction       //gpio_direction_output 设置相应gpio为输出方向
3. echo 1 > /sys/class/gpio/gpio96/value             // gpio_set_value       设置输出高电平
4. cat /sys/class/gpio/gpio96/value                  // gpio_get_value       获取gpio当前状态值
5. echo 96 > /sys/class/gpio/unexport                // gpio_free            释放申请的gpio

2.使用C语言通过sysfs文件系统控制GPIO

#include <stdio.h>        // 引入 printf 和 sprintf 所需的头文件  
#include <string.h>      // 引入 strlen 和 sprintf 所需的头文件  
#include <fcntl.h>       // 引入 open、O_WRONLY 等所需的头文件  
#include <unistd.h>      // 引入 close 所需的头文件  
#include <sys/stat.h>
#include <sys/stat.h>    // 引入文件操作所需的头文件  

int fd;                   // 文件描述符  
int len;                  // 字符串长度  
int ret;                  // 写操作的返回值  
char gpio_path[100];     // 用于保存 GPIO 文件路径的缓冲区  

// 导出 GPIO 引脚的函数  
int gpio_export(char *argv) {  
    fd = open("/sys/class/gpio/export", O_WRONLY);   
    if (fd < 0) {    
        printf("打开 /sys/class/gpio/export 失败\n");  
        return -1;  // 如果打开失败,则返回错误代码  
    }  
    
    len = strlen(argv);  // 获取 GPIO 引脚名称的长度  
    ret = write(fd, argv, len);  // 将引脚名称写入 export 文件  
    if (ret < 0) {  
        printf("写入 /sys/class/gpio/export 失败\n");  
        close(fd);  // 关闭文件描述符  
        return -2;  // 返回错误代码  
    }  
    close(fd);  // 关闭文件描述符  
    return 0;   // 成功返回 0  
}  
 
// 取消导出 GPIO 引脚的函数  
int gpio_unexport(char *argv) {  
    fd = open("/sys/class/gpio/unexport", O_WRONLY);   
    if (fd < 0) {  
        printf("打开 /sys/class/gpio/unexport 失败\n");  
        return -1;  // 如果打开失败,则返回错误代码  
    }  
    
    len = strlen(argv);  // 获取 GPIO 引脚名称的长度  
    ret = write(fd, argv, len);  // 将引脚名称写入 unexport 文件  
    if (ret < 0) {  
        printf("写入 /sys/class/gpio/unexport 失败\n");  
        close(fd);  // 关闭文件描述符  
        return -2;  // 返回错误代码  
    }  
    close(fd);  // 关闭文件描述符  
    return 0;   // 成功返回 0  
}  

// 控制 GPIO 引脚的值和方向的函数  
int gpio_ctrl(char *arg, char *val) {  
    char file_path[100];  
    sprintf(file_path, "%s/%s", gpio_path, arg);  // 生成完整的 GPIO 文件路径  
    
    fd = open(file_path, O_WRONLY);   
    if (fd < 0) {  
        printf("打开文件 %s 失败\n", file_path);  
        return -1;  // 如果打开失败,则返回错误代码  
    }  
    
    len = strlen(val);  // 获取值的长度  
    ret = write(fd, val, len);  // 将值写入对应文件  
    if (ret < 0) {  
        printf("写入文件 %s 失败\n", file_path);  
        close(fd);  // 关闭文件描述符  
        return -2;  // 返回错误代码  
    }  
    close(fd);  // 关闭文件描述符  
    return 0;   // 成功返回 0  
}  

int gpio_read_value(char *arg ) { 

    sprintf(file_path, "%s/%s", gpio_path, arg);  // 生成完整的 GPIO 文件路径  
    fd = open(file_path, O_WRONLY);   
    if (fd < 0) {  
        printf("打开文件 %s 失败\n", file_path);  
        return -1;  // 如果打开失败,则返回错误代码  
    }  
    
    ret = write(fd, buf,1);  // 将值写入对应文件 
    if (!strcmp(buf , "1")){
        prinft ("the value is high\n");
        return 1;
    }else  if (!strcmp(buf , "0")){
        printf("the value is low\n");
        return 0;
    }
    return -1;
    close(fd);

}  

// 主函数  
int main(int argc, char *argv[]) {  
    if (argc < 3) {  
        printf("用法: ./program <GPIO编号> <值>\n");  
        return -1;  // 参数不足时返回错误  
    }  

    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);  // 生成 GPIO 路径  
    
    // 检查 GPIO 是否已经导出,若未导出则调用 gpio_export  
    if (access(gpio_path, F_OK)) {  
        gpio_export(argv[1]);  
    } else {  
        gpio_unexport(argv[1]);  
    }  
    
    // 设置 GPIO 为输出方向  
    gpio_ctrl("direction", "out");  
    
    // 设置 GPIO 的值  
    gpio_ctrl("value", argv[2]);  
    
    // 取消导出 GPIO  
    gpio_unexport(argv[1]);  
    
    return 0;  // 正常结束  
}

3.通过IO命令操作操作寄存器GPIO                                                                                                   

IO命令可以在内核阶段读写寄存器

export:export文件用于通知系统需要导出控制的GPIO引脚

unexport:unexport文件用于通知取消导出

gpiochipX:gpiochipX目录保存系统中GPIO寄存器的信息,包括每个寄存器引脚的起始编号base,寄存器名称,引脚总数导出一个引脚的操作步骤。

3.1GPIO引脚的基地址

去数据手册中找到gpio寄存器的地址(gpio寄存器地址=基地址+偏移地址)

计算出地址后,连接开发板输入IO命令,和第一个是类似的。

4.通过内部映射(通过mem设备)的方式操作GPIO

4.1打开dev/mem设备

直接访问内核空间,是比较危险的操作,所以只有root用户才可以访问dev/men设备,另外有些系统也需要单独开启dev/mem。

开启方法:使用make menuconfig命令打开图形化配置

  1. Device Drivers ---->
  2. Character devices ----->
  3. [*] dev/mem virtual device support

编译好之后,烧录到开发板上,查看dev目录下是否有mem文件。

4.2dev/mem设备的使用方法

使用open函数将访问的物理地址与“dev/mem”文件描述符建,访问权限可以为只读(O_RDONLY),只写(O_WRONLY)的阻塞或者非阻塞方式

int fd = 0;

fd = open("dev/mem",O_RDWR | O_NDELAY)    //读写权限,非阻塞

通过mmap函数将需要访问的物理地址与"/dev/mem"文件 描述符建立映射

char *mmap addr = NULL;
mmap addr = (char *) mmap (NULL,MMAP_SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd ,MMAP_ADDR);

对地址进行访问,也就是对寄存器进行访问 

int a = 0; 
*(int*)mmap addr = 0xff;   //写地址
a = *(int) mmap addr;   //读地址

5.GPIO的调试方法

方法一:可以通过debugfs查看gpio的使用情况,如果没有debugfs,可以使用以下命令进行挂载:mount-t debugfs none sys/kernel/debug ,如果sys/kernel/debug没有文件,则需要打开CONFIG_DEBUGS_FS ,打开make menuconfig图形化配置界面

kernel hacking ----->

Compile-time checks and compiler options ----->

-*- Debug Filesystem

  

打开之后使用命令:cat /sys/kernel/debug/gpio,就可以查看gpio的使用情况

方法二:

  1. 使用sys/kernel/debug/pinctrl命令,打开目录
  2. 然后使用cd pinctrl-rockchip-pinctrl命令,打开目录
  3. 使用 cat pins查看gpio的引脚编号
  4. 使用 cat pinmux-pins 查看引脚的复用
  5. 使用 cat pingroups  查看引脚组
  6. 使用 cat pinconf-pins 查看电器属性

三.GPIO的子系统API函数

1.引入API函数

新的gpio子系统接口定义在inlcude/linux/gpio/consumer.h中,在新的接口涉及到了一个struct gpio_desc结构体,这个结构体定义在drivers/gpio/gpiolib.h头文件中。新的gpio子系统接口是和设备树结合使用的,所以没有设备树就不能使用新的接口。

2.获取API函数(旧API函数)

1.

gpio_request()函数

请求使用一个GPIO引脚,确保这个引脚没有被其他模块占用

使用:int gpio_request(unsigned gpio,const char *label);

gpio:想要使用的gpio引脚号      label:描述这个gpio的标签

2.

gpio_free函数()

释放之前请求的GPIO引脚,使其可供其他模块使用

使用:void gpio_free(unsigned gpio);

3.

gpio_direction_input()

设置GPIO引脚为输入模式

使用:int gpio_direction_input(unsigned gpio);

4.gpio_direction_output()

设置GPio引脚为输出模式,并设定初始输出值(高或低)

使用:int gpio_get_value(unsigned gpio)

5.

gpio_get_value()

读取GPIO引脚当前值(高或低)

使用:int gpio_get_value(unsigned gpio);

6.

gpio_set_value()

设置GPIO引脚的输出值(高或低)

使用:void gpio_set_value(unsined gpio,int value);

7.

gpio_to_irq()

将GPIO引脚映射到IRQ(中断请求),用于配置GPIO中断

使用:int gpio_to_irq(unsinged gpio)

8.

irq_to_gpio()

将IRO映射回GPIO引脚

使用:int gpio_to_irq(unsinged irq)

以下是一个LED连接到GPIO引脚控制的示例

int led_gpio = 25; // 假设LED连接到GPIO 25

// 请求GPIO
if (gpio_request(led_gpio, "LED") < 0) {
    printk("GPIO request failed\n");
    return -1;
}

// 设置GPIO为输出模式,并将LED设为关闭状态
gpio_direction_output(led_gpio, 0);

// 打开LED
gpio_set_value(led_gpio, 1);

// 关闭LED
gpio_set_value(led_gpio, 0);

// 释放GPIO
gpio_free(led_gpio);

3.API操作GPIO函数(新API函数--新函数一般是gpiod)

1.int gpiod_get_direction(struct gpio_desc *desc);

函数作用:获取GPIO的方向

2.int gpiod_firection_input(struct gpio_desc *desc);

函数作用:配置GPIO的方向为输入

3.int gpiod_dorecyion_output(struct gpio_desc*desc,int value);

函数作用:配置GPIO的方向为输出

4.void gpiod_get_value(const struct gpio_desc *desc);

函数作用:读取GPIO的电平状态

5.void gpiod_set_value(const gpio_desc*desc,int value);

函数作用:设置GPIO的电平状态

6.int gpiod_set_irq(const struct gpio_desc*desc);

函数作用:将GPIO描述转变为中断编号

四.实践操作

1.GPIO子系统和pinctrl子系统结合

再驱动中使用pinctrl的步骤:

再驱动中加载或者与运行时,获取设设备树对应的pinctrl结构体的指针

通过pinctrl结构体指针找到需要的 pin  conctrl state结构体指针

找到pin conctrl state结构体指针后,将里面的信息设置到硬件中

在驱动写在的时候,释放pinctrl结构体的指针

1.1获取设备树对应的pinctrl结构体指针函数

函数原型:struct pinctrl * pinctrl_get(struct device * dev);

函数作用:获取设备对用的pinctrl指针函数

函数参数:dev:设备指针

返回值:成功返回对应的pinctrl的指针

1.2释放pinctrl指针函数

函数原型:void pinctrl put (struct pinctrl*p);

函数作用:释放在申请struct pinctrl时候申请的资源

函数参数:要释放的struct pinctrl*p 指针

1.3查找状态函数

函数原型:void pinctrl_state* pinctrl_lookup_state(struct pinctrl *p,const char * name);

函数作用: 根据状态在pinctr中查到对应的状态

函数参数:p:struct pinctrl*p指针  name:状态的名字,即在pinctrl-names属性中的名字

返回值:对应状态的struct pinctrl stste*指针

2.实现动态引脚复用的功能

动态实现引脚复用指的是:可以变换的使用引脚,比如我一会使用的是i2c功能,一会我使用的是gpio功能,一会我又使用串口功能。

分为两个部分:

1.在设备树中,将我们动态切换的引脚对应的复用是,在设备树节点中使用gpio子系统描述出来。

2.在驱动中解析这个信息,从而实现动态切换。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻谈半窗月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值