news 2026/10/21 7:18:02

双MCU架构:基于视觉反馈的简易自行瞄准装置(激光云台控制)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
双MCU架构:基于视觉反馈的简易自行瞄准装置(激光云台控制)

目录

前言

一、 系统总体架构设计

二、 硬件架构设计与电源隔离

三、 双MCU协同控制与视觉闭环实现

1. 底盘寻迹控制(MSPM0)

2. 瞄准云台与视觉闭环(STM32F401)

3. 激光延时与状态机控制

四、 难点攻坚与工程思考

五、核心代码

六、PCB设计

STM32F104RCT6功能板(双板)

MSPM0系列功能板

七、项目展示


前言

在本次全国大学生电子设计竞赛中,我担任硬件设计与嵌入式控制负责人,带队完成了E题《简易自行瞄准装置》的设计与制作。该项目要求系统能够在移动或静止状态下,通过视觉反馈驱动二维云台,实现激光的精准打靶与圆弧画圆。

本项目采用了“双MCU+视觉反馈+闭环控制”的架构。通过双路独立电源隔离设计降低电机干扰,底盘寻迹与云台瞄准分别由不同主控独立处理,最终成功完成了赛题基本要求,并获得省级三等奖。本文将从系统架构、硬件设计、双核协同控制及视觉闭环算法等维度进行硬核复盘。

一、 系统总体架构设计

为满足赛题要求并保证系统的实时性与稳定性,系统整体采用了双MCU协同控制架构:

  • 底盘控制端(TI MSPM0系列MCU):基于CCS开发环境,负责自动寻迹小车底盘的电机驱动、循迹算法与运动控制。

  • 瞄准控制端(STM32F401RCT6):基于Keil 5开发环境,负责二维云台的瞄准模块、外设驱动、视觉坐标解析与激光控制。

  • 视觉模块:作为上位机,通过串口(UART)将靶心与光斑的坐标实时发送给瞄准控制端。


二、 硬件架构设计与电源隔离

作为硬件负责人,我使用嘉立创EDA独立完成了整机的原理图与PCB Layout,并负责整机的机械搭建与走线。

  1. 双路独立供电隔离系统:
    赛题严格要求主控与瞄准模块独立开关控制。更重要的是,小车的直流电机在启动和变速时会产生极大的瞬态电流,引发严重的电压波动与电磁干扰。为此,我设计了双路独立LDO电源树,将电机驱动电源、底盘MCU电源与STM32云台控制电源进行物理隔离,有效阻断了电机噪声对视觉反馈与云台微步进控制的干扰,确保了激光打靶的稳定性。

  2. 结构与抗干扰布线:
    在紧凑的尺寸限制(25cm×15cm×25cm)内,合理规划了PCB布局,将大电流电机走线与弱信号(UART通信、步进电机驱动)走线严格分离,并进行了地线敷铜隔离处理。


三、 双MCU协同控制与视觉闭环实现

系统的核心在于“运动”与“瞄准”的独立解耦与高效协同。

1. 底盘寻迹控制(MSPM0)

底盘采用TI MSPM0系列MCU进行实时控制。利用其定时器资源与PWM模块,结合红外循迹传感器的数组,实现了基于偏差PID的电机差速控制。得益于该MCU的硬件资源调配,小车能够平稳、高速地在100cm×100cm的黑色边线正方形上逆时针稳定寻迹。

2. 瞄准云台与视觉闭环(STM32F401)

瞄准模块的核心在于将视觉坐标系映射到步进电机的物理坐标系。

  • 串口通信与坐标解析:STM32通过UART高速接收视觉模块发送的X/Y坐标偏差。利用状态机处理串口数据帧的接收与校验,确保数据不丢包。

  • 闭环控制算法:将解析出的靶心与光斑X/Y轴偏差,通过比例控制算法转化为二维云台(两相步进电机)的步进脉冲数量与方向。云台快速调整激光笔姿态,直到偏差逼近靶心。

  • 响应时间优化:赛题要求2s/4s内击中靶心。我利用STM32的定时器中断构建了系统状态机逻辑,通过非阻塞式延时替代了传统的delay()死等,极大提高了系统的响应速度与控制精度,光斑精准落点在2cm误差范围内。

3. 激光延时与状态机控制

在赛题中,激光的开启和关闭时机极其重要。通过定时器中断精确控制激光引脚的输出电平,实现打靶时的瞬时点亮与关断,避免多余光斑在感光纸上留下痕迹影响判分。


四、 难点攻坚与工程思考

  1. 多机通信的数据粘包与同步:
    在视觉模块与STM32的串口通信中,容易受到外界环境光与电机干扰。通过自定义数据包头与校验位,并在STM32中利用空闲中断配合DMA进行数据接收,彻底解决了串口数据丢包与错位的问题。

  2. 步进电机的失步与共振:
    二维云台需要微步进调整以达到高精度瞄准。在硬件上通过调整驱动器细分电流;在软件上通过梯形加减速算法控制步进脉冲频率,有效避免了云台启动/停止时的机械抖动与失步现象,保证了光斑的稳定驻留。

五、核心代码

#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "board.h" #include "Delay.h" #include "usart.h" #include "Emm_V5.h" #include "key.h" #include "math.h" #include "Serial Screen.h" #include "usart3.h" #include "JiGuang.h" #include "tip3.h" #include "Time.h" #include "usart4.h" uint8_t KeyNum = 0; uint8_t KeyFalg = 0; uint32_t distance = 0; int32_t x,y; uint8_t Flag_XY = 0; uint8_t Three_Flag = 0; uint8_t Four_Flag = 0; uint8_t Two_Flag = 0; uint16_t flag_4s = 0; uint16_t flag_2s = 0; uint8_t UpFlag = 0; uint8_t up = 0; extern char flag_4,a; int main(void) { board_init(); //串行电机初始化 Key_Init(); OLED_Init(); Serial_Screen_Init(); usart3_Init(); Jiguang_Init(); Timer_Init(); usart4_Init(); UpdateDisplay(); //上电延时2秒等待Emm_V5.0闭环初始化完毕 Delay_ms(2000); while (1) { /**********************串口屏调整步进电机姿态**********************/ Screen_Return0(); if(Return0_Set == 1) { Emm_V5_Origin_Set_O(1, 1); Return0_Set = 0; } else if(Return0_Set == 2) { Emm_V5_Origin_Set_O(2, 1); Return0_Set = 0; } if(Serial_RxFlag == 1) { Control_actuator(1, Serial_RxPacket1[0], (uint16_t)Serial_RxPacket1[1], Serial_RxPacket1[2], (uint32_t)Serial_RxPacket1[3]); Serial_RxFlag = 0; } else if(Serial_RxFlag == 2) { Control_actuator(2, Serial_RxPacket2[0], (uint16_t)Serial_RxPacket2[1], Serial_RxPacket2[2], (uint32_t)Serial_RxPacket2[3]); Serial_RxFlag = 0; } /******************************************************************/ /*****************************第二题*******************************/ if(Two_Flag == 1) { Emm_V5_Origin_Trigger_Return(1,0,0); Delay_ms(200); Emm_V5_Origin_Trigger_Return(2,0,0); Delay_ms(200); Two_Flag = 0; // } /******************************************************************/ /*****************************第三题*******************************/ if(Three_Flag == 1) { if(UpFlag == 0) { UpFlag = 1; Control_actuator(2, 1, 10, 0, 200); Delay_ms(200); } actrual_get(); if(RotationAngle!=0) { Control_actuator(1, (uint8_t)RotationDir, 30, 0, (RotationAngle*3200)/360); RotationAngle = 0; } else if(Flag == 1 && Flag_XY == 0) { //计算水平差值 x = actual_x - 115; if(x <= 1 && x>= -1) { Emm_V5_Stop_Now(1, 0); Flag_XY = 1; } else if(x <= -1) //逆时针 { Control_actuator(1, 0, 10, 0, 1); } else//顺时针 { Control_actuator(1, 1, 10, 0, 1); } } else if(Flag == 1 && Flag_XY == 1) { //102 //98 //90 //计算垂直差值 y = actual_y - FixedValues[CurrentFixedIndex]; if(y <= 1 && y>= -1) { Emm_V5_Stop_Now(2, 0); Three_Flag = 0; } else if(y <= -1) //向上 { Control_actuator(2, 1, 10, 0, 1); } else//向下 { Control_actuator(2, 0, 10, 0, 1); } Flag_XY = 0; } Flag = 0; } /******************************************************************/ /*****************************第四题*******************************/ if(Four_Flag == 1) { if(up == 0) { Control_actuator(2, 1, 10, 0, 165); Delay_ms(200); up = 1; } actrual_get(); if(Flag == 1) { //计算水平差值 x = actual_x - 112; if(x <= 2 && x>= -2) { Emm_V5_Stop_Now(1, 0); } if(a==1) { Control_actuator(1, 0, 20, 0, 800); a=0; } else if(x <= -2&&flag_4==0) //逆时针 { Control_actuator(1, 0, 20, 0, 3); } else if (x >= 2&&flag_4==0) //顺时针 { Control_actuator(1, 1, 20, 0, 3); } else if(x <= -2&&flag_4==2) //逆时针 { Control_actuator(1, 0, 20, 0, 3); } else if (x >= 2&&flag_4==2) //顺时针 { Control_actuator(1, 1, 20, 0, 3); } else if(x <= -2&&(flag_4==1||flag_4==3)) //逆时针 { Control_actuator(1, 0, 25, 0, 3); } else if(x >= 2&&(flag_4==1||flag_4==3))//顺时针 { Control_actuator(1, 1, 25, 0, 3); } else if(x <= -2&&flag_4==4) //逆时针 { Control_actuator(1, 0, 20, 0, 5); } else if(x >= 2&&flag_4==4)//顺时针 { Control_actuator(1, 1, 20, 0, 5); } } Flag = 0; } // /******************************************************************/ /*****************************按键检测*****************************/ KeyNum = Key_GetNum(); if(KeyNum == 1) { KeyFalg = 1; } if(KeyNum == 2) { KeyFalg = 2; } if(KeyNum == 3) { KeyFalg = 3; } if(KeyNum == 4) { KeyFalg = 4; } if(KeyNum == 5) { KeyFalg = 5; } if(KeyNum == 6) { KeyFalg = 6; } if(KeyNum == 7) { KeyFalg = 7; } if(KeyNum == 8) { KeyFalg = 8; } /******************************************************************/ /*****************************按键菜单*****************************/ switch(KeyFalg) { case 1: // 按键1 - 启动第三题 Three_Flag = 1; KeyFalg = 0; flag_4s = 1; break; case 2: // 按键2 - 增加角度 RotationAngle += 15; if(RotationAngle > 180) { RotationAngle = 0; } UpdateDisplay(); KeyFalg = 0; break; case 3: // 按键3 - 切换定值 CurrentFixedIndex = (CurrentFixedIndex + 1) % 3; UpdateDisplay(); KeyFalg = 0; break; case 4: // 按键4 - 切换方向 RotationDir = !RotationDir; UpdateDisplay(); KeyFalg = 0; break; case 5: // 按键5 - 开启第二题 Two_Flag = 1; flag_2s = 1; KeyFalg = 0; break; case 6: // 按键6 - // Four_Flag = 1; // Jiguang_ON(); KeyFalg = 0; break; case 7: // 按键7 - 开始 // Jiguang_OFF(); Four_Flag = 1; KeyFalg = 0; break; case 8: // 按键8 - 开启激光 Jiguang_ON(); KeyFalg = 0; break; } /******************************************************************/ } } void TIM1_UP_IRQHandler(void) //10ms读取一次编码器的数据 即编码器速度 { if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查TIM1更新中断发生与否 { if(flag_4s != 0) { flag_4s++; if(flag_4s > 380) { flag_4s = 0; Jiguang_ON(); } } if(flag_2s != 0) { flag_2s++; if(flag_2s > 180) { flag_2s = 0; Jiguang_ON(); } } //中断时需要做的事情 TIM_ClearITPendingBit(TIM1, TIM_IT_Update ); //清除TIMx更新中断标志 } }

六、PCB设计

STM32F104RCT6功能板(双板)

注:由于电赛题目不确定,选择使用一块核心板配双功能板的形式,确保各个模块都能涵盖。

MSPM0系列功能板

七、项目展示

简易瞄准系统3

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 7:17:42

基于Java+Vue的实验室教学管理系统设计与实现

每年开学季&#xff0c;实验室管理员都要面对一轮"空间与时间大考"&#xff1a;一届又一届的实验课要排进有限的多媒体设备、仪器和房间&#xff0c;学生的自由预约需求又总是在课表缝隙里见缝插针。纸质登记和Excel排课在这种混合场景下很快就会失控——冲突、漏记、…

作者头像 李华
网站建设 2026/10/21 7:17:42

RK3588边缘视频分析平台实战:多路检测跟踪与容器化部署

1. 从一块 RK3588 开发板说起&#xff1a;为什么我要折腾边缘视频分析手里攒了几块 RK3588 的开发板&#xff0c;从 4GB 到 16GB 内存的版本都试过。最开始的想法很简单&#xff0c;就是想看看这块号称"边缘计算神器"的芯片&#xff0c;到底能不能扛住多路视频流的实…

作者头像 李华
网站建设 2026/10/21 7:17:09

Fastjson反序列化漏洞全解析:从AutoType到RCE的攻防博弈

从2017年第一次公开绕过细节开始&#xff0c;Fastjson就一直在安全圈保持着极高的“出镜率”。一个JSON解析库能连续多年成为漏洞研究的热点&#xff0c;在整个Java生态里都很难找到第二个。我见过不少团队被这个库折腾得够呛&#xff1a;有的因为线上系统用了旧版本&#xff0…

作者头像 李华
网站建设 2026/10/21 7:16:13

从零搭建商城后台管理系统:技术选型、权限模型与踩坑实录

后台管理系统这种东西&#xff0c;平时很多人都是拿开源模板改一改&#xff0c;真要自己从零搭一次&#xff0c;才发现里面全是细节。最近我把一个商城项目推进到后台管理阶段&#xff0c;从016到027连着一串子任务&#xff0c;核心都是围绕“怎么把后台管理系统的架子立起来”…

作者头像 李华
网站建设 2026/10/21 7:15:54

Pytorch三元组损失:原理、Batch Hard实现与训练避坑指南

在深度学习里跑过几轮人脸识别、图像检索、行人重识别&#xff08;ReID&#xff09;这类任务的老哥&#xff0c;应该都绕不开一个东西&#xff1a;度量学习&#xff0c;而度量学习里面最经典、被问得最多的就是Pytorch里的三元组损失&#xff08;Triplet Loss&#xff09;。我第…

作者头像 李华
网站建设 2026/10/21 7:15:52

自研RAG落地蓝图:从开源项目中拆解出的五层架构与关键设计

做了几年检索增强生成&#xff08;RAG&#xff09;的落地&#xff0c;我有个越来越强的感受&#xff1a;大部分自研RAG翻车&#xff0c;不是模型不够强&#xff0c;而是设计决策没想清楚。很多人一上来就写代码&#xff0c;把文档一切、向量一存、提示词一拼&#xff0c;一测效…

作者头像 李华