K210摄像头学习笔记
一、启用并初始化摄像头
代码实例:
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**指令多数只能放在源代码开头。
上电操作
开发板上电后,打开串口终端
按键盘
Ctrl+E,然后将复制好的代码,用鼠标右键粘贴进来,按键盘Ctrl+D来开始运行代码
二、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 支持括号内的换行。这时有两种情况。
- 第二行缩进到括号的起始处
foo = long_function_name(var_one, var_two,
var_three, var_four)
- 第二行缩进 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 元组
tuple是list的一种,但是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
切片
- 定义:取一个
list或tuple的部分元素 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 的功能
执行了这句命令后,引脚28和GPIO0就映射(绑定)好了,要取消映射(解绑),则需要调用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



