python实现串口通信的示例代码


Posted in Python onFebruary 10, 2020

1 硬件设备

  • TTL串口摄像头(VC0706)
  • USB转TTL烧录器

2 serial安装

第一次安装的是serial的包导包的时候发现下载错了,正确应该是pyserial。安装后直接import就可以了。

3 实现串口通信

3.1 发现端口

Windows下为COM(N, N=1、2...), Ubuntu下为‘/dev/ttyS0'Windows初学者,可以给您一下两种方式确定端口号。

方法一:输入在终端(cmd)中输入

python -m serial.tools.list_ports

输出结果:

COM5
1 ports found

方法二:搜索电脑上的设备管理器,打开以后然后插入烧录器,自动就会弹出。如果没有弹出就可能是驱动没有安装,安装好以后不好使,重启一下电脑,到了工作的时候大家都知道程序员会跟你说,你重启一下,清一下缓存,这两句话。也有可能是驱动安装的不对。

python实现串口通信的示例代码

方法三:直接找一个有端口扫描的上位机,点击扫描就可以了。大部分上位机都是你一插进去就会检测到你的端口。

python实现串口通信的示例代码----->python实现串口通信的示例代码

注意:当串口被占用的时候也有可能导致失败,例如你在编译器有两个进程运行下面的测试代码,第二个进程就会因为端口占用而失效。也有的上位机是因为同时打开了两个上位机的缘故(实验课的时候同学遇到过情况),可以用任务管理器kill掉。

测试:

import serial
 
 
#Windows
ser = serial.Serial(port='COM5', baudrate=115200, timeout=0.5)
print(ser.name)

控制台打印结果: 

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 发送命令

3.2.1 协议格式

python实现串口通信的示例代码

3.2.2 serial传送的方式

serial传送的方式有:

串行端口对象。只传单个字节。字符串。字节数组+字节数组长度。

所以直接选用数组传数据,这里会遇到一个问题就是python的list会自动把十六进制数转换为整形

python实现串口通信的示例代码

所以要进行转换你可以直接写成b“/x56/x00/x17/x00”。假如你不需要传十进制也可以转成list,直接map(chr,x)或map(ord,x)也是可以的。读的时候也要注意只要你放进list里面就会自动转成整形。

【我觉得这样写很降智,但是又不得不这样写】

#在PicSerial中
  def isreply(self, cmd: bytes, option: str) -> bool:
    """
    检测是否有回复
    :return:回复的内容
    :param cmd:
    :param option:
    :return: True则有回复
    """
    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()

结果:

python实现串口通信的示例代码

3.3 获取版本号(hello world)

按照协议一步一步操作

主 机 发: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')

结果:通过测试

3.4 复位

主 机 发: 56 00 26 00

摄像头回: 76 00 26 00 00

#在PicSerial中
  def reset(self):
    """
    复位
    :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

*测试和运行结果不一样。

python实现串口通信的示例代码

python实现串口通信的示例代码

花了一点时间找到原因了,单元测我都是点击前面绿色的小箭头,以为只是运行当前的测试函数的内容,但是我发现它把其他的函数都运行了。所以要把之前的测试函数注释掉得到的结果就一样了。

python实现串口通信的示例代码

测试通过。

3.5 照相

  • 停止当前帧刷新
  • 获娶图片长度
  • 获取图片
  • 恢复帧更新

3.5.1 停止当前帧刷新

这一步执行一次就够了。因为读命令的时候会出现麻烦。但是这一步是有意义的,就是当你发现图片很大,的时候正常大小就两个byte可以表示完了(排除你的图片面积十分大或十分清晰),又或者是突然读空了。假如数值非常的大,可以使用该函数,再不行就要选择复位。

def stoprefresh(self):
    """
    停止刷新当前帧
    :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 恢复帧更新

def recover_refresh(self):
    """
    恢复帧刷新
    :return:
    """
    cmd = RECOVER_REFRESH_CMD
    option = TAKE_PHOTO
    self.ser.write(cmd)
    if self.isreply(cmd, option):
      # 读出剩余的字节
      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 拍照

在这里卡了很长时间,不知道为什么长度是不确定的,每一次读的长度都没读完,看代码。

下面代码只是演示

#下面代码只是演示不在最终版本中 
  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 # 用于计算当前已经写入的长度
      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,少读内容:", left, "共", left.__len__(), "个字节")
    res = self.ser.readall()
    print(res)

现象:

现象是运行一直不停都是手动stop console,或者没有stop console会一直打印lines为空,就此可以猜测read()不是阻塞的。是图片字节总数不断增多。每次遍历完后满足self.ser.read(len + 10 - countofread_complete_byte)后再readall()还是有剩余的内容。

python实现串口通信的示例代码

python实现串口通信的示例代码

我发现此时readall一共读出了4049个字节,图片数据4030个字节+首尾两部分共10个字节,那多出来的9个字节是什么火眼金睛的Unyielding ● L发现了正确的开始位置为上图红色方块处,碰巧多出来的是九个字节,所以多出来的就不是这一张图片的内容,所以可以猜想程序没有停止的原因是上一次图片还没读完我就手动停止,所以留下了数据,上一次没有读完的内容,这一次读到了。

字节串和字符串都可以切片。直接切出来保存。

def getphoto(self):
    """
    拍照并且保存图片
    :return:
    """
    # self.reset()
    # 1、停止帧刷新
    # self.stoprefresh()
 
    # 获取图片长度
    # 返回字节长度用于整合命令,表示图片的总字节数
    length = self.getlength_bytes()
    # 返回整形,表示图片的总字节数
    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)
 
    # 关闭串口
    self.ser.close()
 
    # 恢复刷新
    # self.recover_refresh()

成功输出结果:

python实现串口通信的示例代码

为了方便debug,停帧回复帧都是手动发送的,剩下的问题就是把注释打开试一试能不能成功组合成一个函数,发现有的命令会读空,所以可以推断:一定又是前一个命令里面又留下来什么还没有被读取的字节造成读到的内容篡位了。

python实现串口通信的示例代码

每一次执行完命令后看一看还有没有遗留字节,把剩余字节都取出来,然后differ的判断都不需要了。【读者看到的代码都是最新版本的,此处我添加了left和print到对应函数中】,处理结果打印到控制台:

python实现串口通信的示例代码

def getphoto(self):
    """
    拍照并且保存图片
    :return:
    """
    # 1、停止帧刷新
    self.stoprefresh()
 
    # 获取图片长度
    # 返回字节长度用于整合命令,表示图片的总字节数
    length = self.getlength_bytes()
    # 返回整形,表示图片的总字节数
    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)
 
    # 恢复刷新
    self.recover_refresh()

输出图片结果(拍的是导线没有聚焦)

python实现串口通信的示例代码

python实现串口通信的示例代码

# 这个测试应该怎么写? 有图片就输出并且可以打开就可以了惹?有人能教教我?
  def test_getphoto(self):
    pass

python实现串口通信的示例代码

 4 反思犯了很多

‘我觉得'的错误,我觉得这个值是什么,多打断点看清楚,那一段演示代码里面因为协议写了是五个字节,isreply我已经读了4个字节再读一个一定是0x00,后来打印那一行返回的值是0x04这才为猜想读到上一张图作下铺垫。

当你写的很复杂超过20行的逻辑代码就知道一定是错了。--UnyieldingL

编码方面的内容耗费了很长时间,就在反思的时候发现了decode("hex")。惹?

list='aabbccddee' 
hexer=list.decode("hex") 
print hexer

打印日志要详细。 每一个变量涉及的变量长度函数名,此时哪个函数运行。

还有一个问题,pycharm还是没有自己停止,打印某个线程的堆栈。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python去除空格和换行符的实现方法(推荐)
Jan 04 Python
Python设计模式之组合模式原理与用法实例分析
Jan 11 Python
Python装饰器限制函数运行时间超时则退出执行
Apr 09 Python
python批量解压zip文件的方法
Aug 20 Python
Django通过dwebsocket实现websocket的例子
Nov 15 Python
在keras中获取某一层上的feature map实例
Jan 24 Python
flask 框架操作MySQL数据库简单示例
Feb 02 Python
Python如何将图像音视频等资源文件隐藏在代码中(小技巧)
Feb 16 Python
解决jupyter运行pyqt代码内核重启的问题
Apr 16 Python
使用npy转image图像并保存的实例
Jul 01 Python
keras.utils.to_categorical和one hot格式解析
Jul 02 Python
Python数据可视化常用4大绘图库原理详解
Oct 23 Python
基于Tensorflow高阶读写教程
Feb 10 #Python
python集合删除多种方法详解
Feb 10 #Python
pandas中的数据去重处理的实现方法
Feb 10 #Python
对tensorflow中cifar-10文档的Read操作详解
Feb 10 #Python
基于Tensorflow:CPU性能分析
Feb 10 #Python
python sorted函数原理解析及练习
Feb 10 #Python
python pprint模块中print()和pprint()两者的区别
Feb 10 #Python
You might like
用PHP生成静态HTML速度快类库
2007/03/18 PHP
PHP sprintf()函数用例解析
2011/05/18 PHP
MySQL连接数超过限制的解决方法
2011/07/17 PHP
php使用glob函数快速查询指定目录文件的方法
2014/11/15 PHP
PHP读取mssql json数据中文乱码的解决办法
2016/04/11 PHP
laravel 解决多库下的DB::transaction()事务失效问题
2019/10/21 PHP
自动生成文章摘要的代码[JavaScript 版本]
2007/03/20 Javascript
发布一个基于javascript的动画类 Fx.js
2010/11/05 Javascript
JavaScript子窗口ModalDialog中操作父窗口对像
2012/12/11 Javascript
使用jQuery实现验证上传图片的格式与大小
2014/12/03 Javascript
Bootstrap 折叠(Collapse)插件用法实例详解
2016/06/01 Javascript
学习vue.js计算属性
2016/12/03 Javascript
JS使用正则表达式获取小括号、中括号及花括号内容的方法示例
2018/06/01 Javascript
快速解决处理后台返回json数据格式的问题
2018/08/07 Javascript
vue webpack打包后图片路径错误的完美解决方法
2018/12/07 Javascript
JavaScript中Dom操作实例详解
2019/07/08 Javascript
[42:27]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#2Fnatic VS OG第三局
2016/03/05 DOTA
[01:21]DOTA2 新英雄 森海飞霞
2020/12/18 DOTA
Python闭包执行时值的传递方式实例分析
2018/06/04 Python
Python+OpenCV感兴趣区域ROI提取方法
2019/01/10 Python
python简单区块链模拟详解
2019/07/03 Python
Python的Lambda函数用法详解
2019/09/03 Python
matplotlib实现显示伪彩色图像及色度条
2019/12/07 Python
利用python实现平稳时间序列的建模方式
2020/06/03 Python
HTML5 微格式和相关的属性名称
2010/02/10 HTML / CSS
德国领先的大尺码和超大尺码男装在线零售商:Bigtex
2019/06/22 全球购物
高级销售求职信
2014/02/21 职场文书
高中毕业生的个人自我评价
2014/02/21 职场文书
电工工作职责范本
2014/02/22 职场文书
个人自荐材料
2014/05/23 职场文书
社会主义核心价值观主题教育活动总结
2015/05/07 职场文书
电影建国大业观后感
2015/06/01 职场文书
离职告别感言
2015/08/04 职场文书
解决Pytorch中关于model.eval的问题
2021/05/22 Python
Python爬虫入门案例之爬取二手房源数据
2021/10/16 Python
Vue OpenLayer测距功能的实现
2022/04/20 Vue.js