【STM32F1】一种使用通用定时器实现各个通道独立输出不同指定数量脉冲的方法
一种使用通用定时器实现独立通道输出指定数量脉冲的方法
- 一种使用通用定时器实现独立通道输出指定数量脉冲的方法
- 概述
- 实验平台
- 配置步骤
- 1. 初始化定时器与GPIO
- 2. 设置定时器工作模式
- 3. 编写脉冲计数逻辑
- 4. 调整参数以满足要求
- 注意事项
- 代码实现
- 电机结构体配置,GPIO配置不单独给出
- TIM2配置
- TIM2中断服务配置
- TIM3配置
- TIM3中断服务配置
- TIM4配置与TIM4中断服务配置
- 输出效果
- 结论
一种使用通用定时器实现独立通道输出指定数量脉冲的方法
概述
本文档描述了如何在STM32F103ZET6微控制器上,通过配置其内部通用定时器(如TIM2、TIM3等),实现每个定时器通道独立输出指定数量的脉冲。此方法适用于需要精确控制电机、LED亮度调节等应用场景。
实验平台
- MCU: STM32F103ZET6
- IDE: Keil MDK
- 开发方式: 标准库
- 编程语言: C语言
配置步骤
1. 初始化定时器与GPIO
首先,在代码中初始化相关定时器和GPIO引脚(hal库可使用STM32CubeMX进行相关配置)。确保选择的定时器支持PWM输出模式,并且对应的GPIO引脚已正确配置为复用功能输出。
2. 设置定时器工作模式
对于每个需要输出脉冲的定时器通道,需将其设置为PWM模式。具体来说:
- 选择PWM模式1或2(根据需求)。
- 设置自动重装载值(ARR),这决定了PWM周期。
- 设置比较值(CCR),用于控制单个脉冲宽度。
3. 编写脉冲计数逻辑
为了使每个通道能够输出指定数量的脉冲,需要编写一个简单的计数逻辑。每当一个PWM周期结束时(即定时器更新事件发生时),检查当前已输出的脉冲数是否达到设定的目标值。如果未达到,则继续输出;否则,停止对应通道的PWM输出。
4. 调整参数以满足要求
根据实际应用调整定时器的频率、PWM周期以及每个脉冲的宽度,以满足特定的应用需求。
注意事项
在调整定时器参数时,请注意不要超出硬件限制。
对于不同的应用场合,可能需要对上述方案进行适当调整,比如增加中断处理函数来提高实时性等。
代码实现
以电机应用为例,本案例使用TIM2为主定时器,TIM3、TIM4为从定时器,控制8个独立电机运动
电机结构体配置,GPIO配置不单独给出
u8 CNTtag = 0;
u8 ADD = 0;
struct DJ
{
u8 num; //使能(0失能1使能)
u8 ena; //使能(0失能1使能)
u8 dir; //方向(0反转1正转)
u8 state; //电机状态(0堵转1正常)
u32 cycle_pulse; //步进电机转一周的脉冲数(=细分数)
u32 pulse_set; //PWM脉冲设定值
u32 pulse_cnt; //PWM脉冲计数值(用于单次转动脉冲计数)
int pulse_tot_cnt; //PWM脉冲总计数值(保存电机位置以及用于角度计算)
char string;
};
#define ALL_DJ_Stop (dj[0].state == DISABLE && dj[1].state == DISABLE) && (dj[2].state == DISABLE && dj[3].state == DISABLE) && (dj[4].state == DISABLE && dj[5].state == DISABLE) && (dj[6].state == DISABLE && dj[7].state == DISABLE)
//电机是否旋转代号
#define ENABLE 1 //ena-停止旋转 state-运动中
#define DISABLE 0 //ena-允许旋转 state-运动结束
//电机旋转方向代号
#define Forward 1 //dir-正方向移动
#define Revers 0 //dir-负方向移动
//电机操作定时器
#define DJ1_Timer TIM3
#define DJ2_Timer TIM3
#define DJ3_Timer TIM3
#define DJ4_Timer TIM3
#define DJ5_Timer TIM4
#define DJ6_Timer TIM4
#define DJ7_Timer TIM4
#define DJ8_Timer TIM4
//电机操作定时器通道
#define DJ1_Channel TIM_Channel_1
#define DJ2_Channel TIM_Channel_2
#define DJ3_Channel TIM_Channel_3
#define DJ4_Channel TIM_Channel_4
#define DJ5_Channel TIM_Channel_1
#define DJ6_Channel TIM_Channel_2
#define DJ7_Channel TIM_Channel_3
#define DJ8_Channel TIM_Channel_4
TIM2配置
//通用定时器2中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器2!
void TIM2_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //使能指定的TIM2中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM2, ENABLE); //使能TIMx
}
TIM2中断服务配置
//定时器2中断服务程序
void TIM2_IRQHandler(void) //TIM2中断
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查TIM2更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); //清除TIMx更新中断标志
//LED1=!LED1;
TIM_CCxCmd(TIM3, TIM_Channel_1, ENABLE);
TIM_CCxCmd(TIM3, TIM_Channel_2, ENABLE);
TIM_CCxCmd(TIM3, TIM_Channel_3, ENABLE);
TIM_CCxCmd(TIM3, TIM_Channel_4, ENABLE);
TIM_Cmd(TIM3, ENABLE); //使能TIM3TIM_Cmd(TIM3, ENABLE); //使能TIM3
TIM_CCxCmd(TIM4, TIM_Channel_1, ENABLE);
TIM_CCxCmd(TIM4, TIM_Channel_2, ENABLE);
TIM_CCxCmd(TIM4, TIM_Channel_3, ENABLE);
TIM_CCxCmd(TIM4, TIM_Channel_4, ENABLE);
TIM_Cmd(TIM4, ENABLE); //使能TIM4TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
}
TIM3配置
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructureCh1;
TIM_OCInitTypeDef TIM_OCInitStructureCh2;
TIM_OCInitTypeDef TIM_OCInitStructureCh3;
TIM_OCInitTypeDef TIM_OCInitStructureCh4;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
// 开启通道1的PWM模式
TIM_OCStructInit(&TIM_OCInitStructureCh1);
TIM_OCInitStructureCh1.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructureCh1.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructureCh1.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStructureCh1);
// 开启通道2的PWM模式
TIM_OCStructInit(&TIM_OCInitStructureCh2);
TIM_OCInitStructureCh2.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructureCh2.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructureCh2.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC2Init(TIM3, &TIM_OCInitStructureCh2);
// 开启通道3的PWM模式
TIM_OCStructInit(&TIM_OCInitStructureCh3);
TIM_OCInitStructureCh3.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructureCh3.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructureCh3.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC3Init(TIM3, &TIM_OCInitStructureCh3);
// 开启通道4的PWM模式
TIM_OCStructInit(&TIM_OCInitStructureCh4);
TIM_OCInitStructureCh4.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructureCh4.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructureCh4.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC4Init(TIM3, &TIM_OCInitStructureCh4);
TIM_OC1Init(TIM3, &TIM_OCInitStructureCh1); //根据T指定的参数初始化外设TIM3 OC1
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR1上的预装载寄存器
TIM_OC2Init(TIM3, &TIM_OCInitStructureCh2); //根据T指定的参数初始化外设TIM3 OC2
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_OC3Init(TIM3, &TIM_OCInitStructureCh3); //根据T指定的参数初始化外设TIM3 OC3
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
TIM_OC4Init(TIM3, &TIM_OCInitStructureCh4); //根据T指定的参数初始化外设TIM3 OC4
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
NVIC_EnableIRQ(TIM3_IRQn);
// 配置TIM3通道1,并使能比较匹配中断
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // PWM模式下一般选择更新事件触发输出
// TIM_SetCompare1(TIM3, dutyCycle); // 设置比较值(占空比)
TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // 使能通道1的比较匹配中断
TIM_ITConfig(TIM3, TIM_IT_CC2, ENABLE); // 使能通道1的比较匹配中断
TIM_ITConfig(TIM3, TIM_IT_CC3, ENABLE); // 使能通道1的比较匹配中断
TIM_ITConfig(TIM3, TIM_IT_CC4, ENABLE); // 使能通道1的比较匹配中断
TIM_Cmd(TIM3, DISABLE); //使能TIM3
}
TIM3中断服务配置
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);
//脉冲计数
if(dj[0].ena == DISABLE)
{
dj[0].pulse_cnt++;
if(dj[0].dir == Revers)
{
dj[0].pulse_tot_cnt-=1;
}
else if(dj[0].dir == Forward)
{
dj[0].pulse_tot_cnt+=1;
}
}
//结束停止
if(dj[0].pulse_cnt > dj[0].pulse_set + ADD)
{
TIM_CCxCmd(TIM3, TIM_Channel_1, DISABLE); // TIM_Channel_1对应通道1
dj[0].pulse_cnt = 0;
dj[0].ena = ENABLE;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC2);
//脉冲计数
if(dj[1].ena == DISABLE)
{
dj[1].pulse_cnt++;
if(dj[1].dir == Revers)
{
dj[1].pulse_tot_cnt-=1;
}
else if(dj[1].dir == Forward)
{
dj[1].pulse_tot_cnt+=1;
}
}
//结束停止
if(dj[1].pulse_cnt > dj[1].pulse_set + ADD)
{
TIM_CCxCmd(TIM3, TIM_Channel_2, DISABLE); // TIM_Channel_1对应通道1
dj[1].pulse_cnt = 0;
dj[1].ena = ENABLE;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC3);
//脉冲计数
if(dj[2].ena == DISABLE)
{
dj[2].pulse_cnt++;
if(dj[2].dir == Revers)
{
dj[2].pulse_tot_cnt-=1;
}
else if(dj[2].dir == Forward)
{
dj[2].pulse_tot_cnt+=1;
}
}
//结束停止
if(dj[2].pulse_cnt > dj[2].pulse_set + ADD)
{
TIM_CCxCmd(TIM3, TIM_Channel_3, DISABLE); // TIM_Channel_1对应通道1
dj[2].pulse_cnt = 0;
dj[2].ena = ENABLE;
}
}
if (TIM_GetITStatus(TIM3, TIM_IT_CC4) != RESET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC4);
//脉冲计数
if(dj[3].ena == DISABLE)
{
dj[3].pulse_cnt++;
if(dj[3].dir == Revers)
{
dj[3].pulse_tot_cnt-=1;
}
else if(dj[3].dir == Forward)
{
dj[3].pulse_tot_cnt+=1;
}
}
//结束停止
if(dj[3].pulse_cnt > dj[3].pulse_set + ADD)
{
TIM_CCxCmd(TIM3, TIM_Channel_4, DISABLE); // TIM_Channel_1对应通道1
dj[3].pulse_cnt = 0;
dj[3].ena = ENABLE;
}
}
if(dj[0].pulse_cnt==0&&dj[1].pulse_cnt==0&&dj[2].pulse_cnt==0&&dj[3].pulse_cnt==0)
{
TIM_Cmd(TIM3, DISABLE);
dj[0].pulse_cnt = 1;
dj[1].pulse_cnt = 1;
dj[2].pulse_cnt = 1;
dj[3].pulse_cnt = 1;
dj[0].pulse_set = 1;
dj[1].pulse_set = 1;
dj[2].pulse_set = 1;
dj[3].pulse_set = 1;
dj[0].state = DISABLE;
dj[1].state = DISABLE;
dj[2].state = DISABLE;
dj[3].state = DISABLE;
//printf("--cccccwwww--\r\n");
// printf("0,%d,%d,%d,%d,%d,%d,%d\r\n",
// dj[0].ena, dj[0].dir, dj[0].state, dj[0].cycle_pulse,
// dj[0].pulse_set, dj[0].pulse_cnt, dj[0].pulse_tot_cnt);
// printf("1,%d,%d,%d,%d,%d,%d,%d\r\n",
// dj[1].ena, dj[1].dir, dj[1].state, dj[1].cycle_pulse,
// dj[1].pulse_set, dj[1].pulse_cnt, dj[1].pulse_tot_cnt);
// printf("2,%d,%d,%d,%d,%d,%d,%d\r\n",
// dj[2].ena, dj[2].dir, dj[2].state, dj[2].cycle_pulse,
// dj[2].pulse_set, dj[2].pulse_cnt, dj[2].pulse_tot_cnt);
// printf("3,%d,%d,%d,%d,%d,%d,%d\r\n",
// dj[3].ena, dj[3].dir, dj[3].state, dj[3].cycle_pulse,
// dj[3].pulse_set, dj[3].pulse_cnt, dj[3].pulse_tot_cnt);
// delay_us(5);
// printf("-------------\r\n");
}
}
TIM4配置与TIM4中断服务配置
类似TIM3实现,不再单独写出
输出效果
结论
通过合理配置STM32F103ZET6上的通用定时器资源,可以方便地实现多通道独立输出指定数量脉冲的功能。这对于控制外部设备如电机速度、灯光强度等提供了灵活有效的手段。