这种其实应该用一个直观通俗的比喻的先讲,再把里面的关系对应到实际的代码,多看图,有详有简,慢慢改吧

1. STM32 MCU GPIO操控机制

1.1 底层寄存器定义架构

头文件结构 (stm32f4xx.h为例)

/* APB1总线基地址定义 */
#define APB1PERIPH_BASE       0x40000000UL
#define APB2PERIPH_BASE       0x40010000UL

/* GPIO外设偏移地址定义 */
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800UL)  
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00UL)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000UL)

/* GPIO寄存器结构体定义 */
typedef struct {
    __IO uint32_t MODER;    /* 模式寄存器           偏移: 0x00 */
    __IO uint32_t OTYPER;   /* 输出类型寄存器       偏移: 0x04 */
    __IO uint32_t OSPEEDR;  /* 输出速度寄存器       偏移: 0x08 */
    __IO uint32_t PUPDR;    /* 上拉下拉寄存器       偏移: 0x0C */
    __IO uint32_t IDR;      /* 输入数据寄存器       偏移: 0x10 */
    __IO uint32_t ODR;      /* 输出数据寄存器       偏移: 0x14 */
    __IO uint32_t BSRR;     /* 位设置/复位寄存器    偏移: 0x18 */
    __IO uint32_t LCKR;     /* 配置锁定寄存器       偏移: 0x1C */
    __IO uint32_t AFR[2];   /* 复用功能寄存器       偏移: 0x20-0x24 */
} GPIO_TypeDef;

/* GPIO外设指针定义 */
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)

1.2 HAL库封装机制

GPIO初始化结构体

typedef struct {
    uint32_t Pin;       /* 指定要配置的GPIO引脚 */
    uint32_t Mode;      /* 指定选中引脚的工作模式 */
    uint32_t Pull;      /* 指定选中引脚的上拉或下拉激活 */
    uint32_t Speed;     /* 指定选中引脚的速度 */
    uint32_t Alternate; /* 外设复用选择 */
} GPIO_InitTypeDef;

/* 标准库初始化函数 */
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

实际应用示例/* 包含相关头文件 */#include "stm32f4xx_hal.h"

void LED_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* 使能GPIO时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* 配置GPIO结构体 */
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    
    /* 初始化GPIO */
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    /* 底层实现等价于: GPIOA->MODER |= (0x01 << (5*2)); */
}
不管是MCU,MPU,只要跑裸机操控GPIO都这样。
采用宏定义的方式在头文件定义相关GPIO,源文件使用宏定义,这样可以将GPIO与实际的GPIO分隔开,使用不同的GPIO或者外设,替换即可。野火的stm32f10x教程就是这样写的,方便不少。那么linux设备和驱动模型就是这样一个映射关系。有一个设备树文件描述这些GPIO等等信息,编译生成dtb文件,在uboot分区下,在系统启动会被转化
为device。
在linux操作系统且mpu有mmu这个硬件(进程地址进行检查),不能直接操作GPIO寄存器,相应的xx外设.core提供了操作函数,若直接操作gpio,需要使用ioremap。需要记住的就是xxxcore.h
提供的接口函数。同时设备树文件会在uboot被内核转为struct platform_device结构体,被注册,包含硬件地址,描述信息等等。
写的驱动结构体在module_init被注册。platform_driver 和 platform_device 被匹配(此时应有一图)

image

 




Linux

probe函数主要是,字符类设备驱动:
  • 确定主设备号,也可以让内核分配

  • 定义自己的file_operations结构体,并定义相应的open,read,write,close,ioctrl函数

    • 需要注意用copy_to_user和copy_from_user做buf检测,操作io寄存器需要ioremap或者传给app操作framebuff需要mmap
  • register_chrdev
  • class_create, device_create,注册设备节点
  • 对于其他如lcd,需要app来操作framebuff,需要mmap,对于需要操作GPIO寄存器,需要ioremap

 

2. Linux设备树GPIO描述机制

2.1 设备树GPIO节点完整示例

 
dts
// arch/arm/boot/dts/imx6ull-myboard.dts
/ {
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
    
    /* GPIO控制器定义 */
    gpio1: gpio@0209c000 {
        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
        reg = <0x0209c000 0x4000>;
        interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&clks IMX6UL_CLK_GPIO1>;
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
        gpio-ranges = <&iomuxc 0 23 10>, <&iomuxc 10 17 6>, 
                      <&iomuxc 16 33 16>;
    };
    
    /* LED设备节点 */
    leds {
        compatible = "gpio-leds";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_led>;
        
        led0 {
            label = "sys-led";
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;  /* GPIO1_IO03, 低电平有效 */
            default-state = "on";
            linux,default-trigger = "heartbeat";
        };
        
        led1 {
            label = "usr-led"; 
            gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>; /* GPIO4_IO19, 高电平有效 */
            default-state = "off";
            retain-state-suspended;
        };
    };
    
    /* 按键设备节点 */
    gpio-keys {
        compatible = "gpio-keys";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpio_keys>;
        
        user-key {
            label = "User Key";
            gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_ENTER>;
            gpio-key,wakeup;
            debounce-interval = <50>;
        };
    };
    
    /* 自定义GPIO设备 */
    my_gpio_device {
        compatible = "mycompany,gpio-device";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_my_gpio>;
        
        reset-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
        power-gpios = <&gpio2 2 GPIO_ACTIVE_HIGH>;
        status-gpios = <&gpio3 15 GPIO_ACTIVE_HIGH>;
        
        gpio-controller;
        #gpio-cells = <2>;
        status = "okay";
    };
};

/* IOMUX配置 */
&iomuxc {
    pinctrl_led: ledgrp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x17059  /* LED0 */
            MX6UL_PAD_CSI_VSYNC__GPIO4_IO19     0x17059  /* LED1 */
        >;
    };
    
    pinctrl_gpio_keys: gpio_keysgrp {
        fsl,pins = <
            MX6UL_PAD_UART1_CTS_B__GPIO1_IO18   0x80000000
        >;
    };
    
    pinctrl_my_gpio: my_gpiogrp {
        fsl,pins = <
            MX6UL_PAD_ENET1_TX_EN__GPIO2_IO01   0x17059  /* RESET */
            MX6UL_PAD_ENET1_TX_CLK__GPIO2_IO02  0x17059  /* POWER */
            MX6UL_PAD_LCD_RESET__GPIO3_IO04     0x17059  /* STATUS */
        >;
    };
};

2.2 设备树GPIO属性详解

属性名称含义示例值说明
compatible 兼容性字符串 "gpio-leds" 匹配驱动程序
gpios GPIO规格说明 <&gpio1 3 GPIO_ACTIVE_LOW> 控制器+引脚号+极性
gpio-controller GPIO控制器标识 - 表明该节点是GPIO控制器
#gpio-cells GPIO单元格数量 <2> 指定GPIO引用所需参数个数
pinctrl-names 引脚控制状态名 "default", "sleep" 定义引脚状态
pinctrl-0 默认引脚配置 <&pinctrl_led> 引用IOMUX配置
default-state 默认状态 "on"/"off"/"keep" LED初始状态
linux,code 按键键值 <KEY_ENTER> 对应input子系统键值

3. Linux内核GPIO子系统架构

3.1 内核GPIO API函数

基础GPIO操作函数

 
gpiod_get / gpiod_put 申请 / 释放 GPIO 句柄 (生命周期管理)
gpiod_direction_input 设置为输入模式
gpiod_direction_output 设置为输出模式并设置初始值
gpiod_set_value 设置输出电平
gpiod_get_value 获取当前电平
gpiod_set_debounce 设置硬件防抖 (输入)
gpiod_set_open_drain 设置为开漏输出
gpiod_to_irq 将 GPIO 转换为 IRQ 号码 (用于中断)