直流馬達驅動板的軟體實作-使用 ESP8266 MultiPWMs Lib ver.0.8

No Comments

這張驅動板使用隔離 IC 以小訊號控制大電流的開關,以 H 橋的方式來操控直流馬達。
光這張板子的六條規則就足以讓我們寫一份程式了。當程式完成後,就是接上馬達來作實際的參數調製。因此在筆者開新這篇文章時,程式已寫好但從未實測過,不過,從波形量測結果就知道成功一半了。
因筆者預期這套硬體將包含 DCDC 電源,level-shifter,及 ESP8266,以及分壓電阻作為電壓變化的 ADC 輸入來判定馬達的負載程度。故,本篇離結束還有硬體配置及參數調製的這一段路該走,將陸續追加。不過現主時,就已成一篇完整的文章了。
程式碼置於最後。

完整的波形圖
可看出先正轉,再反轉,再正轉,而中間夾著剎車訊號。
反轉後正轉的放大圖,中間的剎車訊號。

主程式

#include"Esp8266MultiPwms.h"
// use MultiPwms Lib v.0.8.
// use "D1 R2 & mini"
// 2020.12.31.

// need to call Start() again if Stop()

#define MDELAY delay(50)

typedef enum{
    mstart=0,
    mforward=1,
    mreward=2,
    mforward_pause=3,
    mreward_pause=4,
    mstop=5,
} eTheState;

eTheState the_state;
int the_dc;

cMultiPwms M1(D2, 5000, 100, 0, 0);
cMultiPwms M2(D5, 5000, 100, 0, 0);
cMultiPwms M3(D6, 5000, 100, 0, 0);
cMultiPwms M4(D7, 5000, 100, 0, 0);
cMultiPwms LEDB(D4, 25000, 500, 1, 1);

void setup_cmultipwms() {
    pinMode(D2, OUTPUT); digitalWrite(D2, LOW); // since wrong level is harmful
    pinMode(D5, OUTPUT); digitalWrite(D5, LOW); // since wrong level is harmful
    pinMode(D6, OUTPUT); digitalWrite(D6, LOW); // since wrong level is harmful
    pinMode(D7, OUTPUT); digitalWrite(D7, LOW); // since wrong level is harmful

    pinMode(D4, OUTPUT); // led
    LEDB.setDC(0);
    LEDB.Resume();

    Start();
}

void SetDC_all_temp(unsigned a){
    M1.setDC(a);
    M2.setDC(a);
    M3.setDC(a);
    M4.setDC(a);
}

void SetDC_all(unsigned a){
    SetDC_all_temp(a);
    the_dc=a;
}

void SetDC_temp(class cMultiPwms &m, unsigned a){ // why need to use "class"
    m.setDC(a);
}

void SetDC(cMultiPwms &m, unsigned a){
    SetDC_temp(m, a);
    the_dc=a;
}

void Start(){
    switch (the_state){
        case mstart:
        case mstop:
            the_state=mstart;
            SetDC_all(0); // this is must
            cMultiPwms::SyncStart(M1);
            cMultiPwms::SyncNext(M2, 0);
            cMultiPwms::SyncNext(M3, 0);
            cMultiPwms::SyncNext(M4, 0);
            cMultiPwms::SyncEnd(M1);
            SetDC_all(0);
            M1.Pause();
            M2.Pause();
            M3.Pause();
            M4.Pause();
            break;
    }
}

void Stop(){
    switch (the_state){
        case mforward:
            M4.Stop();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            M1.Stop();
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            MDELAY;
            M2.Stop();
            MDELAY;
            break;
        case mreward:
            M2.Stop();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            M3.Stop();
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            MDELAY;
            M4.Stop();
            MDELAY;
            break;
        case mforward_pause:
            M1.Stop();
            MDELAY;
            M2.Stop();
            MDELAY;
            break;
        case mreward_pause:
            M3.Stop();
            MDELAY;
            M4.Stop();
            MDELAY;
            break;
        default: return;
    }
    SetDC_all(0);
    the_state=mstop;
}

void Pause(){
    switch (the_state){
        case mforward:
            the_state=mforward_pause;
            M4.Pause();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            break;
        case mreward:
            the_state=mreward_pause;
            M2.Pause();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            break;
    }
}

void Resume(){
    switch (the_state){
        case mforward_pause:
            the_state=mforward;
            M2.Pause();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward_pause:
            the_state=mreward;
            M4.Pause();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
    }
}

void Forward(){
    switch (the_state){
        case mstart:
            M1.Resume();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward:
            M2.Pause();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            M3.Pause();
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            MDELAY;
            M1.Resume();
            MDELAY;
            break;
        case mforward_pause:
            M2.Pause();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward_pause:
            M3.Pause();
            MDELAY;
            M1.Resume();
            MDELAY;
            break;
        default: return;
    }
    the_state=mforward;
}

void Reward(){
    switch (the_state){
        case mstart:
            M3.Resume();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
        case mforward:
            M4.Pause();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            M1.Pause();
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            MDELAY;
            M3.Resume();
            MDELAY;
            break;
        case mforward_pause:
            M1.Pause();
            MDELAY;
            M3.Resume();
            MDELAY;
            break;
        case mreward_pause:
            M4.Pause();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
        default: return;
    }
    the_state=mreward;
}

void PrintState(){
    switch (the_state){
        case mstart:
            printf("\r\nStart\r\n");
            break;
        case mforward:
            printf("\r\nForward\r\n");
            break;
        case mreward:
            printf("\r\nReward\r\n");
            break;
        case mforward_pause: case mreward_pause:
            printf("\r\nPause\r\n");
            break;
        case mstop:
            printf("\r\nStop\r\n");
            break;
    }
}


void loop_cmultipwms(){
    static unsigned tmop=micros();
    if (cMultiPwms::Timeout(1500, tmop)) cMultiPwms::traverseLinkedList();

    static unsigned tmoq=micros();
    if (cMultiPwms::Timeout(125, tmoq)){
        static unsigned v=0;
        SetDC_all(v);
        if ((v+=3)>99) v=1;
    }

    static unsigned tmor=micros();
    if (cMultiPwms::Timeout(4000, tmor)){
        switch (random(65535)%20){
            case 0: case 1: case 2: case 3:
                Forward();
                break;
            case 4: case 5: case 6: case 7:
                Reward();
                break;
            case 8: case 9: case 10: case 11: case 12:
                Pause();
                break;
            case 13:
                Resume();
                break;
            case 14:
                Stop();
                break;
            case 15: case 16: case 17: case 18: case 19:
                Start();
                break;
        }
        PrintState();
    }

    static unsigned tmos=micros();
    if (cMultiPwms::Timeout(100, tmos)){
        static unsigned cntt=0;
        cntt++;
        switch (the_state){
            case mstart:
                LEDB.detachGpio();
                digitalWrite(D4, LOW);
                break;
            case mforward:
                LEDB.detachGpio();
                if (cntt&0x4) digitalWrite(D4, LOW);
                else digitalWrite(D4, HIGH);
                break;
            case mreward:
                LEDB.detachGpio();
                if (cntt&0x1) digitalWrite(D4, LOW);
                else digitalWrite(D4, HIGH);
                break;
            case mforward_pause:
                LEDB.setGpio(D4);
                {unsigned light1=cntt%7+1;
                light1=light1*light1*light1;
                LEDB.setDC(0, light1);}
                break;
            case mreward_pause:
                LEDB.setGpio(D4);
                {unsigned light2=cntt%7+1;
                light2=light2*light2*light2;
                LEDB.setDC(0, 500/light2);}
                break;
            case mstop:
                LEDB.detachGpio();
                digitalWrite(D4, HIGH);
                break;
        }
    }
}




void setup(){
    setup_cmultipwms();

    Serial.begin(115200);
    delay(5000);

    //setup_updater();
}


void loop(){
    //loop_updater();
    loop_cmultipwms();
}

硬體部份的實作

  • 筆者前後製作了兩張板子,因第一張對 ADC 了解不清楚造成板子損壞。但,終版應是要第三張板子才行了,後述。簡單講目前是第二張板子。
  • 元件如下:
  • 有 DCDC 5V 及 3.3V 兩張電源板,實測下輸入 5V 至 15 V 皆可用。因考量到功率;筆者並未查實,但號稱可支援到 3A 的這兩張電源板應就不必再顧慮太多。
  • 3.3V-to-5V 的 8 channel level shifter。
  • 數位電位器 X9C104S,只用在第一版,純測試它而上。但也沒機會測試到。
  • 可變電阻 10K,用在 ADC。
  • 直流馬達驅動板。
  • ESP-12S。ESP-i 族系介紹
  • ESP 多用電路板。
第一版成品
  • 第二版的硬體如下圖,不像第一版每個元件都上了杜邦母腳座,也就是若元件損壞就可直接抽換。第二版就直接焊上以縮小高度。
  • 以下的實作細節考量就以第二版來說明,並且是完整的考量,即,包含一/二/三版。
第二版正面成品
第二版反面成品
實際的應用完整接線圖

硬體實作考量與細節

  • 目的:車用的排氣管閥門。對象是直流 12V 二線有刷馬達。
  • 關卡:因對象就是如此單純,單單就一顆傳統馬達。閥門全開即停,全關即停,開度多少,甚至於轉速多高開度如何,這就是我們的關卡與挑戰;將全透過軟體來實現。不過這些關卡之前還有基礎的挑戰關卡,也就是本篇要說的。簡單講,上述關卡,我們透過輸出電壓供應給馬達,並再反饋/監控該電壓,由 ADC 輸入數據來比對當負載發生變化/例如閥門全關後馬達轉動受阻致 CMOS 的輸出發生壓流變化,以此來判定閥門已全關。以此為基礎來實現上述功能。
  • ESP-12S,又或者說,已完整代表 ESP8266,所有可用 GPIO 已拉出。在 ADC 的部份並未內建分壓,故我們必須外部分壓出 1V 給 ADC。故使用上了分壓電阻。
  • GPIO 用掉了 13/12/14/4,作為,分別對應 PWM M1/M2/M3/M4。
  • GPIO16 用作 level shifter 的 OE/output-enable。
  • GPIO2 是燈號。
  • 剩餘 GPIO5,是真尚可利用,將預留為轉速的輸入。
  • 筆者還將 GPIO 15/0/2 拉出,並試圖使用之,但,確認在不加外部 pull-high/low 的前提下:GPIO2(燈號)已內部 pull-high,確認無法在 client code/sketch code 中復用。GPIO15/0 因筆者沒有外部 pull-high/low,故確定也是無法使用。但讀者可嘗試外加看看能不能復用。但因這三根腳位的 high/low 與 booting 關聯,故原則上無法復用請自行谷歌尋求排解之法。
  • UART RX0 作為 PWM 的 parking pin。故只拉出 TX0 作為輸出列印。
  • 因馬達須正反轉,故加了兩顆整流二極體以輸入至 ADC 以可接收正轉或反轉的電壓訊號。
  • 因車用,電壓範圍將是介於 12V 至 14.5V,故最高電壓取 15V。也就是實際施加了 15V 的電壓,並量測與調整,可調分壓電阻(水藍色那一顆)的 ADC 輸入端使得剛好是 1V。
  • 實測下,馬達介於 5.25V 至 14.5V 皆可作動。
  • 馬達驅動板,有 M1/M2/M3/M4 不能同時為 high 的衝突。幸而該 level shifter 有 OE 的控制腳位。而 GPIO16 上電預設就是內部 pull-low。故可不外加 pull-low,便可使用 GPIO16 配 OE。待 level shifter 3.3V 端的準位不再浮動時,再將 OE 拉 high。如此就可保持受控前 M1 至 M4 都在 low 準位。然而,剛上電時,仍會有 6ms 的 high 準位。使得四顆 CMOS 有 6ms 的全導通衝突。筆者在第二版中認為無妨,實測上也無傷大雅。
  • 但,筆者是用 buck-boost power module 作為供電來源,當輸出調為小於 12V 時,全導通造成限流啟動,輸出電壓降為 4V,致系統無正常電壓而 ESP8266 處於無法啟動狀態,再致全導通狀態一直維持著。大於 12V 此現象發生機率才會偏低。因此結論就是,在 level shifter 5V 端的輸出腳位,還是必須 pull-low 才行。
  • 最後,再談到 ADC。因我們現在是使用無 USB-to-UART 的 ESP-12S 及主板。故必須將前面文章整理出來的線上更新主程式加入主程式以得以更新之。但該份程式使用到了 ESP.getVcc(),將必須將其註解掉。否則,一旦 ADC pin 有外接了線路,再使用了 ESP.getVcc(),將使得 3.3V 與線路電壓衝突而損壞電源元件。
  • 配合硬體的主程式如下。

補充

  • ADC 的取樣率,當前查得的 ESP8266 最高為 6k。筆者目前的 PWM 設定是 5ms/0.2kHz。故對於 ADC 的取樣將採 0.1k(為基礎/若倍增則為算平均值)。
  • https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html
  • http://garretlab.web.fc2.com/en/arduino/inside/hardware/arduino/avr/cores/arduino/wiring_analog.c/analogRead.html
  • https://www.arduino.cn/thread-12569-1-1.html
  • 對於直流馬達驅動板的 PWM 輸出波形,空載並沒有意外。但一旦接上了馬達,波形就變了個樣,如下四圖所示。將考驗 ADC 取樣與判斷的準確性與預期性。

20210201 再補充

  • 測試的主程式再作了修改與追加,置於本文最下面的下載點選項目。就是針對本案例的完善。但筆者能力不足地/非以數學方法來實作馬達遇阻時的停止判斷,及實時電壓偏移時的修正,都只以邏輯地來判斷之。
  • 其次,本案例是失敗的。ESP8266 在剛上電時,GPIOs 是會有短暫地拉 high。這正是此馬達驅動板的致命傷。筆者因此燒掉了一張驅動板;當電壓流充足時,就算短暫地只有 5us 照燒。
  • 也因此筆者檢視了 ESP-01M,ESP-12S 兩張模組的所有引出的 GPIOs,唯有 GPIO4,GPIO5 不會發生上電曾拉 high 的狀況;請注意,是拉 high 而非浮動 high;因筆者加了下拉電阻一樣無效。而 GPIO15 雖然有內部或外部 pull low,但它仍有機率短暫(5us)拉 high。因此,本文原則上不再更新,但筆者會改用 4/5 來規避驅動板燒掉的可能。
  • 還有一點很重要,依筆者目前的測試結果,以 ESP-12S 而言,所有引出的 GPIOs 皆可復用,只要確保 GPIO15/GPIO0/GPIO2 的外部 pull-high/low,不會抵觸 ESP8266 boot-up 時機的幾種模式即可。
  • 另外,當使用本案例程式時,GPIO16 是無法復用的,筆者至此仍找不出哪邊影響到了。成板與 ESP-12S 單獨模組都曾嘗試過,除了 wifi 的程式部份仍作動,程式的其他部份都註解掉了,只剩 I/O 的拉動而已,GPIO16 就仍是聞風不動。但另外單獨使用 D1-mini,GPIO16(D0)卻又是可作動的。
  • 以上前兩點,讀者若涉用,尚須再行驗證筆者所言是否相符。

20210217 再補充

  • 這張驅動板 1.4V 就可驅動 IO。輸出的電壓源需 5V 以上。
正轉空載輸出
正轉負載輸出
逆轉空載輸出
逆轉負載輸出

配合硬體的主程式

/*
 *  This sketch demonstrates how to set up a simple HTTP-like server.
 *  The server will set a GPIO pin depending on the request
 *    http://server_ip/gpio/0 will set the GPIO2 low,
 *    http://server_ip/gpio/1 will set the GPIO2 high
 *  server_ip is the IP address of the ESP8266 module, will be 
 *  printed to Serial when the module is connected.
 */

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

#ifdef ESP8266
extern "C" {
    #include "user_interface.h"
}
#endif


// Set up output serial port (could be a SoftwareSerial
// if really wanted).
// Wired to the blue LED on an ESP-01 board, active LOW.
//
// Cannot be used simultaneously with Serial.print/println()
// calls, as TX is wired to the same pin. 
//
// UNLESS: You swap the TX pin using the alternate pinout.


#define AP_MODE_MYSSID      "ESP_TESTAP"
#define AP_MODE_MYPW        "87654321"


#if 0
    #define STA_MODE_SSID    "--------"
    #define STA_MODE_PW      "--------"
#else
    #define STA_MODE_SSID    AP_MODE_MYSSID
    #define STA_MODE_PW      AP_MODE_MYPW
#endif


const char *const WIFI_CRED[][2]={
    {"ESP_TESTAP", "87654321"},
    {"Office", "123456"},
    {"Xperia X Performance_bc10", "e848acbcfd98"},
};


// use either of one
#define HTTP_SERVER 1
#define HTTP_CLIENT (!HTTP_SERVER)


// use either of one
#define AP_MODE 1
#define STA_MODE (!AP_MODE)


// use either of one
//#define USE_STA_NATIVE
#define USE_STA_WIFI_MULTI


// use either of one
//#define USE_HTTP_HTTP_CLIENT
//#define USE_HTTP_WIFI_CLIENT
#define USE_HTTP_WIFIHTTP_HYBRID


#define HOSTNAME "esp8266-iot" // i.e., http://esp8266-iot.local/
#define UPDFILE "a.bin" // the facility has no version check, so should do it by user.
// the webpage for updating to local is http://HOSTNAME.local/update
// local updated from remote is http://HOSTNAME.local/upd




#if defined(USE_STA_WIFI_MULTI)
ESP8266WiFiMulti *WiFiMulti=0;
#endif

Stream &ehConsolePort(Serial);

// for http server
ESP8266WebServer *server_p;

// for remote ip
char remote_ip[16] = {0};

// for url
String web_link;

// for station mode is entered
bool is_station_entered=false;

// webupdater
ESP8266HTTPUpdateServer httpUpdater;




// ==================================================================
const char * const RST_REASONS[] =
{
    "REASON_DEFAULT_RST",
    "REASON_WDT_RST",
    "REASON_EXCEPTION_RST",
    "REASON_SOFT_WDT_RST",
    "REASON_SOFT_RESTART",
    "REASON_DEEP_SLEEP_AWAKE",
    "REASON_EXT_SYS_RST"
};

const char * const FLASH_SIZE_MAP_NAMES[] =
{
    "FLASH_SIZE_4M_MAP_256_256",
    "FLASH_SIZE_2M",
    "FLASH_SIZE_8M_MAP_512_512",
    "FLASH_SIZE_16M_MAP_512_512",
    "FLASH_SIZE_32M_MAP_512_512",
    "FLASH_SIZE_16M_MAP_1024_1024",
    "FLASH_SIZE_32M_MAP_1024_1024"
};

const char * const OP_MODE_NAMES[] 
{
    "NULL_MODE",
    "STATION_MODE",
    "SOFTAP_MODE",
    "STATIONAP_MODE"
};

const char * const AUTH_MODE_NAMES[] 
{
    "AUTH_OPEN",
    "AUTH_WEP",
    "AUTH_WPA_PSK",
    "AUTH_WPA2_PSK",
    "AUTH_WPA_WPA2_PSK",
    "AUTH_MAX"
};

const char * const PHY_MODE_NAMES[]
{
    "",
    "PHY_MODE_11B",
    "PHY_MODE_11G",
    "PHY_MODE_11N"
};

const char * const EVENT_NAMES[]
{
    "EVENT_STAMODE_CONNECTED",
    "EVENT_STAMODE_DISCONNECTED",
    "EVENT_STAMODE_AUTHMODE_CHANGE",
    "EVENT_STAMODE_GOT_IP",
    "EVENT_SOFTAPMODE_STACONNECTED",
    "EVENT_SOFTAPMODE_STADISCONNECTED",
    "EVENT_MAX"
};

const char * const EVENT_REASONS[]
{
    "",
    "REASON_UNSPECIFIED",
    "REASON_AUTH_EXPIRE",
    "REASON_AUTH_LEAVE",
    "REASON_ASSOC_EXPIRE",
    "REASON_ASSOC_TOOMANY",
    "REASON_NOT_AUTHED",
    "REASON_NOT_ASSOCED",
    "REASON_ASSOC_LEAVE",
    "REASON_ASSOC_NOT_AUTHED",
    "REASON_DISASSOC_PWRCAP_BAD",
    "REASON_DISASSOC_SUPCHAN_BAD",
    "REASON_IE_INVALID",
    "REASON_MIC_FAILURE",
    "REASON_4WAY_HANDSHAKE_TIMEOUT",
    "REASON_GROUP_KEY_UPDATE_TIMEOUT",
    "REASON_IE_IN_4WAY_DIFFERS",
    "REASON_GROUP_CIPHER_INVALID",
    "REASON_PAIRWISE_CIPHER_INVALID",
    "REASON_AKMP_INVALID",
    "REASON_UNSUPP_RSN_IE_VERSION",
    "REASON_INVALID_RSN_IE_CAP",
    "REASON_802_1X_AUTH_FAILED",
    "REASON_CIPHER_SUITE_REJECTED",
};


void print_system_info(Stream & consolePort)
{
    const rst_info * resetInfo = system_get_rst_info();
    consolePort.print(F("system_get_rst_info() reset reason: "));
    consolePort.println(RST_REASONS[resetInfo->reason]);

    consolePort.print(F("system_get_free_heap_size(): "));
    consolePort.println(system_get_free_heap_size());

    consolePort.print(F("system_get_os_print(): "));
    consolePort.println(system_get_os_print());
    system_set_os_print(1);
    consolePort.print(F("system_get_os_print(): "));
    consolePort.println(system_get_os_print());

    consolePort.print(F("system_print_meminfo(): "));
    consolePort.println(system_get_os_print());
    system_print_meminfo();

    consolePort.print(F("system_get_chip_id(): 0x"));
    consolePort.println(system_get_chip_id(), HEX);

    consolePort.print(F("system_get_sdk_version(): "));
    consolePort.println(system_get_sdk_version());

    consolePort.print(F("system_get_boot_version(): "));
    consolePort.println(system_get_boot_version());

    consolePort.print(F("system_get_userbin_addr(): 0x"));
    consolePort.println(system_get_userbin_addr(), HEX);

    consolePort.print(F("system_get_boot_mode(): "));
    consolePort.println(system_get_boot_mode() == 0 ? F("SYS_BOOT_ENHANCE_MODE") : F("SYS_BOOT_NORMAL_MODE"));

    consolePort.print(F("system_get_cpu_freq(): "));
    consolePort.println(system_get_cpu_freq());

    consolePort.print(F("spi_flash_get_id(): 0x"));
    consolePort.println(spi_flash_get_id(), HEX);

    consolePort.print(F("system_get_flash_size_map(): "));
    consolePort.println(FLASH_SIZE_MAP_NAMES[system_get_flash_size_map()]);
}


void pre_setup(){

    print_system_info(Serial);

    // Try pushing frequency to 160MHz.
    system_update_cpu_freq(SYS_CPU_160MHZ);

    // System usually boots up in about 200ms.
    Serial.print(F("system_get_time(): "));
    Serial.println(system_get_time());

    Serial.print(F("system_get_cpu_freq(): "));
    Serial.println(system_get_cpu_freq());

    Serial.print(F("wifi_get_opmode(): "));
    Serial.print(wifi_get_opmode());
    Serial.print(F(" - "));
    Serial.println(OP_MODE_NAMES[wifi_get_opmode()]);

    Serial.print(F("wifi_get_opmode_default(): "));
    Serial.print(wifi_get_opmode_default());
    Serial.print(F(" - "));
    Serial.println(OP_MODE_NAMES[wifi_get_opmode_default()]);

    Serial.print(F("wifi_get_broadcast_if(): "));
    Serial.println(wifi_get_broadcast_if());
}


void post_setup(){
    Serial.printf("getResetReason: %s\r\n",         ESP.getResetReason().c_str());
    Serial.printf("getFreeHeap: %d\r\n",            ESP.getFreeHeap());
    Serial.printf("getChipId: %d\r\n",              ESP.getChipId());
    Serial.printf("getCoreVersion: %s\r\n",         ESP.getCoreVersion().c_str());
    Serial.printf("getSdkVersion: %s\r\n",          ESP.getSdkVersion());
    Serial.printf("getCpuFreqMHz: %u\r\n",          ESP.getCpuFreqMHz());
    Serial.printf("getSketchSize: %u\r\n",          ESP.getSketchSize());
    Serial.printf("getFreeSketchSpace: %u\r\n",     ESP.getFreeSketchSpace());
    Serial.printf("getSketchMD5: %s\r\n",           ESP.getSketchMD5().c_str());
    Serial.printf("getFlashChipId: %d\r\n",         ESP.getFlashChipId());
    Serial.printf("getFlashChipSize: %u\r\n",       ESP.getFlashChipSize());
    Serial.printf("getFlashChipRealSize: %u\r\n",   ESP.getFlashChipRealSize());
    Serial.printf("getFlashChipSpeed: %u\r\n",      ESP.getFlashChipSpeed());
    Serial.printf("getCycleCount: %u\r\n",          ESP.getCycleCount());
    ////Serial.printf("getVcc: %d\r\n",                 ESP.getVcc());
}


// callback function for wifi auto-messages
void wifi_event_handler_cb(System_Event_t * event)
{
    if (int(event->event)>=7) return;

    if (event->event<sizeof(EVENT_NAMES)/sizeof(EVENT_NAMES[0]))
        Serial.print(EVENT_NAMES[event->event]);
    Serial.print(" [wifi_auto_info] (");
    
    switch (event->event)
    {
        case EVENT_STAMODE_CONNECTED:
            break;
        case EVENT_STAMODE_DISCONNECTED:
            break;
        case EVENT_STAMODE_AUTHMODE_CHANGE:
            break;
        case EVENT_STAMODE_GOT_IP:
            break;
        case EVENT_SOFTAPMODE_STACONNECTED:
        case EVENT_SOFTAPMODE_STADISCONNECTED:
            {
                char mac[32] = {0};
                snprintf(mac, 32, MACSTR ", aid: %d" , MAC2STR(event->event_info.sta_connected.mac), event->event_info.sta_connected.aid);
                
                Serial.print(mac);
            }
            break;
        default:
            Serial.print(int(event->event));
    }

    Serial.println(")");
}


void print_softap_config(Stream &consolePort, softap_config const &config)
{
    consolePort.println();
    consolePort.println(F("SoftAP Configuration"));
    consolePort.println(F("--------------------"));

    consolePort.print(F("ssid:            "));
    consolePort.println((char *) config.ssid);

    consolePort.print(F("password:        "));
    consolePort.println((char *) config.password);

    consolePort.print(F("ssid_len:        "));
    consolePort.println(config.ssid_len);

    consolePort.print(F("channel:         "));
    consolePort.println(config.channel);

    consolePort.print(F("authmode:        "));
    consolePort.println(AUTH_MODE_NAMES[config.authmode]);

    consolePort.print(F("ssid_hidden:     "));
    consolePort.println(config.ssid_hidden);

    consolePort.print(F("max_connection:  "));
    consolePort.println(config.max_connection);

    consolePort.print(F("beacon_interval: "));
    consolePort.print(config.beacon_interval);
    consolePort.println("ms");

    consolePort.println(F("--------------------"));
    consolePort.println();
}


void print_wifi_general(Stream &consolePort)
{
    consolePort.print(F("wifi_get_channel(): "));
    consolePort.println(wifi_get_channel());
    
    consolePort.print(F("wifi_get_phy_mode(): "));
    consolePort.println(PHY_MODE_NAMES[wifi_get_phy_mode()]);
}


void secure_softap_config(softap_config *config, const char *ssid, const char *password)
{ // hidden mode
    // it tests if new data size over the storage and truncates.
    size_t ssidLen     = strlen(ssid)     < sizeof(config->ssid)     ? strlen(ssid)     : sizeof(config->ssid);
    size_t passwordLen = strlen(password) < sizeof(config->password) ? strlen(password) : sizeof(config->password);

    memset(config->ssid, 0, sizeof(config->ssid));
    memcpy(config->ssid, ssid, ssidLen);

    memset(config->password, 0, sizeof(config->password));
    memcpy(config->password, password, passwordLen);

    config->ssid_len = ssidLen;
    config->channel  = 1;
    config->authmode = AUTH_WPA2_PSK;
    config->ssid_hidden = 1;
    config->max_connection = 4;
    config->beacon_interval = 1000;
}


void enter_ap_mode(){
    WiFi.softAP(AP_MODE_MYSSID, AP_MODE_MYPW);
    IPAddress myIP = WiFi.softAPIP();

    Serial.println("");
    Serial.println("[-= Enter AP Mode =-]");
    Serial.print("AP IP ADDR: ");
    Serial.println(myIP);

    // get the default config and print it.
    softap_config config;
    wifi_softap_get_config(&config);
    /// if want to be more secure(hidden), modify the configurations.
    /// secure_softap_config(&config, AP_MODE_MYSSID, AP_MODE_MYPW);
    /// after config is arranged, set it in.
    /// wifi_softap_set_config(&config);
    print_softap_config(Serial, config);

    print_wifi_general(Serial);

    // start dhcps
    wifi_softap_dhcps_start();
    if (wifi_softap_dhcps_status()==DHCP_STARTED) Serial.println(F("DHCP started"));
    else Serial.println(F("DHCP stopped"));

    // get the AP basic info and print it.
    ip_info info;
    wifi_get_ip_info(SOFTAP_IF, &info);

    /// method 1
    /// Serial.printf("AP IP ADDR: %3d.%3d.%3d.%3d\r\n", ip4_addr1(&info.ip), ip4_addr2(&info.ip), ip4_addr3(&info.ip), ip4_addr4(&info.ip));
    /// Serial.printf("AP GATEWAY: %3d.%3d.%3d.%3d\r\n", ip4_addr1(&info.gw), ip4_addr2(&info.gw), ip4_addr3(&info.gw), ip4_addr4(&info.gw));
    /// Serial.printf("AP NETMASK: %3d.%3d.%3d.%3d\r\n", ip4_addr1(&info.netmask), ip4_addr2(&info.netmask), ip4_addr3(&info.netmask), ip4_addr4(&info.netmask));

    // method2
    Serial.printf("AP IP ADDR: " IPSTR "\r\n", IP2STR(&info.ip));
    Serial.printf("AP GATEWAY: " IPSTR "\r\n", IP2STR(&info.gw));
    Serial.printf("AP NETMASK: " IPSTR "\r\n", IP2STR(&info.netmask));

    // This doesn't work on an ESP-01.
    // wifi_set_sleep_type(LIGHT_SLEEP_T);
    // Try this dirty little thing.
    // Doesn't work because ESP-01 module doesn't link XPD_DCDC -> RST.
    // ESP.deepSleep(15000);
}


int connected_station_status(){

    ///// this function for count couldn't sync the number of stat_infos retrieved at realtime.
    unsigned char number_client=wifi_softap_get_station_num(); // Count of stations

    struct station_info *stat_info;
    IPAddress address;
    int i;

    Serial.print("Total connected clients: ");
    Serial.println(number_client);

    for (i=0, stat_info=wifi_softap_get_station_info(); stat_info!=NULL && i<number_client; i++){
        address=(stat_info->ip).addr;

        snprintf(remote_ip, 16, "" IPSTR, IP2STR(&(stat_info->ip)));

        Serial.printf("Station %d. ", i+1);
        Serial.print("IP= ");
        Serial.print(address);
        Serial.print(" with MAC= ");
        Serial.print(stat_info->bssid[0],HEX);
        Serial.print(stat_info->bssid[1],HEX);
        Serial.print(stat_info->bssid[2],HEX);
        Serial.print(stat_info->bssid[3],HEX);
        Serial.print(stat_info->bssid[4],HEX);
        Serial.print(stat_info->bssid[5],HEX);
        Serial.println();

        stat_info = STAILQ_NEXT(stat_info, next);
    }
    wifi_softap_free_station_info();
    delay(100);
    return i; // not the number_client
}


#if defined(USE_STA_NATIVE) // --------------------------
// 12 seconds waiting
bool enter_station_mode(){
    unsigned char u=0;
    
    // Connect to WiFi network
    Serial.println("");
    Serial.println("[-= Enter Station Mode =-]");
    Serial.print("Connecting to ");
    Serial.print(STA_MODE_SSID);

    WiFi.mode(WIFI_STA);
    WiFi.begin(STA_MODE_SSID, STA_MODE_PW);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(200);
        Serial.print(".");
        if (u++>60) break; // 12 seconds, might not enough
    }
    Serial.println("");
    if (WiFi.status() != WL_CONNECTED){
        Serial.println(" WiFi connection failed...");
        return false;
    }
    Serial.println("WiFi connected");

    // Print the remote MAC address
    uint8_t macAddr[6];
    WiFi.macAddress(macAddr);
    Serial.printf("Remote MAC adr: %02X:%02X:%02X:%02X:%02X:%02X\r\n", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    
    ip_info info;
    wifi_get_ip_info(STATION_IF, &info);
    Serial.printf("IP ADDR: " IPSTR "\r\n", IP2STR(&info.ip));
    Serial.printf("GATEWAY: " IPSTR "\r\n", IP2STR(&info.gw));
    Serial.printf("NETMASK: " IPSTR "\r\n", IP2STR(&info.netmask));

    // resolve the remote ip
    uint32 x=*(uint32*)(&info.ip)&*(uint32*)(&info.netmask);
    ip_addr y=*(ip_addr*)(&x);
    snprintf(remote_ip, 16, "%d.%d.%d.%d",\
        ip4_addr1(&y)? ip4_addr1(&y): 1,\
        ip4_addr2(&y)? ip4_addr2(&y): 1,\
        ip4_addr3(&y)? ip4_addr3(&y): 1,\
        ip4_addr4(&y)? ip4_addr4(&y): 1
        );
    Serial.print(F("Remote AP IP: "));
    Serial.println(remote_ip);

    Serial.printf("Default local hostname: %s\r\n", WiFi.hostname().c_str());
    Serial.print("DNS #1, #2 IP: ");
    WiFi.dnsIP().printTo(Serial);
    Serial.print(", ");
    WiFi.dnsIP(1).printTo(Serial);
    Serial.println();
    Serial.printf("RSSI: %d dBm\r\n", WiFi.RSSI());
    WiFi.printDiag(Serial);
    
    // Print the local IP address
    Serial.println("");
    Serial.print("IP is assigned: ");
    Serial.println(WiFi.localIP());
    return true;
}
#elif defined(USE_STA_WIFI_MULTI) // --------------------------
// 12 seconds waiting
bool enter_station_mode(){
    unsigned char u=0;

    // Connect to WiFi network
    Serial.println("");
    Serial.println("[-= Enter Station Mode =-]");
    Serial.print("WiFi Connecting...");

    WiFi.mode(WIFI_STA);
    if (!WiFiMulti){
        WiFiMulti=new ESP8266WiFiMulti;
        if (!WiFiMulti) return false;
        for (int i=0; i<(sizeof(WIFI_CRED)/sizeof(WIFI_CRED[0])); i++)
            WiFiMulti->addAP(WIFI_CRED[i][0], WIFI_CRED[i][1]);
    }

    while (WiFiMulti->run() != WL_CONNECTED) {
        delay(200);
        Serial.print(".");
        if (u++>60){ // 12 seconds, might not enough
            Serial.println(" WiFi connection failed...");
            return false;
        }
    }
    Serial.println("WiFi connected");

    // Print the remote MAC address
    uint8_t macAddr[6];
    WiFi.macAddress(macAddr);
    Serial.printf("Remote MAC adr: %02X:%02X:%02X:%02X:%02X:%02X\r\n", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
    
    ip_info info;
    wifi_get_ip_info(STATION_IF, &info);
    Serial.printf("IP ADDR: " IPSTR "\r\n", IP2STR(&info.ip));
    Serial.printf("GATEWAY: " IPSTR "\r\n", IP2STR(&info.gw));
    Serial.printf("NETMASK: " IPSTR "\r\n", IP2STR(&info.netmask));

    // resolve the remote ip
    uint32 x=*(uint32*)(&info.ip)&*(uint32*)(&info.netmask);
    ip_addr y=*(ip_addr*)(&x);
    snprintf(remote_ip, 16, "%d.%d.%d.%d",\
        ip4_addr1(&y)? ip4_addr1(&y): 1,\
        ip4_addr2(&y)? ip4_addr2(&y): 1,\
        ip4_addr3(&y)? ip4_addr3(&y): 1,\
        ip4_addr4(&y)? ip4_addr4(&y): 1
        );
    Serial.print(F("Remote AP IP: "));
    Serial.println(remote_ip);

    Serial.printf("Default local hostname: %s\r\n", WiFi.hostname().c_str());
    Serial.print("DNS #1, #2 IP: ");
    WiFi.dnsIP().printTo(Serial);
    Serial.print(", ");
    WiFi.dnsIP(1).printTo(Serial);
    Serial.println();
    Serial.printf("RSSI: %d dBm\r\n", WiFi.RSSI());
    WiFi.printDiag(Serial);
    
    // Print the local IP address
    Serial.println("");
    Serial.print("IP is assigned: ");
    Serial.println(WiFi.localIP());
    return true;
}
#endif // USE_STA_x // --------------------------


// The certificate is stored in PMEM
static const uint8_t x509[] PROGMEM = {
  0x30, 0x82, 0x01, 0x3d, 0x30, 0x81, 0xe8, 0x02, 0x09, 0x00, 0xfe, 0x56,
  0x46, 0xf2, 0x78, 0xc6, 0x51, 0x17, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x26, 0x31,
  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, 0x45, 0x53,
  0x50, 0x38, 0x32, 0x36, 0x36, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
  0x04, 0x03, 0x0c, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e,
  0x31, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x33, 0x31, 0x38, 0x31,
  0x34, 0x34, 0x39, 0x31, 0x38, 0x5a, 0x17, 0x0d, 0x33, 0x30, 0x31, 0x31,
  0x32, 0x35, 0x31, 0x34, 0x34, 0x39, 0x31, 0x38, 0x5a, 0x30, 0x26, 0x31,
  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, 0x45, 0x53,
  0x50, 0x38, 0x32, 0x36, 0x36, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55,
  0x04, 0x03, 0x0c, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e,
  0x31, 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02,
  0x41, 0x00, 0xc6, 0x72, 0x6c, 0x12, 0xe1, 0x20, 0x4d, 0x10, 0x0c, 0xf7,
  0x3a, 0x2a, 0x5a, 0x49, 0xe2, 0x2d, 0xc9, 0x7a, 0x63, 0x1d, 0xef, 0xc6,
  0xbb, 0xa3, 0xd6, 0x6f, 0x59, 0xcb, 0xd5, 0xf6, 0xbe, 0x34, 0x83, 0x33,
  0x50, 0x80, 0xec, 0x49, 0x63, 0xbf, 0xee, 0x59, 0x94, 0x67, 0x8b, 0x8d,
  0x81, 0x85, 0x23, 0x24, 0x06, 0x52, 0x76, 0x55, 0x9d, 0x18, 0x09, 0xb3,
  0x3c, 0x10, 0x40, 0x05, 0x01, 0xf3, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30,
  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
  0x05, 0x00, 0x03, 0x41, 0x00, 0x69, 0xdc, 0x6c, 0x9b, 0xa7, 0x62, 0x57,
  0x7e, 0x03, 0x01, 0x45, 0xad, 0x9a, 0x83, 0x90, 0x3a, 0xe7, 0xdf, 0xe8,
  0x8f, 0x46, 0x00, 0xd3, 0x5f, 0x2b, 0x0a, 0xde, 0x92, 0x1b, 0xc5, 0x04,
  0xc5, 0xc0, 0x76, 0xf4, 0xf6, 0x08, 0x36, 0x97, 0x27, 0x82, 0xf1, 0x60,
  0x76, 0xc2, 0xcd, 0x67, 0x6c, 0x4b, 0x6c, 0xca, 0xfd, 0x97, 0xfd, 0x33,
  0x9e, 0x12, 0x67, 0x6b, 0x98, 0x7e, 0xd5, 0x80, 0x8f
};

// And so is the key.  These could also be in DRAM
static const uint8_t rsakey[] PROGMEM = {
  0x30, 0x82, 0x01, 0x3a, 0x02, 0x01, 0x00, 0x02, 0x41, 0x00, 0xc6, 0x72,
  0x6c, 0x12, 0xe1, 0x20, 0x4d, 0x10, 0x0c, 0xf7, 0x3a, 0x2a, 0x5a, 0x49,
  0xe2, 0x2d, 0xc9, 0x7a, 0x63, 0x1d, 0xef, 0xc6, 0xbb, 0xa3, 0xd6, 0x6f,
  0x59, 0xcb, 0xd5, 0xf6, 0xbe, 0x34, 0x83, 0x33, 0x50, 0x80, 0xec, 0x49,
  0x63, 0xbf, 0xee, 0x59, 0x94, 0x67, 0x8b, 0x8d, 0x81, 0x85, 0x23, 0x24,
  0x06, 0x52, 0x76, 0x55, 0x9d, 0x18, 0x09, 0xb3, 0x3c, 0x10, 0x40, 0x05,
  0x01, 0xf3, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x40, 0x35, 0x0b, 0x74,
  0xd3, 0xff, 0x15, 0x51, 0x44, 0x0f, 0x13, 0x2e, 0x9b, 0x0f, 0x93, 0x5c,
  0x3f, 0xfc, 0xf1, 0x17, 0xf9, 0x72, 0x94, 0x5e, 0xa7, 0xc6, 0xb3, 0xf0,
  0xfe, 0xc9, 0x6c, 0xb1, 0x1e, 0x83, 0xb3, 0xc6, 0x45, 0x3a, 0x25, 0x60,
  0x7c, 0x3d, 0x92, 0x7d, 0x53, 0xec, 0x49, 0x8d, 0xb5, 0x45, 0x10, 0x99,
  0x9b, 0xc6, 0x22, 0x3a, 0x68, 0xc7, 0x13, 0x4e, 0xb6, 0x04, 0x61, 0x21,
  0x01, 0x02, 0x21, 0x00, 0xea, 0x8c, 0x21, 0xd4, 0x7f, 0x3f, 0xb6, 0x91,
  0xfa, 0xf8, 0xb9, 0x2d, 0xcb, 0x36, 0x36, 0x02, 0x5f, 0xf0, 0x0c, 0x6e,
  0x87, 0xaa, 0x5c, 0x14, 0xf6, 0x56, 0x8e, 0x12, 0x92, 0x25, 0xde, 0xb3,
  0x02, 0x21, 0x00, 0xd8, 0x99, 0x01, 0xf1, 0x04, 0x0b, 0x98, 0xa3, 0x71,
  0x56, 0x1d, 0xea, 0x6f, 0x45, 0xd1, 0x36, 0x70, 0x76, 0x8b, 0xab, 0x69,
  0x30, 0x58, 0x9c, 0xe0, 0x45, 0x97, 0xe7, 0xb6, 0xb5, 0xef, 0xc1, 0x02,
  0x21, 0x00, 0xa2, 0x01, 0x06, 0xc0, 0xf2, 0xdf, 0xbc, 0x28, 0x1a, 0xb4,
  0xbf, 0x9b, 0x5c, 0xd8, 0x65, 0xf7, 0xbf, 0xf2, 0x5b, 0x73, 0xe0, 0xeb,
  0x0f, 0xcd, 0x3e, 0xd5, 0x4c, 0x2e, 0x91, 0x99, 0xec, 0xb7, 0x02, 0x20,
  0x4b, 0x9d, 0x46, 0xd7, 0x3c, 0x01, 0x4c, 0x5d, 0x2a, 0xb0, 0xd4, 0xaa,
  0xc6, 0x03, 0xca, 0xa0, 0xc5, 0xac, 0x2c, 0xe0, 0x3f, 0x4d, 0x98, 0x71,
  0xd3, 0xbd, 0x97, 0xe5, 0x55, 0x9c, 0xb8, 0x41, 0x02, 0x20, 0x02, 0x42,
  0x9f, 0xd1, 0x06, 0x35, 0x3b, 0x42, 0xf5, 0x64, 0xaf, 0x6d, 0xbf, 0xcd,
  0x2c, 0x3a, 0xcd, 0x0a, 0x9a, 0x4d, 0x7c, 0xad, 0x29, 0xd6, 0x36, 0x57,
  0xd5, 0xdf, 0x34, 0xeb, 0x26, 0x03
};


#if 0
WiFiServer* create_http_server(){
    // Create an instance of the server
    // specify the 80 port to listen on as an argument
    WiFiServer *a=new WiFiServer(80);

    /// WiFiServerSecure *a=new WiFiServerSecure(443);
    /// a->setServerKeyAndCert_P(rsakey, sizeof(rsakey), x509, sizeof(x509));
    /// a->begin();
    
    // Start the server
    a->begin();
    Serial.println("HTTP Server started");
    return a;
}
#endif


ESP8266WebServer* create_http_server(){ // ESP8266WebServer & MDNS & Updater facilities
    // Create an instance of the server
    // specify the 80 port to listen on as an argument
    ESP8266WebServer *a=new ESP8266WebServer(80);

    MDNS.begin(HOSTNAME);

    httpUpdater.setup(a); // will be http://HOSTNAME.local/update

    // Start the http server
    a->begin();

    MDNS.addService("http", "tcp", 80);

    Serial.println("HTTP Server started");
    return a;
}


#if defined(USE_HTTP_HTTP_CLIENT) // faster but always got nothing // --------------------------
void http_client_connect(const char *url){
    /// #include <ESP8266HTTPClient.h>
    HTTPClient http;
    
    Serial.print("\r\n[HTTP begin...]\r\n");
    
    // configure traged server and url
    /// http.begin("https://192.168.1.12/test.html", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); // HTTPS
    http.begin(url); // HTTP
    Serial.print("[HTTP GET...]\r\n");
    // start connection and send HTTP header
    int httpCode = http.GET();

    // httpCode will be negative on error
    if (httpCode > 0){
        // HTTP header has been send and Server response header has been handled
        Serial.printf("[HTTP GET... code: %d]\r\n", httpCode);
        
        // file found at server
        if (httpCode == HTTP_CODE_OK){ // the connection may be broken and stuck here
            Serial.println(" -= data =- ---------");
            Serial.println(http.getString()); // usually has no data return, so failed.
            Serial.println(" ===================");
        }
        else Serial.printf("[HTTP GET... failed, error: %s]\r\n", http.errorToString(httpCode).c_str());
    }
    else Serial.printf("[HTTP Protocol failed, error: %s]\r\n", http.errorToString(httpCode).c_str());
    
    http.end();
    delay(150);
}
#elif defined(USE_HTTP_WIFI_CLIENT) // slower but usually failed connection // --------------------------
void http_client_connect(const char *url){
    // Use WiFiClient class to create TCP connections
    WiFiClient client;
    int httpPort = 80;
    if (!client.connect(remote_ip, httpPort)) {
        Serial.println("");
        Serial.println("[HTTP Protocol failed, error: 0]");
        Serial.println("");
        return;
    }

    Serial.print("[Requesting URL: [");
    Serial.print(url);
    Serial.println("]");

    // This will send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +\
        "Host: " + remote_ip + "\r\n" +\
        "Connection: close\r\n");

    unsigned long timeout = millis();
    while (client.available() == 0) {
        if (millis() - timeout > 4000) {
            Serial.println("[Client Timeout !]");
            client.stop();
            return;
        }
        delay(50);
    }

    // Read all the lines of the reply from server and print them to Serial
    Serial.println(" -= data =- ---------");
    while(client.available()){
        String line = client.readStringUntil('\r');
        Serial.println(line);
    }
    Serial.println(" ===================");
    delay(50);
}
#elif defined(USE_HTTP_WIFIHTTP_HYBRID) // --------------------------
void http_client_connect(const char *url){
    WiFiClient client;
    HTTPClient http; //must be declared after WiFiClient for correct destruction order, because used by http.begin(client,...)

    Serial.print("[HTTP begin...]\r\n");

    // configure server and url
    http.begin(client, url);
    /// http.begin(client, remote_ip, 80, url);

    Serial.print("[HTTP GET...]\r\n");
    // start connection and send HTTP header
    int httpCode = http.GET();
    if (httpCode > 0) {
        // HTTP header has been send and Server response header has been handled
        Serial.printf("[HTTP GET... code: %d]\r\n", httpCode);

        // file found at server
        if (httpCode == HTTP_CODE_OK) {
    
            // get lenght of document (is -1 when Server sends no Content-Length header)
            int len = http.getSize();
            
            // create buffer for read
            uint8_t buff[128] = { 0 };

#if 0 // with API
            Serial.println(http.getString());
#else // or "by hand"

            // get tcp stream
            WiFiClient * stream = &client;
            
            Serial.println(" -= data =- ---------");
            // read all data from server
            while (http.connected() && (len > 0 || len == -1)) {
                // read up to 128 byte
                int c = stream->readBytes(buff, std::min((size_t)len, sizeof(buff)));
                Serial.printf("[readBytes: %d]\r\n", c);
                if (!c) {
                    Serial.println("[read timeout]");
                    break; // must have or it will stuck here! and then if stuck at GET(), set timeout
                }

                // write it to Serial
                Serial.write(buff, c);

                if (len > 0) len -= c;
            }
            Serial.println(" ===================");
        }
#endif

        Serial.println();
        Serial.print("[HTTP connection closed or file end.]\r\n");
    }
    else Serial.printf("[HTTP GET... failed, error: %s]\r\n", http.errorToString(httpCode).c_str());
    http.end();
}
#endif // USE_HTTP_x // --------------------------


void gbl_update_started() {
  Serial.println("CALLBACK:  HTTP update process started");
}


void gbl_update_finished() {
  Serial.println("CALLBACK:  HTTP update process finished");
}


void gbl_update_progress(int cur, int total) {
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
}


void gbl_update_error(int err) {
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}


void gbl_updater(const char *url) {
    // wait for WiFi connection

    WiFiClient client;

    // The line below is optional. It can be used to blink the LED on the board during flashing
    // The LED will be on during download of one buffer of data from the network. The LED will
    // be off during writing that buffer to flash
    // On a good connection the LED should flash regularly. On a bad connection the LED will be
    // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second
    // value is used to put the LED on. If the LED is on with HIGH, that value should be passed
    ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW);

    // Add optional callback notifiers
    ESPhttpUpdate.onStart(gbl_update_started);
    ESPhttpUpdate.onEnd(gbl_update_finished);
    ESPhttpUpdate.onProgress(gbl_update_progress);
    ESPhttpUpdate.onError(gbl_update_error);

    t_httpUpdate_return ret = ESPhttpUpdate.update(client, url);
    // Or:
    // t_httpUpdate_return ret = ESPhttpUpdate.update(client, "server", 80, "file.bin");

    switch (ret) {
        case HTTP_UPDATE_FAILED:
            Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\r\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
            break;

        case HTTP_UPDATE_NO_UPDATES:
            Serial.println("HTTP_UPDATE_NO_UPDATES");
            break;

        case HTTP_UPDATE_OK:
            Serial.println("HTTP_UPDATE_OK");
            break;
    }
}


bool Timeout_60s(){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + 60000)) return false;
    current_time = new_time;
    return true;
}


bool Timeout_10s(){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + 10000)) return false;
    current_time = new_time;
    return true;
}


bool Timeout_4s(){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + 4000)) return false;
    current_time = new_time;
    return true;
}


void page_gpio_low_cb(){
    // Prepare the response
    String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nLED is now ";
    s += "ON";
    s += "</html>\r\n";
    // Send the response to the client
    server_p->send(200, "text/plain", s.c_str());

    digitalWrite(2, LOW);
}


void page_gpio_high_cb(){
    // Prepare the response
    String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nLED is now ";
    s += "OFF";
    s += "</html>\r\n";
    // Send the response to the client
    server_p->send(200, "text/plain", s.c_str());

    digitalWrite(2, HIGH);
}


void page_led_onoff_inst(){
    /// http://<ip address>/led?state=on will turn the led ON
    /// http://<ip address>/led?state=off will turn the led OFF
    server_p->on("/led", []() {
        String state=server_p->arg("state");
        if (state == "on") digitalWrite(2, LOW);
        else if (state == "off") digitalWrite(2, HIGH);
        server_p->send(200, "text/plain", "Led is now " + state);
    });
}


void page_a_bin_cb(){
    SPIFFS.begin();
    File file = SPIFFS.open((String("/")+UPDFILE).c_str(), "r");
    if (!file) Serial.println((String("/")+UPDFILE+" opened failed").c_str());
    server_p->streamFile(file, "application/octet-stream");
    file.close();
}


void setup_updater(){
    pre_setup();
    post_setup();

#if AP_MODE
    enter_ap_mode();
    wifi_set_event_handler_cb(wifi_event_handler_cb);
#endif


#if HTTP_SERVER

    pinMode(2, OUTPUT); // D4
    server_p=create_http_server();

    server_p->on("/gpio/0", page_gpio_low_cb);
    server_p->on("/gpio/1", page_gpio_high_cb);
    server_p->on("/", [](){server_p->send(200, "text/plain", "This is an index page.");});
    page_led_onoff_inst();
    server_p->on("/upd", page_a_bin_cb);

#endif
}


void loop_updater() {

    if (Timeout_60s()){
        Serial.printf("\r\n\r\n\r\n\r\nUpdating...\r\n\r\n\r\n\r\n");
        gbl_updater((String("http://")+HOSTNAME+".local"+"/upd").c_str());
    }

#if AP_MODE

    static int has_conns=0;
    if (Timeout_10s())
        Serial.printf("Current STA connections: %d\r\n", (has_conns=connected_station_status()));
    if (has_conns<=0) return;

#elif STA_MODE

    if (!is_station_entered){
        if (Timeout_4s()){
            is_station_entered=enter_station_mode();
        }
        return;
    }

#endif


#if HTTP_SERVER

    server_p->handleClient();
    MDNS.update();
    delay(200);

#elif HTTP_CLIENT

    static int x=0;

    web_link="http://";
    web_link+=remote_ip;
    web_link+="/gpio/";

    http_client_connect((web_link+(x^=1)).c_str());
    delay(100);

#endif
}




#include"Esp8266MultiPwms.h"
// use MultiPwms Lib v.0.8. gpio parking is changed to GPIO3.
// use "ESP-12S"
// 2021.01.10.

// need to call Start() again if Stop()
// during power-up/reset/reset-after-update has a 6ms all-high of the four pwms. so better to external pull-down.
// adc is used. so remember not to ESP.getVcc() which would damage power-rail.

#define MDELAY delay(50)

typedef enum{
    mstart=0,
    mforward=1,
    mreward=2,
    mforward_pause=3,
    mreward_pause=4,
    mstop=5,
} eTheState;

eTheState the_state;
int the_dc;

cMultiPwms M1(13, 5000, 100, 0, 0);
cMultiPwms M2(12, 5000, 100, 0, 0);
cMultiPwms M3(14, 5000, 100, 0, 0);
cMultiPwms M4(4, 5000, 100, 0, 0);
cMultiPwms LEDB(2, 25000, 500, 1, 1);

void setup_cmultipwms() {
    //ADC_MODE(ADC_TOUT);
    pinMode(3, INPUT); // by the RX0. set it as the parking pin.
    pinMode(13, OUTPUT); digitalWrite(13, LOW); // since wrong level is harmful
    pinMode(12, OUTPUT); digitalWrite(12, LOW); // since wrong level is harmful
    pinMode(14, OUTPUT); digitalWrite(14, LOW); // since wrong level is harmful
    pinMode(4, OUTPUT); digitalWrite(4, LOW); // since wrong level is harmful

    pinMode(2, OUTPUT); // led
    LEDB.setDC(0);
    LEDB.Resume();

    pinMode(16, OUTPUT); digitalWrite(16, HIGH); // OE for level shifter.
    Start();
}

void SetDC_all_temp(unsigned a){
    M1.setDC(a);
    M2.setDC(a);
    M3.setDC(a);
    M4.setDC(a);
}

void SetDC_all(unsigned a){
    SetDC_all_temp(a);
    the_dc=a;
}

void SetDC_temp(class cMultiPwms &m, unsigned a){ // why need to use "class"
    m.setDC(a);
}

void SetDC(cMultiPwms &m, unsigned a){
    SetDC_temp(m, a);
    the_dc=a;
}

void Start(){
    switch (the_state){
        case mstart:
        case mstop:
            the_state=mstart;
            SetDC_all(0); // this is must
            cMultiPwms::SyncStart(M1);
            cMultiPwms::SyncNext(M2, 0);
            cMultiPwms::SyncNext(M3, 0);
            cMultiPwms::SyncNext(M4, 0);
            cMultiPwms::SyncEnd(M1);
            SetDC_all(0);
            M1.Pause();
            M2.Pause();
            M3.Pause();
            M4.Pause();
            break;
    }
}

void Stop(){
    switch (the_state){
        case mforward:
            M4.Stop();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            M1.Stop();
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            MDELAY;
            M2.Stop();
            MDELAY;
            break;
        case mreward:
            M2.Stop();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            M3.Stop();
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            MDELAY;
            M4.Stop();
            MDELAY;
            break;
        case mforward_pause:
            M1.Stop();
            MDELAY;
            M2.Stop();
            MDELAY;
            break;
        case mreward_pause:
            M3.Stop();
            MDELAY;
            M4.Stop();
            MDELAY;
            break;
        default: return;
    }
    SetDC_all(0);
    the_state=mstop;
}

void Pause(){
    switch (the_state){
        case mforward:
            the_state=mforward_pause;
            M4.Pause();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            break;
        case mreward:
            the_state=mreward_pause;
            M2.Pause();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            break;
    }
}

void Resume(){
    switch (the_state){
        case mforward_pause:
            the_state=mforward;
            M2.Pause();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward_pause:
            the_state=mreward;
            M4.Pause();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
    }
}

void Forward(){
    switch (the_state){
        case mstart:
            M1.Resume();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward:
            M2.Pause();
            MDELAY;
            SetDC_temp(M4, 100);
            SetDC_temp(M3, 100);
            MDELAY;
            M4.Resume();
            MDELAY;
            M3.Pause();
            SetDC(M4, the_dc);
            SetDC(M3, the_dc);
            MDELAY;
            M1.Resume();
            MDELAY;
            break;
        case mforward_pause:
            M2.Pause();
            MDELAY;
            M4.Resume();
            MDELAY;
            break;
        case mreward_pause:
            M3.Pause();
            MDELAY;
            M1.Resume();
            MDELAY;
            break;
        default: return;
    }
    the_state=mforward;
}

void Reward(){
    switch (the_state){
        case mstart:
            M3.Resume();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
        case mforward:
            M4.Pause();
            MDELAY;
            SetDC_temp(M2, 100);
            SetDC_temp(M1, 100);
            MDELAY;
            M2.Resume();
            MDELAY;
            M1.Pause();
            SetDC(M2, the_dc);
            SetDC(M1, the_dc);
            MDELAY;
            M3.Resume();
            MDELAY;
            break;
        case mforward_pause:
            M1.Pause();
            MDELAY;
            M3.Resume();
            MDELAY;
            break;
        case mreward_pause:
            M4.Pause();
            MDELAY;
            M2.Resume();
            MDELAY;
            break;
        default: return;
    }
    the_state=mreward;
}

void PrintState(){
    switch (the_state){
        case mstart:
            printf("\r\nStart\r\n");
            break;
        case mforward:
            printf("\r\nForward\r\n");
            break;
        case mreward:
            printf("\r\nReward\r\n");
            break;
        case mforward_pause: case mreward_pause:
            printf("\r\nPause\r\n");
            break;
        case mstop:
            printf("\r\nStop\r\n");
            break;
    }
}


void loop_cmultipwms(){
    static unsigned tmop=micros();
    if (cMultiPwms::Timeout(1500, tmop)) cMultiPwms::traverseLinkedList();

    static unsigned tmoq=micros();
    if (cMultiPwms::Timeout(125, tmoq)){
        static unsigned v=0;
        SetDC_all(v);
        if ((v+=3)>99) v=1;
    }

    static unsigned tmor=micros();
    if (cMultiPwms::Timeout(4000, tmor)){
        switch (random(65535)%20){
            case 0: case 1: case 2: case 3:
                Forward();
                break;
            case 4: case 5: case 6: case 7:
                Reward();
                break;
            case 8: case 9: case 10: case 11: case 12:
                Pause();
                break;
            case 13:
                Resume();
                break;
            case 14:
                Stop();
                break;
            case 15: case 16: case 17: case 18: case 19:
                Start();
                break;
        }
        PrintState();
        printf("[%fV]\r\n", float(analogRead(A0))/1024.0f*15.0f);
    }

    static unsigned tmos=micros();
    if (cMultiPwms::Timeout(100, tmos)){
        static unsigned cntt=0;
        cntt++;
        switch (the_state){
            case mstart:
                LEDB.detachGpio();
                digitalWrite(2, LOW);
                break;
            case mforward:
                LEDB.detachGpio();
                if (cntt&0x4) digitalWrite(2, LOW);
                else digitalWrite(2, HIGH);
                break;
            case mreward:
                LEDB.detachGpio();
                if (cntt&0x1) digitalWrite(2, LOW);
                else digitalWrite(2, HIGH);
                break;
            case mforward_pause:
                LEDB.setGpio(2);
                {unsigned light1=cntt%7+1;
                light1=light1*light1*light1;
                LEDB.setDC(0, light1);}
                break;
            case mreward_pause:
                LEDB.setGpio(2);
                {unsigned light2=cntt%7+1;
                light2=light2*light2*light2;
                LEDB.setDC(0, 500/light2);}
                break;
            case mstop:
                LEDB.detachGpio();
                digitalWrite(2, HIGH);
                break;
        }
    }
}




void setup(){
    setup_cmultipwms();

    Serial.begin(115200);
    delay(5000);

    setup_updater();
}


void loop(){
    loop_updater();
    loop_cmultipwms();
}

20210201 主程式更新

20210202 再更新

  • 筆者另外使用了另一張馬達的驅動板,MX1508MX1616,成品如下圖,配接上更為簡捷。不過該驅動板的最大供電壓/驅動電壓是 10V,故當外部電壓大於 10V,尚需 DCDC 轉換。並附上主程式 drvm02_mx1616.zip。
  • 而新版的 multi-timer and multi-pwm 則近期在後面的文章再更新。
  • 20210314 補充
  • 關於這張 MX 系列的 DC 馬達驅動板:
  • 供電壓接近 10V 可能會燒掉,雖然例如 9.5V 仍能正常作動。但長時間運作將造成驅動訊號漂移,馬達運轉不順的狀況,很常出現。
  • 故此板僅適合例如小電壓小車驅動。
  • 故,本篇的對象又轉回一開頭的驅動板了。
  • 新開如下段落,如圖及程式碼。
  • 板子 S2-1,S2-2。

20210314 New Board S2-1/S2-2

S2-1,S2-2 正面
S2-1,S2-2 正面

S2-1/S2-2 程式碼 20210314

20210409 程式碼更新,for S2-1,S2-2

程式內容幾近涵蓋完全。只剩步進的角度還是差強人意,就這點需要修好。目前真的是堪用等級剛好及格。

20210414 程式碼更新,for S2-1,S2-2

20210601 程式碼更新 v.0.5,for S2-1,S2-2

20210604 程式碼更新,v0.6,for S2-1,S2-2

20210609 程式碼更新,v0.7,for S2-1,S2-2

此版本開始走向專用化,後續編版號將於其後加英文字母。

auto-tuning

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。

PHP Code Snippets Powered By : XYZScripts.com