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

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

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

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

----->

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

3.2.2 serial传送的方式

serial传送的方式有:

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

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

所以要进行转换你可以直接写成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()

结果:

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

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

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

测试通过。

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()还是有剩余的内容。

我发现此时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()

成功输出结果:

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

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

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()

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

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

 4 反思犯了很多

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

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

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

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

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

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

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

(0)

相关推荐

  • Python实现串口通信(pyserial)过程解析

    pyserial模块封装了对串口的访问,兼容各种平台. 安装 pip insatll pyserial 初始化 简单初始化示例 import serial ser = serial.Serial('com1', 9600, timeout=1) 所有参数 ser = serial.Serial( port=None, # number of device, numbering starts at # zero. if everything fails, the user # can specif

  • Python3简单实现串口通信的方法

    如下所示: import serial import sys import os import time import re def wait_for_cmd_OK():     while True:         line = ser.readline()         try:             print(line.decode('utf-8'),end='')         except:             pass         if ( re.search(b'

  • Python使用pyserial进行串口通信的实例

    安装pyserial pip install pyserial 查看可用的端口 # coding:utf-8 import serial.tools.list_ports plist = list(serial.tools.list_ports.comports()) if len(plist) <= 0: print("没有发现端口!") else: plist_0 = list(plist[0]) serialName = plist_0[0] serialFd = seri

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

    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

  • Python实现TCP通信的示例代码

    使用socket实现tcp通信,需导入socket模块 1.服务端 主要步骤: (1)创建socket:socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family:AF_INET , AF_INET6, AF_UNIX, AF_CAN, AF_PACKET,  AF_RDS type:SOCK_STREAM , SOCK_DGRAM, SOCK_RAW,或者其他SOCK_* proto:一般使用默认值0

  • python实现socket简单通信的示例代码

    首先先来简单介绍下socket: (具体更详细介绍的可以在网上找找,都讲得非常详细),这里主要是我自己的一些理解. socket是在应用层与传输层之间的一个抽象层,它的本质是编程接口,通过socket,才能实现TCP/IP协议. 它就是一个底层套件,用来处理最底层消息的接受和发送. socket翻译为套接字,可以把TCP/IP复杂的操作抽象为简单的几个接口来供应用层调用来实现进程在网络中的通信.socket起源于Unix,而Unix的基本要素之一就是"一切都为文件",即可以通过打开--

  • python 读取串口数据的示例

    python3 读取串口数据 demo 最近在写一个demo,zigbee串口连接树莓派,树莓派使用串口通信接受zigbee穿过来得值.其中我是用的树莓派是3代B+,zigbee每隔三秒钟从串口输出数据. 下面是python串口通信,但是不是linux的,是我在windows上写的测试demo,python版本是3. python串口读取数据 # TODO 串口读取数据 # Auther wjw import serial # 导入串口包 import time # 导入时间包 ser = se

  • Python实现端口扫描器的示例代码

    目录 socket概念 socket基本用法 创建tcp套接字 实现端口扫描 socket概念 socket又称套接字,可以看做是不同主机之间的进程进⾏双向通信的端点,简单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程,发出网络请求或者应答网络请求. socket起源于Unix,⽽Unix/Linux基本哲学之⼀就是"⼀切皆⽂件",对于⽂件⽤"打开"."读写"."关闭"模式来操作.而socket就是该模式的⼀

  • Python实现登录接口的示例代码

    之前写了Python实现登录接口的示例代码,最近需要回顾,就顺便发到随笔上了 要求: 1.输入用户名和密码 2.认证成功,显示欢迎信息 3.用户名3次输入错误后,退出程序 4.密码3次输入错误后,锁定用户名 Readme: 1.UserList.txt 是存放用户名和密码的文件,格式为:username: password,每行存放一条用户信息 2.LockList.txt 是存放已被锁定用户名的文件,默认为空 3.用户输入用户名,程序首先查询锁定名单 LockList.txt,如果用户名在里面

  • python实现log日志的示例代码

    源代码: # coding=utf-8 import logging import os import time LEVELS={'debug':logging.DEBUG,\ 'info':logging.INFO,\ 'warning':logging.WARNING,\ 'error':logging.ERROR,\ 'critical':logging.CRITICAL,} logger=logging.getLogger() level='default' def createFile

  • Python中字符串与编码示例代码

    在最新的Python 3版本中,字符串是以Unicode编码的,即Python的字符串支持多语言 编码和解码 字符串在内存中以Unicode表示,在操作字符串时,经常需要str和bytes互相转换   如果在网络上传输或保存到磁盘上,则从内存读到的数据就是str,要把str变为以字节为单位的bytes,称为编码   如果从网络或磁盘上读取字节流,则从网络或磁盘上读到的数据就是bytes,要把bytes变为str,称为解码   为避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行

  • python+selenium+chromedriver实现爬虫示例代码

    下载好所需程序 1.Selenium简介 Selenium是一个用于Web应用程序测试的工具,直接运行在浏览器中,就像真正的用户在操作一样. 2.Selenium安装 方法一:在Windows命令行(cmd)输入pip install selenium即可自动安装,安装完成后,输入pip show selenium可查看当前的版本 方法二:直接下载selenium包: selenium下载网址 Pychome安装selenium如果出现无法安装,参考以下博客 解决Pycharm无法使用已经安装S

  • Python实现ElGamal加密算法的示例代码

    在密码学中,ElGamal加密算法是一个基于迪菲-赫尔曼密钥交换的非对称加密算法.它在1985年由塔希尔·盖莫尔提出.GnuPG和PGP等很多密码学系统中都应用到了ElGamal算法. ElGamal加密算法可以定义在任何循环群G上.它的安全性取决于G上的离散对数难题. 使用Python实现ElGamal加密算法,完成加密解密过程,明文使用的是125位数字(1000比特). 代码如下: import random from math import pow a = random.randint(2

随机推荐