STM32F103 定时器与 PWM 呼吸灯实验(CubeMX + HAL 库)
文章目录
- STM32F103 定时器与 PWM 呼吸灯实验(CubeMX + HAL 库)
- 一、实验要求
- 二、硬件接线
- 三、CubeMX 配置
- 1. 新建工程和选芯片
- 2. RCC 和 SYS
- 3. 时钟树
- 4. 任务一的 TIM3 配置
- 5. 生成工程
- 四、任务一:定时器中断控制 LED
- 1. 思路
- 2. 主要代码
- 3. 定时时间
- 4. 现象
- 五、任务二:PWM 呼吸灯
- 1. 思路
- 2. 主要代码
- 3. PWM 频率和占空比
- 4. 现象
- 六、Keil 仿真和 Logic Analyzer
- 1. 设置 Use Simulator
- 2. 进入仿真
- 3. 打开 Logic Analyzer
- 4. 看 PWM 波形
- 5. 看任务一的波形
- 七、实验总结
- 八、参考资料
一、实验要求
- 使用 TIM2~TIM5 中的一个定时器通道,连接 LED,用定时器计数方式控制 LED 以 2 s 的周期亮灭。
- 使用定时器 PWM 模式,让 LED 渐亮渐灭,呼吸周期 1~2 s,并调整占空比到比较舒服的效果。
- 使用 Keil 的 Logic Analyzer 观察 PWM 输出波形。
二、硬件接线
LED 采用高电平点亮接法:
PA6 ── LED 阳极 ── LED 阴极 ── GND三、CubeMX 配置
1. 新建工程和选芯片
打开 STM32CubeMX,点File → New Project,在Part Number里输入STM32F103C8,选择STM32F103C8Tx,然后点Start Project。
2. RCC 和 SYS
在System Core → RCC里,把High Speed Clock (HSE)选成Crystal/Ceramic Resonator。板子上是 8 MHz 外部晶振。
在System Core → SYS里,把Debug选成Serial Wire,保留 SWD 下载口。
3. 时钟树
点上面的Clock Configuration,主要设置如下:
- PLL Source 选 HSE
- PLLMul 选 ×9
- System Clock Mux 选 PLLCLK
- AHB 不分频
- APB1 选 /2
- APB2 选 /1
这样 SYSCLK 就是 8 MHz × 9 = 72 MHz,HCLK 和 APB2 是 72 MHz,APB1 是 36 MHz。
4. 任务一的 TIM3 配置
任务一只需要 TIM3 定时,不需要它控制引脚,所以 PA6 还是普通 GPIO 输出。
在Timers → TIM3里这样设置:
Clock Source:Internal Clock
Prescaler:7199
Counter Mode:Up
Counter Period:9999
Auto-reload Preload:Enable
打开
TIM3 global interruptNVIC 里 TIM3 的优先级保持默认或者设成 2 都可以
然后回到引脚图,把 PA6 设成GPIO_Output,输出电平先设成 Low,速度 Low 就行。
5. 生成工程
在Project Manager里把工程名写成STM32F103C8T6_TIM3_PWM,Toolchain/IDE 选MDK-ARM V5,然后点GENERATE CODE。
四、任务一:定时器中断控制 LED
1. 思路
TIM3 每 1 s 产生一次更新中断,在中断回调里翻转 PA6。PA6 高电平 LED 亮,低电平 LED 灭,所以 LED 亮 1 s、灭 1 s,一个完整周期是 2 s。
2. 主要代码
任务一的初始化:
staticvoidMX_TIM3_TimeBase_Init(void){TIM_ClockConfigTypeDef sClockSourceConfig={0};TIM_MasterConfigTypeDef sMasterConfig={0};__HAL_RCC_TIM3_CLK_ENABLE();htim3.Instance=TIM3;htim3.Init.Prescaler=7199U;htim3.Init.CounterMode=TIM_COUNTERMODE_UP;htim3.Init.Period=9999U;htim3.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;htim3.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;HAL_TIM_Base_Init(&htim3);sClockSourceConfig.ClockSource=TIM_CLOCKSOURCE_INTERNAL;HAL_TIM_ConfigClockSource(&htim3,&sClockSourceConfig);sMasterConfig.MasterOutputTrigger=TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim3,&sMasterConfig);HAL_NVIC_SetPriority(TIM3_IRQn,2U,0U);HAL_NVIC_EnableIRQ(TIM3_IRQn);}主函数里启动更新中断:
if(HAL_TIM_Base_Start_IT(&htim3)!=HAL_OK){Error_Handler();}中断服务函数在stm32f1xx_it.c里:
voidTIM3_IRQHandler(void){HAL_TIM_IRQHandler(&htim3);}翻转 LED 的回调:
voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM3){HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_6);}}3. 定时时间
TIM3 的时钟是 72 MHz,PSC=7199,所以计数频率是 10 kHz。ARR=9999,计数器从 0 计到 9999 是 10000 个数:
10 kHz 计 10000 个数 = 1 s中断里每 1 s 翻转一次 PA6,所以 LED 亮 1 s、灭 1 s,完整周期 2 s。
4. 现象
烧进去以后,PA6 上的 LED 会稳定地亮 1 s、灭 1 s。这个时间由定时器决定,不受主循环里其他代码影响。
五、任务二:PWM 呼吸灯
1. 思路
PSC=71,ARR=999,PWM 频率就是 1 kHz。CCR1 从 0 增加到 1000,再减小回 0,LED 就会从灭逐渐变亮,再逐渐变灭。
每 10 ms 改一次 CCR1,上升 100 次、下降 100 次,共 200 次,呼吸周期大约是 2 s。
2. 主要代码
PWM 初始化:
staticvoidMX_TIM3_PWM_Init(void){TIM_ClockConfigTypeDef sClockSourceConfig={0};TIM_MasterConfigTypeDef sMasterConfig={0};TIM_OC_InitTypeDef sConfigOC={0};__HAL_RCC_TIM3_CLK_ENABLE();htim3.Instance=TIM3;htim3.Init.Prescaler=71U;htim3.Init.CounterMode=TIM_COUNTERMODE_UP;htim3.Init.Period=999U;htim3.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;htim3.Init.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;HAL_TIM_Base_Init(&htim3);sClockSourceConfig.ClockSource=TIM_CLOCKSOURCE_INTERNAL;HAL_TIM_ConfigClockSource(&htim3,&sClockSourceConfig);HAL_TIM_PWM_Init(&htim3);sMasterConfig.MasterOutputTrigger=TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim3,&sMasterConfig);sConfigOC.OCMode=TIM_OCMODE_PWM1;sConfigOC.Pulse=0U;sConfigOC.OCPolarity=TIM_OCPOLARITY_HIGH;sConfigOC.OCFastMode=TIM_OCFAST_DISABLE;HAL_TIM_PWM_ConfigChannel(&htim3,&sConfigOC,TIM_CHANNEL_1);}启动 PWM:
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,0U);HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);呼吸灯循环:
#defineBREATH_STEP_MS10U#defineBREATH_STEPS100U#definePWM_FULL_SCALE1000Uuint32_tn;uint32_tphase;uint32_tduty;while(1){for(n=0U;n<(2U*BREATH_STEPS);n++){if(n<=BREATH_STEPS){phase=n;}else{phase=(2U*BREATH_STEPS)-n;}/* 平方映射,低亮度区停留更久,看起来更柔和 */duty=(phase*phase*PWM_FULL_SCALE)/(BREATH_STEPS*BREATH_STEPS);__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);HAL_Delay(BREATH_STEP_MS);}}3. PWM 频率和占空比
计数频率:
72 MHz / (71 + 1) = 1 MHzPWM 频率:
1 MHz / (999 + 1) = 1 kHz占空比:
D = CCR1 / (ARR + 1) = CCR1 / 1000所以 CCR1=0 是 0%,CCR1=500 是 50%,CCR1=1000 是 100%。PWM 周期是 1 ms。
4. 现象
LED 会慢慢变亮,再慢慢变暗,周期大约 2 s。因为 PWM 是 1 kHz,正常看的时候看不到灯在闪,只能看到平均亮度在变。
六、Keil 仿真和 Logic Analyzer
1. 设置 Use Simulator
打开 Keil 工程,点Options for Target,进Debug页:
- 左边选
Use Simulator Dialog DLL填DARMSTM.DLLParameter填-pSTM32F103C8
2. 进入仿真
按Ctrl+F5进入调试。进入后,Keil 左下角会显示Simulation,左边能看到寄存器在变化。
3. 打开 Logic Analyzer
在仿真状态下点:
View → Analysis Windows → Logic Analyzer4. 看 PWM 波形
按F5全速跑一会儿,再按Esc停下来。把时间轴缩到 0~3 ms 左右,就能看到 1 ms 一个周期的 PWM 波形。
测量的时候看两点:
TIM3_CNT从 0 计到 999,一个锯齿波是 1 ms。PA6的高电平时间除以 1 ms,就是占空比。比如高电平 0.5 ms,就是 50%。
前两张图片放大看出PA6占空比一直在变化,后两张图可看出整体变化。
5. 看任务一的波形
把main.c里的DEMO_MODE改成1U,重新编译,再按Ctrl+F5进仿真。把 Logic Analyzer 的时间轴调到 3 s 左右,添加TIM3_CNT、TIM3_SR、GPIOA_ODR,就能看到 PA6 每 1 s 翻转一次。
从第二张图红色圆圈部分不难看出 PA6 每 1 s 翻转一次
七、实验总结
这次实验主要是把定时器的 PSC、ARR 和 CCR 弄明白。任务一里 TIM3 只负责定时,中断里翻转 PA6;任务二里 TIM3_CH1 直接输出 PWM,CCR1 决定高电平时间。
我一开始把 APB1 的 36 MHz 当成 TIM3 的时钟,算出来时间差了一倍,后来才注意到 APB1 分频不为 1 时,定时器时钟会自动倍频到 72 MHz。还有 PA6 在任务一里要设成普通 GPIO 输出,任务二里要设成复用推挽,这两个地方不能配错。
呼吸灯部分,线性改占空比也能亮,但低亮度变化有点快,改成平方映射以后看着更顺一点。整体上,用 Keil 的逻辑分析仪把 CNT、CCR1 和输出波形对起来看,比只看灯的现象直观很多。
八、参考资料
- STM32F103C8T6 数据手册,PA6/TIM3_CH1 引脚复用表。
- 《STM32F10xxx 参考手册》V10,第 13 章通用定时器,PWM 模式部分。
- STM32CubeMX & Keil——STM32F103C8T6:PWM 控制 LED 灯,https://blog.csdn.net/Morzart/article/details/134123197
- STM32CubeMX PWM 输出实验,https://blog.csdn.net/Xkccsdn147/article/details/134191212
- STM32 定时器 PWM 呼吸灯实验,https://blog.csdn.net/weixin_52345779/article/details/135313203