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获取邮件地址的方法
Jul 10 Python
Django框架中数据的连锁查询和限制返回数据的方法
Jul 17 Python
使用Python实现博客上进行自动翻页
Aug 23 Python
python 动态迁移solr数据过程解析
Sep 04 Python
PyTorch 随机数生成占用 CPU 过高的解决方法
Jan 13 Python
基于pytorch的lstm参数使用详解
Jan 14 Python
执行Python程序时模块报错问题
Mar 26 Python
200行python代码实现贪吃蛇游戏
Apr 24 Python
Jupyter Notebook 实现正常显示中文和负号
Apr 24 Python
Python+OpenCV图像处理——实现轮廓发现
Oct 23 Python
Selenium 安装和简单使用的实现
Dec 04 Python
Python开发五子棋小游戏
Apr 28 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
基于mysql的论坛(2)
2006/10/09 PHP
php5中类的学习
2008/03/28 PHP
PHP改进计算字符串相似度的函数similar_text()、levenshtein()
2014/10/27 PHP
javascript控制swfObject应用介绍
2012/11/29 Javascript
javascript实现日历控件(年月日关闭按钮)
2012/12/12 Javascript
如何用jquery控制表格奇偶行及活动行颜色
2014/04/20 Javascript
js使用DOM操作实现简单留言板的方法
2015/04/10 Javascript
解决jquery无法找到其他父级子集问题的方法
2016/05/10 Javascript
AngularJs  Creating Services详解及示例代码
2016/09/02 Javascript
jQuery 获取遍历获取table中每一个tr中的第一个td的方法
2016/10/05 Javascript
js中编码函数:escape,encodeURI与encodeURIComponent详解
2017/03/21 Javascript
nodejs集成sqlite使用示例
2017/06/05 NodeJs
浅谈HTTP 缓存的那些事儿
2018/10/17 Javascript
微信小程序实现商品属性联动选择
2019/02/15 Javascript
JQuery常见节点操作实例分析
2019/05/15 jQuery
ES6 async、await的基本使用方法示例
2020/06/06 Javascript
[02:36]DOTA2上海特锦赛 回忆电竞生涯的重要瞬间
2016/03/25 DOTA
[02:42]决战东方!DOTA2亚洲邀请赛重启荣耀之争
2017/03/17 DOTA
Python学习小技巧之利用字典的默认行为
2017/05/20 Python
使用Python来开发微信功能
2018/06/13 Python
使用python脚本实现查询火车票工具
2018/07/19 Python
python实现桌面壁纸切换功能
2019/01/21 Python
使用Filter过滤python中的日志输出的实现方法
2019/07/17 Python
python异常处理try except过程解析
2020/02/03 Python
Python sql注入 过滤字符串的非法字符实例
2020/04/03 Python
python调用jenkinsAPI构建jenkins,并传递参数的示例
2020/12/09 Python
python中pivot()函数基础知识点
2021/01/03 Python
英国家庭珠宝商:T. H. Baker
2018/02/08 全球购物
外贸主管求职简历的自我评价
2013/10/23 职场文书
自主招生自荐信
2013/12/08 职场文书
弘扬焦裕禄精神走群众路线思想汇报
2014/09/12 职场文书
委托证明书
2014/09/17 职场文书
2015年预备党员自我评价
2015/03/04 职场文书
公务员处分决定书
2015/06/25 职场文书
2019各种保证书范文
2019/06/24 职场文书
深入浅析Django MTV模式
2021/09/04 Python