一、GPIO介绍. J% ^7 C( I( U1 @& Y
1.1 GPIO 简述
, m0 u( R7 s, d4 B0 L8 y/ x GPIO(General purpose input/output,通用型输入输出),一个引脚可以用于输入、输出或其他特殊功能,PIN脚依现实需要可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),并多个GPIO一起组合可实现诸如UART、SPI等外设功能。9 N: E2 m. d- G, g G6 o
8 b+ @7 d+ m( z
GPIO是通过寄存器操作来实现电平信号输入输出的,对于输入,通过读取输入数据寄存器(IDR,Input Data Register)来确定引脚电位的高低;对于输出,通过写入输出数据寄存器(ODR,Output Data Register)来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。GPIO的输出通常可以输出0/1信号,用来实现如LED灯、继电器、蜂鸣器等控制,而GPIO的输入可识别0/1信号,用来实现开关、按键等等动作或状态判定。$ B% }% `# O6 Y7 X9 c: G [' o9 ?& _
* p7 K. h, c. h
3 \- v% O4 m; i2 j$ H9 Y* t- [+ K) Y; D- E' O" \. B& g7 U# x
4 c7 W" ?) l) {, x 1.2 STM32 GPIO寄存器* S! P4 `5 O9 m2 e- W9 G
STM32中GPIO的寄存器是MCU分配的一个连续存储区域,按存储地址划分出不同功能寄存器,如下图所示,有Port mode寄存器、Output Type寄存器等等。
/ `! Z2 m4 K+ s3 z8 t, v" n6 }$ z& A" e# g* Y
9 r' i- }5 `! S4 u+ t
2 x0 P% K1 Q! \: n- f
+ L4 B9 l! S0 O/ _" J 在HLA库中,这些寄存器的数值(宏定义)通常会放在/Drivers/CMSIS/Device/ST/STM32L4xx/Include/stm32XXX.h头文件中,供开发者调用,例如STM32L496VGTx的开发板,该头文件具体名是stm32l496xx.h,自8977行起到10008行,具是按上述表宏定义了GPIO各寄存器的各个数据位可设置数据值(偏移(POS)及相应数值)。
# N G$ V+ {' Y7 h% z' A" X, h7 i* H' X
- ?& |, v/ L5 H
* L: W) C2 q( i4 e
1.3 GPIO引脚与标记
4 A, G2 @2 m# k$ g 在一个STM32 MCU芯片中,所有引脚本质都是GPIO引脚,只是鉴于全GPIO引脚开发者使用起来比较不方便,于是才通过各个GPIO组合形成针对各个外设的IO接口。在CubeMX中,以STM32L496VGTx芯片为例,可以直观看到该芯片有100个引脚:LQFP100,LQFP是封装,100指的是引脚数。0 Y0 u0 `) U2 Q/ |. ^, e
- O* H5 Y* l W6 _
$ Q& ^6 x6 F- ^
7 O) f0 k" X: T8 D STM32的MCU标记一般有两种,直插IC以半圆条口为标记,贴片IC以小圆点为标记,也有缺口及小点都有标记的。通常将标记圆点或缺口正面朝左上角,左上角的下方脚为第一脚,依次围绕芯片中心逆时针计数,在旋转360度以后,左上角右方角为最后一脚。如上图所示,STM32L496VGTx引脚序号如蓝色字体及箭头所示标记。STM32的MCU的引脚标记有三种方式: d7 _! Y9 i$ Y7 x. I9 v v. v
- n0 \7 ]% N2 h
一种是基于上面所述的100个序号排序P1、Pn等,这种标记目前一般是在直接操作寄存器地址或使用标准库中使用,目前HLA库、LL库已经不提供直接地址偏移方式的API了,而是给没一组分组提供了独立寄存器来处理该组引脚; h/ x# P$ ]" x% \. u
. F, u/ Z; c! s2 d+ D3 p- R3 T) G7 E 第二种是标准库或HLA库宏定义了引脚分组,一般为A、B、C、...,按大写字母次序网线,目前支持到16组类别,每组为PX0~PX15,其标记就是分组名+0~15序号,例如GPIOB+GPIO_PIN_3。
9 Z5 z0 S) L6 ^4 m5 c7 p' n; J' P3 R
注意,目前STM32的MCU设计是最大16组类别,支持到256引脚(16*16),通过STM32命名规范可以得知,STM32最做引脚支持到256个,但一般会少于引脚数/16的组数,因为不少引脚在芯片设计时就特别支持了其引脚标识,如VSS、VDD等。另外也不是每组必须0~15序号全用完,分组主要是方便设计及使用时进行引脚区分而已。
+ ?/ l2 ? P J. T- O$ @) i2 l1 R" u$ B, v }8 j+ E' i
# b1 [, U b/ m" H: b3 l9 T7 C6 R s
目前HLA库主要就是这种方式,例如来看看GPIO读取数据的API(HAL_GPIO_ReadPin)实现,主要就是根据引脚序号(0~15)来读取IDR寄存器内对应数据位的数值。
0 E' \4 [( J2 g/ W
2 G9 |' R7 c' p0 D2 L
0 o% u! j: e% ?# g
* I' Z' P6 s) L: U3 q
GPIO分组引脚地址在HLA宏定义数值如下。
& ~$ `1 L' H; {" i1 s& C: u/ |- #define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
- F6 a6 X/ n3 g+ d4 z' ] N - #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected *// B2 f4 L( l/ f, a
- #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
- B- [( Z9 ?& R4 ~6 M: F - #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */8 l7 I! T$ p% d \! B) P) O- L/ T
- #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */9 e: F# A) n3 m
- #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */% z, u- Z7 }, j) z
- #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */$ o! c# y# B) [9 x/ d
- #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */, N/ y5 R4 D7 { d5 m0 `& r$ p
- #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
) n" o6 J6 t' s" a+ D0 {- a - #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */6 x2 h+ w# n$ o7 h
- #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
+ \, U# [- s5 G. g- X3 e - #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */1 A1 X9 F( [) v) z u9 q
- #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
! j7 z. R2 P! P* o" e- i8 s6 V; _# V - #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
' }8 `1 O, E J' q: ? - #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */, Q, [% q n- i( o+ H% F' ?
- #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
" f" ?& t9 y2 S# J# U4 L" J - #define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
复制代码
5 `8 f( j. G+ @9 T 而GPIO各个分组的IDR寄存器为32bit宽度的数据,目前只用到低16位字段。 C8 l3 y: ]! t5 l
- typedef struct: C, h5 u3 X% a* W! G- R
- {3 C# P# d- q- s
- __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
) p" X# l0 X8 g7 O% _1 @7 ` - __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
$ U* E- H" Y& `# \, S1 e, x2 t - __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
7 L+ |- D9 Z! I; e8 u' o& n- l) z" t - __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
9 N* p, @ G2 Z/ d: n - __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */, d" J9 I$ p6 ^( I( A4 s2 I. a
- __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
/ k- F) z$ ^0 r/ b# ~) {) A - __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */% z0 k$ H9 I* A, b- y
- __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */6 r" y* e5 \, y
- __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */: @% @" O5 q) S4 R& J5 r' ]
- __IO uint32_t BRR; /*!< GPIO Bit Reset register, Address offset: 0x28 */
7 V$ M/ I# v3 h9 F2 Q( U2 w - ) ~! q, w: [) n3 q/ t- h
- } GPIO_TypeDef;
复制代码 ( W8 w0 r* Z8 U, D9 E% W5 E
第三种引脚标记其实就是在第二种方式基础上,为引脚设置别名,在cubeMX设置引脚mode后,会开启该引脚,在引脚参数设置列表可以设置该引脚标识名,例如下图,设置PE3(P2)为输出模式(GPIO_Output),的别名为LED1。+ I5 o; |4 b+ ?& K b6 E
$ Q' X! K$ j/ D
$ A* `- o% W* M W
+ j+ G, s8 H0 r3 u# j 那么在生成输出代码时,会通过#define实现标识名替换。5 a$ J5 I- c" V* o% r1 O, w( v2 S
. e$ r) C3 ~! _$ J- w5 m8 r
: M ?+ p9 o- e1 V& }8 a+ U5 }$ v) z7 P5 C8 O* s
% ~, R6 E$ ^: O: ] 1.4 GPIO原理框图及CubeMX配置5 H: w5 Q! v6 t9 t; E
进一步,再看看GPIO结构原理图,它支持4种GPIO输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种GPIO输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。因此才能通过多种输入输出模式组合来实现更复杂的其他外设功能。
8 [, U- I" Z- W3 M' F0 i0 h9 ]5 K: O" t
0 Q: `/ ~8 ~8 R2 F8 E' q
" x9 h: p7 Q' w6 ^5 R5 O* a# _& h 在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输入模式以及其他参数设置。
* @6 } Y- m- z7 p0 q0 H" ?1 t1 f& F0 y: H2 p. e7 d
7 u0 s: |* v+ _+ s' h( \2 e9 o& s$ V$ }/ R$ x% X5 q7 G) i
同样地,在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输出模式以及其他参数设置。: D. S8 x4 R" E- S6 _$ A; y1 F/ h2 m
0 s8 i/ B8 W2 _* \ x; |2 T
; l( H5 M6 \3 `
; u S- h; z. E: I
另外各种基于GPIO组合实现的外设,也可在GPIO栏的各个外设页面下,单独设置各个引脚的GPIO输入或输出模式。; h/ p' G7 t$ w" b
, l9 ]3 b+ j4 a
* Y! L2 t% M/ F9 h
, t$ t7 v4 u) w' X, G( p6 K/ W3 j 二、GPIO的HLA源码分析
, a4 A" R/ v& p. h. }' \0 w6 i; M( N 2.1 HLA库GPIO初始化: S0 G w: h3 ?/ ]* G
通过cubeMX生成输出源码,如果配置GPIO引脚功能,则cubeMX生成代码时,设置了为每个外设生成独立.h/.c文件时,会在Core源码目录下的Inc及Src目录,分别生成gpio.h和gpio.c驱动文件,否则将会GPOI相关输出函数放置main.h和main.c中。
0 `) s: |# Y( K9 U9 u
, w8 E& {6 o' T. F; |2 z: z 在gpio.c文件中,主要定义了MX_GPIO_Init函数,该主要做三件事情,一是启动各个GPIO分组寄存器的时钟,二是对于输出GPIO引脚实现初始值写入,三是将在cubeMX上配置引脚参数写入到参数缓存区域,并依据缓存参数,调用HLA库的HAL_GPIO_Init来实现真正的初始化设定。如下面代码所示。
/ P3 {3 n2 B, O6 w* v* F
$ A) R8 t" O( E' c* Q- //main.h
! U! O3 u# t3 K7 H% q - /* Private defines -----------------------------------------------------------*/
$ |/ J! p0 Q2 `9 G7 g: T# ?% q - #define LED1_Pin GPIO_PIN_3
( a. x- e7 F0 x - #define LED1_GPIO_Port GPIOE
8 N' `1 v7 H- f$ U# c5 t) \* M" J - #define KEY2_Pin GPIO_PIN_10
4 Q9 B& D* d& ]6 P: P% q - #define KEY2_GPIO_Port GPIOE/ ^% @' M* {$ [4 I
- #define KEY0_Pin GPIO_PIN_11; u6 h J1 N' d% e7 Y7 D: [
- #define KEY0_GPIO_Port GPIOE
7 b/ O O# P; Q( o8 @8 i2 O" l4 F - #define KEY1_Pin GPIO_PIN_14
/ d, ]$ n$ w3 ]# |8 q5 B& u - #define KEY1_GPIO_Port GPIOE( l+ R, W+ Y9 T1 J) ?, ?
- #define LED2_Pin GPIO_PIN_15
9 ^) }4 m4 G* y: t, N: Z, R4 { - #define LED2_GPIO_Port GPIOD; J1 x7 z/ d0 r
- #define LED0_Pin GPIO_PIN_6
+ \+ m& H& r% u - #define LED0_GPIO_Port GPIOB$ @) F2 b4 c+ V9 i7 Q' ?: K
- /* USER CODE BEGIN Private defines */
^3 T& F6 [. v7 _) W2 b - //gpio.c
& A* H% ^3 q7 z! R - void MX_GPIO_Init(void)! z. W% ^' B+ H# `! M# r9 ~1 M
- {7 U, E' ]: w" r$ v0 O2 u9 C- \: `
- 3 O; v. A6 \: a1 y
- GPIO_InitTypeDef GPIO_InitStruct = {0};
, U' U9 K4 y2 j' _6 e8 ` -
+ o9 M0 O" I! Y - /* GPIO Ports Clock Enable */
9 m8 X# N6 a1 C& s - __HAL_RCC_GPIOE_CLK_ENABLE();
E1 V+ B8 N" X. C2 _ - __HAL_RCC_GPIOC_CLK_ENABLE();/ U7 n. G& [! {7 Z) U9 r) l
- __HAL_RCC_GPIOH_CLK_ENABLE();
, \ i! @; c% S4 Q% ^' J - __HAL_RCC_GPIOB_CLK_ENABLE();% t4 n0 V( W* q8 _5 z, h( |* g' x
- __HAL_RCC_GPIOD_CLK_ENABLE();0 u. P) `3 {0 R" R8 {
- __HAL_RCC_GPIOA_CLK_ENABLE();9 R/ a, K' Y! \- E
-
7 v% @+ O$ E' E p - /*Configure GPIO pin Output Level */
7 Z+ l8 @+ k: c- D/ U - HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);- o9 l0 T& j. L1 H% F7 h* V
- \* F; @- l) @0 z
- /*Configure GPIO pin Output Level */5 |9 p$ t! @; J0 P
- HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);+ z2 {, g8 f' Q# g6 m
-
, q8 {- B+ t! a( d5 I - /*Configure GPIO pin Output Level */
" v4 T' g7 E: U, R - HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
: i$ Q/ D; J; q- i -
5 y S }9 m7 T' ^6 F4 a: Q - /*Configure GPIO pin : PtPin */
: b1 X: | C* P - GPIO_InitStruct.Pin = LED1_Pin;
4 _; j2 Y9 o& l1 P5 v - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
) e( ]6 `( ]3 z! L. Z3 Z - GPIO_InitStruct.Pull = GPIO_NOPULL;3 }: e A8 P2 G
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;% J \" P1 s( b
- HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);/ D0 t2 f. w' m7 D
-
; T5 u; b4 M" P( O$ z/ g - /*Configure GPIO pins : PEPin PEPin PEPin */
- ?- T2 o: N3 t! x+ }$ y, {' q - GPIO_InitStruct.Pin = KEY2_Pin|KEY0_Pin|KEY1_Pin;1 p$ l& @! Z% @4 V/ o* Y9 o& }
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;% F8 E. G& \6 v9 a8 R+ G
- GPIO_InitStruct.Pull = GPIO_NOPULL;
9 c/ O: C* B# c" K( A9 p4 P1 ] - HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
5 B: L4 p+ k; c z( R -
" Y, ^: C+ q. J) Y* s4 m - /*Configure GPIO pin : PtPin */
3 \" s$ T2 Z& A; q- d" v' } - GPIO_InitStruct.Pin = LED2_Pin;
+ q6 R5 N: Z2 u5 a - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;' C& I7 h" z9 O& T8 U7 z3 s5 ^
- GPIO_InitStruct.Pull = GPIO_NOPULL;1 c+ a+ z8 X# N5 J' T8 ~# D5 y
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
6 M; {' t0 d+ B. i5 y - HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct);
2 o* `+ C* U% ^, l/ H: y1 [" X/ { - / f; E& o+ j$ j% j, v) k
- /*Configure GPIO pin : PtPin */
# y6 I$ g: B) F/ v5 y m" ]$ ` - GPIO_InitStruct.Pin = LED0_Pin;0 T- N$ n- ]8 H4 ?$ z' `
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
7 ?7 [6 j. i! t# P9 i2 D - GPIO_InitStruct.Pull = GPIO_NOPULL;
+ c* h( M* p$ F; L - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
' N9 l8 P! [0 h( i - HAL_GPIO_Init(LED0_GPIO_Port, &GPIO_InitStruct);6 Q% o* a1 M+ m8 _' K" c" Y
-
0 e/ q d# l1 `% o - }
复制代码 6 p3 T- P- |! Y
在stm32l4xx_hal_gpio.c源文件中定义了HAL_GPIO_Init函数,它做以下事情:1)诊断先前缓存配置参数是否合规,2)再将缓存配置参数写入到GPIO的寄存器内,完成GPIO的初始化设定(GPIO 的Mode、Pull、Analog、Alternate、Direction、EXTI等)。
* ^! l/ q- |1 k: `
) {# r/ c% h" l- a( O
, w$ V, J/ B8 F) p! C( c7 |
2 s7 R9 D9 e+ _4 J0 n" C
2.2 GPIO的开发应用接口API简述: M- G1 g; L( W7 |/ I8 }
除了提供GPIO初始化API外,STM32的HLA针对GPIO还外开发这提供如下API:! r- ]) R% l3 ~, D2 l
+ i7 w5 U6 X" z; G' d. j
" J/ t& M* Z" b- p4 ?! b5 [: ?$ z- ?
GPIO对数据的读取主要是HAL_GPIO_ReadPin函数,写入主要是HAL_GPIO_WritePin、HAL_GPIO_TogglePin函数,其参数都是GPIO_TypeDef* GPIOx和 uint16_t GPIO_Pin,前者就是前面参数的分组如GPIOA、GPIOB等,后者就是Pin0~Pin15,例如GPIO_PIN_3。
1 `/ X- C, O# z4 \. ^/ N* }$ r% u- x
. [# T, _4 G6 E9 c% N# T* z, `0 G 关于外部中断回调函数HAL_GPIO_EXTI_Callback,该函数是弱函数
9 B' ~7 r7 O9 ?" P9 M
- u# m( n3 T+ j& e3 I; q
j! L9 }0 t# J7 [( G( @7 I( W2 M: T {" b. A) ^
若需要使用该函数,使其生效,首先需要在cubeMX配置界面,将GPIO引脚调整为GPIO_EXTI类型: r9 ?/ N; d5 Z, ^/ c: U0 I/ _
" t/ a& ]( b2 X! h( a' v W7 a+ ]
9 F" n/ ^& r$ D" d, ~0 Y
7 h( V- _2 ~9 T
并开启该引脚的中断支持3 `" c7 m( z l0 R1 P( ^3 A
: n9 R8 M3 _3 V ~, ] k Y1 m% l
! @/ O4 j, |( v: o3 _
+ {9 o# Y* t( c n5 g$ A0 U 就可以重新实现stm32l4xx_hal_gpio.c文件中的HAL_GPIO_EXTI_Callback函数来实现GPIO外部中断回调功能,例如设置如下,则实现按键KEY0按下异常,触发回调函数HAL_GPIO_EXTI_Callback调用,从而实现LED0灯状态反转(点亮或熄灭):
0 a) _3 K$ l! g- Z+ V8 Q, _- /* USER CODE BEGIN 0 */
u# u/ D4 V+ R& [3 a6 ~ - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)0 P4 O* @7 F! \- l0 s. v1 ~
- {/ _. H* v9 m8 l
- if(GPIO_Pin==KEY0_Pin)% c' q* N7 Q! u8 Y- \
- {
$ Y& t( C: [; X" B0 v1 l" k* ^ - static uint32_t key0_count = 0;. E; t& V. t3 B- g6 z+ B
- printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);+ U$ ^! P% @+ m+ z/ d% X
- Toggle_led0();//LED0等状态反转一次
' j; }" R8 Y/ [" c - }2 l) ~% Q0 m0 I9 O Y2 _. u" v
- }0 m5 K9 j+ X: {, i# B8 {
- /* USER CODE END 0 */
复制代码
Y. P" u; h+ `/ R4 ] 同样地对于外部中断请求函数HAL_GPIO_EXTI_IRQHandler也可以类似地进行调整实现GPIO外部中断触发器调用,从而实现我们需要的业务应用。; ?4 a. r# h, ?1 Z- C: S
2 C$ A# t! a6 {: ~" v GPIO的HLA库中,HAL_GPIO_LockPin函数,主要是操作lock寄存器数值的,lock寄存器会针对GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR等GPIO寄存器配置对应的状态标记,而其他寄存器操作时,会先去lock寄存器读取其使用状态来确定是否能进行相关操作,从而确保防止冲突及达成一致性。7 O* p4 |$ i2 r, @; v* U9 x; X: E
————————————————% w O" \/ M& a
版权声明:py_free-物联智能7 v4 Y) A- {$ q5 \% V& }% j
如有侵权请联系删除
- B( m: N$ q7 `8 w7 t6 ] |