题目:运动目标控制与追踪系统 本项目基于 K230 平台实现运动目标位置复位、屏幕边框巡航、胶带正方形巡航、数字 8 循迹演示;并预留自动追踪红色光斑的扩展接口。
目录
项目概述
题目要求—功能对照
硬件清单
接线与引脚映射
软件与运行环境
标定与坐标映射(关键)
代码结构与主要函数
使用方法
扩展:自动追踪红色光斑(对接题目(5))
时间与性能估算
常见问题
安全与注意事项
项目源码
项目概述
目标:在 1 m 处的白色屏幕(有效面积 ≥ 0.6×0.6 m²)上,用绿色激光光斑模拟“运动目标”,并按键触发不同的轨迹控制;同时给出像素→电机角度的标定方法。
平台:K230(摄像头、FPIOA、GPIO、UART)。
核心思路:
摄像头采图与显示;
像素坐标与双电机角度的双线性插值标定(pixel_to_angle);
通过串口(UART1)下发“使能 / 清零 / 限速位置模式”控制帧驱动两个电机。
使用手册:
K230:https://wiki.01studio.cc/docs/canmv_k230/
张大头步进电机:https://www.yuque.com/zhangdatouzhikong/gzng3d
电机控制工具:https://pan.baidu.com/share/init?surl=rpwA7QtdAglsVxj_y77mmQ&pwd=x7dy
题目要求—功能对照
题目功能 | 本项目触发方式 | 代码入口 |
(1) 原点复位:绿色光斑能从屏幕任意位置回到原点(正方形左上角),误差 ≤ 2 cm | SW2 短按 | Reset_posi() |
(2) 屏幕四周边线顺时针一周(≤ 30 s,光斑中心距边线 ≤ 2 cm) | SW3 短按 | Start_system()(按序发 5 个角点) |
(3) 0.5×0.5 m 胶带正方形顺时针一周 | 与 (2) 同路径/同思想;将点位设在胶带线上(见 §6 标定) | Start_system() / 自定义点列 |
(4) 数字 8 贴于中心位置,顺时针一周 | SW4 短按 | Reco_eight() + Eight_track() |
(5) 一键切换自动追踪红色光斑(≤ 2 s 收敛,成功声光提示) | 预留(需新增视觉检测与闭环控制) | 见 §9 扩展建议 |
(6) 舵机控制不得加额外芯片 | 满足(全部由 K230 控制 UART、电机) | — |
说明:
题目“原点为正方形左上角”,代码中已按此设定(Reset_posi() 将两个电机角度设为 (0,0))。
(3) 胶带巡航与 (2) 边框巡航本质相同,只需将轨迹点标定到胶带中心线上(§6)。
硬件清单
K230 开发板(带摄像头接口)
摄像头(RGB565,工作分辨率 320×240)
3D打印外壳 + 2 台步进电机(串口协议控制)
绿色激光笔(光斑直径 ≤ 1 cm)
红色激光笔(用于扩展项自动追踪)
4个按键(上拉输入)
接线与引脚映射
UART1:TXD=IO3,RXD=IO4(FPIOA.set_function)
按键(上拉输入):
SW1→IO35(使能/失能 切换)
SW2→IO34(原点复位)
SW3→IO33(边线/胶带巡航)
SW4→IO32(数字 8 轨迹)
电机地址:电机1:0x01、电机2: 0x02
软件与运行环境
固件 / SDK:K230 对应 Python 运行环境与 media.* 模块(摄像头、媒体)
主要依赖:time, os, sys, math, media.sensor, media.display, media.media, machine(UART/FPIOA/Pin)
串口参数:UART1,115200 bps
标定与坐标映射(关键)
代码通过 四点标定 + 双线性插值 将 像素 (x,y) 映射到 电机角度 (ax, ay):
# 获取电机角度def pixel_to_angle(x, y): # 像素的四角点 px_tl = (71, 30) # 左上 px_tr = (248, 29) # 右上 px_br = (246, 206) # 右下 px_bl = (72, 205) # 左下 # 对应的电机角度 ang_tl = (0.0, 0.0) # 左上 ang_tr = (26.0, 0.0) # 右上 ang_br = (26.2, 25.8) # 右下 ang_bl = (-0.6, 25.6) # 左下 # 把目标像素(x,y)映射到[0,1]×[0,1]的归一化坐标(u,v) u = (x - px_tl[0]) / (px_tr[0] - px_tl[0]) v = (px_tl[1] - y) / (px_tl[1] - px_bl[1]) u = max(0, min(1, u)) v = max(0, min(1, v)) # 对x轴角/y轴角分别做双线性插值 ax = (1-u)*(1-v)*ang_tl[0] + u*(1-v)*ang_tr[0] + u*v*ang_br[0] + (1-u)*v*ang_bl[0] ay = (1-u)*(1-v)*ang_tl[1] + u*(1-v)*ang_tr[1] + u*v*ang_br[1] + (1-u)*v*ang_bl[1] return (round(ax, 1), round(ay, 1))
(注:这个部分需要自行通过摄像头识别四个角点的像素坐标以及控制电机转动到四个角点并记录下角度,方便后续做像素→电机角度的映射)
u,v 为像素在四边形内的归一化坐标;
通过双线性插值得到 (ax, ay),并四舍五入到 0.1°;
下发控制前再放大 10 倍(协议以 0.1° 为单位):bx=int(ax*10)、by=int(ay*10)。
(注:放大10倍是因为命令里面位置角度:范围为00000000-FFFFFFFF,即0-4294967295,保留一位小数,值为1表示0.1°,值为10表示1°,以此类推)
如何满足 (2)/(3) 的“距边线≤2 cm”:
在标定时,将四角像素点精确对齐到屏幕上 0.5 m 正方形 的四个顶点;
巡航点列(x1..x4, y1..y4 或自定义数组)沿胶带中轴布置;
调整 speed 与采样密度,确保弧度/直线段逼近胶带路径。
代码结构与主要函数
串口协议:
Set_zero(addr):将当前位置角度清零;
# 将当前位置角度清零def Set_zero(addr): # addr=电机地址 cmd = bytearray(4) # 定义长度为4的指令帧 cmd[0] = addr # 地址(电机ID) cmd[1] = 0x0A # 功能码 cmd[2] = 0x6D # 辅助码 cmd[3] = 0x6B # 校验码 uart.write(cmd) # 通过串口发送这条指令
Enable_elect(addr, state):电机使能控制;
# 电机使能控制def Enable_elect(addr, state): # addr=电机地址,state=使能状态 cmd = bytearray(6) # 定义长度为6的指令帧 cmd[0] = addr # 地址(电机ID) cmd[1] = 0xF3 # 功能码 cmd[2] = 0xAB # 辅助码 cmd[3] = state # 使能状态:00/01分别表示不使能/使能 cmd[4] = 0x00 # 同步标志 cmd[5] = 0x6B # 校验码 uart.write(cmd) # 通过串口发送这条指令
Posi_ctrl(addr, vel, ang):直通限速位置模式控制。
# 直通限速位置模式控制def Posi_ctrl(addr, vel, ang): # addr=电机地址, vel=速度, ang=角度 if ang > 50: # 角度限制 pass cmd = bytearray(12) # 定义长度为12的指令帧 cmd[0] = addr # 地址(电机ID) cmd[1] = 0xFB # 功能码 cmd[2] = 0x01 # 方向 cmd[3] = (vel >> 8) & 0xFF # 速度(高8位字节) cmd[4] = vel & 0xFF # 速度(低8位字节) cmd[5] = (ang >> 24) & 0xFF # 位置角度(高8位字节bit24 - bit31) cmd[6] = (ang >> 16) & 0xFF # 位置角度(bit16 - bit23) cmd[7] = (ang >> 8) & 0xFF # 位置角度(bit8 - bit15) cmd[8] = ang & 0xFF # 位置角度(低8位字节bit0 - bit7) cmd[9] = 0x01 # 运动模式 cmd[10] = 0x00 # 同步标志 cmd[11] = 0x6B # 校验码 uart.write(cmd) # 通过串口发送这条指令
系统参数:
addr_1/addr_2、speed、time_1/ time_2、预设角点 (x0..x4, y0..y4)
(x1..x4, y1..y4)这四个坐标是通过实践得出的,为了不完全脱离胶带稍微做了一些调整。
# 设置运动目标控制系统所需参数addr_1 = 0x01 # 电机1地址addr_2 = 0x02 # 电机2地址time_1 = 0.03 # 30mstime_2 = 2 # 2sspeed = 30 # 速度state = 1 # 电机状态x0 = 0 ; y0 = 0 # 原点x1 = 2 ; y1 = 3 # 左上x2 = 257; y2 = 3 # 右上x3 = 260; y3 = 256 # 右下x4 = 0 ; y4 = 255 # 左下
系统功能:
Clean_elect():双电机清零;
# 将电机xy值清零def Clean_elect(): Set_zero(addr_1) time.sleep(time_1) Set_zero(addr_2)
Enabled_elect(state):双电机使能;
# 不使能/使能电机def Enabled_elect(state): Enable_elect(addr_1, state) time.sleep(time_1) Enable_elect(addr_2, state)
Reset_posi():回到原点(左上角);
# 启动运动目标控制系统def Reset_posi(): Posi_ctrl(addr_1, speed, x0) time.sleep(time_1) Posi_ctrl(addr_2, speed, y0)
Start_system():按序到达 5 个角点,实现边框/胶带巡航;
# 启动运动目标控制系统def Start_system(): Posi_ctrl(addr_1, speed, x1) time.sleep(time_1) Posi_ctrl(addr_2, speed, y1) time.sleep(time_2) Posi_ctrl(addr_1, speed, x2) time.sleep(time_1) Posi_ctrl(addr_2, speed, y2) time.sleep(time_2) Posi_ctrl(addr_1, speed, x3) time.sleep(time_1) Posi_ctrl(addr_2, speed, y3) time.sleep(time_2) Posi_ctrl(addr_1, speed, x4) time.sleep(time_1) Posi_ctrl(addr_2, speed, y4) time.sleep(time_2) Posi_ctrl(addr_1, speed, x1) time.sleep(time_1) Posi_ctrl(addr_2, speed, y1)
(注:这部分的代码还未优化,比较冗余,这样写意义不大。可以修改成获取四个角点的像素点,通过pixel_to_angle(x, y)这个函数得到电机转动的角度进而控制电机运动)
pixel_to_angle():像素→角度(双线性插值);(详见 §6 标定)
get_motor_angles_from_pixel():单点下发;
# 传入像素坐标并返回电机角度def get_motor_angles_from_pixel(x, y): ax, ay = pixel_to_angle(x, y) bx = int(ax * 10) # 电机控制需要乘以10 by = int(ay * 10) # 电机控制需要乘以10 print(f"输入像素坐标: ({x}, {y}) -> 电机角度: ({ax}, {ay}) -> 换算角度: ({bx}, {by})") Posi_ctrl(addr_1, 100, bx) time.sleep(0.01) Posi_ctrl(addr_2, 100, by) time.sleep(0.01)
Reco_eight():摄像头找圆(find_circles),采样 N=48 点/圆并缩放至 96% 半径;
# 识别数字8def Reco_eight(): # ----- 拍一张图 ----- for _ in range(5): sensor.snapshot() img = sensor.snapshot() # 用于标记每个圆的采样点编号 point_number = 1 scale_factor = 0.96 # 检测圆形 for c in img.find_circles(threshold=6000, x_margin=10, y_margin=10, r_margin=10, r_min=35, r_max=45, r_step=4): # 画出圆 r = c.r() img.draw_circle(c.x(), c.y(), r, color=(255, 0, 0), thickness=2) print("找到圆: 圆心=(", c.x(), ",", c.y(), ") 半径=", c.r()) # ----- 在圆周上采样 48 个点 ----- N = 48 for i in range(N): theta = 2 * math.pi * i / N - math.pi / 2 px = int(c.x() + r * math.cos(theta)) py = int(c.y() + r * math.sin(theta)) # 计算新的缩小后的点坐标 new_px = int(c.x() + (px - c.x()) * scale_factor) new_py = int(c.y() + (py - c.y()) * scale_factor) trajectory_points.append((new_px, new_py)) point_number += 1 # 增加点编号 # 显示结果 Display.show_image(img, x=round((lcd_width - sensor.width()) / 2), y=round((lcd_height - sensor.height()) / 2))
Eight_track(points, t):按“左圆上半圈→右圆上半圈→右圆下半圈→左圆下半圈”的顺/逆时针顺序形成 8 字轨迹。
# 数字8循迹def Eight_track(trajectory_points,times): # 顺时针走第一个圆的前半圈 for(x, y) in trajectory_points[:24]: get_motor_angles_from_pixel(x, y) time.sleep(times) # 延时times秒 # 顺时针走第二个圆的前半圈 for(x, y) in trajectory_points[48:72]: get_motor_angles_from_pixel(x, y) time.sleep(times) # 延时times秒 # 逆时针走第二个圆的后半圈 for(x, y) in trajectory_points[72:96]: get_motor_angles_from_pixel(x, y) time.sleep(times) # 延时times秒 # 逆时针走第一个圆的后半圈 for(x, y) in trajectory_points[24:48]: get_motor_angles_from_pixel(x, y) time.sleep(times) # 延时times秒 x,y = trajectory_points[1] get_motor_angles_from_pixel(x, y)
使用方法
上电与初始化:
上电前我们需要手动把电机置于原点;
代码开头完成 UART、按键、摄像头初始化;
默认 Enabled_elect(1) 使能电机并执行 Clean_elect(),记录当前位置为原点。
按键操作:
SW1:电机使能/失能切换;
SW2:回到原点(左上角);
SW3:沿预设边框/胶带点位顺时针巡航;
SW4:拍照识别两个圆,生成 8 字轨迹并沿轨迹运行(Eight_track(trajectory_points, 0.1))。
扩展:自动追踪红色光斑(对接题目(5))
当前代码暂未实现自动追踪红光斑,后续可以按照如下开发路径进行完善:
检测红色光斑:在摄像头图像做 HSV/阈值分割,寻找最大连通域质心 (xr, yr);
像素→角度:调用 pixel_to_angle(xr, yr) 得到期望角度 (ax, ay);
闭环控制:周期性下发 Posi_ctrl(vel, ang),或加入 PID(对误差 e=目标角-当前角);
收敛判定与提示:误差持续 < 阈值(例如 0.2°)且时间 < 2 s,触发蜂鸣器/LED 连续提示;
抗抖与安全:对质心做时间滤波,限制角速度与加速度。
时间与性能估算
边框巡航:Start_system() 在四边与对角点之间各等待 time_2 = 2 s,总时长约 8~10 s(远小于 30 s 限制)。如需更“贴边”,可在侧边插入更多中间点。
数字 8:每个圆 N=48 点,Eight_track(..., 0.1) 单圈约 48×2×0.1 = 9.6 s,可按评分需求调节 N 与 times。
常见问题
光斑不在边线上 / 偏差大:检查 §6 标定四点是否准确落在 0.5 m 正方形顶点;必要时重新测量角度对照表(ang_*)。
运动不连贯 / 抖动:适当降低 speed,增加过渡点;在串口下发间隙留足 time.sleep。
找不到圆 / 8 字不闭合:调节 find_circles 的 threshold / r_min / r_max;提高屏幕对比、降低环境光。
电机无响应:确认 Enable_elect(..., 1) 已使能;串口连线正确;地址 0x01/0x02 与实物一致。
安全与注意事项
激光直视有危险,请确保对人眼安全;
设备摆放与图示一致(屏幕 1 m 处),防止反射;
运行前先执行 Clean_elect(),避免机械撞限位。
项目源码
GitHub仓库地址:https://github.com/ruanqiuqiu/RSOC_K230_project
RT-Thread Github 开源仓库,欢迎撒个星(Star)支持,更期待你的代码贡献: https://github.com/RT-Thread/rt-thread
全部0条评论
快来发表一下你的评论吧 !