STM32 CubeMX学习:2. 闪烁LED

STM32 CubeMX学习:2. 闪烁LED

系列文章目录
  1. 前期的准备
  2. 点亮 LED
  3. 闪烁 LED
  4. 定时器闪烁LED
  5. PWM控制LED亮度
  6. 常见的PWM设备
  7. 按键的外部中断
  8. ADC模数转换
  9. 串口收发
  10. 串口打印遥控器数据
  11. 未完待续…


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选项,在该选项下可以看到已经开启的引脚的配置信息,如下图所示:
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>&copy; 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点亮图
LED熄灭
LED熄灭图

代码我已经放到了我的GitHub仓库,如有需要可以下载使用:
CubeMX学习


总结

延时操作也是STM32中的基本操作,本节课介绍的三种延时方式各有特点,调用HAL库提供的HAL_Delay函数最为方便,但是无法完成微秒级的延时,可以通过自己定义的nop延时或者计数延时的方法来进行实现。 阿巴阿巴,这次的博客的确有点长,大家可能已经晕菜 不过大家不要担心,现在开始敲黑板啦

其实我们主要掌握的内容是
1、计时延迟(需要自己编写函数)
2、nop延迟 (需要自己编写函数)
3、HAL_Delay 延时
4、滴答计时器以及HAL_Init初始化
5、HAL_GPIO_TogglePin函数(引脚电平的翻转)


  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,你需要在STM32CubeMX中配置定时器和GPIO引脚。以下是一个基本的步骤: 1. 打开STM32CubeMX并选择你的芯片型号。 2. 在"Pinout & Configuration"选项卡中,选择一个GPIO引脚作为LED的控制引脚。 3. 在"Clock Configuration"选项卡中,启用定时器时钟。 4. 在"Configuration"选项卡中,选择一个定时器,并设置它的时钟源和分频器。 5. 在"NVIC Settings"选项卡中,启用定时器中断(如果需要)。 接下来,你需要在代码中编写相关的控制逻辑。以下是一个示例代码: ``` #include "main.h" #include "tim.h" void LED_Toggle(void) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } int main(void) { HAL_Init(); MX_GPIO_Init(); MX_TIM2_Init(); HAL_TIM_Base_Start_IT(&htim2); while (1) { // Your main code here } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { LED_Toggle(); } } ``` 在这个例子中,我们使用TIM2定时器来控制LED闪烁。首先,我们在main函数中启动了定时器,并在while循环中执行其他操作。然后,在HAL_TIM_PeriodElapsedCallback回调函数中,我们切换LED的状态。这个回调函数会在定时器的计数器达到设定的周期时被调用。在这个例子中,LED的状态会每个定时器周期切换一次。 注意,这个例子仅供参考,你需要根据具体的要求进行修改。例如,你可能需要改变定时器的周期和占空比,或者使用不同的GPIO引脚和定时器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值