文章目录

概要整体架构流程技术名词解释技术细节小结

GPIO简介

        GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚, STM32 芯片 的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。

        根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置成多种模式。

输入浮空输入上拉输入下拉模拟输入开漏输出推挽输出推挽复用开漏复用

        对于点亮一个LED来说,就是使得引脚具有输出功能,由STM32控制引脚输出高低电平,再把LED接入到单片机对应的GPIO引脚,那么就可以控制灯的亮灭了。

基本结构

 在这里主要说以下两点:

1.保护二极管及上、下拉电阻

        两个保护二极管最直接的作用就是防止引脚外部过高或过低的电压输入导致芯片烧毁。

 2.P-MOS 管和 N-MOS 管

        GPIO 引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。

寄存器点亮LED灯

以这个LED电路图为例,点亮PB5这个LED灯 。

1.GPIO模式

前面说了,点亮一个灯,需要将STM32对应的引脚配置成输出模式即可,但是输出模式有很多种,我们该使用哪一种呢?

配置为推挽模式时,双MOS管以轮流的方式工作。输出数据寄存器 GPIOx_ODR 可控制 I/O 输出高低电平。

开漏模式时,只有N-MOS管工作,输出数据寄存器可控制 I/O输出高阻态或低电平。

注意:高阻态,既不是高电平也不是低电平,对于单片机来说就可能无法使用,造成误判断

显而易见,对于点亮LED,我们想要控制灯亮灭,就还是配置为推挽比较适用。 

        根据上述,我们最需要的就是将GPIO配置成推挽输出,根据数据手册找到相应的寄存器端口配置低寄存器(GPIOx_CRL)和端口配置高寄存器(GPIOx_CRH)

        这两个都是用来配置GPIO的模式的,区别是低寄存器是配置0-7,高寄存器配置8-15。因为我们需要配置PB5,所以只需配置低寄存器即可。如下图

        配置PB5,就是需要将CNF5[1:0]和MODE5[1:0]设置为0001,代表的意思就是通用推挽,速度10M,我们将上述转换为程序如下:

//清空控制 PB0 的端口位

GPIOB_CRL &= ~( 0x0F<< (4*4));

// 配置 PB0 为通用推挽输出,速度为 10M

GPIOB_CRL |= (1<<4*4);

2.控制引脚输出电平

在此原理图中,输出低电平,则会点亮LED灯

        在输出模式时,对端口位设置/清除寄存器 BSRR 寄存器、端口位清除寄存器 BRR 和ODR 寄存器写入参数即可控制引脚的电平状态,其中操作 BSRR 和 BRR 最终影响的都是ODR 寄存器,然后再通过 ODR 寄存器的输出来控制 GPIO。为了一步到位,我们在这里直接操作 ODR 寄存器来控制 GPIO 的电平。

// PB5 输出 低电平

GPIOB_ODR |= (0<<4);

         经过以上操作,是不是就可以点亮LED灯了呢?当然不是,由于 STM32 的 外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。

3.开启外设时钟

        STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC( reset and clockcontrol),在数据手册第六章有详细讲解。这里我直接给出,所有的 GPIO 都挂载到 APB2 总线上,具体的时钟由 APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制。

// 开启 GPIOB 端口时钟

RCC_APB2ENR |= (1<<3);

4.点亮灯

        开启时钟,配置引脚模式,控制电平,经过这三步,我们可以控制一个 LED 了。 现在我们完整组织下用 STM32 控制一个 LED 的代码

int main(void)

{

// 开启 GPIOB 端口时钟

RCC_APB2ENR |= (1<<3);

//清空控制 PB5 的端口位

GPIOB_CRL &= ~( 0x0F<< (4*4));

// 配置 PB5 为通用推挽输出,速度为 10M

GPIOB_CRL |= (1<<4*4);

// PB5 输出 低电平

GPIOB_ODR |= (0<<4);

while(1);

}

固件库点亮LED灯

上面说了寄存器点灯,接下来就是标准库了。首先就是,为什么要用标准库来开发呢?

对于 STM32,因为外设资源丰富,带来的必然是寄存器的数量和复杂度的增加,这时 直接配置寄存器方式的缺陷就突显出来了:

开发速度慢

程序可读性差

维护复杂

这些缺陷直接影响了开发效率,程序维护成本,交流成本。库开发方式则正好弥补了这些缺陷。 

现在直接使用标准库编写。牢记步骤

使能GPIO端口时钟

初始化GPIO目标引脚为推挽输出模式

编写简单测试程序控制GPIO引脚输出高低电平

 STM32关于GPIO的固件库名为:stm32f10x_gpio.c ,内容大概就是与GPIO操作有关的程序。

首先进入对应的.h文件,找到结构体GPIO_InitTypeDef,内容如下:

typedef struct

{

uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.

This parameter can be any value of @ref GPIO_pins_define */

GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.

This parameter can be a value of @ref GPIOSpeed_TypeDef */

GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.

This parameter can be a value of @ref GPIOMode_TypeDef */

}GPIO_InitTypeDef;

这个结构体里面就包含的GPIO的引脚编号,速度,GPIO模式,我们在使用库函数的时候就直接调用这个结构,往结构体成员写入相应的值即可。

 1.配置结构体

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

 有的人就很懵,说你怎么知道这个该填写GPIO_Pin_5呢?原因是因为,你在结构体注释里面可以看到 This parameter can be any value of @ref GPIO_pins_define

意思就是说这个参数的值可以参考GPIO_pins_define,在.h文件里面找到这个对应的内容,如下:

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */

#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */

#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */

#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */

#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */

#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */

#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */

#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */

#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */

#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */

#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */

#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */

#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */

#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */

#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */

#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */

#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */

 这样是不是就直接可以看到用什么值了呢。

接下来配置GPIO模式和速度

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

同样 道理,在结构体注释里面都有说明,参考对应的值,在.h里面找到,填入相应的值即可

typedef enum

{

GPIO_Speed_10MHz = 1,

GPIO_Speed_2MHz,

GPIO_Speed_50MHz

}GPIOSpeed_TypeDef;

typedef enum

{ GPIO_Mode_AIN = 0x0,

GPIO_Mode_IN_FLOATING = 0x04,

GPIO_Mode_IPD = 0x28,

GPIO_Mode_IPU = 0x48,

GPIO_Mode_Out_OD = 0x14,

GPIO_Mode_Out_PP = 0x10,

GPIO_Mode_AF_OD = 0x1C,

GPIO_Mode_AF_PP = 0x18

}GPIOMode_TypeDef;

 这样,整个结构体成员的值就全部配置好了。但是呢,只是这样,还不能算写入成功,使用以上初始化结构体的配置,调用 GPIO_Init 函数向寄存器写入参数,完成 GPIO 的初始化

//GPIOx: where x can be (A..G) to select the GPIO peripheral.

//GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that

// * contains the configuration information for the specified GPIO peripheral.

GPIO_Init(GPIOB, &GPIO_InitStructure);

 在这个函数注释,对两个参数都有详细的说明.

2.输出高低电平

在库函数中,输出高低电平,也封装好了库

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

这两个函数,分别有两个参数,意思就是你要驱动哪个口,比如,这里的PB5,你就可以写成:

GPIO_SetBits(GPIOB,GPIO_Pin_5); //输出高电平

3.开启时钟

调用库函数 RCC_APB2PeriphClockCmd 来使能 LED 灯的 GPIO 端口时钟,在前面的内容中我们是直接向 RCC 寄存器赋值来使能时钟的,不如这样直观。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

 4.点亮灯

现在完整组织下用 STM32库函数 控制一个 LED 的代码

int main(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_SetBits(GPIOB,GPIO_Pin_5);

while(1);

}

小结

        感谢大家阅读,以上是两个方式点亮一个LED灯的基础操作,文中有误的地方还望指正。参考书籍STM32数据手册,以及零死角玩转STM32。

        对比两种方式,寄存器方式更偏向于底层,更深入了解STM32寄存器的使用。固件库编程看起来会比较容易阅读。现在大多还是使用固件库编程,在个别需求上,配合寄存器的操作。

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: