国二,使用STC32G12K128 芯片为主控获得全国大学生电子设计竞赛国家二等奖
7月初,我们开始了全国大学生电子设计大赛的备赛。经过分析比较,我们队伍最终选择了 STC32G12K128 单片机 作为主控芯片。该芯片最高支持 80MHz 主频,单周期指令运行效率高;同时集成 12 位 ADC、PWM、多个定时器及 UART 接口,资源丰富且开发友好,能够很好地满足本题所需的实时性与多外设并行处理需求。 在备赛阶段,我们以 23 年E题作为训练题,重点掌握了 PWM 舵机驱动、外部中断捕获 、步进电机驱动等功能。将一些必要的模块函数封装好后,电赛比赛开始了。在众多题目中,我们选择了 E 题建议自动瞄准装置,该题要求实现自动巡线、激光笔打靶功能。自动巡线部分使用赛题要求的MSPM0G3507,激光打靶系统主要由 STC32G12K128 主控芯片、双自由度云台、激光发射电路及显示模块 组成。在选择云台方案时,我们考虑了两种方案,方案一采用舵机驱动,其优势在于扭矩性能突出,如MG996R等典型舵机可输出≥12 kg·cm的扭矩,具备较强的直接负载驱动能力,同时成本低廉(单价约为步进电机的1/3),且集成度较高。然而舵机存在精度不足的问题,机械死区约0.5°–1°,并受齿隙和温度漂移影响,定位偏差可达±2°。相比之下,方案二的步进电机驱动系统虽扭矩较小,但凭借128细分微步控制可实现0.045°的角分辨率,定位精度远优于舵机。综合考虑本项目对扭矩需求不高而对精度要求严格,最终选用方案二,即步进电机云台系统。在比赛中,我们充分发挥芯片的高速与资源优势,成功实现了目标检测、角度解算、自动瞄准与精确发射。1. 系统设计思路本系统以 STC32G12K128 为核心控制器,基于“目标检测—角度解算—步进电机转动—瞄准发射—状态反馈”闭环逻辑工作。
[*]目标检测与定位:光敏阵列或超声波模块实时检测目标位置。
[*]角度解算与控制算法:根据目标误差计算步进电机转角,利用脉冲调节。
[*]自动瞄准与发射:当误差小于阈值时,触发发射模块。
[*]参数设定与显示:通过屏幕实时显示角度、目标位置、命中状态等。
2.云台控制部分
对于由两个步进电机构成的二维云台,激光笔固定于云台的正下方,此时激光笔相对于摄像头的坐标是固定的。云台1控制X轴的运动,云台2控制Y轴的运动。假设云台1的角度为θ1,云台2的角度为θ2。云台的末端坐标为(x, y)。步进云台我们采用双环控制,外环视觉定位(K230输出目标坐标)内环步进电机微步控制(STC32实现)通过云台的运动,可以得到云台的坐标变化关系如下:其中,L 是步进云台到屏幕的距离。这就是由两个步进电机构成的二维舵机云台的运动学模型。3.视觉部分设计k230 模块因其学习资源、图像处理能力强大、高分辨率摄像头,我们综合考虑选择k230。k230 模块是一款具备 ai 功能的摄像头模块,双核RISC-V CPU + 自研KPU NPU:支持最高4TOPS(INT8)算力,兼顾通用计算与神经网络加速,满足YOLOv5s等轻量模型实时推理。能够独立完成图像处理和物体识别,无需外接处理器。它的高度集成化设计减少了对外部组件的依赖,同时其低功耗特性使之适合便携式设备。4.控制工作流程步进电机云台控制部分根据视觉传来的偏差转换为步进电机x y轴需运动的脉冲数进行控制,并通过增加编码器进行闭环控制防止丢步。5.测试方法与数据结果分析用数字万用表分别测试每条导线,看是否导通,有无断路和短路情况,看整个系统的地线是否都接在一起,看焊接点有没有虚焊等。装置可能会出现一些步进电机不转、抖动等故障,有可能是以下方面的问题:焊接不良、接线不正确或者接地电阻过大、 软件设计中参数设置不当、某个芯片已损坏。(1)装置一键启动激光能否击中心
序号移动时间距离偏差cm
11.7s1.9cm
21.8s1.1cm
31.7s1.8cm
41.4s17cm
(2)装置能否成功循迹
序号装置是否成功循迹
1是
2是
3是
4是
依据上表显示该装置具备了循迹的能力,且具有自动瞄准的功能在参加2025年电子设计竞赛的过程中,我们获得了许多宝贵的经验和深刻的感悟。在竞赛过程中,我们深入理解了单片机控制、k230传感器应用以及步进电机驱动的核心技术。特别是在设计和调试阶段,我们学会了如何将这些技术高效整合,实现系统的预期功能。这不仅提升了队伍的硬件设计能力,也增强了我们在图像识别和自动化控制方面的技能。。竞赛是一个团队合作的过程,我们的项目涉及到多个方面的技术,每个成员都有自己的专长。通过有效的沟通和协调,我们能够将各自的专长融入项目中,最终实现了系统的功能目标。这让我们深刻认识到团队合作在技术项目中的重要性,尤其是在面对复杂的系统设计和开发时。
补充下材料及中间拍的两张车照
视频链接:https://www.bilibili.com/video/BV1sd1bBqEU4/?spm_id_from=333.1387.list.card_archive.click&vd_source=dc3122ffbebeb9507ceb2ff2f1ff7357
{:4_180:}{:4_180:}{:4_180:}恭喜获奖 {:4_180:}{:4_180:}{:4_180:}
为了不影响您顺利拿到奖金,麻烦在该贴提供如下材料:
1.赛前在本坛连续3个月发布10次以上自己用 STCAI的MCU, 备赛/竞赛的图片的帖子链接
2.自己学校的校级证明,参与竞赛的技术文档,包括但不限于原理图,源程序,PCB版设计等。
3.关于此获奖项目的视频讲解。
后续我们将邀请专家协助我们进行评审。资料提供完毕后,请耐心等候 {:4_243:}{:4_243:}{:4_243:}
原理图
视频:2025电赛E题分享_哔哩哔哩_bilibili
#include "control.h"
// 命中判定计数(用于激光开关的去抖)
static uint16 aim_cnt = 0;
// 命中判定阈值与保持时间(像素阈值、连续周期数)
const uint16 err_x_th = 6; // X 误差阈值(像素)
const uint16 err_y_th = 6; // Y 误差阈值(像素)
const uint16 hold_need = 5;// 连续满足阈值的周期数
// IMU 角速度与零偏(需要姿态时可用)
float angle_z_0off = 0,angle_z=0,angle_y_0off=0,angle_y=0;
float Yaw_Integral;
// PID 结构体与实例(用于 XY 轴速度控制)
typedef struct
{
float kp;
float ki;
float kd;
float integral;
float prev_error;
float integral_min;
float integral_max;
float out_min;
float out_max;
} pid_t;
// kp, ki, kd, integral, prev_error, integral_min/max, out_min/max
static pid_t pid_x = {0.40f, 0.00f, 0.02f, 0.0f, 0.0f, -200.0f, 200.0f, -80.0f, 80.0f};
static pid_t pid_y = {0.45f, 0.00f, 0.06f, 0.0f, 0.0f, -200.0f, 200.0f, -80.0f, 80.0f};
// 计算一步 PID 输出:像素误差 -> 期望转速(RPM),正负表示方向
static float pid_step(pid_t *pid, float error, float dt_s)
{
float derivative = (error - pid->prev_error) / dt_s;
pid->integral += error * dt_s;
if (pid->integral > pid->integral_max) pid->integral = pid->integral_max;
if (pid->integral < pid->integral_min) pid->integral = pid->integral_min;
float out = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative;
if (out > pid->out_max) out = pid->out_max;
if (out < pid->out_min) out = pid->out_min;
pid->prev_error = error;
return out;
}
void gyroOffsetInit(void)
{
uint8 i = 100;
float temp_z={0};
float temp=0;
while (i)
{
i--;
imu660ra_get_gyro();
temp_z=((float)(imu660ra_gyro_z)) / 16.4;
temp += temp_z;
delay_ms(5);
}
angle_z_0off = temp / 100;
}
uint8 imu_init_flag = 100;
void Imu_Init(void)
{
if(imu660ra_init() == 0)
{
imu_init_flag = 1;
}
gyroOffsetInit();
Yaw_Integral = 0;
}
void Angle_Get(void)
{
imu660ra_get_gyro();
angle_z = ((float)(imu660ra_gyro_z)-angle_z_0off)/16.4;
angle_y = ((float)(imu660ra_gyro_y)-angle_y_0off)/16.4;
// angle_z = imu660ra_gyro_transition(((float)(imu660ra_gyro_z)-angle_z_0off));
}
void Angle_Integral(void)
{
Angle_Get();
Yaw_Integral += angle_z*dt;
}
// 基于图像坐标误差的二维云台跟踪(速度模式 + PID)
void gimbal_track_xy(uint16 real_x, uint16 real_y, uint16 target_x, uint16 target_y)
{
int32 x_err = (int32)target_x - (int32)real_x;
int32 y_err = (int32)target_y - (int32)real_y;
uint16 dead_zone = 5; // 像素死区,避免微抖
uint16 min_vel = 1; // 最小转速 RPM,克服静摩擦
uint16 max_vel = 60; // 最大转速 RPM,结合机械能力
// X 轴控制(电机1)
{
uint32 abs_x = (x_err >= 0) ? (uint32)x_err : (uint32)(-x_err);
if (abs_x <= dead_zone)
{
Emm_V5_Vel_Control_1(1, 0, 0, 10, 0); // 停止
}
else
{
float u = pid_step(&pid_x, (float)x_err, dt); // PID 输出
uint8 dir_x = (u < 0.0f) ? 1 : 0; // 负为右转,正为左转(与现有方向约定一致)
uint16 vel_x = (uint16)((u < 0.0f) ? -u : u);
if (vel_x < min_vel) vel_x = min_vel;
if (vel_x > max_vel) vel_x = max_vel;
Emm_V5_Vel_Control_1(1, dir_x, vel_x, 10, 0);
}
}
// Y 轴控制(电机2)
{
uint32 abs_y = (y_err >= 0) ? (uint32)y_err : (uint32)(-y_err);
if (abs_y <= dead_zone)
{
Emm_V5_Vel_Control_1(2, 0, 0, 10, 0); // 停止
}
else
{
float u = pid_step(&pid_y, (float)y_err, dt); // PID 输出
uint8 dir_y = (u > 0.0f) ? 1 : 0; // 正为上转,负为下转(与现有方向约定一致)
uint16 vel_y = (uint16)((u < 0.0f) ? -u : u);
if (vel_y < min_vel) vel_y = min_vel;
if (vel_y > max_vel) vel_y = max_vel;
Emm_V5_Vel_Control_1(2, dir_y, vel_y, 10, 0);
}
}
uint16 ax = (ex >= 0) ? (uint16)x_err : (uint16)(-x_err);
uint16 ay = (ey >= 0) ? (uint16)y_err : (uint16)(-y_err);
if (has_target && ax <= err_x_th && ay <= err_y_th)
{
if (aim_cnt < 1000) aim_cnt++;
}
else
{
aim_cnt = 0;
}
if (aim_cnt >= hold_need)
{
light_open();
}
else
{
light_close();
}
}
// 目标处理:有目标 -> 跟踪;无目标 -> 低速左右搜索(俯仰保持)
void gimbal_track_handle(uint8 has_target, uint16 real_x, uint16 real_y, uint16 target_x, uint16 target_y)
{
static uint16 scan_tick = 0; // 扫描计数器
static uint8 scan_dir = 0; // 0 左, 1 右
if (has_target)
{
// 发现目标:立即跟踪
gimbal_track_xy(real_x, real_y, target_x, target_y);
scan_tick = 0;
return;
}
// 无目标:低速左右扫描 yaw,俯仰停住
Emm_V5_Vel_Control_1(2, 0, 0, 10, 0); // 停止俯仰
if (scan_tick++ >= 150) // 每隔固定周期换向
{
scan_tick = 0;
scan_dir = (scan_dir == 0) ? 1 : 0;
}
Emm_V5_Vel_Control_1(1, scan_dir, 5, 10, 0);
}
视频: 已收到奖金4000元,感谢STC的支持!!! 厉害的大神。
页:
[1]