电控在二次考核前只有这次培训,这次培训主要给大家讲述考核相关的知识点,和一些使用工具的技巧(调试和使用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
关于调试
这个也是经验,基础的学习其实很简单。现在使用的调试就是打断点,和添加变量看变量的实时变化,再就是单步运行之类的了。
希望大家经过这次的培训能学到一些知识,祝大家通过第二次考核