樹莓派ZeroW串口通訊


>> [目錄] 數據遠程采集 Step by Step


電腦系統:WIN10

樹莓派型號:Zero W

樹莓派系統:Raspbian,2018-11-13-raspbian-stretch-lite.img

Python 2.7.3

軟件&工具:sscom5.13.1(串口助手),串口通訊線,串口驅動(電腦用)


硬件配置


使用樹莓派的GPIO15、GPIO16作為串口的TxD和RxD,另外還有附近的5V、GND。准備好串口通訊線,電腦上裝好驅動。

image

樹莓派CPU的內部有兩個串口,一個是硬件串口,一個是迷你串口(mini-uart)。硬件串口的速率是穩定的,而迷你串口沒有時鍾源,由內核提供時鍾參考源,由於內核本身的頻率是變化的,導致迷你串口的速率不穩定。

系統默認把硬件串口分配給了藍牙模塊,迷你串口分配給了GPIO(GPIO15、GPIO16),這將影響串口通訊的可靠性,所以在安裝樹莓派系統的時候,交換了兩個串口的分配:

image

pi@raspberrypi:~ $ ls -l /dev  #查看設備信息

image

ttyAMA0就是GPIO上的那個串口,系統默認它是作為console使用的,不過在安裝樹莓派系統的時候已經把這個配置給刪掉了,現在可以作為普通的串口使用。

image

數據收發


先實現基本的數據收發功能,主要是為了測試接收和發送的通路是否正常。

新建一個測試文件 test_serial.py:

pi@raspberrypi:~ $ sudo nano test_serial.py

右鍵復制下面的內容,保存退出:

  1 # -*- coding:utf-8 -*-
  2 import serial
  3 import time
  4 
  5 def main():
  6   ser = serial.Serial("/dev/ttyAMA0", 19200)
  7   ser.flushInput()
  8   ser.flushOutput()
  9 
 10   while True:
 11     recv_num = ser.inWaiting()
 12 
 13     if recv_num > 0:
 14       recv_buffer = ser.read(recv_num)
 15       ser.flushInput()
 16 
 17       ser.flushOutput()
 18       ser.write('received ' + str(recv_num) + ' bytes >>  ')
 19       ser.write(recv_buffer)
 20 
 21     time.sleep(0.1)
 22 
 23 if __name__ == '__main__':
 24   main()

這段程序首先是打開串口0,波特率19200。然后在while循環中等待接收數據,接收到數據以后,把這個數據以及它的字節數發送出來。

每0.1s掃描一次,測試數據最好稍微短一點,否則就肯定被截斷了。


運行 test_serial.py:

pi@raspberrypi:~ $ python test_serial.py

如果中途需要退出,比如去重新編輯文件,可以按Ctrl+Z

image


連接串口通訊線,打開串口助手(sscom),選擇掃描到的COM口,設置波特率為19200,不勾選HEX顯示、HEX發送,然后點擊打開串口。

串口助手發送:hello_serial

串口助手接收:received 12 bytes >>  hello_serial

串口助手接收到的就是樹莓派串口發送的,說明硬件連接正常,串口發送接收功能正常。

image


串口通訊


本項目中的串口通訊包含兩種模式(互斥,默認為數據模式):

  • 管理模式下,樹莓派接收配置參數,發送自身狀態;
  • 數據模式下,樹莓派接收設備的廣播數據


管理模式

設計了4個管理指令,均為12字節,不夠的就用*補足,可以根據實際的需要隨意定義,指令的長度也可以不一樣:

ADMIN_QUERY* 查詢狀態:內部控制參數,磁盤信息,數據文件數量等
ADMIN_CONFIG 配置內部控制參數:樹莓派的編號,通訊協議,WIFI用戶名和密碼等
ADMIN_CLEAR* 清空磁盤中的所有數據庫文件
ADMIN_EXIT** 退出管理模式

上電后一段時間內(可以設置為1-5分鍾),樹莓派接收到前3條指令后,均進入管理模式,樹莓派在這個模式下每隔1s發送一次自身的狀態。樹莓派收到退出指令或者一定時間限制后,自動退出管理模式。


數據模式

廣播包約0.5~2s發一幀,每幀數據約50~400字節,數據幀之間的空閑時間≥100ms。樹莓派接收到廣播數據幀后將其存在一個int數組里,其他程序后面會來處理這個數組。


新建一個測試文件 test_serial_comn.py:

pi@raspberrypi:~ $ sudo nano test_serial_comn.py

右鍵復制下面的內容,保存退出:

  1 # -*- coding:utf-8 -*-
  2 import serial
  3 import time
  4 import binascii
  5 
  6 DB_COMN_LENGTH = 400
  7 COMN_LENTH_MIN = 50
  8 COMN_LENTH_MAX = 400
  9 
 10 def DeviceConfigSet(str_config):
 11   print('DeviceConfigSet() function run...')
 12 
 13 def DeviceClear():
 14   print('DeviceClear() function run...')
 15 
 16 def GetDeviceStatus():
 17   print('GetDeviceStatus() function run...')
 18   return 'defined_device_status_string...'
 19 
 20 def main():
 21   global DB_COMN_LENGTH
 22   global COMN_LENTH_MIN
 23   global COMN_LENTH_MAX
 24 
 25   # initialize
 26   print('>> initialize...')
 27   ####
 28 
 29   is_data_received = False
 30   is_admin_mode_enabled = False
 31 
 32   in_admin_count = 0
 33   update_status_interval = 0
 34 
 35   comn_data = [0 for i in range(DB_COMN_LENGTH)]
 36   recv_num_last = 0
 37 
 38   ser = serial.Serial("/dev/ttyAMA0",19200)
 39   ser.flushInput()
 40   ser.flushOutput()
 41 
 42   print('>> wait for data or command...')
 43 
 44   while True:
 45     recv_num = ser.inWaiting()
 46     if recv_num > 0:
 47       time.sleep(0.01)
 48       recv_num_real = ser.inWaiting()
 49       while recv_num_real > recv_num:
 50         recv_num = recv_num_real
 51         time.sleep(0.01)
 52         recv_num_real = ser.inWaiting()
 53 
 54       recv = ser.read(recv_num_real)
 55 
 56       if in_admin_count < 3600 and 'ADMIN_CONFIG' == recv[0:12]:
 57         print('>> command received...')
 58         print(recv)
 59         is_admin_mode_enabled = True
 60         DeviceConfigSet(recv)
 61 
 62       elif in_admin_count < 3600 and 'ADMIN_QUERY*' == recv[0:12]:
 63         print('>> command received...')
 64         print(recv)
 65         is_admin_mode_enabled = True
 66 
 67       elif in_admin_count < 3600 and 'ADMIN_CLEAR*' == recv[0:12]:
 68         print('>> command received...')
 69         print(recv)
 70         is_admin_mode_enabled = True
 71         DeviceClear()
 72 
 73       elif True == is_admin_mode_enabled and 'ADMIN_EXIT**' == recv[0:12]:
 74         print('>> command received...')
 75         print(recv)
 76         is_admin_mode_enabled = False
 77 
 78       elif recv_num_real >= COMN_LENTH_MIN and recv_num_real <= COMN_LENTH_MAX:
 79         is_data_received = True
 80         for i in range(0, recv_num_real):
 81           comn_data[i] = int(binascii.b2a_hex(recv[i]), 16)
 82         for i in range(recv_num_real, recv_num_last):
 83           comn_data[i] = 0
 84         recv_num_last = recv_num_real
 85         print('>> data received...')
 86         print(comn_data)
 87 
 88       else:
 89         print('>> invalid data received!!!...')
 90         print(recv)
 91 
 92       ser.flushInput()
 93 
 94     # save data records if needed
 95     ####
 96 
 97     if in_admin_count < 3600:
 98       in_admin_count = in_admin_count + 1
 99     else:
100       is_admin_mode_enabled = False
101 
102     if True == is_admin_mode_enabled:
103       update_status_interval = update_status_interval + 1
104       if update_status_interval >= 20:
105         update_status_interval = 0
106         ser.flushOutput()
107         ser.write(GetDeviceStatus())
108 
109     # system tick
110     time.sleep(0.05)
111 
112 if __name__ == '__main__':
113   main()


代碼說明


>> 切割通訊幀

通過幀之間的空閑時間來區分兩個幀。軟件正常每隔50ms掃描一次串口,看是否收到新的數據,如果收到了新的數據,認為新的一幀開始了,軟件會每隔10ms掃描一次串口,直到在上個10ms內沒有接受到新的數據,認為這一幀結束(掃描間隔要考慮波特率,19200對應的1個字節持續時間約為0.625ms)。一幀接收完成后,讀取出全部接收到的數據,進入下一步處理。

主要是用這段代碼來實現的:

  1 recv_num = ser.inWaiting()
  2 if recv_num > 0:
  3   time.sleep(0.01)
  4   recv_num_real = ser.inWaiting()
  5   while recv_num_real > recv_num:
  6     recv_num = recv_num_real
  7     time.sleep(0.01)
  8     recv_num_real = ser.inWaiting()
  9 
 10   recv = ser.read(recv_num_real)


>> 有效數據幀

為了考慮到采集系統的通用性,用數據幀的長度來進行過濾,給定一個有效數據幀的長度范圍,超出的都認為是無效數據幀。

當然也可以把通訊協議做進去,可以配置參數里可以配置通訊協議類型,0默認是無協議,其他的自行定義。


>> 數據格式轉換

串口接收到的數據 recv = ser.read(recv_num_real),read函數說明如下:

https://pythonhosted.org/pyserial/pyserial_api.html

image

依次打印recv[0]和recv:

  1 print('>> recv[0]...')
  2 print(recv[0])
  3 print('>> recv...')
  4 print(recv)
  5 print('>> data received...')
  6 print(comn_data)

image

串口助手發送的第一個字節是99(10進制),recv[0]打印出來是c,這個是ascii格式的:

image

要把ascii格式的數據轉換為int格式的數據:

  1 import binascii
  2 
  3 comn_data[i] = int(binascii.b2a_hex(recv[i]), 16)

通過binascii.b2a_hex()函數來接收串口數據recv[i],把這個字節轉換為16進制的數據;

通過int(x [,base])把剛剛得到的16進制數據轉換為int格式的數據。



運行 test_serial_comn.py,顯示初始化完成,等待接收數據或指令:

pi@raspberrypi:~ $ python test_serial_comn.py

image


測試管理指令


樹莓派收到[ADMIN_QUERY*]指令后,進入管理模式,開始每隔1s發送一次自身的狀態;

樹莓派收到[ADMIN_EXIT**]指令后,退出管理模式image

樹莓派收到[ADMIN_CLEAR*]指令后,進入管理模式,調用DeviceClear()函數清空磁盤中的數據文件,並開始每隔1s發送一次自身的狀態;

樹莓派收到[ADMIN_CONFIG]指令后,進入管理模式(之前已經在了),調用DeviceConfigSet()函數配置參數,並開始每隔1s發送一次自身的狀態;

樹莓派收到[ADMIN_EXIT]非法指令,提示收到非法指令;

樹莓派收到[ADMIN_EXIT**]指令后,退出管理模式image


測試廣播數據


測試包長度為222字節,16進制顯示:

63 78 12 10 05 32 01 00 00 00 00 00 00 10 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2E 0A 27 00 00 00 00 00 5A 00 26 00 5A 00 26 00 5A 00 26 00 00 00 00 11 F8 08 FC 0C D0 05 5A 08 5C 03 84 04 9C 01 F4 00 00 00 00 00 52 00 26 10 68 08 FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2A 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00 00 00 03 E8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F8 08 FC 11 F8 08 FC

連續測試10個包,發送間隔500ms,主要是看一下接收到的數據包是否完整(軟件中寫了數組長度為400字節,不夠的就補零)。

image



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM