K210学习笔记五:探索串口通信的奥妙

【K210】K210学习笔记五——串口通信

  • 前言
  • K210如何进行串口通信
  • K210串口配置
  • K210串口发送相关定义
  • K210串口接收相关定义
  • K210串口发送接收测试
  • 完整源码
  • 前言

    本人大四学生,电赛生涯已经走到尽头,一路上踩过不少坑,但运气也不错拿了两年省一,思来想去,决定开始写博客,将电赛经验分享一二,能力有限,高手轻喷。
    往期的博客讲述了 K210 的感光元件模块 sensor 的配置,机器视觉模块 image 中部分函数的使用(目前是用 find_blobs 函数实现一些寻找不同颜色的目标点,寻找不同颜色的线,后面会更新更多 image 模块中的函数使用方法),按键、LCD、LED的使用,以及定时器的使用。

    sensor 的学习笔记传送门
    【K210】K210学习笔记一——sensor
    image 的学习笔记传送门
    【K210】K210学习笔记二——image
    按键、LCD、LED的使用 的学习笔记传送门
    【K210】K210学习笔记三——按键、LCD、LED的使用
    定时器的使用
    【K210】K210学习笔记四——定时器的使用

    本文着重于 K210 如何进行串口通信,如果 MCU 之间无法通信,就算在 K210 上识别做的再好也没有用。我之前发过一篇K210和STM32通信、K210和OpenMV通信的博客,但我觉得那一份的代码还不够“通用”,因此我今天将串口通信的代码进行重置,希望能让大家觉得比上次那一期更好用,要是想回顾上一期代码,了解串口发送与接收,传送门如下。

    串口通信 传送门
    【串口通信】K210与STM32串口通信、K210与OpenMV串口通信

    K210如何进行串口通信

    K210串口配置

    串口配置需要导入串口相关模块,以提供硬件支持。UART、fm、GPIO、math这四个是必不可少的。其他模块是往期代码中的,我没有删除(主要是想让这份代码大家拿去就可以使用,不用一篇篇翻我的博客去添加那些模块的配置,按需删除不需要的部分)。

    # UART_V2.0 - By: FITQY - 周三 8 月 24 日 2022
    #__________________________________________________________________
    # 导入模块
    import sensor, time, image                                  # 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
    import utime                                                # 导入延时模块 utime
    from fpioa_manager import fm                                # 从 fpioa_manager 模块中导入 引脚注册模块 fm
    from Maix import GPIO                                       # 从 Maix 模块中导入 模块 GPIO
    import lcd                                                  # 导入 LCD 模块
    from machine import Timer, PWM, UART                        # 从 machine 模块中导入 定时器模块 Timer 脉宽调制模块 PWM 双向串行通信模块 UART
    import math                                                 # 导入数学函数模块 math
    
    

    然后就是对 K210 的串口进行设置,这里我设置了两个串口。K210的串口引脚是可以自行映射的,我用的 K210 是 01Studio 的,这一款 K210 的 P9 和 P6 引脚是相邻的,P7 和 P8 引脚是相邻的,因此我就把 P9 映射为 串口1的RX,P6 映射为 串口1的TX,P7 映射为 串口2的RX,P8 映射为 串口2的TX。波特率是设置为 921600,这个是可以更改的,你要使用 115200也是可以的,只要和其他硬件的串口波特率对的上都可以。至于其他设置,数据位8,校验位0,停止位1,一般都是这样设置,这个不建议更改。
    需要注意的是,不建议使用K210引脚号数偏大的引脚,比如26以后的,因为这些引脚似乎在接入SD卡、LCD、麦克风阵列等外设的时候会被占用。

    #__________________________________________________________________
    # 串口的使用
    # 串口1 设置 P9 RX P6 TX
    fm.register(9, fm.fpioa.UART1_RX, force = True)             # 配置 9 脚为 UART1_RX 强制注册
    fm.register(6, fm.fpioa.UART1_TX, force = True)             # 配置 6 脚为 UART1_TX 强制注册
    
    uart1 = UART(UART.UART1, 921600, 8, 0, 1)                   # 设置 uart1 为 串口1 波特率 921600 数据位 8位 校验位 0位 停止位 1位
    
    # 串口2 设置 P7 RX P8 TX
    fm.register(7, fm.fpioa.UART2_RX, force = True)             # 配置 7 脚为 UART2_RX 强制注册
    fm.register(8, fm.fpioa.UART2_TX, force = True)             # 配置 8 脚为 UART2_TX 强制注册
    
    uart2 = UART(UART.UART2, 921600, 8, 0, 1)                   # 设置 uart2 为 串口2 波特率 921600 数据位 8位 校验位 0位 停止位 1位
    
    

    K210引脚

    K210串口发送相关定义

    这里我定义了一个类,用来保存串口发送的相关信息,方便传入参数。
    串口发送数据的核心思想就是将要发送的数据打包成一个数组,然后将这个数组发送出去。如果不打包,接收端很难将数据正常解析出来,帧头的目的就是方便接收端找到数组的“头”,然后从这个“头”开始,逐个接收数据,有效数据长度位控制的是接收多少个有效数据(就是你要发送的数据),加有效数据长度位是方便后续添加新的需要发送的数据,另外接收端可以灵活接收不同长度的数据,最后接收端对接收数据进行校验,如果校验通过,则保存这些数据,这里用的校验方法是校验和。
    帧头我都是用 0xAA,因为 0xAA 转换成8位是 1010 1010 电平是有持续跳变的,可以提高点稳定性。

    需要注意的是一次最好不要发送太多的数据,我在接收端代码的定义中是定义有效数据长度不超过40个8位,如果要发送x,y,color,shape,flag,虽然好像这里只有五个数据,但x和y是需要使用两个8位来发送的,因此实际有效数据长度是7个8位。关于为什么要用两个8位来发,原因就是我感光元件设置的图像大小是QVGA,x轴坐标变化范围为0到319,y轴坐标变化范围为0到239,x超过了255因此需要用两个8位来发送,一个8位能保存的数据是从0到255,两个8位是可以保存0到65535的。至于像颜色标志位、形状标志位、目标标志位这些一个8位就足够了,比如用1、2、3分别表示红、绿、蓝,也就是在识别到红色的时候,加一句TSTM32.color = 1,以此类推。当然,这些参数都只是一个名字而已,想来保存什么标志位或者什么数据发送到主控,都可以根据个人需求定义,理解了就不难。
    还有一点就是,如果接收端经常出现乱序,不妨可以试一下更改帧头。当然,发送端发送的帧头和接收端识别接收的帧头要一并修改,这一点需要牢记。

    #__________________________________________________________________
    # 串口发送
    # 定义 UART 发送类
    class UART_Transmit():                                      # 定义 UART 发送类
        pack_flag   = 0                                         # 打包方法标志位
        head1  = 0x00                                           # uint8_t   帧头1
        head2  = 0x00                                           # uint8_t   帧头2
        x      = 0                                              # uint16_t  目标x轴坐标
        y      = 0                                              # uint16_t  目标y轴坐标
        color  = 0                                              # uint8_t   目标颜色标志位
        shape  = 0                                              # uint8_t   目标形状标志位
        flag   = 0                                              # uint8_t   目标标志位
        mode   = 0                                              # uint8_t   模式标志位
    
    # 实例化类
    TSTM32  = UART_Transmit()                                   # 实例化 UART_Transmit() 为 TSTM32
    TSTM32.pack_flag  = 1                                       # 打包方法为 方法1
    TSTM32.head1 = 0xAA                                         # TSTM32 的帧头1为 0xAA
    TSTM32.head2 = 0xAA                                         # TSTM32 的帧头2为 0xAA
    
    TOpenMV = UART_Transmit()                                   # 实例化 UART_Transmit() 为 TOpenMV
    TOpenMV.pack_flag  = 2                                      # 打包方法为 方法2
    TOpenMV.head1 = 0xAA                                        # TOpenMV 的帧头1为 0xAA
    TOpenMV.head2 = 0xAA                                        # TOpenMV 的帧头2为 0xAA
    
    # 定义打包函数
    def Pack_Data(TData):
    
        data = UART_Pack_Method(TData)                          # 根据不同的方法打包发送数据
    
        # 数据包的长度
        data_len = len(data)                                    # 获得数据包总长度
        data[2]  = data_len - 4                                 # 有效数据的长度 扣去 帧头1 帧头2 有效数据长度位 校验位
    
        # 校验和
        sum = 0                                                 # 和置零
        for i in range(0,data_len-1):
            sum = sum + data[i]                                 # 和累加
        data[data_len-1] = sum                                  # 和赋值 给数组最后一位发送 只保存低8位 溢出部分无效
    
        # 返回打包好的数据
        return data
    
    

    与之前不同的是,我将打包函数做成了一个可以带入参数的函数,这样就可以方便实现不同的打包需求,不需要每一次有新的需求的时候,将整段代码复制下来,然后改个名字。 如果你有新的打包需求,就只需要实例化一个新的串口发送类,然后将打包方法设置为3,在这里加个
    elif TData.pack_flag == 3:
    然后在这个 elif 里面写上你新的打包需求即可。

    # 串口数据打包方法函数
    def UART_Pack_Method(TData):
        if TData.pack_flag == 1:
            data = bytearray([TData.head1,                      # 帧头1
                              TData.head2,                      # 帧头2
                              0x00,                             # 有效数据长度 0x00 + data_len - 4
                              TData.mode,                       # 保存目标x轴坐标 高八位
                              0x00])                            # 数据和校验位
    
        elif TData.pack_flag == 2:
            data = bytearray([TData.head1,                      # 帧头1
                              TData.head2,                      # 帧头2
                              0x00,                             # 有效数据长度 0x00 + data_len - 4
                              TData.x>>8,                       # 保存目标x轴坐标 高八位
                              TData.x,                          # 保存目标x轴坐标 低八位
                              TData.y>>8,                       # 保存目标y轴坐标 高八位
                              TData.y,                          # 保存目标y轴坐标 低八位
                              TData.color,                      # 保存目标颜色标志位
                              TData.shape,                      # 保存目标形状标志位
                              TData.flag,                       # 保存目标标志位
                              0x00])                            # 数据和校验位
    
        return data
    
    

    K210串口接收相关定义

    串口接收数据需要用数组来缓冲,因此还需要定义一个类来缓冲串口数据。与之前不同的是,我新增了一点定义,目的是为了让串口接收的函数可以带入参数,就不用在每次有新的接收方法的时候将整段的代码复制下来改名字了。

    #__________________________________________________________________
    # 串口接收
    # 定义 UART 接收类
    class UART_Receive(object):                                 # 定义 UART 接收类
        uart_buf  = []                                          # 串口缓冲区数组
        data_len  = 0                                           # 有效数据长度
        data_cnt  = 0                                           # 总数据长度
        state     = 0                                           # 接收状态
        buf_len   = 0                                           # 保存串口等待字节的数量
    
        head1     = 0x00                                        # 接收帧头1
        head2     = 0x00                                        # 接收帧头2
        lenmax    = 0                                           # 有效数据最大长度
        save_flag = 0                                           # 保存方法标志位
    
    # 实例化类
    RSTM32  = UART_Receive()                                    # 实例化 UART_Receive() 为 RSTM32
    RSTM32.head1        = 0xAA                                  # RSTM32 的帧头1为 0xAA
    RSTM32.head2        = 0xAA                                  # RSTM32 的帧头2为 0xAA
    RSTM32.lenmax       = 40                                    # RSTM32 的有效数据最大长度为 40
    RSTM32.save_flag    = 1                                     # RSTM32 的保存方法标志位为 1
    
    ROpenMV = UART_Receive()                                    # 实例化 UART_Receive() 为 ROpenMV
    ROpenMV.head1       = 0xAA                                  # ROpenMV 的帧头1为 0xAA
    ROpenMV.head2       = 0xAA                                  # ROpenMV 的帧头2为 0xAA
    ROpenMV.lenmax      = 40                                    # ROpenMV 的有效数据最大长度为 40
    ROpenMV.save_flag   = 2                                     # ROpenMV 的保存方法标志位为 2
       
    

    首先是串口数据的读取,这个被我制成了可以带入参数的读取函数,每次有新的需求也不需要整段复制改名字。

    # 定义串口数据读取函数
    def UART_Read(RData, uart):
        RData.buf_len = uart.any()                              # 检查 串口 是否有内容需要读取 返回等待的字节数量(可能为0)
        for i in range(0, RData.buf_len):                       # 读取 RData.buf_len 个数据
            Receive_Data(RData, uart.readchar())                # 接收单个数据 uart.readchar() 然后将这个数据传递到函数 Receive_Data() 进行 数据接收
            
    

    串口数据接收函数的执行流程如下。
    默认状态为状态0,若传入的数据等于帧头1,则保存该数据,然后进入状态1。
    进入状态1后,若传入的数据等于帧头2,则保存该数据,然后进入状态2。
    进入状态2后,判断需要接收的有效数据长度是否小于设定的有效数据最大长度,若小于,则保存该数据以及总数据长度,然后进入状态3。
    进入状态3后,开始接收有效数据长度个数据,接收完毕会进入状态4。
    进入状态4后,保存最后一位校验位,然后将数组以及数据总长传入解析函数进行解析。
    解析完会将状态置0,数组清空,准备下一次接收。
    这个同样也被我制成了可以带入参数的接收函数,每次有新的需求也不需要整段复制改名字。

    # 定义串口数据接收函数
    def Receive_Data(RData, buf):
        if RData.state == 0 and buf == RData.head1:             # 判断帧头1是否符合要求 符合则进入下一个状态
            RData.state = 1                                     # 更改状态为 1
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 1 and buf == RData.head2:           # 判断帧头2是否符合要求 符合则进入下一个状态
            RData.state = 2                                     # 更改状态为 2
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 2 and buf < RData.lenmax:           # 有效数据长度位 规定有效数据长度小于40 符合则进入下一个状态
            RData.state = 3                                     # 更改状态为 3
            RData.data_len = buf                                # 获得有效数据长度
            RData.data_cnt = buf + 4                            # 获得总数据长度 总数据长度 = 帧头1 + 帧头2 + 有效数据长度位 + 有效数据 + 校验位
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 3 and RData.data_len > 0:           # 存储有效数据长度个数据
            RData.data_len = RData.data_len - 1                 # 每存储一次 还需要存储的数据个数减1
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
            if RData.data_len == 0:                             # 直到存储完毕
                RData.state = 4                                 # 进入下一个状态
    
        elif RData.state == 4:                                  # 当接收到存储完毕的信息
            RData.uart_buf.append(buf)                          # 保存最后一位校验位 将这个数据添加到数组末尾
            RData.state = 0                                     # 状态重置为0 调用串口数据解析函数进行数据解析
            Parse_Data(RData)                                   # 解析数据
            #print(RData.uart_buf)                              # 打印接收数组 若接收结果不对 可取消注释查看数组
            RData.uart_buf = []                                 # 清空缓冲区 准备下次接收数据
    
        else:                                                   # 不满足以上条件 视为接收出错 重置状态为0 丢弃所有数据 准备下一次接收数据
            RData.state = 0                                     # 重置状态为0
            RData.uart_buf = []                                 # 清空缓冲区 准备下一次接收数据
    
    

    解析函数其实就是判断接收到的数据累加和是否等于接收到的校验位的值,等于则校验成功,保存数据,不等于则退出,不保存数据。与往期不同的是,这个同样也被我制成了可以带入参数的解析函数,每次有新的需求也不需要整段复制改名字。

    # 定义串口数据解析函数
    def Parse_Data(PData):
        # 和累加
        sum = 0                                                 # 和置0
        i = 0                                                   # 已循环次数置0
        while i < (PData.data_cnt - 1):                         # 循环累加
            sum = sum + PData.uart_buf[i]                       # 累加求数组和
            i = i + 1                                           # 已循环次数自增
        # 求余 因为 校验和 为 8 位 超出部分无效 因此只校验 低8位 即可
        sum = sum % 256                                         # 和对256取余 得低八位
    
        # 和校验失败则退出
        if sum != PData.uart_buf[PData.data_cnt - 1]:           # 和取余结果若不等于校验位的值
            return                                              # 退出
    
        # 和校验成功则根据保存方法的不同 接收数据
        UART_Save_Method(PData)
        
    

    然后说一下数据的保存方法,这里我定义了两种。
    假设发送过来的数据包是
    AA AA 01 01 56
    数组的下标从0开始,AA是帧头,第一个01是有效数据长度位,第二个01是要被保存的模式,56是校验和,因此数组下标范围0到4。
    第二个01在数组中的下标是3,因此数组下标3的数据被保存到K210.mode
    需要注意的是,如果用一个8位来发送一个数据,那么这个数据只能是0到255之间的值,如果要发送超过255的值,则需要用两个8位来发送。
    比如需要发送数据为 257 258 3 4 5 而一个8位只能保存0到255,那么就需要将257分成两个8位发送,第一个8位是高八位,需要乘以256还原,第二个8位是低8位,因此257 = 256 + 1 也就是 01 01,所以258就是 01 02
    因此整个数组为
    AA AA 07 01 01 01 02 03 04 05 6C
    K210.x 所对应的高8位在数组中下标是3,低8位在数组中下标是4,数组下标3的数据是01,下标4的数据是01,则有
    K210.x = 1 * 256 + 1 = 257
    以此类推
    当然,如果你有新的保存需求,只需要加个 elif PData.save_flag == 3:
    然后在这个 elif 中写下你新的保存需求即可

    需要注意的是,最好加一个数据长度的判断,PData.uart_buf[2] 是有效数据长度位,比如发送过来1个数据,那么下标 3 和 下标 4(此时的下标4是校验位) 是存在的,但如果设置保存方法标志的时候错误设置成了2,则下标5之后的所有值都是不存在的,运行就会出错。

    # 串口数据保存方法函数
    def UART_Save_Method(PData):
        if PData.save_flag == 1 and PData.uart_buf[2] > 0:
            K210.mode =  PData.uart_buf[3]
    
        elif PData.save_flag == 2 and PData.uart_buf[2] > 6:
            K210.x      = PData.uart_buf[3]*256 + PData.uart_buf[4]
            K210.y      = PData.uart_buf[5]*256 + PData.uart_buf[6]
            K210.color  = PData.uart_buf[7]
            K210.shape  = PData.uart_buf[8]
            K210.flag   = PData.uart_buf[9]
        
    

    K210串口发送接收测试

    这里就用 K210 进行测试即可,自己发送,自己接收,当然你要是用我之前那篇博客中的测试方法进行测试也是没问题的。后面我回学校了会更新与32通信的测试。
    自发自收就很简单了,只需要将 K210 的6脚和9脚,7脚和8脚连到一起就好,用跳线帽将它们短接即可。

    然后就是设置一下发送的信息。

    # 串口发送测试信息赋值
    TSTM32.mode   = 1
    
    TOpenMV.x     = 65535
    TOpenMV.y     = 65536
    TOpenMV.color = 255
    TOpenMV.shape = 256
    TOpenMV.flag  = 3
    
    

    然后上电运行,在串行终端中将接收到的参数打印出来即可。这里可以看出,如果用两个8位来发送数据,最大可以支持到65535,如果超过这个值,就会变成0了,而用一个8位来发送,就只能支持到255。

    完整源码

    完整源码如下所示,大家可以复制该源码,进行测试,要是遇到什么问题可以问我哦!我们下期再见~!
    现在串口完成啦!那么现在开始,K210这一系列可能不会日更,后面我会着手于写K210跑训练模型,以及怎么训练模型的教程,难度比现在来得大,因此更新的速度会放缓
    但我一定会保持质量,做到句句有注释,句句有原因,思路清晰, 喜欢我的教程的,希望能换到你们的一个关注。

    # UART_V2.0 - By: FITQY - 周三 8 月 24 日 2022
    #__________________________________________________________________
    # 导入模块
    import sensor, time, image                                  # 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
    import utime                                                # 导入延时模块 utime
    from fpioa_manager import fm                                # 从 fpioa_manager 模块中导入 引脚注册模块 fm
    from Maix import GPIO                                       # 从 Maix 模块中导入 模块 GPIO
    import lcd                                                  # 导入 LCD 模块
    from machine import Timer, PWM, UART                        # 从 machine 模块中导入 定时器模块 Timer 脉宽调制模块 PWM 双向串行通信模块 UART
    import math                                                 # 导入数学函数模块 math
    
    #__________________________________________________________________
    # 感光元件设置
    sensor.reset()                                              # 重置并初始化感光元件 默认设置为 摄像头频率 24M 不开启双缓冲模式
    #sensor.reset(freq=24000000, dual_buff=True)                # 设置摄像头频率 24M 开启双缓冲模式 会提高帧率 但内存占用增加
    
    sensor.set_pixformat(sensor.RGB565)                         # 设置图像格式为 RGB565 (彩色) 除此之外 还可设置格式为 GRAYSCALE 或者 YUV422
    sensor.set_framesize(sensor.QVGA)                           # 设置图像大小为 QVGA (320 x 240) 像素个数 76800 K210最大支持格式为 VGA
    
    sensor.set_auto_exposure(0)                                 # 关闭自动曝光
    #sensor.set_auto_exposure(0, exposure=120000)               # 设置手动曝光 曝光时间 120000 us
    
    sensor.set_auto_gain(0)                                     # 关闭画面增益
    sensor.set_auto_whitebal(0)                                 # 关闭RGB自动增益(白平衡)
    #sensor.set_auto_gain(0, gain_db = 12)                      # 设置画面增益 17 dB 影响实时画面亮度
    #sensor.set_auto_whitebal(0, rgb_gain_db = (0,0,0))         # 设置RGB增益 0 0 0 dB 影响画面色彩呈现效果 在 K210 上无法调节增益 初步判定是感光元件 ov2640 无法支持
    
    #sensor.set_contrast(0)                                     # 设置对比度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
    #sensor.set_brightness(0)                                   # 设置亮度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
    #sensor.set_saturation(0)                                   # 设置饱和度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
    
    sensor.set_vflip(1)                                         # 打开垂直翻转 如果是 01Studio 的 K210 不开启会导致画面方向与运动方向相反
    sensor.set_hmirror(1)                                       # 打开水平镜像 如果是 01Studio 的 K210 不开启会导致画面方向与运动方向相反
    
    sensor.skip_frames(time = 2000)                             # 延时跳过2s 等待感光元件稳定
    
    #__________________________________________________________________
    # 创建时钟对象
    clock = time.clock()                                        # 创建时钟对象 clock
    
    #__________________________________________________________________
    # 目标点输入类 举例 2022 年 TI 杯送货无人机 中的目标点输入部分
    class Point_Input():
        point1  = 0                                             # 目标点 1
        point2  = 0                                             # 目标点 2
        cross   = 0                                             # 穿圈模式标志位
        send    = 0                                             # 目标点发送标志位
    
    point = Point_Input()                                       # 实例化目标点输入类 Point_Input() 为 point
    
    # 按键控制下的目标点获取函数
    def Point_Control(ckey):
        if ckey.control == 1:                                   # 按键确认及发送控制标志位为1 即 按键3 按下
            ckey.control = 0                                    # 重置标志位
            if ckey.cs == 0:                                    # 如果当前为模式 0
                point.send = 1                                  # 目标点发送标志置为 1 串口开始发送
    
            elif ckey.cs == 1:                                  # 如果当前为模式 1
                point.point1 = ckey.cinput                      # 将按键输入值赋值给目标点 1
    
            elif ckey.cs == 2:                                  # 如果当前为模式 2
                point.point2 = ckey.cinput                      # 将按键输入值赋值给目标点 2
    
            elif ckey.cs == 3:                                  # 如果当前为模式 3
                point.cross = ckey.cinput                       # 将按键输入值赋值给 穿圈模式标志位
    
        if ckey.csflag == 1:                                    # 如果检测到按键模式切换
            ckey.csflag = 0                                     # 重置按键模式切换标志位
            ckey.cinput = 0                                     # 重置按键输入值
    
    #__________________________________________________________________
    # 按键的使用
    # 定义按键控制类
    class Key_Control():                                        # 定义按键控制类
        cnt     = 0                                             # 按键计数值
        cs      = 0                                             # 按键模式选择标志位
        csmax   = 0                                             # 按键模式上限
        csflag  = 0                                             # 按键模式切换标志位
        cinput  = 0                                             # 按键输入值保存位
        control = 0                                             # 按键确认及发送控制标志位
    
    # 实例化按键类
    key = Key_Control()                                         # 实例化按键控制类 Key_Control() 为 key
    key.csmax = 3                                               # 按键模式上限为 3 即最多有 4 个模式 (0, 1, 2, 3)
    
    # 注册按键引脚
    fm.register(16, fm.fpioa.GPIOHS0, force = True)             # 配置 16 脚为 KEY0 使用高速 GPIO 口 强制注册
    fm.register(18, fm.fpioa.GPIOHS1, force = True)             # 配置 18 脚为 KEY1 使用高速 GPIO 口 强制注册
    fm.register(19, fm.fpioa.GPIOHS2, force = True)             # 配置 19 脚为 KEY2 使用高速 GPIO 口 强制注册
    fm.register(20, fm.fpioa.GPIOHS3, force = True)             # 配置 20 脚为 KEY3 使用高速 GPIO 口 强制注册
    
    # 创建按键对象
    KEY0 = GPIO(GPIO.GPIOHS0, GPIO.IN, GPIO.PULL_UP)            # 创建按键对象 KEY0
    KEY1 = GPIO(GPIO.GPIOHS1, GPIO.IN, GPIO.PULL_UP)            # 创建按键对象 KEY1
    KEY2 = GPIO(GPIO.GPIOHS2, GPIO.IN, GPIO.PULL_UP)            # 创建按键对象 KEY2
    KEY3 = GPIO(GPIO.GPIOHS3, GPIO.IN, GPIO.PULL_UP)            # 创建按键对象 KEY3
    
    # 中断回调函数 KEY0 控制按键模式选择
    def Key0_Switch(KEY0):
        utime.sleep_ms(10)                                      # 延时 10ms 消除按键抖动
        if KEY0.value() == 0:                                   # 确认 按键0 按下
            key.csflag = 1                                      # 标记按键模式切换
            if key.cs < key.csmax:                              # 若当前按键模式值小于按键模式选择上限值
                key.cs = key.cs + 1                             # 控制按键模式选择 自增
            else:                                               # 若达到上限 则重新从 0 开始
                key.cs = 0
    
    # 中断回调函数 KEY1 按键输入值自增
    def Key1_Switch(KEY1):
        utime.sleep_ms(10)                                      # 延时 10ms 消除按键抖动
        if KEY1.value() == 0:                                   # 确认 按键1 按下
            key.cinput = key.cinput + 1                         # 按键输入值自增
    
    # 中断回调函数 KEY2 按键输入值自减
    def Key2_Switch(KEY2):
        utime.sleep_ms(10)                                      # 延时 10ms 消除按键抖动
        if KEY2.value() == 0:                                   # 确认 按键2 按下
            key.cinput = key.cinput - 1                         # 按键输入值自减
    
    # 中断回调函数 KEY3 按键确认及发送控制标志位
    def Key3_Switch(KEY3):
        utime.sleep_ms(10)                                      # 延时 10ms 消除按键抖动
        if KEY3.value() == 0:                                   # 确认按键按下
            key.control = 1                                     # 按键确认及发送控制标志位
    
    # 开启中断 下降沿触发
    KEY0.irq(Key0_Switch, GPIO.IRQ_FALLING)                     # 开启 按键0 外部中断 下降沿触发
    KEY1.irq(Key1_Switch, GPIO.IRQ_FALLING)                     # 开启 按键1 外部中断 下降沿触发
    KEY2.irq(Key2_Switch, GPIO.IRQ_FALLING)                     # 开启 按键2 外部中断 下降沿触发
    KEY3.irq(Key3_Switch, GPIO.IRQ_FALLING)                     # 开启 按键3 外部中断 下降沿触发
    
    #__________________________________________________________________
    # LED的使用
    # 注册 LED 引脚
    fm.register(14, fm.fpioa.GPIO2, force = True)               # 配置 14 脚为 LED_R 强制注册
    fm.register(13, fm.fpioa.GPIO1, force = True)               # 配置 13 脚为 LED_G 强制注册
    fm.register(12, fm.fpioa.GPIO0, force = True)               # 配置 12 脚为 LED_B 强制注册
    
    # 创建 LED 对象
    LED_R = GPIO(GPIO.GPIO2, GPIO.OUT)                          # 创建 LED_R 对象
    LED_G = GPIO(GPIO.GPIO1, GPIO.OUT)                          # 创建 LED_G 对象
    LED_B = GPIO(GPIO.GPIO0, GPIO.OUT)                          # 创建 LED_B 对象
    
    # LED控制函数
    def LED_Control(led_flag):                                  # LED控制函数 根据传入 led_flag 点亮对应的灯 需要注意的是 0为点亮 1为熄灭
        if led_flag == 0:                                       # 传入参数为 0 所有灯打开
            LED_R.value(0)
            LED_G.value(0)
            LED_B.value(0)
    
        elif led_flag == 1:                                     # 传入参数为 1 所有灯关闭
            LED_R.value(1)
            LED_G.value(1)
            LED_B.value(1)
    
        elif led_flag == 2:                                     # 传入参数为 2 红灯常亮
            LED_R.value(0)
            LED_G.value(1)
            LED_B.value(1)
    
        elif led_flag == 3:                                     # 传入参数为 3 绿灯常亮
            LED_R.value(1)
            LED_G.value(0)
            LED_B.value(1)
    
        elif led_flag == 4:                                     # 传入参数为 4 蓝灯常亮
            LED_R.value(1)
            LED_G.value(1)
            LED_B.value(0)
    
        else:                                                   # 其他情况 紫灯
            LED_R.value(0)
            LED_G.value(1)
            LED_B.value(0)
    
    #__________________________________________________________________
    # 定时器的使用
    # 定义定时器属性类
    class Timer_Property():
        cnt     = 0                                             # 定时器计数值
        cnt_max = 0                                             # 定时器计数值上限
        period  = 0                                             # 定时器周期
        freq    = 0                                             # 定时器频率
    
    # 定时器0 配置_______________________________________________________
    # 定时器0 实例化类
    timer0 = Timer_Property()                                   # 实例化定时器属性类 Timer_Property() 为 timer0
    timer0.cnt_max = 9                                          # 设定 定时器0 的计数值上限为 9
    timer0.period = 100                                         # 设定 定时器0 的周期为 100
    
    # 定时器0 定义回调函数
    def Timer0_Back(tim0):
        if timer0.cnt < timer0.cnt_max:                         # 若 定时器0 的计数值小于 定时器0 的计数值上限
            timer0.cnt = timer0.cnt + 1                         # 计数值自增
        else:
            timer0.cnt = 0                                      # 超出计数值上限 则计数值重置为0
    
    # 定时器0 初始化
    tim0 = Timer(Timer.TIMER0,                                  # 定时器编号 定时器0
                Timer.CHANNEL0,                                 # 定时器通道 通道0
                mode = Timer.MODE_PERIODIC,                     # 定时器模式 周期性
                unit = Timer.UNIT_MS,                           # 定时器周期单位 ms
                period = timer0.period,                         # 定时器周期 timer0.period 若 unit 为 Timer.UNIT_MS 则周期为 timer0.period ms
                callback = Timer0_Back)                         # 定时器触发中断后执行的回调函数 Timer0_Back
    
    # 定时器1 配置_______________________________________________________
    # 电机类定义
    class Motor_Property():
        motor1      = 0                                         # 电机1 占空比
        motor2      = 0                                         # 电机2 占空比
        motor3      = 0                                         # 电机3 占空比
        motor4      = 0                                         # 电机4 占空比
    
        motor1_pin  = 0                                         # 电机1 引脚
        motor2_pin  = 0                                         # 电机2 引脚
        motor3_pin  = 0                                         # 电机3 引脚
        motor4_pin  = 0                                         # 电机4 引脚
    
        control_x   = 0                                         # 被控坐标 x
        control_y   = 0                                         # 被控坐标 y
    
    # 实例化电机类
    motor = Motor_Property()                                    # 实例化电机类 Motor_Property() 为 motor
    motor.motor1 = 50                                           # 电机1的占空比 初始设置为 50%
    motor.motor2 = 50                                           # 电机2的占空比 初始设置为 50%
    motor.motor1_pin  = 14                                      # 电机1的引脚 14为红灯引脚 这里先用灯的亮灭观察效果
    motor.motor2_pin  = 13                                      # 电机2的引脚 13为绿灯引脚 这里先用灯的亮灭观察效果
    
    # 定时器1 实例化类
    timer1 = Timer_Property()                                   # 实例化定时器属性类 Timer_Property() 为 timer1
    timer1.freq = 1000                                          # 设定 定时器1 的频率为 1000
    
    # 定时器1 通道0 初始化
    tim1_ch0 = Timer(Timer.TIMER1,                              # 定时器编号 定时器1
                     Timer.CHANNEL0,                            # 定时器通道 通道0
                     mode = Timer.MODE_PWM)                     # 定时器模式 PWM
    
    # 定时器1 通道1 初始化
    tim1_ch1 = Timer(Timer.TIMER1,                              # 定时器编号 定时器1
                     Timer.CHANNEL1,                            # 定时器通道 通道1
                     mode = Timer.MODE_PWM)                     # 定时器模式 PWM
    
    # 创建对象 电机1 通道为 定时器1的通道0 频率为 定时器1的频率 占空比为 电机1的占空比 引脚为 电机1的引脚
    motor1 = PWM(tim1_ch0, freq = timer1.freq, duty = motor.motor1, pin = motor.motor1_pin)
    
    # 创建对象 电机2 通道为 定时器1的通道1 频率为 定时器1的频率 占空比为 电机2的占空比 引脚为 电机2的引脚
    motor2 = PWM(tim1_ch1, freq = timer1.freq, duty = motor.motor2, pin = motor.motor2_pin)
    
    # 定义电机占空比控制函数
    def Motor_Control(motor, x):
        val = 0
        if x < motor.control_x:                                 # 若 当前坐标 小于 被控坐标x 即当前状态小车在目标的 左边
            val = (motor.control_x - x) * 0.3125                # 获取坐标差值 并转换为 0~50 之间的值
            motor.motor1 = 50 - val                             # 减小 电机1 占空比 电机1为左电机 使小车右转
            motor.motor2 = 50 + val                             # 增大 电机2 占空比 电机2为右电机 使小车右转
    
        elif x > motor.control_x:                               # 若 当前坐标 大于 被控坐标x 即当前状态小车在目标的 右边
            val = (x - motor.control_x) * 0.3125                # 获取坐标差值 并转换为 0~50 之间的值
            motor.motor1 = 50 + val                             # 增大 电机1 占空比 电机1为左电机 使小车左转
            motor.motor2 = 50 - val                             # 减小 电机2 占空比 电机2为右电机 使小车左转
    
        motor.motor1 = int(motor.motor1)                        # 将 电机1占空比 转换为 整数
        motor.motor2 = int(motor.motor2)                        # 将 电机1占空比 转换为 整数
    
    #__________________________________________________________________
    # 串口的使用
    # 串口1 设置 P9 RX P6 TX
    fm.register(9, fm.fpioa.UART1_RX, force = True)             # 配置 9 脚为 UART1_RX 强制注册
    fm.register(6, fm.fpioa.UART1_TX, force = True)             # 配置 6 脚为 UART1_TX 强制注册
    
    uart1 = UART(UART.UART1, 115200, 8, 0, 1)                   # 设置 uart1 为 串口1 波特率 921600 数据位 8位 校验位 0位 停止位 1位
    
    # 串口2 设置 P7 RX P8 TX
    fm.register(7, fm.fpioa.UART2_RX, force = True)             # 配置 7 脚为 UART2_RX 强制注册
    fm.register(8, fm.fpioa.UART2_TX, force = True)             # 配置 8 脚为 UART2_TX 强制注册
    
    uart2 = UART(UART.UART2, 921600, 8, 0, 1)                   # 设置 uart2 为 串口2 波特率 921600 数据位 8位 校验位 0位 停止位 1位
    
    #__________________________________________________________________
    # 串口发送
    # 定义 UART 发送类
    class UART_Transmit():                                      # 定义 UART 发送类
        pack_flag   = 0                                         # 打包方法标志位
        head1  = 0x00                                           # uint8_t   帧头1
        head2  = 0x00                                           # uint8_t   帧头2
        x      = 0                                              # uint16_t  目标x轴坐标
        y      = 0                                              # uint16_t  目标y轴坐标
        color  = 0                                              # uint8_t   目标颜色标志位
        shape  = 0                                              # uint8_t   目标形状标志位
        flag   = 0                                              # uint8_t   目标标志位
        mode   = 0                                              # uint8_t   模式标志位
    
    # 实例化类
    TSTM32  = UART_Transmit()                                   # 实例化 UART_Transmit() 为 TSTM32
    TSTM32.pack_flag  = 1                                       # 打包方法为 方法1
    TSTM32.head1 = 0xAA                                         # TSTM32 的帧头1为 0xAA
    TSTM32.head2 = 0xAA                                         # TSTM32 的帧头2为 0xAA
    
    TOpenMV = UART_Transmit()                                   # 实例化 UART_Transmit() 为 TOpenMV
    TOpenMV.pack_flag  = 2                                      # 打包方法为 方法2
    TOpenMV.head1 = 0xAA                                        # TOpenMV 的帧头1为 0xAA
    TOpenMV.head2 = 0xAA                                        # TOpenMV 的帧头2为 0xAA
    
    # 定义打包函数
    def Pack_Data(TData):
    
        data = UART_Pack_Method(TData)                          # 根据不同的方法打包发送数据
    
        # 数据包的长度
        data_len = len(data)                                    # 获得数据包总长度
        data[2]  = data_len - 4                                 # 有效数据的长度 扣去 帧头1 帧头2 有效数据长度位 校验位
    
        # 校验和
        sum = 0                                                 # 和置零
        for i in range(0,data_len-1):
            sum = sum + data[i]                                 # 和累加
        data[data_len-1] = sum                                  # 和赋值 给数组最后一位发送 只保存低8位 溢出部分无效
    
        # 返回打包好的数据
        return data
    
    #__________________________________________________________________
    # 串口接收
    # 定义 UART 接收类
    class UART_Receive(object):                                 # 定义 UART 接收类
        uart_buf  = []                                          # 串口缓冲区数组
        data_len  = 0                                           # 有效数据长度
        data_cnt  = 0                                           # 总数据长度
        state     = 0                                           # 接收状态
        buf_len   = 0                                           # 保存串口等待字节的数量
    
        head1     = 0x00                                        # 接收帧头1
        head2     = 0x00                                        # 接收帧头2
        lenmax    = 0                                           # 有效数据最大长度
        save_flag = 0                                           # 保存方法标志位
    
    # 实例化类
    RSTM32  = UART_Receive()                                    # 实例化 UART_Receive() 为 RSTM32
    RSTM32.head1        = 0xAA                                  # RSTM32 的帧头1为 0xAA
    RSTM32.head2        = 0xAA                                  # RSTM32 的帧头2为 0xAA
    RSTM32.lenmax       = 40                                    # RSTM32 的有效数据最大长度为 40
    RSTM32.save_flag    = 1                                     # RSTM32 的保存方法标志位为 1
    
    ROpenMV = UART_Receive()                                    # 实例化 UART_Receive() 为 ROpenMV
    ROpenMV.head1       = 0xAA                                  # ROpenMV 的帧头1为 0xAA
    ROpenMV.head2       = 0xAA                                  # ROpenMV 的帧头2为 0xAA
    ROpenMV.lenmax      = 40                                    # ROpenMV 的有效数据最大长度为 40
    ROpenMV.save_flag   = 2                                     # ROpenMV 的保存方法标志位为 2
    
    # 定义串口数据读取函数
    def UART_Read(RData, uart):
        RData.buf_len = uart.any()                              # 检查 串口 是否有内容需要读取 返回等待的字节数量(可能为0)
        for i in range(0, RData.buf_len):                       # 读取 RData.buf_len 个数据
            Receive_Data(RData, uart.readchar())                # 接收单个数据 uart.readchar() 然后将这个数据传递到函数 Receive_Data() 进行 数据接收
    
    # 定义串口数据接收函数
    def Receive_Data(RData, buf):
        if RData.state == 0 and buf == RData.head1:             # 判断帧头1是否符合要求 符合则进入下一个状态
            RData.state = 1                                     # 更改状态为 1
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 1 and buf == RData.head2:           # 判断帧头2是否符合要求 符合则进入下一个状态
            RData.state = 2                                     # 更改状态为 2
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 2 and buf < RData.lenmax:           # 有效数据长度位 规定有效数据长度小于40 符合则进入下一个状态
            RData.state = 3                                     # 更改状态为 3
            RData.data_len = buf                                # 获得有效数据长度
            RData.data_cnt = buf + 4                            # 获得总数据长度 总数据长度 = 帧头1 + 帧头2 + 有效数据长度位 + 有效数据 + 校验位
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
    
        elif RData.state == 3 and RData.data_len > 0:           # 存储有效数据长度个数据
            RData.data_len = RData.data_len - 1                 # 每存储一次 还需要存储的数据个数减1
            RData.uart_buf.append(buf)                          # 将这个数据添加到数组末尾
            if RData.data_len == 0:                             # 直到存储完毕
                RData.state = 4                                 # 进入下一个状态
    
        elif RData.state == 4:                                  # 当接收到存储完毕的信息
            RData.uart_buf.append(buf)                          # 保存最后一位校验位 将这个数据添加到数组末尾
            RData.state = 0                                     # 状态重置为0 调用串口数据解析函数进行数据解析
            Parse_Data(RData)                                   # 解析数据
            #print(RData.uart_buf)                              # 打印接收数组 若接收结果不对 可取消注释查看数组
            RData.uart_buf = []                                 # 清空缓冲区 准备下次接收数据
    
        else:                                                   # 不满足以上条件 视为接收出错 重置状态为0 丢弃所有数据 准备下一次接收数据
            RData.state = 0                                     # 重置状态为0
            RData.uart_buf = []                                 # 清空缓冲区 准备下一次接收数据
    
    # 定义串口数据解析函数
    def Parse_Data(PData):
        # 和累加
        sum = 0                                                 # 和置0
        i = 0                                                   # 已循环次数置0
        while i < (PData.data_cnt - 1):                         # 循环累加
            sum = sum + PData.uart_buf[i]                       # 累加求数组和
            i = i + 1                                           # 已循环次数自增
        # 求余 因为 校验和 为 8 位 超出部分无效 因此只校验 低8位 即可
        sum = sum % 256                                         # 和对256取余 得低八位
    
        # 和校验失败则退出
        if sum != PData.uart_buf[PData.data_cnt - 1]:           # 和取余结果若不等于校验位的值
            return                                              # 退出
    
        # 和校验成功则根据保存方法的不同 接收数据
        UART_Save_Method(PData)
    
    #__________________________________________________________________
    # 寻找色块
    # 定义类
    class Color_Property():
        cx                      =  0                            # 色块 x轴 中心坐标
        cy                      =  0                            # 色块 y轴 中心坐标
        flag                    =  0                            # 色块标志位 1 找到 0 未找到
        color                   =  0                            # 色块颜色标志位 例如 你可以用 1 来表示 黑色
        density                 =  0                            # 色块密度比 反映色块锁定程度 值越大 锁定程度越好
        pixels_max              =  0                            # 色块像素最大值
        led_flag                =  0                            # LED标志位 方便调试用
    
        color_threshold         = (0, 0, 0, 0, 0, 0)            # 色块颜色阈值
        color_roi               = (0,0,320,240)                 # 色块寻找区域(感兴趣区域)
        color_x_stride          =  1                            # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
        color_y_stride          =  1                            # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
        color_pixels_threshold  =  100                          # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
        color_area_threshold    =  100                          # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
        color_merge             =  True                         # 是否合并寻找到的色块 True 则合并 False 则不合并
        color_margin            =  1                            # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
    
    # 实例化类
    # 黑色
    black = Color_Property()
    black.color_threshold         = (0, 50, -10, 10, -10, 10)
    black.color_roi               = (0,0,320,240)
    black.color_x_stride          =  1
    black.color_y_stride          =  1
    black.color_pixels_threshold  =  100
    black.color_area_threshold    =  100
    black.color_merge             =  True
    black.color_margin            =  1
    
    # 红色
    red   = Color_Property()
    red.color_threshold           = (0, 100, 20, 127, -10, 127)
    
    #red.color_roi                = (0,0,320,240)
    red.color_roi                 = (0,110,320,20)
    
    red.color_x_stride            =  1
    red.color_y_stride            =  1
    
    #red.color_pixels_threshold   =  100
    #red.color_area_threshold     =  100
    red.color_pixels_threshold    =  10
    red.color_area_threshold      =  10
    
    red.color_merge               =  True
    red.color_margin              =  1
    
    # 绿色 预留
    green = Color_Property()
    
    # 蓝色 预留
    blue  = Color_Property()
    
    # 定义寻找色块函数
    def K210_Find_Blobs(color,led_flag):
        color.pixels_max = 0                                    # 重置 色块 最大像素数量
        color.flag       = 0                                    # 重置 色块 标志位
        color.led_flag   = 0                                    # 重置 led 标志位
    
        for blobs in img.find_blobs([color.color_threshold],    # 色块颜色阈值
        roi = color.color_roi,                                  # 色块寻找区域(感兴趣区域)
        x_stride = color.color_x_stride,                        # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
        y_stride = color.color_y_stride,                        # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
        pixels_threshold = color.color_pixels_threshold,        # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
        area_threshold = color.color_area_threshold,            # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
        merge = color.color_merge,                              # 是否合并寻找到的色块 True 则合并 False 则不合并
        margin = color.color_margin):                           # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
            img.draw_rectangle(blobs[0:4])                      # 圈出找到的色块
            if color.pixels_max < blobs.pixels():               # 找到面积最大的色块
                color.pixels_max = blobs.pixels()
                color.cx = blobs.cx()                           # 将面积最大的色块的 x轴 中心坐标值 赋值给 color
                color.cy = blobs.cy()                           # 将面积最大的色块的 y轴 中心坐标值 赋值给 color
                color.flag = 1                                  # 标志画面中有找到色块
                color.density = blobs.density()                 # 将面积最大的色块的 色块密度比 赋值给 color
                color.led_flag = led_flag                       # 将控制led颜色的标志位的值 赋值给 color
    
        if color.flag == 1:                                     # 标记画面中被找到的最大色块的中心坐标
            img.draw_cross(color.cx,color.cy, color=127, size = 15)
            img.draw_circle(color.cx,color.cy, 15, color = 127)
    
    #__________________________________________________________________
    # 调试区
    # 定义 K210 属性类
    class K210_Property(object):                                # 定义 K210 接收类
        x      = 0                                              # uint16_t  目标x轴坐标
        y      = 0                                              # uint16_t  目标y轴坐标
        color  = 0                                              # uint8_t   目标颜色标志位
        shape  = 0                                              # uint8_t   目标形状标志位
        flag   = 0                                              # uint8_t   目标标志位
        mode   = 0                                              # uint8_t   工作模式位
    
    # 实例化类
    K210 = K210_Property()                                      # 实例化 K210_Property() 为 K210
    
    LED_Control(1)                                              # 关闭一下所有灯 再进入 while 循环 使显示结果正确
    
    # 串口发送测试信息赋值
    TSTM32.mode   = 1
    
    TOpenMV.x     = 65535
    TOpenMV.y     = 65536
    TOpenMV.color = 255
    TOpenMV.shape = 256
    TOpenMV.flag  = 3
    
    # 打印信息函数
    # 打印 sensor 各参数
    def Print_sensor():
        print("Exposure :", sensor.get_exposure_us(), "Gain:", sensor.get_gain_db(), "RGB:", sensor.get_rgb_gain_db())
    
    # 打印 K210 各参数
    def Print_K210():
        print("Mode:", K210.mode, "x:", K210.x, "y:", K210.y, "color:", K210.color, "shape:", K210.shape, "flag:", K210.flag)
    
    # 打印 色块 各参数
    def Print_Blobs_Property(color,name):
        print(name,"cx:",color.cx,"cy:",color.cy,"flag:",color.flag,"color:",color.color,"density:",color.density,"led_flag:",color.led_flag)
    
    # 打印总函数
    def Print_All():
        print("______________________________________________________________________")
        Print_sensor()                                          # 打印 sensor 参数
        Print_Blobs_Property(black,"Black ")                    # 打印 黑色色块 参数
        Print_Blobs_Property(red,  "Red   ")                    # 打印 红色色块 参数
        Print_K210()                                            # 打印 K210 参数
    
    # LCD
    # LCD 初始化
    lcd.init()                                                  # lcd初始化
    
    # LCD 按键信息及目标点信息显示函数
    def LCD_Show():
        lcd.draw_string(0, 0,  "key_cs: "+str(key.cs), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 15, "cinput: "+str(key.cinput), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 30, "point1: "+str(point.point1), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 45, "point2: "+str(point.point2), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 60, "cross : "+str(point.cross), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 75, "red_cx: "+str(red.cx), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 90, "motor1: "+str(motor.motor1), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 105,"motor2: "+str(motor.motor2), lcd.BLUE, lcd.WHITE)
        lcd.draw_string(0, 120,"FPS   : "+str(clock.fps()), lcd.BLUE, lcd.WHITE)
    
    # 串口数据打包方法函数
    def UART_Pack_Method(TData):
        if TData.pack_flag == 1:
            data = bytearray([TData.head1,                      # 帧头1
                              TData.head2,                      # 帧头2
                              0x00,                             # 有效数据长度 0x00 + data_len - 4
                              TData.mode,                       # 保存目标x轴坐标 高八位
                              0x00])                            # 数据和校验位
    
        elif TData.pack_flag == 2:
            data = bytearray([TData.head1,                      # 帧头1
                              TData.head2,                      # 帧头2
                              0x00,                             # 有效数据长度 0x00 + data_len - 4
                              TData.x>>8,                       # 保存目标x轴坐标 高八位
                              TData.x,                          # 保存目标x轴坐标 低八位
                              TData.y>>8,                       # 保存目标y轴坐标 高八位
                              TData.y,                          # 保存目标y轴坐标 低八位
                              TData.color,                      # 保存目标颜色标志位
                              TData.shape,                      # 保存目标形状标志位
                              TData.flag,                       # 保存目标标志位
                              0x00])                            # 数据和校验位
    
        return data
    
    # 串口数据保存方法函数
    def UART_Save_Method(PData):
        if PData.save_flag == 1 and PData.uart_buf[2] > 0:
            K210.mode =  PData.uart_buf[3]
    
        elif PData.save_flag == 2 and PData.uart_buf[2] > 6:
            K210.x      = PData.uart_buf[3]*256 + PData.uart_buf[4]
            K210.y      = PData.uart_buf[5]*256 + PData.uart_buf[6]
            K210.color  = PData.uart_buf[7]
            K210.shape  = PData.uart_buf[8]
            K210.flag   = PData.uart_buf[9]
    
    #__________________________________________________________________
    # 主函数
    while(True):
    
        clock.tick()                                            # 跟踪运行时间
    
        # 模式选择______________________________________________________
    
        if K210.mode == 0x00:
            img=sensor.snapshot()                               # 拍摄一张照片
    
        elif K210.mode == 0x01:
            img=sensor.snapshot()                               # 拍摄一张照片
            K210_Find_Blobs(black,1)                            # 找黑色色块 LED标志为1 表示黑色
            motor.control_x = 160                               # 控制目标处于 x轴中心点 160
            Motor_Control(motor,black.cx)                       # 电机占空比控制函数获取电机控制占空比
            motor1.duty(motor.motor1)                           # 将获取到的电机1占空比 装载
            motor2.duty(motor.motor2)                           # 将获取到的电机2占空比 装载
    
        elif K210.mode == 0x02:
            img=sensor.snapshot()                               # 拍摄一张照片
            K210_Find_Blobs(red,2)                              # 找黑色色块 LED标志为1 表示黑色
            motor.control_x = 160                               # 控制目标处于 x轴中心点 160
            Motor_Control(motor,red.cx)                         # 电机占空比控制函数获取电机控制占空比
            motor1.duty(motor.motor1)                           # 将获取到的电机1占空比 装载
            motor2.duty(motor.motor2)                           # 将获取到的电机2占空比 装载
    
        elif K210.mode == 0x03:
            img=sensor.snapshot()                               # 拍摄一张照片
    
        else:
            img=sensor.snapshot()                               # 拍摄一张照片
    
        # 模式选择______________________________________________________
    
        Point_Control(key)                                      # 按键控制下的目标点获取函数
    
        lcd.display(img)                                        # LCD 显示图像
        LCD_Show()                                              # LCD 显示按键信息及目标点信息
    
        #LED_Control(red.led_flag)                              # LED 标记色块识别情况
    
        UART_Read(RSTM32,uart1)                                 # 串口1 数据接收
        UART_Read(ROpenMV,uart2)                                # 串口2 数据接收
    
        if timer0.cnt == 0:                                     # 如果 timer0.cnt 等于 0 此步骤的目的是控制打印周期 不要打印的太快
            Print_All()                                         # 打印各参数
            uart1.write(Pack_Data(TSTM32))                      # 串口1 数据发送
            uart2.write(Pack_Data(TOpenMV))                     # 串口2 数据发送
    
    #__________________________________________________________________
    
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » K210学习笔记五:探索串口通信的奥妙

    发表评论