#include <Arduino.h>
#define BAUD_RATE 115200
#define STEER_PWM_PIN 5 // Gray wire
#define STEER_RIGHT_PIN 6 // Orange wire
#define STEER_LEFT_PIN 7 // Yellow wire
#define MARCH_BACKWARDS_PIN 8 // Green wire
#define MARCH_FORWARD_PIN 9 // Blue wire
#define MARCH_PWM_PIN 10 // Purple wire
#define CLACSON_PIN 2
bool clacson_toggled = false;
#define NEON_PIN_1 12 // Blue wires
#define NEON_PIN_2 13
bool neon_toggled = false;
#define CAR_BATTERY_PIN A0 // Orange wire
#define ELECTRONICS_BATTERY_PIN A1 // Yellow wire
#define VOLTAGE_UPDATE_INTERVAL_MS 1000
#define ADC_RESOLUTION 10 // bits
const float ADC_VREF = 5.0; // Volt
const float ADC_STEPS = (1 << ADC_RESOLUTION) - 1;
const float R1_CAR = 5079 + 1990; // Ohm | From battery + to ADC pin
const float R2_CAR = 9945; // Ohm | From ADC pin to GND
const float RESISTOR_RATIO_CAR = (R1_CAR + R2_CAR) / R2_CAR;
const float R1_ELECTRONICS = 5088 + 1994; // Ohm | From battery + to ADC pin
const float R2_ELECTRONICS = 9935; // Ohm | From ADC pin to GND
const float RESISTOR_RATIO_ELECTRONICS = (R1_ELECTRONICS + R2_ELECTRONICS) / R2_ELECTRONICS;
// Given the current battery configuration, the smallest possible
// PWM signal duty cycle [0-255] that makes the motors move
#define PWM_MIN 55
#define PWM_MAX 255
/**
* Commands are encoded in a single byte:
* TOGGLE_NEON [202]
* TOGGLE_CLACSON [201]
* SWITCH_CAMERA [200] (android-only)
* STEER LEFT [150-199]
* STEER RIGHT [100-149]
* MARCH BACKWARD [050-099]
* MARCH FORWARD [000-049]
*/
#define CMD_TOGGLE_CLACSON 201
#define CMD_TOGGLE_NEON 202
#define FORWARD_BACKWARD_SPLIT_POINT 50
#define RIGHT_LEFT_SPLIT_POINT 150
#define INTENSITY_MAX 50 // Maximum intensity of each movement
unsigned long time_since_voltage_reading;
int last_steer_pin;
float read_car_battery_voltage()
{
float adc_reading = analogRead(CAR_BATTERY_PIN);
float adc_reading_volt = adc_reading / ADC_STEPS * ADC_VREF;
return adc_reading_volt * RESISTOR_RATIO_CAR;
}
float read_electronics_battery_voltage()
{
float adc_reading = analogRead(ELECTRONICS_BATTERY_PIN);
float adc_reading_volt = adc_reading / ADC_STEPS * ADC_VREF;
return adc_reading_volt * RESISTOR_RATIO_ELECTRONICS;
}
void setup()
{
Serial.begin(BAUD_RATE);
pinMode(STEER_PWM_PIN, OUTPUT);
pinMode(STEER_RIGHT_PIN, OUTPUT);
pinMode(STEER_LEFT_PIN, OUTPUT);
pinMode(MARCH_BACKWARDS_PIN, OUTPUT);
pinMode(MARCH_FORWARD_PIN, OUTPUT);
pinMode(MARCH_PWM_PIN, OUTPUT);
pinMode(CLACSON_PIN, OUTPUT);
pinMode(NEON_PIN_1, OUTPUT);
pinMode(NEON_PIN_2, OUTPUT);
analogWrite(STEER_PWM_PIN, 0);
digitalWrite(STEER_RIGHT_PIN, LOW);
digitalWrite(STEER_LEFT_PIN, LOW);
analogWrite(MARCH_PWM_PIN, 0);
digitalWrite(MARCH_BACKWARDS_PIN, LOW);
digitalWrite(MARCH_FORWARD_PIN, LOW);
digitalWrite(CLACSON_PIN, LOW);
digitalWrite(NEON_PIN_1, LOW);
digitalWrite(NEON_PIN_2, LOW);
}
void loop()
{
unsigned long current_time = millis();
if (current_time - time_since_voltage_reading > VOLTAGE_UPDATE_INTERVAL_MS)
{
unsigned int available_bytes = Serial.availableForWrite();
if (available_bytes >= 2)
{
float car_battery_volt = read_car_battery_voltage();
float electronics_battery_volt = read_electronics_battery_voltage();
time_since_voltage_reading = millis();
/**
* The theoretical maximum voltage for a 2S lithium ion battery is 8.4 volts
* This means that we can send the measured voltage in centivolts (1 decimal
* place of precision) using only one byte and still have the most significant
* bit free (0-84 is well within the range of 7 bits).
* The MSB is therefore used to encode the measured battery (0 = car, 1 = electronics)
*/
uint8_t car_battery_centivolt = int(car_battery_volt * 10);
uint8_t electronics_battery_centivolt = 0x80 | int(electronics_battery_volt * 10);
Serial.write(&car_battery_centivolt, 1);
Serial.write(&electronics_battery_centivolt, 1);
}
}
int available_bytes = Serial.available();
if (available_bytes < 1) { return; }
uint8_t cmd_byte = Serial.read();
if (cmd_byte == CMD_TOGGLE_CLACSON)
{
clacson_toggled = !clacson_toggled;
digitalWrite(CLACSON_PIN, clacson_toggled ? HIGH : LOW);
return;
}
if (cmd_byte == CMD_TOGGLE_NEON) {
neon_toggled = !neon_toggled;
digitalWrite(NEON_PIN_1, neon_toggled ? HIGH : LOW);
digitalWrite(NEON_PIN_2, neon_toggled ? HIGH : LOW);
return;
}
// 0 <-> (INTENSITY_MAX - 1) remapped to PWM_MIN <-> PWM_MAX
uint8_t intensity = (cmd_byte % INTENSITY_MAX);
float intensity_percent = (float)intensity / (INTENSITY_MAX - 1);
uint8_t pwm_speed = (intensity == 0) ? 0 : round(intensity_percent * (PWM_MAX - PWM_MIN)) + PWM_MIN;
if (cmd_byte < 100)
{
// 0 <= cmd_byte < 100 : MARCH
analogWrite(MARCH_PWM_PIN, pwm_speed);
digitalWrite(MARCH_FORWARD_PIN, cmd_byte < FORWARD_BACKWARD_SPLIT_POINT ? HIGH : LOW);
digitalWrite(MARCH_BACKWARDS_PIN, cmd_byte >= FORWARD_BACKWARD_SPLIT_POINT ? HIGH : LOW);
}
else if (cmd_byte < 200)
{
// 100 <= cmd_byte < 200 : STEER
// For physical constraints of the steering mechanism, we currently always steer all the way
if (pwm_speed > 0)
{
analogWrite(STEER_PWM_PIN, 255);
last_steer_pin = cmd_byte < RIGHT_LEFT_SPLIT_POINT ? STEER_RIGHT_PIN : STEER_LEFT_PIN;
digitalWrite(last_steer_pin, HIGH);
}
else
{
// If the desired speed is 0 (drive straight) briefly counter steer
// to mitigate the imperfections of the steering mechanism
digitalWrite(last_steer_pin, LOW);
int counter_steer_pin = last_steer_pin == STEER_RIGHT_PIN ? STEER_LEFT_PIN : STEER_RIGHT_PIN;
digitalWrite(counter_steer_pin, HIGH);
delay(10);
digitalWrite(counter_steer_pin, LOW);
analogWrite(STEER_PWM_PIN, 0);
}
}
}