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份 | · |
实验所需软件
ST-Link下载器 & ST-Link下载器连接线
STM32底座:HIVE PRO STM32是一种基于STM32F103C8T6芯片的蜂巢底座。
LED模块:LED模块共有四个按键,4个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输出模式配置如图所示:
端口低配置寄存器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位的形式读出。该寄存器各位的描述如图所示:
要想知道某个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口的输出电平。该寄存器的各位描述如图所示:
在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。下面看看该寄存器的描述如下图:
该寄存器通过举例子可以很清楚了解它的使用方法。例如要设置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口操作步骤为:
- 使能IO口时钟,调用函数为__HAL_RCC_GPIOX_CLK_ENABLE(其中X=A~G);
- 初始化IO参数。调用函数HAL_GPIO_Init();
- 操作IO输入输出。
下面来介绍单片机的特殊引脚:PB3,PB4,PA13,PA14,PA15。在STM32单片机中以上几个引脚是特殊的IO口,用作JTAG/SWD仿真器的调试接口。其中PA13,PA14分别作为SWD调试的SWIO和SWCLK;PB3,PB4,PA13,PA14,PA15用于JTAG调试。这五个引脚的中英文描述如下图所示:
这五个IO引脚非常特殊,正常情况下作为JTAG/SWD仿真器的调试引脚,如果要作为普通IO口使用需要特别的配置。以PA13引脚为例,该引脚在STM32F1数据手册中的描述如下图:
相较于其它的普通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.c
及HARDWARE
中的代码。
② 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灯灭
}
常见问题
-
弹出警告窗口,不能下载程序。
- 请确认STLink驱动、STM32F103C8的DFP包是否安装。
- STLink仿真器是否正常接入。
-
下载代码后程序没观察到实验现象。
- 请重新上电,或者按下底座上的复位按键。
- 模块没有安装稳妥。
实验思考
-
实现LED1~LED4,以1秒的间隔依次亮起。
-
LED1、LED2亮时LED3、LED4灭;LED1、LED2灭时LED3、LED4亮。