這種其實應(yīng)該用一個直觀通俗的比喻的先講,再把里面的關(guān)系對應(yīng)到實際的代碼,多看圖,有詳有簡,慢慢改吧
1. STM32 MCU GPIO操控機制
1.1 底層寄存器定義架構(gòu)
頭文件結(jié)構(gòu) (stm32f4xx.h為例)
/* APB1總線基地址定義 */
#define APB1PERIPH_BASE 0x40000000UL
#define APB2PERIPH_BASE 0x40010000UL
/* GPIO外設(shè)偏移地址定義 */
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800UL)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00UL)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000UL)
/* GPIO寄存器結(jié)構(gòu)體定義 */
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; /* 輸入數(shù)據(jù)寄存器 偏移: 0x10 */
__IO uint32_t ODR; /* 輸出數(shù)據(jù)寄存器 偏移: 0x14 */
__IO uint32_t BSRR; /* 位設(shè)置/復(fù)位寄存器 偏移: 0x18 */
__IO uint32_t LCKR; /* 配置鎖定寄存器 偏移: 0x1C */
__IO uint32_t AFR[2]; /* 復(fù)用功能寄存器 偏移: 0x20-0x24 */
} GPIO_TypeDef;
/* GPIO外設(shè)指針定義 */
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
1.2 HAL庫封裝機制
GPIO初始化結(jié)構(gòu)體
typedef struct {
uint32_t Pin; /* 指定要配置的GPIO引腳 */
uint32_t Mode; /* 指定選中引腳的工作模式 */
uint32_t Pull; /* 指定選中引腳的上拉或下拉激活 */
uint32_t Speed; /* 指定選中引腳的速度 */
uint32_t Alternate; /* 外設(shè)復(fù)用選擇 */
} GPIO_InitTypeDef;
/* 標(biāo)準(zhǔn)庫初始化函數(shù) */
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
實際應(yīng)用示例/* 包含相關(guān)頭文件 */#include "stm32f4xx_hal.h"
void LED_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 使能GPIO時鐘 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 配置GPIO結(jié)構(gòu)體 */ 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); /* 底層實現(xiàn)等價于: GPIOA->MODER |= (0x01 << (5*2)); */ }不管是MCU,MPU,只要跑裸機操控GPIO都這樣。
采用宏定義的方式在頭文件定義相關(guān)GPIO,源文件使用宏定義,這樣可以將GPIO與實際的GPIO分隔開,使用不同的GPIO或者外設(shè),替換即可。野火的stm32f10x教程就是這樣寫的,方便不少。那么linux設(shè)備和驅(qū)動模型就是這樣一個映射關(guān)系。有一個設(shè)備樹文件描述這些GPIO等等信息,編譯生成dtb文件,在uboot分區(qū)下,在系統(tǒng)啟動會被轉(zhuǎn)化為device。
在linux操作系統(tǒng)且mpu有mmu這個硬件(進程地址進行檢查),不能直接操作GPIO寄存器,相應(yīng)的xx外設(shè).core提供了操作函數(shù),若直接操作gpio,需要使用ioremap。需要記住的就是xxxcore.h
提供的接口函數(shù)。同時設(shè)備樹文件會在uboot被內(nèi)核轉(zhuǎn)為struct platform_device結(jié)構(gòu)體,被注冊,包含硬件地址,描述信息等等。寫的驅(qū)動結(jié)構(gòu)體在module_init被注冊。platform_driver 和platform_device 被匹配(此時應(yīng)有一圖)

Linux
probe函數(shù)主要是,字符類設(shè)備驅(qū)動:
-
-
- register_chrdev
- class_create, device_create,注冊設(shè)備節(jié)點
- 對于其他如lcd,需要app來操作framebuff,需要mmap,對于需要操作GPIO寄存器,需要ioremap
2. Linux設(shè)備樹GPIO描述機制
2.1 設(shè)備樹GPIO節(jié)點完整示例
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設(shè)備節(jié)點 */
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;
};
};
/* 按鍵設(shè)備節(jié)點 */
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設(shè)備 */
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 設(shè)備樹GPIO屬性詳解
| 屬性名稱 | 含義 | 示例值 | 說明 |
|---|---|---|---|
| compatible | 兼容性字符串 | "gpio-leds" | 匹配驅(qū)動程序 |
| gpios | GPIO規(guī)格說明 | <&gpio1 3 GPIO_ACTIVE_LOW> | 控制器+引腳號+極性 |
| gpio-controller | GPIO控制器標(biāo)識 | - | 表明該節(jié)點是GPIO控制器 |
| #gpio-cells | GPIO單元格數(shù)量 | <2> | 指定GPIO引用所需參數(shù)個數(shù) |
| pinctrl-names | 引腳控制狀態(tài)名 | "default", "sleep" | 定義引腳狀態(tài) |
| pinctrl-0 | 默認(rèn)引腳配置 | <&pinctrl_led> | 引用IOMUX配置 |
| default-state | 默認(rèn)狀態(tài) | "on"/"off"/"keep" | LED初始狀態(tài) |
| linux,code | 按鍵鍵值 | <KEY_ENTER> | 對應(yīng)input子系統(tǒng)鍵值 |
3. Linux內(nèi)核GPIO子系統(tǒng)架構(gòu)
3.1 內(nèi)核GPIO API函數(shù)
基礎(chǔ)GPIO操作函數(shù)
| gpiod_get / gpiod_put | 申請 / 釋放 GPIO 句柄 (生命周期管理) |
| gpiod_direction_input | 設(shè)置為輸入模式 |
| gpiod_direction_output | 設(shè)置為輸出模式并設(shè)置初始值 |
| gpiod_set_value | 設(shè)置輸出電平 |
| gpiod_get_value | 獲取當(dāng)前電平 |
| gpiod_set_debounce | 設(shè)置硬件防抖 (輸入) |
| gpiod_set_open_drain | 設(shè)置為開漏輸出 |
| gpiod_to_irq | 將 GPIO 轉(zhuǎn)換為 IRQ 號碼 (用于中斷) |