自從 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 電源開関,直接銲 3.3V logic-level 嘅 N-Channel MOSFET IRLS 3813 喺 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: