上一篇文章介紹了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);
}
