开发环境:
IDE:MKD 5.38a
STM32CubeMX: V6.12.0
开发板:STM32H7S78-DK开发板
MCU:STM32H7S7L8H6H
1 GPIO工作原理
熟悉单片机的朋友都知道,学习的第一个例程就是流水灯,要想实现流水灯,首先必须了解GPIO的工作原理。GPIO的基本结构如图所示。
Figure 1-1 GPIO的基本结构
STM32 的 IO 口可以由软件配置成如下 8 种模式:
- 浮空输入:浮空(floating)就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。 通俗讲就是让管脚什么都不接,浮空着。信号进入芯片内部后,既没有接上拉电阻也没有接下拉电阻,经由触发器输入。配置成这个模式后,用电压变量引脚电压为1点几伏,这是个不确定值。由于其输入阻抗比较大,一般把这种模式用于标准的通讯协议,比如IIC、USART的等。该模式是STM32复位之后的默认模式。
Figure 1-2 输入浮空模式
- 上拉输入:上拉就是把电位拉高,比如拉到Vcc。上拉就是将不确定的信号通过一个电阻嵌位在高电平,电阻同时起限流作用,弱强只是上拉电阻的阻值不同,没有什么严格区分。上拉输入就是信号进入芯片后加了一个上拉电阻,再经过施密特触发器转换成0、1信号,读取此时的引脚电平为高电平;
Figure 1-3 输入上拉模式
- 下拉输入:就是把电压拉低,拉到GND。与上拉原理相似。下拉输入就是信号进入芯片后加了一个下拉电阻,再经过施密特触发器转换成0、1信号,读取此时的引脚电平为低电平;
Figure 1-4 输入下拉模式
- 模拟输入:信号进入后不经过上拉电阻或者下拉电阻,关闭施密特触发器,经由另一线路把电压信号传送到片上外设模块。模拟输入是指传统方式的输入,数字输入是输入PCM数字信号,即0、1的二进制数字信号,通过数模转换,转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的。比如传送给ADC模块,由ADC采集电压信号。所以可以理解为模拟输入的信号是未经处理的信号,是原汁原味的信号。
Figure 1-5 模拟输入模式
- 开漏输出:一般用在电平不匹配的场合,如需要输出5V的高电平。输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
Figure 1-6 开漏输出模式
- 复用开漏输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)。端口必须配置成复用开漏功能输出模式。
Figure 1-7 开漏复用功能
- 推挽式输出:可以输出高、低电平,连接数字器件;推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源决定。推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。
Figure 1-8 推挽式输出
- 推挽式复用输出
Figure 1-9 推挽式复用功能
2 I/O复用和重映射
2.1 I/O复用
STM32 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个 GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。当I/O端口被配置为复用功能时:
● 在开漏或推挽式配置中,输出缓冲器被打开
● 内置外设的信号驱动输出缓冲器(复用功能输出)
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止
● 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
● 开漏模式时,读输入数据寄存器时可得到I/O口状态
● 在推挽模式时,读输出数据寄存器时可得到最后一次写的值
Figure 2-1 复用功能配置
大家都知道,MCU都有串口,STM32H7S7L8H6H有好几个串口。我们可以查手册知道,串口 1 的引脚对应的 IO 为PA9、PA10。PA9、PA10 默认功能是 GPIO,所以当PA9、PA10 引脚作为串口 1 的 TX,RX引脚使用的时候,那就是端口复用。
2.2 I/O重映射
为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。 STM32 中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。 我们知道每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
Table 2-1 USART1重映像
复用功能 |
USART1_REMAP = 0 |
USART1_REMAP = 1 |
USART1_TX |
PA9 |
PB6 |
USART1_RX |
PA10 |
PB7 |
从表中可以看出,默认情况下,串口 1 复用的时候的引脚位 PA9、PA10,同时我们可以将 TX 和 RX 重新映射到管脚 PB6 和 PB7 上面去。所以重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后要调用重映射函数。
3 GPIO流水灯硬件电路分析
发光二极管是属于二极管的一种,具有二级管单向导电特性,即只有在正向电压(二极管的正极接正,负极接负)下才能导通发光。PO1引脚接发光二极管(LED1)的负极,所以PO1引脚输出低电平LED1亮,PO1引脚输出低电平LED1熄灭,,LED2/LED3/LED4同理。
Figure 3-1 LED电路图
值得注意的,不同的开发板,LED连接的GPIO一般是不同的,请注意修改。
4 GPIO流水灯寄存器分析
要想真正掌握一款单片机,分析寄存器是必不可少,但是对于STM32来再说,ST已经将寄存器操作封装成库函数,开发者只需要调用库函数即可,对于初学者来说,只需学会使用函数即可,对于没有基础的读者朋友就不必细究每个寄存器,当学到一定程度,再来一探究竟吧,笔者在这里只是给出GPIO的寄存配置相关配置表,在后面的章节也是如此。好了,继续进入正题吧。
每个GPIO端口都有一个32位模式寄存器 (GPIOx_MODER),一个32位输出类型寄存器 (GPIOx_ OTYPER),一个32位输出速度寄存器 (GPIOx_OSPEEDR),一个32位输出上拉/下拉寄存器 (GPIOx_PUPDR),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/ 复位寄存器(GPIOx_BSRR),一个32位锁定寄存器(GPIOx_LCKR),两个复用功能寄存器(GPIOx_AFRL和GPIOx_AFRH)。每个I/O端口位可以自由编程,I/O端口寄存器可通过字节( 8 位)、半字( 16 位)或字( 32 位)对 GPIO 寄存器进行访问。
点亮LED,基本步骤是:配置寄存器;控制寄存器。库开发只是将传统的配置方式编程函数,是的单片机开发变得简单方便快捷。
我们常用的IO端口寄存器只有几个:MODER、OTYPER、OSPEEDR、PUPDR、IDR、ODR。其中MODER、OTYPER、OSPEEDR、PUPDR控制着每个IO口的模式及输出速率。
Table 4-1 端口位配置表
Table 4-2 输出模式位
GPIO 端口模式寄存器 (GPIOx_MODER)描述如下图所示。
Figure 4-1 模式寄存器(GPIOx_MODER)
GPIO 端口输出类型寄存器 (GPIOx_OTYPER)如下图所示。
Figure 4-2 输出类型寄存器 (GPIOx_OTYPER)
GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR)如下图所示。
Figure 4-3 输出速度寄存器 (GPIOx_OSPEEDR)
GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) 如下图示所示。
Figure 4-4 上拉/下拉寄存器 (GPIOx_PUPDR)
5 GPIO 流水灯实现流程
笔者在上文已经分析了GPIO的原理及操作步骤,现在我们就来写代码吧。本书是用库来对STM32来开发的,这是本书的第一个实例,笔者为了读者比较直接配置寄存和库开发的区别,笔者在此用了两种方式进行开发,希望读者能自己体会两种方式的优劣。这里笔者会使用两种库:标准库和HAL库。
不管使用何种实现方式,其流程都是一样的。
GPIO是开发STM32最基本的配置,所以掌握GPIO的配置显得尤为重要。要实现流水灯,一般步骤可以总结为如下:
- GPIO 时钟使能;
- GPIO 端口模式设置;
- 初始化IO口;
- 编写处理函数;
6 GPIO 流水灯实现
6.1 STM32Cube新建工程
关于如何使用STM32Cube新建工程在前文已经讲解过了,这里直说配置GPIO部分内容。本文要实现流水灯,其实输出为初始化设置为高电平还是低电平都可以,因为流水灯需要不断反转。
1.首先进行系统配置,debug模式选择可以选择其他模式,默认选择Disable,Timebase Source选择SysTick,关于Timebase Source在后文会详细讲解。
Figure 6-1 系统配置
2.时钟配置,外部高速时钟。
Figure 6-2 时钟选择
STM32CubeMX配置时钟的界面非常方便,有很多提示(比如最大时钟),很多地方直接点击选择就行了。默认高速时钟是使用内部(HSI),而且CPU时钟配置的比较低。以我选择的STM32H7S,外部24M晶振为例(如下图)。配置系统时钟配置600MHz,APB1总线配置150MHz。
Figure 6-3 时钟配置
3. GPIO初始化配置
我们将PO1、PO5、PM2、PM3配置输出模式(高电平、低电平均可)、输出速率、上/下拉等,默认即可。
Figure 6-4 GPIO初始化
4.工程生成设置
Figure 6-5 工程配置
其他默认即可。最后点击“生成代码”即可生成代码。
6.2 GPIO流水灯实现
我们要实现流水灯,需要在主函数添加以下代码。
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
6.3 流水灯代码分析
在分析代码前,先看看流水灯编程流程:
1>使能GPIO端口时钟;
2>初始化GPIO引脚,即为GPIO初始化结构体赋值,并调用相应初始化函数完成初始化配置;
3>根据实际需求控制流水灯。
STM32H7S HAL库开发,代码比较多,因此,我们在看一个实际工程时,只需从主函数开始,好了,接下来,笔者就带领大家一步一步看看流水灯是怎么实现的。笔者先贴出主函数代码。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* Enable the CPU Cache */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* MCU Configuration--------------------------------------------------------*/
/* Update SystemCoreClock variable according to RCC registers values. */
SystemCoreClockUpdate();
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
注释部分不用管,我们只看代码。
1.复位所有硬件
HAL_Init()函数为复位所有硬件,这个就不细说了。
/**
* @brief Configure the time base source, NVIC and any required global low level hardware
* by calling the HAL_MspInit() callback function to be optionally defined in user file
* stm32h7rsxx_hal_msp.c.
*
* @note HAL_Init() function is called at the beginning of program after reset and before
* the clock configuration.
*
* @note In the default implementation the System Timer (Systick) is used as source of time base.
* The Systick configuration is based on HSI clock, as HSI is the clock
* used after a system Reset and the NVIC configuration is set to Priority group 4.
* Once done, time base tick starts incrementing: the tick variable counter is incremented
* each 1ms in the SysTick_Handler() interrupt handler.
*
* @retval HAL status
*/
HAL_StatusTypeDef HAL_Init(void)
{
HAL_StatusTypeDef status = HAL_OK;
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use SysTick as time base source and configure 1ms tick */
if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
status = HAL_ERROR;
}
else
{
/* Init the low level hardware */
HAL_MspInit();
}
/* Return function status */
return status;
}
2.时钟初始化函数
SystemCoreClockUpdate ()函数用于时钟初始化,也就是通过STM32Cube实现。
时钟初始化函数如下:
/**
* @brief Update SystemCoreClock variable according to RCC registers values.
* The SystemCoreClock variable contains the core clock (sys_cpu_ck), it can
* be used by the user application to setup the SysTick timer or configure
* other parameters.
*
* @note Each time the core clock changes, this function must be called
* to update SystemCoreClock variable value. Otherwise, any configuration
* based on this variable will be incorrect.
*
* @note - The system frequency computed by this function is not the real
* frequency in the chip. It is calculated based on the predefined
* constant and the selected clock source:
*
* - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*)
*
* - If SYSCLK source is CSI, SystemCoreClock will contain the CSI_VALUE(**)
*
* - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(***)
*
* - If SYSCLK source is PLL, SystemCoreClock will contain the HSI_VALUE(*)
* or CSI_VALUE(**) or HSE_VALUE(***) multiplied/divided by the PLL factors.
*
* (*) HSI_VALUE is a constant defined in stm32h7rsxx_hal.h file (default value
* 64 MHz) but the real value may vary depending on the variations
* in voltage and temperature.
*
* (**) CSI_VALUE is a constant defined in stm32h7rsxx_hal.h file (default value
* 4 MHz) but the real value may vary depending on the variations
* in voltage and temperature.
*
* (***) HSE_VALUE is a constant defined in stm32h7rsxx_hal.h file (default value
* 24 MHz), user has to ensure that HSE_VALUE is same as the real
* frequency of the crystal used. Otherwise, this function may
* have wrong result.
*
* - The result of this function could be not correct when using fractional
* value for HSE crystal.
*
* @retval None
*/
void SystemCoreClockUpdate(void)
{
uint32_t sysclk, hsivalue, pllsource, pllm, pllp, core_presc;
float_t pllfracn, pllvco;
/* Get SYSCLK source -------------------------------------------------------*/
switch (RCC->CFGR & RCC_CFGR_SWS)
{
case 0x00: /* HSI used as system clock source (default after reset) */
sysclk = (HSI_VALUE >> ((RCC->CR & RCC_CR_HSIDIV) >> RCC_CR_HSIDIV_Pos));
break;
case 0x08: /* CSI used as system clock source */
sysclk = CSI_VALUE;
break;
case 0x10: /* HSE used as system clock source */
sysclk = HSE_VALUE;
break;
case 0x18: /* PLL1 used as system clock source */
/* PLL1_VCO = (HSE_VALUE or HSI_VALUE or CSI_VALUE/ PLLM) * PLLN
SYSCLK = PLL1_VCO / PLL1R
*/
pllsource = (RCC->PLLCKSELR & RCC_PLLCKSELR_PLLSRC);
pllm = ((RCC->PLLCKSELR & RCC_PLLCKSELR_DIVM1) >> RCC_PLLCKSELR_DIVM1_Pos) ;
if ((RCC->PLLCFGR & RCC_PLLCFGR_PLL1FRACEN) != 0U)
{
pllfracn = (float_t)(uint32_t)(((RCC->PLL1FRACR & RCC_PLL1FRACR_FRACN)>> RCC_PLL1FRACR_FRACN_Pos));
}
else
{
pllfracn = (float_t)0U;
}
if (pllm != 0U)
{
switch (pllsource)
{
case 0x02: /* HSE used as PLL1 clock source */
pllvco = ((float_t)HSE_VALUE / (float_t)pllm) * ((float_t)(uint32_t)(RCC->PLL1DIVR1 & RCC_PLL1DIVR1_DIVN) + (pllfracn/(float_t)0x2000) +(float_t)1 );
break;
case 0x01: /* CSI used as PLL1 clock source */
pllvco = ((float_t)CSI_VALUE / (float_t)pllm) * ((float_t)(uint32_t)(RCC->PLL1DIVR1 & RCC_PLL1DIVR1_DIVN) + (pllfracn/(float_t)0x2000) +(float_t)1 );
break;
case 0x00: /* HSI used as PLL1 clock source */
default:
hsivalue = (HSI_VALUE >> ((RCC->CR & RCC_CR_HSIDIV) >> RCC_CR_HSIDIV_Pos));
pllvco = ( (float_t)hsivalue / (float_t)pllm) * ((float_t)(uint32_t)(RCC->PLL1DIVR1 & RCC_PLL1DIVR1_DIVN) + (pllfracn/(float_t)0x2000) +(float_t)1 );
break;
}
pllp = (((RCC->PLL1DIVR1 & RCC_PLL1DIVR1_DIVP) >> RCC_PLL1DIVR1_DIVP_Pos) + 1U ) ;
sysclk = (uint32_t)(float_t)(pllvco/(float_t)pllp);
}
else
{
sysclk = 0U;
}
break;
default: /* Unexpected, default to HSI used as system clock source (default after reset) */
sysclk = (HSI_VALUE >> ((RCC->CR & RCC_CR_HSIDIV) >> RCC_CR_HSIDIV_Pos));
break;
}
/* system clock frequency : CM7 CPU frequency */
core_presc = (RCC->CDCFGR & RCC_CDCFGR_CPRE);
if (core_presc >= 8U)
{
SystemCoreClock = (sysclk >> (core_presc - RCC_CDCFGR_CPRE_3 + 1U));
}
else
{
SystemCoreClock = sysclk;
}
}
RCC_TypeDef结构体类型定义时钟来源和系统时钟生成配置,本例程使用外部24MHz晶振,通过PLL锁相环后得到600M系统时钟。一般选择使能系统时钟、AHB、APB1和APB2总线时钟。
值得注意的是,这是使用的App部分工程,Boot工程的时钟配置为SystemClock_Config()。
3.GPIO初始化函数
MX_GPIO_Init()函数用于LED的GPIO初始化,代码如下:
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOM_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOO_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOM, LED4_Pin|LED3_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOO, LED1_Pin|LED2_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : LED4_Pin LED3_Pin */
GPIO_InitStruct.Pin = LED4_Pin|LED3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOM, &GPIO_InitStruct);
/*Configure GPIO pin : PD0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF8_UART4;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pins : LED1_Pin LED2_Pin */
GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOO, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
以上就是LED的初始化函数,初始化GPIO有个重要的结构体GPIO_InitTypeDef,先看看该结构体。
/**
* @brief GPIO Init structure definition
*/
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of [url=home.php?mod=space&uid=1064992]@ref[/url] 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 Pull-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 */
uint32_t Alternate; /*!< Peripheral to be connected to the selected pins.
This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;
- Pin:引脚编号选择,一个GPIO有16个引脚可选,参数可选有:GPIO_PIN_0、….、GPIO_PIN_15和GPIO_PIN_ALL。很多时候我们可以使用或运算进行选择:GPIO_PIN_0| GPIO_PIN_4。
- Mode:引脚模式选择,在前文笔者已经介绍了引脚有8中工作模式,根据外设选择相应模式。
Table 6-1 GPIO引脚工作模式
- Pull:上下拉,用于输入模式,可选:GPIO_NOPULL不上下拉;GPIO_PULLUP:使能下拉;GPIO_PULLDOWN使能下拉;
- Speed:引脚速度,可选:GPIO_SPEED_FREQ_LOW:低速(2MHz);中速(10MHz);高速(50MHz)。
在初始化代码中,还有一个重要的函数HAL_GPIO_WritePin()。HAL_GPIO_WritePin()函数为2个LED灯时钟初始化状态,这里设置为低电平,所以初始化状态2个LED都是暗的。
4.流水灯实现
在前文已经贴出了流水灯的相应代码。
while (1)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin);
/* Insert delay 200 ms */
HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
HAL_GPIO_TogglePin函数用于GPIO电平反转,HAL_Delay用于延时,单位是毫秒,以此不断将LED1、LED2、LED3、LED4关灭,这就是实现流水灯的效果。
7 实验现象
将编译好的程序下载到板子中,可以看到四个LED灯不同地闪烁。
本章虽然用不同的方式实现了流水灯,也就是不断控制GPIO的高低电平,我想大家会有很多疑问,寄存器、表混库、HAL的区别是什么,流水灯是怎么一步步调用等等,后面的章节会一一解答,尽请期待!