python實(shí)現(xiàn)串口通信的示例代碼
1 硬件設(shè)備
- TTL串口攝像頭(VC0706)
- USB轉(zhuǎn)TTL燒錄器
2 serial安裝
第一次安裝的是serial的包導(dǎo)包的時(shí)候發(fā)現(xiàn)下載錯了,正確應(yīng)該是pyserial。安裝后直接import就可以了。
3 實(shí)現(xiàn)串口通信
3.1 發(fā)現(xiàn)端口
Windows下為COM(N, N=1、2...), Ubuntu下為‘/dev/ttyS0
'。
Windows初學(xué)者,可以給您一下兩種方式確定端口號。
方法一:輸入在終端(cmd)中輸入
python -m serial.tools.list_ports
輸出結(jié)果:
COM5 1 ports found
方法二:搜索電腦上的設(shè)備管理器,打開以后然后插入燒錄器,自動就會彈出。如果沒有彈出就可能是驅(qū)動沒有安裝,安裝好以后不好使,重啟一下電腦,到了工作的時(shí)候大家都知道程序員會跟你說,你重啟一下,清一下緩存,這兩句話。也有可能是驅(qū)動安裝的不對。
方法三:直接找一個有端口掃描的上位機(jī),點(diǎn)擊掃描就可以了。大部分上位機(jī)都是你一插進(jìn)去就會檢測到你的端口。
----->
注意:當(dāng)串口被占用的時(shí)候也有可能導(dǎo)致失敗,例如你在編譯器有兩個進(jìn)程運(yùn)行下面的測試代碼,第二個進(jìn)程就會因?yàn)槎丝谡加枚?。也有的上位機(jī)是因?yàn)橥瑫r(shí)打開了兩個上位機(jī)的緣故(實(shí)驗(yàn)課的時(shí)候同學(xué)遇到過情況),可以用任務(wù)管理器kill掉。
測試:
import serial #Windows ser = serial.Serial(port='COM5', baudrate=115200, timeout=0.5) print(ser.name)
控制臺打印結(jié)果:
COM5 Process finished with exit code 0
建立ser對象的代碼:
class PicSerial: __ser = None # ser的單例 __isinit = False @staticmethod def get_available_port(): """ 檢測可以使用的端口號 :return->str: 端口號的名稱 """ port = list(list_ports.comports()) if len(port) > 0: port_name = port[0].device print(port_name) return port_name # logging.info("Available port:", ports) else: print("There is no available port.") # logging.error("There is no available port.") def __new__(cls, *args, **kwargs): if PicSerial.__ser is None: cls.__ser = object.__new__(cls) return cls.__ser def __init__(self): if not PicSerial.__isinit: self.sername = self.get_available_port() self.ser = serial.Serial(port=self.sername, baudrate=BAUDRATE) PicSerial.__isinit = False print("PicSerial init.")
3.2 發(fā)送命令
3.2.1 協(xié)議格式
3.2.2 serial傳送的方式
serial傳送的方式有:
串行端口對象。只傳單個字節(jié)。字符串。字節(jié)數(shù)組+字節(jié)數(shù)組長度。
所以直接選用數(shù)組傳數(shù)據(jù),這里會遇到一個問題就是python的list會自動把十六進(jìn)制數(shù)轉(zhuǎn)換為整形。
所以要進(jìn)行轉(zhuǎn)換你可以直接寫成b“/x56/x00/x17/x00”。假如你不需要傳十進(jìn)制也可以轉(zhuǎn)成list,直接map(chr,x)或map(ord,x)也是可以的。讀的時(shí)候也要注意只要你放進(jìn)list里面就會自動轉(zhuǎn)成整形。
【我覺得這樣寫很降智,但是又不得不這樣寫】
#在PicSerial中 def isreply(self, cmd: bytes, option: str) -> bool: """ 檢測是否有回復(fù) :return:回復(fù)的內(nèi)容 :param cmd: :param option: :return: True則有回復(fù) """ if isinstance(cmd, bytes) and isinstance(option, str) and len(cmd) > 0 and len(option) > 0: self.ser.write(cmd) reply = self.ser.read(4) reply = list(map(chr, list(reply))) print("49h,The function'{}' is running. reply:{}".format(sys._getframe().f_code.co_name, reply)) if len(reply) >= 4 and reply[0] == 'v' and reply[1] == SERIAL_NUM and reply[2] == option and reply[3] == STATUS: return True return False
測試:
#在test文件中 class TestSerial(unittest.TestCase): def test_isreply(self): self.assertTrue(ser.isreply(GET_VERSION_CMD, VERSION)) self.assertFalse(ser.isreply('\x56\x00\x11\x00', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, b'\x11')) self.assertFalse(ser.isreply(123456, b'\x11')) self.assertFalse(ser.isreply('', VERSION)) self.assertFalse(ser.isreply(b'', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, '')) self.assertFalse(ser.isreply(GET_VERSION_CMD, None)) self.assertFalse(ser.isreply(b'', '')) self.assertFalse(ser.isreply(b'\x56\x00\xAA\x00', VERSION)) self.assertFalse(ser.isreply(GET_VERSION_CMD, '\xAA')) #之后就省略不寫了 if __name__ == '__main__': unittest.main()
結(jié)果:
3.3 獲取版本號(hello world)
按照協(xié)議一步一步操作
主 機(jī) 發(fā):56 00 11 00 攝像頭回:76 00 11 00 0B 56 43 30 37 30 33 20 31 2E 30 30 (VC0703 1.00)
#在PicSerial中 def getversion(self) -> str: """ 獲取版本號 :return: """ cmd = GET_VERSION_CMD option = VERSION if self.isreply(cmd, option): left = self.ser.readall() print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return self.ser.read(12).decode()[1:]
測試:
#在test文件中 def test_getversion(self): self.assertEqual(ser.getversion(), 'VC0703 1.00')
結(jié)果:通過測試
3.4 復(fù)位
主 機(jī) 發(fā): 56 00 26 00
攝像頭回: 76 00 26 00 00
#在PicSerial中 def reset(self): """ 復(fù)位 :return: """ cmd = REST_CMD option = RESET if self.isreply(cmd, option): if self.ser.read(1) == b'': left = self.ser.readall() print("75h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
*測試和運(yùn)行結(jié)果不一樣。
花了一點(diǎn)時(shí)間找到原因了,單元測我都是點(diǎn)擊前面綠色的小箭頭,以為只是運(yùn)行當(dāng)前的測試函數(shù)的內(nèi)容,但是我發(fā)現(xiàn)它把其他的函數(shù)都運(yùn)行了。所以要把之前的測試函數(shù)注釋掉得到的結(jié)果就一樣了。
測試通過。
3.5 照相
- 停止當(dāng)前幀刷新
- 獲娶圖片長度
- 獲取圖片
- 恢復(fù)幀更新
3.5.1 停止當(dāng)前幀刷新
這一步執(zhí)行一次就夠了。因?yàn)樽x命令的時(shí)候會出現(xiàn)麻煩。但是這一步是有意義的,就是當(dāng)你發(fā)現(xiàn)圖片很大,的時(shí)候正常大小就兩個byte可以表示完了(排除你的圖片面積十分大或十分清晰),又或者是突然讀空了。假如數(shù)值非常的大,可以使用該函數(shù),再不行就要選擇復(fù)位。
def stoprefresh(self): """ 停止刷新當(dāng)前幀 :return: """ cmd = STOP_REFRESH_CMD option = TAKE_PHOTO self.ser.write(cmd) if self.isreply(cmd, option) and self.ser.read(1) == b"\x00": left = self.ser.readall() print("87h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
通過測試
def test_stoprefresh(self): self.assertTrue(ser.stoprefresh())
3.5.2 獲娶圖片長度
def getlength_bytes(self) -> bytes: """ 獲取圖片的長度 :return: """ cmd = GET_LENGTH_CMD option_pic = '4' self.ser.write(cmd) if self.isreply(cmd, option_pic): if self.ser.read(1) == b'\x04': res = self.ser.read(4) left = self.ser.readall() print("103h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return res return b'\x00\x00\x00\x00'
測試通過
def test_getlength(self): self.assertEqual(ser.getlength(), b'\x00\x00\x12\x34')
3.5.3 恢復(fù)幀更新
def recover_refresh(self): """ 恢復(fù)幀刷新 :return: """ cmd = RECOVER_REFRESH_CMD option = TAKE_PHOTO self.ser.write(cmd) if self.isreply(cmd, option): # 讀出剩余的字節(jié) left = self.ser.readall() print("142h,The function'{}' has responded.left{}".format(sys._getframe().f_code.co_name, left)) return True return False
測試并通過:
def test_recover_refresh(self): self.assertTrue(ser.recover_refresh())
3.5.4 拍照
在這里卡了很長時(shí)間,不知道為什么長度是不確定的,每一次讀的長度都沒讀完,看代碼。
下面代碼只是演示
#下面代碼只是演示不在最終版本中 def savephoto(self, cmd, option, len): """ 保存圖片 :param cmd: :param option: :param len: 照片的長度 :return: """ with open('write_pic/serialpic/photo.jpg', 'wb') as f: if self.isreply(cmd, option): print(self.ser.read(1)) countofread_complete_byte = 0 # 用于計(jì)算當(dāng)前已經(jīng)寫入的長度 while countofread_complete_byte != len + 10: # read()是有上限的,不可以把全部都讀取 lines = self.ser.read(len + 10 - countofread_complete_byte) countofread_complete_byte += lines.__len__() f.write(lines) print("142h,countofread_complete_byte:", countofread_complete_byte, "lines", lines.__len__()) left = self.ser.readall() print("146h,少讀內(nèi)容:", left, "共", left.__len__(), "個字節(jié)") res = self.ser.readall() print(res)
現(xiàn)象:
現(xiàn)象是運(yùn)行一直不停都是手動stop console,或者沒有stop console會一直打印lines為空,就此可以猜測read()不是阻塞的。是圖片字節(jié)總數(shù)不斷增多。每次遍歷完后滿足self.ser.read(len + 10 - countofread_complete_byte)后再readall()還是有剩余的內(nèi)容。
我發(fā)現(xiàn)此時(shí)readall一共讀出了4049個字節(jié),圖片數(shù)據(jù)4030個字節(jié)+首尾兩部分共10個字節(jié),那多出來的9個字節(jié)是什么火眼金睛的Unyielding ● L發(fā)現(xiàn)了正確的開始位置為上圖紅色方塊處,碰巧多出來的是九個字節(jié),所以多出來的就不是這一張圖片的內(nèi)容,所以可以猜想程序沒有停止的原因是上一次圖片還沒讀完我就手動停止,所以留下了數(shù)據(jù),上一次沒有讀完的內(nèi)容,這一次讀到了。
字節(jié)串和字符串都可以切片。直接切出來保存。
def getphoto(self): """ 拍照并且保存圖片 :return: """ # self.reset() # 1、停止幀刷新 # self.stoprefresh() # 獲取圖片長度 # 返回字節(jié)長度用于整合命令,表示圖片的總字節(jié)數(shù) length = self.getlength_bytes() # 返回整形,表示圖片的總字節(jié)數(shù) len = self.bytesToInt(length) print("158h,len:", len) # 拍照 cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD print("159hcmd", cmd) self.ser.write(cmd) readall = self.ser.readall() readall_len = readall.__len__() differ = readall_len - len - 10 if differ != 0: res = readall[differ + 5:-5] print("172h:", res) self.savephoto(res) else: res = readall[5:-5] print("175h:", res) self.savephoto(res) # 關(guān)閉串口 self.ser.close() # 恢復(fù)刷新 # self.recover_refresh()
成功輸出結(jié)果:
為了方便debug,停幀回復(fù)幀都是手動發(fā)送的,剩下的問題就是把注釋打開試一試能不能成功組合成一個函數(shù),發(fā)現(xiàn)有的命令會讀空,所以可以推斷:一定又是前一個命令里面又留下來什么還沒有被讀取的字節(jié)造成讀到的內(nèi)容篡位了。
每一次執(zhí)行完命令后看一看還有沒有遺留字節(jié),把剩余字節(jié)都取出來,然后differ的判斷都不需要了?!咀x者看到的代碼都是最新版本的,此處我添加了left和print到對應(yīng)函數(shù)中】,處理結(jié)果打印到控制臺:
def getphoto(self): """ 拍照并且保存圖片 :return: """ # 1、停止幀刷新 self.stoprefresh() # 獲取圖片長度 # 返回字節(jié)長度用于整合命令,表示圖片的總字節(jié)數(shù) length = self.getlength_bytes() # 返回整形,表示圖片的總字節(jié)數(shù) len = self.bytesToInt(length) print("161h,The function'{}' is running. len:{}".format(sys._getframe().f_code.co_name, len)) # 拍照 cmd = GET_PHOTO_START_CMD + length + GET_PHOTO_END_CMD print("165h,The function'{}' is running. cmd:{}".format(sys._getframe().f_code.co_name, cmd)) self.ser.write(cmd) readall = self.ser.readall() readall_len = readall.__len__() differ = readall_len - len - 10 if differ != 0: res = readall[differ + 5:-5] print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res)) self.savephoto(res) else: res = readall[5:-5] print("161h,The function'{}' is running. res:{}".format(sys._getframe().f_code.co_name, res)) self.savephoto(res) # 恢復(fù)刷新 self.recover_refresh()
輸出圖片結(jié)果(拍的是導(dǎo)線沒有聚焦)
# 這個測試應(yīng)該怎么寫? 有圖片就輸出并且可以打開就可以了惹?有人能教教我? def test_getphoto(self): pass
4 反思犯了很多
‘我覺得'的錯誤,我覺得這個值是什么,多打斷點(diǎn)看清楚,那一段演示代碼里面因?yàn)閰f(xié)議寫了是五個字節(jié),isreply我已經(jīng)讀了4個字節(jié)再讀一個一定是0x00,后來打印那一行返回的值是0x04這才為猜想讀到上一張圖作下鋪墊。
當(dāng)你寫的很復(fù)雜超過20行的邏輯代碼就知道一定是錯了。--UnyieldingL
編碼方面的內(nèi)容耗費(fèi)了很長時(shí)間,就在反思的時(shí)候發(fā)現(xiàn)了decode("hex")。惹?
list='aabbccddee' hexer=list.decode("hex") print hexer
打印日志要詳細(xì)。 每一個變量涉及的變量長度函數(shù)名,此時(shí)哪個函數(shù)運(yùn)行。
還有一個問題,pycharm還是沒有自己停止,打印某個線程的堆棧。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Python圖像形態(tài)學(xué)處理(開運(yùn)算,閉運(yùn)算,梯度運(yùn)算)
數(shù)學(xué)形態(tài)學(xué)(Mathematical Morphology)是一種應(yīng)用于圖像處理和模式識別領(lǐng)域的新方法。本文將為大家介紹Python圖像形態(tài)學(xué)處理中的開運(yùn)算、閉運(yùn)算和梯度運(yùn)算,感興趣的可以了解一下2022-06-06Pandas實(shí)現(xiàn)一列數(shù)據(jù)分隔為兩列
這篇文章主要介紹了Pandas實(shí)現(xiàn)一列數(shù)據(jù)分隔為兩列,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05Python GUI編程之tkinter模塊Toplevel控件實(shí)現(xiàn)搭建父子窗口
這篇文章主要介紹了Python使用tkinter模塊Toplevel控件搭建父子窗口的實(shí)現(xiàn)方法,Tkinter是Python的標(biāo)準(zhǔn)GUI庫,Python使用Tkinter可以快速的創(chuàng)建GUI應(yīng)用程序,用到相關(guān)控件的同學(xué)可以參考下2023-12-12python使用正則表達(dá)式提取網(wǎng)頁URL的方法
這篇文章主要介紹了python使用正則表達(dá)式提取網(wǎng)頁URL的方法,涉及Python中urllib模塊及正則表達(dá)式的相關(guān)使用技巧,需要的朋友可以參考下2015-05-05ubuntu16.04升級Python3.5到Python3.7的方法步驟
這篇文章主要介紹了ubuntu16.04升級Python3.5到Python3.7的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08使用Python FastAPI構(gòu)建Web服務(wù)的實(shí)現(xiàn)
這篇文章主要介紹了使用Python FastAPI構(gòu)建Web服務(wù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Python流式游標(biāo)與緩存式(默認(rèn))游標(biāo)的那些坑及解決
這篇文章主要介紹了Python流式游標(biāo)與緩存式(默認(rèn))游標(biāo)的那些坑及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07