DIY 自动追踪奈夫炮塔:基于步进电机、摄像头与 Arduino 的智能发射装置
元器件清单
| 元件 | 数量 | 备注 |
|---|---|---|
| OpenBuilds NEMA 17 步进电机 | 1 | 偏航轴驱动 |
| DFRobot 带减速箱步进电机 | 1 | 俯仰轴驱动 |
| DFRobot DRV8825 双路步进驱动板 | 1 | 驱动两个步进电机 |
| DFRobot Pixy2 智能摄像头 | 1 | 识别霓虹粉色目标 |
| Arduino Mega 2560 | 1 | 主控板,引脚丰富 |
| HC-SR04 超声波测距模块 | 1 | 测量目标距离 |
| Nerf Nitron 电动发射枪 | 1 | 发射主体 |
所需工具
- 3D 打印机(通用)
- 电烙铁
- CNC 雕刻机(切割旋转底盘)
软件与在线服务
- Arduino IDE
- Autodesk Fusion 360(CAD 设计、CAM 生成刀路)
项目全记录
创意起源
几年前看到一个半自动炮塔项目,启发我用 Pixy2 实现全自动捕获-瞄准-发射。目标颜色设为霓虹粉,便于视觉锁定。
核心组件选型
- 视觉:Pixy2 易于与主控通讯,内置颜色追踪算法。
- 主控:Arduino Mega 2560,IO 口充足,驱动两路步进+舵机+超声波+触发 MOSFET。
- 双轴驱动:偏航(水平旋转)用 NEMA 17 直驱旋转盘;俯仰(垂直倾斜)用带减速箱步进电机驱动线性滑块机构,通过连杆推动枪身绕中心轴摆动。
CAD 设计
在 Fusion 360 中导入 Nerf 枪轮廓图建模,设计带轴承支撑的旋转平台,侧装偏航电机。俯仰机构采用丝杠+光轴线性滑台,一端固定滑块,另端铰接枪尾,实现精准俯仰。
零件制造
绝大多数零件 3D 打印;旋转底盘用 Fusion 360 生成 CAM 刀路,CNC 从胶合板切割。
组装调试
- 轴承座安装旋转盘。
- 组装俯仰线性机构:穿 6 mm 光轴与丝杠。
- 用钢销和铝型材支架固定 Nerf 枪。
- 接线:步进驱动板接 Mega 数字口,舵机控制扳机,MOSFET 控制发射电机供电,超声波模块测距,限位开关回零。
固件逻辑(状态机)
状态流转:
ACQUIRE(捕获目标) → POSITION(定位枪口) → SPOOL(发射电机加速 5s) → FIRE(舵机扣扳机) → WIND_DOWN(关电机延时 2s) → RETURN(回初始态)
捕获目标:Pixy2 追踪粉色色块,云台运动直到色块中心落在画面中心死区(±15 像素)内,记录俯仰步数换算当前角度。
定位:超声波测距得斜距,结合当前角度三角函数解算水平/垂直距离,get_angle() 计算抛物线弹道所需仰角(初速 21.3 m/s,重力 9.8),驱动俯仰电机转至目标角度。
发射:MOSFET 导通发射电机 5 秒加速,舵机推杆扣动扳机 400 ms,复位 2 秒后关电机,激光指示灯熄灭,回到捕获状态。
实测效果
墙面贴粉色卡片测试,程序自动标定不同距离仰角,命中率高。附视频演示。
核心代码(Arduino C++)
#include <math.h>
#include <Arduino.h>
#include "BasicStepperDriver.h"
#include <Servo.h>
#include <Pixy2I2C.h>
// 引脚定义
const int pins[] = {6,7,8,5,4,12}; // MX_STEP,DIR,EN, MY_STEP,DIR,EN
const int limit_switch = 26, laser_pin = 11, spool_pin = 10, servo_pin = 13;
const int distance_trig = 29, distance_echo = 30;
// 弹道参数
double velocity = 21.336; // m/s
double velocity_squared = 455.225;
float current_angle = 0.0;
float hyp_distance; // 斜距
#define X_MID 164
#define Y_MID 150
#define DEADZONE 15
#define G 9.8
#define STP_PER_DEG_YAW 3.333
#define STP_PER_DEG_PITCH 184859
#define MICROSTEPS 32
#define RPM 120
#define MOTOR_STEPS_Y 200
#define MOTOR_STEPS_X 1036
BasicStepperDriver pitch_stepper(MOTOR_STEPS_X, pins[1], pins[0]);
BasicStepperDriver yaw_stepper(MOTOR_STEPS_X, pins[4], pins[3]);
Servo trigger;
Pixy2I2C pixy;
enum States { ACQUIRE, POSITION, SPOOL, FIRE, WIND_DOWN, RETURN };
States state = ACQUIRE;
void setup() {
Serial.begin(115200);
init_pins();
delay(1000);
pixy.init();
Serial.println("Ready...");
}
void loop() {
switch(state){
case ACQUIRE: acquire_target(); state = POSITION; digitalWrite(laser_pin,HIGH); break;
case POSITION: position_gun(); state = SPOOL; break;
case SPOOL: digitalWrite(spool_pin,HIGH); delay(5000); state = FIRE; break;
case FIRE: fire_gun(); state = WIND_DOWN; break;
case WIND_DOWN: digitalWrite(spool_pin,LOW); delay(2000); state = RETURN; digitalWrite(laser_pin,LOW); state = ACQUIRE; break;
}
}
void fire_gun(){
trigger.write(108); delay(400); trigger.write(90); delay(2000);
}
void position_gun(){
hyp_distance = ping()/100.0; // 转米
while(!hyp_distance) hyp_distance = ping()/100.0;
float x = cos(current_angle) * hyp_distance;
float y = sin(current_angle) * hyp_distance;
float target_angle = get_angle(x,y)/100.0;
move_pitch(target_angle - current_angle);
current_angle = target_angle;
}
void acquire_target(){
long steps_taken=0; bool lock=false;
while(!lock){
pixy.ccc.getBlocks();
if(pixy.ccc.numBlocks){
int x=pixy.ccc.blocks[0].m_x, y=pixy.ccc.blocks[0].m_y;
if(x <= X_MID-DEADZONE) move_yaw(1);
else if(x >= X_MID+DEADZONE) move_yaw(-1);
else if(y <= Y_MID-DEADZONE) { pitch_stepper.move(33152); steps_taken+=33152; }
else if(y >= Y_MID+DEADZONE) { pitch_stepper.move(-33152); steps_taken+=33152; }
else { lock=true; current_angle = steps_taken/STP_PER_DEG_PITCH; }
}
}
}
void init_pins(){
pinMode(pins[2],OUTPUT); pinMode(pins[5],OUTPUT);
pinMode(limit_switch,INPUT_PULLUP);
pinMode(laser_pin,OUTPUT); pinMode(spool_pin,OUTPUT);
pinMode(distance_echo,INPUT); pinMode(distance_trig,OUTPUT);
digitalWrite(pins[2],LOW); digitalWrite(pins[5],LOW);
digitalWrite(laser_pin,LOW); digitalWrite(spool_pin,LOW);
trigger.attach(servo_pin);
pitch_stepper.begin(RPM, MICROSTEPS);
yaw_stepper.begin(5, MICROSTEPS);
trigger.write(90);
}
void move_yaw(float deg){ yaw_stepper.move(deg*STP_PER_DEG_YAW*32); }
void move_pitch(float deg){ current_angle+=deg; pitch_stepper.move(deg*STP_PER_DEG_PITCH); }
float get_angle(float dist, float h){
float i = 2*h*velocity_squared + G*dist*dist;
float j = G*i;
i = sqrt(pow(velocity_squared,2) - j);
return atan((velocity_squared-i)/(G*dist))*(180/PI);
}
float ping(){
digitalWrite(distance_trig,LOW); delayMicroseconds(5);
digitalWrite(distance_trig,HIGH); delayMicroseconds(10);
digitalWrite(distance_trig,LOW);
long dur = pulseIn(distance_echo,HIGH);
return dur/2/29.1; // cm
}
void home_pitch(){
if(!digitalRead(limit_switch)) pitch_stepper.rotate(720);
while(digitalRead(limit_switch)) pitch_stepper.move(-32);
pitch_stepper.rotate(2880*2);
}
3D 打印模型
Thingiverse 仓库:项目文件下载
原理图
见项目附件原理图(接线示意)。
制造工艺
- Raspoulette:太阳能自主鸡舍门控制系统原型
- Simple Pi Robot:用树莓派打造 2WD 自主巡航车
- Arduino 通用双极性步进电机控制库——精准控制转速、角度、圈数与方向
- Arduino 步进电机自动刷 Tinder/Bumble:小时 800+ 次滑动神器
- 自主家庭助理机器人 O'nine - ROS + Alexa 语音控制 + 机械臂 + 麦克纳姆轮全向移动
- SmartWay:基于 Arduino MKR Fox 1200 的儿童上学路径监护系统
- Arduino 串口控制步进电机旋转台:28BYJ-48 精准定位
- 线性电机原理与演进:从电能到精密直线运动的技术演变
- 伺服电机深度解析:工作原理、分类与核心价值
- 伺服与步进电机:打破认知边界的现代灰色地带
- 步进电机:类型、特性与实际应用
- 集成驱动器简化大功率步进电机设计