2017年10月17日 星期二

Python 與 Modbus RTU 通訊協定 -- minimalmodbus


=====
前言:
=====

大部份的人都沒有聽過RS485和modbus,但是都聽過RS232這個東西。因為在以前稍舊款的電腦都有附有這個接頭,外觀長得很像D-sub 但是上面只有兩排9針的。




=======
實驗目的
=======


=============
使用材料及設備

=============



=======
原理介紹

=======


========
程式說明

========



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


2017年10月3日 星期二

Python 處理Excel (.csv) 欄位計算使用Numpy



Python 處理Excel (.csv) 欄位計算使用Numpy



Numpy是Python用來科學計算的一個非常重要的函式庫,numpy主要用來處理一些矩陣對象,可以說numpy讓Python有了Matlab功能。

import numpy as np

p.s  使用numpy 要先安裝此module
   pip3 install numpy

產生5x7陣列
a = np.arange(35).reshape(5,7)
print(a)


[[ 0  1  2  3  4  5  6]
 [ 7  8  9 10 11 12 13]
 [14 15 16 17 18 19 20]
 [21 22 23 24 25 26 27]
 [28 29 30 31 32 33 34]]


取出特定cell的值
print(a[2,4])


18

取出特定列
print(a[0:3,:])  #取出某些列中的全部欄位 從第0列~第3列



[[ 0  1  2  3  4  5  6]
 [ 7  8  9 10 11 12 13]
 [14 15 16 17 18 19 20]]


取出特定列、欄
print(a[0:3,0:4]) 
# [start:end:step] 用來連續抓取一塊矩陣範圍, 可以使用step參數 (step=1,2,3...) , 預設step=1 (step=1可以不寫)


[[ 0  1  2  3]
 [ 7  8  9 10]
 [14 15 16 17]]

print(a[1:5:2,::3])   #row : 1,3 ; column: no start, so start(0):no end:step 3==> 0,3,6,....
[[ 7 10 13]
 [21 24 27]]


取出特定欄
print(a[:,1])     #start 不寫表示從0開始, end不寫表示算到最後  [ 1  8 15 22 29]



[ 1  8 15 22 29]

取出多個特定欄位
print(a[:,[1,3,4]])  #取出所有列中的某些特定欄, 第1欄, 第3欄,第4欄


[[ 1  3  4]
 [ 8 10 11]
 [15 17 18]
 [22 24 25]
 [29 31 32]]


欄位相乘
f1,f2=1,2
y1=a[:,f1]
y2=a[:,f2]
y=y1*y2
print(y)  


[  2  72 240 506 870]

找最大值 print(np.max(y))


870

找最小值

print(np.min(y))


2

陣列元素值加總
print(np.sum(y))


1690

陣列元素四捨五入
np.around([0.55, 0.65, 0.05], decimals=1)

array([0.6, 0.6, 0.0])

[python] numpy meshgrid



numpy


numpy
import numpy as np
import matplotlib.pyplot as plt

#plt.axis([0, 6, 0, 20])

xvalues = np.array([1,2,3,4]);
yvalues = np.array([5,6,7]);
xx, yy = np.meshgrid(xvalues, yvalues)
print(xx)
print(yy)
plt.plot(xx, yy, marker='.', color='k', linestyle='none')
plt.show()


[[1 2 3 4]
 [1 2 3 4]
 [1 2 3 4]]
[[5 5 5 5]
 [6 6 6 6]
 [7 7 7 7]]










2017年9月26日 星期二

Python 函式使用 (四)


Pyton 函數呼叫

函數定義

def function_name (a,b):
...  
...  
...  return xxxxx


全域變數vs 區域變數


  1. 全域變數: 變數宣告在任何函數外,即為全域變數
  2. 區域變數: 定義在函數內部的變數, 非def 內無法存取此區域變數
  3. Python 函數可以直接存取(直接用或操作)全域變數 (不必傳參數)
  4. 小心: 若宣告區域變數與全域變數的名稱相同, 則以區域變數為主, 若函數內要使用外部的全域變數, 必須用global 關鍵字, 表示其參考外部的全域變數,而非重新定義的區域變數


# 全域變數vs 區域變數

x1=[2,4,6]
a=5

def func1():
    x1[1]=100   #存取會更改x1

def func2(): 
    x1=[1,2,3]    #初值設定,宣告變數x1 (和global x1相同, 故此為Local Variable )
    print(x1)    #[1, 2, 3]
    
def func3():
    a=10     #屬初值設定而非更改變數,因為是宣告, 則變數a為Local (雖然和global a相同
    print(a)

    
def func4():
    global a
    a=10      #初值設定為local, 但global明確定義a是global
    print(a)  #10

    
def func5():
    global x1
    x1=[1,2,3]    #初值設定:x1是local

 
func1()
print(x1)   #[2, 100, 6]
func2()
print(x1)  #[2, 100, 6]
func3()
print(a)   #5
func4()
print(a)  10
func5()
print(x1)  [1, 2, 3]


lambda Function
 
Python提供了一個簡易的function define:lambda,用完即丟,不著痕跡。讓你實作出很簡單的function (只處理一個運算式)


語法:
ambda argument_list: expression 

def func(x, y, z):
    return x + y + z

func2 = lambda x,y,z : x+y+z

print(func(1,2,3))       
print(func2(1,2,3))    


map 
Function

map() is a function which takes two arguments: 
r = map(func, seq)

The first argument func is the name of a function and the second a sequence (e.g. a list) seqmap() applies the function func to all the elements of the sequence seq. Before Python3, map() used to return a list, where each element of the result list was the result of the function func applied on the corresponding element of the list or tuple "seq". With Python 3, map() returns an iterator. 
my_list = [1, 2, 3]
ans=list(map( lambda i: i * i, my_list ))  
print(ans)  


[1, 4, 9]

不定個數參數函數用法


#不定個數參數 * (tuple)


def hello(*names):
    for n in names
        print("Hello, %s."%n)

names_tuple=("Tom","Peter","Bob","Rain")  
hello(*names_tuple)
hello("Tom","Peter","Bob","Rain")



#不定個數參數 ** (dict)

def hello(**names):
    for n in names:
        print("Hello %s, you're %d years old"%(n,names[n]))

names_dict={'John':25, 'Tom':20, 'Bob':33, 'Tony':18}
hello(**names_dict)
hello(John=25, Tom=20, Bob=33, Tony=18)



















2017年9月18日 星期一

SoC FPGA 嵌入式系統晶片?


Why SoC FPGA? 

目前嵌入式系統晶片開發人員必須面對激烈的市場挑戰,被要求更高的單位功耗性能/運算效能、更低的時延以及更短的開發週期來滿足各種的市場需求,因此基於FPGA的SoC已成為最流行且可行的解決方案。簡單來說,就是在FPGA可程式邏輯晶片嵌入了一個「硬核」處理器系統-- SoC (包含了ARM處理器、記憶體控制器、I/O週邊),在Intel (Altera)把這個SoC稱作是HPS (Hard Processor System) 。

在SoC FPGA 架構下可以彈性的設計,單純使用ARM SoC、僅單獨使用FPGA或兩者一起使用。當兩者一起用時, FPGA可以作為ARM週邊的角色,針對大量且須經複雜運算的資料,如數據資料、影像訊號、聲音訊號等,可利用FPGA 硬體並行運算能力設計演算法追求最佳效能。因此,利用SoC FPGA 架構所設計的嵌入式平台,不僅可以突顯了產品優勢,在價格和性能上都可以達到最優,更重要的是產品能夠及時上市。

HPS是屬於hard core 方式, 也可以選擇使用 soft core 的方式. 就是用Intel 自己的CPU--NiosII, 但用Soft core的方式, 就會消秏到原本FPGA的邏輯匣。


HPS 和 FPGA 有自己的Bus系統, HPS 的ARM 是 AXI Bus , FPGA 是 Avalon Bus, , 故需要設計一個Bridge 讓兩邊系統能夠溝通。 





Intel SoC FPGA 開發板 ( 內含Dual Core ARM Cortex-A9@800MHz)

DE10-Nano Kit





更多FPGA的應用領域: 

AI人工智慧的機器學習、5G傳輸速度提升到10Gbps、無人駕駛汽車網路延遲時間需要小於1ms, 這些都已無法再仰賴CPU計算來達成了為了獲得更高的運算效能與功耗、更低的延遲,以及用更短的開發週期, FPGA 將扮演著關鍵性的角色!


資料參考:

  1. http://www.ittraining.com.tw/ittraining/index.php/course/hardware/fpga
  2. https://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=Taiwan&No=1047