第一步:Linux中啟用串列埠裝置。【以樹莓派4B為例】
樹莓派4B有6個串列埠,參考上一篇《樹莓派4B串列埠設定與開發》,在 /boot/config.txt 中新增一行,開啟 uart2 功能:
dtoverlay=uart2
重啟後,檢視是否有多出來一個 /dev/AMA1 裝置:
$ ls -l /dev/tty*
crw-rw---- 1 root dialout 204, 64 Jul 20 11:52 /dev/ttyAMA0
crw-rw---- 1 root dialout 204, 65 Jul 20 11:59 /dev/ttyAMA1
crw------- 1 root root 5, 3 Jul 20 11:52 /dev/ttyprintk
crw--w---- 1 root tty 4, 64 Jul 20 11:52 /dev/ttyS0
也可以config.txt 中新增多行(uart2,uart3,uart4,uart5)啟動多個串列埠功能 (對應 ttyAMA1,ttyAMA2,ttyAMA3 和 ttyAMA4).
可以用下面命令檢視 uart2 對應的GPIO針腳對映:
# dtoverlay -h uart2
Name: uart2
Info: Enable uart 2 on GPIOs 0-3. BCM2711 only.
Usage: dtoverlay=uart2,<param>
Params: ctsrts Enable CTS/RTS on GPIOs 2-3 (default off)
從輸出可見,GPIO針腳為0-3, 其中針腳0和1分別為TxD和RxD,針腳2-3為流控 CTS/RTS. 此處針腳0-1為BCM編碼號,物理引腳號為27-28.
第二步: 使用python程式碼,測試 uart2 功能是否正確
硬體接線: 將 GPIO引腳0 和 1 短接,實現自發自收。
軟體測試:python控制檯中,執行如下程式碼測試
>>> import serial
>>> ted = serial.Serial(port="/dev/ttyAMA1", baudrate=9600)
>>> ted.write("Hello World".encode("gbk"))
11
>>> ted.read(11)
b'Hello World'
>>>
能收到字串‘Hello World’表示 uart2 功能和接線均一切正常。
第三步:編輯 CodeSys 組態檔,對映 /dev/ttyAMA* 到 COMx 埠號。
在老版本的CodeSys 中,需要編輯 "/ect/CODESYSControl.cfg" 末尾新增:
[SysCom]
Linux.Devicefile = /dev/ttyUSB
portnum := COM.SysCom.SYS_COMPORT1;
這樣,在codesys中指定串列埠號1,代表使用的裝置為 /dev/ttyUSB0, 非常不直觀。
從codesys v3.5 SP15 起(據說),改為在檔案 /etc/CODESYSControl_User.cfg 裡這麼設定:
[SysCom]
Linux.Devicefile.1=/dev/ttyS0
Linux.Devicefile.2=/dev/ttyAMA1
Linux.Devicefile.4=/dev/ttyUSB0
這樣, Com1 即為 ttyS0, Com2即為 ttyAMA1, Com4 即為 ttyUSB0,依次類推。支援多個串列埠,方便多了。
如上面設定,對映關係 uart2 --> ttyAMA1 ---> Com2, 所以codesys中指定埠號為 2 (即Com2)即可。
第四步: CodeSys中程式設計實現串列埠收發功能
參考 youtube 上的學習視訊: https://www.youtube.com/watch?v=NFREG2U07Rg
只需參考codesys程式設計部分即可,程式碼我在他基礎上又做了修改完善,
(1)程式塊匯入3個庫: Memory, Serial Communication, Util
(2)定義部分:
PROGRAM SerialPort
VAR
(*開啟埠部分*)
Open_0: COM.Open;
Open_xExecute: BOOL := TRUE; //預設開啟埠
aParams : ARRAY [1..7] OF COM.PARAMETER := [
(udiParameterId := COM.CAA_Parameter_Constants.udiPort, udiValue := 2),
(udiParameterId := COM.CAA_Parameter_Constants.udiBaudrate, udiValue := 9600),
(udiParameterId := COM.CAA_Parameter_Constants.udiParity, udiValue := INT_TO_UDINT(COM.PARITY.NONE) ),
(udiParameterId := COM.CAA_Parameter_Constants.udiStopBits, udiValue := INT_TO_UDINT(COM.STOPBIT.ONESTOPBIT) ),
(udiParameterId := COM.CAA_Parameter_Constants.udiTimeout, udiValue := 0),
(udiParameterId := COM.CAA_Parameter_Constants.udiByteSize, udiValue := 8),
(udiParameterId := COM.CAA_Parameter_Constants.udiBinary, udiValue := 0)
];
hCom: CAA.HANDLE;
(* read模組 *)
BLINK0: BLINK;
Read_0: COM.Read;
bReadData : ARRAY[1..80] OF BYTE;
read_szSize: CAA.SIZE;
sReadData : STRING;
(* write模組 *)
Write_0: COM.Write;
write_xExecute: BOOL; //執行write操作
bWriteData : ARRAY[1..80] OF BYTE;
sWriteData : STRING;
sWriteDataLast : STRING; //上一次 Write值
END_VAR
(3)梯形圖部分
先要 開啟串列埠 (串列埠引數在定義部分已預設定):
注意此處,引數 xExecute 需始終為 True,否則 會關閉串列埠 hCom=0 !
讀串列埠部分的程式碼:
使用 blink 定期讀取,讀出的內容放到陣列 bReadData 中,讀出長度為 read_szSize.
為了防止讀入空(讀出為空是常態,有內容 是少數)時 覆蓋掉前面值,非空時才拷貝和更新到某個string,程式碼如下:
這樣,僅在有新內容讀出時,才更新值到 sReadData 中。末尾的 MEM.MemFill() 用於寫入 string 的結束字元 '\0' .
下面到了 寫串列埠 的部分。基本思路也是差不多,字串中有新值時,才將 字串內容 拷貝到 陣列中用於寫出,並使能一次寫動作,程式碼如下:
之後開始真正的 串列埠寫 動作:
程式碼後半行,如果寫成功,就把此次內容儲存到 sWriteDataLast 字串裡,用於下一次比較,內容不同時才觸發一次 COM.Write() 寫動作。
需要注意的是,若寫動作發生error,會一直卡住 不更新 sWriteDataLast,所以加上並聯條件 Write_0.xError , 不管成功/Error失敗 均結束此次寫動作!就算寫失敗,想再一次嘗試,也必須將 sWriteData 改為其他才能再次觸發 寫動作。
(4)CodeSys中測試串列埠讀寫功能
若串列埠正確開啟, 則 hCom 的值非空,否則 hCom=0 表示失敗。
blink產生的訊號定時讀一遍資料,有新內容顯示在字串 sReadData 中;
字串 sWriteData 中的內容會通過串列埠寫出去,只有更新 sWriteData 值的瞬間才會觸發一次寫操作,不管是否寫出成功。
以上程式碼,使用 樹莓派4B, Codesys 3.5.18.2 ,Codesys Control for Linux ARM64 SL 測試通過。
使用樹莓派 自帶的 uart2 (ttyAMA1)和 usb轉ttl串列埠(ttyUSB0) 均測試通過。
2022-07-21