使用 STM32F103 和 HAL 库实现 PWM 呼吸灯
硬件准备
STM32F103 微控制器开发板、LED 灯、连接线
软件准备
STM32CubeMX、Keil MDK
知识PWM理解:
在PWM输出模式下,CNT为计数器当前值,ARR为自动重装载值,CCRx表示捕获/比较寄存器值。假定定时器工作在向上计数模式,当CNT小于CCRx时,TIMx_CHx通道输出低电平;当CNT等于或大于CCRx时,TIMx_CHx通道输出高电平。当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。从0~t2表示一个周期,即改变 ARR的值,就可以改变PWM输出的频率;t1~t2表示高电平占整个周期的时间,因此改变CCRx的值,就可以改变PWM输出的占空比。
下面开始步骤:
1. 配置 STM32CubeMX
创建新工程
打开 STM32CubeMX 并创建一个新工程。
选择 STM32F103 微控制器。
这里配有那个高速的就行,不用两个都配上。
在 RCC 配置中启用 HSE 和 PLL。
配置 GPIO
选择一个引脚用于 PWM 输出,PA7。
将其模式设置为 PWM Generation CH2。
配置 TIM3
启用 TIM3,启用中断。
设置 Prescaler 和 Counter Period。
Prescaler 设置为 7200-1,这将使定时器时钟降低到 1kHz。
Counter Period 设置为 1000-1,这将产生 10Hz 的 PWM 频率。
说明一下:
根据公式fclk=(Fcore/(Prescaler+1)/(Period+1),结合我的配置,得到fclk=72000000/(71+1)/(999+1)=1000HZ,这个频率不会有太明显的闪烁感。
PWM模式1(向上计数):计数器从0计数加到自动重装载值(TIMx_ARR),然后重新从0开始计数,并且产生-个计数器溢出事件。
PWM模式2(向下计数):计数器从自动重装载值(TIMx_ARR)减到0,然后重新从重装载值(TIMx_ARR)开始递减,并且产生一个计数器溢出事件。
Mode 选择PWM模式1
Pulse(占空比值) 先给0
Fast Mode PWM脉冲快速模式 :和我们配置无关,不使能· PWM 极性: 设置为低电平 PS:由于LED是低电平点亮,所以我们把极性设置为low
配置时钟
最后就是工程设置:
生成代码
点击“Project -> Generate Code”生成 Keil 或 STM32CubeIDE 工程。
下面看代码:
// 系统时钟配置函数 void SystemClock_Config(void) { // 定义并初始化RCC振荡器配置结构体 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // 定义并初始化RCC时钟配置结构体 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // ------------------- RCC振荡器配置部分 -------------------- // 初始化RCC振荡器根据指定的RCC_OscInitTypeDef结构体参数 // 选择振荡器类型为HSE(高速外部振荡器) RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 打开HSE振荡器 RCC_OscInitStruct.HSEState = RCC_HSE_ON; // HSE预分频值为1(即不分频) RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // 打开HSI振荡器(虽然在这里可能不会被使用,但通常作为备用时钟源) RCC_OscInitStruct.HSIState = RCC_HSI_ON; // 打开PLL(相位锁定环) RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 设置PLL的源为HSE RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 设置PLL的倍频因子为9 RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 使用HAL库函数配置振荡器 // 如果配置失败,则调用错误处理函数 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // ------------------- RCC时钟配置部分 -------------------- // 初始化CPU、AHB和APB总线的时钟 // 设置要配置的时钟类型(HCLK: AHB时钟, SYSCLK: 系统时钟, PCLK1/PCLK2: APB1/APB2时钟) RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; // 设置系统时钟源为PLL时钟 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // AHB总线时钟与系统时钟不分频(即相同频率) RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // APB1总线时钟是HCLK的1/2 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB2总线时钟与HCLK相同 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // 使用HAL库函数配置时钟 // 同时设置FLASH的延迟(根据时钟频率选择) // 如果配置失败,则调用错误处理函数 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } }
int main(void) { /* USER CODE BEGIN 1 */ // 定义一个16位无符号整数变量cnt_Pwm,用于PWM的计数值 uint16_t cnt_Pwm=0; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* 重置所有外设,初始化Flash接口和Systick。 */ HAL_Init(); /* USER CODE BEGIN Init */ // 这里是用户自定义的初始化代码区域,但在这个示例中它是空的 /* USER CODE END Init */ /* 配置系统时钟 */ SystemClock_Config(); // 调用系统时钟配置函数 /* USER CODE BEGIN SysInit */ // 这里是用户自定义的系统初始化代码区域,但在这个示例中它是空的 /* USER CODE END SysInit */ /* 初始化所有已配置的外设 */ MX_GPIO_Init(); // 调用GPIO初始化函数 MX_TIM3_Init(); // 调用TIM3定时器初始化函数 /* USER CODE BEGIN 2 */ // 启动TIM3的PWM通道2 HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); /* USER CODE END 2 */ /* 无限循环 */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // 第一个循环:逐渐增加PWM的占空比 while (cnt_Pwm < 1000) { cnt_Pwm++; // 增加计数值 // 设置TIM3的PWM通道2的比较值(即占空比) __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, cnt_Pwm); HAL_Delay(1); // 等待1毫秒 } HAL_Delay(100); // 等待100毫秒 // 第二个循环:逐渐减小PWM的占空比 while (cnt_Pwm) { cnt_Pwm--; // 减少计数值 // 设置TIM3的PWM通道2的比较值(即占空比) __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, cnt_Pwm); HAL_Delay(1); // 等待1毫秒 } HAL_Delay(100); // 等待100毫秒 /* USER CODE END 3 */ } }
主要的功能:
初始化MCU(微控制器)和相关的外设(GPIO和TIM3定时器)。
配置TIM3定时器的PWM通道2,并使其开始工作。
在一个无限循环中,逐渐增加PWM的占空比(从0到1000),等待一段时间,然后逐渐减小PWM的占空比(从1000到0),再次等待一段时间。这个过程不断重复,产生了一个PWM信号的变化效果。
void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ // 用户可以在这里添加TIM3初始化之前的自定义代码 /* USER CODE END TIM3_Init 0 */ // 定义TIM3时钟配置结构体变量 TIM_ClockConfigTypeDef sClockSourceConfig = {0}; // 定义TIM3主输出配置结构体变量 TIM_MasterConfigTypeDef sMasterConfig = {0}; // 定义TIM3输出通道配置结构体变量 TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM3_Init 1 */ // 用户可以在这里添加TIM3初始化之前的其他自定义代码 /* USER CODE END TIM3_Init 1 */ // 初始化htim3变量,指向TIM3硬件实例 htim3.Instance = TIM3; // 设置TIM3的基本配置 htim3.Init.Prescaler = 71; // 预分频器值 htim3.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式 htim3.Init.Period = 999; // 自动重载寄存器的周期值 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频因子 htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 使能自动重载预装载 // 初始化TIM3基本定时器 if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { // 如果初始化失败,调用错误处理函数 Error_Handler(); } // 配置TIM3的时钟源 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 使用内部时钟 if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { // 如果时钟源配置失败,调用错误处理函数 Error_Handler(); } // 初始化TIM3的PWM模式 if (HAL_TIM_PWM_Init(&htim3) != HAL_OK) { // 如果PWM初始化失败,调用错误处理函数 Error_Handler(); } // 配置TIM3的主输出和主从模式 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; // 触发输出复位 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; // 禁用主从模式 if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { // 如果主输出和主从模式配置失败,调用错误处理函数 Error_Handler(); } // 配置TIM3的PWM输出通道2 sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM模式1 sConfigOC.Pulse = 0; // 初始脉冲值(这里设置为0,实际PWM值由__HAL_TIM_SetCompare设置) sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; // 输出极性为低 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 禁用快速模式 if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { // 如果PWM通道配置失败,调用错误处理函数 Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ // 用户可以在这里添加TIM3初始化之后的其他自定义代码 /* USER CODE END TIM3_Init 2 */ // 调用HAL库提供的MspPostInit函数,用于初始化与外设相关的GPIO和NVIC(如果需要) HAL_TIM_MspPostInit(&htim3); }MX_TIM3_Init函数用于初始化TIM3定时器,并配置其PWM输出通道2。
在函数内部,首先定义了一些配置结构体变量,并设置了TIM3的基本参数、时钟源、PWM模式、主输出和主从模式以及PWM输出通道的配置。
如果任何一步的初始化或配置失败,都会调用Error_Handler函数来处理错误。此外,还为用户提供了在初始化前后添加自定义代码的区域。
调用了HAL_TIM_MspPostInit函数,这个函数通常在自动生成的代码中定义,用于初始化与外设相关的GPIO和NVIC。
KEIL下载程序到开发板:
要确保可以找到单片机,才能正常下载程序。
视频效果: