一、启用并初始化摄像头

代码实例:

import sensor, lcd

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
sensor.skip_frames()

lcd.init(freq=15000000)

while(True):
    lcd.display(sensor.snapshot())

引入了对库的使用

  • import sensor, lcd:首先是将sensor, lcd两个内置库进行导入

  • sensor.reset():初始化摄像头

    如果初始化失败要检查硬件损坏和兼容的问题;

  • sensor.set_pixformat(sensor.RGB565): 设置摄像头为RGB565格式

    默认都是用RGB565即可

  • sensor.run(1):摄像头启动!

    可省略,因为完成前三项的设置后,摄像头会自动启动

  • sensor.skip_frames():skip(跳过),用于跳过摄像头刚启动时的不稳定画面

  • lcd.init(freq=15000000):初始化LCD,并设置频率

  • lcd.display(sensor.snapshot())sensor.snapshot()用于获取图像,lcd.display()将图像显示到LCD

Python程序中对库的导入理解上可以对比于C、C++程序中的#include,但不同于C、C++,得益于Python统一的包管理器,Python中的import语句相当于引用了外部程序的特定功能,而C语言中的#include指令相当于在程序前面展开头文件的内容。需要注意的是,Python中的import语句是在程序运行时动态导入模块的,而C语言中的#include指令是在编译时静态的将头文件内容包含在源代码中,这就意味着,在Python中,**import语句可以在程序的任意位置多次使用,而在C语言中,#include**指令多数只能放在源代码开头。

上电操作

  • 开发板上电后,打开串口终端

    Untitled

  • 按键盘Ctrl+E,然后将复制好的代码,用鼠标右键粘贴进来,按键盘Ctrl+D来开始运行代码

    Untitled

二、MicroPython 背景知识

Python代码素养

Python语法

  • 两个字:缩进
# print absolute value of an integer:
a = 100
if a >= 0:
    print(a)
else:
    print(-a)

#开头的语句是注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。注意:Python对大小写是有区分的。

缩进

  • 统一使用 4 个空格进行缩进
  • 详细语法见 #2.2MicroPython(Python3) 基本语法

行宽

  • 每行代码尽量不超过 80 个字符

空行

  • 模块级函数和类定义之间空两行;
  • 类成员函数之间空一行;
class A:

    def __init__(self):
        pass

    def hello(self):
        pass

def main():
    pass

换行

Python 支持括号内的换行。这时有两种情况。

  1. 第二行缩进到括号的起始处
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
  1. 第二行缩进 4 个空格,适用于起始括号就换行的情形
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

使用反斜杠\换行,二元运算符+ .等应出现在行末;长字符串也可以用此法换行

session.query(MyTable).\
        filter_by(id=1).\
        one()

print 'Hello, '\
      '%s %s!' %\
      ('Harry', 'Potter')

禁止复合语句,即一行中包含多个语句:

# 正确的写法
do_first()
do_second()
do_third()

# 不推荐的写法
do_first();do_second();do_third();

if/for/while一定要换行:

# 正确的写法
if foo == 'blah':
    do_blah_thing()

# 不推荐的写法
if foo == 'blah': do_blash_thing()

引号

简单说,自然语言使用双引号,机器标示使用单引号,因此 代码里 多数应该使用 单引号

  • 自然语言 使用双引号 "..."例如错误信息;很多情况还是 unicode,使用u"你好世界"
  • 机器标识 使用单引号 '...' 例如 dict 里的 key
  • 正则表达式 使用原生的双引号 r"..."
  • 文档字符串 (docstring) 使用三个双引号 """......"""

MicroPython(Python3) 基本语法#

变量

在 Python 中,等号 = 是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,这种变量本身类型不固定的语言称之为动态语言

a = 123 # a是整数
print(a)
a = 'ABC' # a变为字符串
print(a)

list 列表

  • list 是一种有序的集合, 可以随时添加和删除其中的元素。
classmates = ['Michael', 'Bob', 'Tracy']
classmates
['Michael', 'Bob', 'Tracy']
  • 写法类似数组,用索引来访问 list 中每一个位置的元素, 索引是从 0 开始的,记得最后一个元素的索引是 len(classmates) - 1。
  • list 里面的元素的数据类型也可以不同
L = ['Apple', 123, True]

tuple 元组

  • tuplelist的一种,但是 tuple 一旦初始化就不能修改。正因为 tuple不可变, 所以代码更安全。写代码时优先考虑tuple
classmates = ('Michael', 'Bob', 'Tracy')
  • 定义tuple时注意:定义只有一个元素的tuple时,必须加一个,来消除歧义
t = (1, 2)
t
(1, 2)

t = (1,)
t
(1,)

循环

  • for x in ... 循环:把每个元素代入变量x, 然后执行缩进块的语句.
names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)
  • while 循环
sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

数据类型转换

  • >>> 要转换的类型(转化对象)
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
from machine import GPIO
float f=5.75;
printf("f=%d,f=%f\n",(int)f,f);

函数

  • def语句:定义函数
def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

切片

  • 定义:取一个listtuple的部分元素
  • list的切片
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

L[0:3]
['Michael', 'Sarah', 'Tracy']

L[:3]
['Michael', 'Sarah', 'Tracy']

L[1:3]
['Sarah', 'Tracy']
  • tuple的切片:切片后的结果还是tuple
(0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
  • 字符串 的切片:’xxx’ 也可以看成是一种 list,每个元素就是一个字符
'ABCDEFG'[:3]
'ABC'

对象

Python 是面向对象编程的

from pyb import LED

red_led = LED(1)
red_led.on()

#点亮 on, 关掉 off, 查看 value

模块

  • 在Python 中,一个 .py 文件就称之为一个模块(Module).
  • import语句使用模块:
import time

time.sleep_ms(500)

点亮LED

FPIOA(现场可编程IO阵列):外设到引脚的映射

MaixPy 所使用的硬件 K210 的片上外设对应的引脚(硬件引脚)是可以任意映射

from fpioa_manager import fm    # 导入库
fm.register(28, fm.fpioa.GPIO0) #将引脚 28 映射为了 GPIO0 的功能

执行了这句命令后,引脚28GPIO0就映射(绑定)好了,要取消映射(解绑),则需要调用fm.unregister函数,具体看API文档

程序

from fpioa_manager import fm                 #导入库
from Maix import GPIO

io_led_red = 13
fm.register(io_led_red, fm.fpioa.GPIO0)      #注册芯片的外设和引脚的对应关系

led_r=GPIO(GPIO.GPIO0, GPIO.OUT)             #定义一个 GPIO 对象led_r
led_r.value(0)                               #设置高低电平

三、K210外设配置

sensor

import sensor, time                                         # 导入感光元件模块 sensor 跟踪运行时间模块 time

导入模块

# 感光元件设置
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(1)                                 # 设置自动曝光
#sensor.set_auto_exposure(0, exposure=120000)               # 设置手动曝光 曝光时间 120000 us

sensor.set_auto_gain(0, gain_db = 17)                       # 设置画面增益 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 等待感光元件稳定

初始化——格式、大小——曝光(A/M)——画面增益、RGB增益——水平翻转、垂直镜像——延时开机

image

定义一个类来保存颜色属性

# 寻找色块
# 定义类
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)              #设置黑色寻找区域,(x,y,w,h):即x轴开始坐标,y轴开始坐标,x轴宽度,y轴高度 对于QVGA(0,0,320,240)就是全屏
black.color_x_stride          =  1                         
black.color_y_stride          =  1                         #设置色块x轴、y轴上像素的最小宽度
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, -5, 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 opv_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)

打印寻找到色块信息

# 定义打印色块参数函数
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)

主函数

# 主函数
while(True):

    clock.tick()                                            # 跟踪运行时间

    img = sensor.snapshot()                                 # 拍摄一张照片
    opv_find_blobs(black,1)                                 # 找黑色色块 led标志为1 表示黑色
    opv_find_blobs(red,2)                                   # 找红色色块 led标志为2 表示红色

    if mycnt == 0:                                          # 如果 mycnt 等于 0 此步骤的目的是控制打印周期 不要打印的太快
        mycnt = 1                                           # 将 1 赋值给 mycnt 使下一次不再满足 mycnt == 0 进入 elif
        print_sensor()                                      # 打印sensor参数
        print_blobs_property(black,"Black-")                # 打印黑色色块参数
        print_blobs_property(red,  "Red-  ")                # 打印红色色块参数

    elif mycnt < mycnt_max:                                 # 计数变量 小于 计数上限 则 计数变量 自增
        mycnt = mycnt + 1

    else:                                                   # 计数变量 超出 计数上限 则 将0赋值给 mycnt 使下一次进入 if
        mycnt = 0

定时器

from machine import Timer                                   # 从 machine 模块中导入 定时器模块 Timer 

# 定时器的使用
# 定义定时器属性类
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)类

# 定时器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

定时器0初始化,定时器模式有一次性、周期性、PWM,一般都是周期性或PWM,一次性使用的很少

PWM

from machine import Timer, PWM                              # 从 machine 模块中导入 定时器模块 Timer 脉宽调制模块 PWM

# 定时器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

导入定时器和PWM模块,定义一个类来保存电机属性

# 实例化电机类
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)类

# 定时器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

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)

创建电机对象

四、模块代码

视觉巡线

#__________________________________________________________________
# 导入模块
import sensor, time, image                                         # 导入感光元件模块 sensor 跟踪运行时间模块 time
import utime
import lcd
from fpioa_manager import fm
from Maix import GPIO
from machine import Timer, PWM

#__________________________________________________________________
# 配置sensor
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(1)                                 # 设置自动曝光
#sensor.set_auto_exposure(0, exposure=120000)               # 设置手动曝光 曝光时间 120000 us

sensor.set_auto_gain(0, gain_db = 17)                       # 设置画面增益 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 等待感光元件稳定

# 打印sensor参数
def print_sensor():
    print("Expose "+str(sensor.get_exposure_us()))          # 打印 曝光时间
    print("Gain: "+str(sensor.get_gain_db()))               # 打印 画面增益
    print("RGB: "+str(sensor.get_rgb_gain_db()))            # 打印 RGB 增益

#__________________________________________________________________
# 配置定时器

# 定义定时器属性类
class timer_property():
    cnt     = 0                                             # 定时器计数值
    cnt_max = 0                                             # 定时器计数值上限
    period  = 0                                             # 定时器周期
    freq    = 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 通道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实例化类
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

#__________________________________________________________________
# 定义目标点输入类
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 = 4                                               # 按键模式上限为 4 即最多有 4 个模式

# 注册按键引脚
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 外部中断 下降沿触发

#__________________________________________________________________
# LCD
# LCD 初始化
lcd.init()                                                  # lcd初始化

# LCD 按键信息及目标点信息显示函数
def lcd_key():
    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)

#__________________________________________________________________
# LED 的使用
# 注册LED引脚
fm.register(13, fm.fpioa.GPIO2, force = True)               # 配置 13 脚为 LED_R 强制注册
fm.register(12, fm.fpioa.GPIO1, force = True)               # 配置 12 脚为 LED_G 强制注册
fm.register(14, fm.fpioa.GPIO0, force = True)               # 配置 14 脚为 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 点亮对应的灯
    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 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 通道为 定时器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占空比 转换为 整数

#__________________________________________________________________
# 寻找色块

# 定义寻找色块类:
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)              #设置黑色寻找区域,(x,y,w,h):即x轴开始坐标,y轴开始坐标,x轴宽度,y轴高度 对于QVGA(0,0,320,240)就是全屏
black.color_x_stride          =  1                         
black.color_y_stride          =  1                         #设置色块x轴、y轴上像素的最小宽度
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, -5, 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 opv_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)

# 定义打印色块参数函数
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)

#__________________________________________________________________
# 创建时钟对象
clock = time.clock()                                        # 创建时钟对象 clock

#__________________________________________________________________
# 调试区
led_control(1)                                              # 关闭一下所有灯 再进入 while 循环 使显示结果正确

#__________________________________________________________________
# 主函数
while(True):

    clock.tick()                                            # 跟踪运行时间

    img = sensor.snapshot()                                 # 拍摄一张照片

    #opv_find_blobs(black,1)                                # 找黑色色块 led标志为1 表示黑色
    opv_find_blobs(red,2)                                   # 找红色色块 led标志为2 表示红色

    point_control(key)                                      # 按键控制下的目标点获取函数

    lcd.display(img)                                        # LCD 显示图像
    lcd_key()                                               # LCD 显示按键信息及目标点信息

    #led_control(red.led_flag)                              # LED 标记色块识别情况

    motor.control_x = 160                                   # 控制目标处于 x轴中心点 160
    motor_control(motor,red.cx)                             # 电机占空比控制函数获取电机控制占空比

    motor1.duty(motor.motor1)                               # 将获取到的电机1占空比 装载
    motor2.duty(motor.motor2)                               # 将获取到的电机2占空比 装载

    if timer0.cnt == 0:                                     # 如果 timer0.cnt 等于 0 此步骤的目的是控制打印周期 不要打印的太快
        print_sensor()                                      # 打印sensor参数
        print_blobs_property(black,"Black-")                # 打印黑色色块参数
        print_blobs_property(red,  "Red-  ")                # 打印红色色块参数

数字识别


串口通信

框架:

帧头1 帧头2 有效长度位 data[0] data[1] …… data[n] 校验位

用两个帧头是为了防止帧头和数据混在一起,只有当检测到两个帧头时,才开始接收数据

K210部分

#__________________________________________________________________
# 导入模块
import sensor, time, image                                         # 导入感光元件模块 sensor 跟踪运行时间模块 time
import utime
import lcd
from fpioa_manager import fm
from Maix import GPIO
from machine import Timer, PWM

#__________________________________________________________________
# 配置sensor
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(1)                                 # 设置自动曝光
#sensor.set_auto_exposure(0, exposure=120000)               # 设置手动曝光 曝光时间 120000 us

sensor.set_auto_gain(0, gain_db = 17)                       # 设置画面增益 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 等待感光元件稳定

#__________________________________________________________________
# 配置定时器

# 定义定时器属性类
class timer_property():
    cnt     = 0                                             # 定时器计数值
    cnt_max = 0                                             # 定时器计数值上限
    period  = 0                                             # 定时器周期
    freq    = 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 通道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实例化类
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

#__________________________________________________________________
# 定义目标点输入类
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 = 4                                               # 按键模式上限为 4 即最多有 4 个模式

# 注册按键引脚
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 外部中断 下降沿触发

#__________________________________________________________________
# LCD
# LCD 初始化
lcd.init()                                                  # lcd初始化

# LCD 按键信息及目标点信息显示函数
def lcd_key():
    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)

#__________________________________________________________________
# LED 的使用
# 注册LED引脚
fm.register(13, fm.fpioa.GPIO2, force = True)               # 配置 13 脚为 LED_R 强制注册
fm.register(12, fm.fpioa.GPIO1, force = True)               # 配置 12 脚为 LED_G 强制注册
fm.register(14, fm.fpioa.GPIO0, force = True)               # 配置 14 脚为 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 点亮对应的灯
    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 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 通道为 定时器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 设置 P6 RX P7 TX
fm.register(6, fm.fpioa.UART1_RX, force = True)             # 配置 6 脚为 UART1_RX 强制注册
fm.register(7, fm.fpioa.UART1_TX, force = True)             # 配置 7 脚为 UART1_TX 强制注册

uart1 = UART(UART.UART1, 115200, 8, 0, 1)                   # 设置 uart1 为 串口1 波特率 921600 数据位 8位 校验位 0位 停止位 1位

# 串口2 设置 P9 RX P10 TX
fm.register(9, fm.fpioa.UART2_RX, force = True)             # 配置 9 脚为 UART2_RX 强制注册
fm.register(10, fm.fpioa.UART2_TX, force = True)            # 配置 10 脚为 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)              #设置黑色寻找区域,(x,y,w,h):即x轴开始坐标,y轴开始坐标,x轴宽度,y轴高度 对于QVGA(0,0,320,240)就是全屏
black.color_x_stride          =  1                         
black.color_y_stride          =  1                         #设置色块x轴、y轴上像素的最小宽度
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, -5, 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 opv_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)

#__________________________________________________________________
# 创建时钟对象
clock = time.clock()                                        # 创建时钟对象 clock

#__________________________________________________________________
# 调试区
# 定义 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 参数

# 串口数据打包方法函数
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 数据发送

单片机部分

硬件部分:

uartconfig.c

#include "uartconfig.h"

/********************************************************************

串口发送数据函数 Data_Transmit

     各参数作用
     DataTransmit *data: 	        		选择要发送的串口发送数据结构体
     UART_HandleTypeDef *huart, :			选择通过哪一个串口发送
     
********************************************************************/
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
    
    HAL_UART_Transmit(huart, data -> transmit_data, data -> cnt , 0xFFFF);
    
}

/********************************************************************

串口打包发送数据函数 Data_Pack_Transmit

     各参数作用
     DataTransmit *data: 	        		选择要发送的串口发送数据结构体
     UART_HandleTypeDef *huart, :			选择通过哪一个串口发送
     
********************************************************************/
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart)
{
    
    Data_Pack(data);				
    Data_Transmit(data,huart);			
    
}

/********************************************************************

串口接收1个数据函数 Buffer_Receive

     各参数作用
     DataReceive *data: 					选择通过哪一个串口接收数据结构体接收
     UART_HandleTypeDef *huart, :			选择通过哪一个串口接收 
     
********************************************************************/
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart)
{
    
    HAL_UART_Receive_IT(huart, &data -> data, 1);
    return data -> data;
    
}

uartconfig.h

#ifndef __UARTCONFIG_H
#define __UARTCONFIG_H

#include "stm32f1xx_hal.h"
#include "uartprotocol.h"

// 重命名方便定义
typedef signed char             	int8_t;   
typedef short int               	int16_t;  
typedef int                     	int32_t; 

typedef unsigned char 				uint8_t;
typedef unsigned short int 			uint16_t;
typedef unsigned int            	uint32_t;

// 串口发送相关函数
void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);
void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);

// 串口接收相关函数
uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart);

#endif

软件部分:

uartprotocol.c

#include "uartprotocol.h"

/********************************************************************

串口发送数据结构体初始化函数 Data_Transmit_Init

     各参数作用
     DataTransmit *data: 							选择要初始化的串口发送数据结构体
     head1:			        					帧头1 
     head2:			        					帧头2	
     length:			        					有效数据长度 
     
********************************************************************/
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length)
{
    
    data -> head1  = head1;
    data -> head2  = head2;
    data -> length = length;
    data -> cnt		 = length + 4;
    
    for(uint8_t i = 0; i < length; i++)
    {
        
        data -> data[i] = 0;
        
    }
    
    for(uint8_t j = 0; j < length + 4; j++)
    {
        
        data -> transmit_data[j] = 0;
        
    }
    
}

/********************************************************************

串口发送数据打包函数 Data_Pack

     各参数作用
     DataTransmit *data: 							选择要打包的串口发送数据结构体
     
********************************************************************/
void Data_Pack(DataTransmit *data)
{
    
    data -> transmit_data[0] = data -> head1;
    data -> transmit_data[1] = data -> head2;
    data -> transmit_data[2] = data -> length;
    
    for(uint8_t i = 0; i < data -> length; i++)
    {
        
        data -> transmit_data[3+i] = data -> data[i];
        
    }
    
    uint8_t sum = 0;
    
  for(uint8_t j = 0; j < data -> length + 3; j++)
    {
        
        sum = sum + data -> transmit_data[j];
        
    }
    
  data -> transmit_data[data -> length + 3] = sum;
    
}

// 至此串口数据发送函数定义结束 使用例如下
/*

DataTransmit data_transmit_uart;												// 声明全局结构体 data_transmit_uart 这个要放在 main 函数外面

Data_Transmit_Init(&data_transmit_uart,0xAA,0xAA,1);		// main函数中在 while 前 对结构体 data_transmit_uart 进行初始化 帧头都为 0xAA 有效数据长度为 1	

data_transmit_uart.data[0] = 1;													// 将发送的第一个数据赋值为 1 位置不限 

// HAL库 使用方法
// 打包与发送分开
Data_Pack(&data_transmit_uart);													// 对数据进行打包 每次要发送的数据改变的时候 都要重新打包 这个可放在 while 中

Data_Transmit(&data_transmit_uart, USART1);							// 将数据通过USART1 发送

// 打包与发送合并
Data_Pack_Transmit(&data_transmit_uart, USART1);				// 对数据进行打包 并通过USART1 发送

// 固件库 使用方法
// 打包与发送分开
Data_Pack(&data_transmit_uart);													// 对数据进行打包 每次要发送的数据改变的时候 都要重新打包 这个可放在 while 中

Data_Transmit(&data_transmit_uart, USART1);							// 将数据通过USART1 发送

// 打包与发送合并
Data_Pack_Transmit(&data_transmit_uart, USART1);				// 对数据进行打包 并通过USART1 发送

*/
// 至此串口数据发送函数使用例结束 

/********************************************************************

串口接收数据结构体初始化函数 Data_Receive_Init

     各参数作用
     DataReceive *data: 							选择要初始化的串口接收数据结构体
     head1:			        					帧头1
     head2:			        					帧头2
     
********************************************************************/
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2)
{
    
    data -> head1  = head1;
    data -> head2  = head2;
    data -> length = 0;
    data -> cnt    = 0;
    data -> state  = 0;
    data -> i      = 0;
    data -> data   = 0;
    
    for(uint8_t j = 0; j < 50; j++)
    {
        
        data -> receive_data[j] = 0;
        
    }
    
}

/********************************************************************

串口接收数据函数 Data_Receive

     各参数作用
     DataReceive *data: 						选择要接收的串口接收数据结构体
   buf:											接收数据
     
********************************************************************/
void Data_Receive(DataReceive *data, uint8_t buf)
{
    
    if(data -> state == 0 && buf == data -> head1)
    {
        
        data -> state = 1;
        data -> receive_data[0] = buf;
        
    }
    else if(data -> state == 1 && buf == data -> head2)
    {
        
        data -> state = 2;
        data -> receive_data[1] = buf;
        
    }
    else if(data -> state == 2 && buf < 40)
    {
        
        data -> state = 3;
        data -> length = buf;
        data -> cnt = buf+4;
        data -> receive_data[2] = buf;
        
    }
    else if(data -> state == 3 && data -> length > 0)
    {
        
        data -> length = data -> length - 1;
        data -> receive_data[3 + data -> i] = buf;
        data -> i = data -> i + 1;
        
        if(data -> length == 0)
        {
            
            data -> state = 4;
            
        }
        
    }
    else if(data -> state == 4)
    {
        
        data -> receive_data[3 + data -> i] = buf;
        data -> state = 0;
        data -> i = 0;
        
    }
    else
    {
        
        data -> state = 0;
        data -> i = 0;
        
    }
    
}

// 至此串口数据接收函数定义结束 使用例如下
/*

DataReceive data_receive_uart;														// 声明全局结构体 data_receive_uart 这个要放在 main 函数外面

Data_Receive_Init(&data_receive_uart,0xAA,0xAA);					// main函数中在 while 前 对结构体 data_receive_uart 进行初始化 设置帧头为 0xAA

// HAL库 使用方法
Buffer_Receive(&huart1,&data_receive_uart);							  // main的函数在 while 前 HAL库需要接收一次 使之可以进入中断回调

// 串口中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)		// 中断回调函数中调用即可 函数 HAL_UART_RxCpltCallback 是一个弱定义 可以在 main.c 的 USER CODE BEGIN 4 中进行重写
{

    if( huart == &huart1 )																	// 检测到串口中断1
    {

        Data_Receive(&data_receive_uart,Buffer_Receive(&huart1,&data_receive_uart));

    }

}

// 固件库 使用方法 直接将中断函数复制到 main.c 下面即可

// 串口1 中断函数
void USART1_IRQHandler(void)
{
    
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
    {
            
            Data_Receive(&data_receive_uart, USART_ReceiveData(USART1));									// 从 串口1 接收1个数据
                
            USART_ClearFlag(USART1,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
            
    }
    
}

*/
// 至此串口数据接收函数使用例结束 

// 以下为串口接收数据解析函数 此类函数可自行定义 增删改变量 方法不唯一
/********************************************************************

串口接收数据结构体初始化函数 Target_Init

     各参数作用
     TargetAttribute *target: 			选择要初始化的结构体
     
********************************************************************/
void Target_Init(TargetProperty *target)
{
    
    target -> x     = 0;
    target -> y     = 0;
    target -> color = 0;
    target -> shape = 0;
    target -> flag  = 0;
    
}

/********************************************************************

串口接收数据解析函数 Target_Parse

     各参数作用	
   DataReceive *data: 						选择被解析的结构体
     TargetProperty *target: 			  选择解析完成保存的结构体
     
********************************************************************/
void Target_Parse(DataReceive *data, TargetProperty *target)
{
    
    uint8_t sum = 0;
    uint8_t i   = 0;
    
    while(i < data -> cnt - 1)
    {
        
        sum = sum + data -> receive_data[i];
        i = i + 1;
        
    }
    
    if(sum == data -> receive_data[data -> cnt - 1])
    {
        
        target -> x     = data -> receive_data[3]*256 + data -> receive_data[4];
        target -> y     = data -> receive_data[5]*256 + data -> receive_data[6];
        target -> color = data -> receive_data[7];
        target -> shape = data -> receive_data[8];
        target -> flag  = data -> receive_data[9];
        
    }
    
}

// 至此数据解析函数定义结束 使用例如下
/*

TargetProperty target;											  					// 声明全局结构体 target 这个要放在 main 函数外面

Target_Init(&target);																		// main函数中在 while 前 对结构体 target 进行初始化 

Target_Parse(&data_receive_uart,&target);								// 解析接收数据 可以放在 while 中 也可放在其他地方 不唯一

*/
// 解析函数可根据自身需求自由定义 方法不唯一

// 库函数版本 测试范例 第一个使用UART1 第二个使用UART1、UART2、UART3 
// 测试 main.c 如下 初始化 一个UART 并接收数据 可复制该代码 进行测试 
/*

#include "uartconfig.h"
#include "uartprotocol.h"

#include "timer.h"

// 串口发送
DataTransmit data_transmit_uart1;												// 声明全局结构体 data_transmit_uart1 这个要放在 main 函数外面

// 串口接收
DataReceive data_receive_uart1;													// 声明全局结构体 data_receive_uart1 这个要放在 main 函数外面

// 被发送数据
TargetProperty t1;											  							// 声明全局结构体 t1 这个要放在 main 函数外面

// 接收数据解析
TargetProperty target1;											  					// 声明全局结构体 target1 这个要放在 main 函数外面

// 定时器任务队列参数
uint8_t tim_task = 0;																		// 任务序号
uint8_t task_flag = 0;																	// 任务完成标志 0完成 1未完成

// 运行任务函数声明
void Run_Task(void);

int main(void)
{
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 			// 设置NVIC中断分组2 2位抢占优先级 2位响应优先级 即可设置 0-3 抢占优先级 0-3 子优先级 2位就是2的2次方 
    
    LED_Init();																						// LED 初始化 用于观察程序是否正常运行
    
    // 定时器3 初始化
    Init_TIM3(9999,71,0,1);															  // 定时器3 初始化 计数值 9999 分频系数 71 抢占优先级 0 子优先级 1 定时时间 (72M/(71+1))/(9999+1)=100 Hz 即 0.01s 产生一次中断
    
    // 串口发送初始化
    Init_UART1(115200,1,1);																// 串口1 初始化 波特率115200 抢占优先级1 子优先级1  
    
    Data_Transmit_Init(&data_transmit_uart1,0xAA,0xAA,7);	// main函数中在 while 前 对结构体 data_transmit_uart1 进行初始化 帧头都为 0xAA 有效数据长度为 7	
    
    // 串口接收初始化
    Data_Receive_Init(&data_receive_uart1,0xAA,0xAA);			// main函数中在 while 前 对结构体 data_receive_uart1 进行初始化 设置帧头为 0xAA

  // 被发送数据初始化
    Target_Init(&t1);																			// main函数中在 while 前 对结构体 t1 进行初始化 

    // 接收数据解析初始化
    Target_Init(&target1);																// main函数中在 while 前 对结构体 target1 进行初始化  
    
    while(1)
    {
        
        Run_Task();																					// 运行任务
    
    }
}

// 运行任务函数
void Run_Task(void)
{
    
    if(tim_task == 0 && task_flag == 1)
    {
        
        data_transmit_uart1.data[0] = t1.x/256;											
        data_transmit_uart1.data[1] = t1.x%256;											
        data_transmit_uart1.data[2] = t1.y/256;											
        data_transmit_uart1.data[3] = t1.y%256;											
        data_transmit_uart1.data[4] = t1.color;											
        data_transmit_uart1.data[5] = t1.shape;											
        data_transmit_uart1.data[6] = t1.flag;	
        
        Data_Pack_Transmit(&data_transmit_uart1, USART1);		// 对数据进行打包 并通过USART1 发送
        
        GPIO_SetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
    else if(tim_task == 1 && task_flag == 1)
    {
        
        Target_Parse(&data_receive_uart1,&target1);					// 解析 data_receive_uart1 接收数据 给 target1
        
        GPIO_ResetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
}

// 定时器3 中断函数
void TIM3_IRQHandler(void)   											  		// TIM3中断服务函数
{
    
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 		// 检查TIM3更新中断发生与否
    {
            
        if(tim_task < 1)
        {
            
            tim_task = tim_task + 1;													// 任务切换
            task_flag = 1;																		// 任务完成标志 0完成 1未完成 每次只做一次任务
            
        }
        else
        {
            
            tim_task = 0;																			// 重头开始执行任务
            task_flag = 1;																		// 任务完成标志 0完成 1未完成 每次只做一次任务
            
        }
        
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  				// 清除TIM3更新中断标志 
                
    }
    
}

// 串口1 中断函数
void USART1_IRQHandler(void)
{
    
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
    {
            
            Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1));									// 从 串口1 接收1个数据
                
            USART_ClearFlag(USART1,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
            
    }
    
}

*/

// 测试 main.c 如下 初始化 三个UART 并接收数据 可复制该代码 进行测试 
/*

#include "uartconfig.h"
#include "uartprotocol.h"

#include "timer.h"

// 串口发送
DataTransmit data_transmit_uart1;												// 声明全局结构体 data_transmit_uart1 这个要放在 main 函数外面
DataTransmit data_transmit_uart2;												// 声明全局结构体 data_transmit_uart2 这个要放在 main 函数外面
DataTransmit data_transmit_uart3;												// 声明全局结构体 data_transmit_uart3 这个要放在 main 函数外面

// 串口接收
DataReceive data_receive_uart1;													// 声明全局结构体 data_receive_uart1 这个要放在 main 函数外面
DataReceive data_receive_uart2;													// 声明全局结构体 data_receive_uart2 这个要放在 main 函数外面
DataReceive data_receive_uart3;													// 声明全局结构体 data_receive_uart3 这个要放在 main 函数外面

// 被发送数据
TargetProperty t1;											  							// 声明全局结构体 t1 这个要放在 main 函数外面
TargetProperty t2;											  							// 声明全局结构体 t2 这个要放在 main 函数外面
TargetProperty t3;											  							// 声明全局结构体 t3 这个要放在 main 函数外面

// 接收数据解析
TargetProperty target1;											  					// 声明全局结构体 target1 这个要放在 main 函数外面
TargetProperty target2;											  					// 声明全局结构体 target2 这个要放在 main 函数外面
TargetProperty target3;											  					// 声明全局结构体 target3 这个要放在 main 函数外面

// 定时器任务队列参数
uint8_t tim_task = 0;																		// 任务序号
uint8_t task_flag = 0;																	// 任务完成标志 0完成 1未完成

// 运行任务函数声明
void Run_Task(void);

int main(void)
{
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 			// 设置NVIC中断分组2 2位抢占优先级 2位响应优先级 即可设置 0-3 抢占优先级 0-3 子优先级 2位就是2的2次方 
    
    LED_Init();																						// LED 初始化 用于观察程序是否正常运行
    
    // 定时器3 初始化
    Init_TIM3(9999,71,0,1);															  // 定时器3 初始化 计数值 9999 分频系数 71 抢占优先级 0 子优先级 1 定时时间 (72M/(71+1))/(9999+1)=100 Hz 即 0.01s 产生一次中断
    
    // 串口发送初始化
    Init_UART1(115200,1,1);																// 串口1 初始化 波特率115200 抢占优先级1 子优先级1 
    Init_UART2(115200,1,1);																// 串口2 初始化 波特率115200 抢占优先级1 子优先级1 
    Init_UART3(115200,1,1);																// 串口3 初始化 波特率115200 抢占优先级1 子优先级1 
    
    Data_Transmit_Init(&data_transmit_uart1,0xAA,0xAA,7);	// main函数中在 while 前 对结构体 data_transmit_uart1 进行初始化 帧头都为 0xAA 有效数据长度为 7	

    Data_Transmit_Init(&data_transmit_uart2,0xBB,0xBB,7);	// main函数中在 while 前 对结构体 data_transmit_uart2 进行初始化 帧头都为 0xBB 有效数据长度为 7	
    
    Data_Transmit_Init(&data_transmit_uart3,0xCC,0xCC,7);	// main函数中在 while 前 对结构体 data_transmit_uart3 进行初始化 帧头都为 0xCC 有效数据长度为 7	
    
    // 串口接收初始化
    Data_Receive_Init(&data_receive_uart1,0xAA,0xAA);			// main函数中在 while 前 对结构体 data_receive_uart1 进行初始化 设置帧头为 0xAA
    Data_Receive_Init(&data_receive_uart2,0xBB,0xBB);			// main函数中在 while 前 对结构体 data_receive_uart2 进行初始化 设置帧头为 0xBB
    Data_Receive_Init(&data_receive_uart3,0xCC,0xCC);			// main函数中在 while 前 对结构体 data_receive_uart3 进行初始化 设置帧头为 0xCC

  // 被发送数据初始化
    Target_Init(&t1);																			// main函数中在 while 前 对结构体 t1 进行初始化 
    Target_Init(&t2);																			// main函数中在 while 前 对结构体 t2 进行初始化 
    Target_Init(&t3);																			// main函数中在 while 前 对结构体 t3 进行初始化 

    // 接收数据解析初始化
    Target_Init(&target1);																// main函数中在 while 前 对结构体 target1 进行初始化 
    Target_Init(&target2);																// main函数中在 while 前 对结构体 target2 进行初始化 
    Target_Init(&target3);																// main函数中在 while 前 对结构体 target3 进行初始化 
    
    while(1)
    {
        
        Run_Task();																					// 运行任务
    
    }
}

// 运行任务函数
void Run_Task(void)
{
    
    if(tim_task == 0 && task_flag == 1)
    {
        
        data_transmit_uart1.data[0] = t1.x/256;											
        data_transmit_uart1.data[1] = t1.x%256;											
        data_transmit_uart1.data[2] = t1.y/256;											
        data_transmit_uart1.data[3] = t1.y%256;											
        data_transmit_uart1.data[4] = t1.color;											
        data_transmit_uart1.data[5] = t1.shape;											
        data_transmit_uart1.data[6] = t1.flag;	
        
        Data_Pack_Transmit(&data_transmit_uart1, USART1);		// 对数据进行打包 并通过USART1 发送
        
        GPIO_SetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
    else if(tim_task == 1 && task_flag == 1)
    {
        
        data_transmit_uart2.data[0] = t2.x/256;											
        data_transmit_uart2.data[1] = t2.x%256;											
        data_transmit_uart2.data[2] = t2.y/256;											
        data_transmit_uart2.data[3] = t2.y%256;											
        data_transmit_uart2.data[4] = t2.color;											
        data_transmit_uart2.data[5] = t2.shape;											
        data_transmit_uart2.data[6] = t2.flag;
        
        Data_Pack_Transmit(&data_transmit_uart2, USART2);		// 对数据进行打包 并通过USART2 发送
        
        GPIO_ResetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
    else if(tim_task == 2 && task_flag == 1)
    {
        
        data_transmit_uart3.data[0] = t3.x/256;											
        data_transmit_uart3.data[1] = t3.x%256;											
        data_transmit_uart3.data[2] = t3.y/256;											
        data_transmit_uart3.data[3] = t3.y%256;											
        data_transmit_uart3.data[4] = t3.color;											
        data_transmit_uart3.data[5] = t3.shape;											
        data_transmit_uart3.data[6] = t3.flag;	
        
        Data_Pack_Transmit(&data_transmit_uart3, USART3);		// 对数据进行打包 并通过USART3 发送
        
        GPIO_SetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
    else if(tim_task == 3 && task_flag == 1)
    {
        
        Target_Parse(&data_receive_uart1,&target1);					// 解析 data_receive_uart1 接收数据 给 target1
        Target_Parse(&data_receive_uart2,&target2);					// 解析 data_receive_uart2 接收数据 给 target2
        Target_Parse(&data_receive_uart3,&target3);					// 解析 data_receive_uart3 接收数据 给 target3
        
        GPIO_ResetBits(GPIOB,GPIO_Pin_5);
        
        task_flag = 0;
        
    }
}

// 定时器3 中断函数
void TIM3_IRQHandler(void)   											  		// TIM3中断服务函数
{
    
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) 		// 检查TIM3更新中断发生与否
    {
            
        if(tim_task < 3)
        {
            
            tim_task = tim_task + 1;													// 任务切换
            task_flag = 1;																		// 任务完成标志 0完成 1未完成 每次只做一次任务
            
        }
        else
        {
            
            tim_task = 0;																			// 重头开始执行任务
            task_flag = 1;																		// 任务完成标志 0完成 1未完成 每次只做一次任务
            
        }
        
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  				// 清除TIM3更新中断标志 
                
    }
    
}

// 串口1 中断函数
void USART1_IRQHandler(void)
{
    
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
    {
            
            Data_Receive(&data_receive_uart1, USART_ReceiveData(USART1));									// 从 串口1 接收1个数据
                
            USART_ClearFlag(USART1,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
            
    }
    
}

// 串口2 中断函数
void USART2_IRQHandler(void)
{
    
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
    {
            
            Data_Receive(&data_receive_uart2, USART_ReceiveData(USART2));									// 从 串口2 接收1个数据
                
            USART_ClearFlag(USART2,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
            
    }
    
}

// 串口3 中断函数
void USART3_IRQHandler(void)
{
    
    if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) 													// 判断接收数据寄存器是否有数据
    {
            
            Data_Receive(&data_receive_uart3, USART_ReceiveData(USART3));									// 从 串口3 接收1个数据
                
            USART_ClearFlag(USART3,USART_IT_RXNE);																				// 清空中断标志 准备下一次接收
            
    }
    
}

*/

uartprotocol.h

#ifndef __UARTPROTOCOL_H
#define __UARTPROTOCOL_H

// 重命名方便定义
typedef signed char             	int8_t;   
typedef short int               	int16_t;  
typedef int                     	int32_t; 

typedef unsigned char 				uint8_t;
typedef unsigned short int 			uint16_t;
typedef unsigned int            	uint32_t;

// 串口发送相关结构体
typedef struct
{
    
    uint8_t	head1;							// 帧头1
    uint8_t	head2;							// 帧头2
    uint8_t length;							// 有效数据长度
    uint8_t cnt;							// 总数据长度
    uint8_t data[40];						// 有效数据数组
    uint8_t transmit_data[50];				// 实际发送的数组 附带上帧头1 帧头2 有效数据长度位 校验位
    
}DataTransmit;

// 串口发送相关函数
void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length);
void Data_Pack(DataTransmit *data);

// 串口接收相关结构体
typedef struct
{
    
    uint8_t	head1;							// 帧头1
    uint8_t	head2;							// 帧头2
    uint8_t length;							// 有效数据长度
    uint8_t cnt;							// 总数据长度
    uint8_t state;							// 接收状态
    uint8_t i;								// 有效数据下标
    uint8_t data;							// 接收数据缓冲位
    uint8_t receive_data[50];				// 实际接收的数组 附带上帧头1 帧头2 有效数据长度位 校验位
    
}DataReceive;

// 串口接收相关函数
void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2);
void Data_Receive(DataReceive *data, uint8_t buf);

// 接收数据解析相关结构体
typedef struct
{
    
    uint16_t	x;						    // 目标x轴坐标
    uint16_t	y;							// 目标y轴坐标
    uint8_t   color;						// 目标颜色标志位
    uint8_t   shape;						// 目标形状标志位
    uint8_t   flag;							// 目标标志位
    
}TargetProperty;

// 接收数据解析相关函数
void Target_Init(TargetProperty *target);
void Target_Parse(DataReceive *data, TargetProperty *target);

#endif

图像矩形顶角获取