STM32 正交解碼功能採集編碼器信號


layout: post
tags: [STM32]
comments: true

什麼是正交解碼?

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

編碼器接口模式

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

標準庫接口

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

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

所以,綜合以上,只需要關注時基單元輸入捕獲單元即可,下面對於其成員的以及其註釋做一下簡單解釋;

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計
數)。具體如下圖所示;

官方正交解碼時序

檢測方法

綜上所述,如果想得到轉速,和方向:

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

標準庫配置

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

  • 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法測速,如果配合機械復位信號,可以通過編碼器的脈衝數得到位置信息,轉過多少度,但當前並未實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章