GPIO功能描述  

  STM32F103ZET6共有112个IO可用于GPIO(部分IO也可用作其他功能,如USART、IIC等),每16个作为一组,共分为7组,分别记做GPIOA、GPIOB…GPIOG,每组中的各个GPIO分别记做Px0~Px15,如对于GPIOA,对应PA0、PA1…PA15,把每组GPIO的Px0-Px7称作GPIO的低8位端口,Px8-Px15称作高8位端口。
每一组GPIO由一套共7个寄存器控制,分别是:

  • GPIOx_CRL:配置(低8位端口)
  • GPIOx_CRH:配置(高8位端口)
  • GPIOx_IDR:输入数据
  • GPIOx_ODR:输出数据或输入上下拉选择
  • GPIOx_BSRR:置位/复位(高16bit复位,低16bit置位)(复位即清零)
  • GPIOx_BRR:复位
  • GPIOx_LCKR:锁定

详细的寄存器地址映像如下图:

其中CRL和CRH寄存器中的CNF[1:0]和MODE[1:0]对应的意义如下表:

复位期间和刚复位后,复用功能未开启,IO端口被配置成浮空输入模式。
所有端口都有外部中断功能。
在输入模式下由ODR寄存器控制上拉还是下拉。

复用功能:使用复用功能前,必须先配置CRL/CRH寄存器(STM32参考手册8.1.11节对各个外设的IO配置有详细说明)。
如果把端口配置成复用输出功能,则引脚和输出寄存器断开,并和片上外设的输出信号连接。
如果软件把一个GPIO脚配置成复用输出功能,但是外设没有被激活,它的输出将不确定。

IO复用功能重映射

同一个外设可以被映射到不同的端口上去,比如USART1的TX/RX引脚,即可以映射到PA9、PA10,也可以映射到PB6、PB7。此功能通过AFIO寄存器组来配置,可以重映射的接口包括:

  • LSE振动器引脚OSC32_IN/OSC32_OUT
  • 外部振荡器引脚OSC_IN/OSC_OUT
  • CAN1、CAN2
  • JTAG/SWD
  • ADC1、ADC2的外部触发注入转换、外部触发规则转换引脚
  • TIM5_CH1-CH4、TIM4_CH1-CH4、TIM2_CH1~CH4、TIM1_…
  • USART3、USART2、USART1
  • I2C1
  • SPI1、SPI3

AFIO寄存器如下图:

配置注意事项:

  • 配置STM32任何外设的时候,都要先使能该外设的时钟。
  • 可使用位带操作单独控制某个bit
  • STM32F1单个IO最大可以提供25mA电流。LED驱动电流在10~50mA,电流越大寿命越短,使用推挽输出;板上蜂鸣器驱动电流30mA,使用推挽输出,三极管扩流。
  • IO口作为输入时,如果外部没有上下拉电阻,则需要在STM32F1内部设置上下拉

实战

我所使用的开发板上有2个LED,3个按键分别是:

  • LED0接PB5,低电平亮
  • LED1接PE5,低电平亮
  • KEY_UP接PA0,按下高电平(可用作唤醒功能)
  • KEY_0接PE4,按下低电平
  • KEY_1接PE3,按下低电平
  • KEY_2接PE2,按下低电平

计划实现的功能(功能之间互斥):

  1. LED闪烁
    LED0每隔500ms翻转一次
  2. 按键控制LED0和LED1
    按下并松开KEY_0,翻转LED0
    按下并松开KEY_1,翻转LED1
    按下并松开KEY_2,翻转LED0和LED1
  3. 按下KEY_UP, LED0和LED1亮,松开灭

代码实现:
  Cortex-M系列的位带功能特别适合这种需要操作单个bit位的场景,所以本示例中的IO口输出和检测高低电平都用位带操作实现,位带操作的详细介绍参见《Cortex-M3权威指南》的5.5节。

  共有两个位带区,0x20000000和0x40000000起始的1M字节范围,分别位于SARM和外设寄存器区,我们要操作的GPIO位于第二个位带区,但是我们可以把接口做成兼容二者。

  代码中会用到我们上一节中写的延时函数。

把LED和key的代码合在一起,放在led_key.c文件中
led_key.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef LED_KEY_H
#define LED_KEY_H
#include "common.h"

#define LED0 PBout(5)
#define LED1 PEout(5)

#define KEY_PRESSED 0
#define KEY_RELEASE 1

#define KEY_UP 0
#define KEY_0 1
#define KEY_1 2
#define KEY_2 3

void led_key_init(void);
int KeyScan(u32 key);
#endif

led_key.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "led_key.h"
#include "common.h"
#include "delay.h"

void led_key_init(void)
{
// 开启GPIOA、GPIOB和GPIOE的时钟
SETUP_BIT(RCC->APB2ENR, 2); // GPIOA时钟使能
SETUP_BIT(RCC->APB2ENR, 3); // GPIOB时钟使能
SETUP_BIT(RCC->APB2ENR, 6); // GPIOE时钟使能

// 初始化LED0(PB5)、LED1(PE5)
SET_BITS(GPIOB->CRL, 21, 20, 1); // PB5 mode
SET_BITS(GPIOB->CRL, 23, 22, 0); // PB5 推挽输出
SET_BITS(GPIOE->CRL, 21, 20, 1); // PE5 mode
SET_BITS(GPIOE->CRL, 23, 22, 0); // PE5 推挽输出

LED0 = 0;
LED1 = 0;

// 初始化- KEY_UP接PA0,KEY_0接PE4,KEY_1接PE3,KEY_2接PE2
SET_BITS(GPIOA->CRL, 1, 0, 0); // PA0输入模式
SET_BITS(GPIOA->CRL, 3, 2, 0x2); // 上下拉模式
CLR_BIT(GPIOA->ODR, 0); // 下拉
SET_BITS(GPIOE->CRL, 17, 16, 0); // PE4输入模式
SET_BITS(GPIOE->CRL, 19, 18, 0x2); // 上下拉模式
SETUP_BIT(GPIOE->ODR, 4); // 上拉
SET_BITS(GPIOE->CRL, 13, 12, 0); // PE3输入模式
SET_BITS(GPIOE->CRL, 15, 14, 0x2); // 上下拉模式
SETUP_BIT(GPIOE->ODR, 3); // 上拉
SET_BITS(GPIOE->CRL, 9, 8, 0); // PE2输入模式
SET_BITS(GPIOE->CRL, 11, 10, 0x2); // 上下拉模式
SETUP_BIT(GPIOE->ODR, 2); // 上拉
}

int CheckKeyPressed(u32 key)
{
switch (key) {
case KEY_UP:
return PAin(0)?KEY_PRESSED:KEY_RELEASE;
case KEY_0:
return PEin(4)?KEY_RELEASE:KEY_PRESSED;
case KEY_1:
return PEin(3)?KEY_RELEASE:KEY_PRESSED;
case KEY_2:
return PEin(2)?KEY_RELEASE:KEY_PRESSED;
}
return KEY_RELEASE;
}

int KeyScan(u32 key)
{
if (CheckKeyPressed(key) == KEY_PRESSED) {
delay_ms(10); // 去抖动
if (CheckKeyPressed(key) == KEY_PRESSED) {
return KEY_PRESSED;
}
}

return KEY_RELEASE;
}

main()函数中3个功能每次只能开启一个,剩下的两个要屏蔽掉
main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "common.h"
#include "led_key.h"
#include "delay.h"
#include "sys.h"

int main(void)
{
int key0_pressed = 0;
int key1_pressed = 0;
int key2_pressed = 0;
Stm32_Clock_Init();
delay_init(72);
led_key_init();

while (1) {
// 功能1 闪烁LED0
#if 0
LED0 = ~LED0;
delay_ms(500);
#endif

// 功能2 按键控制LED0和LED1
#if 0
if (KeyScan(KEY_0) == KEY_PRESSED) {
key0_pressed = 1;
} else if (key0_pressed){
key0_pressed = 0;
LED0 = ~LED0;
}

if (KeyScan(KEY_1) == KEY_PRESSED) {
key1_pressed = 1;
} else if (key1_pressed){
key1_pressed = 0;
LED1 = ~LED1;
}

if (KeyScan(KEY_2) == KEY_PRESSED) {
key2_pressed = 1;
} else if (key2_pressed){
key2_pressed = 0;
LED0 = ~LED0;
LED1 = ~LED1;
}
#endif

// 功能3 按键控制LED0和LED1,支持长按
#if 1
if (KeyScan(KEY_UP) == KEY_PRESSED) {
LED0 = 0;
LED1 = 0;
} else {
LED0 = 1;
LED1 = 1;
}
#endif

delay_ms(10);
}
}