一、GPIO介绍( l8 r% J, D7 H% A I8 H
1.1 GPIO 简述 \" }) H9 I/ u1 M! W+ ^2 B
GPIO(General purpose input/output,通用型输入输出),一个引脚可以用于输入、输出或其他特殊功能,PIN脚依现实需要可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),并多个GPIO一起组合可实现诸如UART、SPI等外设功能。% k- D# T+ x& T
. ?0 M/ i Q$ e7 \+ d& I. d
GPIO是通过寄存器操作来实现电平信号输入输出的,对于输入,通过读取输入数据寄存器(IDR,Input Data Register)来确定引脚电位的高低;对于输出,通过写入输出数据寄存器(ODR,Output Data Register)来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。GPIO的输出通常可以输出0/1信号,用来实现如LED灯、继电器、蜂鸣器等控制,而GPIO的输入可识别0/1信号,用来实现开关、按键等等动作或状态判定。
4 C# V" m% J6 \% y& T
& o& A# u N: V5 P
6 u7 z0 u& ]7 M: y
6 y# s1 x& d) m
9 l, E7 @8 G$ L8 ~+ n 1.2 STM32 GPIO寄存器9 n5 x" p* s. Q4 w! Z
STM32中GPIO的寄存器是MCU分配的一个连续存储区域,按存储地址划分出不同功能寄存器,如下图所示,有Port mode寄存器、Output Type寄存器等等。
% Q/ D' Z6 u r: _0 Q. T! U9 d7 x5 b. X+ q4 |
9 [' Q2 @ f1 T+ ?6 q
) M& S- V+ X5 Z/ |% ^2 z4 [$ D" c4 T3 z2 b
在HLA库中,这些寄存器的数值(宏定义)通常会放在/Drivers/CMSIS/Device/ST/STM32L4xx/Include/stm32XXX.h头文件中,供开发者调用,例如STM32L496VGTx的开发板,该头文件具体名是stm32l496xx.h,自8977行起到10008行,具是按上述表宏定义了GPIO各寄存器的各个数据位可设置数据值(偏移(POS)及相应数值)。
, T: O: t0 ?. @# A) `
# Z1 Z2 @& H/ q* \: y# ~' \
9 Q& w1 t2 A7 C6 x2 T
0 C: T9 C: V+ H' i 1.3 GPIO引脚与标记
: h" g9 c1 [! I/ L 在一个STM32 MCU芯片中,所有引脚本质都是GPIO引脚,只是鉴于全GPIO引脚开发者使用起来比较不方便,于是才通过各个GPIO组合形成针对各个外设的IO接口。在CubeMX中,以STM32L496VGTx芯片为例,可以直观看到该芯片有100个引脚:LQFP100,LQFP是封装,100指的是引脚数。; g+ g; J, [+ r
* y; E5 g- i }$ e
* k1 V! ?* N$ O/ b! d4 T
5 I4 W( T; k" Q; z- K* e# p- X, k" I0 ? STM32的MCU标记一般有两种,直插IC以半圆条口为标记,贴片IC以小圆点为标记,也有缺口及小点都有标记的。通常将标记圆点或缺口正面朝左上角,左上角的下方脚为第一脚,依次围绕芯片中心逆时针计数,在旋转360度以后,左上角右方角为最后一脚。如上图所示,STM32L496VGTx引脚序号如蓝色字体及箭头所示标记。STM32的MCU的引脚标记有三种方式:! O* O I% F3 M5 E! i% C
2 p/ I: j) ^3 K& N7 C1 M 一种是基于上面所述的100个序号排序P1、Pn等,这种标记目前一般是在直接操作寄存器地址或使用标准库中使用,目前HLA库、LL库已经不提供直接地址偏移方式的API了,而是给没一组分组提供了独立寄存器来处理该组引脚;
1 R x2 i. i$ A
& l* T1 o& s4 G2 F; N+ _ 第二种是标准库或HLA库宏定义了引脚分组,一般为A、B、C、...,按大写字母次序网线,目前支持到16组类别,每组为PX0~PX15,其标记就是分组名+0~15序号,例如GPIOB+GPIO_PIN_3。
& n6 G5 C0 D. J- E5 A, y# N/ ^- m( Q
- V% {& ^4 h8 C" K" g 注意,目前STM32的MCU设计是最大16组类别,支持到256引脚(16*16),通过STM32命名规范可以得知,STM32最做引脚支持到256个,但一般会少于引脚数/16的组数,因为不少引脚在芯片设计时就特别支持了其引脚标识,如VSS、VDD等。另外也不是每组必须0~15序号全用完,分组主要是方便设计及使用时进行引脚区分而已。
9 o1 _/ G( q: H5 m% l) Q: T8 I! l9 Y5 \0 K: e: n
% r) Z; I& r- `1 F2 }- |5 Y! a( V7 `8 P: n" N) ~0 b$ w, n4 A
目前HLA库主要就是这种方式,例如来看看GPIO读取数据的API(HAL_GPIO_ReadPin)实现,主要就是根据引脚序号(0~15)来读取IDR寄存器内对应数据位的数值。
8 h. f2 b9 w3 |; D* y$ L3 g" E
' Y* y9 U$ o, V3 j- y
& e7 S3 T; D! }+ d6 r, A
0 y1 c+ h6 N1 ] GPIO分组引脚地址在HLA宏定义数值如下。
6 o, P5 _: w2 D6 P8 T; g7 {( K- #define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
; C' F: m/ q3 X - #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */: r! O3 x& f( c; z
- #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */& ~( Y; A) c2 z9 A/ s3 D" I4 {
- #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */, C7 Z9 K% b" q$ a6 |+ s
- #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
4 R, [1 R; a* U1 c* G2 m) W/ ]5 B - #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
$ B6 U! {. T, b! g" x0 H6 q9 p! F9 w - #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */; G# n2 L& d4 H9 d& H3 U
- #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
, q8 h8 `0 Y* [, ^4 p( F$ n - #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */* x4 U9 [6 F% x- q" @
- #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
7 t7 h" M7 |; `, A - #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
7 ?# w/ v5 M; j - #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
9 _7 t5 I, f0 y& A - #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
8 } D8 F: T4 |: {( ]: D$ a$ Z# { - #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
/ z4 g$ t/ L# P6 c& C7 H' y x - #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */# g9 }; P; T6 Z0 ]7 A8 K
- #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */$ p- y* a1 @4 N2 l) A# a6 T- a
- #define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
复制代码 2 V. U1 t0 N; L$ K+ b' D. _8 A/ R- D
而GPIO各个分组的IDR寄存器为32bit宽度的数据,目前只用到低16位字段。$ Y8 `- u; Z" j9 ]; w
- typedef struct% ~9 M# c. g B. ~
- {
5 o6 h& E$ h# Y4 H* m - __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
}7 `" a \, ~ m - __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */$ a7 w ~1 P( U# D, N& r
- __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
: @, W4 Y8 _6 m5 D' M; E - __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
# ?# f6 y: F: D: M# |3 o/ g. j - __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
6 t b$ z t5 N5 N7 q1 { - __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
: Z7 z' ~! Y8 \" L - __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */- q7 B) H5 ^6 @: \
- __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */! |( V; G9 N" E+ t( \
- __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
6 U) i' q. w$ e6 X - __IO uint32_t BRR; /*!< GPIO Bit Reset register, Address offset: 0x28 */% M; | U& b+ l) L& s/ N
-
/ C" Z) x! w. I. Z9 l' B0 D- C - } GPIO_TypeDef;
复制代码 1 P4 g7 Y Y; Z/ M, B9 m
第三种引脚标记其实就是在第二种方式基础上,为引脚设置别名,在cubeMX设置引脚mode后,会开启该引脚,在引脚参数设置列表可以设置该引脚标识名,例如下图,设置PE3(P2)为输出模式(GPIO_Output),的别名为LED1。- K2 t' }: l" B" p- h) x
: G/ F/ Z+ m( J9 [& \! e- O# U2 v
1 g* |8 Q; ?0 D0 `( L& S8 ?
8 d- U3 K7 L6 X1 {, q$ f 那么在生成输出代码时,会通过#define实现标识名替换。- ?0 ]: ]# J2 v: K2 h- D6 k
m P2 y$ K: F0 u
2 p9 h- H3 V, C7 n/ ]
. G+ B9 D, f/ d6 `6 Y! a8 E4 d* `) o t& W/ t/ Q- [" }
1.4 GPIO原理框图及CubeMX配置. @0 G5 K: x, I# H/ G
进一步,再看看GPIO结构原理图,它支持4种GPIO输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种GPIO输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。因此才能通过多种输入输出模式组合来实现更复杂的其他外设功能。
! z* z( V+ s: Z* L+ k, ?5 q2 D4 `
; V# {. ~: E& ?0 n p
, W8 D, X( n7 r' ?6 Y" i) ]
" z, B7 @4 P5 M( ^: B7 b& [ 在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输入模式以及其他参数设置。
- L4 y3 i9 U2 @7 C! M2 x- T
% G+ M: a v5 h9 ^2 n
: k$ q1 R; P" ]1 n( U
8 `" [* G8 T, @( n0 ?0 G) }* Z, } 同样地,在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输出模式以及其他参数设置。: R# A9 L, Z5 L6 Y3 c C
* | s% m! r4 I( R4 G( F% |: R
$ W) g9 y* Q7 F; ^0 y( Y0 y8 `. Y( ]
3 r1 f$ l4 g* y( h0 a 另外各种基于GPIO组合实现的外设,也可在GPIO栏的各个外设页面下,单独设置各个引脚的GPIO输入或输出模式。
1 O9 D/ J! A1 O8 ~* Z. P- h
9 p7 [- c' K+ ~) U, @: O
) m5 W3 z9 H( N
$ P5 q4 `) a4 Y2 s8 e0 q
二、GPIO的HLA源码分析
$ y3 V$ b& Y9 j: @$ B 2.1 HLA库GPIO初始化
/ v) Q% ]* d' D, N2 ?+ u, o 通过cubeMX生成输出源码,如果配置GPIO引脚功能,则cubeMX生成代码时,设置了为每个外设生成独立.h/.c文件时,会在Core源码目录下的Inc及Src目录,分别生成gpio.h和gpio.c驱动文件,否则将会GPOI相关输出函数放置main.h和main.c中。
/ l- w& U& E$ @" ]# i! x5 r4 W' q' z M/ c9 n" @( X
在gpio.c文件中,主要定义了MX_GPIO_Init函数,该主要做三件事情,一是启动各个GPIO分组寄存器的时钟,二是对于输出GPIO引脚实现初始值写入,三是将在cubeMX上配置引脚参数写入到参数缓存区域,并依据缓存参数,调用HLA库的HAL_GPIO_Init来实现真正的初始化设定。如下面代码所示。+ p8 D. B( r2 ^* R" ~6 A! w3 i% r
$ K* \ j& y: q# I+ A
- //main.h0 \- M p4 v: q4 H& R9 [
- /* Private defines -----------------------------------------------------------*/
( w% B6 Z z8 e! T - #define LED1_Pin GPIO_PIN_33 a$ {7 j7 C a1 @7 n
- #define LED1_GPIO_Port GPIOE
, _- x0 O& d% i: M# P& d: ` - #define KEY2_Pin GPIO_PIN_10
3 X3 X: j/ P: V1 z0 y* o. s% G - #define KEY2_GPIO_Port GPIOE
, J7 w) p0 z( l A1 } - #define KEY0_Pin GPIO_PIN_11
e7 T3 U3 f# V% x' z5 E - #define KEY0_GPIO_Port GPIOE
5 l: Y" B3 x8 U% j$ O - #define KEY1_Pin GPIO_PIN_14
$ F$ ~ V* C( o7 R4 F$ I) K( X! L - #define KEY1_GPIO_Port GPIOE
* H8 p" Z/ i" t - #define LED2_Pin GPIO_PIN_15
3 |8 N( R7 R1 H# [* B1 G' L4 R; l* g - #define LED2_GPIO_Port GPIOD+ i; B+ i7 Q J1 O( h8 e! e3 w* g
- #define LED0_Pin GPIO_PIN_6- {% w, e5 [! Y! {4 Q( N
- #define LED0_GPIO_Port GPIOB
" q) k$ ]% T* i. s1 _0 D0 h: t - /* USER CODE BEGIN Private defines */% w8 n) ^5 q. _: Z
- //gpio.c
; O5 W" w% j& k0 o, n: l! w# O0 a - void MX_GPIO_Init(void)6 h* Y- {0 _$ P) w3 l. _
- {
/ a5 K5 }9 f' {$ n9 c8 Z -
% \8 _ n" e, w7 r3 e( A6 Q - GPIO_InitTypeDef GPIO_InitStruct = {0};) j1 |& e' U" v6 t5 e9 ]' x
- / X2 H. Y) \% |8 e. e
- /* GPIO Ports Clock Enable */
# b2 p/ p9 y3 C. f7 g: a' {. C* k - __HAL_RCC_GPIOE_CLK_ENABLE();
. f3 ]0 Y7 z y, e" f - __HAL_RCC_GPIOC_CLK_ENABLE();
7 g+ c; j0 V" L7 `2 v2 H - __HAL_RCC_GPIOH_CLK_ENABLE();& h2 i4 ], Y& s7 U: i# ]' C, I
- __HAL_RCC_GPIOB_CLK_ENABLE();
2 b& x. J3 N% X8 G - __HAL_RCC_GPIOD_CLK_ENABLE();
0 o- C9 S8 D( m. _7 l- s - __HAL_RCC_GPIOA_CLK_ENABLE();% S X: c C& t/ K9 C, D+ {
-
6 [7 e6 J$ u5 R. X* |$ Y2 v - /*Configure GPIO pin Output Level */
4 ?7 @; F T4 K# n' V% I' J, T6 C - HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);. _$ a* |2 Z) [3 y$ K! e+ j
-
" u1 `* W/ I- @8 v - /*Configure GPIO pin Output Level */
+ \ W( h/ z/ a0 J, K - HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);4 X2 F2 w; K' k0 u1 e; p
- 4 d7 E# n8 U: ?" a- f3 ~' h1 `: t
- /*Configure GPIO pin Output Level */
+ N$ e. |3 I4 d1 P; M, _0 O. h - HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);0 w$ V/ }! ]% k# y. M
- % O% L/ }* q4 ~- k% |
- /*Configure GPIO pin : PtPin */$ P; A. h0 W+ @' c& b
- GPIO_InitStruct.Pin = LED1_Pin;
& X0 D( }' T4 {/ U/ w+ m4 H& G - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;, }# C k' p9 A6 [" b6 [$ A
- GPIO_InitStruct.Pull = GPIO_NOPULL;
3 H2 Y: f; ~+ m4 s* H4 ?: M - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/ n, n( B5 R9 Y3 y" I8 S ]2 K( ` - HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);
! v. B9 h; ^7 ~: M - . k- C) b4 e# h. V- ]' c
- /*Configure GPIO pins : PEPin PEPin PEPin */6 W( B, `$ }% D
- GPIO_InitStruct.Pin = KEY2_Pin|KEY0_Pin|KEY1_Pin;
! |: I' c8 F- S - GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
! G! a4 ?5 X) ~" ~9 X! Q' P - GPIO_InitStruct.Pull = GPIO_NOPULL;
& Q; J, I- u0 ^: D3 A: o0 T - HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);8 W, S$ V7 B7 B5 G7 ^$ l
-
& l+ H/ q5 {/ s: I+ v - /*Configure GPIO pin : PtPin */# R" k. ^; o* h- _5 `3 |# v% d
- GPIO_InitStruct.Pin = LED2_Pin;
& y5 I& l6 H9 Y0 ]( l - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
* z3 U: r/ p4 E: B3 Z& R - GPIO_InitStruct.Pull = GPIO_NOPULL;
9 v1 q# t, \3 n% P/ g. m - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
. c+ G6 a5 b: X# ~( w/ V - HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct); k& |7 X; T3 T8 n' i
- ; }8 B/ H: P1 \) a
- /*Configure GPIO pin : PtPin */) m$ N- T; T5 u: U9 s
- GPIO_InitStruct.Pin = LED0_Pin;
5 _' F E8 j) ~- Y% E, x; K! i: r8 J5 M6 _5 t - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
+ |* M& `* a. N - GPIO_InitStruct.Pull = GPIO_NOPULL;6 P i/ g2 \* n8 w
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;2 s+ o5 C; z, \1 ^( r) @( j2 B
- HAL_GPIO_Init(LED0_GPIO_Port, &GPIO_InitStruct);
; z& P3 G2 r3 q# ^, m -
7 u2 J2 U: v) K8 e1 f& Y! V - }
复制代码 , O3 H1 Q7 Z$ A& z: m
在stm32l4xx_hal_gpio.c源文件中定义了HAL_GPIO_Init函数,它做以下事情:1)诊断先前缓存配置参数是否合规,2)再将缓存配置参数写入到GPIO的寄存器内,完成GPIO的初始化设定(GPIO 的Mode、Pull、Analog、Alternate、Direction、EXTI等)。0 j; A: d I5 U
8 E$ I# P# c K B3 Z+ j( x, y
7 P( ?3 v! @$ Q8 M& H
! m; D' W; q# s0 J1 ~( I
2.2 GPIO的开发应用接口API简述
5 s1 T) {9 i& B7 [6 V 除了提供GPIO初始化API外,STM32的HLA针对GPIO还外开发这提供如下API:
/ ]8 \- B( u! f/ Z$ m8 H' a1 |; C3 h1 X8 K) K
' O) o; J; s2 [! d' ^! h
y9 t$ O4 h$ L, _4 a
GPIO对数据的读取主要是HAL_GPIO_ReadPin函数,写入主要是HAL_GPIO_WritePin、HAL_GPIO_TogglePin函数,其参数都是GPIO_TypeDef* GPIOx和 uint16_t GPIO_Pin,前者就是前面参数的分组如GPIOA、GPIOB等,后者就是Pin0~Pin15,例如GPIO_PIN_3。; t7 N' `) o8 z
( m% }. W! |5 N4 _. N! P 关于外部中断回调函数HAL_GPIO_EXTI_Callback,该函数是弱函数
/ E3 m& t: u" F8 X# m7 N1 k
. \. x9 I) |4 T. ]9 Y
4 V" N) m. P' U7 x' H6 c; l* m; U4 q7 z3 F+ B* W; E5 x* u. d
若需要使用该函数,使其生效,首先需要在cubeMX配置界面,将GPIO引脚调整为GPIO_EXTI类型. {+ v: T* n( h/ u
* U1 d. \6 X3 x9 Q5 w
1 c% ^3 G1 p4 e* B' ]8 Q6 N( J/ ]( d$ x$ h. _
并开启该引脚的中断支持0 a; [# z l1 s+ P2 A" R
( ~2 ^) G6 q4 [
8 M+ H+ v3 R0 W! |, Q
" c% c9 Z0 P2 d# U. v- W 就可以重新实现stm32l4xx_hal_gpio.c文件中的HAL_GPIO_EXTI_Callback函数来实现GPIO外部中断回调功能,例如设置如下,则实现按键KEY0按下异常,触发回调函数HAL_GPIO_EXTI_Callback调用,从而实现LED0灯状态反转(点亮或熄灭):% y$ o. k W6 _
- /* USER CODE BEGIN 0 */
$ l: t0 x8 x' x+ f7 z - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)3 _. w x1 w, M' s$ y; M6 L
- {2 x8 x }, b% v7 B9 p# q
- if(GPIO_Pin==KEY0_Pin); Y, k3 @( {/ M# A' X# |/ o! Z5 S* N
- {8 k' d6 W3 a2 z
- static uint32_t key0_count = 0;, k+ I% |* `& X. D$ N
- printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);
% a" a! C8 b$ C0 ]8 s$ W/ u$ _ - Toggle_led0();//LED0等状态反转一次
: \' j* b) w8 F* @! s - }
1 s, N5 B! Y; ? - }
4 e0 @4 ]4 k2 t0 f H5 \ - /* USER CODE END 0 */
复制代码 8 n9 {+ o+ U6 y! ~8 \1 G
同样地对于外部中断请求函数HAL_GPIO_EXTI_IRQHandler也可以类似地进行调整实现GPIO外部中断触发器调用,从而实现我们需要的业务应用。 L9 j1 W# m. x- z
! G0 H3 G8 W$ w
GPIO的HLA库中,HAL_GPIO_LockPin函数,主要是操作lock寄存器数值的,lock寄存器会针对GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR等GPIO寄存器配置对应的状态标记,而其他寄存器操作时,会先去lock寄存器读取其使用状态来确定是否能进行相关操作,从而确保防止冲突及达成一致性。: ^( H2 L- m( B9 q9 M
————————————————+ s7 {8 j- `1 z3 |" ^# I
版权声明:py_free-物联智能
3 b1 a( Z- r* z( w, C如有侵权请联系删除
7 f6 P4 M% ]% q3 ? |