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为tornado添加recaptcha验证码功能
Feb 26 Python
Python中文件遍历的两种方法
Jun 16 Python
Python 爬虫的工具列表大全
Jan 31 Python
Python实现的绘制三维双螺旋线图形功能示例
Jun 23 Python
opencv python 傅里叶变换的使用
Jul 21 Python
django框架实现模板中获取request 的各种信息示例
Jul 01 Python
使用python telnetlib批量备份交换机配置的方法
Jul 25 Python
Tkinter中复选菜单是否被选中的判断与设置方式
Mar 04 Python
使用keras根据层名称来初始化网络
May 21 Python
keras load model时出现Missing Layer错误的解决方式
Jun 11 Python
使用anaconda安装pytorch的实现步骤
Sep 03 Python
python集合的新增元素方法整理
Dec 07 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
Windows下的PHP5.0详解
2006/11/18 PHP
PHP获取浏览器信息类和客户端地理位置的2个方法
2014/04/24 PHP
ThinkPHP模板输出display用法分析
2014/11/26 PHP
完美利用Yii2微信后台开发的系列总结
2016/07/18 PHP
laravel框架 api自定义全局异常处理方法
2019/10/11 PHP
Laravel 登录后清空COOKIE的操作方法
2019/10/14 PHP
JS 强制设为首页的代码
2009/01/31 Javascript
JavaScript 继承详解(四)
2009/07/13 Javascript
Jquery实现的一种常用高亮效果示例代码
2014/01/28 Javascript
对Jquery中的ajax再封装,简化操作示例
2014/02/12 Javascript
javascript与css3动画结合使用小结
2015/03/11 Javascript
nodejs中使用多线程编程的方法实例
2015/03/24 NodeJs
JavaScript中String.match()方法的使用详解
2015/06/06 Javascript
iframe中子父类窗口调用JS的方法及注意事项
2015/08/25 Javascript
jQuery学习笔记之Ajax用法实例详解
2015/12/01 Javascript
省市联动效果的简单实现代码(推荐)
2016/06/06 Javascript
浅谈jquery采用attr修改form表单enctype不起作用的问题
2016/11/25 Javascript
Bootstrap基本组件学习笔记之导航(10)
2016/12/07 Javascript
jQuery Form表单取值的方法
2017/01/11 Javascript
vue与vue-i18n结合实现后台数据的多语言切换方法
2018/03/08 Javascript
微信小程序与公众号实现数据互通的方法
2019/07/25 Javascript
简述vue-cli中chainWebpack的使用方法
2019/07/30 Javascript
vue实现手机端省市区区域选择
2019/09/27 Javascript
python使用cPickle模块序列化实例
2014/09/25 Python
初步解析Python中的yield函数的用法
2015/04/03 Python
python出现"IndentationError: unexpected indent"错误解决办法
2017/10/15 Python
Python Celery多队列配置代码实例
2019/11/22 Python
使用Python实现分别输出每个数组
2019/12/06 Python
15款Python编辑器的优缺点,别再问我“选什么编辑器”啦
2020/10/19 Python
CSS3实现任意图片lowpoly动画效果实例
2017/05/11 HTML / CSS
最新的小工具和卓越的产品设计:Oh That Tech!
2019/08/07 全球购物
餐饮部总监岗位职责范文
2014/02/13 职场文书
2014小学植树节活动总结
2014/03/10 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
python3读取文件指定行的三种方法
2021/05/24 Python
Python趣味挑战之给幼儿园弟弟生成1000道算术题
2021/05/28 Python