Python可以這樣玩(14):廣告流水燈


先準備下面的實驗器件,如果還沒有這些東西,務必跑一趟光華商場,在新大樓以及八德路對面的地下室都買的到。

l   Arduino UNO 開發板一個
l   麵包板一個
l   Led 燈:6 個,最好是紅、黃、綠各兩個。
l   220Ω的電阻:6 個,我買的一組220Ω電阻是五個,少一個可以用 1KΩ 電阻代替,亮度會低一點,不會燒掉。
l   多彩麵包板實驗跳線:若干

單獨看下面的概要圖,其實接法很簡單,數位PIN 1-6 (下圖有小錯誤,請不要接 PIN 0,從 PIN 1開始接,我在連接 PIN 0 的時候上傳發生問題,應該是因為 PIN 0 同時有 RX的作用,會與上傳的READ動作衝突,因為上傳的動作對板子而言就是READ) 接出來六個電阻與LED,最後全部接地。



不過看到下面這張圖,可能就會亂了:




這就是我們要練習的地方,凡是如果沒有親自動手做,只是在平板上面滑手指,永遠不會進步。現在試著從PIN 1的正極開始,一條一條接到 PIN 6 (也要跳過PIN 0)。接線的時候,請將連接電腦的 USB 街頭拔開。

實驗原理

在生活中我們經常會看到一些由各種顏色的 LED 燈組成的廣告燈(例如台灣的檳榔攤),廣告燈上各個位置上的 LED 燈不斷的亮滅變化,就形成各種不同的效果。本節實驗就是利用 LED 燈程式設計模擬廣告燈的效果。

在程式中我們設置 LED 燈亮滅的次序和時間,這樣就可以組成不同的效果。我們提供幾個不同的樣式。

樣式一副程式:LED 首先從左邊的綠燈開始間隔 200ms 依次點亮六個 LED 燈,如上圖,接著從右邊的綠燈開始間隔 200ms 依次熄滅六個 LED (左右先別太在意,如果按照前面的接法,由於是反過來,會是右邊開始)

燈閃爍副程式:六個 LED 燈首先全部點亮,接著延時 200ms,最後六個 LED 燈全部熄滅,這個過程迴圈兩次就實現了閃爍的效果。

樣式二副程式:設置 k j 的值讓中間的兩個黃燈亮先亮,接著讓挨著兩個黃燈兩邊的紅燈亮,最後讓兩邊的綠燈亮;執行一遍後改發 k j 的值讓讓兩邊的綠燈先熄滅,接著兩邊的紅燈熄滅,最後中間的兩個黃燈熄滅。

樣式三副程式:設置 k j 的值,讓兩邊的綠燈亮 400ms 後再熄滅,
接著讓兩邊的紅燈亮 400ms 後再熄滅,最後讓中間的兩個黃燈亮 400ms 後再熄滅;執行一遍後改發 k j 的值讓兩個紅燈亮 400ms 後熄滅,接著讓兩邊的綠燈亮 400ms 後熄滅。

程式碼

程式碼如下:

int Led1 = 1;
int Led2 = 2;
int Led3 = 3;
int Led4 = 4;
int Led5 = 5;
int Led6 = 6;

void style_1(void)
{
  unsigned char j;
  for(j=1;j<=6;j++)
  {
    digitalWrite(j,HIGH);
    delay(200);
  }
  for(j=6;j>=1;j--)
  {
    digitalWrite(j,LOW);
    delay(200);
  }
}

void flash(void)
{  
  unsigned char j,k;
  for(k=0;k<=1;k++)
  {
    for(j=1;j<=6;j++)
      digitalWrite(j,HIGH);
    delay(200);
    for(j=1;j<=6;j++)
      digitalWrite(j,LOW);
    delay(200);
  }
}

void style_2(void)
{
  unsigned char j,k;
  k=1;
  for(j=3;j>=1;j--)
  {  
    digitalWrite(j,HIGH);
    digitalWrite(j+k,HIGH);
    delay(400);
    k +=2;
  }
  k=5;
  for(j=1;j<=3;j++)
  {
    digitalWrite(j,LOW);
    digitalWrite(j+k,LOW);
    delay(400);
    k -=2;
  }
}

void style_3(void)
{
  unsigned char j,k;
  k=5;
  for(j=1;j<=3;j++)
  {
    digitalWrite(j,HIGH);
    digitalWrite(j+k,HIGH);
    delay(400);
    digitalWrite(j,LOW);
    digitalWrite(j+k,LOW);
    k -=2;
  }
  k=3;
  for(j=2;j>=1;j--)
  {  
    digitalWrite(j,HIGH);
    digitalWrite(j+k,HIGH);
    delay(400);
    digitalWrite(j,LOW);
    digitalWrite(j+k,LOW);
    k +=2;
  }
}
void setup()
{
  unsigned char i;
  for(i=1;i<=6;i++)
    pinMode(i,OUTPUT);
}
void loop()
{  
  style_1();
  flash();
  style_2();
  flash();
  style_3();
  flash();
}

Python 控制

使用 Python 控制之前,我們要先把 Standard Firmata上傳到UNO上,前面說明過步驟,上傳完畢之後,燈就不亮了。

開啟IDLE,先行測試下面的程式片段:

import pyfirmata
import time

port = 'COM3'
board = pyfirmata.Arduino(port)

def style1():
    for i in range(1, 7):
        board.digital[i].write(1)
        time.sleep(0.2)
    for i in range(6, 0, -1):
        board.digital[i].write(0)
        time.sleep(0.2)

style1()

馬上就發生下面的錯誤,這回是 PIN 1 的問題:

RESTART: C:/Users/huskywang/Documents/GitHub/python/ArduinoPrograms/Play6LED.py
Traceback (most recent call last):
  File "C:/Users/huskywang/Documents/GitHub/python/ArduinoPrograms/Play6LED.py", line 15, in <module>
    style1()
  File "C:/Users/huskywang/Documents/GitHub/python/ArduinoPrograms/Play6LED.py", line 9, in style1
    board.digital[i].write(1)
  File "C:\Users\huskywang\AppData\Local\Programs\Python\Python36\lib\site-packages\pyfirmata\pyfirmata.py", line 526, in write
    raise IOError("{0} can not be used through Firmata".format(self))
OSError: Digital pin 1 can not be used through Firmata
>>> 

為了忠實傳真,我保留了所有我在coding 時候發生的錯誤,因為這些錯誤也一定會發生在其他人身上。仔細看一下手上的 UNO板,會發現 PIN 0 是與 RX 共用,所以我們在上傳時如果佔用,就會發生問題。PIN 1 則是與 TX 共用,在 Firmata 通訊協定中,這些特殊用途的 PIN 我們都要避開。因此請大家把線接到 PIN 2 – PIN 7 上面。在將程式改寫如下:

import pyfirmata
import time

port = 'COM3'
board = pyfirmata.Arduino(port)

def style1():
    for i in range(2, 8):
        board.digital[i].write(1)
        time.sleep(0.2)
    for i in range(7, 1, -1):
        board.digital[i].write(0)
        time.sleep(0.2)

while(True):
style1()

再加上while() 迴圈,這樣燈號就會一直循環了:


剩下的 style2, style3 以及 flash,就是這部分的隨堂練習,請自行完成,您可以將答案在我的部落格中留言。


現在我突然有個想法,就是想把著個六燈電路板改成八燈,並且代表 Do Re Mi Fa So La Si Do 八度音的燈號,作為彈奏鋼琴的音符指示燈。一般的情況,我們修改完電路之後,要重新寫C語言程式,重新編譯,重新上傳,之後才能展示新的功能,中間有任何錯誤,又必須重新編譯,重新上傳,一直在做這種反覆的動作。如果使用 Python 透過 Firmata 通訊協定,這些過程就免了,因為 Firmata 是標準通訊協定,上傳一次就好,Python 是直譯語言,寫完直接看結果,非常有效率。

我們從上一小節的程式碼直接修改,可以看到我們剛才新增的兩個燈也亮了,這當成我們的一個開機燈號:

import pyfirmata
import time

led1, led2, led3, led4, led5, led6, led7, led8 = 2, 3, 4, 5, 6, 7, 8, 9
port = 'COM3'
board = pyfirmata.Arduino(port)

def style1():
    for i in range(led1, led8 + 1):
        board.digital[i].write(1)
        time.sleep(0.2)
    for i in range(led8, led1 - 1, -1):
        board.digital[i].write(0)
        time.sleep(0.2)

for i in range(1):
    style1()

接著,我們寫一個 note() 函數,傳入 1 就亮Do燈號,2 就亮 Re燈號,以此類推,接著上面的 for 迴圈往下寫:

def note(x):
    global last
    for i in x:
        if last != 0:
            board.digital[last].write(0)
        if i != p:
            board.digital[i].write(1)
            time.sleep(0.4)
        else:
            time.sleep(0.4)
        board.digital[i].write(0)
        time.sleep(0.1)
        last = i
       
play = [led5, led3, led3, p, led4, led2, led2, p, led1, led2, led3,
 led4, led5, led5, led5, p]

for i in range(2):
    note(play)

我們利用 play 列表來存讓音符,note()函數來演奏每個音符,使用p來定義停頓一拍,這樣您可以看到一首小蜜蜂了。

如果我們利用音樂小節的重複特性,就可以用最簡潔的方式完成整首歌,完整程式碼如下:

import pyfirmata
import time

led1, led2, led3, led4, led5, led6, led7, led8, p = 2, 3, 4, 5, 6, 7, 8, 9, 10
port = 'COM3'
board = pyfirmata.Arduino(port)


def style1():
    for i in range(led1, led8 + 1):
        board.digital[i].write(1)
        time.sleep(0.2)
    for i in range(led8, led1 - 1, -1):
        board.digital[i].write(0)
        time.sleep(0.2)

for i in range(1):
    style1()

last = 0
def note(x):
    global last
    for i in x:
        if last != 0:
            board.digital[last].write(0)
        if i != p:
            board.digital[i].write(1)
            time.sleep(0.4)
        else:
            time.sleep(0.4)
        board.digital[i].write(0)
        time.sleep(0.1)
        last = i
       
play1 = [led5, led3, led3, p, led4, led2, led2, p]
play2 = [led1, led2, led3, led4, led5, led5, led5, p]
play3 = [led1, led3, led5, led5, led3, led3, led3, p]
play4 = [led2, led2, led2, led2, led2, led3, led4, p]
play5 = [led1, led3, led5, led5, led1, led1, led1, p]

note(play1)
note(play2)
note(play1)
note(play3)
note(play4)
note(list(map(lambda x:x+1,play4[:7:]))+[p])
note(play1)
note(play5)

請注意紅色的部分,我使用了 map, lambda, 切片等技巧,如果看不懂,請再回到列表介紹的部分重新看一次。

留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(11):數學繪圖

Python可以這樣玩(15):蜂鳴器與音樂