上篇介紹了定時器捕獲輸入脈沖的原理,那種方式是根據捕獲的原理,手動切換上升沿與下降沿捕獲,計算脈沖寬度的過程原理比較清晰,但編程操作起來比較麻煩。
對于電機測速用到的正交編碼器,測速時需要捕獲2路脈沖,如果使用上一篇介紹的方法,編程就較為復雜。還好單片機的通用定時器具有專門的正交編碼器接口,只需配置相應的寄存器,就可實現編碼器輸入的上下沿自動捕獲與計數,非常便于編碼器的測速。
下面就來介紹下定時器的編碼器模式的使用:
1 正反轉計數原理示例
編碼器模式下,計數器的計數方向代表的電機的正轉與反轉,計數的大小代表了轉速的大小。
如下圖,電機正轉時,編碼器的通道A(TI1)的信號超前通道B,計數器向上計數,反轉時,通道A的信號滯后,向下計數。
設置信號的極性反相,可以使向下計數代表電機正轉。
2 定時器編碼器模式配置
以STM32 芯片為例,其內部有專門用來采集增量式編碼器方波信號的接口,這些接口實際上是STM32 定時器的其中一種功能。不過編碼器接口功能只有高級定時器TIM1、TIM8 和通用定時器的TIM2~TIM5 才有。
正交編碼器有兩路正交的輸入信號(關于正交編碼的介紹,可查看之前的文章:編碼器計數原理與電機測速原理——多圖解析),根據實際需要,可以設置只捕獲某個通道的上升沿或下降沿,也可以設置同時捕獲兩個通道的上升沿與下降沿,這樣就可以提高編碼器的計數精度,實現倍頻。
編碼器模式的配置實際上是通過配置SMCR寄存器和CCER寄存器來實現。
2.1 SMCR寄存器配置觸發模式
SMCR即從模式控制寄存器(slave mode control register),查閱STM32F4的參考手冊,可以找到類似如下信息,現在我們只需關注SMS這幾位:
-
位 15 ETP:外部觸發極性 (External trigger polarity)
-
位 14 ECE:外部時鐘使能 (External clock enable)
-
位 13:12 ETPS:外部觸發預分頻器 (External trigger prescaler)
-
位 11:8 ETF[3:0]:外部觸發濾波器 (External trigger filter)
-
位 7 MSM:主/從模式 (Master/Slave mode)
-
位 6:4 TS:觸發選擇 (Trigger selection)
-
位 3 保留,必須保持復位值
-
位 2:0 SMS:從模式選擇 (Slave mode selection)
-
000:禁止從模式––如果 CEN =“1”,預分頻器時鐘直接由內部時鐘提供。
-
001:編碼器模式 1––計數器根據 TI1FP1 電平在 TI2FP2 邊沿 遞增/遞減計數。
-
010:編碼器模式 2––計數器根據 TI2FP2 電平在 TI1FP1 邊沿 遞增/遞減計數。
-
011:編碼器模式 3––計數器在 TI1FP1 和 TI2FP2 的邊沿計數,計數的方向取決于另外一個信號的電平。
-
100:復位模式––在出現所選觸發輸入 (TRGI) 上升沿時,重新初始化計數器并生成一個寄存器更新事件。
-
101:門控模式––觸發輸入 (TRGI) 為高電平時使能計數器時鐘。只要觸發輸入變為低電平,計數器立即停止計數(但不復位)。計數器的啟動和停止都是受控的。
-
110:觸發模式––觸發信號 TRGI 出現上升沿時啟動計數器(但不復位)。只控制計數器的啟動。
-
111:外部時鐘模式 1––由所選觸發信號 (TRGI) 的上升沿提供計數器時鐘。
-
上面的SMCR寄存器介紹中,關于TI1、TI2等的函數:
TI1 和 TI2對應編碼器的A、B兩相輸入信號。
TI1FP1 和 TI2FP2 是進行輸入濾波器和極性選擇后 TI1 和 TI2 的信號,如果不進行濾波和反相,則 TI1FP1=TI1,TI2FP2=TI2。
從上面的SMCR寄存器的功能介紹可知,選擇編碼器接口模式時:
如果計數器僅在 TI2 邊沿處計數,在 TIMx_SMCR 寄存器中寫入 SMS=001
如果計數器僅在 TI1 邊沿處計數,寫入 SMS=010
如果計數器在 TI1 和 TI2 邊沿處均計數,則寫入 SMS=011
定時器的編碼器模式根據兩個輸入的信號轉換序列,產生計數脈沖和方向信號。根據該信號轉換序列,計數器相應遞增或遞減計數,同時硬件對 TIMx_CR1 寄存器的DIR位進行相應修改。任何輸入(TI1 或 TI2)發生信號轉換時,都會計算 DIR 位。
2.2 CCER寄存器配置極性
通過編程 TIMx_CCER 寄存器的 CC1P 和 CC2P 位,可以選擇 TI1 和 TI2 極性。實際上就是設置TIxFP1 是否與TIx反相,來設置正轉時是向下計數還是向下計數。
-
位 15、11、7、3 CCxNP:捕獲 /比較x 輸出極性 (Capture/Comparex output Polarity)
-
位 14、10、6、2 保留,必須保持復位值
-
位 13、9、5、1 CCxP:捕獲 /比較x 輸出極性 (Capture/Comparex output Polarity)。
-
00
:非反相/上升沿觸發電路對 TIxFP1 上升沿敏感(在復位模式、外部時鐘模式或觸發模式下執行捕獲或觸發操作), TIxFP1 未反相 (在門控模式或編碼器模式下執行觸發操作)。 -
01
:反相/下降沿觸發 電路對 TIxFP1 下降沿敏感 (在復位模式、外部時鐘模式或觸發模式下執行捕獲或觸發操作), TIxFP1 反相 (在門控模式或編碼器模式下執行觸發操作)。 -
10
:保留,不使用此配置。 -
11
:非反相/上升沿和下降沿均觸發 電路對 TIxFP1 上升沿和下降沿都敏感(在復位模式、外部時鐘模式或觸發模式下執行捕獲或觸發操作),TIxFP1 未反相(在門控模式下執行觸發操作)。編碼器模式下不得使用此配置!!!。 -
0:OCx 高電平有效
-
1:OCx低電平有效
-
CCx 通道配置為輸出:
-
CCx 通道配置為輸入:
CCxNP/CCxP 位可針對觸發或捕獲操作選擇 TI1FP1 和 TI2FP1 的極性。
-
-
位 12、8、4、0 CCxE:捕獲 /比較 x 輸出使能 (Capture/Comparex output enable)
注:在編碼器模式下,極性的作用是設置TIxFP1 是否反相,不要被”上升沿敏感“誤導為是只捕獲上升沿信號!
”上升沿敏感“是在非編碼器模式下的功能。所以,編碼模式下,只能配置為
00
或01
。另一方面來看,編碼器模式下,只能通過SMCR的模式設置倍頻,要么是2倍頻,要么是4倍頻,貌似不能設置1倍頻(只對1個通道的上升沿或下降沿計數)。
2.3 CCMR寄存器配濾波參數
如果需要,通過配置CCMR寄存器的IC1F與IC2F,還可以對編碼器輸入信號進行濾波配置:
這些寄存器的說明在上篇文章已有介紹,這里不再展開。
3 計數方向對照表解讀
編碼器模式下,計數器的計數方向(遞增計數還是遞減計數)會根據增量編碼器的速度和方向自動進行修改,因此,其計數值始終表示編碼器的位置。計數方向對應于所連傳感器的旋轉方向。下表匯總了可能的組合(假設 TI1 和 TI2 不同時切換)。
注:STM32 的編碼器接口在計數的時候,并不是單純采集某一通道信號的上升沿或下降沿,而是需要綜合另一個通道信號的電平。(通俗的講就是,使用編碼器接口時,編碼器的兩個輸入通道A與通道B都需要進行電路連接!!!,雖然你設置了僅在某一個通道上計數,但這個通道的計數時機需要參考另一路通道的信號)表中“相反信號的電平”指的就是在計數的時候所參考的另一個通道信號的電平,這些電平決定了計數器的計數方向。
3.1 僅在TI1處計數
這里的僅在TI1處計數,就是僅統計編碼器的通道A的信號跳變,先以電機正轉為例:
注:以下的介紹中,“通道A“代表TI1,“通道B“代表TI2。
3.1.1 電機正轉(向上計數)
假定電機正轉時,編碼的通道A的信號比通道B提前1/4個周期(也即相位提前90度),在通道A的上升沿與下降沿均計數(如下圖TI1波形中的綠色和紅色箭頭),因為計數的方向代表的電機轉動的方向,所以,在正轉的情況下:
-
通道A上升沿,通道B為低電平,向上計數,代表電機正轉
-
通道A下降沿,通道B為高電平,向上計數,代表電機正轉
3.1.2 電機反轉(向下計數)
反轉的情況,編碼的通道A的信號比通道B滯后1/4個周期:
-
通道A下降沿,通道B為低電平,向上計數,代表電機反轉
-
通道A上升沿,通道B為高電平,向上計數,代表電機反轉
3.2 僅在TI2處計數
僅在TI2處計數,就是僅統計編碼器的通道B的信號跳變,同樣可以分為正轉和反轉兩種情況,具體的對應關系參考上面的”僅在TI1處計數“自行分析,實際上通道A與通道B從自身來說功能是等價的。
3.3 在TI1與TI2處均計數
在TI1與TI2處均計數,就是講編碼器的通道A與通道B的信號均統計并進行計數,這樣可以提高計數頻率,實現倍頻。
這里還以電機正轉為例*:
觀察下圖,編碼器在開始階段可依次捕獲到:通道A上升沿、通道B上升沿、通道A下降沿、通道B下降沿,所以有:
-
通道A上升沿,通道B為低電平,向上計數,代表電機正轉
-
通道B上升沿,通道A為高電平,向上計數,代表電機正轉
-
通道A下降沿,通道B為高電平,向上計數,代表電機正轉
-
通道B下降沿,通道A為高電平,向上計數,代表電機正轉
4 編程實現
4.1 定時器編碼器模式配置
這里使用的通用定時器中的 TIM4,配置定時器最基礎的功能就是要配置時基,使用輸入功能還要配置定時器的GPIO和輸入通道。
#define ENCODER_TIM_PSC 0 /*計數器分頻*/ #define ENCODER_TIM_PERIOD 65535 /*計數器最大值*/ #define CNT_INIT 0 /*計數器初值*/ void TIM4_ENCODER_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /*GPIO*/ TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; /*時基*/ TIM_ICInitTypeDef TIM_ICInitStruct; /*輸入通道*/ /*GPIO初始化*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); /*使能GPIO時鐘 AHB1*/ GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; /*復用功能*/ GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; /*速度100MHz*/ GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4); GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4); /*時基初始化*/ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); /*使能定時器時鐘 APB1*/ TIM_DeInit(TIM4); TIM_TimeBaseStructInit(&TIM_TimeBaseStruct); TIM_TimeBaseStruct.TIM_Prescaler = ENCODER_TIM_PSC; /*預分頻 */ TIM_TimeBaseStruct.TIM_Period = ENCODER_TIM_PERIOD; /*周期(重裝載值)*/ TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; /*連續向上計數模式*/ TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct); /*編碼器模式配置:同時捕獲通道1與通道2(即4倍頻),極性均為Rising*/ TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICStructInit(&TIM_ICInitStruct); TIM_ICInitStruct.TIM_ICFilter = 0; /*輸入通道的濾波參數*/ TIM_ICInit(TIM4, &TIM_ICInitStruct); /*輸入通道初始化*/ TIM_SetCounter(TIM4, CNT_INIT); /*CNT設初值*/ TIM_ClearFlag(TIM4,TIM_IT_Update); /*中斷標志清0*/ TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); /*中斷使能*/ TIM_Cmd(TIM4,ENABLE); /*使能CR寄存器*/ }
這里將定時器的計數溢出值設為65535,即TIM4的計數最大值(TIM4為16位計數器)。目的是避免計數器溢出,簡化后續的速度計算方式(計數器器若溢出,在計算轉速時,還要將溢出的次數考慮進去)。
編碼器模式設置為TIM_EncoderMode_TI12
,即兩路信號均計數,實現4倍頻。
編碼器兩個輸入的極性均設置為TIM_ICPolarity_Rising
,即極性不反相。
這里編碼器模式的設置,調用了TIM_EncoderInterfaceConfig()
函數,其內部即是對相關的寄存器進行配置:
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode, uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity) { uint16_t tmpsmcr = 0; uint16_t tmpccmr1 = 0; uint16_t tmpccer = 0; /* Check the parameters */ assert_param(IS_TIM_LIST2_PERIPH(TIMx)); assert_param(IS_TIM_ENCODER_MODE(TIM_EncoderMode)); assert_param(IS_TIM_IC_POLARITY(TIM_IC1Polarity)); assert_param(IS_TIM_IC_POLARITY(TIM_IC2Polarity)); tmpsmcr = TIMx->SMCR;/* Get the TIMx SMCR register value */ tmpccmr1 = TIMx->CCMR1; /* Get the TIMx CCMR1 register value */ tmpccer = TIMx->CCER;/* Get the TIMx CCER register value */ tmpsmcr &= (uint16_t)~TIM_SMCR_SMS;/* Set the encoder Mode */ tmpsmcr |= TIM_EncoderMode; /* Select the Capture Compare 1 and the Capture Compare 2 as input */ tmpccmr1 &= ((uint16_t)~TIM_CCMR1_CC1S) & ((uint16_t)~TIM_CCMR1_CC2S); tmpccmr1 |= TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_0; /* Set the TI1 and the TI2 Polarities */ tmpccer &= ((uint16_t)~TIM_CCER_CC1P) & ((uint16_t)~TIM_CCER_CC2P); tmpccer |= (uint16_t)(TIM_IC1Polarity | (uint16_t)(TIM_IC2Polarity << (uint16_t)4)); TIMx->SMCR = tmpsmcr; /* 配置數據寫入 SMCR 寄存器 */ TIMx->CCMR1 = tmpccmr1; /* 配置數據寫入 CCMR1 寄存器 */ TIMx->CCER = tmpccer; /* 配置數據寫入 CCER 寄存器 */ }
4.2 電機轉軸轉速計算
這里使用一款直流減速電機:
-
減速比是34(即電機轉軸轉1圈,電機本身要轉34圈)
-
電機轉一圈的物理脈沖數是11
所以,電機轉軸轉1圈時,可以產生的物理脈沖為34*11=374個,又由于編碼器器模式實現了4倍頻計數,所以,電機轉軸轉1圈時,定時器可以計數374×4=1496個。
對于轉速的計算,這里使用M法測速(M法測速的具體原理參考之前的文章:http://www.nxhydt.com/d/1639052.html),即統計固定時間間隔內的編碼器的脈沖數,來計算速度值。
,單位為:轉/秒
-
C:編碼器單圈總脈沖數
-
每次的統計時間(單位為秒)
-
:該時間內統計到的編碼器脈沖數
比如,對于本次實驗的電機,轉軸轉1圈時,定時器計數1496個,即C=1496個,對應程序中的TOTAL_RESOLUTION
。T0可以選擇100ms,即0.1s。
程序編寫如下,這里通過另外一個定時器7來實現每100ms調用一次calc_motor_rotate_speed()
函數來進行轉速的實時計算,每次使用read_encoder()
讀取編碼器器的值后,都將計數值CNT設為初始值0,重新開始計數,這樣就可以保證每次讀到的都是上個100ms的計數值。
另外,這里通過將CNT的uint32類型的計數值, 轉為int16類型,就可以利用正負來區分上個100ms電機整體的轉動方向(正轉CNT從0向上計數,轉為int16還是正值;反轉CNT從0向下計數,會溢出,轉為int16就為負數)。
#define ENCODER_RESOLUTION 11 /*編碼器一圈的物理脈沖數*/ #define ENCODER_MULTIPLE 4 /*編碼器倍頻,通過定時器的編碼器模式設置*/ #define MOTOR_REDUCTION_RATIO 34 /*電機的減速比*/ /*電機轉一圈總的脈沖數(定時器能讀到的脈沖數) = 編碼器物理脈沖數*編碼器倍頻*電機減速比 */ #define TOTAL_RESOLUTION ( ENCODER_RESOLUTION*ENCODER_MULTIPLE*MOTOR_REDUCTION_RATIO ) // 讀取定時器計數值 static int read_encoder(void) { int encoderNum = 0; encoderNum = (int)((int16_t)(TIM4->CNT)); /*CNT為uint32, 轉為int16*/ TIM_SetCounter(TIM4, CNT_INIT);/*CNT設初值*/ return encoderNum; } //計算電機轉速(被另一個定時器每100ms調用1次) void calc_motor_rotate_speed() { int encoderNum = 0; float rotateSpeed = 0; /*讀取編碼器的值,正負代表旋轉方向*/ encoderNum = read_encoder(); /* 轉速(1秒鐘轉多少圈)=單位時間內的計數值/總分辨率*時間系數 */ rotateSpeed = (float)encoderNum/TOTAL_RESOLUTION*10; printf("encoder: %d\t speed:%.2f rps\r\n",encoderNum,rotateSpeed); }
5 實驗演示
通過串口發送指令,控制另外一個定時器產生指定占空比的PWM來控制電機進行恒速轉動,然后測試編碼器讀到的速度值。
(串口指令用到了字符串切割和串口接收不定長字符的功能,可參考之前的文章:與 中的部分內容,PWM的產生可參考:)
視頻中,串口打印的encoder為100ms內讀到的編碼器器的計數值,正負號代表正反轉,speed為根據編碼器的計數值計算的電機輸出軸的轉速,單位為圈每秒。
首先是全速正反轉,轉速接近5圈每秒。
視頻演示:https://www.bilibili.com/video/BV13p4y1h7F9
-
單片機
+關注
關注
6032文章
44522瀏覽量
633147 -
電機控制
+關注
關注
3529文章
1859瀏覽量
268388 -
編碼器
+關注
關注
45文章
3599瀏覽量
134181 -
定時器
+關注
關注
23文章
3241瀏覽量
114492
發布評論請先 登錄
相關推薦
評論