在工业自动化和小型机电控制系统中,步进电机的精确控制一直是开发者面临的经典挑战。传统方案往往需要额外硬件或复杂代码来实现脉冲计数和频率调节,而STM32F103C8T6的主从定时器模式提供了一种硬件级解决方案。本文将彻底拆解如何用TIM1+TIM2组合实现频率、占空比、脉冲数的独立控制,这种方案特别适合资源受限但要求高精度的应用场景。
STM32的定时器互联功能允许不同定时器之间形成触发链。在本方案中:
这种硬件级联的优势在于:
要实现精准控制,必须理解三个核心参数的数学关系:
| 参数 | 计算公式 | 示例值(72MHz系统时钟) |
|---|---|---|
| PWM频率 | Fpwm = 72MHz/(PSC+1)/(ARR+1) | 10kHz (PSC=71, ARR=99) |
| 占空比 | Duty = CCR/(ARR+1)*100% | 50% (CCR=50) |
| 脉冲数 | 直接写入TIM2.ARR寄存器 | 200脉冲(ARR=199) |
特别注意:当需要动态调整频率时,必须同步更新TIM1的PSC和ARR值,保持:
c复制// 动态修改频率示例
void set_PWM_freq(uint32_t freq) {
uint16_t arr = (72000000 / freq) - 1;
TIM1->PSC = 0; // 预分频清零
TIM1->ARR = arr;
TIM1->CCR1 = arr/2; // 保持50%占空比
}
TIM1需要配置为PWM模式并启用主模式输出,关键步骤包括:
c复制RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_Prescaler = 71; // 1MHz计数频率
TIM_TimeBaseStruct.TIM_Period = 999; // 1kHz PWM
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);
c复制TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 500; // 50%占空比
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
c复制TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);
TIM2需要设置为从模式并启用中断:
c复制// 时基配置
TIM_TimeBaseStruct.TIM_Period = 199; // 计数200个脉冲
TIM_TimeBaseStruct.TIM_Prescaler = 0; // 不分频
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// 从模式配置
TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0); // 连接TIM1
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_External1);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// NVIC配置
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_SetPriority(TIM2_IRQn, 0);
当频繁修改定时器参数时,可能出现脉冲丢失现象。解决方法:
TIM_GenerateEvent(TIM1, TIM_EventSource_Update)手动触发更新事件为确保精确停止,中断服务函数应优化为:
c复制void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update)) {
TIM1->CR1 &= ~TIM_CR1_CEN; // 直接操作寄存器停止
TIM2->CR1 &= ~TIM_CR1_CEN;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
需要同时修改频率和脉冲数时,推荐流程:
对于高精度应用,建议:
在需要节能的场景下:
c复制// 进入停止模式前
TIM1->BDTR |= TIM_BDTR_MOE; // 保持PWM输出
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// 唤醒后
SystemInit(); // 重建时钟
TIM_Reinit(); // 重新初始化定时器
当需要控制多个步进电机时,可采用:
配置示例:
c复制// 双定时器主从配置
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
// DMA配置
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM1->CCR1;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)ccr_values;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_Init(DMA1_Channel5, &DMA_InitStruct);
实现S型加减速时,可以预计算CCR值表:
python复制# Python示例:生成S曲线参数
import math
def s_curve(t, total_time, max_freq):
x = t / total_time
return int(max_freq * (0.5 - 0.5*math.cos(x*math.pi)))
ccr_table = [s_curve(t, 1000, 10000) for t in range(1000)]
对应的STM32实现:
c复制// 定时器更新中断中动态调整ARR
void TIM1_UP_IRQHandler(void) {
static uint16_t index = 0;
TIM1->ARR = ccr_table[index++];
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
}
使用STM32的DWT计数器测量关键路径:
c复制#define DWT_CYCCNT *(volatile uint32_t*)0xE0001004
void measure_latency(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t start = DWT_CYCCNT;
// 被测代码
uint32_t end = DWT_CYCCNT;
printf("Cycles: %lu\n", end - start);
}
步进电机运行时会在电源线上产生高频噪声,建议:
在完成基础功能后,可以尝试以下优化方向: