全能電路設計實戰

2017年10月17日 星期二

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 的板子了。


沒有留言 :

張貼留言