安阳市网站建设_网站建设公司_Java_seo优化
2026/1/12 16:17:25 网站建设 项目流程

第一部分:PWM 基础概念

什么是 PWM?

想象一下用开关控制一个灯泡:

  • 一直开着 → 灯泡最亮
  • 一直关着 → 灯泡熄灭
  • 快速开关(如每秒 1000 次)→ 人眼看到不同亮度

PWM(脉宽调制)就是这个原理,核心是通过调节「高电平占总周期的比例」实现模拟量控制,以下是核心概念的类比与代码映射:

概念类比说明在代码中的体现
周期一次完整开关的时间自动重装载值(ARR=999)
高电平时间开灯的时间比较值(CCR=500)
占空比高电平时间 / 周期CCR/ARR = 500/1000=50%
频率1 秒内开关的次数1MHz/1000=1000Hz(1kHz)

第二部分:呼吸灯完整流程图

第三部分:代码分块详细解释

块 1:主函数 - 程序入口和整体控制

int main(void) { // 1. 初始化HAL库(硬件抽象层) HAL_Init(); // 必须第一个调用,初始化系统 // 2. 配置系统时钟为72MHz SystemClock_Config(); // CPU、外设的工作速度由此决定 // 3. 初始化GPIO(通用输入输出) MX_GPIO_Init(); // 设置引脚功能 // 4. 初始化定时器1为PWM模式 MX_TIM1_Init(); // 核心配置,让定时器产生PWM波 // 5. 启动PWM输出 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 开始输出PWM信号 // 6. 主循环 - 程序永不停止 while (1) { // 呼吸灯逻辑 if (direction == 0) { // 如果是增加亮度方向 pwm_duty += 10; // 占空比增加10(0-1000范围) if (pwm_duty >= 1000) { // 如果达到最大值100%(1000对应1000) direction = 1; // 改为减少方向 } } else { // 如果是减少亮度方向 pwm_duty -= 10; // 占空比减少10 if (pwm_duty <= 0) { // 如果达到最小值0% direction = 0; // 改为增加方向 } } // 更新PWM比较值,改变占空比 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_duty); // 延时20ms,控制呼吸速度 HAL_Delay(20); // 延时20毫秒 } }

块 2:时钟配置 - 芯片的 "心脏"

void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // 定义时钟源结构体 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 定义时钟配置结构体 // 配置时钟源 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 使用外部8MHz晶振 RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开外部晶振 RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; // 不分频 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启PLL锁相环 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL的输入是HSE RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 9倍频: 8MHz×9=72MHz HAL_RCC_OscConfig(&RCC_OscInitStruct); // 应用时钟源配置 // 配置系统时钟 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟用PLL输出 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB总线不分频 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // APB1总线2分频 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2不分频 HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); // 应用配置 }

块 3:GPIO 配置 - 引脚功能设置

static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 定义GPIO配置结构体 // 1. 使能GPIOA的时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); // 打开GPIOA的电源开关 // 2. 配置PA8引脚 GPIO_InitStruct.Pin = GPIO_PIN_8; // 选择第8号引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 模式:复用推挽输出 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 速度:高速模式 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 应用这个配置 }
GPIO 模式解释:
  • 复用推挽输出:引脚被定时器 "借用",非普通 GPIO 输出,专为外设信号输出设计
  • 高速模式:引脚电平翻转速度快,适配 PWM 高频信号的快速切换

块 4:定时器 PWM 配置 - 核心部分

static void MX_TIM1_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; // 时钟源配置 TIM_MasterConfigTypeDef sMasterConfig = {0}; // 主模式配置 TIM_OC_InitTypeDef sConfigOC = {0}; // 输出比较配置 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; // 刹车死区配置 // 1. 打开TIM1的时钟 __HAL_RCC_TIM1_CLK_ENABLE(); // 给TIM1供电 // 2. 基本参数配置 htim1.Instance = TIM1; // 使用定时器1 htim1.Init.Prescaler = 72 - 1; // 预分频72,72MHz/72=1MHz htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数:0,1,2,3...到ARR htim1.Init.Period = 1000 - 1; // 自动重装载值=999 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟不分频 htim1.Init.RepetitionCounter = 0; // 重复计数为0 htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 允许更新ARR HAL_TIM_Base_Init(&htim1); // 初始化定时器基础 // 3. 配置PWM输出 sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM模式1 sConfigOC.Pulse = 0; // 初始比较值=0,0%占空比 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 高电平有效 HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); // 配置通道1 }

块 5:PWM 参数计算公式

核心是理解「定时器时钟 → 预分频 → 周期 → 频率 / 分辨率」的关系:

  1. 定时器时钟 = 系统时钟 = 72MHz
  2. 预分频后时钟 = 72MHz / (Prescaler+1) = 72MHz / 72 = 1MHz
  3. PWM 频率 = 1MHz / (Period+1) = 1MHz / 1000 = 1000Hz(1kHz)
  4. 分辨率 = Period+1 = 1000 级(0-999,对应 0%-100% 占空比)
  5. 占空比 = (Pulse / (Period+1)) × 100%

举例

  • Pulse=250 → 占空比 = 250/1000=25%
  • Pulse=500 → 占空比 = 500/1000=50%
  • Pulse=750 → 占空比 = 750/1000=75%

第四部分:汇总与核心要点

1. PWM 工作流程总结

步骤功能关键函数 / 代码新手重点
时钟使能给外设供电__HAL_RCC_TIM1_CLK_ENABLE()没时钟,外设完全不工作
GPIO 配置设置引脚功能GPIO_MODE_AF_PP必须配置为复用模式,否则无 PWM 输出
定时器基础配置计数参数Prescaler=71, Period=999决定 PWM 频率和调节分辨率
PWM 模式设为 PWM 输出TIM_OCMODE_PWM1模式错误会导致输出异常
启动输出开始产生信号HAL_TIM_PWM_Start()不调用此函数,引脚无波形输出
动态调节改变占空比__HAL_TIM_SET_COMPARE()修改比较值即可实时调占空比

2. 关键参数记忆口诀

一频二分三占空

  • 一频:频率 = 时钟 / (预分频 + 1) / (周期 + 1)
  • 二分:预分频(分频定时器时钟) 和 周期(分频预分频后时钟)
  • 三占空:占空比 = 比较值 / (周期 + 1)

3. 常见问题排查

现象可能原因解决方法
LED 不亮引脚配置错误检查 GPIO 是否设为 AF_PP 模式
LED 常亮不调光PWM 没启动检查 HAL_TIM_PWM_Start () 是否调用
亮度变化不平滑分辨率太低增加 Period 值(如改为 2000-1)
呼吸太快 / 太慢延时不当调整 HAL_Delay () 的参数(如改 10ms/30ms)
频率不对时钟计算错误重新计算预分频和周期值

4. 实际应用扩展

学会呼吸灯后,可扩展到以下场景:

// 1. 控制直流电机速度(70%占空比=70%速度) __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 700); // 2. 控制舵机角度(舵机需50Hz PWM,1-2ms高电平对应角度) __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 150); // 1.5ms脉冲,中位 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 100); // 1.0ms脉冲,左转 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 200); // 2.0ms脉冲,右转 // 3. 控制蜂鸣器音调(通过频率改变音调) HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 产生固定频率发声 HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1); // 停止发声

5. 新手学习建议

  1. 先模仿再创新:完全按上述代码运行,确保呼吸灯正常工作
  2. 修改参数观察:改 Period=500-1(频率 2kHz),看呼吸灯变化
  3. 硬件验证:用示波器观察 PA8 引脚的 PWM 波形,直观理解占空比变化
  4. 扩展实验:改用 TIM2_CH1(PA0)实现相同功能,熟悉不同定时器 / 引脚
  5. 查阅手册:找到芯片「Alternate function」表格,确认引脚复用功能

总结

  1. PWM 的核心本质是「快速开关」,所有控制都是通过调节「开的时间比例(占空比)」实现;
  2. 掌握 PWM 的关键是理解「时钟 - 预分频 - 周期 - 占空比」的计算公式,参数配置错误会直接导致功能异常;
  3. 从 LED 呼吸灯入门,可逐步拓展到电机、舵机、音频等复杂控制场景,核心逻辑一致。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询