Arduino [Modbus] RC Car with Web Camera and Remote Browser Control

Here is my project with remote control of different devices with Arduino Nano

Video tutorial on how to configure remote control of devices connected to an Arduino Nano using a gamepad and a real-time video stream.

A humorous video featuring an RC car that uses an Arduino Nano.

The Arduino code used in the first video.

#include <ModbusRTUSlave.h>
#include <Servo.h>

// ================== PIN MAPPING ==================
const uint8_t digitalPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; // Digital ping
const bool pwmPins[] = {false, true, false, true, true, false, false, true, true, true, false, false};  // PWM
const uint8_t digitalInPins[] = {A0, A1, A2, A3};              // digital
const uint8_t analogPins[] = {A0, A1, A2, A3, A4, A5, A6, A7};     // analog

Servo servos[11];
uint16_t servosDefault[] = {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500};

// ================== STATE ARRAYS ==================
#define NUM_COILS       sizeof(digitalPins)/sizeof(digitalPins[0])
#define NUM_HOLDINGS    sizeof(digitalPins)/sizeof(digitalPins[0])
#define NUM_DISCRETE    sizeof(digitalInPins)/sizeof(digitalInPins[0])
#define NUM_INPUTS      sizeof(analogPins)/sizeof(analogPins[0])

bool coils[NUM_COILS] = {0};
uint16_t holdingRegs[NUM_HOLDINGS] = {0};
bool discreteInputs[NUM_DISCRETE] = {0};
uint16_t inputRegs[NUM_INPUTS] = {0};

// ================== MODBUS ==================
ModbusRTUSlave modbus(Serial, -1, -1);

unsigned long lastCommandTime = 0;
const unsigned long COMMAND_TIMEOUT = 1000;

// ================== SETUP ==================
void setup() {
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  delay(500);
  digitalWrite(13, HIGH);

  Serial.begin(9600);

  for(uint8_t i=0; i<NUM_COILS; i++){
    if(pwmPins[i]) {
      servos[i].attach(digitalPins[i]);
      servos[i].writeMicroseconds(servosDefault[i]);
      holdingRegs[i] = servosDefault[i];
    } else {
      pinMode(digitalPins[i], OUTPUT);
      digitalWrite(digitalPins[i], LOW);
    }
  }

  for(uint8_t i=0;i<NUM_DISCRETE;i++){
    pinMode(digitalInPins[i], INPUT_PULLUP);
  }

  modbus.configureCoils(coils, NUM_COILS);
  modbus.configureHoldingRegisters(holdingRegs, NUM_HOLDINGS);
  modbus.configureDiscreteInputs(discreteInputs, NUM_DISCRETE);
  modbus.configureInputRegisters(inputRegs, NUM_INPUTS);

  // Modbus slave (ID=1, baud 9600, SERIAL_8N1)
  modbus.begin(1, 9600, SERIAL_8N1);
}

// ================== LOOP ==================
void loop() {
  if(true) {
    modbus.poll();

//========================================
// ===== DETECT COMMAND CHANGES =====
  if(false) {
    bool commandReceived = false;

    for (uint8_t i = 0; i < NUM_HOLDINGS; i++) {
      static uint16_t prev[NUM_HOLDINGS] = {0};
      if (holdingRegs[i] != prev[i]) {
        prev[i] = holdingRegs[i];
        commandReceived = true;
      }
    }

    for (uint8_t i = 0; i < NUM_COILS; i++) {
      static bool prevC[NUM_COILS] = {0};
      if (coils[i] != prevC[i]) {
        prevC[i] = coils[i];
        commandReceived = true;
      }
    }

    if (commandReceived) {
      lastCommandTime = millis();
    }

    // ===== TIMEOUT FAILSAFE =====
    if (millis() - lastCommandTime > COMMAND_TIMEOUT) {
      for (uint8_t i = 0; i < NUM_HOLDINGS; i++) {
        if (pwmPins[i]) {
          holdingRegs[i] = servosDefault[i];
        }
      }
    }
  }
//========================================
    for(uint8_t i=0;i<NUM_COILS;i++){
      if(pwmPins[i]){
        servos[i].writeMicroseconds(holdingRegs[i]);
      } else {
        digitalWrite(digitalPins[i], coils[i] ? HIGH : LOW);
      }
    }

    for(uint8_t i=0;i<NUM_DISCRETE;i++){
      discreteInputs[i] = digitalRead(digitalInPins[i]);
    }

    for(uint8_t i=0;i<NUM_INPUTS;i++){
      inputRegs[i] = analogRead(analogPins[i]);
    }
  }
}

The Arduino code used in the second video.

#include <ModbusRTUSlave.h>
#include <Servo.h>

// ================== PIN MAPPING ==================
const uint8_t digitalPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; // Digital ping
const bool pwmPins[] = {false, true, false, true, true, false, false, true, true, true, false, false};  // PWM
const float pwmSmooth[] = {1.0, 1.0,   1.0,  1.0,  1.0,   1.0,   1.0,  0.1,  1.0,  0.1,   1.0,   1.0};  // smoothing
const uint8_t digitalInPins[] = {A0, A1, A2, A3};              // digital
const uint8_t analogPins[] = {A0, A1, A2, A3, A4, A5, A6, A7};     // analog

Servo servos[11];
uint16_t servosDefault[] = {1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500};
uint16_t servosShift[] = {0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0};

// ================== STATE ARRAYS ==================
#define NUM_COILS       sizeof(digitalPins)/sizeof(digitalPins[0])
#define NUM_HOLDINGS    sizeof(digitalPins)/sizeof(digitalPins[0])
#define NUM_DISCRETE    sizeof(digitalInPins)/sizeof(digitalInPins[0])
#define NUM_INPUTS      sizeof(analogPins)/sizeof(analogPins[0])

bool coils[NUM_COILS] = {0};
uint16_t holdingRegs[NUM_HOLDINGS] = {0};
bool discreteInputs[NUM_DISCRETE] = {0};
uint16_t inputRegs[NUM_INPUTS] = {0};

uint16_t servoFiltered[NUM_HOLDINGS] = {0};

// ================== MODBUS ==================
ModbusRTUSlave modbus(Serial, -1, -1); // Serial, DE/RE pins (-1 if no RS-485)

unsigned long lastCommandTime = 0;
const unsigned long COMMAND_TIMEOUT = 1000;

// ================== SETUP ==================
void setup() {
    pinMode(13, OUTPUT);
    digitalWrite(13, LOW);
    delay(500);
    digitalWrite(13, HIGH);

    Serial.begin(9600);

    for(uint8_t i=0; i < NUM_COILS; i++){
        if(pwmPins[i]) {
            servos[i].attach(digitalPins[i]);
            servos[i].writeMicroseconds(servosDefault[i] + servosShift[i]);
            holdingRegs[i] = servosDefault[i];
        } else {
            pinMode(digitalPins[i], OUTPUT);
            digitalWrite(digitalPins[i], LOW);
        }
    }

    for(uint8_t i = 0; i < NUM_DISCRETE; i++){
        pinMode(digitalInPins[i], INPUT_PULLUP);
    }

    modbus.configureCoils(coils, NUM_COILS);
    modbus.configureHoldingRegisters(holdingRegs, NUM_HOLDINGS);
    modbus.configureDiscreteInputs(discreteInputs, NUM_DISCRETE);
    modbus.configureInputRegisters(inputRegs, NUM_INPUTS);

    // Modbus slave (ID=1, baud 9600, SERIAL_8N1)
    modbus.begin(1, 9600, SERIAL_8N1);
}

// ================== LOOP ==================
void loop() {
    modbus.poll();

    //========================================
    // ===== DETECT COMMAND CHANGES =====
    if(false) {
        bool commandReceived = false;

        for (uint8_t i = 0; i < NUM_HOLDINGS; i++) {
            static uint16_t prev[NUM_HOLDINGS] = {0};
            if (holdingRegs[i] != prev[i]) {
                prev[i] = holdingRegs[i];
                commandReceived = true;
            }
        }

        for (uint8_t i = 0; i < NUM_COILS; i++) {
            static bool prevC[NUM_COILS] = {0};
            if (coils[i] != prevC[i]) {
                prevC[i] = coils[i];
                commandReceived = true;
            }
        }

        if (commandReceived) {
            lastCommandTime = millis();
        }

        // ===== TIMEOUT FAILSAFE =====
        if (millis() - lastCommandTime > COMMAND_TIMEOUT) {
            for (uint8_t i = 0; i < NUM_HOLDINGS; i++) {
                if (pwmPins[i]) {
                    holdingRegs[i] = servosDefault[i];
                }
            }
        }
    }

    //========================================
    for(uint8_t i=0; i < NUM_COILS; i++) {
        if(pwmPins[i]) {
            if (servoFiltered[i] == 0) {
                servoFiltered[i] = holdingRegs[i];
            }
            servoFiltered[i] = servoFiltered[i] * (1.0 - pwmSmooth[i]) + holdingRegs[i] * pwmSmooth[i];
            servos[i].writeMicroseconds(servoFiltered[i] + servosShift[i]);
        } else {
            digitalWrite(digitalPins[i], coils[i] ? HIGH : LOW);
        }
    }

    for(uint8_t i = 0; i < NUM_DISCRETE; i++) {
        discreteInputs[i] = digitalRead(digitalInPins[i]);
    }

    for(uint8_t i=0;i<NUM_INPUTS;i++) {
        inputRegs[i] = analogRead(analogPins[i]);
    }
}
1 Like