旋轉編碼器原理、選型及編碼

来源:https://www.cnblogs.com/jaceeshen/p/17974511
-Advertisement-
Play Games

旋轉編碼器(rotary encoder)也稱為軸編碼器,是將旋轉的機械位移量轉換為電氣信號,對該信號進行處理後檢測位置速度等信號的感測器。檢測直線機械位移量的感測器稱為線性編碼器[1]。一般裝設在旋轉物體中垂直旋轉軸的一面。 ...


旋轉編碼器原理、選型及編碼

原理

旋轉編碼器(rotary encoder)也稱為軸編碼器,是將旋轉的機械位移量轉換為電氣信號,對該信號進行處理後檢測位置速度等信號的感測器。檢測直線機械位移量的感測器稱為線性編碼器[1]。一般裝設在旋轉物體中垂直旋轉軸的一面。

旋轉編碼器用在許多需要精確旋轉位置及速度的場合,如工業控制、機器人技術、專用鏡頭、電腦輸入裝置(如滑鼠及軌跡球)等。

旋轉編碼器可分為絕對型(absolute)編碼器及增量型(incremental)編碼器兩種。增量型編碼器也稱作相對型編碼器(relative encoder),利用檢測脈衝的方式來計算轉速及位置,可輸出有關旋轉軸運動的信號,一般會由其他設備或電路進一步轉換為速度、距離、每分鐘轉速或位置的信號。絕對型編碼器會輸出旋轉軸的位置,可視為一種角度感測器。

分類[2]:

  • 以編碼器工作原理可分為:光電式磁電式觸點電刷式
  • 以碼盤刻孔方式不同分為:增量式絕對式兩類。

二者的主要區別在於碼盤的結構和輸出信號的形式不同。增量型編碼器輸出的是脈衝信號,而絕對編碼器輸出的是二進位的數值[10]。

絕對值型編碼器

絕對型編碼器將轉軸的不同位置加以編號,再依目前轉軸位置輸出對應的編號,依構造主要可分為兩種:光學式機械式[3]。
絕對型編碼器的特點是隨時可以知道轉軸的位置,有時也會將解角器視為是絕對型編碼器。

增量型編碼器

增量型編碼器和絕對型編碼器不同,當轉軸旋轉時,增量型編碼器輸出會隨之變化,根據輸出變化可以檢測轉軸的旋轉量。絕對型編碼器有針對轉軸旋轉的位置給予編號,轉軸不動時根據其輸出的信號可以求得其對應的位置,增量型編碼器無此功能,無法在轉軸不動時得到轉軸旋轉位置的信息。

增量型編碼器可用來感應轉軸旋轉量的信號,再由程式轉化旋轉方向、位置及角度等信息,增量型編碼器可以是線性的,也可以是旋轉型。增量型編碼器因為其低成本,以及其信號容易轉換為運動相關的信號(例如速度)等特性,是最廣為使用的編碼器。

增量型編碼器有機械式的及光學式的,機械式的編碼器需要對信號處理抖動,一般用在消費性產品上的旋鈕。例如大部分家用及車用的收音機就是用增量型編碼器作為音量控制的旋鈕,一般機械式編碼器只適用在轉速不高的應用場合。光學式的編碼器則用在高速或是需要高精準度的場合。

增量型編碼器有兩個主要輸出,分別稱為A和B,兩個輸出是正交輸出,相位差為90度。增量型編碼器的單圈脈衝數(PPR)為其旋轉一圈時會輸出的方波數,如PPR為600表示旋轉一圈時A和B都會輸出600個方波,但先後順序不同。光學式增量型編碼器可以有較高的單圈脈衝數,例如 2500 到 10000。
以下是順時針及逆時針旋轉時,編碼器輸出的變化:

逆時針旋轉的輸出 順時針旋轉的輸出
Phase A B Phase A B
1 0 0 1 1 0
2 0 1 2 1 1
3 1 1 3 0 1
4 1 0 4 0 0

二個信號有90度的相位差,在不同旋轉方向時,二個信號的相序也有所不同,可以利用程式將兩個信號進行解碼。根據其相序不同,在有方波時使一計數器上數或是下數,此計數器的值即可對應轉軸的旋轉量。
例如上一次的數值是00,目前的數值是01,表示轉軸已順時針旋轉了四分之一個單位(若單圈脈衝數為600,此處的單位即為六百分之一圈)。根據單位時間的旋轉量可以計算轉速,若是轉速很慢時可以直接根據方波的寬度計算轉速。若轉軸的旋轉速度太快,程式可能會跳過中間的狀態變化,出現無法識別轉軸的旋轉方向或是旋轉方向誤判的情形。

當順時針旋轉時A信號提前B信號90度相位,當逆時針旋轉時B信號提前A信號90度相位,電路接收到旋轉編碼器的A、B信號時,可以根據A、B的狀態組合判定編碼器的旋轉方向。
程式設計中我們可以對A、B信號檢測,檢測A信號的邊沿及B信號的狀態,

  • 當A信號上升沿時B信號為低電平,或當A信號下降沿時B信號為高電平,證明當前編碼器為順時針轉動
  • 當A信號上升沿時B信號為高電平,或當A信號下降沿時B信號為低電平,證明當前編碼器為逆時針轉動

有些旋轉編碼器除了A相B相外還有一個輸出,一般稱為Z相,每旋轉一圈Z相信號會有一個方波輸出,可以用來判斷轉軸的絕對位置,例如用在位置控制的系統中。
若旋轉編碼器只有單獨一相的輸出,仍然可以判斷轉軸的轉速,只是不能判斷旋轉的方向。可以用在量測轉速的場合,有時也會以此量測運動的距離。

增量型編碼器輸出A相、B相和Z相分別代表的含義[4]:
編碼器軸每旋轉一圈,A相和B相都發出相同的脈衝個數,但是A相和B相之間存在一個90°(電氣角的一周期為360°)的電氣角相位差,可以根據這個相位差來判斷編碼器旋轉的方向是正轉還是反轉,正轉時,A相超前B相90°先進行相位輸出,反轉時,B相超前A相90°先進行相位輸出(如下圖所示)。編碼器每旋轉一圈,Z相只在一個固定的位置發一個脈衝,所以可以作為複位相或零位相來使用。
img

選型

在選型或採購旋轉編碼器的時候,需要從多方面進行考慮,特別是在技術參數上需要進行一個技術參數上的參考:包括編碼器的尺寸類型解析度電氣介面等等,總的來說,第一步則是判斷應用需要的是增量編碼器、絕對編碼器還是換向編碼器。一經確定,就必須考慮解析度、安裝方式、電機軸尺寸等其他參數[5]。(對磁場環境有要求的務必不能選擇磁電技術的編碼器!!!)

外形尺寸

除了編碼器的定位止口,軸徑,安裝孔位;安裝空間體積等常規參數,還需考慮比如:安裝空間與選定軸的形態(中空軸、桿軸類):
imgimg
以及連接線纜類型:
imgimg

分解率精度

在考慮組裝機械裝置的要求精度和機械的成本的基礎上,選擇最適合的產品。一般選擇機械綜合精度的1/2~1/4精度的解析度。

輸出電路方式

對增量型編碼器而言,其輸出電路有很多類型,當使用高速計數器對編碼器的脈衝信號進行計數時,必須首先搞清楚該編碼器的輸出類型才能正確的接線並調試。
增量型編碼器的輸出電路包括集電極輸出(Collector Output)型電壓輸出(Voltage Output)型推輓輸出(Push-Pull Output)型線驅動輸出(Line Driver Output)型。輸出電路的核心元器件是三極體。我們知道三極體有三個極:基極(Base)、發射極(Emitter)和集電極(Collector)。

最合適的輸出信號類型並不總是那麼明顯,而且往往受到忽視。最常見的三種類型是開集輸出(電壓輸出-E)、推輓輸出(F型HTL格式)和差分線路驅動器輸出。本文將分別介紹這三種輸出類型,幫助大家根據具體應用需求選擇合適的設備。

首要原則

無論是增量編碼器的正交輸出,換向編碼器的電機極輸出,還是使用特定協議的串列輸出,這些編碼器輸出都是數字信號。因此,5 V 編碼器的信號會一直在近似 0 V 與 5 V 之間切換,這兩個電壓分別對應邏輯 0 和 1。增量編碼器的輸出是基本方波,如下圖所示。
img

開集輸出(開路集電極輸出)

集電極輸出電路是以三極體的發射極為公共端,信號從集電極輸出的電路。由於三極體分為PNP和NPN兩種,相應的,編碼器的集電極輸出電路也分為PNP和NPN兩種。
下麵這張圖是歐姆龍E6B2-CWZ5B編碼器的集電極輸出電路(PNP型),可以看到編碼器的電源(+12V或+24V)通過一個3.3歐姆的電阻連接到三極體的發射極上,而脈衝信號(A/B/Z)則是從集電極進行輸出:
img
NPN型集電極輸出電路與之類似,只不過發射極(公共端)連接的是0V,如下麵的E6B2-CWZ6C輸出電路:
img

旋轉編碼器大多採用開集輸出,即輸入信號為高電平時,晶體管的集電極引腳保持開路或斷開。當輸出為低電平時,輸出直接接地。
img
NPN集電極開路輸出: 輸出電路採用NPN晶體管,發射極直接連接0V端,集電極作為輸出端,輸出端和+V端之間為開路(Open)狀態,這種輸出形態即為NPN集電極開路輸出。
img
集電極開路輸出是以輸出電路的晶體管發射極作為公共端,並且集電極懸空的輸出電路。一般分為NPN集電極開路輸出PNP集電極開路輸出
img
由於輸入信號為高電平時輸出斷開,需要使用外部“上拉”電阻,才能確保集電極電壓達到所需的電平,即邏輯 1。因此,工程師在連接不同電壓的系統時就更具靈活性:通過上拉電阻可將集電極電壓上拉至不同電壓,使之高於或低於編碼器工作電壓。
img
不過,這種介面也具有一些缺陷。許多現成的控制器都已內置了上拉電阻,而這些上拉電阻會消耗電流,即產生耗散功率。此外,當該電阻與寄生電容組成 RC 電路時,輸出在高電壓與低電壓之間的轉換速率將因此降低。轉換斜率即轉換速率。
img
通過降低轉換速率,上拉電阻會顯著降低編碼器運行速度,從而降低增量編碼器的解析度。減小電阻值可以提高轉換速率,但是當信號為低電平時,上拉電阻功耗的電流更大,耗散功率也更大。

當旋轉編碼器的電源電壓與控制部的電源電壓不一致時,此種輸出方式為最佳使用方案[6]。

電壓輸出

電壓輸出型是在NPN型三極體的集電極與電源之間連接一個上拉電阻,這樣集電極的輸出電壓會被鉗制在一個穩定的範圍。如下麵這張圖是歐姆龍E6B2-CWZ3E的電壓輸出電路:
img

以輸出電路的晶體管發射極為共通型,在集電極與電源間插入電阻,並輸出因電壓而變化的集電極的輸出電路[7]。得集電極和電源之間能有一個穩定的電壓狀態。
電壓輸出就是集電極開路輸出的反相增加一個電阻,構成一個極性是PNP或NPN,而另一個極性是電壓,實際上就是NPN+電壓或PNP+電壓,這是針對是PNP的或NPN的形式的接收設備的一種權宜,便於兩者都可以連接,但現在這種電壓介面往往已經做在了經濟型PLC上了,如果是那樣的PLC,還是應該直接選集電極開路輸出的,或電壓型的極性相當的編碼器,因為如果選電壓輸出型的編碼器PNP+電壓的,而連接的PLC是NPN+電壓的,就會有漏電流而產生錯誤[8]。
img

推輓輸出

推輓輸出(Push-Pull Output)電路由兩個三極體組成,比如下麵這張圖的T1和T2。
img
推輓輸出的兩個三極體分別接受輸入信號和該信號的反相信號,當輸入信號(Input Signal)為1時,T1導通,此時輸入信號的反相為0,因此T2截止;同樣的,當輸入信號為0時,T1截止,此時輸入信號的反相為1,因此T2導通;可見推輓式輸出電路可以輸出信號的正反兩相(比如A和A補),其抗干擾能力比較強,適合較遠距離的傳輸。

推輓式放大輸出,有的歐洲編碼器用HTL表示,其相當於NPN+PNP的推輓放大,而且大部分有標準的集成放大電路,根據供電,輸出10—30V,對於接收設備的相容性強,信號強而穩定,若果再有與差分長線驅動一樣有反相信號的話,因信號電壓高,傳遞最遠,差分傳遞及接受,抗干擾最好,工程項目或大型設備中,首選推輓式輸出。
推輓式含反相6通道輸出,也就是HTL-6。重型機械設備常常有較遠傳遞或大變頻電機工況下,就需要選推輓式並且含具有反相輸出的推輓式輸出編碼器(例如ABB變頻控制器,就有這樣的介面。

推輓輸出使用兩個晶體管,而不是一個,因此可以彌補上述開集輸出介面的缺陷。上部晶體管取代上拉電阻,導通時可將電壓上拉至電源電壓,由於電阻極小,因而轉換速率較快。而輸出信號為低電平時,晶體管關斷,因此相較於開集電路,該有源上拉電路的耗散功率也相對較小,從而使電池供電設備的運行時間相對較長。
img
在+V和0V之間由兩個晶體管迴路構成的輸出方式即為推拉輸出方式。當輸出信號為H時,上面的晶體管為ON,下麵的晶體管為OFF。當輸出信號為L時,則上面的晶體管為OFF,下麵的晶體管為ON。推拉輸出方式由於輸出電流為兩個方向(流入,流出)控制,輸出阻抗較低,波形不容易失真,也不易受到干擾的影響。
當編碼器的連接線較長時,可使用此輸出方式。

差分線路驅動器輸出(線路驅動器輸出)

線驅動輸出(Line Driver Output)電路是使用專用輸出晶元,輸出符合RS422標準的差分信號,抗干擾能力更強,適合用於傳輸速度較高、距離較遠的場合。下麵這張圖,是歐姆龍E6B2-CWZ1X的線驅動輸出電路:
img
線性驅動輸出是採用RS-422標準,用AM26LS31晶元應用於高速、長距離數據傳輸的輸出模式。信號以差分形式輸出,因此抗干擾能力更強。輸出信號需專門能接收線性驅動輸出的設備才能接收。輸出電路見下圖。
img
雖然使用推輓輸出的編碼器彌補了開集輸出的一些缺陷,但兩者都是單端輸出。在佈線距離較長的應用或存在電雜訊和干擾的環境中,使用單端輸出具有一定局限性。
佈線距離較長時,信號幅度衰減,電容效應將減慢轉換速率。由於單端信號的傳輸信號以地為參考,這類衰減就可能產生誤差,從而導致系統性能下降。
此外,在電雜訊環境中,不同幅度的干擾電壓都將耦合到電纜上,從而導致單端系統的接收器錯誤地解碼信號電壓。
電纜長度超過一米時,建議使用差分信號。使用差分線路驅動器的編碼器可產生兩個輸出信號:一個與原始信號相匹配,另一個與之完全相反,即互補信號。這兩個信號之間的幅度差是原始單端信號的兩倍,有助於剋服電壓降和電容引起的衰減問題。
img
此外,由於兩個信號均存在共模雜訊,可以相互抵消,因此接收系統可忽略其影響。由於雜訊抑制能力相當出色,差分線路驅動器介面廣泛用於工業和汽車應用。多種 CUI 編碼器都提供差分線路驅動器輸出選項,可用於要求嚴苛的應用。
img
本輸出方式採用高速、長距離輸送用的專用IC方式,該IC具有高速應答特性,適用於長距離傳送,不易受干擾影響,是依據RS422-A規格的數據傳送方式。信號以差動的2信號輸出,因此抗干擾能力強。接受線路驅動器輸出的信號時,可使用稱為線路接
該IC具有高速應答特性,適用於長距離傳送,不易受干擾影響。

補碼輸出(互補輸出)

補碼輸出是輸出上具備NPN和PNP 2種輸出晶體管的輸出電路。根據輸出信號的「H」、「L」,2個輸出晶體管交互進行「ON」、「OFF」動作。使用時,請在正極電源、OV上進行上拉、下降後再使用。補碼輸出,包括輸出電流的流出、流入兩個動作,其特征為信號的上、下降速度快,可延長代碼的長距離。比集電極開路輸出的電路傳輸距離能稍遠,可與開路集電極輸入機器(NPN、PNP)連接。
img

廠商

除了以上幾點外,價格也是十分關鍵的因素,購置到性價比高的產品當然是每個使用者最理想的想法,在選擇旋轉編碼器時要選擇價格與設備性能相符並且符合自己使用的產品,對於同類產品進行多方面的對比,在選擇編碼器廠家時也要儘量選擇大型公司,產品質量和服務質量更加有保障。

硬體電路及接線

電路

註意上拉電阻及信號線的104電容濾波,可以使信號更穩定[10]。
img

接線(PLC)

增量編碼器的連接,要清楚編碼器的信號輸出形式與接收設備的匹配問題,選編碼器或選接收設備一定要兩者信號形式的匹配。增量編碼器的信號輸出從波形上看,分正餘弦輸出(sin/cos)與方波輸出兩種。
①. 正餘弦輸出的信號是模擬量變化的信號周期,又分電壓輸出1Vpp和電流輸出1uApp,這兩種輸出一般PLC都沒有介面,大部分是連接專用的運動控制卡,其內部可做細分而獲得更高的解析度和動態特性,也有連接專用的細分盒再細分後輸出方波的,選型時搞清楚是電壓輸出還是電流輸出(現在大部分是電壓輸出了)。
②. 方波輸出的也有分集電極開路輸出(NPN或PNP)、電壓輸出、差分長線驅動、推輓式輸出等。

1、開路集電極輸出[9]

集電極開路輸出就是類晶體管放大電路,三極體放大集電極開路輸出,依據三極體的極性,分NPN與PNP,接收設備選型需要匹配不可選錯,這種輸出電路簡單經濟,但選型面窄,傳輸距離根據放大管有遠有近,但總體傳遞距離不遠,且保護不夠,較易損壞,大部分用在單機設備上而不是工程項目中。這種輸出的電壓依據供電,有5-12V輸出和12—24V輸出,這也要搞清楚才能確保信號的連接。

  • NPN型集電極輸出

img

  • PNP型集電極輸出

img

2、 線性驅動輸出(差分信號)

差分長線驅動是一種差分放大的電路,大部分是5V,提供A+、B+、Z+及其180度反相的A—、B—、Z—,讀取時,以A+與A-差分值讀取,對於共摸干擾有抑製作用,傳遞距離較遠,由於抗干擾能力較強,在運動控制(數控機床)中用得較多。
img

3、 電壓輸出型

電壓輸出就是集電極開路輸出的反相增加一個電阻,構成一個極性是PNP或NPN,而另一個極性是電壓,實際上就是NPN+電壓或PNP+電壓,這是針對是PNP的或NPN的形式的接收設備的一種權宜,便於兩者都可以連接,但現在這種電壓介面往往已經做在了經濟型PLC上了,如果是那樣的PLC,還是應該直接選集電極開路輸出的,或電壓型的極性相當的編碼器,因為如果選電壓輸出型的編碼器PNP+電壓的,而連接的PLC是NPN+電壓的,就會有漏電流而產生錯誤。
img

4、 互補輸出型

img

編碼

Raspberry Pi Pico

  • C
#include <stdio.h>
#include "pico/stdlib.h"
#define PinA 27
#define PinB 26
#define PinSW 22
uint64_t time = 0;
int32_t count = 0;
int32_t num = 0;

void blinkA()
{
    if ((time_us_64() - time) > 3000)
    {
        if (gpio_get(PinB))
        {
            count++;
        }
        else
        {
            count--;
        }
    }

    time = time_us_64();
}

void init()
{
    gpio_init(PinA);
    gpio_init(PinB);
    gpio_init(PinSW);
    gpio_set_irq_enabled_with_callback(PinA, GPIO_IRQ_LEVEL_LOW, true, &blinkA);
    gpio_set_dir(PinA, 0);
    gpio_set_dir(PinB, 0);
    gpio_set_dir(PinSW, 0);
}
int main()
{
    stdio_init_all();
    init();
    time = time_us_64();
    int i=0;
    while (1)
    {
        if (num != count)
        {
            i = 1;
            num = count;
            printf("%ld\r\n", num);
        }
        if((!gpio_get(PinSW)) && 1 == i)
        {
            i = 0;          
            count = 0;        
            while(!gpio_get(PinSW));
        }
    }

    return 0;
}

  • Python
from machine import Pin,ADC
import utime

#Define pins and their initialization
right = machine.Pin(27, machine.Pin.IN)
left = machine.Pin(26, machine.Pin.IN)
down = machine.Pin(22, machine.Pin.IN)


right_assist = 0
count = 0             #Rotate the value of the encoder


def right_handler(pin):
    global right_assist
    right.irq(handler=None)
    right_assist = 1


def down_handler(pin):
    global count
    down.irq(handler=None)
    count = 0
    print("down",count)
    down.irq(trigger=machine.Pin.IRQ_FALLING, handler=down_handler)


right.irq(trigger=machine.Pin.IRQ_FALLING, handler=right_handler)
down.irq(trigger=machine.Pin.IRQ_FALLING, handler=down_handler)


while True :
    if (right_assist == 1 ):
        
        if (left.value() == 1 ):
            count = count - 1
            print("left",  count)

        elif (left.value() == 0 ):
            count = count + 1
            print("right",  count)
            
        while (left.value() == 0 ) | (right.value() == 0):
            utime.sleep_ms(1)
            
        right_assist = 0
        right.irq(trigger=machine.Pin.IRQ_FALLING, handler=right_handler)

STM32

/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file           : main.c
* @brief          : Main program body
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*                        opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t turn_flag;
int32_t count;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#include "stdio.h"

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2 , (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* USER CODE END 0 */

/**
* @brief  The application entry point.
* @retval int
*/
int main(void)
{
    /* USER CODE BEGIN 1 */
    
    /* USER CODE END 1 */
    
    /* MCU Configuration--------------------------------------------------------*/
    
    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();
    
    /* USER CODE BEGIN Init */
    
    /* USER CODE END Init */
    
    /* Configure the system clock */
    SystemClock_Config();
    
    /* USER CODE BEGIN SysInit */
    
    /* USER CODE END SysInit */
    
    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    /* USER CODE BEGIN 2 */
    
    /* USER CODE END 2 */
    
    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {   
        if(turn_flag == 1)
        {
            if(HAL_GPIO_ReadPin(SIB_GPIO_Port, SIB_Pin ) == GPIO_PIN_RESET )
            {
                count --;
                printf("Turn left!\r\n");
                while(HAL_GPIO_ReadPin(SIA_GPIO_Port, SIA_Pin ) == GPIO_PIN_RESET);
                
            }           
            else 
            {
                count ++;
                printf("Turn right!\r\n");
                while(HAL_GPIO_ReadPin(SIA_GPIO_Port, SIA_Pin ) == GPIO_PIN_SET);
            }
            
            
            printf("count = %d \r\n",count);
            turn_flag = 0;
        }
        
        if(HAL_GPIO_ReadPin(SW_GPIO_Port, SW_Pin ) == GPIO_PIN_RESET )
        {
            HAL_Delay(10);
            if(HAL_GPIO_ReadPin(SW_GPIO_Port, SW_Pin ) == GPIO_PIN_RESET )
            {
                turn_flag = 0;
                count = 0;
                printf("Turn down!\r\n");
                printf("count = %d \r\n",count);
                while(HAL_GPIO_ReadPin(SW_GPIO_Port, SW_Pin ) == GPIO_PIN_RESET);
            }
        }
        
        /* USER CODE END WHILE */
        
        /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}

/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    /** Initializes the RCC Oscillators according to the specified parameters
    * in the RCC_OscInitTypeDef structure.
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    /** Initializes the CPU, AHB and APB buses clocks
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
        |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

/* USER CODE BEGIN 4 */

/**
* @brief EXTI line detection callbacks
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if((GPIO_Pin == SIA_Pin) && (turn_flag == 0))
    {
        turn_flag = 1;
    }
}

/* USER CODE END 4 */

/**
* @brief  This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
    /* USER CODE BEGIN Error_Handler_Debug */
    /* User can add his own implementation to report the HAL error return state */
    __disable_irq();
    while (1)
    {
    }
    /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
* @brief  Reports the name of the source file and the source line number
*         where the assert_param error has occurred.
* @param  file: pointer to the source file name
* @param  line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
    /* USER CODE BEGIN 6 */
    /* User can add his own implementation to report the file name and line number,
    ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

Arduino

int cnt = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:

  Serial.println(cnt);
  Serial.println("Hello World\r\n");
//  digitalWrite(3, HIGH); 
//  delay(1000);
//  digitalWrite(3, LOW); 
//  delay(1000);  
//  digitalWrite(4, HIGH); 
//  delay(1000);
//  digitalWrite(4, LOW); 
//  delay(1000);
  digitalWrite(5, HIGH); 
  delay(1000);
  digitalWrite(5, LOW); 
  delay(1000);
  cnt++;   
}

參考

  1. Omron:旋轉式編碼器
  2. 電子森林:基於STEP FPGA的旋轉編碼器電路驅動
  3. Wiki:旋轉編碼器
  4. Omron:增量型編碼器輸出A相、B相和Z相分別代表什麼含義?
  5. Digi-Key:瞭解編碼器輸出信號有助於選擇最佳設備
  6. Autonics:編碼器NPN集電極開路輸出、推拉輸出、線性驅動輸出的特點
  7. Oromn:編碼器的電壓輸出,集電極信號輸出,線性驅動輸出的區別?
  8. 工控網:增量編碼器的常用信號輸出有哪些?
  9. 編碼器的集電極輸出、電壓輸出、互補輸出和線性驅動輸出
  10. 微雪:Rotation Sensor
  11. 知乎:談談增量型編碼器的信號電路輸出方式

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 利用wmproxy可以輕鬆的轉化tcp到websocket的流量互轉,配置簡單。可以利用現成的websocket高速通道輔助我們的tcp程式獲取更穩定的流量通道。 ...
  • 面試官:“小伙子,在日常的寫代碼過程中,使用過String,StringBuilder和StringBuffer沒?” 我:“用過的呀!” 面試官:“那你就來聊一聊,他們之間有什麼區別,不同場景下如何選擇吧” 我:“好嘞!” 在Java的開發過程中,使用頻率最高的就是String字元串,但由於在字元 ...
  • C# 數據類型 C# 中的變數必須是指定的數據類型: int myNum = 5; // 整數(整數) double myDoubleNum = 5.99D; // 浮點數 char myLetter = 'D'; // 字元 bool myBool = true; // 布爾 string myT ...
  • 在發佈完:開源:Taurus.DTC 微服務分散式事務框架,之後想想,好像除了事務外,感覺裡面多了一個任務發佈訂閱的基礎功能,本想既然都有了基礎發佈訂閱功能了,那要不要順帶加上延時發佈功能呢?加上了會不會讓事務組件不純了? 經過一翻深思......於是就有了這個Taurus.DTS 任務組件,而且功... ...
  • 在我們開發Winform界面的時候,有時候會遇到需要對一些欄位進行一些彙總的管理,如果在列表中能夠對錶格列表中的內容進行分組展示,將比較符合我們的預期,本篇隨筆介紹在Winform開發中如何利用DevExpress的GridView實現該功能。 ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式幾天記憶體就要爆一次,不知道咋回事,找不出原因,讓我幫忙看一下,這種問題分析dump是最簡單粗暴了,拿到dump後接下來就是一頓分析。 二:WinDbg 分析 1. 程式為什麼會暴 程式既然會爆,可能是虛擬地址受限,也可能是系統記憶體不足,可以用 ...
  • 實例方法和靜態方法之間有幾個關鍵的區別: 1. 實例方法: 關聯對象: 實例方法是與對象實例相關聯的,必須通過實例來調用。 this 關鍵字: 實例方法中可以使用 this 關鍵字引用當前實例。 訪問實例成員: 實例方法可以直接訪問和修改實例的欄位、屬性和其他成員。 生命周期: 實例方法的生命周期與 ...
  • 一、前言 到這篇文章為止,關於.NET "溫故知新"系列的基礎知識就完結了,從這一系列的系統回顧和再學習,對於.NET core、ASP.NET CORE又有了一個新的認識。 不光是從使用,還包括這些知識點的原理,雖然深入原理談不上,但對於日常使用也夠了,我想的是知其然,知其所以然。 在實際開發過程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...