This project implements a 4-wheel drive tank-style rover controlled by an Arduino, which receives commands from a Raspberry Pi via USB Serial connection. The rover features tank steering, collision detection, emergency stop capability, and automatic collision recovery.
- 4x 12V DC Motors - One per wheel
- 2x L298N Motor Drivers
- Left L298N: Controls both left side motors (front + rear)
- Right L298N: Controls both right side motors (front + rear)
- Arduino - Motor control and safety monitoring
- Raspberry Pi - High-level control, connected to Arduino via USB
- 2x Bumper Switches (normally open)
- Left bumper for left side collision detection
- Right bumper for right side collision detection
- 1x Emergency Stop Switch (normally closed)
- Opens circuit when pressed to immediately stop all motors
Reference : https://learn.sparkfun.com/tutorials/pro-micro--fio-v3-hookup-guide/all
| Pin | Function | Description |
|---|---|---|
| 5 | LEFT_ENA | Left front motor speed (PWM) |
| 4 | LEFT_IN1 | Left front motor direction |
| 7 | LEFT_IN2 | Left front motor direction |
| 8 | LEFT_IN3 | Left rear motor direction |
| 14 | LEFT_IN4 | Left rear motor direction |
| 6 | LEFT_ENB | Left rear motor speed (PWM) |
| Pin | Function | Description |
|---|---|---|
| 9 | RIGHT_ENA | Right front motor speed (PWM) |
| 15 | RIGHT_IN1 | Right front motor direction |
| 16 | RIGHT_IN2 | Right front motor direction |
| 18 | RIGHT_IN3 | Right rear motor direction |
| 19 | RIGHT_IN4 | Right rear motor direction |
| 10 | RIGHT_ENB | Right rear motor speed (PWM) |
| Pin | Function | Type |
|---|---|---|
| 20 | LEFT_BUMPER | Normally open (INPUT_PULLUP) |
| 21 | RIGHT_BUMPER | Normally open (INPUT_PULLUP) |
| 0 | ESTOP_SWITCH | Normally closed (INPUT_PULLUP) |
Pin 2 for left wheel Pin 3 for right wheel
- All safety switches connect between their pin and GND
- Internal pullup resistors are enabled on all switch pins
- Bumpers: LOW when pressed (collision detected)
- E-Stop: LOW when pressed (emergency stop activated)
- Interface: USB Serial (Arduino ↔ Raspberry Pi)
- Baud Rate: 115200
- Format: JSON commands sent as strings with newline terminator
- Responses: Arduino sends "OK" or error messages
{"cmd":"forward", "speed":200}
{"cmd":"forward", "speed":200, "duration":2.5}- Moves both tracks forward at the same speed
- Speed range: 0-255
- Optional duration in seconds
{"cmd":"backward", "speed":200}
{"cmd":"backward", "speed":150, "duration":1.5}- Moves both tracks backward at the same speed
- Speed range: 0-255
- Optional duration in seconds
{"cmd":"left", "speed":180}
{"cmd":"left", "speed":180, "duration":1}- Left track moves backward, right track moves forward
- Creates sharp pivot turn in place
- Speed range: 0-255
- Optional duration in seconds
{"cmd":"right", "speed":180}
{"cmd":"right", "speed":180, "duration":1}- Right track moves backward, left track moves forward
- Creates sharp pivot turn in place
- Speed range: 0-255
- Optional duration in seconds
{"cmd":"curve", "left_speed":200, "right_speed":150}
{"cmd":"curve", "left_speed":200, "right_speed":150, "duration":3}
{"cmd":"curve", "left_speed":200, "right_speed":150, "right_clicks":42, "left_clicks":35}- Independent speed control for each track
- Creates smooth curved paths
- Speed range: -255 to 255 (negative = reverse)
- Default speeds: 150 if not specified
- Optional
durationin seconds - Optional
right_clicks/left_clicks: odometer pulse targets for each track (used by predefined curve directives calculated by Django from rover geometry measurements)
{"left":200, "right":150}
{"left":200, "right":150, "duration":2}- Direct control of left and right track speeds
- Most flexible control method
- Speed range: -255 to 255 (negative = reverse)
- Optional duration in seconds
{"cmd":"stop"}- Immediately stops all motors
- Cancels any active timed movement
- Does NOT clear collision flags
{"cmd":"reset"}- Clears collision detection flags
- Does NOT clear e-stop flag (clears automatically on pin release)
- Required after collision to resume normal operation
{"cmd":"identify"}- Returns
{"cr_usb_device":"arduino_mcu_4wd_tank"} - Also broadcast automatically on startup (after "Tank Rover Ready")
- Used by
rpi_rover_servicesto identify the connected device
The Arduino broadcasts events without being asked. The RPi reads these asynchronously in a background thread to maintain live safety state.
| Event | Format | Meaning |
|---|---|---|
| Startup | {"cr_usb_device":"arduino_mcu_4wd_tank"} |
Device identification on boot |
| Left collision | {"left_collision": 1} or {"left_collision": -1} |
+1 = hit while moving forward, -1 = hit while reversing |
| Right collision | {"right_collision": 1} or {"right_collision": -1} |
Same as above for right side |
| E-stop active | EMERGENCY STOP ACTIVATED |
Plain text |
| E-stop cleared | Emergency stop cleared |
Plain text |
| Recovery done | Recovery complete |
Plain text |
rpi_rover_services exposes these via GET /www/api/robot/state so the React UI can poll for live safety status.
All movement commands support an optional "duration" parameter:
- Specified in seconds (float)
- Motors run for specified time then automatically stop
- Can be cancelled early with stop command
- Examples:
0.5,1,2.5,10
Without duration: Motors run continuously until stop command or collision
With duration: Motors run for specified time then stop automatically
- Trigger: E-stop pin 0 reads LOW (
INPUT_PULLUP; connect to GND to activate, open/connect to PWR to clear) - Action: Immediately stops all motors
- Serial output:
EMERGENCY STOP ACTIVATED - Recovery: Flag clears automatically when pin returns HIGH (switch released). No
{"cmd":"reset"}needed for e-stop alone. - Also broadcasts:
Emergency stop clearedwhen released
- Trigger: Bumper switch pressed (normally open closes, pin reads LOW)
- Action:
- Immediately reverses last movement direction
- Backs away for
inverse_action_time(default 1000ms) - Stops motors
- Sets collision flag
- Serial output:
LEFT COLLISION DETECTED - ReversingRIGHT COLLISION DETECTED - ReversingRecovery complete- JSON event broadcast:
{"left_collision": 1}or{"right_collision": -1}(value:+1= forward hit,-1= reverse hit)
- Recovery: Send
{"cmd":"reset"}to clear collision flags; also broadcasts{"left_collision": 0, "right_collision": 0}via thesafety_resetevent
- E-stop - Highest priority, stops everything
- Collision Detection - Triggers automatic recovery
- Movement Commands - Blocked if safety flags are set
unsigned long inverse_action_time = 1000; // Collision recovery duration in milliseconds- Left Track: Front left motor + Rear left motor (move in unison)
- Right Track: Front right motor + Rear right motor (move in unison)
- Independent track control enables:
- Forward/backward movement (both tracks same speed/direction)
- Pivot turns (tracks opposite directions)
- Curved turns (tracks different speeds, same direction)
- Point turns (one track stopped, other moving)
When collision detected:
- Capture current movement direction and speed
- Reverse the movement (invert speeds)
- Execute reverse movement for
inverse_action_timeduration - Stop motors after recovery complete
- Set collision flag (requires reset to resume)
Examples:
- Moving forward at 200 → backs up at -200 for 1 second
- Turning right (left:200, right:-100) → reverses to (left:-200, right:100)
- Stopped (0 speed) → no movement during recovery
When duration specified:
- Start motors at requested speed
- Track elapsed time
- Stop automatically when duration expires
- Print "Timed movement complete" to serial
- Allow new commands to override current timed movement
- ArduinoJson (version 6.x)
- Install via Arduino IDE: Sketch → Include Library → Manage Libraries
- Search for "ArduinoJson" by Benoit Blanchon
Send: {"cmd":"forward","speed":200,"duration":2}
Response: OK
(After 2 seconds): Timed movement complete
Send: {"cmd":"right","speed":180,"duration":1}
Response: OK
(After 1 second): Timed movement complete
Send: {"cmd":"stop"}
Response: OK
Send: {"cmd":"forward","speed":200}
Response: OK
(Collision occurs)
Output: RIGHT COLLISION DETECTED - Reversing
(Motors reverse for 1 second)
Output: Recovery complete
Send: {"cmd":"forward","speed":200}
Response: ERROR: Collision detected, send {"cmd":"reset"} to clear
Send: {"cmd":"reset"}
Response: Safety flags reset
Send: {"cmd":"forward","speed":200}
Response: OK
Send: {"cmd":"forward","speed":200}
Response: OK
(E-stop pin pulled LOW)
Output: EMERGENCY STOP ACTIVATED
Send: {"cmd":"forward","speed":200}
Response: ERROR: Emergency stop active
(E-stop pin released / returned HIGH)
Output: Emergency stop cleared
// Flag clears automatically — no reset command needed
Send: {"cmd":"forward","speed":200}
Response: OK
{"cmd":"forward","speed":150,"duration":2}
{"cmd":"backward","speed":150,"duration":2}
{"cmd":"left","speed":180,"duration":1}
{"cmd":"right","speed":180,"duration":1}
{"cmd":"stop"}{"cmd":"curve","left_speed":200,"right_speed":100,"duration":2}
{"cmd":"curve","left_speed":100,"right_speed":200,"duration":2}
{"cmd":"curve","left_speed":200,"right_speed":-50,"duration":1}{"left":200,"right":200,"duration":2}
{"left":150,"right":200,"duration":2}
{"left":-150,"right":-150,"duration":1}
{"left":0,"right":180,"duration":1}// Test collision recovery (manually trigger bumper)
{"cmd":"forward","speed":200}
// Press bumper, observe reversal
{"cmd":"reset"}
// Test e-stop
{"cmd":"forward","speed":200}
// Press e-stop, motors should stop
// Release e-stop
{"cmd":"reset"}
{"cmd":"forward","speed":200}import serial
import json
import time
# Open serial connection to Arduino
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
time.sleep(2) # Wait for Arduino to initialize
def send_command(cmd_dict):
"""Send JSON command to Arduino"""
cmd_json = json.dumps(cmd_dict) + '\n'
ser.write(cmd_json.encode())
response = ser.readline().decode().strip()
print(f"Sent: {cmd_json.strip()} | Response: {response}")
return response
# Example usage
send_command({"cmd": "forward", "speed": 200, "duration": 2})
time.sleep(2.5)
send_command({"cmd": "right", "speed": 180, "duration": 1})
time.sleep(1.5)
send_command({"cmd": "stop"})
# Close connection
ser.close()Linux/Raspberry Pi:
ls /dev/ttyACM* /dev/ttyUSB*
# Usually /dev/ttyACM0 or /dev/ttyUSB0macOS:
ls /dev/tty.usb*
# Usually /dev/tty.usbmodemXXXXWindows:
Check Device Manager → Ports (COM & LPT)
Usually COM3, COM4, etc.
- Check e-stop switch is not pressed
- Verify collision flags cleared with
{"cmd":"reset"} - Check L298N power connections (12V supply)
- Verify all motor driver enable pins connected
- Test with simple command:
{"cmd":"forward","speed":100}
- Verify baud rate is 115200
- Check USB cable connection
- Ensure correct serial port selected
- Add newline character to commands
- Check for JSON syntax errors
- Verify bumper switches connected to correct pins
- Test switch continuity with multimeter
- Check that rover was moving when collision occurred (recovery inverts last movement)
- Adjust
inverse_action_timeif backing up too little/much
- Check connections for that specific L298N
- Verify both ENA and ENB pins connected and PWM-capable
- Test individual track with direct control:
{"left":200,"right":0} - Check motor power supply connections
- Ensure proper JSON format with newline
- Check Arduino not resetting during operation
- Verify
millis()functioning (shouldn't overflow for 50 days)
- L298N requires separate power for motors (typically 7-12V)
- Ensure adequate current capacity for 4 motors
- Recommended: 12V battery pack with 5A+ capacity
- Use common ground between Arduino and motor power supply
- Can be powered via USB from Raspberry Pi
- Or use separate 5V regulator from battery
- Ensure stable power during motor operation
- Ultrasonic distance sensors for obstacle avoidance
- IMU/Gyroscope for heading control
- GPS module for outdoor navigation
- Camera integration with Raspberry Pi
- Battery voltage monitoring
- Current sensing for motor load detection
- PID control for precise movements
- Odometry/encoder support for accurate distance measurement (implemented via INT0/INT1 hardware interrupts)
- Multiple speed profiles (slow/medium/fast presets)
- Acceleration/deceleration ramps for smooth starts/stops
- Add motor speed calibration values
- Implement command queue for sequential movements
- Add telemetry reporting (position, heading, battery)
- Create higher-level navigation commands
- Add diagnostic mode for testing individual components
This documentation and associated Arduino firmware are provided as-is for educational and hobbyist use. Modify and adapt as needed for your specific rover configuration.
- v1.0 - Initial implementation with tank steering
- v1.1 - Added collision detection and recovery
- v1.2 - Added timed movement duration parameter
- v1.3 - Optimized to use 2 L298N drivers instead of 4
- v1.4 - Added curve command for smooth turns
**Happy Roving! �