STM32 IO输出实验

实验内容

  • 使用Keil 5编写实验代码,使得LED功能模块上的四个LED灯(LED1、LED2、LED3、LED4)循环闪烁。

实验目的

  • 熟悉Keil 5开发环境
  • 了解LED的工作原理
  • 了解STM32开发的一般过程
  • 掌握STM32官方库的查看和调用方法
  • 掌握STM32上的IO口作为普通输出端口的配置方法

实验环境

实验所需硬件

序号 名称 数量 备注
1 电脑 1台 系统Windows7及以上
2 STM32底座模块 1个 ·
3 LED模块 1个 ·
4 ST-Link下载器 1个 ·
5 ST-Link下载器连接线 1根 ·
6 IO口输出实验代码 1份 ·

实验所需软件

  • MDK5 安装步骤

  • ST-LINK 驱动安装步骤

  • Git 软件下载(可选)

ST-Link下载器 & ST-Link下载器连接线

STLink下载器

STM32底座:HIVE PRO STM32是一种基于STM32F103C8T6芯片的蜂巢底座。

STM32底座

LED模块:LED模块共有四个按键,4个LED灯,可供完成流水灯、按键处理等相关实验。按键的触发为低电平,LED灯低电平点亮。

LED模块

实验要求

  • 掌握STM32通用IO口的配置方法
  • 了解STM32中IO口控制相关的寄存器的功能和用法:

    2个32位的端口配置寄存器CRL和CRH;

    2个32位的数据寄存器IDR和ODR;

    1个32位的置位/复位寄存器BSRR;

    一个16位的复位寄存器BRR;

    1个32位的锁存寄存器LCKR

实验原理

I/O介绍

本节将要实现的是STM32底座模块上的四个LED实现一个类似流水灯的效果,该实验的关键在于如何控制STM32的IO口输出。了解了STM32的IO口如何输出的,就可以实现流水灯了。通过本节的学习,将初步掌握STM32基本IO口的使用,而这是迈向STM32的第一步。

STM32每个IO口可以自由编程,但IO口寄存器必须按32位字被访问。STM32的很多IO口都是5V兼容的,这些IO口在与5V电平的外设连接的时候很有优势,具体哪些IO口是与5V兼容的,可以从该芯片的数据手册管脚描述章节查到(I/O Level标有FT的就是5V电平兼容的)。

STM32的每个IO端口都有7个寄存器来控制。他们分别是:2个32位的端口配置寄存器CRL和CRH;2个32位的数据寄存器IDR和ODR;1个32位的置位/复位寄存器BSRR;一个16位的复位寄存器BRR;1个32位的锁存寄存器LCKR。想要了解每个寄存器的详细使用方法,可以参考:

中文版:《STM32F10xxx参考手册》 P105~P129;

英文版:《STM32 Reference Manual (RM0008)》 P159~P196。

CRL和CRH控制着每个IO口的模式及输出速率。

STM32的IO口位配置如图所示:

STM32的IO口位配置

STM32输出模式配置如图所示: STM32输出模式配置

端口低配置寄存器CRL的描述如图所示: 端口低配置寄存器CRL各位描述

该寄存器的复位值为0X4444 4444,从图4.2.2可以看到,复位值其实就是配置端口为浮空输入模式。从上图还可以得出:STM32的CRL控制着每组IO端口(A~G)的低8位的模式。每个IO端口的占用CRL的4个位,高两位为CNF,低两位为MODE。

CRH的作用和CRL完全一样,只是CRL控制的是低8位输出口,而CRH控制的是高8位输出口。 下面介绍一下怎样通过HAL库设置GPIO的相关参数和输出。 GPIO相关的函数和定义分布在HAL库文件stm32f1xx_hal_gpio.c和头文件stm32f1xx_hal_gpio.h文件中。在HAL库开发中,操作寄存器CRH和CRL来配置IO口的模式和速度是通过GPIO初始化函数完成:

void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

这个函数有两个参数,第一个参数是用来指定GPIO,取值范围为GPIOA~GPIOG。第二个参数为IO口初始化参数结构体指针,结构体类型为GPIO_InitTypeDef。下面看看这个结构体的定义,用MDK5打开例程:NB-IoT应用技术开发\单片机基础应用实验\IO口输出实验,找到工程组HALLIB\STM32F1xx_HAL_Driver下面的stm32f1xx_hal_gpio.c文件,定位到HAL_GPIO_Init函数体处,双击入口参数类型GPIO_InitTypeDef后右键选择“Go to definition of…”可以查看结构体的定义:

typedef struct
{
  uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
      This parameter can be any value of @ref GPIO_pins_define */
  uint32_t Mode;/*!< Specifies the operating mode for the selected pins.
      This parameter can be a value of @ref GPIO_mode_define */
  uint32_t Pull; /*!< Specifies the Pull up or Down activation for the selected pins.
      This parameter can be a value of @ref GPIO_pull_define */
  uint32_t Speed; /*!< Specifies the speed for the selected pins.
      This parameter can be a value of @ref GPIO_speed_define */
}
GPIO_InitTypeDef;

下面通过一个GPIO初始化实例来介绍这个结构体的成员变量的含义:打开HARDWARE\led.c,通过初始化结构体初始化GPIO的常用格式是:

GPIO_Initure.Pin=LED1|LED2|LED3|LED4; //PB0,PB1,PB13,PB14
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;//推挽输出
GPIO_Initure.Pull=GPIO_PULLUP;//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;//高速
HAL_GPIO_Init(LED_PORT,&GPIO_Initure);

上面代码的意思是设置GPIOB的0、1、13和14端口为推挽输出模式,上拉,同时速度为高速。可以看出,结构体GPIO_Initture的第一个成员变量Pin是用来配置要初始化的IO口;第二个成员变量Mode是用来设置对应IO端口的输入输出模式,这些模式目前有下面这些定义:

#define  GPIO_MODE_INPUT  0x00000000U  
/*!< Input Floating Mode */
#define  GPIO_MODE_OUTPUT_PP  0x00000001U 
/*!< Output Push Pull Mode */
#define  GPIO_MODE_OUTPUT_OD  0x00000011U 
/*!< Output Open Drain Mode */
#define  GPIO_MODE_AF_PP  0x00000002U 
/*!< Alternate Function Push Pull Mode */
#define  GPIO_MODE_AF_OD  0x00000012U 
/*!< Alternate Function Open Drain Mode */
#define  GPIO_MODE_AF_INPUT  GPIO_MODE_INPUT
/*!< Alternate Function Input Mode */
#define  GPIO_MODE_ANALOG  0x00000003U   
/*!< Analog Mode */
#define  GPIO_MODE_IT_RISING  0x10110000U   
/*!< External Interrupt Mode with Rising edge trigger detection */
#define  GPIO_MODE_IT_FALLING  0x10210000U   
/*!< External Interrupt Mode with Falling edge trigger detection*/
#define  GPIO_MODE_IT_RISING_FALLING  0x10310000U   
/*!< External Interrupt Mode with Rising/Falling edge trigger detection*/
 #define  GPIO_MODE_EVT_RISING  0x10120000U   
/*!< External Event Mode with Rising edge trigger detection */
#define  GPIO_MODE_EVT_FALLING  0x10220000U   
/*!< External Event Mode with Falling edge trigger detection */
#define  GPIO_MODE_EVT_RISING_FALLING  0x10320000U   
/*!< External Event Mode with Rising/Falling edge trigger detection*/

第三个成员变量是IO口速度设置,有三个可选值:

#define  GPIO_SPEED_FREQ_LOW  (GPIO_CRL_MODE0_1) 
/*!< Low speed */
#define  GPIO_SPEED_FREQ_MEDIUM  (GPIO_CRL_MODE0_0) 
/*!< Medium speed */
#define  GPIO_SPEED_FREQ_HIGH  (GPIO_CRL_MODE0)   
/*!< High speed */

这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型,在前面的“MDK中使用HAL库快速组织代码代码”一节有介绍,在后面的实验中,不再重复介绍怎么定位每个参数的取值范围的方法。

IDR是一个端口输入数据寄存器,只用了低16位。该寄存器为只读寄存器,并且只能以16位的形式读出。该寄存器各位的描述如图所示:

端口输入数据寄存器IDR各位描述

要想知道某个IO口的电平状态,只要读这个寄存器,再看某个位的状态就可以了。使用起来是比较简单的。

在HAL库中操作IDR寄存器读取IO端口数据是通过GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)函数实现的。比如要读GPIOA.3的电平状态,那么方法是:

HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_3);

返回值是1 (GPIO_PIN_SET)或者0 (GPIO_PIN_RESET)。

ODR是一个端口输出数据寄存器,也只用了低16位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前IO口的输出状态。而向该寄存器写数据,则可以控制某个IO口的输出电平。该寄存器的各位描述如图所示:

端口输出数据寄存器ODR各位描述

在HAL库中设置ODR寄存器的值来控制IO口的输出状态是通过函数HAL_GPIO_WritePin来实现的:

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

该函数一般用来一次性往一个GPIO的多个端口设值。

BSRR寄存器是端口位设置/清除寄存器。该寄存器和ODR寄存器具有类似的作用,都可以用来设置GPIO端口的输出位是1还是0。下面看看该寄存器的描述如下图:

端口位设置/清除寄存器BSRR各位描述

该寄存器通过举例子可以很清楚了解它的使用方法。例如要设置GPIOA的第1个端口值为1,那么只需要往寄存器BSRR的低16位对应位写1即可:

GPIOA->BSRR = (1<<1);

如果要设置GPIOA的第1个端口值为0,只需要往BSRR寄存器高16位对应位写1即可:

GPIOA->BSRR = (1<<(16+1));

BSRR寄存器往相应位写0是无影响的。GPIO相关的函数先介绍到这里。虽然IO口操作步骤很简单,这里还是做个概括性的总结,IO口操作步骤为:

  1. 使能IO口时钟,调用函数为__HAL_RCC_GPIOX_CLK_ENABLE(其中X=A~G);
  2. 初始化IO参数。调用函数HAL_GPIO_Init();
  3. 操作IO输入输出。

下面来介绍单片机的特殊引脚:PB3,PB4,PA13,PA14,PA15。在STM32单片机中以上几个引脚是特殊的IO口,用作JTAG/SWD仿真器的调试接口。其中PA13,PA14分别作为SWD调试的SWIO和SWCLK;PB3,PB4,PA13,PA14,PA15用于JTAG调试。这五个引脚的中英文描述如下图所示:

SWJ调试端口脚

SWJ debug port pins

这五个IO引脚非常特殊,正常情况下作为JTAG/SWD仿真器的调试引脚,如果要作为普通IO口使用需要特别的配置。以PA13引脚为例,该引脚在STM32F1数据手册中的描述如下图:

PA13引脚

相较于其它的普通IO,PA13的Mainfunction为JTMS-SWDIO。反而普通IO口的功能在Alternate functions中的remap里。

硬件设计

本次实验用到的元器件是4个LED灯,其电路在蜂巢实验套件上已经设计好了。LED1接PB0,LED2接PB1,LED3接PB13,LED4接PB14。其连接原理图如下:

连接原理图

实验步骤

① LED模块安装于STM32底座上。ST-Link连接底座与计算机,如下图:

安装模块

② 访问github,进入github界面后点击Code,Clone HTTPS安全链接,如下图所示:

操作步骤

③ 打开电脑终端,进入工作目录workspace (workspace 为工程文件夹所在目录):

cd workspace

④ 运行clone命令:

git clone https://github.com/aiotcom/eps.git

下载目录至指定文件夹下。

如果提示“command not found”表示电脑没有安装Git,请至Git官网下载。

如果电脑没有安装 Git 软件,也可以进入Github,点击 Code -> DownLoad ZIP 下载所有工程代码。如下图所示:

下载代码

如果电脑没有公网,可以进:D盘\实验教程与代码选择相应的代码。

⑤ 打开 Keil 5 (即安装的MDK5)工程软件,点击工具栏: ` Project -> Open Project,选择工程文件:STM32基础实验\1.IO输出实验\USER\LED.uvprojx` 并打开。 打开工程

选择文件

如果没有安装该软件,请至Keil官网下载。

⑥ 工程启动后,点击 Rebuild 重新编译。如下图:

重新编译工程

⑦ 编译成功,如下图:

编译成功

⑧ 点击 Download 按钮下载程序,如下图所示:

下载程序

下载成功

⑨ 下载完成后,将USB线进行重连操作(即:将STLink的USB线从底座上取下,再重新接上)。

⑩ 观察LED灯的运行情况,如果程序运行正常,LED灯应该从LED1到LED4以300毫秒时间为间隔,依次循环闪烁。

代码讲解

① 程序目录结构,如下图。CORE文件夹为STM32内核代码,HALLIB文件文件夹为底层HAL库文件。我们主要关心main.cHARDWARE中的代码。

代码目录结构

② main.c代码:

#define TIMEOUT_MS 300 /*亮灭的间隔时间,单位为ms*/
int main(void)
{
    HAL_Init();//初始化HAL库,为随后用到的HAL_Delay()函数提供时钟。    
    LED_Init();//初始化LED灯控制的IO接口	
    while(1)
    {
        LED1_ON();//LED1亮                
        HAL_Delay(TIMEOUT_MS);//延时 TIMEOUT_MS 毫秒,HAL_Delay()为HAL库函数提供的延时函数。      
        LED1_OFF();//LED1灭

        LED2_ON();//LED2亮
        HAL_Delay(TIMEOUT_MS);//延时 TIMEOUT_MS 毫秒     
        LED2_OFF();//LED2灭

        LED3_ON();//LED3亮
        HAL_Delay(TIMEOUT_MS);//延时 TIMEOUT_MS 毫秒     
        LED3_OFF();//LED3灭

        LED4_ON();//LED4亮
        HAL_Delay(TIMEOUT_MS);//延时 TIMEOUT_MS 毫秒      
        LED4_OFF();//LED4灭 
    }
}

③ LED.c为LED驱动代码,代码实现对LED灯控制的IO进行初始化设置IO口为推挽上拉模式,重点不要忘记使用能IO接口的时钟及设置输出的速度。

void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    LED_IO_RCC();

    GPIO_Initure.Pin   = LED1_PIN|LED2_PIN|LED3_PIN|LED4_PIN;
    GPIO_Initure.Mode  = GPIO_MODE_OUTPUT_PP;//推挽输出
    GPIO_Initure.Pull  = GPIO_PULLUP;//上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;//高速
    HAL_GPIO_Init(LED_PORT,&GPIO_Initure);
        
    LED_OFF();///初始化完成全部LED灯灭
}

常见问题

  1. 弹出警告窗口,不能下载程序。

    • 请确认STLink驱动、STM32F103C8的DFP包是否安装。
    • STLink仿真器是否正常接入。
  2. 下载代码后程序没观察到实验现象。

    • 请重新上电,或者按下底座上的复位按键。
    • 模块没有安装稳妥。

实验思考

  1. 实现LED1~LED4,以1秒的间隔依次亮起。

  2. LED1、LED2亮时LED3、LED4灭;LED1、LED2灭时LED3、LED4亮。