当前位置: 首页 > article >正文

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


http://www.kler.cn/a/538119.html

相关文章:

  • 数据库高安全—审计追踪:传统审计统一审计
  • 在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码
  • 【Java八股】JVM
  • 2025.1.8(qt图形化界面之消息框)
  • Python Pandas(5):Pandas Excel 文件操作
  • 面试真题 | Momenta c++
  • 掌握内容中台与人工智能技术的新闻和应用场景分析
  • vue的响应式原理以及Vue 3.0在响应式原理上的优化方案
  • Blocked aria-hidden on an element because its descendant retained focus.
  • CASAIM与马来西亚 Perodua汽车达成合作,共推汽车制造质量升级
  • 【C++八股】const和define的区别
  • 在亚马逊云科技上云原生部署DeepSeek-R1模型(下)
  • Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
  • 基于HTML5 Canvas 的盖楼游戏
  • 大数据治理新纪元:全面解读前沿开源技术的力量
  • DeepSeek——DeepSeek模型部署实战
  • 使用 OpenGL ES 加载图片并写入纹理单元
  • 推理大模型DeepSeek迅速觉醒
  • LeetCode:63. 不同路径 II(DP Java)
  • 腾讯云TI平台×DeepSeek:开启AI超强体验,解锁部署秘籍
  • Linux iftop 命令使用详解
  • 机器学习数学基础:20.方程组解的结构
  • React受控组件的核心原理与实战精要
  • 从0到1深入大数据治理:解锁数据价值的密码
  • Spring Boot 3.4 中 MockMvcTester 的新特性解析
  • 【对比测评】 .NET 应用的 Web 视图控件:DotNetBrowser 或 EO.WebBrowser