Xcom串口語言

Xcom 串口語言

Xcom串口語言是一種面向過程的計算機語言,用於串口協議指令的封裝和解析。簡單靈活,支持任意格式的指令和數學表達式運算。在實際運用中通過這種語言可以快速從串口指令序列中提取任意需要的數據並解析出結果。
Xcom 是用c++語言實現的翻譯型語言,支持多種平臺。

1. 基本語法

1.1 數字和運算符

1.1.1 常量和編碼

Xcom 語言的常量可以是數和指令序列。數最大可以支持64字節的整型和雙精度浮點數,最小可以是單個字節的數。
編碼格式只有10進制和16進制,不提供二進制或八進制表示。16進制後面加個H後綴或0x前綴,例如0x13和13H是相等的。 串口最常用的編碼有BCD編碼,餘3編碼,IEEE754 規範浮點數等,這些提供了內置函數轉換。

1.1.2 運算符

下面是基本的運算符。基本滿足各種需求。

  • () :小括號
    小括號與函數組成了函數調用,括號裏面爲傳入參數列表,括號裏如果是表達式將優先運算,這跟其他語言基本都一樣。
  • []: 中括號
    中括號是一種運算符,用於串口指令合成。比如Modbus 指令 01 03 00 00 00 02 C4 0B,用Xcom 語言表示就是 [01H 03H 00H 00H 00H 02H C4H 0BH], 後面跟着H表示16進制,如果沒有H則爲10進制數。
  • {}:大括號
    用於定義函數數組或表達式數組。 比如def send[2]:{[01H 03H],[02H 03]}, 定義了兩個函數。一般用於需要連續發送多條串口指令的情況下,可以把過程變得很簡單。
  • $: 函數或變量標誌。
    在表達式中引用函數或變量需要用’$'作前綴。比如$sum。如果引用參數則$1,$2 等分別表示傳入的第一個,第二個參數
  • &: 函數或變量引用。
    這種運算符提供了一種可以將函數或變量的地址作爲參數傳遞。

下面所有運算可以是數與數,也可以是指令序列與指令序列,指令序列和數(數放後面)。凡是涉及指令序列的運算字節之間不產生進位。這也符合串口特點。

  • +: 加法運算。
  • -:減法運算
  • *: 乘法運算
  • /: 除法運算
  • &: 與
  • |: 或
  • !: 非

上面不包括 ‘=’ 運算符,可以用$assign(&v,03H) 代替,等價於 v=03H
涉及左移右移運算,目前只能用除法和乘法代替。例如左移3位,相當於乘以8,左移則相當於除以8.

1.2 函數定義

1.2.1 def 關鍵字和函數定義

def 是函數定義的最基本關鍵字。所有函數的定義都要以def開始,基本格式爲:
def 函數名:表達式
所有的函數都是由表達式構成,可以包含多個表達式。
函數名和表達式之間用冒號隔開。函數名的定義規則和其他語言一樣,只能保護字母,數字,下劃線,且不能以數字開頭。例如func_max4,就是合格的,5func_min 就是不合格的。表達式可以是任意數學表達式的組合。後面會詳細說明。

下面定義的是累加求和校驗函數:
def chksum:$sum($1)%256

定義的是名稱爲chksum 的函數,它需要一個傳入參數$1,$sum 是內置函數,用於對$1 求和運算。很顯然$1 應該是一個指令序列,否則$sum 求的結果就是自身。這個函數的功能是對一個指令序列累加求和,然後和256求餘運算。這就是串口協議Mbus裏最常用的累加求和校驗運算。

1.2.2 內置變量

內置變量只有3個,除$pos是隱含變量 外,其他都是由用戶設置的。

  • $addr: 設備地址
    設備地址如果是bcd編碼,後綴加B即可。例如20190104B, 等價於 [04H 01H 19H 20H], 十六進制地址可以寫成 0925H,表示[25H 09H],沒有後綴則按十進制處理。
  • $pos: 當前位置
    只有在[]表達式內部纔有效。 例如:
    [68H 01H 03H $3 $chk($pos) 16H]
    如果$3 是4個字節,那麼$pos等於7,從0開始計算。
  • $len: 寄存器字節
    表示寄存器長度或個數,現在市場上大多數寄存器都是每個寄存器兩個字節,因此寄存器長度如果是2,字節長度就是4。

從以上可見Xcom語言是既能方便專業人員又給普通用戶是提供了極大的方便的,專業人員只需要寫好Xcom語句,可能就兩三個表達式,普通用戶只需要設置三四個變量,基本就能實現數據提取。具體看後面實際案例

下面是我們的實際案例:

# 能量表數據採集協議
def chksum: $sum($1)%256

def send: [68H $1 $addr  $reverse($2) 01H 03H $3 $fill(1,$chksum($group(0,$pos))) 16H]
def recv: [68H $ditch(1) $ditch(7) 81H $ditch(1,$assign(&dl,$0-3)) $3 $data($ditch($dl)) $ditch(1) 16H]

#--------------------下面是用戶要求的能量表採集的7個數據點位------------------------------
#機械式熱量表
積累冷量
寄存器字節:4
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(0,$len)))
公式係數:100

積累熱量
寄存器字節:4
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(5,$len)))
公式係數:100

功率
寄存器字節:4
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(10,$len)))
公式係數:100

瞬時流速
寄存器字節:4
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(15,$len)))
公式係數:100

累積流量
寄存器字節:4
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(20,$len)))
公式係數:100

供水溫度
寄存器字節:3
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(25,$len)))
公式係數:100

回水溫度
寄存器字節:3
數據域:20H;11H 11H 00H;1FH 90H 01H
數據表達式:$bcd($reverse($data(28,$len)))
公式係數:100

1.2.3 內置函數

內置函數是Xcom 語言最重要的一個部分。它幫助我們完成了許多串口中最基本的運算。比如指令序列求和,將指令序列解析成BCD碼,整數,IEEE 754規範浮點數等。

下面所有內置函數在說明時使用的$1,$2 等表示傳入的第一個第二個參數,具體講解時不再重複。

  • $goup:指令序列捕捉,只能用於[] 表達式內部
    如果兩個整型參數,表示從$1 開始的連續 $2 個字節重新捕捉。
    如果一個整型參數,表示從0 開始的連續 $1 個字節重新捕捉。
  • $assgn: 賦值運算
    兩個參數,第一個參數必須是& 開頭的變量,第二個參數可以是數或指令序列
  • $cmp: 比較是否相等
    兩個參數。比較$1 和 $2 是否相等。相等返回1,否則返回0。 這種比較只是大小的比較,跟具體類型無關。比較特別的是,指令序列[03H] 和數字03H 是相等的,但是[03H 01H] 和 0301H 不相等,因爲指令序列轉換成數字還要分大端和小端模式。
  • $chk: 校驗確認
    跟$cmp 處理基本相同.不同的是$chk 專門用於校驗確認,並記錄校驗結果,影響整個指令序列的解析結果。
  • $reverse: 反轉指令序列
    一個參數,必須是指令序列
  • $crc: CRC 校驗碼計算
    共五個參數,crc 16位MODBUS校驗碼計算,$crc($1,0x8005,0xffff,0x0000,0), 大多數情況下,這個函數使用記住就行,$1 是待計算的指令序列,最後一個0,表示高字節在前,低字節在後,如果是1,則反過來。目前基本所有modbus 協議的CRC16 計算用這個都支持,也支持8位和32位的。主要是有後面四個參數決定。下表是常用校驗算法說明:
算法 $2:wcPoly $3:wCRCin $4: wXor $5:大端0小端1
CRC16_CCIT 0x1021 0x0000 0x0000 0
CRC16_CCIT_FALSE 0x1021 0xffff 0x0000 1
CRC16_XMODEM 0x1021 0x0000 0x0000 1
CRC16_X25 0x1021 0xFFFF 0xFFFF 0
CRC16_MODBUS 0x8005 0xFFFF 0x0000 0
CRC16_IBM 0x8005 0x0000 0x0000 0
CRC16_MAXIM 0x8005 0x0000 0xFFFF 0
CRC16_USB 0x8005 0xFFFF 0xFFFF 0

MODBUS 協議一般使用的就是CRC16_MODBUS,即$crc($1,0x8005,0xffff,0x0000,0)

  • $eram: 涉及加密的crc modbus校驗
  • $itom: 將整數轉化成指令序列
    兩個參數。$1 爲轉化的指令長度,1~4;第二個參數$2 爲被轉化的整數。高字節在前低字節在後。
  • $bit: 位與運算
    共三個參數,後面兩個參數如果不用默認都是0。$1 是指令序列,$2 是第幾個字節,從0開始。$3 是對第幾位進行與運算。例如:$bit([0FH 09H],0,3), 與 0x0F&(0x01<<3)等價
  • $rand: 產生隨機序列
    一個參數,$1 表示產生的隨機序列長度。

下面的函數參數設置一樣。有三種參數模式:
如果只有一個參數,必須是指令序列,則是對整個指令序列進行這種運算。
如果有兩個參數,第一個參數爲指令序列,第二個參數爲長度。即表示從第0個字節開始的$2 個字節進行運算
如果有三個參數,第一個依然爲指令序列,第二個爲起始索引,從0開始;第三個參數爲長度。

  • $sum: 指令序列求和。

例如:

def chksum: $sum($1)%256
def send: [68H $1 $addr  $reverse($2) 01H 03H $3 $fill(1,$chksum($group(0,$pos))) 16H]
def recv: [68H $ditch(1) $ditch(7) 81H $ditch(1,$assign(&dl,$0-3)) $3 $data($ditch($dl)) $ditch(1) 16H]

上面定義了三個函數,第一個定義的是名稱爲chksum 的函數,它需要一個參數,即$1,$sum 是內置函數,用於對$1 求和運算。很顯然$1 應該是一個指令序列,否則$sum 求的結果就是自身。這個函數的功能是對一個指令序列累加求和,然後和256求餘運算。實質上這就是串口協議Mbus裏最常用的累加求和校驗運算。
第二個函數send

前面兩行def 開頭的表示定義函數。使用時可以用冒號前面的command0 代表冒號後面的指令。
第三行的’#’ 表示這一行是註釋。後面分別註釋對應各自的數據項,ID,名稱、發送指令、接收指令、表示、單位.
第四行開始爲數據指令,數據項之間用空格隔開。

  • ID: 爲了統一定義了不同數據指令的ID,設備地址的指令ID爲0000,上面的水錶計量數據的ID是0101,高位的01 表示水錶,02表示電錶,03表示能量表,地位的01是水錶計量數據。
  • 名稱: 數據指令的名稱,可以自己定義,儘量統一。
  • 發送指令 : 是指獲取這條數據需要發送給儀表設備的指令
  • 接收指令: 是指獲取這條數據接收到的指令。上面爲方便理解寫的是靜態指令,實際中比較複雜。具體定義參考後面函數說明。
  • 表示: 是指這條數據實際表示。目前默認都寫1。
  • 單位: 這條數據的單位,地址和控制指令沒有單位可以寫none,也可以不寫。

但是僅定義數據項,只能使用靜態的指令,這樣無法在程序中獲取數據,因此必須定義許多關鍵字和函數來提取其中的有效數據,達到一種協議只需要一個配置文件就可以解決所有問題的目的。

1.2 常量

主要包括16進制數字和10進制數字。16進制數字用0x 開頭或H結尾,比如0x86 和 86H 都表示16進制的86。 如果是10進制,直接寫成數字即可。10進制可以使用小數,16進制暫時不支持小數。

1.3 運算符號

  • []
    []表示將中間的數字合成指令,比如:
    [68H 11H 68H 76H 00H 04H 00H 33H 78H 01H 03H 1fH 90H 01H BAH 16H] 就是一條指令。
  • $
    表示後面是函數、變量或參數。例如:
    def func: ($1*$1 )%$2
    上面是定義了一個函數func,$1、$2 表示func的第一和第二個參數。在使用func時 需要這樣: $func(16,256),這樣$1 就是16, $2就是256代入表達式進行運算。
  • &
    與$ 相對,它後面跟的一般是函數或變量。但不立馬進行計算,而是整個的作爲參數。比如:
def acount: $1%256
$data(&acount,255)

其中data 是關鍵字,它表示把255進行某種運算得出一個結果。這裏傳入acount 表示把255 作爲acount的參數進行運算。
而如果前面是$,必須這樣調用 $acount(255) 得到的結果是一樣的。

  • ()
    參數列表,比如:$func(16,256),參數之間用空格或逗號隔開。或者是一個表達式需要優先計算。比如 (2+3)*4

  • +,-,*,/,%
    分別是加減乘除,模運算。

1.4 函數

1.4.1 函數定義和使用

通過上面數據指令可以知道,僅僅靠數據指令有很多東西無法解決。比如設備地址、校驗碼、接收指令時數據的長度、數據內容都是不確定的,必須寫成動態的指令格式。而且考慮到一種型號或同一種協議的設備只需要一個指令配置文件,寫成靜態指令會導致每個設備需要一個配置指令,會增加工作量。因此必須定義函數。
函數格式 def func: 表達式
函數調用 $func(參數1,參數2,…)
例如:

def  func: $bcd(($1)%$2  
def command0:[68H 11H $1 33H 78H 01H 07H $2 $func($ditch(4),256)  01H  $sum($group(0,$pos)) 16H]
$command0($addr,[1fH 90H 01H])

這個例子看起來比較複雜,但是瞭解了各個運算符號和內置函數的定義也並不難理解。
**$command0($addr,[1fH 90H 01H]) ** 這是調用了command0 函數,有兩個參數分別是$addr 和 [1fH 90H 01H] ,代入command0中的 $1 $2 ,$addr 是儀表地址,這由系統自動補充。
再來看$command0的定義。 $func 在上面已經定義,它是把第一個參數轉化成bcd碼與第二個參數求餘,$ditch(4) 是關鍵字不是函數,後面會講,$ditch(4)表示把接收到的指令從當前位置挖出4個字節作爲$func的第一個參數。

後面還有sum,group pos 都是內置函數或變量。

1.4.2 內置函數

函數可以是用def 自定義的函數,也可以使用內置的函數。這些函數目前主要有以下幾個:

  • reverse: 反轉指令順序,比如$reverse([01H 90H]) 得到的是[90H 01H],一般指令都是高位在前低位在後,但有時候可能是地位在前高位在後,這時候可以使用reverse
  • assign: 賦值運算 。 比如$assign(&len,5),表示把5賦值給一個變量len。第一個參數必須是&接的變量。assign 也起到了定義變量的作用。例如:
    def command: [16H $addr 78H 83H $assign(&length,$ditch(1)) $data($length,&sum) $ditch(2)]
    這裏面 $assign 裏定義了length的值,在$data 裏就使用了length 。但要注意定義必須在使用之前。

以下函數有一個或三個參數,第一個參數是多字節指令,第二、三個參數(m,n)分別表示取出指令的從第m位開始的n個字節。

  • int: 將指令轉換成整數,例如int([01H 90H]),表示將指令[01H 90H] 轉換成整數是400(計算011616+9*16)
  • float: 將指令轉換成浮點數,即IEEE 754規範的浮點數,也是計算機本身的浮點數標準,例如float([01H 90H]),轉換成浮點數是0.400
  • sum: 將指令從第一個字節到最後一個進行累加求和。
  • product: 將指令從第一個字節到最後一個進行累乘求積。
  • bcd: 指令轉換成BCD編碼
  • rbcd: bcd編碼轉換成指令

1.3 關鍵字

1.2.1 **def **

定義函數關鍵字,行開頭使用def 表示定義函數。格式如下:
def func: ($1*$1)%$2
就表示定義了一個名稱爲func的函數,它至少應該有兩個參數,分別用$1,$2代替。比如:func(16,256) $1就是16,$2就是256,那麼,func(16,256)=1616%256=1
需要注意不要在表達式內部有空格,爲避免這個問題可以加上括號。比如 def func: (($1
$1)%$2)

1.2.2 **ditch **

本意表示挖。它的形式是$ditch(m,$do), 即挖出m個字節的指令,進行$do運算,這裏面的do可以是任何函數。
ditch用於接收指令分析。比如接收的指令是:

[68 11 68 76 00 04 00 33 78 81 16 1F 90 01 00 01 00 00 2C 00 00 00 00 2C 00 00 00 00 00 00 00 00 00 A6 16]

根據協議知道,第一個16 是它的數據長度,1F 90 01 後面的13個字節是其數據部分,我們需要把前4個字節提取出來轉化成我們需要的計量數值,那麼解析指令可以這樣定義:

def measure: $bcd($1,0,4)/100.0
def command1: [68H 11H  $ditch(8) $ditch(1,$assign(&len,$1-3)) $ditch(3) $ditch(4,$data(&measure,$1)) $ditch($len-4+2)]

command1裏用到了多個ditch,第一個ditch(8),表示挖出8個字節的指令,但不做任何處理,即[68 76 00 04 00 33 78 81] 相當於被忽視。第二個ditch帶兩個參數,第一個參數是1,第二個嵌套了一個函數assign, 它表示挖出一個字節,即16H,然後將16H 減去3 賦值給len, 這時得到的len=13H。
緊接着又是ditch(3),即忽視接下來的三個字節。
再後面是挖出4個字節[00 01 00 00]交給data並作爲$measure的參數進行運算就可以得到我們需要的結果。具體見data部分。

1.2.3 **data **

data 只能用於接收指令提取數據,比如水錶的計量數據。它的第一個參數是一個&接的函數,第二個參數是第一個參數的參數。例如:
$ditch(4,$data(&measure,$1))

表示挖出4個字節的指令,作爲data的第二個參數,第一個參數是自定義的函數measure,他是把一個多字節的指令轉化成bcd碼,然後除以100. measure的參數是data的第二個參數,即$1,也就是$ditch 挖到的4個字節指令[00 01 00 00]。計算的結果是100.00
注意: 發送指令不能使用ditch ,data 關鍵字

1.2.4 chk

用於校驗。它有兩個參數,即比較這兩個參數是否相等,如果不相等會返回錯誤。

1.2.5 group

組合指令。是指對現有指令進行組合。在校驗碼字段進行累加求和時用到比較多。比如對從第0個字節開始到校驗碼之前所有的指令進行累加和,這時就可以使用$group(0,$pos) 得到從0到校驗碼之前的所有合併的指令。$pos 表示當前關鍵字在整個指令中的索引,從0開始計算,所以這裏$pos 也表示指令的長度。即從0開始的連續$pos 個字節的指令進行合併。

1.2.6 pos

當前在指令中的位置。例如:
[86H 11H $group(1,$pos) 16H]
因爲pos 是group的參數,group 在指令中的位置是2,那麼pos=2. group(1,$pos) = [11H]

1.2.7 addr

儀表地址。

2.1 完整例子

這時hed型號儀表的完整指令配置

#型號HED09E3Y/C 

def crc16: $crc($1,0x8005,0xffff,0x0000,0)
def charge:$bcd($1)*0.1
#讀寄存器   :地址      功能號   起始地址     長度           校驗(crc16_modbus)
def command0: [$1        03H     $itom(2,$2)  $itom(2,$3)    $crc16($group(0,$pos))]

#應答       : 地址      功能號   數據長度                        數據                  校驗
def command1: [$addr     03H     $ditch(1,$assign(&len,$1))     $data($1,$ditch($len)) $crc16($group(0,$pos))]
#01 03 04 00 00 00 00 FA 33 
def command2: [01H       03H     02H     $ditch(2,$assign(&addr,$int($1))) $crc16($group(0,$pos))]
#獲取地址應答

0000   設備地址       $command0(01H 45H 1)   $command2           1  none

0110   正向有功電度   $command0($addr 36H 2) $command1(&charge)  1  kwh
0111   反向有功電度   $command0($addr 38H 2) $command1(&charge)  1  kwh
0112   正向無功電度   $command0($addr 3CH 2) $command1(&charge)  1  kvarh
0113   反向無功電度   $command0($addr 3EH 2) $command1(&charge)  1  kvarh
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章