【STM32F1】一种使用通用定时器实现各个通道独立输出不同指定数量脉冲的方法

一种使用通用定时器实现独立通道输出指定数量脉冲的方法

一种使用通用定时器实现独立通道输出指定数量脉冲的方法

概述

本文档描述了如何在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配置不单独给出

c 复制代码
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配置

c 复制代码
//通用定时器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中断服务配置

c 复制代码
//定时器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配置

c 复制代码
//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中断服务配置

c 复制代码
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上的通用定时器资源,可以方便地实现多通道独立输出指定数量脉冲的功能。这对于控制外部设备如电机速度、灯光强度等提供了灵活有效的手段。

相关推荐
蓝斯49713 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip14 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程14 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工14 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip15 小时前
CAN与CANopen的本质区别
单片机
lilihewo16 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET17 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院17 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰18 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥19 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5