Firmware and test notes for the Waveshare 2-axis pan-tilt camera module using
pan_tilt_base_v0.9.ino.
This README focuses on:
- how to upload and use the firmware from Arduino IDE
- how to send serial JSON commands to the pan-tilt
- how to initialize the two motor IDs
- how to set the zero-state degree for both axes
- Keep the pan-tilt clear of cables, hands, and hard stops during setup.
- Power off before unplugging or reconnecting either servo bus cable.
- During ID initialization, disconnect one motor before changing the other one. Both motors can ship with the same default ID, so changing IDs with both attached can affect the wrong motor.
- Use small motions first before trying high-frequency control.
This firmware expects the gimbal motor IDs below:
- pan / X-axis motor:
2 - tilt / Y-axis motor:
1
These values are defined in ugv_config.h as:
GIMBAL_PAN_ID 2GIMBAL_TILT_ID 1
The firmware also clamps gimbal commands to these angle ranges:
X:-180to180Y:-30to90
For the mechanical assembly and vendor-side configuration guide, see:
- Open
pan_tilt_base_v0.9.inoin Arduino IDE. - Select the correct ESP32 board and serial port.
- Upload the firmware.
- Open Serial Monitor.
- Set Serial Monitor to:
- baud rate:
115200 - line ending:
Newline
- baud rate:
Important:
- The firmware serial parser waits for
\n, soNewlinematters. - Only one program can own the serial port at a time. Close Serial Monitor
before using
pan_tilt_hf_test.py, and close Python tools before returning to Serial Monitor.
After boot, you can control the pan-tilt by sending JSON commands in Serial Monitor.
The latest firmware boots in gimbal mode by default. After the pan-tilt is
plugged in, powered on, and finished booting, it automatically sends T:1001
feedback to the computer through USB serial.
The T:1001 gimbal feedback no longer sends the module's own roll, pitch, yaw,
or voltage fields. It only sends the current pan/tilt values as X and Y:
{"T":1001,"X":0,"Y":0}Field meaning:
T: feedback message type, always1001X: current pan / X-axis angleY: current tilt / Y-axis angle
You should see this stream in Arduino IDE Serial Monitor after uploading
pan_tilt_base_v0.9.ino and
opening the correct serial port at 115200 baud.
Useful motion command:
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}This sends a simple pan/tilt goal:
X: pan angle targetY: tilt angle targetSPD: speed parameterACC: acceleration parameter
Example moves:
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}
{"T":133,"X":30,"Y":15,"SPD":120,"ACC":20}
{"T":133,"X":-45,"Y":10,"SPD":120,"ACC":20}Use this procedure when both motors still have the factory default ID and you need to separate them into:
- pan / X-axis motor -> ID
2 - tilt / Y-axis motor -> ID
1
Prepare Arduino IDE and upload pan_tilt_base_v0.9.ino.
Use Arduino IDE Serial Monitor as the communication tool during initialization.
- Remove external power.
- Turn off the power button.
- Keep USB-C connected to the pan-tilt controller.
- Unplug the bus/transmission wire of the second motor so that only one motor remains on the servo bus.
This prevents both default-ID motors from responding to the same ID-change command.
- Plug in power.
- Turn on the power button.
- Wait for the controller to finish booting.
Send:
{"T":501,"raw":1,"new":2}Meaning:
raw: current servo IDnew: new servo ID to store
Because factory default ID is usually 1, this changes the connected motor from
ID 1 to ID 2.
Recommended assumption:
- Do this first for the pan / X-axis motor, since the firmware expects pan to be
ID
2.
- Reconnect the other motor.
At this point the two motors should now be addressable separately as:
- pan / X-axis motor:
2 - tilt / Y-axis motor:
1
Optional torque-release command:
{"T":210,"cmd":0}Note:
T210is torque control, not ID setup.{"T":210,"cmd":0}releases torque.{"T":210,"cmd":1}enables torque.
This can be useful if you need to manually align the mechanism before setting the zero position.
In practice, this is how you define the physical pose that later corresponds to:
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}After both motors are connected, test the current saved zero:
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}Observe whether the current zero position matches the pose you want.
Send a target command that moves the pan-tilt to the physical pose you want to be treated as zero:
{"T":133,"X":??,"Y":??,"SPD":0,"ACC":0}Replace ?? with the temporary angles that place the mechanism at your desired
home pose.
Example:
{"T":133,"X":15,"Y":-5,"SPD":0,"ACC":0}When the pan-tilt reaches the desired physical orientation, keep it there for the next step.
Send:
{"T":502,"id":1} to set the tilt / Y-axis motor zero.
{"T":502,"id":2} to set the pan / X-axis motor zero.This calibrates the current motor positions as the saved middle positions.
Send again:
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}If the calibration succeeded, the pan-tilt should return to the physical pose you wanted to define as zero.
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}
{"T":210,"cmd":0}
{"T":210,"cmd":1}
{"T":501,"raw":1,"new":2}
{"T":502,"id":1}
{"T":502,"id":2}For normal usage after IDs and zero-state are already configured:
- Power on the pan-tilt.
- Open Serial Monitor at
115200withNewline. - Confirm automatic
{"T":1001,"X":...,"Y":...}feedback is streaming. - Send
{"T":133,"X":0,"Y":0,"SPD":0,"ACC":0}. - Try small test moves before high-frequency control.
Once Arduino IDE control is working, close Arduino Serial Monitor and use:
Only one program can use the USB serial port at a time, so Arduino Serial Monitor must be closed before starting the Python test.
- Connect the pan-tilt over USB-C and power it on.
- Close Arduino IDE Serial Monitor.
- List serial ports:
/opt/miniconda3/bin/python3 -m serial.tools.list_ports -v- Use the detected
/dev/cu.usbserial-...or/dev/cu.usbmodem...port in the commands below.
Safe center-position test:
/opt/miniconda3/bin/python3 ./pan_tilt_base_v0.9/pan_tilt_hf_test.py \
--port /dev/cu.usbserial-3140 \
--duration 5 \
--cmd-hz 5 \
--x-center 0 \
--y-center 0 \
--x-amp 0 \
--y-amp 0 \
--spd 0 \
--acc 0 \
--request-xy-hz 0 \
--startup-delay 4High-frequency vigorous test:
/opt/miniconda3/bin/python3 ./pan_tilt_base_v0.9/pan_tilt_hf_test.py \
--port /dev/cu.usbserial-3140 \
--vigorous \
--duration 10 \
--request-xy-hz 0 \
--startup-delay 4In vigorous mode, the script sends T133 targets at about 200 Hz with large
X/Y swings. The startup log should show a profile similar to:
Motion profile: cmd_hz=200.0, x_amp=110.0, y_amp=30.0, x_wave_hz=2.2, y_wave_hz=3.1, spd=320.0, acc=120.0
If serial.tools.list_ports fails with ModuleNotFoundError: No module named 'serial', use the Conda Python shown above or install pyserial into the
Python interpreter you are using.
- No response to serial commands:
- confirm baud rate is
115200 - confirm line ending is
Newline - confirm only one serial client is connected
- confirm baud rate is
- Both motors move during ID setup:
- both motors are probably still sharing the same default ID
- power off and disconnect one motor before sending
T501
X=0, Y=0points to the wrong physical pose:- repeat the zero-state procedure with
T502
- repeat the zero-state procedure with
- Feedback is missing:
- confirm the latest firmware has been uploaded
- confirm Serial Monitor baud rate is
115200 - confirm the pan-tilt has finished booting
- optionally send
{"T":4,"cmd":2}to force gimbal mode - optionally send
{"T":131,"cmd":1}to re-enable feedback flow