电控在二次考核前只有这次培训,这次培训主要给大家讲述考核相关的知识点,和一些使用工具的技巧(调试和使用AI),以PWM的学习为例,希望大家认真学习,在下次考核中取得好成果,加入队伍。
PWM学习
什么PWM
PWM 是一种通过周期性脉冲信号的占空比来模拟模拟信号的技术。核心参数包括:
- 周期(Period):一个完整脉冲的时间长度(T),单位通常是毫秒(ms)或微秒(μs)
 - 频率(Frequency):周期的倒数(f=1/T),表示每秒脉冲的数量
 - 占空比(Duty Cycle):高电平时间在一个周期中所占的比例(D = 高电平时间 / T×100%)
 
例如:一个周期为 10ms(频率 100Hz)、占空比 30% 的 PWM 信号,意味着在 10ms 内,3ms 为高电平,7ms 为低电平。
其实,这个在高中就已经接触到类似的知识:平均值


PWM有什么用
PWM(脉冲宽度调制)是嵌入式开发中非常基础且应用广泛的技术,其核心价值在于通过数字信号模拟模拟量控制,实现对各种外设的精准调节。以下是 PWM 的主要应用场景,结合 STM32 等嵌入式系统的实际开发场景说明:
1. 灯光亮度调节(最基础应用)
- 原理 :LED 的亮度由流过的电流决定,而 PWM 通过改变高电平占空比,调节单位时间内的平均电流(占空比越高,平均电流越大,亮度越高)。
 - 优势 :相比直接用 DAC(数模转换器)调节电压,PWM 仅需普通 GPIO(复用为定时器通道)即可实现,硬件成本低,且调节范围更广(0~100% 占空比对应 0~ 最大亮度)。
 - 示例 :STM32 通过 PWM 控制 LED 实现 "呼吸灯" 效果(占空比从 0 逐渐增加到 100%,再逐渐减小,模拟呼吸节奏)。
 
2. 电源与能量控制(高效节能)
DC-DC 转换器 :在开关电源中,PWM 控制功率开关管(MOS 管、三极管)的导通 / 关断时间,通过电感、电容储能释放,将输入电压转换为稳定的输出电压(如 5V 转 3.3V)。相比线性稳压器(如 LDO),PWM 开关电源效率更高(通常 80%~95%),适合大电流场景。
3. 音频信号生成(简单发声场景)
蜂鸣器发声 :无源蜂鸣器需要外部交流信号驱动,PWM 的频率决定音调(如 440Hz 对应 "A 调"),占空比决定音量(占空比 50% 时音量最大)。STM32 通过改变定时器的 ARR 值(调整频率)和 CCRx 值(调整占空比),可实现不同音调、音量的报警声或简单音乐。
如何实现PWM(呼吸灯)
呼吸灯 :用定时器实现呼吸灯的核心原理是通过动态改变 PWM 的占空比,使 LED 的平均电流随时间线性变化,从而呈现 "呼吸" 效果。

stm32如何代码实现:
1.只用定时器基本功能
#include "main.h"
TIM_HandleTypeDef htim3;
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 呼吸灯参数
uint16_t bright_time = 1;   // 亮的时间(单位:10ms,初始10ms)
uint16_t dark_time = 99;    // 灭的时间(单位:10ms,初始990ms)
int8_t step = 1;            // 步长(每次增减1)
uint8_t led_state = 0;      // LED状态(0:灭,1:亮)
uint16_t counter = 0;       // 计数变量(累计中断次数)
void SystemClock_Config(void);
static void MX_TIM3_Init(void);
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  
  // 初始化LED引脚(PA6)
  __HAL_RCC_GPIOA_CLK_ENABLE();
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  MX_TIM3_Init();
  HAL_TIM_Base_Start_IT(&htim3);  // 启动定时器中断
  while (1)
  {
    // 主循环空闲,中断中处理核心逻辑
  }
}
// 定时器初始化
static void MX_TIM3_Init(void)
{
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 7199;       // 72MHz/(7199+1)=10kHz(0.1ms/计数)
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 99;            // 99+1=100计数 → 定时100×0.1ms=10ms
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
}
// 定时器更新中断回调函数(每10ms触发一次)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim == &htim3)
  {
    counter++;  // 累计中断次数(每次10ms)
    
    if (led_state == 1)  // 当前LED亮
    {
      // 亮的时间达到设定值,切换为灭
      if (counter >= bright_time)
      {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_RESET);  // 灭
        led_state = 0;
        counter = 0;  // 重置计数器
      }
    }
    else  // 当前LED灭
    {
      // 灭的时间达到设定值,切换为亮
      if (counter >= dark_time)
      {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);  // 亮
        led_state = 1;
        counter = 0;  // 重置计数器
        
        // 调整亮/灭时间比例(模拟占空比变化)
        bright_time += step;
        dark_time -= step;
        
        // 到达边界时反向调整
        if (bright_time >= 99) step = -1;
        if (bright_time <= 1) step = 1;
      }
    }
  }
}
// 系统时钟配置(CubeMX自动生成)
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}
void Error_Handler(void)
{
  while(1) {}
}
        2.使用pwm输出
#include "main.h"
TIM_HandleTypeDef htim3;
// PWM占空比参数
uint16_t pwm_duty = 0;   // 比较值(0~999,对应0~100%占空比)
int8_t step = 5;         // 步长(每次增减5)
void SystemClock_Config(void);
static void MX_TIM3_Init(void);
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_TIM3_Init();
  
  // 启动TIM3_CH1的PWM输出
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  while (1)
  {
    // 更新PWM占空比(核心函数)
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwm_duty);
    
    // 延时20ms,控制呼吸速度
    HAL_Delay(20);
    
    // 调整占空比,到达边界时反向
    pwm_duty += step;
    if (pwm_duty >= 999) step = -5;
    if (pwm_duty <= 0) step = 5;
  }
}
// 定时器及PWM初始化
static void MX_TIM3_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 71;       // 72MHz/(71+1)=1MHz(1μs/计数)
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 999;         // 周期1000×1μs=1ms(频率1kHz)
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 配置时钟源为内部时钟
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 初始化PWM模式
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 主模式配置(默认即可)
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  // PWM通道1配置
  sConfigOC.OCMode = TIM_OCMODE_PWM1;  // PWM模式1:CNT < CCRx时输出高电平
  sConfigOC.Pulse = 0;                 // 初始比较值(占空比0)
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;  // 高电平有效
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  
  // 初始化复用GPIO(PA6)
  HAL_TIM_MspPostInit(&htim3);
}
// 复用GPIO初始化(CubeMX自动生成,在HAL_TIM_MspPostInit中调用)
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(timHandle->Instance==TIM3)
  {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    // PA6 -> TIM3_CH1
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}
// 系统时钟配置(同前,省略)
void SystemClock_Config(void) { /* ... */ }
void Error_Handler(void) { while(1) {} }
        关于AI
上面都是用ai写的,其实能够灵活地运用ai是一个非常重要的技能。ai的使用是有技巧的。随着机器学习的普及,相信大家在日常生活中也会听到"训练"这个词,在使用ai时我们要合理地投喂ai,才能从ai得到我们想要的结果。
例如,你想要ai帮你生成呼吸灯的代码,你不能直接跟ai说"我需要呼吸灯的代码,给我生成一份",这样得到的代码往往不能令人满意。如果你懂得训练ai,你可能会先问他一些相关的问题,比如"呼吸灯的原理是什么","pwm是什么","我是stm32 hal库的初学者,请你教教我如何用简单的定时器中断实现呼吸灯",往往会得到想要的结果。(事先投喂相关的知识,加上限制词,是很好的方法)
另外,不要盲目的相信ai(尤其是豆包)。我之前考核时就犯了这个错误,当时也是糊涂了,豆包给我生成的有错误,我就一直问"为什么xxx不符合预期结果",其实,对于ai生成有错误的,我们应该汲取有用的,对于可能导致出现问题的,单独进行提问,而不是把一整套发上去,盲目地提问,要有针对性
其他地没办法多说,这个在后续使用过程中汲取经验也能学会,在不明白可以去b站上学一下如何使用ai(没有废物的ai,只有废物的使用者,豆包除外)
https://www.doubao.com/thread/wfa88382ee212c3c7
关于调试
这个也是经验,基础的学习其实很简单。现在使用的调试就是打断点,和添加变量看变量的实时变化,再就是单步运行之类的了。
希望大家经过这次的培训能学到一些知识,祝大家通过第二次考核