上一篇文章介紹了ESP32內部的脈衝計數器單元(Pulse Counter Unit,簡稱PCNT),以及操控脈衝計數器的pcnt.h程式庫,本文將改用新版的pulse_cnt.h程式庫,編寫感測旋轉編碼器脈衝的Arduino程式。
附帶說明,脈衝計數器單元的暫存器詳細說明,可參閱樂鑫官方的「ESP32技術參考手冊」第17章(PDF格式)。
使用pulse_cnt.h程式庫計數脈衝
pulse_cnt.h程式庫的語法,跟上一篇文章採用的pcnt.h不太一樣,主要是舊版用一個結構體(pcnt_config_t)設定脈衝計數器,新版將它拆分成數個。
以檢測旋轉編碼器的轉向和脈衝數為例,程式首先要引用pulse_cnt.h標頭檔,然後宣告兩個分別參照到「脈衝計數器單元」和「通道(channel)」的全域變數:
#include <driver/pulse_cnt.h> // 適用於2.x與3.x平台的脈衝計數器程式庫 pcnt_unit_handle_t pcnt_unit = NULL; // 參照到脈衝計數器單元 pcnt_channel_handle_t pcnt_chan = NULL; // 參照到通道
然後設定計數值上下限、濾波器和訊號輸入腳,底下各節將說明設定方式。
設定脈衝計數值的上、下限
透過pcnt_unit_config_t型態的結構體,設置脈衝計數值的上、下限,然後執行pcnt_new_unit()套用設定。
pcnt_unit_config_t unit_config = { .low_limit = -10, // 計數值下限 .high_limit = 10, // 計數值上限 }; pcnt_new_unit(&unit_config, &pcnt_unit); // 套用計數值
套用計數值的pcnt_new_unit()函式接收兩個參數,第一個參數是包含計數值上下限的pcnt_unit_config_t結構體的參照;第二個參數是脈衝計數器單元的參照。此函式將傳回esp_err_t型態的下列可能值,本文的程式沒有處理傳回值。
- ESP_OK:成功建立脈衝計數器單元
- ESP_ERR_INVALID_ARG:參數無效(如:上、下限值超出範圍)
- ESP_ERR_NO_MEM:記憶體不足錯誤。
- ESP_ERR_NOT_FOUND:所有脈衝計數器單元都已經用完,沒有可用的單元。
- ESP_FAIL:因為其他錯誤,無法建立脈衝計數器單元。
設定脈衝訊號的上升與下降邊緣的動作
突波(glitch)指的正常訊號之外的干擾雜訊。脈衝計數器內建濾波器,可忽略小於指定臨界值寬度的脈衝,達到濾波效果,此寬度的單位為ns(奈秒,即;10-9秒)。普通開關的彈跳震盪訊號,也能透過這個方式濾除。
程式透過pcnt_glitch_filter_config_t型態的結構體設置突波濾波器,然後執行pcnt_unit_set_glitch_filter()套用設定。經實驗發現,最大突波寬度設為10µs(微秒),適用於旋轉編碼器(開關);若寬度值太大,某些正常訊號會被當成雜訊過濾掉。
pcnt_glitch_filter_config_t filter_config = { .max_glitch_ns = 10000, // 設最大突波寬度為10µs }; pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config); // 套用濾波器設定
套用濾波器設定的pcnt_unit_set_glitch_filter ()函式接收兩個參數,第一個參數是脈衝計數器單元,第二個參數是包含濾波器設定值的pcnt_glitch_filter_config_t結構體的參照。此函式也將傳回esp_err_t型態值。
設置通道訊號輸入腳
透過pcnt_chan_config_t型態的結構體,設置脈衝訊號與控制訊號的輸入腳,然後執行pcnt_new_channel()套用設定。
pcnt_chan_config_t chan_config = { .edge_gpio_num = CLK_PIN, // 邊緣(脈衝訊號)輸入腳 .level_gpio_num = DT_PIN, // 電位(控制訊號)輸入腳 }; pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan); // 新增通道
新增通道的pcnt_new_channel()函式接收三個參數,第一個是脈衝計數器單元,第二個是包含訊號輸入腳設定的結構體參照,第三個是全域變數“pcnt_chan”脈衝通道的參照。此函式也將傳回esp_err_t型態值。
設定脈衝訊號的計數模式
脈衝訊號的邊緣(edge)以及電位(level)會影響計數器的運作方式,例如,增加、減少或者不改變計數值,其行為分別由pcnt_channel_set_edge_action()和pcnt_channel_set_level_action()函式決定。
pcnt_channel_set_edge_action()函式用於設定訊號通道的上升、下降邊緣的動作,其原型如下:
esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act);
此函式接受如下三個參數,並傳回上文介紹過的esp_err_t型態值。
- chan參數:指向pcnt_new_channel()建立的脈衝計數器通道
- pos_act參數:對上升邊緣執行的操作
- neg_act參數:對下降邊緣執行的操作
對上升和下降邊緣的操作,都是定義在pcnt_types.h裡的pcnt_channel_edge_action_t型態常數值,如下:
typedef enum { PCNT_CHANNEL_EDGE_ACTION_HOLD, // 保持目前的計數值 PCNT_CHANNEL_EDGE_ACTION_INCREASE, // 增加計數值 PCNT_CHANNEL_EDGE_ACTION_DECREASE, // 減少計數值 } pcnt_channel_edge_action_t;
底下的設定代表:於訊號上升邊緣「增加計數值」,下降邊緣「保持不變」。
pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD);
pcnt_channel_set_level_action()函式用於設定訊號通道電位發生變化時(例如:從高變低)的操作,其原型如下:
esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act);
此函式接受如下三個參數,並傳回上文介紹過的esp_err_t型態值。
- chan:參照到pcnt_new_channel()建立的脈衝計數器通道
- high_act:高電位時的動作
- low_act:低電位時的動作
高、低電位的動作,都是定義在pcnt_types.h裡的pcnt_channel_level_action_t型態常數值,如下:
typedef enum { PCNT_CHANNEL_LEVEL_ACTION_KEEP, // 維持目前的計數模式 PCNT_CHANNEL_LEVEL_ACTION_INVERSE, // 反向計數(如:增加變減少) PCNT_CHANNEL_LEVEL_ACTION_HOLD, // 保持目前的計數值 } pcnt_channel_level_action_t;
底下的設定代表:高電位時「維持」目前的計數模式、低電位時「反向」計數模式。
pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE);
完整的旋轉編碼器程式碼
同樣採用《超圖解ESP32應用實作》「動手做15-1:使用自訂程式庫製作旋鈕介面」的電路,順時針轉動旋轉編碼器時,增加計數值;逆時針轉動,減少計數值,完整的程式碼如下:
#include <driver/pulse_cnt.h> // 適用於2.x與3.x平台的脈衝計數器程式庫 #define CLK_PIN 4 // 旋轉編碼器的接腳 #define DT_PIN 5 #define SW_PIN 6 pcnt_unit_handle_t pcnt_unit = NULL; // 宣告脈衝計數器單元 pcnt_channel_handle_t pcnt_chan = NULL; // 宣告通道物件 void initPCNT() { // 設置並建立脈衝計數器單元 pcnt_unit_config_t unit_config = { .low_limit = -10, // 計數值下限 .high_limit = 10, // 計數值上限 }; pcnt_new_unit(&unit_config, &pcnt_unit); // 設置濾波器 pcnt_glitch_filter_config_t filter_config = { .max_glitch_ns = 10000, // 設最大突波雜訊寬度為10µs }; pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config); // 設置訊號輸入腳以及新增通道 pcnt_chan_config_t chan_config = { .edge_gpio_num = CLK_PIN, // 邊緣(脈衝訊號)輸入腳 .level_gpio_num = DT_PIN, // 電位(控制訊號)輸入腳 }; pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan); // 新增通道 // 設定訊號上升、下降邊緣的動作 pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD); // 設定訊號通道的高、低電位動作 pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE); pcnt_unit_clear_count(pcnt_unit); // 清除計數器值 pcnt_unit_enable(pcnt_unit); // 啟用計數器單元 pcnt_unit_start(pcnt_unit); // 啟動計數器單元 } void setup() { Serial.begin(115200); pinMode(CLK_PIN, INPUT); // 全部接腳都設為「輸入」模式 pinMode(DT_PIN, INPUT); pinMode(SW_PIN, INPUT); initPCNT(); // 初始化 } void loop() { static int lastCount = 0; // 儲存「上一次」計數值 int count = 0; // 儲存本次計數值 pcnt_unit_get_count(pcnt_unit, &count); // 讀取計數器值 if (lastCount != count) { lastCount = count; Serial.printf("%d\n", count); // 顯示計數值 } if (digitalRead(SW_PIN) == LOW) { pcnt_unit_clear_count(pcnt_unit); } delay(10); }