STM32 CubeMX学习:2. 闪烁LED
系列文章目录文章目录
0.前言
前2篇博客,我们已经学会了如何通过配置引脚的高低电平点亮LED,但是LED不能总是开着鸭,或者做的更加酷炫一点,让它闪烁起来。这篇博客我来带领大家做一个可以闪烁的LED小工程。
咳咳咳,我们现在开始吧。
1.基础学习
1.1 学习目标
今天,我们将学习如何控制STM32的电平变化,学习STM32中的三种延时方法,了解Hal_Init函数的作用,了解滴答定时器的原理及作用。使用CubeMX软件完成引脚的配置,然后编写程序,通过引脚电平反转翻转函数HAL_GPIO_TogglePin和延时函数,实现LED灯的闪烁效果。
1.2 GPIO的反转速度
1、在不同情况下,需要GPIO引脚会输出不同频率的方波。例如在led闪烁时,只需要1Hz的频率就可以看到明显的闪烁效果,但在软件模拟I2C时,需要数百KHz频率才能完成正常的通信。在cubeMX中可以根据不同的输出频率需求,设置GPIO不同的翻转速度。
2、下面我们直接开始使用CubeMX进行代码的生成吧!!!
(1)采用上一篇博客介绍的方法开启PF9,PF10引脚的输出功能。
(2)在cubeMX的左侧边栏中的System Core下有GPIO选项,在该选项下可以看到已经开启的引脚的配置信息,如下图所示:
(3)在这个标签页下,可以选中需要配置的GPIO,并查看其详细状态,其中Maximum output speed就是可以选择的翻转速度模式,如图所示,对应的翻转模式是Low,为低速输出模式。
3、点击右端小箭头,可选的输出速度分为Low,Medium,High,Very High四档,如图所示。一般使用GPIO输出驱动LED等功能时选择Low档翻转速度即可,而一般用于通信的GPIO需要设置为High或者Very High,具体设置可以根据相关通信协议对GPIO的翻转速度的要求进行设置。
1.3 三种延时方式
1.3.1 计时延迟
在 STM32 内执行任何一条指令是需要消耗时间的。实现延时效果可以让 STM32 持续执行一段计数循环,直到其计数到设定的数目后,再让 STM32 退出循环。
在程序中,通过user_delay_us函数完成微秒级的延时功能,函数的实现如下所示:
void user_delay_us(uint16_t us)
{
for(; us > 0; us--)
{
for(uint8_t i = 50; i > 0; i--)
{
;
}
}
}
这里我们将内层循环变量i初始化为50,外层循环变量的赋值则由函数的入口参数来决定,入口参数对应了外层循环的次数,继而相应的实现了对应时长的延时。
类似于微秒级延迟的实现,再加一层外部循环便可以实现毫秒级的延迟,对应函数为user_delay_ms,其具体实现如下所示。该函数调用了user_delay_us函数,并给其参数赋值1000,1毫秒等于1000微秒。通过循环调用的方式,将延时时间折算到毫秒级。
void user_delay_ms(uint16_t ms)
{
for(; ms > 0; ms--)
{
user_delay_us(1000);
}
}
1.3.2 nop延时
使用nop函数是第二种延时方法,其原理与计时延时类似。同样是通过重复执行指令,直到消耗时间,再进行下一步的工作。但与计数延时不同的是,nop延时通过空操作指令__nop()函数来实现延时。当stm32执行到__nop()时,可以理解为在当前指令周期中,stm32没有进行任何工作。
同样是通过循环的方式,可以完成微秒级的延迟nop_delay_us和nop_delay_ms
void nop_delay_us(uint16_t us)
{
for(; us > 0; us--)
{
for(uint8_t i = 10; i > 0; i--)
{
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
}
}
}
void nop_delay_ms(uint16_t ms)
{
for(; ms > 0; ms--)
{
nop_delay_us(1000);
}
}
1.3.3 滴答计时器介绍以及HAL_Init初始化
在介绍第三种延时方法之前,先介绍滴答定时器及其初始化。
滴答定时器也称为SysTick,是STM32内置的倒计时定时器,每当计数到0时,触发一次SysTick中断,并重载寄存器值。滴答计时器的初始化在HAL_Init函数中完成,配置成1ms的中断。HAL_Init的内容如下,通过注释了解到:在HAL_Init中,实现了SysTick以及底层硬件的初始化。
HAL_StatusTypeDef HAL_Init(void)
{
/* Configure Flash prefetch, Instruction cache, Data cache */
#if (INSTRUCTION_CACHE_ENABLE != 0U)
__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
#if (DATA_CACHE_ENABLE != 0U)
__HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */
#if (PREFETCH_ENABLE != 0U)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
HAL_InitTick(TICK_INT_PRIORITY);
/* Init the low level hardware */
HAL_MspInit();
/* Return function status */
return HAL_OK;
}
在HAL_Init的实现中,我门需要关注滴答定时器初始化函数。
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority);
HAL_InitTick函数将设定SysTick的定时周期为1ms,即频率为1000Hz,并使SysTick开始工作。
每当滴答计时器递减到0时,会触发中断,使程序进入SysTick中断处理函数。
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
在其中会调用HAL_IncTick函数,该函数实现如下
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
在uwTick变量中存储的是从STM32的Systick初始化以来所经过的时间(ms),uwTick的存在相当于给整个程序提供了一个绝对的时间基准,而HAL_Delay函数延时功能便是通过uwTick的值完成的。
获取当前的uwTick值可以使用HAL库提供的函数HAL_GetTick
uint32_t HAL_GetTick(void)
该函数的返回值是当前时刻的uwTick,此函数的作用就是返回当前时刻的uwTick值
1.3.4 HAL_Delay 延时
现在有了上面的基础,我们终于可以隆重地介绍第三种延时——HAL_Delay函数,它是由HAL库提供的用于毫秒级延迟的函数
(使用_weak修饰符说明该函数是可以用户重定义的)。
__weak void HAL_Delay(uint32_t Delay)
该函数的返回值为void,其作用在于使系统延迟对应的毫秒级时间,其参数是Delay,对应的延迟毫秒数,比如延迟1秒就为1000。
在HAL_Delay的实现中,调用HAL_GetTick函数获取基准时间uwTick,说明了HAL_Delay函数的实现是基于滴答计时器(Systick)。
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
以上三种延时方法各自有各自的特点,计数延时与nop延时都需要用户编写函数来自行实现,比较麻烦,而直接调用HAL库提供的HAL_Delay函数会更加方便一些。但HAL_Delay只能够实现毫秒级的延时,如果需要时间更短的延时函数则必须使用用户编写的延时函数。
1.3.5 HAL_GPIO_TogglePin函数
1、这次使用的cubeMX配置与之前的相同。
之前我已经介绍了操作GPIO电平的函数:HAL_GPIO_WritePin,通过这个函数可以设置GPIO输出高电平或者低电平。为了实现LED灯的闪烁,只要交替输出高低电平即可,因此可以通过两句HAL_GPIO_WritePin,各自分别将引脚设置为高电平和低电平,就可以实现闪烁功能。/2、但是除了以上方法之外,这里我们还有更简便的函数来实现引脚电平翻转——HAL库提供的HAL_GPIO_TogglePin函数。
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
这个函数的返回值为void,其作用在于翻转对应引脚的电平,该函数有两个参数
(1)GPIOx——对应GPIO总线,其中x可以是A…I。
(例如PF9,则输入GPIOF)
(2)GPIO_Pin——对应引脚数。可以是0-15。
(例如PF9,则输入GPIO_PIN_9)
1.4 程序学习
程序经过HAL_Init初始化,SystemClock初始化,GPIO初始化后,进入主循环,在主循环内分别使用之前介绍过的三种方式进行延时,然后将LED的电平翻转,从而使LED按照500毫秒的固定频率进行亮灭,主循环代码如下。
bsp_led_toggle();
//nop delay
nop_delay_ms(500);
bsp_led_toggle();
//cycly count
delay user_delay_ms(500);
bsp_led_toggle();
//systick delay
HAL_Delay(500);
整体main.c文件如下所示哦
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void bsp_led_toggle(void);
void user_delay_us(uint16_t us);
void user_delay_ms(uint16_t ms);
void nop_delay_us(uint16_t us);
void nop_delay_ms(uint16_t ms);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/**
* @brief Toggle the red led AND green led led
* @param[in] none
* @retval none
*/
/**
* @brief 反转红灯,绿灯电平
* @param[in] none
* @retval none
*/
void bsp_led_toggle(void)
{
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
}
/**
* @brief use nop function to wait a time
* @param[in] us: us microseconds
* @retval none
*/
/**
* @brief 使用nop函数延迟一段时间
* @param[in] us:us微秒
* @retval none
*/
void nop_delay_us(uint16_t us)
{
for(; us > 0; us--)
{
for(uint8_t i = 10; i > 0; i--)
{
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
__nop();
}
}
}
/**
* @brief use nop_delay_us wait a time
* @param[in] ms: ms milliseconds
* @retval none
*/
/**
* @brief 使用nop_delay_us函数延迟一段时间
* @param[in] ms:ms毫秒
* @retval none
*/
void nop_delay_ms(uint16_t ms)
{
for(; ms > 0; ms--)
{
nop_delay_us(1000);
}
}
/**
* @brief use cycle count to wait a time
* @param[in] us: us microseconds
* @retval none
*/
/**
* @brief 使用循环计数延迟一段时间
* @param[in] us:us微秒
* @retval none
*/
void user_delay_us(uint16_t us)
{
for(; us > 0; us--)
{
for(uint8_t i = 50; i > 0; i--)
{
;
}
}
}
/**
* @brief use user_delay_us function to wait a time
* @param[in] ms: ms milliseconds
* @retval none
*/
/**
* @brief 使用user_delay_us函数延迟一段时间
* @param[in] ms:ms毫秒
* @retval none
*/
void user_delay_ms(uint16_t ms)
{
for(; ms > 0; ms--)
{
user_delay_us(1000);
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* 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)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
bsp_led_toggle();
//nop delay
nop_delay_ms(500);
bsp_led_toggle();
//cycle count delay
user_delay_ms(500);
bsp_led_toggle();
//systick delay
HAL_Delay(500);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 6;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
1.5 效果展示
单片机上的LED灯会以约500ms跳变一次,呈现闪烁的效果。如图所示,分别为单片机LED点亮和熄灭效果。
LED点亮图
LED熄灭图
代码我已经放到了我的GitHub仓库,如有需要可以下载使用:
CubeMX学习
总结
延时操作也是STM32中的基本操作,本节课介绍的三种延时方式各有特点,调用HAL库提供的HAL_Delay函数最为方便,但是无法完成微秒级的延时,可以通过自己定义的nop延时或者计数延时的方法来进行实现。 阿巴阿巴,这次的博客的确有点长,大家可能已经晕菜 不过大家不要担心,现在开始敲黑板啦其实我们主要掌握的内容是
1、计时延迟(需要自己编写函数)
2、nop延迟 (需要自己编写函数)
3、HAL_Delay 延时
4、滴答计时器以及HAL_Init初始化
5、HAL_GPIO_TogglePin函数(引脚电平的翻转)