今天和大家分享一下,定时器的输入捕获功能,用来检测编码器行走的圈数。
一:编码器知识:
编码器一般是正交编码器或者是拉线式编码器,通过两个信号线(A、B)的输出一定频率的脉冲输出来进行数据输出,编码器在转动时,利用2000r/rpm,意思就是编码器每转动一圈,会发出2000个脉冲,意味着编码器一个输出脉冲信号就意味着一个固定角度,单片机在通过读取单位时间脉冲信号的数量,通过定时器的执行时间就可以得到编码器行走的速度,间接的我们得到加载机构或者电机实际行走的距离。
编码器硬件介绍:
VCC :供电正极 一般是DC 5-24V
GND:供电负极
A:脉冲输出A相
B:脉冲输出B相
Z:脉冲输出Z相
其中:编码器在工作时,A相、相输出的脉冲信号总是相差90°相位差。反之如果两个信号相位差为90度,则这两个信号称为正交。由于两个信号相差90度,因此可以根据两个信号哪个先哪个后来判断方向、并且可以根据AB相脉冲信号数量测得速度,位移等;
正转的时候信号线A先输出信号,信号线B后输出 A相超前B相90度 证明是正转
反转的时候信号线B先输出信号,信号线A后输出 B相超前A相90度 证明是反转
二:STM32定时器输入捕获知识:
输入捕获总结工作过程:通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。我们只需要在其他定时器回调函数中,读取该定时器的计数值就可以,同时每次读取时将数据清零,否则会导致数据的溢出;
STM32的编码器模式共有三种:仅在TL1计数(A相),仅在TL2计数(B相),在TL1和TL2都计数(A相和B相都计数))
在使用三种定时器模式时的注意事项如下:
1.需要增加测量的精度时,可以采用4倍频方式,即分别在A、B相波形的上升沿和下降沿计数,分辨率可以提高4倍,
2.如果只是测速,不要求方向,那么只需要用单片机随意选择一个信号线就行了,,然后定时器边沿触发,检测脉冲计数即可
3.一般是定时器的通道1和2才能作为编码器输入口,对应编码器输出的两相。
4.GPIO配置为配置为上拉输入模式
5.一个定时器做一种工作,如果你配置了编码器模式,那么剩下的通道就不能配置其他模式 H! d7 N. c) u
6.两相计数模式下, 读出来数需要/4 一个脉冲信号对应四次计数
三:STM32cube MX 软件配置
如上图所示:说明如何在编码器模式下配置 TIM1 外设以确定旋转方向。
为了模拟正交编码器,TIM3 配置为切换模式,以在 10KHz 的(PA6 和 PB5)上生成 2 个正交信号。每 1 秒,信号会改变相位 (+90°/-90°) 以模拟向前/向后旋转。
TIM1 在编码器模式接口中配置,依靠 TI1 和 TI2。计数方向对应于所连接传感器的旋转方向(由 TIM3 信号模拟)。可以通过在 Live Watch 窗口中放置 “uwDirection” 变量来监控旋转方向。当 uwDirection = 0 时,根据“计数方向与编码器信号”表,旋转方向为“向前”。当 uwDirection = 1 时,根据“计数方向与编码器信号”表,旋转方向为“向后”。同时在程序中读取定时器1的内部寄存器的计数值。
程序编写步骤:
① 初始化定时器和通道对应IO的时钟。
② 初始化IO口,模式为输入:
③初始化定时器ARR,PSC
④初始化输入捕获通道
⑤如果要开启捕获中断
⑥使能定时器
⑦编写中断服务函数:
四:部分代码分享:
4.1输入捕获定时器1 初始化部分:
/** * @brief TIM1 Initialization Function * @param None * @retval None */ static void MX_TIM1_Init(void) { /* USER CODE BEGIN TIM1_Init 0 */ /* USER CODE END TIM1_Init 0 */ TIM_Encoder_InitTypeDef sConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM1_Init 1 */ /* USER CODE END TIM1_Init 1 */ htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 65535; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; sConfig.EncoderMode = TIM_ENCODERMODE_TI12; sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler = TIM_ICPSC_DIV1; sConfig.IC1Filter = 0; sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC2Prescaler = TIM_ICPSC_DIV1; sConfig.IC2Filter = 0; if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM1_Init 2 */ /* USER CODE END TIM1_Init 2 */ }
4.2 定时器3 输出PWM配置参数:
static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_OC_InitTypeDef sConfigOC = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = EMU_PERIOD; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_OC_Init(&htim3) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } sConfigOC.OCMode = TIM_OCMODE_TOGGLE; sConfigOC.Pulse = PULSE1_VALUE; sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) { Error_Handler(); } sConfigOC.Pulse = PULSE2_VALUE; if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ HAL_TIM_MspPostInit(&htim3); }
4.3 主程序如下:
/* Start the encoder interface */ HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* Step 1: */ /* Emulate a Forward direction */ Emulate_Forward_Direction(&htim3); /* Insert 1s delay */ HAL_Delay(1000); /* Get the current direction */ uwDirection = __HAL_TIM_DIRECTION_STATUS(&htim1); /* Step 2: */ /* Emulate a Backward direction */ Emulate_Backward_Direction(&htim3); /* Insert 1s delay */ HAL_Delay(1000); /* Get the current direction */ uwDirection = __HAL_TIM_DIRECTION_STATUS(&htim1); temp = __HAL_TIM_GET_COUNTER(&htim1); __HAL_TIM_SET_COUNTER(&htim1,0); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
实物测试图如下:
如上图所示:在读取到定时器1中的计数值时,我们需要将定时器中的计数值清零,否则数据异常溢出时候,会导致数据不准确。