IIC timer-based soft stack
案,打上了這個標題,自己就害怕了起來。最害怕的是架構設計,一腦子空白。再來是,按,spec 從以前以來不知看了多少遍了看過就忘,一絲不留。現下,就是得再重新看過一遍,大量時間是少不了的,能不能通,又是另一個問題。timer-based 意謂著 non-blocking,是很吸引我。但此主題對筆者而言是此前遭遇的最大挑戰。文章沒有下文,剛好而已。。。
筆者按:timer-based,以 esp8266 而言,其時脈是不足以達到 100kbps 的,或者說就在這個臨界,因為一次的 timer interrupt 就費時 4us。不過因是 master 送 clock 所以是有機會低於規範時脈的。slave 而言,是依於 clock 而作 GPIO 中斷,故沒事。
附上連結,各位有興趣一同挑戰。
暗,筆者是一點興趣都沒有,但,不得不嘗試看看。。。
筆記
- multi-masters while collision exists; with collision detection and arbitration.
- standard mode: 100Kb/s, bi-dir.
- fast mode: 400Kb/s, bi-dir.
- fast mode plus: 1Mb/s, bi-dir.
- high speed mode: 3.4Mb/s, bi-dir.
- ultra fast mode: 5Mb/s, uni-dir.
- Arbitration: procedure to ensure that, if more than one master simultaneously tries to control the bus, only one is allowed to do so and the winning message is not corrupted. This procedure relies on the wired-AND connection of all I2C interfaces to the I2C-bus. If two or more masters try to put anything onto the SDA bus, the first to produce a“1”when the other produces a“0”loses the arbitration. The clock signals during arbitration are a synchronized combination of the clocks generated by the masters using the wired-AND connection to the SCL line.
- Clock: Bus clock signals from a master can only be altered when they are stretched by a slow slave device holding down the clock line or by another master when arbitration occurs.
- Signaling:
- The bus stays busy if a repeated START (Sr) is generated instead of a STOP condition. In this respect, the START (S) and repeated START (Sr) conditions are functionally identical. Therefore, the S symbol is used as a generic term to represent both the START and repeated START conditions, unless Sr is particularly relevant.
- Acknowledge (ACK) and Not Acknowledge (NACK).
- The transmitter releases the SDA line during the acknowledge clock pulse so the receiver can pull the SDA line LOW and it remains stable LOW during the HIGH period of this clock pulse. Set-up and hold times must also be taken into account.
- Not Acknowledge signal, then, the master can generate either a STOP condition to abort the transfer, or a repeated START condition to start a new transfer.
- A master-receiver must signal the end of the transfer to the slave transmitter where uses the NACK.
- Clock synchronization.
- The synchronized SCL clock is generated with its LOW period determined by the master with the longest clock LOW period, and its HIGH period determined by the one with the shortest clock HIGH period.
- Arbitration.
- Two masters may generate a START condition within the minimum hold time (t HD;STA ) of the START condition which results in a valid START condition on the bus. Arbitration is then required to determine which master will complete its transmission.
- Arbitration proceeds bit by bit. During every bit, while SCL is HIGH, each master checks to see if the SDA level matches what it has sent. This process may take many bits.
- Whenever a master tries to send a HIGH, but detects that the SDA level is LOW, the master knows that it has lost the arbitration and turns off its SDA output driver. The other master goes on to complete its transaction.
- The master that loses the arbitration can generate clock pulses until the end of the byte in which it loses the arbitration and must restart its transaction when the bus is free; such a way in order to prevent from starvation.
- However if a master also incorporates a slave function and it loses arbitration during the addressing stage, it is possible that the winning master is trying to address it. The losing master must therefore switch over immediately to its slave mode; such a way in order to prevent from race hazard.
- There is an undefined condition if the arbitration procedure is still in progress at the moment when one master sends a repeated START or a STOP condition while the other master is still sending data. In other words, the following combinations result in an undefined condition:
- Master 1 sends a repeated START condition and master 2 sends a data bit.
- Master 1 sends a STOP condition and master 2 sends a data bit.
- Master 1 sends a repeated START condition and master 2 sends a STOP condition.
- Clock stretching.
- Clock stretching is the way to pause a transaction by holding the SCL line LOW.
- On the byte level, a device may be able to hold the SCL line LOW after reception and acknowledgment of a byte to force the master into a wait state until the slave is ready for the next byte transfer in a type of handshake procedure.
- On the bit level, a device such as a microcontroller, can slow down the bus clock by extending each clock LOW period. The speed of any master is adapted to the internal operating rate of this device.
- In Hs-mode, this handshake feature can only be used on byte level (see Section 5.3.2).
- The slave address and R_h/W_l bit, and the transfer modes.
- A data transfer is always terminated by a STOP condition (P) generated by the master. However, if a master still wishes to communicate on the bus, it can generate a repeated START condition (Sr) and address another slave without first generating a STOP condition.
- Note:
- The SlaA of the Sr+SlaA can be different from the previous addressed one.
- I2C-bus compatible devices must reset their bus logic on receipt of a START or repeated START condition such that they all anticipate the sending of a slave address, even if these START conditions are not positioned according to the proper format. That is, the receipt of the Start/Repeat-start pattern to reorder device IIC logic if bus abnormal previously even if the logic is correct.
- A START condition immediately followed by a STOP condition (void message) is an illegal format. Many devices however are designed to operate properly under this condition.
- 10-bit address.
- Which compatible to the processes of 7-bit address.
- 1st byte pattern 11110xx. xx is the 10-bit address MSB.
- 2nd byte pattern xxxxxxxx. The remaining part of the 10-bit address.
- The R_h/W_l bit of the 1st byte must be 0(W_l) because there is the 2nd byte which is another part of the 10-bit address. Such the condition also holds to the repeat start process which defined followed only by the 1st byte of the 10-bit address pattern 11110xx; need not the 2nd byte; because this time the R_h/W_l bit must be 1 which tells to other 10-bit address devices it is not a brand new start session. All of which implies that the addressed one must keep the memory it was addressed before and might to proceed next.
- General call address format.
- The meaning of the general call address is always specified in the second byte.
- When the least significant bit B is a “1”: the 2-byte sequence is a “hardware general call” of the following format; for telling to devices its address. The one raises the call could be either as a master or as a slave in succeeding processes.
- Note that such a facility is also applicable to 10-bit address by using the Fig.15 format as, the 2nd byte is the LSB 8-bit part address and the 3rd byte which follows the Sr, is the MSB 2-bit address.
- When the least significant bit B is a “0”, the second byte has the following definition:
- 0000 0110 (06h): Reset and write programmable part of slave address by hardware. On receiving this 2-byte sequence, all devices designed to respond to the general call address reset and take in the programmable part of their address. Precautions must be taken to ensure that a device (during reset) is not pulling down the SDA or SCL line after applying the supply voltage, since these low levels would block the bus.
- 0000 0100 (04h): Write programmable part of slave address by hardware. Behaves as above, but the device does not reset.
- 0000 0000 (00h): This code is not allowed to be used as the second byte.
- START byte.
- It is used as a buffer of time for (slow response) slave detecting and having enough time starting transfer from the latter Sr. no slave is allowed to ACK on the START byte.
- Bus clear.
- If SCL stuck, POR.
- if SDA stuck, fulfill a byte (or more) of clock signals would do or if failed then POR.
當前結論與實作先期考量
- 看到 device id 時就看不懂了,後面還有一節 ultra fast mode。不過真的負的興趣也用光了。就實作的考量上,其實就只有 read/write/readwrite 三個指令與延伸而已,實作真的超簡單,但要全面涵蓋又是另一回事了;故是真的自覺得複雜。自己就再想想吧,很可能沒有下文了。
- Masters 間的仲裁,必須在可以 START 後的任何時刻,每個 clock 回讀 data bit,即便不存在 multi-master。一來立判嵌入式軟體不適合做此多餘的事,乾脆由硬體 IC 來處理才是。再來,搜尋文中關鍵字 “In other words” 那一段來看看。其實簡單講,multi-masters 只要判斷 “iic bus 都為 high”(但很可能 data transfer 當中),之於 “bus 處於 idle” 是同義的,現下某 master 便可立即發送 START bit,那麼,資料便被破壞掉了。簡單講,multi-master 的機制的可行性是被存疑的(上述有誤)。
事實上,規範的並非筆者上述所說的,而是:當 STOP condition 發生後,bus 才是所謂的 idle 狀態。但這又表明了 multi-masters 必須時時監控 SDA & SCL,或說,SDA 中斷發生後查看 SCL 為 high,就是抓到 STOP/idle condition 了(及 START/busy condition)。
因此,實作 multi-masters,GPIO 必須在 timer-triggered output 及 input by interrupt 間切換,增加程序處理的複雜度及降低 device 本身效率。故其必要性便需放大;devices 絕大部份都是 slave,除非多個 uC 在同一系統中;其某些 uC,不見得會做 arbitration,則便白搭;反之,多個 uC,是可試著其餘都當作 slave 的便可取代 multi-masters,或說 multi-masters 的存在機率更低了。而不可避免地,multi-masters 很可能的意圖是,masters 相互間主動通訊收送即時資料,意謂著這樣的矛盾:某方無法即時當衝突發生時;解法可考量用上 iic 其他通道;但不管多少通道,multi-masters 存在就必存在塞車。
簡單講 multi-masters 並沒有實作上或運行上的問題(except 3 undefined)。但有實用性上的疑慮。 - 10-bit address,實作上雖其定義及行為相容於 7-bit address version 而易於擴充且不失效率,但真會用上嗎?他方實測下,不多的 devices 處於同一 bus,電容效應可能就不堪負荷了,所以用上機會不高。
- START byte,是一種輔助。此時反要放大嵌入式系統,現今其時脈已不低,不需要支援,況且,此規格有顧及地定義了,支不支援此規格的 slave 都不能 ack 它,這表示不支援此規格的 slave 支援了它。故不作,就支援了,是頗佳的定義。
- 最後,general call address,只有 hardware general call 可能有符合日常使用的。其他像 reset 等的機制,實作品本身就必須實現 exception 處理,例如 timeout,所以 reset 似乎多此一舉了。反而 spec 應定義 timeout 的大小,以讓實作可以遵循及預期。
談回 hardware general call,只適用於 master,又,slave 認識 master 做什麼?大部份情況需要知道的是接上的 slave 的 address,或是應明定,slave 一接上就一定是 master,被 ACK 後才轉為 slave。那麼 iic devices 就用得順心了。(但此時 multi-masters 省不了了)。 - 暗,以上列了那麼多,就是為了消去法。。。按,此時內心乍然覺得海闊天空,舒坦太多了。
實作考量
- master retry/repeat-start if nacked; stop if retries failed.
- master 必須檢查 clock 是否 stretched. abort (and reset) if timeout.
- 支援 START condition immediately followed by a STOP condition.
- slave 並不作 bit-level clock stretch。但可考量 byte-level 怎麼做。
- 問題,當 master data write 當中,被 slave nacked 掉,該當如何?採取的策略應會是重寫該 byte,而非是下個 byte。而在 bytes 尺度上,應會是採取 LSByte first。
- slave 不論是在 read or write,若在任一個 byte 中,某個 bit 會是 START/STOP bit,則因為 slave 是正處於 SCL GPIO interrupt 模式當中,故領受 SDA 的變化是強人所難的;亦即,slave 只支援 byte 為單元的 bus 狀態改變的因應。
但若然且發生,亦即隨後不再有 SCL 中斷,亦即 bus 被認定是 idle,乃或接續新的 session,那麼 slave 勢必要有 timeout 機制。當然此情況下,master 早已被 NACKed 或自己 NACKed 掉了,也或錯誤 ack/nack 掉了。如此情境下,master/slave 是狀態不同步的,bus 是錯亂的。因此 timeout 的時間要相當短以減少 wrong bytes。
但再論 timeout,或許僅僅只是 master 延遲而已,因此 slave 可嘗試為,1 個 clock cycle 沒有中斷發生,便進入 idle。此 clock cycle 則為前一個 clock cycle time,但可調。這在 uni-master 中應是合適的。而誤失的代價單看 master 延遲的頻率;timer-triggered 的方式其機率應該很低。
承此,master 若收到 nack,那麼延遲 1.5 個 clock cycle 再送 Sr or STOP 也是合適。 - 定位上界:
假設 50 萬畫素的攝像頭,25 FPS。0.5 x 10^6 x 25 x 3 bytes = 37.5 x 10^6 = 37.5MBytes/s 的頻寬。所以要當正規的攝影工具,走 IIC,可以直接放棄。
但若再降階到可接受範圍呢,1/3 FPS:4Mbit/s;也是要 IIC 的最高級 ultra fast mode 才有辦法辦到。
4Mbit/s 頻寬代表著 80MHz 的 CPU,每執行 20 條指令當中就必須處理掉 1 bit;1 bit 可能需要用上至少 4 條指令以上;移位,test-bit,GPIO,終止判斷。亦即,至少 20% 的 CPU 資源。剩餘的資源還必須用在顯示或網路傳輸。
所以通常攝像頭都是走 SPI,增加頻寬但耗用差不多的 CPU 資源;硬體 SPI 則更快更少資源。
簡單講,降階的影像功能走 IIC 姑且可行,但得 blocking transfer 及 ultra fast mode 才行。圖片功能則無此限。
回題,
400Kbps,每 2.5 us 就必須解決 1 bit。斥或至少 blocking waiting 22.5 us 解決 1 byte,還不算被拉住的代價。
100Kbps,每 10 us 就必須解決 1 bit。
這兩點對 timer interrupt 而言,bit time 1us,bit payload 4us,400K 顯然不可能。100K,則有約 50% CPU 資源被耗用,含 40% 的資源浪費。
因此結論是,
IIC 高速串流傳輸,對整個系統是相當大的負擔,CP 值異常地低。
高速間歇性傳輸,快得顯然沒有必要。
blocking waiting,將有機會拖累系統。
故,低速間歇性 timer-triggered interrupt transfer,將是餘選。
還記得不忘了查一下,MultiTimers 允許最快是 30us,那筆者就把 IIC 的 trigger timing 設為 30us,60us 1 個 bit,大約是 17000 bps,反推,取 14400 bps,得 34 us 來設定 MultiTimers。
Prototype
下面附上才剛完成的原型,寫了至少兩個禮拜有,完全沒測試過,全部紙上談兵。以比對日後修改了什麼。
20210705 v.0.2
0.2 版就實際在 PAJ7620 gesture sensor 上跑過,已測試過 write-read,write-write 是正確的,也因藉此修正了一些 bug 及擴充追加了一些功能。原本計劃在此基石上架構第二層資料存取層,但各家 slave 的指令格式頗有出入,程度上不易整合出通用性,故,再看看吧。再者,也應需寫個 slave 才是,其應比 master 更重要,其將可讓 uc 與 master uc 溝通或偽裝,但,再看看吧。後續應將會分支出來,例如用 timer 驅動的及 polling 的如目前版本。
致能 debug 參數,uart dump 將會使得 slave timeout 掉,此 gesture sensor 大概 250us 就會 timeout 了。各家的 timeout 值應都各異;不 timeout 也是有可能的,遇到 start or stop 才會釋放狀態。
此外經測試,官方 iic 的 bit period 就是 10us,並且就第三方 gesture sensor 範例,僅使用了不到 1% 的 CPU 資源。未來本程式使用 timer trigger,將會是 70us 左右,相差了 35 倍。不過,本程式本意就僅僅是彈性/nonblocking 而已。在複雜系統下,即多重目的系統下,便可顯現優勢,如同前例 U8g2 的顯示應用。
不過要非常重要提一下,MultiTimers,白浪費了相當多的 CPU 時間在 isr invoked 的 payload 上;若此 payload 遠小於 4us,則再完美不過了。
20210709 update v.0.4
加入了 slave,藉以測試 master,再藉以相互測試,修正了彼此的一些 bug。哈,太怪了吧!就好像,筆者曾經這輩子有想做但絕不會去做的事之一是,寫一支編繹器來編繹自己。
故基本的幾種 protocols 都測試過應是沒有問題了才對。
另外實時資料存取,還未完成,應是說致能。故眼前只剩這部份了。
另外附入這版跑出來的波型結果。提一下怕自己忘記。載入 waveform,要指定取樣率,本例是 1000000,第幾個 channel 開始(start from 0),共幾個 channel,有標頭要略過。
20210713 update v.0.5
20210716 update v.0.6 – librarized
20210720 update v.0.7
U8g2 example
- 以下範例是針對 U8g2,取代它的 iic 運作。
- 使用 ttgo taobao white board,請參考前面 0.91 inch ttgo 那篇文章。
- SDA/SCL/RST 是 4/5/16。driver 是 ssd1306。
- 然而結果是失敗的。會 wdt reset。主要是花了不少時間仍不清楚其 iic 的運作。再加上改寫只能還是 blocking manner;若要改為 nonblocking 便得熟悉更上一層的運作,且修改的層面更廣,但此 U8g2 wrap 太多東西了很不容易攻克,故放棄了。
- 此範例恰好展示了 realtime write 的一些用法。當然使用上不恰當,做成了 blocking manner。
- 其次,也嘗試了不同寫法,不會 wdt reset 且資料應是傳輸正確,但 display 仍舊是沒有畫面,猜測與 reset 有關。也在一開頭加了拉 low 一會以重置,沒用。我追不出更上層關於 reset 的相關運作以改寫。故放棄。
- 補充:先前就試過,0.91″ TTGO,若不設定 reset pin,display 是顯示不出來的。但用上 0.91″ OLED IIC interface standalone,它並沒有 reset pin,且 U8g2 也設定成 none,便可作動。故 TTGO 可能需再次驗證看看 reset pin 的關聯性。
迷思
- 經過好長一段時間的沈澱,筆者再次嘗試取代掉 u8g2 的 iic 存取,最終卻遭遇到死胡同了。一來是因為使用 timer based 消化 data,跟不上 data 的産生速度,此受限於 gpio 的頻寬只有 1M,以及中斷響應有 4us 的 payload。再來就是關鍵的部份,在整個 u8g2 處理 data 的流程當中,無可避免地 timer(isr)將被暫停些許導致死結發生。例如我可等待資料消化完,再讓 u8g2 接續産生資料,然而,在該時間點,timer 因其他處的連續中斷或 u8g2 本身行為而被暫停。
- 因此我就在想了,functionaliic 到底實不實用;
- 高階以上的 MCU,要不是有硬體 iic,要不就是時脈與資料頻寬頗高,軟體 iic 隨便怎麼跑從來就不是問題。因此 functionaliic 是實用的,因為 400K 相當於例如 8M 頻寬的 gpio 與 cpu,相差 20 倍,如果等待 1 byte,將浪費 160 cpu cycles。而若是 timer based for each bit,則這浪費掉的便可別處利用。同理對於低階 mcu 亦然但 esp8266 不然因為它的中斷代價太高不適合用在高資料量傳輸。
- 關於 u8g2,之動態顯示,表示 data bus 是不時地傳輸著資料。(算一下頻寬需求:25fps x 128 x 32 x 1bit / 8bit = 12800 bytes; 1Mus / 12800 – 20 = 58.125us)
- 此時我們便需倒著想,blocked 地控制每一個 bit 成 400k,在每一個 byte 之後(20us)便是 repeat-start or stop,這意謂著此時我們便可等多久都行(58.125us,即,有 1/4 的時間用在作顯示傳輸)只要 slave 不 timeout,因此與其使用 functionaliic 不如使用 timer 控制在每個 byte 之後再作中斷或說每次中斷去處理 1 個 blocking wait byte(此時 timer 設為 80us)。(註:筆者忽略 address 了)
- 再論 400k 是規範 slave 的至少能力。故我們可以退而求其次使用 100k 或以下,當使用 functionaliic 時;必得 400k 當 blocked waiting 時。
- 最後,functionaliic 是不適用於 u8g2 但最適用於低階 mcu 的唯當資料傳輸速度不是檯面上問題乃因其 cpu 資源愈顯珍貴,唯一的考量點就是中斷的代價值不值得。
- 關於以上迷思乃是兩版 u8g2 debug 後的結論,附於下。
- (補充:筆者又再次作了第三次 debug,結論如下,)
- 此份程式中,有兩支 timers 觸發了 gpio ints 分別處理了 iic data processing & u8g2 drawing。因此將 gpio toggle 埋進了這些 isrs,證實了,isrs 本身是沒有任何拖累的點除了,在此一 isr 中 u8g2 drawing,且其耗時過久,便是上述 timer isr 暫停的唯一成因。由於現下我也忘了 gpio isr 當中若發生 timer int 是否能夠再即時岔斷,但現下看來是不行。推測(需看以前文章才是正解)在任何 isr 中須等到離開,中斷才能再次發生;之前發明 timer int + gpio int,應只是當佇留時間過長,timer 的會導致 wdt reset,但 gpio 的不會。故,。。。應結案了!
- 因此,讓我們再全盤審視一次問題,functionaliic 是否能用在 u8g2 身上:
- functionaliic 沿用 timer trigger;u8g2 processing 也使用 timer trigger(如此 debug code 內的做法)因之而改用 temporal polling 的,答案是可以的/解了!但,但,但,從後面文章 u8g2 porting 中得知關鍵問題是 u8g2 將一次 iic 的寫入切成多次 cb function 的呼叫(during writing data phase)。我們的 functionaliic 不也實作了 realtime version 了嗎?問題是每一小段 write data 都必需做一次無謂的複製,直至收集完一次完整的 write data(以讓我們可在 main code 內的上層控管 u8g2),斥或(違反我們的 nonblocking style)不複製而 spin waiting 直至 iic 消化完那一小段 data 才讓 u8g2 繼續走下去。除非繞路做法我們能在 u8g2 呼叫 cb function 的扣的更上層作控管。顯然,這三種方式都不是 ken style XD。
- 伏筆:或許,u8g2 完整一次的 write data,最多也只是它的 buffer 完整一次/不重覆的巡訪;正可驗證我們的 realtime version。
- 筆者歡迎在上面的前提下,有漂亮的解法^^
短暫的曯光
- 因此,筆者還是先完成方才提到的重覆複製 buffer 的做法;移植成功了,正常顯示也成功了(但下了猛藥/即,易崩潰的),如下附。
- code base 採自 wifi code base,顯示改自 u8g2 動態顯示,這二篇前文章。
- 不過,筆者還是預期,這應是終點了,因為前面提到的伏筆,筆者不樂觀看待(一次 u8g2 object 的生成或 sendbuffer 呼叫,皆有多條 write data,詳見下附扣。結論無法在既有規則下做到真正的 nonblocking)。先醬了。
強健度測試
- 本文雖然是 iic 主題,但一直圍繞在 U8g2 身上。事實上也很適恰,因它屬於大量資料傳輸的應用,可檢驗整個系統的強健度。很不幸的,如下附例(有修正一處 bug)及附圖,WiFi connection 很快就死了。即,只能不定地 http 傳輸一陣子,顯示也從不扣分。
- functionaliic speedup enabled 的前提下。若 disabled,則 WiFi 連正常啟用都做不到。可知 iic 的中斷造成相當大的負擔。
- 也看到了,IoT 部份的扣,會讓 timer 暫停?
- 更看到了,估在 1 秒內,只有約 100ms 是不跑 iic 的,即有 90% 資源全用來跑 iic,換言之,blocked waiting 行嗎?(事實上 speedup 是屬於 blocked waiting,不過在每次 iic stop 之後會跳出來到 main loop;異於 wire.h 之數次 iic stop 才會跳離並且顯示可能會比較不順)
20220319 改版測試
- 強迫症作祟,怎麼都停不住 XD
- 終於要改版了,
- 加入了動態物件,
- 當然,for u8g2 是不可能的,因為前述 limitation 是,一次呼叫 sendbuffer,就會有多次 write data,除非就是從 u8g2 上層的扣去修改,並且當前結論它並非接續地從某塊 buffer 的某位置接續地傳送資料而是從 buffer 頭開始,其表示我們非得間接複製及等待傳完不可。
- 目前測試到底 memory 的配置/釋放有沒有出錯。及要除錯當跑一陣子後,queue 中還有 dynamic objects 為何會停止傳送。
- 可肯定的是,當啟用另一支 station 作 http request 於此 display ap,此 ap 不久後就會 wdt reset。單獨 display ap 跑,尚無 wdt reset 現象/測到 650 秒。
- 所謂動態物件,即,原先舊版限制 iic objects 只能是全域的。現只要呼叫 QConfig(),即可有一次性的 iic object 於佇列中跑。故更簡化使用了。
關掉 speedup 記憶體配置是一致的,134/134,跑了七十多萬次配置與釋放,一直一致。
20:14:27.994 -> 4402
20:14:28.990 -> 4403
20:14:29.951 -> [PAUSE]
20:14:29.951 -> [733680 733546 134 134]
20:14:30.017 -> 4404
20:14:31.011 -> 4405
20:14:32.004 -> 4406
打開 speedup,則 34/116,不一致,但曾跑到八十幾萬筆系統還未崩潰故關於記憶體存取應是沒問題的。
最終會跑到例如 226080 226080 0 102 且停止動作便是此 bug 所在。正確是沒有停止的可能。
20:19:17.407 -> 131
20:19:18.401 -> 132
20:19:19.427 -> 133
20:19:19.825 -> [PAUSE]
20:19:19.825 -> [171792 171758 34 116]
20:19:20.421 -> 134
20:19:21.414 -> 135
20:19:22.408 -> 136
停止動作,bug。
20:25:08.617 -> 482
20:25:09.644 -> 483
20:25:09.843 -> [PAUSE]
20:25:09.843 -> [226080 226080 0 102]
20:25:10.641 -> 484
20:25:11.634 -> 485
現下最新的除錯結果就是,當 main loop 恰遇到 queue 內數量上界而暫停追加資料的同時,queue 內開始/一口氣消耗掉全部資料。
故 isr 成 idle,但 dynamic_obj_num 仍維持在原上界數字(問題所在)致整個停止動作。記憶體配置釋放是一致的為何 dynamic_obj_num 不為零?
bb++ 與 dynamic_obj_num-- 是同時執行,又 aa 與 bb 是一致的,故問題將會是出現在 dynamic_obj_num++,
那麼該邏輯到底有什麼問題呢?
20220321 改版測試之除錯結論
- 這是一次沒有抓到真正 bug 但卻有最終之很可能正確的推論與做法。
- 這次的 bug 單單就是 dynamic_obj_num 之行為非所預期。別懷疑,它在整個程式中,只出現一次 + +,只出現一次 – -,就沒了要除錯的就這麼單純!!!
- 當然,實際若單單只是筆者的盲點,拜請看倌指教 Orz
- 附件是當前的除錯程式碼,就不再多加修飾。關於除錯的歴程便自行推論。
- 藉由添加 aa, bb, ccc, ddd 變數,與 dynamic_obj_num 來觀察所有彼此間的一致性,其追蹤於物件的生成及移除。實務上不論觀察點置於何處,除 dynamic_obj_num(don 簡稱)外皆符合預期。筆者作結的 bug 對象,便是 don- -,而非前述的 don + +,並且,假定此一變數 bb+ +,也置於 don – – 同處,bb+ +,也不會有問題;唯獨 don- – 出問題。
- 結論一:因只有中斷與主程式即消費者與生産者,然而二者並非會同時執行,故只需一個 gate 變數便可順利控管。
- 因此,記憶體的配置,new,不會有問題,因為即開即用(存在),就算有可能會被中斷岔開或返回也都控管了;同理釋放,是於中斷中執行的,嗯,就是這裏出問題了!是的,也控管住了但就出問題了因為,真正的記憶體釋放,有可能在 delete/free 當下就做掉了,但也有可能,必須在離開中斷後回到 clib_core,才會做釋放。簡單講,唯有離開中斷,才會做 ~destructor(),或 free() 若當 user code 那當下 clib_core 沒做時;即謂,clib_core 有實作非同步記憶體配置管理。事實上筆者的這份 code,也同樣地實作了非同步記憶體釋放(即常見的程設規劃)。
- 問題便呈現在,當出了中斷外 clib_core 做了 free 的同時,中斷又發生了!
- 還是不會有問題,但若我們在解構子中做了某些非尋常的處理,則必要留意它的一致性存取!!!don 可能就是因為如此而失了一致性的。
- 結論二:正常情況下,中斷 v.s. clib_core::free v.s. don,仍不會有問題,因。。。它已超乎筆者認知了,所以看倌若知其所以然,敬請指教。因為我想 don + +,- – 皆是單行指令動作,而非如 if (don < 100) don+ +,就真必須注意一致性!!!然而,也因唯一一支變數 don 皆在 main code 及 中斷中的解構子中跑,因此我們採取的權衡解法,便是用某另一變數專職做 + + 的動作便無一致性問題了/有的,也只是延遲性問題。
- 結論三:同前,解構子中的處理,便須移至 user code 中做處理,斥或,解構子中不存在一致性的隱憂便無妨乃或加工成無妨!
- 結論四:故由以上,程式 bug 用了多個專職變數解了因為原先跑了幾十萬筆配置釋放皆停住了,這次已持續跑過了一千萬筆了。
- 最後,對了,看一下程式碼,don 不也移至 user code 中了嗎,為何還不一致?是的,敬請點破筆者的盲點 Orz
12:14:57.952 -> 7221
12:14:58.615 -> [PAUSE]
12:14:58.615 -> [9373104 9372992 112 3795]
12:14:58.615 -> [9373104 9372996 108]
12:14:58.946 -> 7222
第一千萬筆,終止。
12:31:53.663 -> 8233
12:31:53.729 -> [PAUSE]
12:31:53.729 -> [10000008 10000008 0 3956]
12:31:53.763 -> [10000008 10000008 0]
12:31:54.657 -> 8234
20220322 update
- 再追加修改過的範例如下,
- 完全使用 dynamic objects,
- 並且在先天的侷限下將 delay 控制得最適切了,
- 也將 cHwTimers 改為原 functionaliic 所使用的 native hardware timer1。
- 因此,發現原先的 dynamic_obj_num 的 bug 消失了,有可能是 cHwTimers 所影響到,當然也有可能是中斷頻率降低了才沒造成 bug;所以關於此 bug 可接著從此來開展釐清。
- 可說,此範例可作為動態顯示的 code base 了;因為我一直想定,display 的 iic 要用 gpio expander 來實現而不使用 esp8266 的 gpios。故才會一直執著在 functional iic v.s. u8g2。
- 此外,若不實現上一點,那麼轉從多個 devices 在同一組 bus 上著手,也是 functioniic 可發揮之處。
- 此範例之後會納入 functionaliic 範例。
20220325 update
- functionaliic 小改版
- 比較了 wire.h v.s. functionaliic,筆者失望了 XD 真的,要是 esp8266 的 interrupt payload 再低一點就好了!!!(差 640 倍)
- 如附影片,wire.h 快太多了,並且 IoT/WiFi 很順利跑扣。並不會像用了 functionaliic,WiFi 只跑一段時間便斷線異常。