NTP 時鐘 – interrupt 中斷程式 & WiFi 的殺手

Interrupt 中斷程式應用

繼上次分享到在 8×8 LED Matrix 上面顯示我要的圖形 (一個方型的 O) 後,接下來的挑戰即是如何變換 LED 面板上的圖形。

Bird 在告訴我有 interrupt 這個功能之前,他問了我一個問題讓我印象非常深刻:

你知道為什麼馬力歐裡面,天空的雲、地上的怪物、還有馬力歐本人可以同步進行嗎?

我當下的直覺反應是,這不是一件很理所當然的事情嗎?如果不是一起動,難不成我在吃蘑菇長大成大馬力歐的時候,蘑菇跟馬力歐不是一起動的?答案很驚人,他們的確不是一起動的。

原因是這樣,CPU 一次只能處理一件事情,所以我們看到一起動的結果仍舊是因為視覺暫留的結果,馬力歐裡面的雲、蘑菇、怪物,還有馬力歐本人每個在動的東西都存在著時間差,只是這個時間差很小很小,小到人眼無法辨識 (可能只有 1 millisec),因而能產生看似多工的效果。

既然 CPU 無法多工,那如何做到看似多工的效果,迷惑眾人的眼睛? 這就是 interrupt 的價值。interrupt 就是那個可以達成看似多工效果的主要功臣,它的原理,以 LED 顯示不同圖形為例,原本在 loop 裡面,因為 LED 顯示是以掃描的概念進行的,所以基本上是不可能可以變換圖形,而 interrupt 的精髓就在於,把變換圖形的主要工作 (就是掃描) 拿到背後的機制做,而前面 (就是 loop) 可以隨心所欲的做別的事情,(只是每隔 n 個 millisec 會從 loop 跳去 interrupt 執行某個動作,當 n 很小的時候基本上是沒感覺的),因此達到看似多工的效果。

參考資料:interrupt 的 timer 觀念以及應用

在理解完 interrupt 的原理後,首先操作的是在 LED board 上做數字的變換 (0~9),當然先從簡單的 (兩個數字的變換 1 & 2) 開始著手,以下是部份程式碼:

extern “C"
{
#include “user_interface.h"
}
#define NUM 10
#define COL 4
#define ROW 6
os_timer_t myTimer;
const int col[COL] = {0,1,2,3};
const int row[ROW] = {4,5,12,13,14,15};
// three-dimensional array for pattern of number 0-9
int number[NUM][COL][ROW] =
{
// zero
{{1,1,1,1,1,0},
{1,0,0,0,1,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
// one
{{0,0,0,0,0,0},
{0,0,0,0,0,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
// two
{{1,0,1,1,1,0},
{1,0,1,0,1,0},
{1,1,1,0,1,0},
{0,0,0,0,0,0}},
// three
{{1,0,1,0,1,0},
{1,0,1,0,1,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
// four
{{1,1,1,0,0,0},
{0,0,1,0,0,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
// five
{{1,1,1,0,1,0},
{1,0,1,0,1,0},
{1,0,1,1,1,0},
{0,0,0,0,0,0}},
// six
{{1,1,1,1,1,0},
{1,0,1,0,1,0},
{1,0,1,1,1,0},
{0,0,0,0,0,0}},
// seven
{{1,0,0,0,0,0},
{1,0,0,0,0,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
// eight
{{1,1,1,1,1,0},
{1,0,1,0,1,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}},
//nine
{{1,1,1,0,0,0},
{1,0,1,0,0,0},
{1,1,1,1,1,0},
{0,0,0,0,0,0}}
};
int pixel[NUM][COL][ROW]; // the empty dimension array use in the loop
unsigned long changeNumber = 0; // the variable detected for each number
void timerCallback(void *pArg) // detect each col and row
{
static int x=0, y; // the variable for the pixel
// x represent column
// y represent row
if (x == 0)
{
digitalWrite(col[COL-1], LOW);
digitalWrite(col[x], HIGH);
for (y = 0; y <= 5; y++)
{
int light = pixel[changeNumber][x][y];
if (light == 1)
digitalWrite(row[y], LOW);
else
digitalWrite(row[y], HIGH);
}
x++;
}
else
{
digitalWrite(col[x-1], LOW);
digitalWrite(col[x], HIGH);
for (y = 0; y <= 5; y++)
{
int light = pixel[changeNumber][x][y];
if (light == 1)
digitalWrite(row[y], LOW);
else
digitalWrite(row[y], HIGH);
}
if (x == COL-1)
x = 0;
else
x++;
}
}
void user_init(void)
{
os_timer_setfn(&myTimer, timerCallback, NULL);
os_timer_arm(&myTimer, 1, true);
}
void setup()
{
Serial.begin(115200);
int i;
for (i=0;i<=3;i++)
{
pinMode(col[i], OUTPUT); // initialize pin mode as output
digitalWrite(col[i],LOW); // initialize pin stat, anode = LOW -> off
}
for (i=0;i<=5;i++)
{
pinMode(row[i], OUTPUT); // pin mode as output
digitalWrite(row[i],HIGH); // cathode = HIGH -> off
}
user_init(); //to spark the callback function
}
void loop()
{
int i, k;
for (i = 0; i < COL; i++) // show something in the LED board
{
for (k = 0; k < ROW; k++)
{
pixel[changeNumber][i][k] = number[changeNumber][i][k]; // let pixel work on the the number which depends “changeNumber"
}
}
delay(500); // each number showing on the LED board will last for 500 millisec
if (changeNumber == NUM-1)
changeNumber = 0;
else
changeNumber++;
}

以上的程式碼只截取部份,在這裡,我是用事先設計好的三維陣列 number [][][] (圖形 0-9),一個全域變數 changeNumber 和一個空的三維陣列 pixel[][][],讓在 loop (上文所謂的“前面”) 裡面,從 changeNumber == 0 開始 (即圖形數字 0),餵給空個三維陣列 pixel [][][],而 interrupt 的 callback function 會每隔 1 個 miilisec 把主控權從 loop 裡拉走 (拉去 callback function 執行掃描的動作),在 callback function 裡進行的掃描就像上一篇 NTP 時鐘 – SSD1306 & 控制 8 x 8 LED Matrix 所提到的,是從第一個 column 開始掃,掃到最後一個 column,然後一直 repeat 再 repeat,所以說當 changeNumber 改變時,loop 裡面的的 number [][][] 也會跟著改變,變成下一個要顯示的數字,並且在餵給 pixel[][][],讓 callback function 再去做掃描,最後顯示在 LED board 上頭。

其實這段程式碼我試錯的許久,原因是光是掃描部份 (即 callback function) 要做到上面這個簡化的版本,我就吃足了苦頭,不是用 if statement 產生的邏輯漏洞,就是程式碼太冗長,如果可以使用 switch 做條件式,那就不要用多層 if statement 造成不必要的麻煩 (Bird 的小提醒),不只是掃瞄的 callback function,loop 裡面也是簡化再簡化,我記得我當初一開始寫的時候,很直覺的用了一拖拉機的 for loop,把整個程式碼拉得非常長,直到我將 number 改成三維陣列才解決程式碼過於冗長的問題。

分享到這,已經把 interrupt 主要的功能以及應用的方面交代過一遍,接著 Bird 要我進行的下一步:將變換圖形,改成使用者可以自己操控的形式。也就是說,LED board 上顯示出來的不再只是每 delay 500 millisecs 就會自己變換圖形,而是我想要它是哪個數字的時候,他就會乖乖的顯示在 LED board 上面。我要將這個東西連上網路,並且設計一個簡單的網頁在 browser 上有 textbox 可以讓我輸入我想要顯示在 LED board 上的數字。

所以第一步我要連上網路和開啟 ESP8266 的 server,當然就是我們熟悉的 WiFi。殊不知這一連,連出了個大秘密…….

Interrupt 和 WiFi 會打架

把程式碼寫好,一個 block 一個 block 標示清楚,畢竟程式碼開始有點長度了,設定好 WiFi 的參數以及連線的 function,打開 ESP8266 的 web server,一切就緒後,按下 run,直接 crash 掉……,console 裡面根本沒有 IP 位置的回傳值,表示我根本沒有連上網路。再不斷地懷疑是否是自己程式碼的問題後,經過和 Bird 的討論,他懷疑可能是 interrupt 的功能導致 WiFi 無法連線,果然,不是只有我遇到這個問題,interrupt event 在執行時如果你 call WiFi 相關的 function,會使得你的 ESP8266 掛掉,而處理的方法 – 站在巨人的肩膀上,將 interrupt 在 call WiFi function 前先 disable,等到真的連上網路後,再 re-enable interrupt 的功能。

參考資料:Crashes and libraries that use interrupts

順利連上 WiFi 後,接著就是在 browser 上的網頁給指令,跟 ESP8266 的 server 做溝通,達成我要的結果,而這部分我將在下一篇網誌分享出來,包括一些關於回傳的 IP 值的問題。

1 thoughts on “NTP 時鐘 – interrupt 中斷程式 & WiFi 的殺手

  1. 引用通告: NTP 時鐘 – browser 網頁控制 LED board – 自造者萊恩

發表留言