
一、代码分析1.1 纯软件延时实现 PWM不用 TIM实现原理纯软件模拟 PWM 的核心是完全依靠 CPU 执行指令来手动控制 GPIO 引脚的电平翻转不借助芯片内置的定时器硬件外设。PWM 波形的两个核心参数为周期与占空比软件实现通过固定的循环延时来维持周期稳定在单个周期内分别控制高电平与低电平的持续时长通过改变两段时长的比例即可实现占空比的调节。整个波形生成过程完全依赖 CPU 逐条执行代码每一次电平翻转都需要 CPU 主动调用 GPIO 操作函数完成。设计思路代码以固定的延时单位作为 PWM 的周期基准通过循环变量控制单个周期内高低电平的分配比例。程序设置两个连续的循环结构第一个循环逐步增加低电平的延时时长、对应缩短高电平时长实现占空比的线性递减第二个循环逐步增加高电平的延时时长、对应缩短低电平时长实现占空比的线性递增。两个循环首尾衔接即可实现占空比在 0%~100% 区间内的连续往复变化。#include stm32f10x.h void MY_GPIOInit() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA外设时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 配置引脚为推挽输出模式 GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOA, GPIO_InitStructure); } void MY_Delay(int t) { while(t--); // 空循环消耗CPU时钟实现粗略软件延时 } int main() { MY_GPIOInit(); while(1) { for(int i 0; i 10000; i 3) // i控制低电平时长总周期固定为10000个延时单位 { GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); MY_Delay(i); GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); MY_Delay(10000 - i); } for(int i 0; i 10000; i 3) // i控制高电平时长占空比随i递增逐步升高 { GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); MY_Delay(10000 - i); GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); MY_Delay(i); } } }1.2 硬件定时器实现 PWM用 TIM实现原理硬件定时器 PWM 依托 STM32 内置的通用定时器外设通过输出比较功能由硬件电路自动生成 PWM 波形。定时器内部的计数器 CNT 会按照预设的时钟频率自动累加计数硬件实时将 CNT 的计数值与捕获比较寄存器 CCR 的数值进行比对根据比对结果自动翻转输出引脚的电平状态。当 CNT 计数值达到自动重装载寄存器 ARR 的设定值时计数器自动清零并开启下一个计数周期。其中 ARR 的数值决定 PWM 的周期长度CCR 的数值决定单个周期内有效电平的持续时长即占空比。设计思路代码采用模块化设计将 PWM 的硬件初始化与占空比调节封装为独立函数。初始化流程遵循标准的外设配置逻辑依次完成外设时钟使能、GPIO 引脚复用配置、定时器时基参数配置、输出比较通道模式配置四个步骤最终启动定时器即可让硬件自动输出稳定的 PWM 波形。主循环中通过循环变量逐步修改 CCR 的数值先递增后递减实现占空比在设定范围内的连续往复调节延时函数用于控制占空比变化的速率。main.c#include stm32f10x.h #include Delay.h #include PWM.h int main() { PWM_Init(); uint8_t i; while(1) { for(i 0; i 100; i) { PWM_SetCompare1(i); // 修改CCR值线性提升占空比 Delay_ms(10); } for(i 0; i 100; i) { PWM_SetCompare1(100 - i); // 修改CCR值线性降低占空比 Delay_ms(10); } } }PWM.c#include stm32f10x.h void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2定时器时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA端口时钟 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出引脚控制权交给定时器硬件 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_InternalClockConfig(TIM2); // 选择内部72MHz时钟作为定时器计数时钟源 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; // 设置为向上计数模式 TIM_TimeBaseInitStructure.TIM_Period 100 - 1; // ARR自动重装载值决定PWM周期与分辨率 TIM_TimeBaseInitStructure.TIM_Prescaler 720 - 1; // PSC预分频值分频得到计数频率 TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure); TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // 配置通道为PWM1输出模式 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 设置有效电平极性为高电平 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; // 初始CCR值上电默认占空比为0 TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE); // 启动定时器硬件开始自动输出PWM波形 } void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare); // 修改捕获比较寄存器数值调整占空比 }二、波形显示完成 PWM 代码编写后可直接利用 Keil5 内置的软件仿真功能通过逻辑分析仪工具在线观测引脚输出的波形无需连接实物硬件即可验证 PWM 的周期、占空比变化是否符合设计预期。具体操作流程如下1. 仿真环境参数配置点击工程工具栏的 “魔术棒” 图标打开Options for Target...配置窗口切换到Debug选项卡勾选上方的Use Simulator启用纯软件仿真模式将Dialog DLL栏的内容修改为DARMSTM.DLL加载 STM32 系列对应的仿真驱动库将Parameter栏的内容修改为-pSTM32F103ZE指定仿真的芯片型号确保外设寄存器模型与代码中的 STM32F10x 系列完全匹配。2. 添加引脚观测通道配置完成后点击工具栏的Start/Stop Debug Session按钮进入调试会话在调试界面的菜单栏中调出Logic Analyzer逻辑分析仪窗口点击逻辑分析仪窗口内的Setup...按钮在弹出的配置面板中点击New在输入框中输入PORTA.0并按下回车即将 PA0 引脚添加为波形观测对象与代码中 PWM 的输出引脚对应。3. 启动仿真查看波形关闭配置面板后在逻辑分析仪的波形显示区域右键在弹出的菜单中将显示模式设置为Bit以标准数字电平的形式展示引脚高低电平状态。 设置完成后点击调试工具栏的Run按钮启动仿真即可在逻辑分析仪窗口中看到 PA0 引脚输出的连续 PWM 波形。随着程序循环运行能够直观观察到占空比线性递增、再线性递减的完整变化过程以此验证两种 PWM 实现方式的输出逻辑是否正确。