DIY 电动相机滑轨带云台——Arduino 控制项目
概述
本教程将学习如何制作带云台(俯仰/偏航)的电动相机滑轨。这是一个 100% DIY 的 Arduino 项目,使用 MDF 和胶合板等廉价材料,通过 Arduino、三个步进电机、按钮和摇杆(集成在定制 PCB 上)控制。尽管成本低廉,成品效果令人印象深刻,能实现超平滑的相机运动,拍出专业级电影镜头。
你可以观看下方视频或阅读以下图文教程。
通过控制器可手动移动相机,或设定起止点让相机自动往返。配合支撑臂,甚至可将滑轨安装在小型三脚架上、任意角度,仍能保持稳定运动。

首先用 3D 建模软件设计滑轨。

可在下方下载 3D 模型获取所有尺寸,也可在 Thangs 浏览器中查看。

3D 打印 GT2 带轮链接:Thingiverse
制作相机滑轨
滑轨用 22mm 铜管,长 1 米。铜管易氧化,需抛光(我用牙膏代替抛光剂)。

木质底座用两块 21mm 胶合板胶合,复刻三脚架快装板固定其上,便于安装三脚架。

用 8mm 螺纹杆固定铜管,管上钻 8mm 孔(用简易夹具保证两侧孔对齐,分级钻进 2/4/6/8mm)。


铜管穿过螺纹杆,垫圈螺母锁紧;两端同理固定。

滑动平台用 8mm MDF 切割,装 608Z 轴承(外径 22mm),两轴承间用垫圈和螺母间隔,确保与 22mm 铜管良好接触。为防倾斜滑动时脱轨,底部再加两组轴承。平台滑动顺滑。


小三脚架承重不足,制作支撑臂稳定:木块夹具夹在三脚架腿上,支撑臂开长槽(密集钻 6mm 孔再锉平)以调节角度,效果完美。


加装固定腿(8mm MDF),无需三脚架也能用。

云台部分:按 3D 模型切割胶合板/MDF,制作水平调节铰链、电机安装座和轴承座。

滑轨步进电机一侧固定,另一侧装惰轮,用 GT2 同步带连接平台(螺栓+扎带固定带)。

友人 3D 打印两个 80 齿带轮,配合电机 16 齿轮实现 5:1 减速。

按需剪裁同步带做成闭环,用 401 胶水粘接并贴胶带加固。
云台最终组装:固定俯仰电机,装轴承并用环氧树脂固定;俯仰平台用 8mm 螺栓锁在偏航平台上,同时装上 80 齿带轮和同步带。带子稍松,在电机螺栓处加小轴承作张紧轮解决。

装偏航电机,水平平台上下装轴承、推力轴承、80 齿带轮、同步带,最后装上俯仰头并锁紧螺栓。机械结构完成。

电路图
三个 NEMA17 步进电机由三个 A4988 驱动。滑轨用电位器(模拟输入),云台用摇杆模块(双电位器,两路模拟输入),另有一电位器设自动运行速度。设定起止点用按钮(上拉电阻,数字引脚)。另有复位按钮、电源开关、电源插座、滑轨限位开关、两指示 LED。供电 9V 或 12V。

所需组件:
- 步进电机 (NEMA17)
- A4988 步进驱动
- 12V 2A 适配器
- 摇杆模块
- 电源插座
- Arduino 开发板
PCB 设计
按电路图用 EasyEDA 设计双层 PCB,生成 Gerber 文件,JLCPCB 制板(首单 10 片 $2)。




电子组装
PCB 焊排针,所有模块焊跳线插排针,便于安装拆卸。

控制器外壳用 4mm 透明亚克力,圆锯切割,前面板开孔装按钮、电位器、开关、摇杆。5 分钟环氧胶组装,顶盖用两螺栓穿插螺母固定。

将 Arduino 和 A4988 插 PCB,前面板元件接排针,侧板装电源插座,侧板孔引出跳线连电机和限位开关。热缩管整理线材。最后接三节 3.7V 锂电串联(约 11V)供电,滑轨完工完美运行。




DIY 相机滑轨 Arduino 代码
代码较长,分段讲解,文末附完整代码。基于 Mike McCauley 的 AccelStepper 库,支持多电机同步控制。
引脚定义与变量
#include <AccelStepper.h>
#include <MultiStepper.h>
#define JoyX A0
#define JoyY A1
#define slider A2
#define inOutPot A3
#define JoySwitch 10
#define InOutSet 12
#define limitSwitch 11
#define inLED 8
#define outLED 9
AccelStepper stepper1(1, 7, 6); // 滑轨
AccelStepper stepper2(1, 5, 4); // 偏航
AccelStepper stepper3(1, 3, 2); // 俯仰
MultiStepper StepperControl;
long gotoposition[3];
int JoyXPos=0, JoyYPos=0, sliderPos=0;
int currentSpeed=100, inOutSpeed=100;
int XInPoint=0, YInPoint=0, ZInPoint=0;
int XOutPoint=0, YOutPoint=0, ZOutPoint=0;
int InandOut=0;
setup:初始化与回零
void setup() {
stepper1.setMaxSpeed(3000); stepper1.setSpeed(200);
stepper2.setMaxSpeed(3000); stepper2.setSpeed(200);
stepper3.setMaxSpeed(3000); stepper3.setSpeed(200);
pinMode(JoySwitch, INPUT_PULLUP);
pinMode(InOutSet, INPUT_PULLUP);
pinMode(limitSwitch, INPUT_PULLUP);
pinMode(inLED, OUTPUT);
pinMode(outLED, OUTPUT);
StepperControl.addStepper(stepper1);
StepperControl.addStepper(stepper2);
StepperControl.addStepper(stepper3);
// 回零:撞限位开关设为 0,再退 200 步释放
while (digitalRead(limitSwitch) != 0) {
stepper1.setSpeed(3000);
stepper1.runSpeed();
stepper1.setCurrentPosition(0);
}
delay(20);
while (stepper1.currentPosition() != -200) {
stepper1.setSpeed(-3000);
stepper1.run();
}
}
loop 主逻辑
限位保护:触发限位或超行程(-64800 步)则停止。
while (digitalRead(limitSwitch) == 0 || stepper1.currentPosition() < -64800) {}
按摇杆按键增加云台速度(每按 +50)。
if (digitalRead(JoySwitch) == 0) { currentSpeed += 50; delay(200); }
设定按钮(InOutSet)切换状态机:
- 第 1 次按:记录 IN 点(三电机当前位置),亮 IN LED。
- 第 2 次按:记录 OUT 点,亮 OUT LED。
- 第 3 次按:读自动速度电位器,将 IN 点写入数组,设三电机最大速度,用 moveTo() 计算各电机速度,runSpeedToPosition() 阻塞移动至 IN 点。
- 第 4 次按:同理移动至 OUT 点,之后在案例 2 和 3 间循环。
- 长按 >0.5s:复位 IN/OUT,熄灭 LED。
switch (InandOut) {
case 0: InandOut=1; XInPoint=stepper1.currentPosition(); YInPoint=stepper2.currentPosition(); ZInPoint=stepper3.currentPosition(); digitalWrite(inLED,HIGH); break;
case 1: InandOut=2; XOutPoint=stepper1.currentPosition(); YOutPoint=stepper2.currentPosition(); ZOutPoint=stepper3.currentPosition(); digitalWrite(outLED,HIGH); break;
case 2: InandOut=3; inOutSpeed=analogRead(inOutPot); gotoposition[0]=XInPoint; gotoposition[1]=YInPoint; gotoposition[2]=ZInPoint; stepper1.setMaxSpeed(inOutSpeed); stepper2.setMaxSpeed(inOutSpeed); stepper3.setMaxSpeed(inOutSpeed); StepperControl.moveTo(gotoposition); StepperControl.runSpeedToPosition(); delay(200); break;
case 3: InandOut=2; inOutSpeed=analogRead(inOutPot); gotoposition[0]=XOutPoint; gotoposition[1]=YOutPoint; gotoposition[2]=ZOutPoint; stepper1.setMaxSpeed(inOutSpeed); stepper2.setMaxSpeed(inOutSpeed); stepper3.setMaxSpeed(inOutSpeed); StepperControl.moveTo(gotoposition); StepperControl.runSpeedToPosition(); delay(200); break;
case 4: InandOut=0; digitalWrite(inLED,LOW); digitalWrite(outLED,LOW); delay(1000); break;
}
摇杆控制云台:X 轴控制偏航,Y 轴控制俯仰。模拟值 0-1024,中位约 500。>600 正转,<400 反转,中间停止。电位器控制滑轨同理,且转动越大速度越快(map 映射)。
// 摇杆 X - 偏航
JoyXPos = analogRead(JoyX);
if (JoyXPos > 600) stepper2.setSpeed(currentSpeed);
else if (JoyXPos < 400) stepper2.setSpeed(-currentSpeed);
else stepper2.setSpeed(0);
// 摇杆 Y - 俯仰
JoyYPos = analogRead(JoyY);
if (JoyYPos > 600) stepper3.setSpeed(currentSpeed);
else if (JoyYPos < 400) stepper3.setSpeed(-currentSpeed);
else stepper3.setSpeed(0);
// 滑轨电位器
sliderPos = analogRead(slider);
if (sliderPos > 600) { sliderPos = map(sliderPos, 600, 1024, 0, 3000); stepper1.setSpeed(sliderPos); }
else if (sliderPos < 400) { sliderPos = map(sliderPos, 400, 0, 0, 3000); stepper1.setSpeed(-sliderPos); }
else stepper1.setSpeed(0);
// 执行
stepper1.runSpeed(); stepper2.runSpeed(); stepper3.runSpeed();
完整源代码
/* DIY Camera Slider with Pan and Tilt Head by Dejan Nedelkovski www.HowToMechatronics.com
Library - AccelStepper by Mike McCauley: https://www.airspayce.com/mikem/arduino/AccelStepper/index.html */
#include <AccelStepper.h>
#include <MultiStepper.h>
#define JoyX A0
#define JoyY A1
#define slider A2
#define inOutPot A3
#define JoySwitch 10
#define InOutSet 12
#define limitSwitch 11
#define inLED 8
#define outLED 9
AccelStepper stepper1(1, 7, 6);
AccelStepper stepper2(1, 5, 4);
AccelStepper stepper3(1, 3, 2);
MultiStepper StepperControl;
long gotoposition[3];
int JoyXPos=0, JoyYPos=0, sliderPos=0;
int currentSpeed=100, inOutSpeed=100;
int XInPoint=0, YInPoint=0, ZInPoint=0;
int XOutPoint=0, YOutPoint=0, ZOutPoint=0;
int InandOut=0;
void setup() {
stepper1.setMaxSpeed(3000); stepper1.setSpeed(200);
stepper2.setMaxSpeed(3000); stepper2.setSpeed(200);
stepper3.setMaxSpeed(3000); stepper3.setSpeed(200);
pinMode(JoySwitch, INPUT_PULLUP);
pinMode(InOutSet, INPUT_PULLUP);
pinMode(limitSwitch, INPUT_PULLUP);
pinMode(inLED, OUTPUT);
pinMode(outLED, OUTPUT);
StepperControl.addStepper(stepper1);
StepperControl.addStepper(stepper2);
StepperControl.addStepper(stepper3);
while (digitalRead(limitSwitch) != 0) { stepper1.setSpeed(3000); stepper1.runSpeed(); stepper1.setCurrentPosition(0); }
delay(20);
while (stepper1.currentPosition() != -200) { stepper1.setSpeed(-3000); stepper1.run(); }
}
void loop() {
while (digitalRead(limitSwitch) == 0 || stepper1.currentPosition() < -64800) {}
if (digitalRead(JoySwitch) == 0) { currentSpeed += 50; delay(200); }
if (digitalRead(InOutSet) == 0) {
delay(500);
if (digitalRead(InOutSet) == 0) { InandOut = 4; }
switch (InandOut) {
case 0: InandOut=1; XInPoint=stepper1.currentPosition(); YInPoint=stepper2.currentPosition(); ZInPoint=stepper3.currentPosition(); digitalWrite(inLED,HIGH); break;
case 1: InandOut=2; XOutPoint=stepper1.currentPosition(); YOutPoint=stepper2.currentPosition(); ZOutPoint=stepper3.currentPosition(); digitalWrite(outLED,HIGH); break;
case 2: InandOut=3; inOutSpeed=analogRead(inOutPot); gotoposition[0]=XInPoint; gotoposition[1]=YInPoint; gotoposition[2]=ZInPoint; stepper1.setMaxSpeed(inOutSpeed); stepper2.setMaxSpeed(inOutSpeed); stepper3.setMaxSpeed(inOutSpeed); StepperControl.moveTo(gotoposition); StepperControl.runSpeedToPosition(); delay(200); break;
case 3: InandOut=2; inOutSpeed=analogRead(inOutPot); gotoposition[0]=XOutPoint; gotoposition[1]=YOutPoint; gotoposition[2]=ZOutPoint; stepper1.setMaxSpeed(inOutSpeed); stepper2.setMaxSpeed(inOutSpeed); stepper3.setMaxSpeed(inOutSpeed); StepperControl.moveTo(gotoposition); StepperControl.runSpeedToPosition(); delay(200); break;
case 4: InandOut=0; digitalWrite(inLED,LOW); digitalWrite(outLED,LOW); delay(1000); break;
}
}
JoyXPos = analogRead(JoyX);
if (JoyXPos > 600) stepper2.setSpeed(currentSpeed);
else if (JoyXPos < 400) stepper2.setSpeed(-currentSpeed);
else stepper2.setSpeed(0);
JoyYPos = analogRead(JoyY);
if (JoyYPos > 600) stepper3.setSpeed(currentSpeed);
else if (JoyYPos < 400) stepper3.setSpeed(-currentSpeed);
else stepper3.setSpeed(0);
sliderPos = analogRead(slider);
if (sliderPos > 600) { sliderPos = map(sliderPos, 600, 1024, 0, 3000); stepper1.setSpeed(sliderPos); }
else if (sliderPos < 400) { sliderPos = map(sliderPos, 400, 0, 0, 3000); stepper1.setSpeed(-sliderPos); }
else stepper1.setSpeed(0);
stepper1.runSpeed(); stepper2.runSpeed(); stepper3.runSpeed();
}
希望你喜欢这个 Arduino 项目并学到了新知识。欢迎在评论区提问,并查看我的 Arduino 项目合集。
制造工艺
- 蓝牙控制 Arduino 电动相机滑轨:打造丝滑电影级运镜
- 用 Arduino 打造绚丽显示器 Ambilight 系统
- 基于 Arduino 与 Python 的可靠 RFID 考勤系统
- Arduino 与智能手机打造智能电压表——简单易做的 DIY 项目
- Arduino DIY 电压表:Nokia 5110 屏幕显示,手把手教学
- Arduino Nano 驱动 128x64 OLED 实时运行康威生命游戏 (元胞自动机)
- Arduino 高速转速计 - 单次检测算法实测 6.5MHz 级响应,红外 + 红激光方案
- 使用 Arduino Nano 和 RDA8057M 制作紧凑型 FM 收音机
- 基于 Arduino 与 Google Sheets 的实时 RFID 考勤系统
- Arduino 合成乐音:从零实现 88 键钢琴全音域
- 基于 Arduino UNO 构建可靠的智能能源监测仪——分步指南
- 精准降雨预测:基于 Arduino、Python 与 Keras 的端到端气象站系统