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

目录

前言

[一、 系统总体架构设计](#一、 系统总体架构设计)

[二、 硬件架构设计与电源隔离](#二、 硬件架构设计与电源隔离)

[三、 双MCU协同控制与视觉闭环实现](#三、 双MCU协同控制与视觉闭环实现)

[1. 底盘寻迹控制(MSPM0)](#1. 底盘寻迹控制(MSPM0))

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

[3. 激光延时与状态机控制](#3. 激光延时与状态机控制)

[四、 难点攻坚与工程思考](#四、 难点攻坚与工程思考)

五、核心代码

六、项目展示


前言

在本次全国大学生电子设计竞赛中,我担任硬件设计与嵌入式控制负责人,带队完成了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更新中断标志 
		}
	
}

六、项目展示

简易瞄准系统3

相关推荐
恒锐丰科技林技术员1 小时前
EG27517 高速低侧单通道栅极驱动芯片解析
经验分享·嵌入式硬件·硬件工程
铅笔小新z2 小时前
【stm32】RTC(实时时钟)
stm32·单片机·实时音视频
Yiran_G2 小时前
LoRa数据帧设计与工程实现:地址、长度、校验及版本兼容
网络·嵌入式硬件·物联网
糖糖单片机设计2 小时前
基于 STM32 的风力发电机控制系统
stm32·单片机·嵌入式硬件
企鹅的蚂蚁3 小时前
Zephyr RTOS 常用 API 详解:从 GPIO、线程到 CAN FD(附 STM32/FreeRTOS 对照)
stm32·单片机·嵌入式硬件·rtos·zephyr
2501_922639581 天前
率能半导体 SS6285L直流双向马达驱动芯片:18V/5A,覆盖电子锁、按摩仪、电动玩具、机器人四大应用场景
单片机·嵌入式硬件·硬件工程
专一的咸鱼哥1 天前
预训练 ONNX 模型转换为 .eim
嵌入式硬件·机器人
SuperByteMaster1 天前
三极管 做开关电路 要尽快跳过放大区
嵌入式硬件
铅笔小新z1 天前
【stm32】输入捕获-超声波测距
stm32·单片机·嵌入式硬件