一、GPIO介绍
1 o. r. i: x2 J$ U( B 1.1 GPIO 简述: o& E7 J5 {( B% y" I
GPIO(General purpose input/output,通用型输入输出),一个引脚可以用于输入、输出或其他特殊功能,PIN脚依现实需要可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),并多个GPIO一起组合可实现诸如UART、SPI等外设功能。
+ m1 G2 w" F2 ~ u) x5 f9 H6 o
& }6 y$ g2 T6 X& K; c! [6 e GPIO是通过寄存器操作来实现电平信号输入输出的,对于输入,通过读取输入数据寄存器(IDR,Input Data Register)来确定引脚电位的高低;对于输出,通过写入输出数据寄存器(ODR,Output Data Register)来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。GPIO的输出通常可以输出0/1信号,用来实现如LED灯、继电器、蜂鸣器等控制,而GPIO的输入可识别0/1信号,用来实现开关、按键等等动作或状态判定。2 P9 _3 A, C" t' a, p" q: e
l3 B' n& Q' e+ [
2 |. } X$ d L0 e
* T5 ~ H$ P- G5 Y0 Y
) {% V9 ?( n0 @* f+ K- r
1.2 STM32 GPIO寄存器
( }0 B% ?' |1 q! ^ [/ z: S STM32中GPIO的寄存器是MCU分配的一个连续存储区域,按存储地址划分出不同功能寄存器,如下图所示,有Port mode寄存器、Output Type寄存器等等。0 r, d" N3 D- @! `# a' l) a
. r* m2 z# M0 E a8 s. a& E3 F
5 U7 _5 q4 }5 z( s% P; t( X
# u& z! n! o8 [6 s: E. U) p* @: \
% F* t$ x& y( W+ a 在HLA库中,这些寄存器的数值(宏定义)通常会放在/Drivers/CMSIS/Device/ST/STM32L4xx/Include/stm32XXX.h头文件中,供开发者调用,例如STM32L496VGTx的开发板,该头文件具体名是stm32l496xx.h,自8977行起到10008行,具是按上述表宏定义了GPIO各寄存器的各个数据位可设置数据值(偏移(POS)及相应数值)。( \7 S* z' X) S6 }2 f' l
; `2 |1 q7 P3 L4 l( j
# n; x/ o9 `! I/ O1 ~
( k! a6 g8 _ B# i! {: A 1.3 GPIO引脚与标记+ i/ N T; z+ l/ ^( A
在一个STM32 MCU芯片中,所有引脚本质都是GPIO引脚,只是鉴于全GPIO引脚开发者使用起来比较不方便,于是才通过各个GPIO组合形成针对各个外设的IO接口。在CubeMX中,以STM32L496VGTx芯片为例,可以直观看到该芯片有100个引脚:LQFP100,LQFP是封装,100指的是引脚数。; a: W) L) \; d+ a. D
7 c! Q/ f- |# ~3 H( i" R0 o
# z+ n1 N# |& m
2 J, I) P5 Y; p$ Y# W ]5 y STM32的MCU标记一般有两种,直插IC以半圆条口为标记,贴片IC以小圆点为标记,也有缺口及小点都有标记的。通常将标记圆点或缺口正面朝左上角,左上角的下方脚为第一脚,依次围绕芯片中心逆时针计数,在旋转360度以后,左上角右方角为最后一脚。如上图所示,STM32L496VGTx引脚序号如蓝色字体及箭头所示标记。STM32的MCU的引脚标记有三种方式:
" B4 D6 l/ _, g r x) ~8 A+ [& F5 W" l, v6 v$ @/ w* G2 w7 j8 f' |* L4 R
一种是基于上面所述的100个序号排序P1、Pn等,这种标记目前一般是在直接操作寄存器地址或使用标准库中使用,目前HLA库、LL库已经不提供直接地址偏移方式的API了,而是给没一组分组提供了独立寄存器来处理该组引脚;" U% D$ {6 D; I2 P& o
& T( \ H2 u9 R4 a
第二种是标准库或HLA库宏定义了引脚分组,一般为A、B、C、...,按大写字母次序网线,目前支持到16组类别,每组为PX0~PX15,其标记就是分组名+0~15序号,例如GPIOB+GPIO_PIN_3。0 s9 U4 P& q/ r( F
7 ]/ c% d$ Z ~' e3 p# l$ a8 i" p 注意,目前STM32的MCU设计是最大16组类别,支持到256引脚(16*16),通过STM32命名规范可以得知,STM32最做引脚支持到256个,但一般会少于引脚数/16的组数,因为不少引脚在芯片设计时就特别支持了其引脚标识,如VSS、VDD等。另外也不是每组必须0~15序号全用完,分组主要是方便设计及使用时进行引脚区分而已。2 H9 x8 `% [9 Y# x% y
9 t/ U' d% ]4 t
) b$ R# \# ~2 i
/ ~ c* a1 S; k% T9 U( j9 q 目前HLA库主要就是这种方式,例如来看看GPIO读取数据的API(HAL_GPIO_ReadPin)实现,主要就是根据引脚序号(0~15)来读取IDR寄存器内对应数据位的数值。) z; c/ h( T0 ~1 a6 e3 |9 t% U3 }
5 {- I) k+ p5 E) G; o
0 w, R h1 F8 J4 t
% C9 g& U) r. C/ y2 w GPIO分组引脚地址在HLA宏定义数值如下。+ |' D6 y- n( h" U
- #define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */6 y4 @. Q8 L/ G
- #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */5 s$ [& w# ]- O7 V L9 H% D7 C4 d
- #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
E* q P# k* M. j - #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */ _3 s0 { F. [' N2 q& q% u
- #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
) [& y$ T% B1 d! }1 v3 s. y q - #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
T6 V6 P7 E* }; v# ?6 }1 z& o$ o - #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */$ A3 o& d! b, F7 F7 [& t$ ?+ I- B
- #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */7 P# g8 W* I K! ^% g
- #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */, ?- Q9 q0 f( [4 y
- #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */: c+ u( s- s+ Y7 V0 v& p1 `
- #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected *// l$ C# T$ M6 F
- #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
7 ^- ~* {" w6 L9 j* V - #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */4 E8 v$ p* g/ W0 P
- #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */! R2 _ n2 T4 E
- #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */1 g2 u' e9 x9 s- F: Z/ L
- #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
5 `/ X( ?8 T% p* [3 k7 V - #define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
复制代码
8 _" n* x+ b9 W 而GPIO各个分组的IDR寄存器为32bit宽度的数据,目前只用到低16位字段。
3 q. O6 p# n; \- typedef struct
. ]( ]% M4 F' @& s - {1 F3 s. @) b5 S/ A
- __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
+ {2 Y( |0 h9 b* v8 w - __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */- q0 B6 U/ N/ i% I7 w
- __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */% b' r% |3 X# A
- __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */. ?; y( n9 }+ B7 t- o5 l
- __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
' ~- F- f+ p* ~( a - __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
3 m+ D8 X: K& O4 ^6 z/ I - __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
# ?3 H% ~2 D* e/ F+ ~ - __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
1 A8 Q3 Y; ^0 O - __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
1 s4 r$ h! F* I% f% |0 X) { - __IO uint32_t BRR; /*!< GPIO Bit Reset register, Address offset: 0x28 */2 K8 a0 f% a, o
- 4 c% c, U7 }" I! M& c. q9 t
- } GPIO_TypeDef;
复制代码
+ h5 i# u# i2 n 第三种引脚标记其实就是在第二种方式基础上,为引脚设置别名,在cubeMX设置引脚mode后,会开启该引脚,在引脚参数设置列表可以设置该引脚标识名,例如下图,设置PE3(P2)为输出模式(GPIO_Output),的别名为LED1。
, p3 Y" e; L& q9 @! ^3 t
8 I- G0 L/ T1 Q/ J' M
( R: A7 }# Z9 b" s1 R' \8 k, r$ {$ L4 l) i T9 E
那么在生成输出代码时,会通过#define实现标识名替换。* i. ^( Y& h1 \7 E4 e& L# R
# a E" m, h- Y" Q. W
3 C) @- [+ n6 d L- G
/ v9 q) I, m; h- {7 e, R
+ o- A4 ^4 [6 U# T) o 1.4 GPIO原理框图及CubeMX配置8 W1 y# v E" P% \& q
进一步,再看看GPIO结构原理图,它支持4种GPIO输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种GPIO输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。因此才能通过多种输入输出模式组合来实现更复杂的其他外设功能。
; d; D! _, e K0 v
- N) [8 a! p; f0 _/ a: j
* Q1 p! g) l/ y1 R
/ e6 j$ T2 {% S& h( E7 i 在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输入模式以及其他参数设置。3 B" P7 M* x, `4 N
- @9 m" ~8 M" C4 P7 H6 D" q
% g4 s. k/ s; j' u! z6 b6 j' w
. C% T4 h% {- L) |
同样地,在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输出模式以及其他参数设置。
J+ k, u( X; f
* _ u+ ~$ M) e
# s' z8 K: Z* \6 n' N5 P4 n
; ?) ?' Z- l: Q) R/ @" ?: R8 @/ ^
另外各种基于GPIO组合实现的外设,也可在GPIO栏的各个外设页面下,单独设置各个引脚的GPIO输入或输出模式。
M2 p, M9 l/ ?3 ~5 T6 X( n
- W& M& E1 e8 f/ r: t
$ a& M& _, {& y! O' ]4 R
" ` D8 J) R4 Y( o 二、GPIO的HLA源码分析
& C7 n- f# l' L0 @2 Z; M; T 2.1 HLA库GPIO初始化
+ m) D6 x' m% k4 b( T4 U4 Y 通过cubeMX生成输出源码,如果配置GPIO引脚功能,则cubeMX生成代码时,设置了为每个外设生成独立.h/.c文件时,会在Core源码目录下的Inc及Src目录,分别生成gpio.h和gpio.c驱动文件,否则将会GPOI相关输出函数放置main.h和main.c中。
. F7 J8 }, i4 h7 ~" o8 S% R, Q: k9 U
在gpio.c文件中,主要定义了MX_GPIO_Init函数,该主要做三件事情,一是启动各个GPIO分组寄存器的时钟,二是对于输出GPIO引脚实现初始值写入,三是将在cubeMX上配置引脚参数写入到参数缓存区域,并依据缓存参数,调用HLA库的HAL_GPIO_Init来实现真正的初始化设定。如下面代码所示。
, |# b- j8 g# [# l! }7 X) d( p& T! ~) M8 O5 I8 K( p2 A1 Y
- //main.h* ^2 O! ?$ r5 e$ U
- /* Private defines -----------------------------------------------------------*/+ ~2 a6 t& d, d
- #define LED1_Pin GPIO_PIN_3
% c7 d; |& K# I - #define LED1_GPIO_Port GPIOE( E+ s! B3 M. c, k4 i5 S3 z. Z
- #define KEY2_Pin GPIO_PIN_10
5 P) X" v+ H7 _; U2 a) a$ j2 S - #define KEY2_GPIO_Port GPIOE
* [# z7 C# V, l! q' B' f - #define KEY0_Pin GPIO_PIN_11
9 P9 U/ }) j# _* n8 r& L - #define KEY0_GPIO_Port GPIOE% w! ~% [$ q1 Q ^8 c _" O: U7 w
- #define KEY1_Pin GPIO_PIN_14
) O+ i8 V5 a' p - #define KEY1_GPIO_Port GPIOE2 I6 @( s7 w: Z5 W6 E5 K, ^6 C
- #define LED2_Pin GPIO_PIN_151 }7 Y% [: f9 t! T- S
- #define LED2_GPIO_Port GPIOD9 Y, t, f4 w4 }' D
- #define LED0_Pin GPIO_PIN_6# ^) {7 _6 Z7 k' O
- #define LED0_GPIO_Port GPIOB
" ]( k7 f7 v5 C& m# L/ l - /* USER CODE BEGIN Private defines */% p4 f" ?2 ^; w: O4 q* ?
- //gpio.c. ?0 S( H/ E4 d& L5 ?
- void MX_GPIO_Init(void)/ D; k# \& G8 M# e* a
- {$ \3 k. p7 t6 l' q4 R1 a
- - E/ {9 j. z" L M1 g2 ?
- GPIO_InitTypeDef GPIO_InitStruct = {0};: x! J& r* ~# c: O! M- _$ n
-
5 N$ D) j6 a. l4 I; ` - /* GPIO Ports Clock Enable */) k: O% e6 t: A) `$ |0 W: P4 ]
- __HAL_RCC_GPIOE_CLK_ENABLE();/ a( z; |8 N ^' ~! V1 `
- __HAL_RCC_GPIOC_CLK_ENABLE();) U& B3 q1 H( N. f/ F: Z
- __HAL_RCC_GPIOH_CLK_ENABLE();$ Q) h8 t: H% e6 \6 B* h; B) a
- __HAL_RCC_GPIOB_CLK_ENABLE();
o+ `, h; u6 \ - __HAL_RCC_GPIOD_CLK_ENABLE();2 q( b+ q: g& G, Q6 |' t
- __HAL_RCC_GPIOA_CLK_ENABLE();
+ r K1 F, Z% @: I: H( b- q0 e -
7 x3 }0 A0 \$ n V' J - /*Configure GPIO pin Output Level */: b' a5 `( o. z
- HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
" p9 V V7 S7 ?+ l5 D7 }: n - 7 s+ m' y7 R0 b* F9 ]
- /*Configure GPIO pin Output Level */
2 Y* R0 O* o( m; t/ x - HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);8 p1 Z) e, Q) { I! v f% O
-
2 r+ G' o, Y1 U1 E - /*Configure GPIO pin Output Level */$ O: t$ c" W! O6 C; [! p
- HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);+ K8 Z: ], _. z) T" X% I( F
- 6 j4 t9 K# O7 O5 B# }$ u
- /*Configure GPIO pin : PtPin */
/ V, o( K$ n) t! v( v0 p$ r - GPIO_InitStruct.Pin = LED1_Pin;+ f X: ]8 m1 x3 l+ w6 t
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
7 U7 t6 _9 [$ y6 x1 ?( c - GPIO_InitStruct.Pull = GPIO_NOPULL;
0 S2 V* g% [ Z' O j+ h; x - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;$ _6 X" I8 C8 t9 P, z5 L: V" k/ `
- HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);6 V& \8 S9 | Z* `7 P( m4 X
- . M/ j; n& y6 q" R
- /*Configure GPIO pins : PEPin PEPin PEPin */
9 P6 k2 ^' H8 y# E/ k! U$ x5 w - GPIO_InitStruct.Pin = KEY2_Pin|KEY0_Pin|KEY1_Pin;
, Z8 y$ K' t- U" h# \ - GPIO_InitStruct.Mode = GPIO_MODE_INPUT;: c7 M8 D. A0 M" q1 ]/ i. y1 A
- GPIO_InitStruct.Pull = GPIO_NOPULL;
3 I0 x8 z6 O1 U - HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
$ d$ S2 `: C5 ? -
6 V9 ?' d" E7 @- q1 [5 A% l - /*Configure GPIO pin : PtPin */1 T3 L4 b: P2 z X
- GPIO_InitStruct.Pin = LED2_Pin;
0 R; {6 b9 l5 b9 s/ o! p - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;6 N2 @2 X' p6 V# h
- GPIO_InitStruct.Pull = GPIO_NOPULL;$ \6 R2 m( R* M' k( t9 U
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
6 k2 B5 y- ]1 \3 x5 O& k9 [" k - HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct);
6 t7 B' J8 b. P5 _3 F - ' h ~% w6 ^- B. B( x
- /*Configure GPIO pin : PtPin */
# o) q5 E2 X5 a* N/ a/ r - GPIO_InitStruct.Pin = LED0_Pin;1 s2 C; t! s0 U$ N
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
: Q; r' e# X* ^- O$ ?# ?& v$ ? - GPIO_InitStruct.Pull = GPIO_NOPULL;
% J4 U* Q2 \- y3 R - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;3 e* b1 m+ W) }
- HAL_GPIO_Init(LED0_GPIO_Port, &GPIO_InitStruct);
2 B8 A2 F I1 g -
( r6 A0 m' f( ~9 B' | - }
复制代码
0 T% x, |8 [: m! D: P! J 在stm32l4xx_hal_gpio.c源文件中定义了HAL_GPIO_Init函数,它做以下事情:1)诊断先前缓存配置参数是否合规,2)再将缓存配置参数写入到GPIO的寄存器内,完成GPIO的初始化设定(GPIO 的Mode、Pull、Analog、Alternate、Direction、EXTI等)。! y4 q* H" _4 {; r4 ?- g& d1 X9 G
, _2 W5 g- e% Q8 ~
# U+ i0 N+ O5 `- n# R9 M2 x6 P# G k
2.2 GPIO的开发应用接口API简述
: s; K4 p7 N, x4 o5 c5 x 除了提供GPIO初始化API外,STM32的HLA针对GPIO还外开发这提供如下API:
$ ? L+ c0 A/ w# N4 \; a( s% F
0 Y3 u. x3 X! u6 Y8 c5 j6 }! C! z8 h
GPIO对数据的读取主要是HAL_GPIO_ReadPin函数,写入主要是HAL_GPIO_WritePin、HAL_GPIO_TogglePin函数,其参数都是GPIO_TypeDef* GPIOx和 uint16_t GPIO_Pin,前者就是前面参数的分组如GPIOA、GPIOB等,后者就是Pin0~Pin15,例如GPIO_PIN_3。0 I' D& l+ L- d
. ?. i- m* y6 M. X 关于外部中断回调函数HAL_GPIO_EXTI_Callback,该函数是弱函数! }/ K5 k: H. q ]
& w* P# H( ~3 w: z0 m3 s
& \, |2 s8 z# v7 K1 J# n; Y
# h7 {9 u; Q- K: b: o! B 若需要使用该函数,使其生效,首先需要在cubeMX配置界面,将GPIO引脚调整为GPIO_EXTI类型
6 x( c( j2 h! @& I* {' ?- h6 }
% a5 o; {1 N* \9 D9 r6 B( }3 O
; F1 E! `: p9 Y3 g' A$ l! J8 s, c
7 l& U! K) q2 c n# X/ p
并开启该引脚的中断支持
6 T8 Z2 i. m( N4 }* }: o
* I9 h6 b0 p6 @! Z
, J( a/ o, R! J! k& ]/ L' G3 b
2 E- W, G. m: m" ^7 Z
就可以重新实现stm32l4xx_hal_gpio.c文件中的HAL_GPIO_EXTI_Callback函数来实现GPIO外部中断回调功能,例如设置如下,则实现按键KEY0按下异常,触发回调函数HAL_GPIO_EXTI_Callback调用,从而实现LED0灯状态反转(点亮或熄灭):7 b7 f" m( F) D( u. W7 r8 \ M5 |
- /* USER CODE BEGIN 0 */
: ]. E ?3 }5 z" I, w - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)3 A6 J! c2 l! I# R2 l. U- ` I' g
- {3 k1 O* n' A5 _: w+ [1 p
- if(GPIO_Pin==KEY0_Pin)" k7 w% i" T! T' v
- {
4 k& C5 ^* m3 m - static uint32_t key0_count = 0;4 u5 f1 i( t: P' F
- printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);
# `1 w4 Z5 t0 T z; W) u B6 R - Toggle_led0();//LED0等状态反转一次* G4 Y0 q |' R
- }
& N) a1 |6 C4 L, L1 s - }3 B* v, i9 D" O9 ~4 W( `
- /* USER CODE END 0 */
复制代码 ; B5 }5 }8 c7 }( q
同样地对于外部中断请求函数HAL_GPIO_EXTI_IRQHandler也可以类似地进行调整实现GPIO外部中断触发器调用,从而实现我们需要的业务应用。
& ~* O2 N h( z: N6 n) ^8 J# ?7 i& x+ C9 Y! _* u( Z, {# y
GPIO的HLA库中,HAL_GPIO_LockPin函数,主要是操作lock寄存器数值的,lock寄存器会针对GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR等GPIO寄存器配置对应的状态标记,而其他寄存器操作时,会先去lock寄存器读取其使用状态来确定是否能进行相关操作,从而确保防止冲突及达成一致性。
: c+ Q) s6 z+ ]0 r# ~7 ?: O1 N' ?————————————————, I7 r6 a' e- B. h, `4 o
版权声明:py_free-物联智能
# d( V8 [$ r# R, F: ]5 T; d如有侵权请联系删除
) t; j4 Y# K- i6 Z |