STM32 GPIO点亮LED灯教程(详细步骤,手把手指导)

写在前面:在前面的学习中,我们学习了STM32的编译环境(MDK)、时钟树以及GPIO的8种工作模式;这节我们学习正式入门STM32—点亮第一个LED灯;即利用GPIO进行电灯,尽管是一个十分简单的实现,但是其步骤也是一个完整的STM32项目,可以说是“麻雀虽小,五脏俱全”;因此,作为入门十分合适;

实验介绍:利用STM32开发板,通过HAL库函数对相关寄存器的控制,实现对开发板上LED灯的控制;

实验硬件:正点原子—STM32F1精英版开发板; 

目录

一、GPIO寄存器介绍

1.1 端口配置寄存器(GPIOx_CRL 和 GPIOx_CRH)     

1.2  端口输入数据寄存器(GPIOx_IDR)

1.3 端口输出数据寄存器(GPIOx_ODR)

1.4 端口置位/复位寄存器(GPIOx_BSRR)

1.5 端口位清除寄存器(GPIOx_BRR)

1.6 端口配置锁定寄存器(GPIOx_LCKR)

二、GPIO配置步骤

2.1 使能时钟

2.2 设置工作模式 

2.3 设置输出状态

2.4 读取输入状态

三、硬件设计

3.1 实现功能

3.2 原理图

四、程序设计

4.1 程序流程图

4.2 程序分析

 4.2.1 led驱动函数

4.2.2 main函数

五、实验现象


一、GPIO寄存器介绍

        在前面的学习中,我们对GPIO进行了基本的介绍,基本结构进行了分析以及GPIO的8中工作模式的介绍;详细的内容请大家参考我前面的博客;

STM32–GPIO(8种工作模式)-CSDN博客

         STM32F1 每组(这里是 A~D)通用 GPIO 口有 7 个 32 位寄存器控制; 分别为:CRL、CRH、TDR、ODR、BSRR、BRR以及LCKR寄存器;下面对7个寄存器做出详述(注:每组都有7个寄存器,例如:GPIOA、GPIOB都有7个相关的寄存器);

1.1 端口配置寄存器(GPIOx_CRL 和 GPIOx_CRH)     

        这个寄存器是用于配置GPIO端口的寄存器,其中CRL 控制端口的低八位,CRH 控制端口的 高八位,每个组GPIO有16个端口(例如:GPIOA0-GPIOA15);那么这两个寄存器控制GPIO口的什么呢?用于控制 GPIO 口的工作模式和工作速度;

CRL寄存器:

 CRH寄存器:

        这两个32位的寄存器,一共为64位,64位共控制16个端口(如:PA0-PA15);那么相当于每4位控制一个IO口的工作模式与工作速度;CRL寄存器控制(0-7)端口,CRH寄存器控制(8-15)端口;

以3-0位(控制PX_0端口)为例:

    1-0位控制其输入输出: 

    3-2位控制其对应的模式:

        如0-3位为1010,则表示:端口1工作在复用功能的推挽输出模式,最大速度为2MHz;

        又如,我们需要让Px_7端口工作在浮空输入模式,则就要求CRL寄存器的31-28位赋值为:0100;

        故通过上面两个寄存器,作用是控制端口的输入输出模式以及工作速度;

        但是,我们仔细观察,在上面的寄存器中,如果选择的是输入模式,在上拉/下拉输入模式下,通过上面两个寄存器无法确定其到底为上拉还是下拉模式;此时,就需要另外的寄存器;

1.2  端口输入数据寄存器(GPIOx_IDR)

        该寄存器用于读取GPIOx的输出高电平或低电平;

         寄存器低 16 位有效,分别对应每一组 GPIO 的 16 个引脚。当 CPU 写访问该寄存器,可以读取对应引脚的输入电平(IDRy=0、IDRy=1),该寄存器下方为r,表示为只读寄存器,只可以读取值,不能写入值;

1.3 端口输出数据寄存器(GPIOx_ODR)

        该寄存器用于控制 GPIOx 的输出高电平或者低电平;

        该寄存器低 16 位有效,分别对应每一组 GPIO 的 16 个引脚。当 CPU 写访问该寄存器,如 果对应的某位写 0(ODRy=0),则表示设置该 IO 口输出的是低电平,如果写 1(ODRy=1),则表示设置该 IO 口输出的是高电平。

        除了 ODR 寄存器,还有一个寄存器也是用于控制 GPIO 输出的,它就是 BSRR 寄存器。

1.4 端口置位/复位寄存器(GPIOx_BSRR)

         该寄存器也用于控制 GPIOx 的输出高电平或者低电平.

        该寄存器的31-16(BRy)位控制15-0引脚的ODRy的值,如果为0,对ODRy的值不产生影响,如果为1,则将ODRy的值改为0;

        该寄存器的15-0(BSy)位也控制15-0引脚的ODRy的值,如果为0,对ODRy的值不产生影响,如果为1,则将ODRy的值改为1;

        当BRy与BSy同时作用时,以BSy为主;

        此处,我们需要思考一下,为什么输出一个引脚的值,需要两个寄存器对其进行双重控制呢?

官方给出的解释是:在使用ODR在读和修改之间产生中断时,会对ORD的值产生风险,而BSRR无风险;

        我们对其进行简单的分析,ODR是一个可读可写的寄存器,那么对其进行赋值时(确定某个IO口输出电平),首先需要读出ODR寄存器的值,然后再重新为ODR进行赋值。而BSRR是一个只写寄存器,赋值时直接设置即可。BSRR 寄存器改变引脚状态的时候,不会被中断打断,而 ODR 寄存器有被中断打断的风险。

1.5 端口位清除寄存器(GPIOx_BRR)

        该寄存器用于清除ODR寄存器的值,不经常使用;

        该寄存器低 16 位有效,若为0,对应的ODRy位不产生影响;若为1,清除对应位ODRy为0;

1.6 端口配置锁定寄存器(GPIOx_LCKR)

        当执行正确的写序列设置了位16(LCKK)时,该寄存器用来锁定端口位的配置。位[15:0]用于锁 定GPIO端口的配置。在规定的写入操作期间,不能改变LCKP[15:0]。当对相应的端口位执行了 LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。 每个锁定位锁定控制寄存器(CRL, CRH)中相应的4个位。

以上7个寄存器中,我们需要重视的为CRL、CRH、ODR、IDR以及BSRR寄存器;

二、GPIO配置步骤

一般外设的配置驱动模型为:

1、初始化

分为:时钟设置(包括时钟源与开启时钟);

           参数设置;

           IO设置(可选);

           中断设置(可选);

2、读函数:从外设读取数据(可选);

3、写函数:往外设写入数据(可选);

4、中断服务函数(可选);

         在此,我学习的时候就有个疑问与大家分享,为什么STM32进行IO口操作时,必须先要进行时钟设置(使能时钟)?

        在学习52单片机的时候,只需要直接将对应的外设寄存器配置好,就直接使用,为什么32要先配置时钟呢?

        经过查阅资料发现,51单片机的做法虽然简单,但是这样对于功耗的浪费很大,也就是说尽管有些地方用不到,还需要配置资源。为了降低功耗与发热,芯片厂商需要精准的控制某个设置的开与关,达到节能省电的目的;此外,51只有一个晶振时钟,好控制,而32的晶振4个起步,对于不同的外设需要分配不同的晶振时钟,故STM32中I/O要先进行时钟配置,在进行IO口操作;

GPIO配置步骤:

1、使能时钟 __HAL_RCC_GPIOB_CLK_ENABLE();//宏定义

2、设置工作模式:HAL_GPIO_Init 函数;

3、设置输出状态:HAL_GPIO_WritePin 函数;

                               HAL_GPIO_TogglePin 函数;

4、读取输入状态:HAL_GPIO_ReadPin函数;

口诀:GPIO配置,一个宏,四个函数;

下面我们对于这四个步骤进行详细说明,包括利用HAL库函数进行相关的配置说明;

2.1 使能时钟

        STM32 在使用任何外设之前,我们都要先使能其时钟(也就是打开时钟),此处利用一个宏定义:

__HAL_RCC_GPIOX_CLK_ENABLE();

此处,我们看看代码种对于其宏定义的说明是什么。打开任意一个例程源码进行搜索;(此处以GPIOA作为举例); 

宏定义的作用就是替代,将被定义的量用定义的量进行替代;

 代码中对于此宏的定义是这样说的:

其中关键在于划线这句:

 SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);

其中,SET_BIT的意思我们不知,再次进行查找,右键Go to Definiation of "SET_BIT"其又是一个宏定义:其含义为括号中的两个参数做或运算;

 SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN)的参数1:RCC->APB2ENR;参数2:RCC_APB2ENR_IOPAEN;分别代表什么意思呢?

RCC_APB2ENR_IOPAEN:这又是一个宏定义,我们同样跳转过去看一下去其含义:

这三行代码可以说是层层宏定义,其表达的意思就是将0X00000002(二进制为:0x00000000000000000000000000000010)左移一位成为0X00000004(二进制为:0x00000000000000000000000000000100);

RCC->APB2ENR:是APB2外设时钟使能寄存器,让其与0X00000004进行或运算,则该寄存器的第2位进行置1;

查看参考手册,将位2置1的作用就是打开IO口时钟;

例如,我们需要使能IO端口B时钟使能,那么怎么设置呢?

__HAL_RCC_GPIOB_CLK_ENABLE();

综上过程,__HAL_RCC_GPIOX_CLK_ENABLE();宏定义的作用就是打开GPIO端口的时钟;

2.2 设置工作模式 

        设置工作模式:HAL_GPIO_Init 函数;

        那么同样我们对于HAL_GPIO_Init函数进行分析;

对其两个参数进行分析:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)

这两个参数都为结构体指针:

结构体指针1:GPIO_TypeDef其定义格式为:

主要包括一些相关的寄存器,当然库函数已经给我们定义了这些结构体变量,例如下面的:

因此也将GPIOA成为GPIO寄存器基地址

结构体指针2:GPIO_InitTypeDef其定义格式为:

该结构体变量需要在使用时对其结构体成员进行定义使用,包括端口的引脚、工作模式、上

下拉方式以及输出速度;

PIN:引脚设置;

Mode:(工作模式)

Pull上下拉

Speed:速度

例如,我们需要初始化IO端口B的引脚5,使其工作在推挽模式,输出速度为低速,那么怎么设置呢?

GPIO_InitTypeDef  gpio_init_struct;//定义第二个结构体变量

设置变量对应的结构体成员;

gpio_init_struct.Pin=GPIO_PIN_5;
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW ; 

HAL_GPIO_Init(GPIOB,&gpio_init_struct);、、引用初始化函数;

2.3 设置输出状态

        设置输出状态:HAL_GPIO_WritePin 函数;

        下面我们对于Wirte_Pin进行分析:同样利用右键或者查找找到对应函数的定义;

该函数共有三个参数:GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState

参数1:GPIO_TypeDef *GPIOx结构体指针类型,与设置工作模式的参数相同,此处不进行详述;

参数2:uint16_t GPIO_Pin与上文所说的PIN引脚的相同,选择端口对应的引脚;

参数3:GPIO_PinState此处为一个枚举类型,GPIO的状态0/1;

例如,我们需要设置PB6=1,那么怎么设置呢?

参数1为:GPIOB;参数2为:GPIO_PIN_1;参数3为:GPIO_PIN_SET;   

        HAL_GPIO_TogglePin 函数;

        下面对于TogglePin 函数进行详述:

该函数有两个参数:GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin

参数1:GPIO基地址,上文已经提及;

参数2:GPIO_Pin,端口引脚上文已经提及;

例如,我们需要设置PB10进行翻转,那么怎么设置呢?

参数1为:GPIOB;参数2为:GPIO_PIN_10;

2.4 读取输入状态

        HAL_GPIO_ReadPin函数;

        下面我们对于ReadPin函数进行详述:

该函数共有三个参数:GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin这两个参数上文已经介绍过,分别为:GPIO基地址、GPIO_Pin,端口引脚;

该函数的返回值为枚举类型,返回0代表IO为低电平,返回1代表IO为高电平;

例如,我们需要知道PB10的输入,那么怎么设置呢?

参数1为:GPIOB;参数2为:GPIO_PIN_10;在设置一个变量,将返回值赋值给变量即可;

三、硬件设计

3.1 实现功能

        使STM32F1精英版上的LED1进行闪烁;

3.2 原理图

        在该开发板上的LED灯共有3个,本次我们点亮LED0,即对PB5进行操作;我们看到LED0右端接的是VCC3.3V,那么只需要控制左端的PB5输出即可;(输出0点亮,输出1熄灭);

        我们还需要确定GPIOB_5的工作模式;我们采用推挽输出,其特点为:输出引脚电平,高电平为VDD,低电平为VSS;

四、程序设计

4.1 程序流程图

4.2 程序分析

 4.2.1 led驱动函数

void led_init(void)//led初始化函数
{

     __HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIOB时钟
    GPIO_InitTypeDef  gpio_init_struct; //定义结构体变量  
    gpio_init_struct.Pin=GPIO_PIN_5;//定义结构体变量成员-引脚号为5;
    gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;//定义结构体变量成员-工作模式为推挽输出;
    gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW ; 定义结构体变量成员-输出速度为低速;
    HAL_GPIO_Init(GPIOB,&gpio_init_struct);//引用GPIO初始化函数;
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);//引用GPIO写入函数;

}

4.2.2 main函数

int main(void)
{
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);      /* 设置时钟, 72Mhz */
    delay_init(72);                          /* 延时初始化 */
    led_init();                              /* LED初始化 */
    
    while(1)
    { 
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);    /* PB5置1 */ 
        delay_ms(500);
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);  /* PB5置0 */
        delay_ms(500); 
    }
}

五、实验现象

32点灯

总结:本文主要讲解了如何在STM32开发板上点亮第一个LED灯,主要的学习内容为:GPIO配置步骤以及GPIO相关的寄存器,硬件设计部分入代码分析;尽管本文的代码难度不难,但是对于程序中相关函数以及寄存器的理解,对于初学者还有一定的难度因此还是需要多加练习;

创作不易,还请大家多多点赞支持!!!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 GPIO点亮LED灯教程(详细步骤,手把手指导)

发表评论