这种其实应该用一个直观通俗的比喻的先讲,再把里面的关系对应到实际的代码,多看图,有详有简,慢慢改吧
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 被匹配(此时应有一图)
Linux
probe函数主要是,字符类设备驱动:
-
-
- 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 号码 (用于中断) |