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

雖然 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 空針。

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

所有金屬接觸開関都要加 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: