認識ESP32內建的脈衝計數器(二)

上一篇文章介紹了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秒)。普通開關的彈跳震盪訊號,也能透過這個方式濾除。

突波(glitch)與脈衝寬度

程式透過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);
}
Posts created 494

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top