ESP8266 & encoder 調速開関

自從 Arduino IDE 出現之後,microprocessor 嘅軟件開發變得容易得多。衡量過功能、價錢、良率之後,我認為淘宝可以買倒嘅 Wemos D1 R2 開發板可索性最高;佢用 ESP8266 MCU,比相對為人熟悉嘅 Arduino UNO R3 開發板速度快 10 倍以上,有 WiFi 功能可以運用於 IoT,兼容 Arduino language。

Wemos D1 R2 with DIY shields

雖然 Wemos D1 R2 同 Arduino UNO R3 尺寸一樣,但因為 GPIO 嘅電壓係 3.3V 同 UNO 唔同,所以 UNO 絶大部份嘅 shield 都唔啱用;但可以用 4*6cm PCB 板自制 shield 配合 UNO 用嘅外殼。Shield 嘅模組化概念可以做到功能分割,容易 debug 同擴充功能,但最大問題在於排母品質唔夠高,有接觸不良問題;淘宝有款 11cm 加长排母可以解決依個関鍵問題。ESP8266 嘅 GPIO 有唔同特質,如果要用 D6, D7, D8 就要喺開發板銲三條電線帶去 4*6 shield 空針。

Wemos D1 R2 pinout & DIY power output shield

一般 IoT 都係用手机 App 操控硬件,但我既諗法係方便實用至上,喺 Wemos D1 R2 上面直接加一塊旋轉編碼器 shield,一個制做晒 12V 電源開関同 PWM 調速;絶大部份電腦 4pin PWM 風扇都可以用 3.3V PWM 調較 30-100% 輸入功率,一個 18cm DC 風扇比一般交流電風扇節能十倍以上。至於 12V 電源開関,直接銲 3.3V logic-level 嘅 N-Channel MOSFET IRLS 3813DC-007B 插座上面會比較高效同簡單。

hardware debounce at encoder pin

所有金屬接觸開関都要加 0.1uF 電容做 debounce,另外仲要喺 code 裏面加 software debounce 先可以正常操作。一般 Arduino 嘅 encoder library 效果未如理想,我已經重新寫過穏定更高嘅 encoder 操控;單係一項調速開関功能要做到可靠耐用嘅地步唔係想像之中咁簡單。

//*******************************************************************
//   Instantiate class objects, global var and Function Prototypes
//*******************************************************************

const uint8_t arr_pwm[] = { 255, 191, 127, 77, 0 };
int8_t arr_pwm_index = 3;

// Function Prototypes
void fn_handle_int();
void fn_handle_encoder(bool encoder_cw);
void fn_encoder_pin1_change();
void fn_encoder_pin2_change();
void fn_but_change();

// GPIO
const uint8_t pin_rx = 3;  //ws2812 LED
const uint8_t pin_tx = 1;
const uint8_t pin_a0 = 17;
const uint8_t pin_d0 = 16;
const uint8_t pin_d1 = 5;
const uint8_t pin_d2 = 4;
const uint8_t pin_d3 = 0;
const uint8_t pin_d4 = 2;
const uint8_t pin_d5 = 14;
const uint8_t pin_d6 = 12;
const uint8_t pin_d7 = 13;
const uint8_t pin_d8 = 15;

const uint8_t pin_standby = pin_d0;
const uint8_t pin_scl = pin_d1;
const uint8_t pin_sda = pin_d2;
const uint8_t pin_int = pin_d3;
const uint8_t pin_led = pin_d4;  // LED_BUILTIN
const uint8_t pin_pwm = pin_d5;
const uint8_t pin_in1 = pin_d6;
const uint8_t pin_in2 = pin_d7;
const uint8_t pin_counter = pin_d8;

//*******************************************************************
//   setup
//*******************************************************************

void setup() {
  analogWriteFreq(38000);  // IRremote
  analogWriteRange(255);
  pinMode(pin_d0, OUTPUT);
  digitalWrite(pin_d0, 0);  // standby
  pinMode(pin_d4, OUTPUT);
  digitalWrite(pin_d4, 1);  // led off
  pinMode(pin_d5, OUTPUT);
  analogWrite(pin_d5, 0);  // pwm 0

  // encoder
  pinMode(pin_d3, INPUT_PULLUP);
  pinMode(pin_d6, INPUT_PULLUP);
  pinMode(pin_d7, INPUT_PULLUP);
  // detect interrupt(CHANGE, FALLING, RISING, LOW)
  attachInterrupt(digitalPinToInterrupt(pin_d3), fn_but_change, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pin_d6), fn_encoder_pin1_change, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pin_d7), fn_encoder_pin2_change, CHANGE);
}

//*******************************************************************
//   interrupt & loop
//*******************************************************************

ICACHE_RAM_ATTR void fn_handle_int() {
  if (!digitalRead(pin_standby)) {
    analogWrite(pin_pwm, arr_pwm[arr_pwm_index]);
  } else {
    analogWrite(pin_pwm, 0);
  }
  digitalWrite(pin_standby, !digitalRead(pin_standby));
}

ICACHE_RAM_ATTR void fn_handle_encoder(bool encoder_cw) {
  if (digitalRead(pin_standby)) {
  	const uint8_t arr_size = (sizeof(arr_pwm) / sizeof(uint8_t));
    (encoder_cw) ? arr_pwm_index++ : arr_pwm_index--;
    arr_pwm_index = constrain(arr_pwm_index, 0, arr_size - 1);
    analogWrite(pin_pwm, arr_pwm[arr_pwm_index]);
  }
}

void loop() {}

//*******************************************************************
//  function
//*******************************************************************

volatile static bool pin1_press = false;  // volatile for interupt
volatile static bool pin2_press = false;  // volatile for interupt

ICACHE_RAM_ATTR void fn_encoder_pin1_change() {
  volatile static uint32_t encoder_pin1_time = -1;
  pin1_press = !digitalRead(pin_in1);
  if (pin1_press) {
    encoder_pin1_time = millis();
  } else {
    volatile uint32_t pin1_holdtime = millis() - encoder_pin1_time;
    // Serial.println(pin1_holdtime);
    if (pin1_holdtime >= 0 && pin1_holdtime < 80) {  // debounce
      pin1_press = true;
      if (!pin2_press) {
        fn_handle_encoder(false);
      }
    }
    encoder_pin1_time = -1;
    pin1_press = false;
  }
}

ICACHE_RAM_ATTR void fn_encoder_pin2_change() {
  volatile static uint32_t encoder_pin2_time = -1;
  pin2_press = !digitalRead(pin_in2);
  if (pin2_press) {
    encoder_pin2_time = millis();
  } else {
    volatile uint32_t pin2_holdtime = millis() - encoder_pin2_time;
    // Serial.println(pin2_holdtime);
    if (pin2_holdtime >= 0 && pin2_holdtime < 80) {  // debounce
      pin2_press = true;
      if (!pin1_press) {
        fn_handle_encoder(true);
      }
    }
    encoder_pin2_time = -1;
    pin2_press = false;
  }
}

ICACHE_RAM_ATTR void fn_but_change() {
  volatile static uint32_t but_presstime = 0;
  volatile static bool but_press = false;
  but_press = !digitalRead(pin_int);
  if (but_press) {
    but_presstime = millis();
  } else {
    but_presstime = millis() - but_presstime;
    if (but_presstime < 600) {  // debounce
      fn_handle_int();
    }
    but_presstime = -1;
  }
}

Reference: