ESP8266 跟時間賽跑

No Comments

這個標題是什麼意思?乃因這個立場及疑問。
因為為與 Arduino 相容,所以使與 Arduino 相容。給了我們我們給了預設立場。
疑問是,難道 esp8266 就剛好只是如此能耐?160 MHz 的核心速度與因之給我們的信心,不會讓我們就此滿足的。所以筆者便試著深入去了解。果不其然,這些問題早在 esp8266 arduino bsp 函式庫製作之初及接續便有人探討之。
至少於此,即本章要了解的,我們提出兩個問題,
arduino 提供的最小時間單位是 1 microsecond。如 delayMicroseconds(),及 micros()。esp8266 還能更小嗎?答案是預期的,只是該怎麼做?
GPIOs 在之前的量測,週期都在 1us 上下。真正的頻寬乃基於邏輯閘本身的響應速度且基於 cpu 的 peripheral bus 存取速度。即便閘響應速度飛快/實際也不可能太低而都是正妹等級,仍該受限於 peripheral bus 頻寬;同理 160MHz(6.25ns) 給了我們信心深入去了解。
事實上中斷響應速度(落差 640=4us/6.25ns)也該去深入了解,不過這個預設立場太強烈/wifi-keep-alive,所以至少不在本篇的求答範圍。又或是 wifi 關了,沒有理由有這落差表明,160MHz/NT$35 真的很好用!

參考資料

實驗一。認識 GPIOs

  • 實驗對象是 wemos d1 mini,esp8266ex。因其所有腳位都有拉出來。希望其他 ic 都一樣結果。
  • 首先我們來觀察一下所有 GPIOs 的行為。
  • 在程式開始跑後,所有 GPIOs 都是可控可預期的。
  • 因此,我們關注的是上電/reset 時,flash programming 時及其前後的 GPIOs 的狀態與行為。
  • 此需求是必要的因為,應用上若是有任兩支 gpios 被要求要互斥狀態,否則導致燒掉。則必得瞭解那些不可控的時期的狀態。
  • 以下以 flash programming 來作為例子。上電應是與之無異才是。
  • 所有 GPIOs 都抓出來了,一睹其全貌。
  • B,是 UART Rx 的寫入動作之起始點。
  • A,則是下達 flash programming 後,起始的狀態。
  • 在 A 之前,則是主程式正常運作下的 waveform,所有 gpios 都被 toggling。
  • 看一下圖一之三之 16,可以肯定程式跑完一輪迴後,即進入 flash programming 模式,因其在非預期的時間下被拉 high 了(*)。因此,
  • 關注一,進入 flash programming 模式,所有 gpios 的狀態如下:
  • 12, 13, 14, 16 被拉 high。
  • 4, 5 unchanged。
  • boot strap pins 0, 1(Tx), 2, 3(Rx), 15 於 flash programming 期間,0 會有類似 clock 的行為。1, 2 彼此有同樣的行為陪伴 3 作 programming。15 則一直處於 low 的狀態。
  • (是不是若 2 壞掉可用 1 替代?真正應該說,量産品可能只會拉出 3,故 boot strap pin 2 用以替代可能必要的 1)
  • 關注二,完成 flash programming 作 reset,或說上電,即標示 C 的位置起始:
  • 0, 1, 2, 3 拉 high。
  • 12, 13, 14, 16 拉 low。
  • 4, 5 unchanged。
  • 15 原本就是 pull-low,所以在該時間點有一支 high-low pulse and keep low,應就可代表 power on/reset 時間點。
  • 比較有趣的是,12 有一點小動作,並且 16 是在 15 pulse 之後才拉 low。如圖一之六。
  • reset 只是舉手之勞,也如前述(但注意,也有 reset 起始及 reset 後兩個時間點。且,應是需視按 reset button 為軟體行為,因為 gpio16 仍在該時間點被拉 high 如前述 *),所以如圖一之七。僅秀 0, 1, 2, 3, 4, 15。
  • 圖一之八,圖一之九,則是上電。
  • 以上,就是全貌了,再也不用怕 GPIOs 會搞怪了。
圖一之一
圖一之二
圖一之三
圖一之四
圖一之五
圖一之六
圖一之七
圖一之八
圖一之九

實驗二。撥雲見日

  • 首先,我們來看一下這段程式碼(Code.A)
  • waveform 如圖二之一
  • 圖二之一 的 waveform 的展開,如接續的三張圖。
// Code.A.
//
// gpio12, gpio13 toggling.
// a pulse when power on.
// after 10 seconds the power on, another tiny waveform.
// after another 10 seconds the tiny waveform, the continuous tiny waveform.

#define D6 12
#define D7 13

#define IO12 0x1000
#define IO13 0x2000

volatile unsigned &PIN_OUT_SET = *((unsigned*)0x60000304);
volatile unsigned &PIN_OUT_CLEAR = *((unsigned*)0x60000308);

#define digitalWriteHIGH(x) (PIN_OUT_SET|=(x)) // (WRONG!!!!!!)
#define digitalWriteLOW(x) (PIN_OUT_CLEAR|=(x)) // (WRONG!!!!!!)


void setup() {
    pinMode(D6, OUTPUT);
    pinMode(D7, OUTPUT);

    digitalWrite(D6, HIGH); digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
    digitalWrite(D7, HIGH); digitalWrite(D7, LOW); digitalWrite(D7, HIGH);

    delay(10000); // 10 seconds
}


void loop() {
    static unsigned x=1, y=micros();

    if (micros()>y+10000000 || x){ // another 10 seconds
        x=0;


        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);
        digitalWrite(D6, LOW); digitalWrite(D6, HIGH);

        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);
        digitalWrite(D7, LOW); digitalWrite(D7, HIGH);


        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);
        digitalWriteLOW(IO12); digitalWriteHIGH(IO12);

        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);
        digitalWriteLOW(IO13); digitalWriteHIGH(IO13);


    }
}
圖二之一
圖二之一之一
圖二之一之二
圖二之一之三
  • 我們花現,緊鄰的 digitalWrite,居然要花費 2ms,另外兩張圖也好不到哪去,它能多細已經不重要了,重要的是:
  • 按!果然便宜的沒好貨,按!
  • 筆者徹底對 esp8266 失望透了!!!
  • 這下子,如何做出能有固定週期,做出相當細的 bit-banging waveform 呢?
  • 相信各位看倌現在也花現了,也已跟筆者一樣同仇敵慨了吧!!!準備拒用 esp8266 了!
  • 唉,
  • 不過,等等,長久以來的摸索,
  • 致有一些想法能夠突然浮現,
  • 還是有幫助的。
  • 等等,
  • 筆者臨幸它也臨幸了兩年多了,
  • 現在才浮現。。。等等。。。這二字,
  • 所以堅持是有用的,經驗是有用的。
  • 一個字道破盲點,一個字解除封印(長久以來筆者都在 gpio toggle at the minimal time 1us 打轉),破了,它破了!
  • 就一個字,其他不必再多作解釋了:IRAM_ATTR。
  • 就這個字,底下是這三張 waveform 的新解。
圖二之一之解
圖二之一之一解
圖二之一之二解
圖二之一之三解
  • 我們發現,setup() 並沒有被影響到,這是可以試著推論的,因 flash to ram 是在 setup() 後做的。且,one-shot setup,毌需此。
  • 而解析度,細到 2.5MHz,哈!別懷疑,還能更細!讓我們一步一步來摸索。
  • 天啊我的 esp8266,強!!!錯怪它了!!!

實驗三。IoTs

  • 承繼實驗二的程式碼,它在上電二十秒後不斷重覆地跑 tiny waveform,我們目的是為了看,離開了 loop() 又再次進了 loop(),到底這中間花費了多少時間。因程式碼,誤差最多也不過幾百 ns 我們忽略之。
  • 量出來答案是 4.4us,我們抓個大概,那麼就是 4us。此 4us 筆者大膽稱為最小時距
  • 這段時距,勢必去跑 WiFi/wdt/maintenance 相關的扣(wifi 預設是開啟的)。因此我們首先在實驗二的程式碼加入三行扣,重點是,筆者不清楚這兩行到底影響了什麼而只能確定,這兩行都可能關掉 wifi。故兩行都加。
  • 加的點在,setup() 內,上電 10 秒後的 loop 內只跑一次,兩種。
#include <ESP8266WiFi.h>
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin(-1);
  • 結果,時距並沒有改變。換言之,我們可以大膽假設結論,
  • 不論 WiFi 是否打開或關閉,相鄰兩次 loop() 呼叫的中間,最少都須花費 4us 去跑 maintenance 的扣。
  • 因此接下來問題來了,預設打開,與手動將其關閉,這兩種前提下,是否能一直維持最小時距?
  • 很不幸地量測與觀察到,答案是否定的。
  • 不論 WiFi 是否打開或關閉,在沒有跑其他 IoT 相關的扣的前提下,此時距仍可高達 60us 以上。

實驗四。GPIOs 頻寬

  • 接下來來量測我們解除封印後的 gpio 頻寬。有分 arduino 提供的函式,即,我們常使用的那些;及直接存取 gpio 兩種方式。
  • 重點是,因筆者的 LA 取樣率不是很頂規,即,最高只有 80MHz,所以會有誤差之餘,量測下亦須交叉比對對應少通道及多通道的量測數據。有使用低端 LA 的使用者便懂所指。
  • 首先先記得,前例量測的結果,是於 80Msps 下 digitalWrite 450ns/475ns,直接設定是 200ns 此二數據。digitalWrite 到底是 450 還是 475,是無法斷定的。因為此取樣率的誤差恰等於 1000ns / (80 / 2) = 25ns。所以後者連續都取出 200ns,就能斷定必是 200ns,斥或小於 25ns(長時間取樣/最小公倍數來勘誤)。是故,經由量測,我們可以先下兩個結論,
  • 結論一。digitalWrite 在最優條件下(IRAM_ATTR,之後不再強調)保證有 2MHz 的反應速度。因此可製作 1MHz 的方波無虞。並且適用於所有 GPIOs,除了 GPIO16。所有 GPIOs:0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 16。
  • 結論二。承一,GPIO16,固定只有 900ns 的響應時間。亦即只能保證製造最高 500KHz waveform。
  • 接下來我們來看直接存取 gpio 的方式,下表將簡述。我們將逐一加上每一種用法的全貌。
IRAM_ATTR volatile unsigned &PIN_OUT = *((unsigned*)0x60000300);

// O1. 指定的值將同時影響所有 gpios。例如 0x2,則 gpio1 將為 high,其餘將為 low。


IRAM_ATTR volatile unsigned &PIN_OUT_SET = *((unsigned*)0x60000304);

// O2. 指定的值(後略),當中 bits 為 1s 者將對應的 gpios 設為 high,餘不影響而保持原有狀態。


IRAM_ATTR volatile unsigned &PIN_OUT_CLEAR = *((unsigned*)0x60000308);

// O3. 當中 bits 為 1s 者將對應的 gpios 設為 low,餘不影響而保持原有狀態。


///// 以上 PIN_OUT{},都是 output 的操作。
///// 以下 PIN_DIR{},都是 direction(input/output) 的操作,且意義上類比上述 output 操作。
///// PIN_OUT 和 PIN_DIR 都可以被讀取回來且正確代表其各 gpios 狀態。
///// 其餘 registers 讀取回來都是 0。


IRAM_ATTR volatile unsigned &PIN_DIR = *((unsigned*)0x6000030C);

// D1. 同時影響所有 gpios 的 directions。例如 0x2,則 gpio1 將為 output,其餘將為 input。


IRAM_ATTR volatile unsigned &PIN_DIR_OUTPUT = *((unsigned*)0x60000310);

// D2. 當中 bits 為 1s 者將對應的 gpios 設為 output,餘不影響而保持原有狀態。


IRAM_ATTR volatile unsigned &PIN_DIR_INPUT = *((unsigned*)0x60000314);

// D3. 當中 bits 為 1s 者將對應的 gpios 設為 input,餘不影響而保持原有狀態。


IRAM_ATTR volatile unsigned &PIN_IN = *((unsigned*)0x60000318);

// I1. 反映 gpios 的 levels 狀態且無關乎方向是 input or output。看來是讀取用,故可指定嗎?


IRAM_ATTR volatile unsigned &PIN_0 = *((unsigned*)0x60000328); // 未明。但 bit0 可設定該 pin level。其餘 bits 不可動之。
IRAM_ATTR volatile unsigned &PIN_2 = *((unsigned*)0x60000330); // 未明。但 bit0 可設定該 pin level。其餘 bits 不可動之。


#define IO00    0x00001
#define IO01    0x00002
#define IO02    0x00004
#define IO03    0x00008
#define IO04    0x00010
#define IO05    0x00020
#define IO12    0x01000
#define IO13    0x02000
#define IO14    0x04000
#define IO15    0x08000
#define IO16    0x10000

#define ALLGPIOS 0x1F03F // (WRONG!!!!!! should be 0x0F03F)

實驗四之一。PIN_OUT

  • 在連續操作 PIN_OUT 的前提下,即,不加 delay,觀察最快 toggle 時間。
  • 結論一。所有 GPIOs 除了 16,皆有最高 13.33MHz(75ns) 的反應速度,可製作 6.67MHz waveform。
  • 結論二。GPIO16 無作用。
  • 結論三。異常。5, 12, 13,這三支若各單獨設為 input,其餘設為 output,且所有 gpios 由 PIN_OUT toggling(無論有無 toggle 該 gpio),則 PIN_OUT 仍會影響之使之輸出波形;其他 gpios 並無此現象。但若全為 input,則 PIN_OUT toggling 不發生作用於任何 gpios。
  • 結論四。所有 gpios 設為 output,且由 PIN_OUT toggling,任一支 gpio 即便不設 toggle,也不受其他 gpio 影響。即,行為無異常。
  • 結論五。因此綜合三,四,只能說若有 gpios 設為 input,則可能會被 PIN_OUT 及其他 gpio 影響。
  • 結論六。若設為(硬體)功能腳位,例如 GPIO1 設為 Tx,則即便被 PIN_OUT toggling,輸出功能也不受影響。即 PIN_OUT 對它無作用。

實驗四之二。PIN_OUT_SET/PIN_OUT_CLEAR

  • 狀況與 PIN_OUT 全同。除了以下幾點,
  • 結論一。皆有最高 5MHz(200ns) 的反應速度,可製作 2.5MHz waveform。
  • 結論二。異常的現象較輕微,即例如不該有而有的完整的波形,於此只呈現部份的該不該有的完整波形。因此,或許是特定的板子,就是那幾支 gpios 易受 cross talk 影響。

實驗四之三。PIN_DIR/PIN_DIR_OUTPUT/PIN_DIR_INPUT

  • 結論一。若從 input 轉為 output,則 level 將呈現 PIN_OUT 所存的值。所以若 output 初狀態是 critical 的,則先設定 PIN_OUT(此時為 input),再轉成 output。
  • 結論二。PIN_OUT 所作用者 GPIOs,是同時作用同時響應。而 PIN_DIR 則同時作用,但是不見得會同時變化。因此我們可以就此點抓出表現較好與較差的腳位。
  • 結論三。PIN_OUT 作用於 GPIOa & GPIOb,PIN_DIR 僅作用於 GPIOa;觀察多次 PIN_OUT toggling 中夾雜著一次 PIN_DIR。則發現 GPIOb 中該次 toggle 會多延遲 75ns。因此 PIN_DIR 延遲時間為 75ns,並且此時間與 level 呈現的時間(幾乎完全/見結論二)重疊,即,PIN_DIR(75ns)=PIN_DIR(0ns)+PIN_OUT(75ns)。
  • 結論四。若讀取 register 時間,僅為 CPU 時間而不涉及 IO 時間,則我們可以將 PIN_DIR 或 PIN_OUT 讀取回來再決定是否轉態。因 CPU 時間 6.25ns < 75ns。但須例如抓 3 條指令解決。
  • 結論五。GPIO16 無作用。
  • 結論六。PIN_DIR_INPUT/PIN_DIR_OUTPUT/PIN_DIR 狀況應都相同。
  • 請注意所指的“同時”,是會有 25ns 的儀器誤差的。

實驗四之四。PIN_IN


// Code.B.
//
// the PIN_IN test.
// PIN_OUT at all gpios for toggling, however only output pins could response.
// GPIO 12/13/14/15 as the OUTPUTs output to GPIO 2/3/4/5 as the INPUTs respectively.
// read the gpio 2/3/4/5 by the PIN_IN function. also check the read whether as expected.

#define THE_CHECK 1 // check the read value.

#if 0//////// 1 to bypass, 0 to PIN_IN.

    #define THE_PIN_IN t
    #define CHECK(x)

#else////////

    #define THE_PIN_IN PIN_IN

    #if THE_CHECK////
        #define CHECK(x) if((PIN_IN&0xF03D)!=((x==HIGH)?0xF03D:0))while(1); // check gpios 0/2/3/4/5/12/13/14/15.
    #else////
        #define CHECK(x)
    #endif////

#endif////////


#include <ESP8266WiFi.h>


IRAM_ATTR volatile unsigned &PIN_OUT = *((unsigned*)0x60000300);
IRAM_ATTR volatile unsigned &PIN_IN = *((unsigned*)0x60000318);


// wemos d1 mini pinout.
#define D0      16
#define D1      5
#define D2      4
#define D3      0
#define D4      2
#define D5      14
#define D6      12
#define D7      13
#define D8      15
#define TX      1
#define RX      3

#define IO00    0x00001
#define IO01    0x00002
#define IO02    0x00004
#define IO03    0x00008
#define IO04    0x00010
#define IO05    0x00020
#define IO12    0x01000
#define IO13    0x02000
#define IO14    0x04000
#define IO15    0x08000
#define IO16    0x10000

#define ALLGPIOS 0x1F03F // (WRONG!!!!!! should be 0x0F03F)


void setup() {

    Serial.begin(115200);
    while (!Serial) delay(50);
    Serial.println("");

    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);


    pinMode(0, OUTPUT);
    // pinMode(1, OUTPUT); // tx used.

    pinMode(2, INPUT);
    pinMode(3, INPUT);
    pinMode(4, INPUT);
    pinMode(5, INPUT);

    pinMode(12, OUTPUT);
    pinMode(13, OUTPUT);
    pinMode(14, OUTPUT);
    pinMode(15, OUTPUT);

    pinMode(16, INPUT);

    delay(5000); // 5 seconds
}


IRAM_ATTR volatile unsigned the_z=0, t=0;


IRAM_ATTR void loop() {

    IRAM_ATTR static unsigned y9=micros();
    if (micros()>y9+5000000){ // every 5 seconds to print out
        y9=micros();
        Serial.println(the_z+1);
    }


    IRAM_ATTR static unsigned x=1, y=micros();
    if (micros()>y+5000000 || x){ // after 5 seconds to start waveform
        if (x){
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
        }
        x=0;


        t++;


        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        the_z=THE_PIN_IN;//+the_z;
        CHECK(LOW);

        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low
        PIN_OUT=ALLGPIOS; PIN_OUT=~ALLGPIOS; // high/low

        PIN_OUT=ALLGPIOS;
        the_z=THE_PIN_IN;//+the_z;
        CHECK(HIGH);
        PIN_OUT=~ALLGPIOS; // high/low

        PIN_OUT=ALLGPIOS;
        the_z=THE_PIN_IN;//+the_z;
        CHECK(HIGH);
        PIN_OUT=~ALLGPIOS; // high/low

        PIN_OUT=ALLGPIOS;
        the_z=THE_PIN_IN;//+the_z;
        CHECK(HIGH);
        PIN_OUT=~ALLGPIOS; // high/low

        PIN_OUT=ALLGPIOS;
        the_z=THE_PIN_IN;//+the_z;
        CHECK(HIGH);
        PIN_OUT=~ALLGPIOS; // high/low

        delay(5);
    }
}
  • 來看看 Code.B 這支程式,
  • gpio 12/13/14/15 為 output,分別接至 gpio 為 input 的 2/3/4/5。我們將檢測 PIN_IN 到底花費多少時間。
  • 有兩個開關,及一個註解。
  • 一個是設為 0,則真正去檢測 PIN_IN 的時間。設為 1,則僅為純運算(另一個開關便無作用),用以對照存取 PIN_IN 時的差異。
  • 另一個是 THE_CHECK,設為 1,則會檢查 PIN_IN 讀取回來的值是否符合預期,若不符合會 wdt reset。當然結果會是符合的,故需另一方面的檢測,將輸入腳位,例如 2,離線,則會發生 wdt reset,表示 PIN_IN 確實有作用。
  • 註解則是這行,the_z=THE_PIN_IN;//+the_z; 互換成 the_z=THE_PIN_IN+the_z; 用意是讓 PIN_IN 真正被取值,因為若不然,可能編譯器最佳化會動些手腳得到非我們想要的結果。而事實呈現這兩種都會取值,故另一方面我們可以再作些比較。
  • 以下放上 3 張圖,THE_CHECK 為 0 的前提下,開關 0(註解),開關 0(沒註解),1(沒註解)。
圖四四一。開關為 1,無註解,純運算;兩變數相加。
圖四四二。開關為 0,無註解,時距在 288ns 及 300ns 此兩者跳動。PIN_IN 及另一變數相加。
圖四四三。開關為 0,註解。PIN_IN 直接指定給另一變數。
  • 圖二對圖三的差別是多了一個加的動作及載入變數,相差 50ns。又,圖二其實是因取樣率不夠細而跳動,故猜測時間應介於此二值之間,取 295ns,故我們取最終結果是,訂為多了 55ns。再參照圖一表示 PIN_IN 皆有被讀取。
  • 比對圖一二,圖二多了 PIN_IN 的讀取時間。圖一多了一個變數的載入時間。因此二圖相減會多減了變數載入時間。由於加法的時間及載入變數是 55ns,我們取比例 1:2,則載入時間是,我們取 37ns。一二圖差是 133ns。故大膽結論 PIN_IN 耗時是 170ns。
  • 然而,取值 PIN_IN,必定要指定值給某變數,例如 a=PIN_IN,故,通常結論 PIN_IN 耗時是,圖三,238ns。
  • 為何筆者的此二結論會差這麼多(存變數時間 68ns),筆者也不知道XD
  • 後面會評估 cycle count,我們再用 cycle count 來夾 PIN_IN,應最準確了。
  • 再一個終極殺招,在 PIN_OUT 之間,放入 120 個 PIN_IN,得到平均值(把誤差也平均掉了)為 158ns。因 PIN_IN 不影響 GPIOs,故,或許,它可以作為 ns 級的 delay。但記得 PIN_OUT 為 75ns,所以控制 GPIO,需加/減這個值。

實驗五。Cycle Counts


// Code.C.
//
// evaluate cycle count and PIN_{}.


#include <ESP8266WiFi.h>


IRAM_ATTR volatile unsigned &PIN_OUT = *((unsigned*)0x60000300);
IRAM_ATTR volatile unsigned &PIN_OUT_SET = *((unsigned*)0x60000304);
IRAM_ATTR volatile unsigned &PIN_OUT_CLEAR = *((unsigned*)0x60000308);

IRAM_ATTR volatile unsigned &PIN_DIR = *((unsigned*)0x6000030C);
IRAM_ATTR volatile unsigned &PIN_DIR_OUTPUT = *((unsigned*)0x60000310);
IRAM_ATTR volatile unsigned &PIN_DIR_INPUT = *((unsigned*)0x60000314);

IRAM_ATTR volatile unsigned &PIN_IN = *((unsigned*)0x60000318);

IRAM_ATTR volatile unsigned &PIN_0 = *((unsigned*)0x60000328);
IRAM_ATTR volatile unsigned &PIN_2 = *((unsigned*)0x60000330);


#define digitalWriteHIGH(x) (PIN_OUT_SET|=(x)) // (WRONG!!!!!!)
#define digitalWriteLOW(x) (PIN_OUT_CLEAR|=(x)) // (WRONG!!!!!!)


// wemos d1 mini pinout.
#define D0      16
#define D1      5
#define D2      4
#define D3      0
#define D4      2
#define D5      14
#define D6      12
#define D7      13
#define D8      15
#define TX      1
#define RX      3

#define IO00    0x00001
#define IO01    0x00002
#define IO02    0x00004
#define IO03    0x00008
#define IO04    0x00010
#define IO05    0x00020
#define IO12    0x01000
#define IO13    0x02000
#define IO14    0x04000
#define IO15    0x08000
#define IO16    0x10000

#define ALLGPIOS 0x1F03F // (WRONG!!!!!! should be 0x0F03F)


IRAM_ATTR static uint32_t _getCycleCount() __attribute__((always_inline));
IRAM_ATTR static inline uint32_t _getCycleCount(){
    uint32_t ccount;
    __asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
    return ccount;
}


void setup() {

    Serial.begin(115200);
    while (!Serial) delay(50);
    Serial.println("");

    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);


    pinMode(0, OUTPUT);
    // pinMode(1, OUTPUT); // tx
    pinMode(2, OUTPUT);
    pinMode(3, OUTPUT);
    pinMode(4, OUTPUT);
    pinMode(5, OUTPUT);
    pinMode(12, INPUT);
    pinMode(13, INPUT);
    pinMode(14, INPUT);
    pinMode(15, INPUT);
    pinMode(16, INPUT);

    delay(5000); // 5 seconds
}


IRAM_ATTR volatile unsigned z1, z2, z3, z4, z5;
IRAM_ATTR volatile unsigned x1, x2, x3, x4;


IRAM_ATTR void loop() {

    IRAM_ATTR static unsigned x=1, y=micros();

    if (micros()>y+5000000 || x){ // another 5 seconds
        if (x){
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
        }
        x=0;


        z1=_getCycleCount();
        z2=_getCycleCount();
        z3=_getCycleCount();
        z4=_getCycleCount();
        z5=_getCycleCount();
        Serial.printf("\r\n(1)\r\n%u %u %u %u %u\r\n[%d %d %d %d]\r\n\r\n", z1, z2, z3, z4, z5, z2-z1, z3-z2, z4-z3, z5-z4);

        PIN_OUT=ALLGPIOS;
        z1=_getCycleCount();
        PIN_OUT=~ALLGPIOS;
        z2=_getCycleCount();
        PIN_OUT=ALLGPIOS;
        z3=_getCycleCount();
        PIN_OUT=~ALLGPIOS;
        z4=_getCycleCount();
        PIN_OUT=ALLGPIOS;
        z5=_getCycleCount();
        PIN_OUT=~ALLGPIOS;
        Serial.printf("\r\n(2)\r\n%u %u %u %u %u\r\n[%d %d %d %d]\r\n\r\n", z1, z2, z3, z4, z5, z2-z1, z3-z2, z4-z3, z5-z4);

        z1=_getCycleCount();
        delayMicroseconds(1);
        z2=_getCycleCount();
        delayMicroseconds(1);
        z3=_getCycleCount();
        delayMicroseconds(1);
        z4=_getCycleCount();
        delayMicroseconds(1);
        z5=_getCycleCount();
        Serial.printf("\r\n(3)\r\n%u %u %u %u %u\r\n[%d %d %d %d]\r\n\r\n", z1, z2, z3, z4, z5, z2-z1, z3-z2, z4-z3, z5-z4);

        z1=_getCycleCount();
        x1=PIN_IN;
        z2=_getCycleCount();
        x2=PIN_IN;
        z3=_getCycleCount();
        x3=PIN_IN;
        z4=_getCycleCount();
        x4=PIN_IN;
        z5=_getCycleCount();
        Serial.printf("\r\n(4)\r\n%u %u %u %u %u\r\n[%d %d %d %d]\r\n\r\n", z1, z2, z3, z4, z5, z2-z1, z3-z2, z4-z3, z5-z4);


    }
    delay(750);
}
  • _getCycleCount(),
  • 筆者也看不懂它做了什麼事,也不深究了XD
  • 我們看一下執行結果如下,
20:32:57.391 -> (1)
20:32:57.391 -> 3744894177 3744894191 3744894205 3744894221 3744894237
20:32:57.391 -> [14 14 16 16]
20:32:57.391 -> 
20:32:57.391 -> 
20:32:57.391 -> (2)
20:32:57.391 -> 3745010819 3745010850 3745010879 3745010910 3745010939
20:32:57.391 -> [31 29 31 29]
20:32:57.391 -> 
20:32:57.424 -> 
20:32:57.424 -> (3)
20:32:57.424 -> 3745448242 3745448461 3745448680 3745448899 3745449118
20:32:57.424 -> [219 219 219 219]
20:32:57.424 -> 
20:32:57.424 -> 
20:32:57.424 -> (4)
20:32:57.424 -> 3746614177 3746614228 3746614278 3746614334 3746614390
20:32:57.424 -> [51 50 56 56]
  • 這是 @160MHz 的結果,
  • 若是 @80MHz,則只有(3)是有差異的,值為 147。並且先說,若對比於按照以下的分析@160MHz,將會是有出入的。原因筆者未知XD
  • _getCycleCount(),本身用了,我們取 15 個 count。
  • 根據(3),我們取 delayMicroseconds(1),為 200 個 counts。故,1 個 count 為 5ns。
  • 檢驗之前 PIN_OUT 的量測是 75ns。此處(2)我們取 30 counts,減 _getCycleCount() 自身 15 counts,所以恰 5ns x (30-15) = 75ns 無誤。
  • 再看(4),取 53 counts,得 PIN_IN 為 38 counts。5ns x 38 = 190ns 平均。最小是 175ns,最大是 205ns。
  • 結論 _getCycleCount(),本身用了 15 個 counts,1 個 count 為 5ns。
  • P.S. 有沒有覺得怪怪的,
  • 好像有,但,也不深究了;最小時脈週期應是不會小過 6.25ns 才是,所以,肯定是誤差或粗略算法而造成 5ns 的答案。因此,答案寧可不復思索地取 6.25ns! 6.25ns! 6.25ns!
  • 最後,承前一段,以 cycle counts 來夾 PIN_IN,得到 156.875ns,這是最精確的結果了!

附錄

  • 從官方參考文件找不到跟中斷向量表相關的資料。所以關於中斷的延遲就以後有空再研究看看。
  • 因本章涉及 GPIO 暫存器的直接存取,所以使用前確認一下沒有 side effects。例如 GPIO_OUT bit 16 to 31。且筆者前扣也真的違反了,即,將 bit 16 當成 GPIO16。故記得改一下 ALLGPIOS 0x0F03F。事實上從表格中可以找出更多有用的資訊的。
  • GPIO16 不歸類/不是一般 GPIO,細節請參考官方資料。
  • 官方文件有提到 gpios 可以軟體設定為 pull-up or pull-down,除此之外筆者就再也找不到更多佐證的資料及其設 pull-up/down 的 registers(猜測是 GPIO_PINx_DRIVER and GPIO_PINx_SOURCE 這兩欄位。有認知的讀者敬請不吝告知)。據筆者目前認知,所有 gpios 都可設 pull-up。而 gpio15 不可外部 pull-up 因會影響 boot-strap,故它實際上都被 external pull-down。
GPIO5-1
GPIO5-1-lost_addendum
GPIO5-2
GPIO5-3
GPIO5-4
GPIO5-5
SPI3-1
SPI3-2
SPI3-3
UART5-1
UART5-2
UART5-3
UART5-4
UART5-5
Timer

應用

digitalWrite475ns
PIN_OUT75ns
PIN_OUT_SET/CLEAR200ns
PIN_DIR/_OUTPUT/_INPUT75ns
PIN_IN156.875ns
_getCycleCount()93.75ns
本章結論
T0Hcode 0, high voltage400ns+-150ns
T0Lcode 0, low voltage850ns+-150ns
T1Hcode 1, high voltage800ns+-150ns
T1Lcode 1, low voltage450ns+-150ns
RESreset, only low voltage50usat least or more
WS2812B signal timing(the time needed to delay for a level lasting)
  • 此表是控制 led 的 ws2812b ic。
  • PIN_OUT 會作用於所有 output gpios。所幸此 ic timing 不是很 critical,故我們可以用 PIN_OUT_CLEAR/SET 及搭配 PIN_IN 來實作。
  • 思考方式是,狀態改變 + delay。問題是狀態改變需要時間,如本章結論一表。故狀態改變所需要的時間,便會與 delay 重疊,才能製作出如我們所預期。亦即,real_delay = needed_delay – state_change_time。因為我們用上 PIN_OUT_SET/CLEAR,state change time 規格是 200ns,可用,所以首步加工後的規格如下,
T0Hreal_delay = 200ns
T0Lreal_delay = 650ns
T1Hreal_delay = 600ns
T1Lreal_delay = 250ns
real delay
  • 唯須注意,最後一個 bit 的最後一個狀態,需額外加上已減掉的 state change time。
  • 嘗試一:
  • 我們是否能以“保持該狀態”,來作為 delay 的手段?故作以下嘗試。
  • T0H,PIN_OUT_SET + PIN_OUT_SET,將會是 HIGH(400ns)
  • T0L,PIN_OUT_CLEAR + PIN_OUT_CLEAR x 3,將會是 LOW(800ns)
  • T1H,T1L 亦同。
  • 尾延遲,則只要該當下狀態再重覆一次即可滿足所求。
  • 接下來我們試試反覆發送 0011 1000 1010b + HIGH + delay(300us) + LOW + delay(100us),試試,程式碼如下,
IRAM_ATTR void try01(){ // gpio0

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L


    // PIN_OUT_CLEAR=0x01; // tail delay
    // since there are more added levels, so the tail delay does not need.
    PIN_OUT_SET=0x01; // HIGH level
    delayMicroseconds(300);
    PIN_OUT_CLEAR=0x01; // LOW level
    delayMicroseconds(100);
}
  • 結果 waveform 如下圖,
  • 我們非常興奮地看到,此嘗試是失敗的,從該有的 400ns/800ns 變成是 150ns/300ns,
  • 但它卻透露一些想法到我們腦袋中,因為它讓我們看到了四點閃光,
  • 一,怎 level 可以比我們所使用的 PIN_OUT_SET/CLEAR 還要短?
  • 二,此 waveform 表示,CPU 並不會等待 level 就定位,才執行下一筆 IO 指令。
  • 三,那麼,我們是否可用逐一遞增的 IO 指令,來窺探 CPU/IO 更細部的時序,例如 SET/CLEAR,SET-SET/CLEAR,SET-SET-SET/CLEAR,等。
  • 四,是否可如法泡製做出更小的時距呢?
  • 以上,再用力想一想,其實那也不對了,更糟了!如果 CPU 與 IO 不同步,那麼前面的所有結論就都毀了而得真的以遞增的 IO 指令來查一次 IO 所真正耗用的時間。
  • 然而,上述的數據也不會騙人,確實更短了!到底問題出在哪?
  • 按以上所有邏輯假設全正確再推演便是不同的 IO 指令接續執行造成如此結果,若然,那也是很糟,必須檢驗更多的組合了。
  • 除錯的結果,問題抓到了!
  • 我們量測 PIN_OUT_SET/CLEAR 是用以下的方式。
  • #define digitalWriteHIGH(x) (PIN_OUT_SET|=(x))
  • 展開成為,PIN_OUT_SET=(PIN_OUT_SET|(x)),因此,它實際做了兩次 IO,這是第一個錯誤;PIN_OUT_SET 是不允許被讀取的,這是所犯的第二個錯誤。
  • 因此,錯誤不回溯修正只劃刪除線,以維持本文的連貫性。筆者修正結論如下:
digitalWrite475ns
PIN_OUT75ns
PIN_OUT_SET/CLEAR75ns
PIN_DIR/_OUTPUT/_INPUT75ns
PIN_IN156.875ns
_getCycleCount()93.75ns
本章結論(修正後)
  • 接著我們修正 try01 的程式如下,
  • 因為我們用上 PIN_OUT_SET/CLEAR,state change time 規格是 75ns,可用,所以首步加工後的規格如下,
T0Hreal_delay = 325ns
T0Lreal_delay = 775ns
T1Hreal_delay = 725ns
T1Lreal_delay = 375ns
real delay(修正後)
  • T0H,PIN_OUT_SET + PIN_OUT_SET x 4,將會是 HIGH(300ns + 75ns)
  • T0L,PIN_OUT_CLEAR + PIN_OUT_CLEAR x 10,將會是 LOW(750ns + 75ns)
  • T1H,T1L 亦同。
IRAM_ATTR void try02(){ // gpio0

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T1H
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01;
    PIN_OUT_SET=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T1L

    PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; PIN_OUT_SET=0x01; // T0H
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; // T0L
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01; PIN_OUT_CLEAR=0x01;
    PIN_OUT_CLEAR=0x01;


    // PIN_OUT_CLEAR=0x01; // tail delay
    // since there are more added levels, so the tail delay does not need.
    PIN_OUT_SET=0x01; // HIGH level
    delayMicroseconds(300);
    PIN_OUT_CLEAR=0x01; // LOW level
    delayMicroseconds(100);
}
成功了,分亳不差!
  • 以上篇幅太長,就到此為止。我們可以用迴圈來簡化程式碼,不過就會多出些 cpu cycles。因此需作調配與預估例如,一圈用了 110ns。相反地說,如上的實例,就不需顧慮時間上的問題。各有優缺點。
  • 精製將另開一篇實作 ws2812b。
  • 重點提醒,所以我們看到,一單位多寬乘上倍數,就決定了 level 恰多寬。但“尾延遲”是必要的,當若有另一某與時間相依之運算或判斷在等待彼訊號完整發完後即執行時;否則(若沒有)就會搶先在該彼訊號發完最後一個狀態隨即執行,亦即重疊了彼訊號的最後一個(過渡中)狀態。當然我們還可以進一步這樣看,input,時間恰是 output 的兩倍加一個 cpu cycle(實驗值非理論值),故,自身的 GPIOs 自洽下無尾延遲的必要當 output + input;但 input + output 呢?即,需考慮不同的 IO 指令組合。
    最後我們可以不負責任(未經證實)地這樣看,75/6.25=12,156.875/6.25=25.1,cpu 最快每 12(n?) cycles 才會存取一次 pbus,延遲 25 cycles 才會取一次 input;而這些時距都是為了讓訊號就定位。

補充

  • 如附圖及程式。
  • 還是玩了一下 ws2812b。串接了四顆,使用 3.3V,當然正常情況下最後一顆 dout 不會有資料。
  • 幾點注意,
  • 若 reset 時間過短,則會造成 dout 會 bypass din,即不該送出的資料仍送出。
  • 嘗試將 0 & 1 的差異性擴大,如此程式,但,卻沒有半點幫助。
  • 簡單講,愈後面的 led,其 din/dout 資料的失真度愈大到是嚴重的錯誤。
  • 簡單講,看來,ws2812b 並未對 din 的資料重製,而是 filter 掉自己所屬的資料,其餘 bypass 送出,才會有如此的失真度,
  • 亦即,若有重製能力的 led controller,將是更好的。
  • 當然筆者還有一點未驗證,如此遇到的嚴重錯誤(請看附圖,是第一顆 din 及第四顆 din 的 waveform 比較)或許是電壓不夠所致,即,3.3V 是不夠的。所以後續若要再玩必須使用 5V 了!

#include <ESP8266WiFi.h>

IRAM_ATTR volatile unsigned &PIN_OUT_SET = *((unsigned*)0x60000304);
IRAM_ATTR volatile unsigned &PIN_OUT_CLEAR = *((unsigned*)0x60000308);

#define IO04    0x00010

#define ALLGPIOS 0x0F03F

#define IOx IO04


IRAM_ATTR void try03(){ // gpio4, 001110001010b x 2. 0(300ns, 900ns), 1(600ns, 675ns).
    for (int i=0; i<2; i++){
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T1H
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T1L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;

        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T1H
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T1L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T1H
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T1L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T1H
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T1L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T1H
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T1L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    
        PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; PIN_OUT_SET=IOx; // T0H
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; // T0L
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
        PIN_OUT_CLEAR=IOx; PIN_OUT_CLEAR=IOx;
    }
}


void setup() {

    Serial.begin(115200);
    while (!Serial) delay(50);
    Serial.println("");

    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin(-1);
    WiFi.mode(WIFI_OFF);


    pinMode(0, OUTPUT);
    // pinMode(1, OUTPUT); // tx
    pinMode(2, OUTPUT);
    pinMode(3, OUTPUT);
    pinMode(4, OUTPUT);
    pinMode(5, OUTPUT);
    pinMode(12, INPUT);
    pinMode(13, INPUT);
    pinMode(14, INPUT);
    pinMode(15, INPUT);
    pinMode(16, INPUT);

    delay(5000); // 5 seconds
}


IRAM_ATTR void loop() {

    IRAM_ATTR static unsigned x=1, y=micros();
    if (micros()>y+5000000 || x){ // another 5 seconds
        if (x){
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
            WiFi.forceSleepBegin(-1);
            WiFi.mode(WIFI_OFF);
        }
        x=0;


        try03();try03();try03();try03();
        PIN_OUT_CLEAR=IOx; // tail delay
        delayMicroseconds(150); // reset, not enough delay makes abnormal.


    }

}
channel 3 嚴重失真

結論

  • 單說這點,
  • 看回我們在 functionaliic 那篇文章,
  • 姑且不論 functionaliic 的效能是多麼低下。
  • esp8266 for arduino compatibility 的前提下,iic 能跑 400k 無虞,在本篇文章之前,這好似就是 esp8266 的極限了,畢竟只是一顆不到 50 元的埋控。
  • 但就本文 esp8266 gpios 的探究結果,我們喜出望外地得知,esp8266 是有能力跑 iic 的所有最新規格的,即,3.4Mbps 的 bi-dir high-speed mode,5Mbps 的 uni-dir(master out) ultra-fast mode。
  • 試問當今同級距的 uc,誰可為之?
  • 而 functionaliic 目的之一是為了更彈性控制 iic 的每個 bit(bit-wise interrupts),或每個 byte(enabling speedup,byte-wise interrupts)以實時地控制呑吐資料的時機。故它可部份修改以亦可支援 iic all modes。若有空再來改改看。
  • 我們還剩最後一紙封印,即,中斷響應時間;希望網路上有足夠的資料可以加持我們突破。
  • (本文中有些地方筆者還是以 cpu/io 不同步的觀點在看,不過不勘誤了,僅提醒之)

再補充

  • 參考資料:
  • esp8266 nonos sdk api 參考 文件
  • esp8266 nonos sdk
  • esp8266 管腳清單
  • esp8266 技術參考
  • esp8266ex 技術規格書
  • arduino core for esp8266
  • 由以上的資料得知,sdk 僅提供 .h 檔,大部份的 .c 源碼在私有權的前提下封裝成 .a/lib 檔。
  • 因此 esp8266 arduino 仍基於 nonos sdk 的函式庫上再加工包裝成 arduino compatible library。
  • 因此,當我們直接設定存取週邊外設時,至少至少都只能是用 sdk api 的方式來呼叫。
  • 關於 gpio 相關的存取,esp8266 管腳清單提供相當充份的資訊,以設定例如 pull-up 等,佐以技術參考文件中的中斷設定流程,便可自行實作 gpio 中斷。
  • 想必,esp8266 arduino 所提供的中斷設定也是基於此而實作的;甚而,我們基於此已得知 gpios 中斷是必須額外被控管的,因此 esp8266 arduino 處理此型中斷必有其自有的方式,例如排程響應中斷。
  • 換言之,使用 esp8266 arduino 中斷並量測其響應時間,及直接使用 sdk 中斷與量測,是必要的因為,很可能耗時上有相當的差距。若沒有的話也省得我們再三糾結之。
  • 預期一旦,直接使用 sdk 來設定中斷,我們就不能再使用 arduino 中斷了因一定衝突。
  • 關於所有中斷定義都可在 api ref doc 中找到。
  • 由於 gpios 中斷只有唯一的進入口/大家共用,故排程是避免不了的否則 starvation,但這代表著花用更多的時間來響應中斷。故,專案專用,雖扼殺了普適性但也是最快的。否則就是排程,即落入了 esp8266 arduino 的相同的坑內。要嘛用它的要嘛自己實作,但自己的方法有比它還好嗎:加入中斷,即是將該腳位置入 queue 中。每當進入 isr 中,將現存於 queue 中所有腳位移除及再加入而若遇到發生中斷者就會依序服務之。但這存在一個先天上的問題是有可能後中斷者先服務單看其在佇列中的位置。又若同時兩支腳位以上中斷且只服務一支而離開,會再進來嗎?預期是會的故而在一次 isr 中解決掉所有中斷服務是較佳的,但又可能為時過久導致 wdt reset;故而可呼叫 api 先餵一下 watchdog。故實作上只要巡訪 vector,沒理由用上 queue。再一個問題是,若某些 gpios 之間是相依的,則同時發生中斷下某誰先服務與若改為後服務將可能造成結果不同,則解決的方法是各自 isr 內所處理的東西必須是與對方無關的。假若無法無關,則筆者無解;有想法的讀者可不吝指教。
  • 這段的再補充透露了筆者下篇文章很可能是兩種中斷響應時間的量測,鎖定在 gpios。

Categories: Arduino

Tags: ,

發佈留言

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

PHP Code Snippets Powered By : XYZScripts.com