2015年10月29日 星期四

Python 操控 S4A 透過通訊協定來下手

Python 操控 S4A 透過通訊協定來下手

S4A Project 是一個有趣的專案,讓Scratch 也可以操作Arduino。 要達到這樣的功能,Arduino需燒入S4A的官方韌體。
在這邊要說的是,其實不用 scratch 也可以直接操作帶有 S4A 韌體的板子,只要搞清楚其中的連線是如何進行的就可以。這邊用 Python 的程式碼來控制 S4A 的板子。

通訊協定封包格式

其實 S4A 的板子在連線之後是透過 UART 介面來收發封包進行連線和操作的。所以我們可以使用邏輯分析儀來進行UART封包的分析:

取樣之後會發現 S4A 板每 16 ms 就發送一個封包給 scratch,而 scratch 會每85ms就回一個封包給 S4A 板子。如下圖:

我們先將S4A板子傳給 scratch 的封包放大來看,可以看到裡面的傳遞數值的情形:

可以知道每個封包帶有 16 bytes,而S4A封包的設計上是用一個 high byte 和 一個low byte 共兩個 byte 組成一個 Channel 的資訊。所以可以知道,S4A 每16ms送一次的封包之中就帶有8個channel的資訊,這就符合 scratch 上在連線時所看到的即時channel資訊(analog x 6 和 digit x 2)。也就是 s4a每16ms就會更新一次所有 輸入pin腳(共8個)的資訊。
將目光轉移到 scratch 傳給 s4a 板子的封包。

可以知道每個封包帶有 20 bytes,所以 S4A 每 85ms 送一次的封包之中就帶有10個channel的資訊,也就是 scratch 每 85ms 就要更新一次所有 輸出 pin腳(共20個)的資訊。而這10個channel所代表的輸出資訊表如下。表格中有所有輸出入channel的定義和封包格式。其中我們將 motoduino所使用到用來控制車輪的輸出項標示出來。

不過光看表格其實很難理解所,每2byte所組成的 channel 資訊的格式是怎麼樣的,所以下我們整理了一下,以方便理解。

如上圖所示,每個 channel 的資訊是由兩個值: Channel ID 和 Value 所組成。
Channel ID 用來表示指定的輸出入 pin 腳號碼,以 s4a 板子傳遞給 scratch 來說,channel ID 為 "2" ,二進位表示 "0010",此二進位值要填進去上圖的4格紅色格子中。
Value 用來表示指定 pin 腳的數值,如果是數位輸出入其值為1或0,如果是pwm輸出時其值為0~255,如果是類比輸入時其值為0~1023。將數值轉換為二進位時最多會使用到10個bit,將這10bit分成前三格 後七格 的形式分別填入 high-byte 的右邊三格綠色格子和low-byte的右邊7個格子。
而 high byte 的最左邊的一格格子會固字填入 "1" ,low byte 的最左邊格子會固定填入 "0",這樣的話,我們在解析封包的時候才不會把 high-byte 和 low-byte 搞錯。

用Python 程式碼來做編碼和解碼的動作

知道這樣的規則之後,我們就可以用一小段程式碼寫出編解碼的函示:
def pack_to_data(data):
    dev_id = (data[0] & 0b01111000) >> 3
    dev_val = ((data[0] & 0b00000111 ) << 7) | (data[1] & 0b01111111)
    return (dev_id, dev_val)

def data_to_pack(dev_id,dev_val):
    data[0] = 0b10000000|( dev_id<<3)|((dev_val>>7)&0b00000111)
    data[1] = (0b0001111111&dev_val)
    return bytes([data[0],data[1]])
第一段程式碼是用來解碼的。參數 data 是一個 2byte 的s4a封包。最後會回傳dev_id(就是channel ID)和 dev_val (就是value)。
而第二段程式碼是用來解碼的。代入參數dev_id (channel ID)和 dev_val (Value)之後就會回傳s4a 形式的封包。

Python 程式碼和s4a板子溝通

在Linux 系統上,arduino 用USB連接之後,會出現 "/dev/ttyUSB0" 這個裝置檔。這表示說,arduino 是用標準的 serial port 的形式來連線。所以我們用下面的一段python程式碼來建立起連線:
#!/usr/bin/env python3

import serial
from time import sleep
import sys
import threading


#ser = serial.Serial('/dev/ttyUSB0',9600,8,serial.PARITY_NONE,serial.STOPBITS_ONE)




class s4a_slave(object):

    def __init__(self,port):
        self.ser = serial.Serial(port,38400,8,'N',1)
        self.pin_outputs = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # dev id : 4 ~ 13
        self.pin_inputs = [0,0,0,0,0,0,0,0,0,0,0] # analog 0 ~ 5
        self.count = 0

    def main_loop(self):
        while(True):
            sleep(0.001)
            data = self.ser.read(2)
            if data[0] & 0b10000000:
                dev_id, dev_val = self.pack_to_data(data)
                self.pin_inputs[dev_id] = dev_val
            else:
                data = self.ser.read()
            self.count += 1
            if( self.count >= 64):
                self.count = 0
                for i in range(4,14):
                    data = self.data_to_pack( i , self.pin_outputs[i] )
                    self.ser.write(data)

    def pack_to_data(self,data):
        dev_id = (data[0] & 0b01111000) >> 3
        dev_val = ((data[0] & 0b00000111 ) << 7) | (data[1] & 0b01111111)
        return (dev_id, dev_val)

    def data_to_pack(self,dev_id,dev_val):
        data = [0,0]
        data[0] = 0b10000000 | ((dev_id & 0b00001111) << 3) | ( ( dev_val >> 7 ) & 0b00000111 )
        data[1] = ( 0b0001111111 & dev_val )
        return bytes([data[0],data[1]])

    def start(self):
        self.th = threading.Thread(target = self.main_loop , args=())
        self.th.start()

    def set_dev(self,dev_id,dev_val):
        if((dev_val >= 0) and (dev_val < 1024)):
            self.pin_outputs[dev_id] = int(dev_val)



if __name__=="__main__":
    s4a = s4a_slave('/dev/ttyUSB0')
    s4a.start()
    while(True):
        cmd = input().split()
        s4a.set_dev(int(cmd[0]),int(cmd[1]))
程式碼執行之後,畫面會等待使用者輸入如下的指令:
5 200 (按ENTER)
表示將channel ID 5 的 Value 設定為 200(channel ID 5 的 pin 腳被s4a定義為 pwm 輸出)。 如果正確連線的話,s4a板子的 pin 5 就會開始輸出 pwm 訊號 (duty cycle 為 200/255)。
進行到這裡,其實我們就可以不用再管scratch 而直接使用python來控制 s4a 的板子了。

沒有留言 :

張貼留言