STM32 TIM 編碼器模式採集編碼器信號


layout: post
tags: [STM32]
comments: trueweb

什麼是正交解碼?

對於經常使用增量式編碼器,光學編碼器,採用帶槽圓盤,一側是發射光線的發射端,而光電晶體管在相對的一側。當圓盤轉動時,光程被阻斷,獲得的脈衝指示軸的轉動和方向。一般的說法是1000線的編碼器,即轉一圈會產生1000個方波脈衝,馬盤上刻了1000個柵格,中間被鏤了1000個空,舉個例子,未免顯得有點囉嗦,下面直奔主題,至於什麼是編碼器仍是搜索引擎說的明明白白。
增量編碼器一般有A,B兩相信號,相位相差90°,因此也叫正交,還有一個復位信號是機械復位,即轉了一圈,復位信號會有一個跳變沿。具體以下圖所示:
編碼器
因此,正交解碼,就是把解碼A,B兩相的方波信號,檢測相位,以及脈衝數和轉向,固然也能夠計算出轉速,加速度,以及轉動到相應的位置。ide

編碼器接口模式

參考《STM32 參考手冊中文版》,能夠看到,對於TIM定時器中通用的功能,廣泛支持編碼器接口模式,下面配合手冊和標準庫進行配置。svg

標準庫接口

首先看到標準庫的代碼stm32f10x_tim.h中的接口,先簡單分析如下源碼,找到如下四個數據類型:post

  • TIM_TimeBaseInitTypeDef:時基單位,配置定時器預分頻參數,計數器模式(上溢/下溢),週期頻率以及分頻係數;
  • TIM_OCInitTypeDef:振盪輸出單元,能夠用於產生PWM波形;
  • TIM_ICInitTypeDef:輸入捕獲單元,能夠用於檢測編碼器信號的輸入;
  • TIM_BDTRInitTypeDef:適用於TIM1TIM8做爲插入死區時間配置的結構體;

因此,綜合以上,只須要關注時基單元輸入捕獲單元便可,下面對於其成員的以及其註釋作一下簡單解釋;ui

TIM_TimeBaseInitTypeDef

typedef struct
{ 
 
   
 uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock. This parameter can be a number between 0x0000 and 0xFFFF */

 uint16_t TIM_CounterMode;       /*!< Specifies the counter mode. This parameter can be a value of @ref TIM_Counter_Mode */

 uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active Auto-Reload Register at the next update event. This parameter must be a number between 0x0000 and 0xFFFF. */ 

 uint16_t TIM_ClockDivision;     /*!< Specifies the clock division. This parameter can be a value of @ref TIM_Clock_Division_CKD */

 uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter reaches zero, an update event is generated and counting restarts from the RCR value (N). This means in PWM mode that (N+1) corresponds to: - the number of PWM periods in edge-aligned mode - the number of half PWM period in center-aligned mode This parameter must be a number between 0x00 and 0xFF. @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;

TIM_ICInitTypeDef

typedef struct
{ 
 
   

  uint16_t TIM_Channel;      /*!< Specifies the TIM channel. This parameter can be a value of @ref TIM_Channel */

  uint16_t TIM_ICPolarity;   /*!< Specifies the active edge of the input signal. This parameter can be a value of @ref TIM_Input_Capture_Polarity */

  uint16_t TIM_ICSelection;  /*!< Specifies the input. This parameter can be a value of @ref TIM_Input_Capture_Selection */

  uint16_t TIM_ICPrescaler;  /*!< Specifies the Input Capture Prescaler. This parameter can be a value of @ref TIM_Input_Capture_Prescaler */

  uint16_t TIM_ICFilter;     /*!< Specifies the input capture filter. This parameter can be a number between 0x0 and 0xF */
} TIM_ICInitTypeDef;

寄存器接口

配置寄存器,能夠直接參考《STM32 參考手冊中文版》的十三章的編碼器接口模式,詳細內容能夠參考一下手冊,這裏結合前面標準庫的結構體,將重點的內容作一下提煉,編碼器接口大概須要進行如下幾項的配置:搜索引擎

  • 編碼器接口模式的配置:
    • 上升沿觸發
    • 降低沿觸發
    • 跳變沿觸發
  • 極性配置
  • 濾波器配置

如下是官方給出的配置方案:編碼

● CC1S=01(TIMx_CCMR1寄存器, IC1FP1映射到TI1)
● CC2S=01(TIMx_CCMR2寄存器, IC2FP2映射到TI2)
● CC1P=0(TIMx_CCER寄存器, IC1FP1不反相, IC1FP1=TI1)
● CC2P=0(TIMx_CCER寄存器, IC2FP2不反相, IC2FP2=TI2)
● SMS=011(TIMx_SMCR寄存器,全部的輸入均在上升沿和降低沿有效).
● CEN=1(TIMx_CR1寄存器,計數器使能)

這意味着計數器TIMx_CNT寄存器只在0到TIMx_ARR寄存器的自動裝載值之間連續計數(根據方向,或是0到ARR計數,或是ARR到0計
數)。具體以下圖所示;url

官方正交解碼時序

檢測方法

綜上所述,若是想獲得轉速,和方向:spa

  • 在間隔固定時間Ts,讀取TIMx_CNT寄存器的值,假設是1000線的編碼器,轉速:n = 1/Ts*TIMx_CNT*1000;
  • 根據TIMx_CNT的計數方向判斷轉向,不一樣極性,TIMx_CNT增加方向也不一樣,這裏要加以區分;

標準庫配置

下面是基於標準庫V3.5的代碼,基於STM32F103系列的單片機,硬件接口:.net

  • TIM3通道1,Pin6和Pin7;
  • 機械復位信號;

能夠經過encoder_get_signal_cnt接口讀取當前編碼的脈衝數,採用M法測速;

關於計數器溢出的狀況

TIM3_IRQHandler中斷經過判斷SR寄存器中的上溢和下溢標誌位,檢測定時器可能溢出的方向,經過N作一個補償,encoder_get_signal_cnt中未考慮到定時器溢出的狀況;

#ifndef ENCODER_H
#define ENCODER_H
#include <stdint.h>
/* QPEA--->PA6/TIM3C1 QPEB--->PA7/TIM3C1 --------------------------- TIM3_UPDATE_IRQ EXTI_PA5 --------------------------- */
typedef enum{ 
 
   
	FORWARD = 0,
	BACK
}MOTO_DIR;

/** * @brief init encoder pin for pha phb and zero * and interrpts */
void encoder_init(void);

/** * @brief get encoder capture signal counts */
int32_t encoder_get_signal_cnt(void);

/** * @brief get encoder running direction */
MOTO_DIR encoder_get_motor_dir(void); 

#endif
#include "encoder.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_exti.h"
#include "misc.h"

#define SAMPLE_FRQ 10000L
#define SYS_FRQ 72000000L

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
volatile int32_t N = 0;
volatile uint32_t EncCnt = 0;

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

static void encoder_pin_init(void){ 
 
   
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

static void encoder_rcc_init(void){ 
 
   

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	
}

static void encoder_tim_init(void){ 
 
   

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure; 		
	
	TIM_TimeBaseStructure.TIM_Period = ENCODER_MAX_CNT;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Prescaler = 0;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;

	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12, 
									TIM_ICPolarity_Rising, 
									TIM_ICPolarity_Rising);

	//must clear it flag before enabe interrupt
	TIM_ClearFlag(TIM3,TIM_FLAG_Update);
	TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);	
	
	//TIM_ITConfig(TIM3, TIM_IT_CC2, ENABLE);
	TIM_SetCounter(TIM3,ENCODER_ZERO_VAL);
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	TIM_Cmd(TIM3, ENABLE);
	
// TIM3->CCMR1 |= 0x0001;
// TIM3->CCMR2 |= 0x0001;
// TIM3->CCER &= ~(0x0001<<1);
// TIM3->CCER &= ~(0x0001<<5);
// TIM3->SMCR |= 0x0003;
// TIM3->CR1 |= 0x0001;

}

/** * @brief Configure the nested vectored interrupt controller. * @param None * @retval None */
static void encoder_irq_init(void)
{ 
 
   
	NVIC_InitTypeDef NVIC_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;

	/* Enable the TIM3 global Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5);
	EXTI_InitStructure.EXTI_Line = EXTI_Line5;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
	EXTI_Init(&EXTI_InitStructure);
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
 
}

void encoder_init(void){ 
 
   
	encoder_rcc_init();
	encoder_pin_init();
	encoder_irq_init();
	encoder_tim_init();
}
// 機械復位信號
void EXTI9_5_IRQHandler(void){ 
 
   

	if(EXTI_GetITStatus(EXTI_Line5) == SET){ 
 
    
		
	}
	EXTI_ClearITPendingBit(EXTI_Line5);
}

MOTO_DIR encoder_get_motor_dir(void)
{ 
 
   
	if((TIM3->CR1 & 0x0010) == 0x0010){ 
 
   
		return FORWARD;
	}else{ 
 
   
		return BACK;
	}
}

int32_t encoder_get_signal_cnt(void){ 
 
   
	int32_t cnt = 0;
	if(TIM3->CNT > ENCODER_ZERO_VAL){ 
 
   
		EncCnt = cnt = TIM3->CNT - ENCODER_ZERO_VAL;	
	}else{ 
 
   
		EncCnt = cnt = ENCODER_ZERO_VAL - TIM3->CNT;	
	}
	TIM_SetCounter(TIM3,ENCODER_ZERO_VAL);
	return cnt;
}

/******************************************************************************/
/* STM32F10x Peripherals Interrupt Handlers */
/******************************************************************************/
/** * @brief This function handles TIM3 global interrupt request. * @param None * @retval None */
void TIM3_IRQHandler(void)
{ 
 
    
	uint16_t flag = 0x0001 << 4;
	if(TIM3->SR&(TIM_FLAG_Update)){ 
 
   		
		//down mode
		if((TIM3->CR1 & flag) == flag){ 
 
   
			N--;
		}else{ 
 
   
			//up mode
			N++;
		}
	}	
	TIM3->SR&=~(TIM_FLAG_Update);		
}

TIM3 global interrupt request.
  * @param  None
  * @retval None
  */
void TIM3_IRQHandler(void)
{ 
 
    
	uint16_t flag = 0x0001 << 4;
	if(TIM3->SR&(TIM_FLAG_Update)){ 
 
   		
		//down mode
		if((TIM3->CR1 & flag) == flag){ 
 
   
			N--;
		}else{ 
 
   
			//up mode
			N++;
		}
	}	
	TIM3->SR&=~(TIM_FLAG_Update);		
}

總結

本文實現了STM32編碼器接口模式的配置以及編碼器的M法測速,若是配合機械復位信號,能夠經過編碼器的脈衝數獲得位置信息,轉過多少度,但當前並未實現。

本文同步分享在 博客「小麥大叔」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索