一、GPIO介绍) X( K" K3 a- h
1.1 GPIO 简述
* [5 C- u( [! v0 o9 X GPIO(General purpose input/output,通用型输入输出),一个引脚可以用于输入、输出或其他特殊功能,PIN脚依现实需要可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),并多个GPIO一起组合可实现诸如UART、SPI等外设功能。- D D7 `' ~& {4 Z
* a% S& z/ ~' \3 c1 Y9 S4 ?5 t
GPIO是通过寄存器操作来实现电平信号输入输出的,对于输入,通过读取输入数据寄存器(IDR,Input Data Register)来确定引脚电位的高低;对于输出,通过写入输出数据寄存器(ODR,Output Data Register)来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。GPIO的输出通常可以输出0/1信号,用来实现如LED灯、继电器、蜂鸣器等控制,而GPIO的输入可识别0/1信号,用来实现开关、按键等等动作或状态判定。; u! y0 k& Q! b4 F8 s
7 j; x+ K/ M; l K4 \- Z
; H/ h$ i4 v, ]4 e3 s
5 e# k$ [8 S$ S0 X
* l" U2 j1 _: Q2 K, K 1.2 STM32 GPIO寄存器" h: \7 f' ^) L; G2 v# y
STM32中GPIO的寄存器是MCU分配的一个连续存储区域,按存储地址划分出不同功能寄存器,如下图所示,有Port mode寄存器、Output Type寄存器等等。
( Z; }, y7 ^1 y" s7 p9 i# j( Z7 K
: Q" ?3 V R6 ?7 _% j7 B2 U0 e% J% q% O S% o. ~0 d
$ w, T/ s9 x; a7 ]0 p5 j2 v0 e. x( L- l1 p h; G' v" b) k; s# a
在HLA库中,这些寄存器的数值(宏定义)通常会放在/Drivers/CMSIS/Device/ST/STM32L4xx/Include/stm32XXX.h头文件中,供开发者调用,例如STM32L496VGTx的开发板,该头文件具体名是stm32l496xx.h,自8977行起到10008行,具是按上述表宏定义了GPIO各寄存器的各个数据位可设置数据值(偏移(POS)及相应数值)。 m' T9 n: j/ B* F) }( E
/ r4 }" c" P: y
" a4 k) q. z' [: M. |1 f$ d6 ]& D1 g7 [8 N3 j; `
1.3 GPIO引脚与标记
" I* I, s4 W+ d. w5 x( D2 W 在一个STM32 MCU芯片中,所有引脚本质都是GPIO引脚,只是鉴于全GPIO引脚开发者使用起来比较不方便,于是才通过各个GPIO组合形成针对各个外设的IO接口。在CubeMX中,以STM32L496VGTx芯片为例,可以直观看到该芯片有100个引脚:LQFP100,LQFP是封装,100指的是引脚数。
3 f# j- f! e0 x; B
: ?+ z5 V8 n9 p e
' c1 }# {4 J0 Q
: q- ^; A b( M; s2 a: ?5 N1 q STM32的MCU标记一般有两种,直插IC以半圆条口为标记,贴片IC以小圆点为标记,也有缺口及小点都有标记的。通常将标记圆点或缺口正面朝左上角,左上角的下方脚为第一脚,依次围绕芯片中心逆时针计数,在旋转360度以后,左上角右方角为最后一脚。如上图所示,STM32L496VGTx引脚序号如蓝色字体及箭头所示标记。STM32的MCU的引脚标记有三种方式:
) `, n% G, n, ^6 S: B; J! D
# R5 Y2 x2 L( E; m 一种是基于上面所述的100个序号排序P1、Pn等,这种标记目前一般是在直接操作寄存器地址或使用标准库中使用,目前HLA库、LL库已经不提供直接地址偏移方式的API了,而是给没一组分组提供了独立寄存器来处理该组引脚;
9 I! T# ~$ Z$ @" G5 E- { c
' m) r$ T! ^0 g( p ~ 第二种是标准库或HLA库宏定义了引脚分组,一般为A、B、C、...,按大写字母次序网线,目前支持到16组类别,每组为PX0~PX15,其标记就是分组名+0~15序号,例如GPIOB+GPIO_PIN_3。
. b7 L% J- j/ f
% @0 X2 B9 i; s5 D* q" ` 注意,目前STM32的MCU设计是最大16组类别,支持到256引脚(16*16),通过STM32命名规范可以得知,STM32最做引脚支持到256个,但一般会少于引脚数/16的组数,因为不少引脚在芯片设计时就特别支持了其引脚标识,如VSS、VDD等。另外也不是每组必须0~15序号全用完,分组主要是方便设计及使用时进行引脚区分而已。
) r6 Q. K$ s1 W" _' t5 g3 Y$ x, `6 u7 j$ y
' B. G6 \4 N/ s! ?
7 s/ Z1 f, B* T6 H! E* ]0 r5 Q 目前HLA库主要就是这种方式,例如来看看GPIO读取数据的API(HAL_GPIO_ReadPin)实现,主要就是根据引脚序号(0~15)来读取IDR寄存器内对应数据位的数值。
% A; c7 T" ?# F3 k% g7 S' y9 A i( v8 Q W6 g
4 I% k2 a8 V! T# n- D: d1 C( X7 _
! O3 c' A$ X: t% r# }( J1 ]% h$ ?
GPIO分组引脚地址在HLA宏定义数值如下。
- \3 H1 o h6 {% b$ w% b) @- #define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
- S% W6 ~, H/ ]6 m+ [' n - #define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
9 b: ~, x- q9 L( O1 D - #define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */: h* {" r/ ^' I0 @( ]- d
- #define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */. @$ r- f* S' c9 D( Z \6 C" S
- #define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
8 r+ s6 W7 j0 V; } - #define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
: d1 K2 b% z+ \ - #define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
0 \8 }7 a q/ `6 G' E' K - #define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */) J% f9 P* m, X$ v2 }
- #define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
; f L8 m. N1 t' H7 E; l R5 O - #define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
/ L. F0 Z b% D! p - #define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
% e+ _* k) w/ X3 F Y/ d* @ - #define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */: [- N2 C% ~: M0 T
- #define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
, q, _" T. G% m" d, w) j, p( S& @% m - #define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */0 s7 ~& I, M" t
- #define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */1 H9 H# ^, a F# ]& q! u
- #define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */! s6 s3 @+ y$ q3 }4 _+ E( i
- #define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
复制代码 6 |1 X( k" v1 u4 A/ f
而GPIO各个分组的IDR寄存器为32bit宽度的数据,目前只用到低16位字段。
3 R* [) a q* c' L- q- typedef struct3 {1 P) f* B: |) @! |9 E+ |
- {0 f# A) b7 u3 u& P; i
- __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
8 {6 j9 k; B4 h3 y - __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
# s0 a! s/ M9 \4 O1 e/ H - __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */' K0 O' e! ?. B
- __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */# _- P8 c: @2 {/ u
- __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
: y6 a8 S1 H( c- J+ \7 i - __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
- U3 A! u0 `8 c1 H - __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */9 L( l- q7 d6 s7 u R! H) Y
- __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
4 L5 r+ q0 \/ o. |( h3 s4 K - __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */) s: w/ O& H8 g
- __IO uint32_t BRR; /*!< GPIO Bit Reset register, Address offset: 0x28 */
( ?. Z8 d& U: t% R1 b; n" o - 4 k9 r) f6 ~# u
- } GPIO_TypeDef;
复制代码 3 F; V3 B B" r, k. G
第三种引脚标记其实就是在第二种方式基础上,为引脚设置别名,在cubeMX设置引脚mode后,会开启该引脚,在引脚参数设置列表可以设置该引脚标识名,例如下图,设置PE3(P2)为输出模式(GPIO_Output),的别名为LED1。& d; I" m2 O: d5 h- E$ x3 d
|& {" @0 {& P% P/ p( n# R, \
3 C7 r/ C/ S, X6 Z7 S- O+ U: s1 j6 d( a, [1 S. ]9 N" j0 `
那么在生成输出代码时,会通过#define实现标识名替换。
, e' O4 U: N" ]* n) ]* w' R' { W ^/ ?0 J2 v
# I- m+ W' m& z; O, i
2 k6 k$ T& E: t4 F/ R
4 @0 ~: I3 d% o8 Q& z- T. [& y* [
1.4 GPIO原理框图及CubeMX配置
! M8 u4 b- X; S7 D7 {; U) X$ O. n 进一步,再看看GPIO结构原理图,它支持4种GPIO输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种GPIO输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)。因此才能通过多种输入输出模式组合来实现更复杂的其他外设功能。
" x/ T$ ~' R! {, L! A
. C5 I0 L9 Z, a! d
' Z$ b) K, M7 `6 }$ P. k# g7 Q' k2 e' R( V$ N& u2 X4 \
在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输入模式以及其他参数设置。
; ]0 M6 }+ L! s4 g. i) s; W, A& s: S" ]
5 ~2 M2 A3 n# a$ v- b8 E+ U) {' g% a Z) @' U: x1 C) q1 G0 E
同样地,在cubeMX中在GPIO配置界面中,可以配置GPIO引脚的GPIO输出模式以及其他参数设置。
4 u3 ?6 B/ k) U2 X) I
% h. f( H8 n4 g/ N
* |' c% s) K& _9 ^" B4 ]: b5 I5 o/ [5 V+ b1 E
另外各种基于GPIO组合实现的外设,也可在GPIO栏的各个外设页面下,单独设置各个引脚的GPIO输入或输出模式。# g9 ^0 d. M5 A, c# Z& m# n! n _, z
- d% S5 S/ e9 L# x5 J
+ T4 Q2 I5 c) G8 O) Q. C) K8 s0 o9 q% Z* w. n, [% R
二、GPIO的HLA源码分析. a6 [: l) o) r9 G' ]$ ]% E
2.1 HLA库GPIO初始化9 b; S6 s- B( C& ?. z) g& r" L
通过cubeMX生成输出源码,如果配置GPIO引脚功能,则cubeMX生成代码时,设置了为每个外设生成独立.h/.c文件时,会在Core源码目录下的Inc及Src目录,分别生成gpio.h和gpio.c驱动文件,否则将会GPOI相关输出函数放置main.h和main.c中。' N3 D- G% j( w- e" M
# C. @6 a6 F9 P) Q! H: T$ _ 在gpio.c文件中,主要定义了MX_GPIO_Init函数,该主要做三件事情,一是启动各个GPIO分组寄存器的时钟,二是对于输出GPIO引脚实现初始值写入,三是将在cubeMX上配置引脚参数写入到参数缓存区域,并依据缓存参数,调用HLA库的HAL_GPIO_Init来实现真正的初始化设定。如下面代码所示。7 q% f0 y4 L+ t" k0 x9 L7 q
3 p n+ g `2 @/ T" c
- //main.h! ]) n0 l, A' e8 S% F1 U
- /* Private defines -----------------------------------------------------------*/# A' }* |) g: B3 r3 p8 x4 z0 {
- #define LED1_Pin GPIO_PIN_3; L$ _0 a2 c) J& l w
- #define LED1_GPIO_Port GPIOE- A: T2 z( P6 p; h& j
- #define KEY2_Pin GPIO_PIN_10
' `2 i* U+ i. u6 u/ F0 b7 r& X - #define KEY2_GPIO_Port GPIOE
0 K2 V) s- a- j6 ]9 @' F - #define KEY0_Pin GPIO_PIN_116 ?$ S& s/ T! c9 j: Q6 r
- #define KEY0_GPIO_Port GPIOE
3 f5 U9 u- j9 M6 C' c - #define KEY1_Pin GPIO_PIN_14
& r k2 D$ ~3 m7 } - #define KEY1_GPIO_Port GPIOE
! [: C6 o, S/ [ - #define LED2_Pin GPIO_PIN_15; ^2 E$ a& P- ]4 Y
- #define LED2_GPIO_Port GPIOD
3 R& e X+ x: z& a! D, q( k - #define LED0_Pin GPIO_PIN_64 T. N; c' ?6 Z# m
- #define LED0_GPIO_Port GPIOB
$ Y% |; u. D1 i8 A: G - /* USER CODE BEGIN Private defines */& p8 b1 f1 l) @/ M; ?
- //gpio.c& P/ |% I/ H( N. h% }
- void MX_GPIO_Init(void)% Y. Z$ S+ v) h; i0 p
- {
4 _+ ?' ~" F' r% G2 _ - . ^% p5 _( r7 ]) _! I: {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
8 { B" D5 Q) h2 ~, s/ D - - L2 g' m$ N; k, o. T% R
- /* GPIO Ports Clock Enable */
+ }0 F7 d, @' ^: c X$ I' U, i. P - __HAL_RCC_GPIOE_CLK_ENABLE(); O# D* V, j# O/ j8 w2 H. [
- __HAL_RCC_GPIOC_CLK_ENABLE();& y. W/ Q. L0 h8 X- [
- __HAL_RCC_GPIOH_CLK_ENABLE();- f( t' t* ?5 M D4 Q! @- M1 F1 r
- __HAL_RCC_GPIOB_CLK_ENABLE();
: ]0 f2 P" d4 }+ }) h - __HAL_RCC_GPIOD_CLK_ENABLE();8 `! Z5 A8 a7 r- J! |" i
- __HAL_RCC_GPIOA_CLK_ENABLE();' D& g+ D) G/ F
-
8 }$ J- Y3 }4 d. ?! q) ~) h - /*Configure GPIO pin Output Level */) W, b4 T% P4 s! x
- HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);0 j$ g3 r$ h* _( E. U& Y1 u @0 y3 q
-
; E2 I, R2 W6 ]1 x% ]9 f8 @ - /*Configure GPIO pin Output Level */
0 K3 }7 p. W. j0 a - HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);6 v( c6 c* U) F: t( {: t
-
6 N, k2 n! p3 w1 s - /*Configure GPIO pin Output Level */
/ h" N e. P5 z0 c5 ] - HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
( E( b/ i) T/ G5 d - ( O# ^: r4 _( W8 F3 A! M: @9 o
- /*Configure GPIO pin : PtPin */
4 O5 ? `% r. U" M7 Y. w) L+ J3 L - GPIO_InitStruct.Pin = LED1_Pin;
/ l# r/ Y n2 r- f1 C0 r - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
+ |, V3 Z4 @: Z6 V7 |7 G, b - GPIO_InitStruct.Pull = GPIO_NOPULL;6 Q) N1 c( r9 W4 P, {: U( V- P/ T
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
: a6 x9 R6 ]4 v4 a& a/ q7 D - HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);
3 t. }9 l0 i' C* T: k -
& y5 P6 @% D2 r: N - /*Configure GPIO pins : PEPin PEPin PEPin */: [! G4 z; z; c4 s; N1 o4 O
- GPIO_InitStruct.Pin = KEY2_Pin|KEY0_Pin|KEY1_Pin;6 z& p J( I C
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+ k! T: `- }$ L+ B0 M - GPIO_InitStruct.Pull = GPIO_NOPULL;
4 y/ r& X E; K) c3 S" e F* R - HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
9 @: h2 o6 m! I/ f( f" C% R. T3 n -
' h( x* J& h5 E4 X1 D! z - /*Configure GPIO pin : PtPin */ x4 d$ Z: ^3 J$ h
- GPIO_InitStruct.Pin = LED2_Pin;
9 P3 Y- c7 {$ d7 g) w - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
" }+ s* i: R& d( P3 Q% @: E6 ` - GPIO_InitStruct.Pull = GPIO_NOPULL;
- d! n9 A8 k' f$ G. M( b - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;# i0 q' V8 @; x/ `) F) E
- HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct);
% ` k0 ~( p5 u8 v: x - ' C- F- o B1 u( _5 u) W
- /*Configure GPIO pin : PtPin */
5 v. Y0 U2 J3 k" B# `. ~, ^8 t - GPIO_InitStruct.Pin = LED0_Pin;
! H) b: R9 t, ]) M6 b* c( o - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;7 Y4 w2 O# m$ b' i4 A
- GPIO_InitStruct.Pull = GPIO_NOPULL;
" g" V- `7 z: E6 t - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;1 _# l/ H( G. V2 ]
- HAL_GPIO_Init(LED0_GPIO_Port, &GPIO_InitStruct);- b# {! [5 y+ w+ q4 @6 ?
- 1 h" y$ n; O! D$ T/ R+ `
- }
复制代码 " b; ] v: i+ G5 |# l1 V& d
在stm32l4xx_hal_gpio.c源文件中定义了HAL_GPIO_Init函数,它做以下事情:1)诊断先前缓存配置参数是否合规,2)再将缓存配置参数写入到GPIO的寄存器内,完成GPIO的初始化设定(GPIO 的Mode、Pull、Analog、Alternate、Direction、EXTI等)。
: z1 D$ k7 V& T1 ?( F/ {( _/ S8 b4 v" X8 y" A
, x( q- V3 \& Y! Y: Y/ W" o
& q2 B0 P9 _0 c8 d7 b6 _ 2.2 GPIO的开发应用接口API简述
$ }- q3 d% J" y, O7 b9 Z0 F. h 除了提供GPIO初始化API外,STM32的HLA针对GPIO还外开发这提供如下API:
2 z7 \4 ]8 g# Z+ O1 x9 d; Y- N. n# K* Y+ a! v
& P V/ q6 P C$ a, y9 |. D
6 r2 k( T! D0 ~ GPIO对数据的读取主要是HAL_GPIO_ReadPin函数,写入主要是HAL_GPIO_WritePin、HAL_GPIO_TogglePin函数,其参数都是GPIO_TypeDef* GPIOx和 uint16_t GPIO_Pin,前者就是前面参数的分组如GPIOA、GPIOB等,后者就是Pin0~Pin15,例如GPIO_PIN_3。
& ?8 v7 ^5 _8 v* s6 W8 |$ p& a! ~% s6 L+ s
关于外部中断回调函数HAL_GPIO_EXTI_Callback,该函数是弱函数
/ |- v" i2 F5 B; W/ Z& m# o5 ?; `; H
8 M8 ]0 R( k( G4 N' l8 m1 Y& p
' q5 b; q' j% _% `# V3 F- E6 X: E/ q0 k
若需要使用该函数,使其生效,首先需要在cubeMX配置界面,将GPIO引脚调整为GPIO_EXTI类型
! a6 F o/ q* ~0 G l, f8 ^9 ]! j
; y' n H# z5 \- H6 d r- Z' u
8 @7 d7 i& w& B6 Q( x
并开启该引脚的中断支持3 Z2 z. s8 t4 T+ o7 q1 B! Q
0 [/ i0 V% H; e0 W* P
/ ^4 \6 l! H j3 _. J! Q% K5 ]/ R' w$ R$ J- B0 u# l m
就可以重新实现stm32l4xx_hal_gpio.c文件中的HAL_GPIO_EXTI_Callback函数来实现GPIO外部中断回调功能,例如设置如下,则实现按键KEY0按下异常,触发回调函数HAL_GPIO_EXTI_Callback调用,从而实现LED0灯状态反转(点亮或熄灭):
7 ^( l& ^4 Z U: i8 P1 N% u$ n- /* USER CODE BEGIN 0 */
& C; u+ e. K- o - void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
8 ?3 [' M) J: e - {
( {. o5 \' E* _+ t - if(GPIO_Pin==KEY0_Pin)
% O% q. A D9 |5 b( T" e/ l- h) O7 d - {
7 m6 U6 i+ z5 R9 }% D - static uint32_t key0_count = 0;
6 y& P: z5 x; Z7 g7 t - printf("this is %lu count for HAL_GPIO_EXTI_Callback!\r\n",key0_count++);7 Q2 |( j! n( O5 S9 M
- Toggle_led0();//LED0等状态反转一次
_) p) K9 T! D% b3 r( ? - }
- a+ R% m* q0 N+ } d, ]% w: C - }! h$ @% H# M Z
- /* USER CODE END 0 */
复制代码 + B/ O* y6 D3 K8 F; ^# H; Y! ?
同样地对于外部中断请求函数HAL_GPIO_EXTI_IRQHandler也可以类似地进行调整实现GPIO外部中断触发器调用,从而实现我们需要的业务应用。/ P/ `* b6 X* A, n; F4 n( X4 ~
. a5 j. m7 }) \5 I GPIO的HLA库中,HAL_GPIO_LockPin函数,主要是操作lock寄存器数值的,lock寄存器会针对GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR等GPIO寄存器配置对应的状态标记,而其他寄存器操作时,会先去lock寄存器读取其使用状态来确定是否能进行相关操作,从而确保防止冲突及达成一致性。
$ Y: @$ }- `1 j2 p% W3 D————————————————# F" a8 B' v: ], s4 a
版权声明:py_free-物联智能5 A) R' u# T: m1 |) i2 [: d
如有侵权请联系删除3 _. M: f6 w- C% o
|