Hull controller technical documentation - SergeGit/rc-tank-platform GitHub Wiki
The RC Tank Hull Controller is an Arduino Nano-based system that acts as an I2C slave to a Raspberry Pi Turret Controller. The system provides comprehensive control over a model tank's movement, weapons systems, and lighting.
The controller architecture is modular, separating functionality into distinct systems:
- BMS (Battery Management System): Monitors and controls battery voltage
- Tracks: Controls tank forward/backward movement and turning
- Turret: Controls turret rotation and elevation
- Weapons: Controls cannon, laser, and IR emitters
- Lights: Controls front and rear lighting systems
- Motor Control: Unified system for multiple motor types with speed ramping
- Arduino Nano microcontroller
- Motor drivers:
- Cytron motor drivers for tracks
- L293D motor drivers for turret controls
- Voltage divider for battery monitoring
- Relay for battery control (optional)
- LED lights for front/rear indicators
- Cannon, laser, and IR emitter components
- Appropriate wiring and power supply
All pin assignments are centralized in config.h for easy modification. Key pin assignments include:
- Battery Voltage: A0
- Battery Relay: A1
- Front Lights: A2
- Rear Lights: A7
- Cannon: 13 (also built-in LED)
- Laser: A3
- IR Light: 6
- Cannon Fired Interrupt: 2
- Left Track PWM1: 3
- Left Track PWM2: 9
- Right Track PWM1: 10
- Right Track PWM2: 11
- Rotation Enable: 5
- Rotation Input 1: 8
- Rotation Input 2: 7
- Elevation Enable: 6
- Elevation Input 1: 4
- Elevation Input 2: 12
The controller operates as an I2C slave at address 0x08 (configurable in config.h). It responds to commands from the Raspberry Pi master.
Commands typically consist of two bytes:
- Command byte (identifies the action)
- Data byte (parameter for the action)
| Command | Hex Code | Description | Data Range |
|---|---|---|---|
| Turret Rotate | 0x10 | Controls turret rotation | 0-200 (maps to -100 to +100) |
| Turret Elevate | 0x11 | Controls turret elevation | 0-200 (maps to -100 to +100) |
| Tracks Move | 0x20 | Controls forward/backward movement | 0-200 (maps to -100 to +100) |
| Tracks Turn | 0x21 | Controls left/right turning | 0-200 (maps to -100 to +100) |
| Laser Toggle | 0x30 | Toggles laser on/off | 0 (off) or 1 (on) |
| IR Toggle | 0x31 | Toggles IR emitter on/off | 0 (off) or 1 (on) |
| Cannon Fire | 0x32 | Fires cannon | Duration (see config) |
| Front Lights | 0x40 | Controls front lights | 0 (off), 1 (on), 2 (blinking) |
| Rear Lights | 0x41 | Controls rear lights | 0 (off), 1 (on), 2 (blinking) |
| Get Battery | 0xE0 | Request battery info | N/A |
| Get Position | 0xE1 | Request position data | N/A |
| Get Cannon Status | 0xE2 | Request cannon ready state | N/A |
| Get GPS | 0xE3 | Request GPS data | N/A |
| Get All Sensors | 0xEF | Request all sensor data | N/A |
| Get Status | 0xF0 | Request system status | N/A |
| Emergency Stop | 0xFF | Emergency stop all systems | 0 (stop), 1 (resume) |
The controller responds with varying amounts of data depending on the command:
| Command | Response Size | Response Format |
|---|---|---|
| Get Battery | 5 bytes | Battery data (voltage, type, relay state) |
| Get Position | 4 bytes | Position data (x, y coordinates as uint16) |
| Get Cannon Status | 1 byte | Cannon ready state (0 or 1) |
| Get GPS | 8 bytes | GPS coordinates (latitude, longitude as uint16) |
| Get All Sensors | 16 bytes | Combined sensor data |
| Get Status | 1 byte | Status flags (see below) |
- Bit 0: Cannon ready state
- Bit 1: Laser on
- Bit 2: IR emitter on
- Bit 3: Front lights on/blinking
- Bit 4: Rear lights on/blinking
- Bit 5: Battery relay enabled
- Bit 6: Error state
- Bit 7: Emergency stop active
The BMS monitors battery voltage through a voltage divider connected to analog pin A0. It can:
- Detect battery type
- Enable/disable a battery relay
- Provide battery status information
- Implement voltage compensation for motors
Key functions:
-
setupBatteryManagement(): Initialize the BMS -
updateBatteryManagement(): Regularly update battery readings -
updateBatteryVoltage(): Get current battery voltage -
detectBatteryType(): Auto-detect the battery type -
enableRelay(): Control the battery relay
The unified motor control system handles both track and turret motors. It features:
- Support for multiple motor types (L293D, Cytron)
- Smooth speed ramping
- Voltage compensation based on battery level
- Minimum speed thresholds to overcome inertia
Key functions:
-
registerL293DMotor(): Add an L293D motor to the control system -
registerCytronMotor(): Add a Cytron motor to the control system -
setMotorSpeed(): Set target speed (-100 to 100) -
stopMotor(): Immediately stop a specific motor -
stopAllMotors(): Emergency stop all motors -
updateMotors(): Update motor speeds (call in main loop)
Controls the tank's movement using two track motors in a differential drive configuration.
Key functions:
-
handleTracksMove(): Process forward/backward movement commands -
handleTracksTurn(): Process turning commands -
setArcadeDriveInput(): Direct joystick-style control -
trackdrive(): Set left and right track speeds directly -
emergencyStopTracks(): Immediately stop both tracks
Controls the turret's rotation and elevation motors.
Key functions:
-
handleTurretRotate(): Process turret rotation commands -
handleTurretElevate(): Process turret elevation commands -
stopTurret(): Stop all turret motors
Controls the tank's weapons systems including cannon, laser, and IR emitter.
Key functions:
-
handleCannonFire(): Fire the cannon for a specified duration -
handleLaserToggle(): Toggle the laser on/off -
handleIrToggle(): Toggle the IR emitter on/off -
getIsCannonReady(): Check if cannon is ready to fire
Controls the tank's front and rear lights with support for blinking.
Key functions:
-
handleFrontLights(): Control front lights -
handleRearLights(): Control rear lights -
getFrontLightState(): Get current front light state -
getRearLightState(): Get current rear light state
The controller can be in one of three states:
- STATE_NORMAL: Normal operation
- STATE_ERROR: Error detected
- STATE_EMERGENCY_STOP: Emergency stop activated
The system includes several safety features:
- Watchdog timer (1-second timeout)
- Emergency stop command
- Input validation for all commands
- Battery voltage monitoring
- LED indicators for system state
All configurable parameters are centralized in config.h:
- Battery check interval: 2000ms
- Light blink interval: 500ms
- Motor update interval: 20ms
- Cannon cooldown period: 2000ms
- Default ramp step: 5 PWM units per update
- Track motor minimum threshold: 20 PWM units
- Turret motor minimum threshold: 25 PWM units
- Voltage divider values: R1=100kΩ, R2=22kΩ
- Reference voltage: 5.0V
- Readings for moving average: 10
#include "main.h"
void setup() {
// All subsystems are initialized in main.cpp's setup()
// No additional setup required
}
void loop() {
// All subsystem updates are handled in main.cpp's loop()
// No additional loop code required
}import smbus
import time
# Create I2C bus
bus = smbus.SMBus(1) # Use 0 for older Raspberry Pi models
# I2C address of the Arduino
ARDUINO_ADDRESS = 0x08
# Command codes
CMD_TRACKS_MOVE = 0x20
CMD_CANNON_FIRE = 0x32
CMD_FRONT_LIGHTS = 0x40
# Move forward at half speed
bus.write_byte_data(ARDUINO_ADDRESS, CMD_TRACKS_MOVE, 150) # 150 maps to +50 speed
# Fire cannon with medium duration
bus.write_byte_data(ARDUINO_ADDRESS, CMD_CANNON_FIRE, 128) # ~half max duration
# Turn on front lights
bus.write_byte_data(ARDUINO_ADDRESS, CMD_FRONT_LIGHTS, 1) # 1 = on
# Request battery data
bus.write_byte(ARDUINO_ADDRESS, 0xE0)
battery_data = bus.read_i2c_block_data(ARDUINO_ADDRESS, 0, 5)
voltage = (battery_data[0] | (battery_data[1] << 8)) / 100.0 # Convert to voltage
print(f"Battery: {voltage}V")-
Motors not responding:
- Check motor driver connections
- Verify battery voltage is sufficient
- Ensure motor speed exceeds minimum threshold
-
I2C communication errors:
- Verify I2C address matches in both devices
- Check SDA/SCL connections
- Reduce I2C speed if experiencing issues
- Use pull-up resistors on SDA/SCL lines
-
Erratic behavior:
- Check for adequate power supply
- Verify no pin conflicts exist
- Check for proper grounding
- Isolate motor power from logic power if possible
-
System repeatedly resets:
- Watchdog timer may be triggering
- Ensure loop() executes quickly enough
- Add wdt_reset() calls if needed in long operations
// In your setup function:
byte newMotorIndex = registerL293DMotor(
9, // Enable pin (PWM)
10, // Input 1 pin
11, // Input 2 pin
"New Motor", // Name for diagnostics
25 // Minimum threshold
);
// In your command handler:
void handleNewMotorCommand(byte data) {
// Convert data (0-200) to speed (-100 to 100)
int speed = data - 100;
setMotorSpeed(newMotorIndex, speed);
}- Add new command code in main.h:
#define CMD_NEW_FEATURE 0x50- Add command handler in processCommand() in main.cpp:
case CMD_NEW_FEATURE:
handleNewFeature(data);
break;- Create the handler function:
void handleNewFeature(byte data) {
// Your implementation here
}This section provides a complete reference to the API functions available in each module of the tank controller system.
| Function | Parameters | Return Type | Description |
|---|---|---|---|
processCommand(byte command, byte data) |
command: command codedata: parameter value |
void | Processes I2C commands from the master |
prepareResponseData(byte command) |
command: command code |
void | Prepares response data buffer for I2C requests |
emergencyStop() |
none | void | Activates emergency stop mode |
resumeFromEmergencyStop() |
none | void | Resumes normal operation after E-stop |
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupBatteryManagement() |
none | void | Initializes the battery monitoring system |
updateBatteryManagement() |
none | void | Updates battery readings (call periodically) |
updateBatteryResponseData(byte *responseData) |
responseData: buffer to fill |
void | Fills buffer with battery status data |
logBatteryStatus() |
none | void | Outputs battery status to serial monitor |
updateBatteryVoltage() |
none | float | Measures and returns current battery voltage |
detectBatteryType() |
none | void | Auto-detects connected battery type |
enableRelay(bool enable) |
enable: relay state |
void | Controls battery relay |
-
float batteryVoltage- Current battery voltage -
int batteryType- Detected battery type -
bool isRelayEnabled- Current relay state
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupMotorControl() |
none | void | Initializes the motor control system |
registerL293DMotor() |
enablePin: PWM pin for speedin1Pin: direction control 1in2Pin: direction control 2motorName: name for debugminThreshold: starting power |
byte | Registers an L293D motor and returns its index |
registerCytronMotor() |
driver: CytronMD objectmotorName: name for debugminThreshold: starting power |
byte | Registers a Cytron motor and returns its index |
setMotorSpeed(byte motorIndex, int speed) |
motorIndex: motor IDspeed: -100 to +100 |
void | Sets target speed with ramping |
stopMotor(byte motorIndex) |
motorIndex: motor ID |
void | Immediately stops specified motor |
stopAllMotors() |
none | void | Immediately stops all motors |
updateMotors() |
none | void | Updates motor speeds (call in main loop) |
setSpeedRampStep(byte motorIndex, byte rampStep) |
motorIndex: motor IDrampStep: ramp increment |
void | Adjusts ramping rate for smoother starts/stops |
getMotorSpeed(byte motorIndex) |
motorIndex: motor ID |
int | Returns current speed (-100 to +100) |
getMotorTargetSpeed(byte motorIndex) |
motorIndex: motor ID |
int | Returns target speed (-100 to +100) |
getMotorState(byte motorIndex) |
motorIndex: motor ID |
int | Returns state (0=stopped, 1=running, 2=ramping) |
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupTracks() |
none | void | Initializes track control system |
handleTracksMove(byte data) |
data: 0-200 value |
void | Handles forward/backward from command protocol |
handleTracksTurn(byte data) |
data: 0-200 value |
void | Handles turning from command protocol |
setArcadeDriveInput(int forwardBackward, int leftRight) |
forwardBackward: -100 to +100leftRight: -100 to +100 |
void | Direct arcade-style control input |
trackdrive(int leftSpeed, int rightSpeed) |
leftSpeed: -100 to +100rightSpeed: -100 to +100 |
void | Direct control of track speeds |
emergencyStopTracks() |
none | void | Immediately stops both tracks |
stopTracks() |
none | void | Alias for emergencyStopTracks() |
getLeftTrackSpeed() |
none | int | Returns current left track speed |
getRightTrackSpeed() |
none | int | Returns current right track speed |
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupTurret() |
none | void | Initializes turret control system |
handleTurretRotate(byte data) |
data: 0-200 value |
void | Handles rotation from command protocol |
handleTurretElevate(byte data) |
data: 0-200 value |
void | Handles elevation from command protocol |
getTurretRotationSpeed() |
none | int | Returns current rotation motor speed |
getTurretElevationSpeed() |
none | int | Returns current elevation motor speed |
stopTurret() |
none | void | Immediately stops all turret motors |
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupWeapons() |
none | void | Initializes weapons systems |
updateWeapons() |
none | void | Updates weapons state (call in main loop) |
handleLaserToggle(byte data) |
data: 0 or 1 |
void | Toggles laser on/off |
handleIrToggle(byte data) |
data: 0 or 1 |
void | Toggles IR emitter on/off |
handleCannonFire(byte data) |
data: duration value |
void | Fires cannon with specified duration |
getIsCannonReady() |
none | bool | Returns true if cannon is ready to fire |
getIsLaserOn() |
none | bool | Returns true if laser is active |
getIsIrOn() |
none | bool | Returns true if IR emitter is active |
| Function | Parameters | Return Type | Description |
|---|---|---|---|
setupLights() |
none | void | Initializes lighting system |
updateLights() |
none | void | Updates light states (call in main loop) |
handleFrontLights(byte data) |
data: 0=off, 1=on, 2=blink |
void | Controls front lights |
handleRearLights(byte data) |
data: 0=off, 1=on, 2=blink |
void | Controls rear lights |
getFrontLightState() |
none | byte | Returns front light state (0,1,2) |
getRearLightState() |
none | byte | Returns rear light state (0,1,2) |
#include <Wire.h>
// Command codes
#define CMD_TRACKS_MOVE 0x20
#define CMD_TRACKS_TURN 0x21
#define CMD_FRONT_LIGHTS 0x40
#define CMD_EMERGENCY_STOP 0xFF
// I2C address of the tank controller
#define TANK_ADDRESS 0x08
void setup() {
Wire.begin(); // Initialize as master
Serial.begin(115200);
delay(1000); // Give slave time to initialize
// Turn on front lights
sendCommand(CMD_FRONT_LIGHTS, 1);
}
void loop() {
// Move forward at half speed
sendCommand(CMD_TRACKS_MOVE, 150); // 150 = +50 speed
delay(2000);
// Turn right at half rate
sendCommand(CMD_TRACKS_TURN, 150); // 150 = 50% right turn
delay(1000);
// Stop
sendCommand(CMD_TRACKS_MOVE, 100); // 100 = 0 speed
sendCommand(CMD_TRACKS_TURN, 100); // 100 = 0 turn
delay(2000);
}
void sendCommand(byte command, byte data) {
Wire.beginTransmission(TANK_ADDRESS);
Wire.write(command);
Wire.write(data);
byte error = Wire.endTransmission();
if (error) {
Serial.print("I2C error: ");
Serial.println(error);
}
}#include <Wire.h>
#define TANK_ADDRESS 0x08
#define CMD_GET_BATTERY 0xE0
void setup() {
Wire.begin();
Serial.begin(115200);
}
void loop() {
// Request battery data
Wire.beginTransmission(TANK_ADDRESS);
Wire.write(CMD_GET_BATTERY);
Wire.endTransmission();
// Read response (5 bytes)
Wire.requestFrom(TANK_ADDRESS, 5);
if (Wire.available() >= 5) {
// First two bytes are voltage (uint16, hundredths of volts)
uint16_t rawVoltage = Wire.read() | (Wire.read() << 8);
float voltage = rawVoltage / 100.0;
// Third byte is battery type
byte batteryType = Wire.read();
// Fourth byte is relay state
bool relayEnabled = Wire.read() > 0;
// Fifth byte is reserved/status
byte status = Wire.read();
// Print battery info
Serial.print("Battery: ");
Serial.print(voltage);
Serial.print("V, Type: ");
Serial.print(batteryType);
Serial.print(", Relay: ");
Serial.println(relayEnabled ? "ON" : "OFF");
}
delay(2000);
}To add a new command to the system, follow these steps:
- Define the command code in main.h:
#define CMD_CUSTOM_FEATURE 0x60- Add command handler in the processCommand function in main.cpp:
case CMD_CUSTOM_FEATURE:
handleCustomFeature(data);
break;- Implement the handler function:
void handleCustomFeature(byte data) {
// Implement your custom functionality
// For example, controlling a new accessory
digitalWrite(PIN_CUSTOM_FEATURE, data > 0 ? HIGH : LOW);
}This table provides a complete reference for connecting all components to the Arduino Nano.
| Arduino Pin | Connected To | Wire Color (Recommended) | Notes |
|---|---|---|---|
| Power Pins | |||
| VIN | 7-12V Power Supply (+) | Red | Main power input |
| GND | System Ground | Black | Connect to all GND connections |
| 5V | 5V Components | Red | For low-power sensors/devices |
| 3.3V | 3.3V Components | Orange | For 3.3V logic level components |
| I2C Pins | |||
| A4 (SDA) | Raspberry Pi SDA | Yellow | I2C Data line |
| A5 (SCL) | Raspberry Pi SCL | White | I2C Clock line |
| Analog Pins | |||
| A0 | Battery Voltage Divider | Purple | Voltage divider output |
| A1 | Battery Relay Control | Brown | Connect via transistor if needed |
| A2 | Front Lights | Blue | Via transistor or LED driver |
| A3 | Laser Module | Yellow | Via transistor |
| A7 | Rear Lights | Green | Via transistor or LED driver |
| Digital Pins | |||
| D2 | Cannon Fired Interrupt | White | Connect to firing detection |
| D3 | Left Track PWM1 | Gray | Cytron motor driver |
| D4 | Turret Elevation IN1 | Brown | L293D motor driver |
| D5 | Turret Rotation EN | Purple | L293D motor driver (PWM) |
| D6 | Turret Elevation EN | Blue | L293D motor driver (PWM) |
| D7 | Turret Rotation IN2 | Green | L293D motor driver |
| D8 | Turret Rotation IN1 | Yellow | L293D motor driver |
| D9 | Left Track PWM2 | White | Cytron motor driver |
| D10 | Right Track PWM1 | Gray | Cytron motor driver |
| D11 | Right Track PWM2 | White | Cytron motor driver |
| D12 | Turret Elevation IN2 | Orange | L293D motor driver |
| D13 | Cannon Fire Control | Red | Controls cannon firing mechanism |
| Component | Connection | Wire Color |
|---|---|---|
| Battery + | Voltage Divider R1 (100kΩ) | Red |
| R1 & R2 Junction | Arduino A0 | Purple |
| R2 (22kΩ) | GND | Black |
| Driver Pin | Connection | Wire Color | Notes |
|---|---|---|---|
| PWM1 | Arduino D3 (Left), D10 (Right) | Gray | Speed control |
| PWM2 | Arduino D9 (Left), D11 (Right) | White | Direction control |
| GND | System Ground | Black | |
| VIN | Motor Power Supply | Red | May need separate power supply |
| M+ | Motor Positive Terminal | Red | |
| M- | Motor Negative Terminal | Black |
| L293D Pin | Connection | Wire Color | Notes |
|---|---|---|---|
| 1 (Enable) | Arduino D5 (Rotation) | Purple | PWM speed control |
| 2 (Input) | Arduino D8 | Yellow | Direction control |
| 3 (Output) | Turret Rotation Motor + | Red | |
| 4, 5 (GND) | System Ground | Black | Heat sink ground |
| 6 (Output) | Turret Rotation Motor - | Black | |
| 7 (Input) | Arduino D7 | Green | Direction control |
| 8 (Vcc2) | Motor Power Supply | Red | Motor voltage |
| 9 (Enable) | Arduino D6 (Elevation) | Blue | PWM speed control |
| 10 (Input) | Arduino D4 | Brown | Direction control |
| 11 (Output) | Turret Elevation Motor + | Red | |
| 12, 13 (GND) | System Ground | Black | Heat sink ground |
| 14 (Output) | Turret Elevation Motor - | Black | |
| 15 (Input) | Arduino D12 | Orange | Direction control |
| 16 (Vcc1) | 5V Logic Supply | Red | Logic voltage |
| Component | Positive Connection | GND | Control Pin | Notes |
|---|---|---|---|---|
| Front Lights | 5V or Battery + | GND | Arduino A2 via transistor | Use NPN transistor or MOSFET |
| Rear Lights | 5V or Battery + | GND | Arduino A7 via transistor | Use NPN transistor or MOSFET |
| Laser Module | 5V or Battery + | GND | Arduino A3 via transistor | Use NPN transistor |
| IR Emitter | 5V via resistor | GND | Arduino D6 via transistor | Include current limiting resistor |
| Cannon Mechanism | 5V or Battery + | GND | Arduino D13 via driver | May need relay or driver circuit |
| Relay Pin | Connection | Wire Color | Notes |
|---|---|---|---|
| Control Pin | Arduino A1 via transistor | Brown | NPN transistor or MOSFET |
| NO (Normally Open) | Battery + Output | Red | |
| COM (Common) | Battery + Input | Red | |
| GND | System Ground | Black | |
| Vcc | 5V | Red | For relay coil |
- Logic Power: Arduino Nano requires 7-12V DC input via VIN or USB
- Motor Power: Motors may require separate power supply to prevent voltage drops affecting logic
- Power Isolation: Consider using optoisolators or separate regulators to isolate motor noise from logic circuits
-
Current Requirements:
- Arduino Nano: ~50mA
- Track Motors: ~1-3A per motor
- Turret Motors: ~500mA per motor
- Lights: ~100-200mA total
- Weapons: ~50-500mA depending on components
- Ground Connections: All grounds (Arduino, motors, sensors, power supplies) must be connected together
- Signal Isolation: Use transistors or MOSFETs between Arduino pins and higher power components
- PWM Pins: Use only designated PWM pins (3, 5, 6, 9, 10, 11) for motor speed control
- Noise Reduction: Add 100nF ceramic capacitors across motor terminals to reduce EMI
-
Wire Gauge: Use appropriate wire gauge for current requirements:
- 22-24 AWG for signals and low current
- 18-20 AWG for moderate current (lights, small motors)
- 16 AWG or larger for high current (main track motors)
This controller software was designed for Arduino Nano to control an RC tank.
Note: This documentation covers the functionality shown in the provided code files. Additional functionality may require updates to this documentation.