element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
EZ-EV Challenge
  • Challenges & Projects
  • Design Challenges
  • EZ-EV Challenge
  • More
  • Cancel
EZ-EV Challenge
Forum SmartAssist EV - Wireless Command and H-Bridge Direct Drive - Part 3
  • News
  • Projects
  • Forum
  • DC
  • Leaderboard
  • Files
  • Members
  • More
  • Cancel
  • New
Join EZ-EV Challenge to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 0 replies
  • Subscribers 59 subscribers
  • Views 63 views
  • Users 0 members are here
Related

SmartAssist EV - Wireless Command and H-Bridge Direct Drive - Part 3

jelektro
jelektro 21 days ago

Project Roadmap

Part 1 - Experimental Smart Assistive Platform for Elderly and Disabled People

Part 2 - Hardware Platform

Part 3 - Wireless Command and H-Bridge Direct Drive

Part 4 - Introducing Autonomous Line Following (TCRT5000)

Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)

Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers

Part 7 - Mobile Robot Control and Live Video Streaming


Every robotic vehicle requires a foundation for movement and communication. We begin with an Infrared (IR) receiver based on the standard IRremote library alongside a continuous PWM-controlled H-Bridge system.
By mapping explicit hex codes from a handheld remote, the car acts on standard movement directions. Because a single button press should not trigger indefinite movement, an optional timeout sequence safely cuts power to the motors when the transmitter goes quiet.
This sketch boots the vehicle into manual mode, giving you directional control via your handheld remote. When a key is released, a built-in safety timeout automatically halts the motors to prevent runaway scenarios.
On the Arduino UNO Q board, the analogWrite() function might not work correctly if you explicitly define the pin mode using pinMode(pin, OUTPUT).
Due to its architecture and a known issue in the underlying Zephyr core, removing the pinMode statement from your setup() function allows the PWM signal to generate properly.
How to use analogWrite on UNO Q?
  • Do not call pinMode(pin, OUTPUT) for your chosen PWM pin.
  • Call analogWrite(pin, value) directly in your code.
  • The value ranges from 0 (always off) to 255 (always on).
The STM32 microcontroller controls the motors using the L9110S chip located on a board attached to the motor. The power module board contains two L9110s chips, each controlling a single DC motor.
image
The vehicle can be controlled locally using a remote control
image
The assembled vehicle, ready for testing, is shown in the photo below. The vehicle includes an Arduino Uno Q, a 4x1.5V battery pack, and a breadboard containing an infrared receiver.
image

Here is the circuit connection and pin configuration guide for setting up your Arduino board with the L9110S motor driver, IR receiver, and power system.

Pin Mapping Table

Component Component Pin Arduino UNO Q Pin Notes / Description
L9110S Motor Driver A-1A / A-1B Pin 5 (PWM) Motor A Direction & Speed
A-1B / A-2A Pin 6 (PWM) Motor A Direction & Speed
B-1A / B-1B Pin 9 (PWM) Motor B Direction & Speed
B-1B / B-2A Pin 10 (PWM) Motor B Direction & Speed
VCC External Power (+) 6V - 12V (Battery pack)
GND Common Ground (-) Shared GND with Arduino
IR Receiver OUT / DATA Pin 2 Signal line (INPUT_PULLUP)
VCC 5V / 3.3V Logic power supply
GND Common Ground (-) Shared GND with Arduino

This Arduino sketch implements a complete control system for a two-wheeled mobile robot managed via an Infrared (IR) remote. It decodes custom IR timing signals, controls DC motors using an L9110S H-bridge driver, provides visual status updates via onboard RGB LEDs, and displays directional arrows on an 8x13 LED matrix.

Key Code Sections

1. Libraries and Hardware Definitions

Imports necessary libraries for the LED matrix and assigns GPIO pins for the L9110S motor driver, IR receiver, and motor speed parameters.

#include <Arduino.h>
#include <Arduino_LED_Matrix.h>   // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize the large LED matrix

// L9110S Motor Driver Pins on Arduino UNO Q
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin (Configured with PULLUP)
const int IR_RECEIVE_PIN = 2; 

// Motor speed (Scale 0-255 for Arduino analogWrite)
const int MOTOR_SPEED = 200; 

2. LED Matrix Graphics Arrays

Defines 104-element arrays (8x13 display) storing grayscale values from 0 (OFF) to 7 (max brightness). These pre-rendered graphics represent directional arrows (Up, Down, Left, Right) and an empty frame for stopping.


const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels for the LED matrix

// Example: Arrow Up Array
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 

3. Onboard RGB LED Helper Functions

Provides utility functions to control status indicator LEDs. set_led3_color uses analog PWM values for smooth color mixing, while set_led4_color handles active-LOW digital switching.

void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}

4. Low-Level IR Signal Decoder (readElecrowIR)

Implements custom pulse-width decoding for the Elecrow IR protocol without relying on standard IR libraries. It measures microsecond pulse durations, reconstructs 32 bits (4 bytes) of data, and validates signal integrity using a bitwise checksum (data + data == 0xFF).

long readElecrowIR() {
  int count = 0;
  
  // 1. Wait for leading LOW pulse
  while (digitalRead(IR_RECEIVE_PIN) == LOW && count < 200) {
    count++;
    delayMicroseconds(60);
  }
  if (count >= 200) return -1;

  // 2. Wait for leading HIGH space
  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH && count < 80) {
    count++;
    delayMicroseconds(60);
  }
  if (count >= 80) return -1;

  // 3. Read 32 bits of payload data
  int idx = 0, cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i < 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW && count < 15) {
      count++;
      delayMicroseconds(60);
    }
    
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH && count < 40) {
      count++;
      delayMicroseconds(60);
    }

    if (count > 8) {
      data[idx] |= (1 << cnt); // Store logical '1'
    }

    if (cnt == 7) { cnt = 0; idx++; } else { cnt++; }
  }

  // 4. Checksum verification
  if ((byte)(data[0] + data[1]) == 0xFF && (byte)(data[2] + data[3]) == 0xFF) {
    return data[2]; // Return valid command byte
  }

  return -1;
}

5. Motor Control and Command Execution

driveMotors sends PWM values directly to the driver pins. executeCommand maps hex command codes (e.g., 0x18 for forward) to corresponding motor movements, LED status colors, and matrix graphics.

void driveMotors(int a1, int a2, int b1, int b2) {
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

void executeCommand(byte command) {
  switch (command) {
    case 0x18: // Up Arrow - Forward
      driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
      set_led4_color(false, true, false);  
      set_led3_color(0, 200, 0);           
      matrix.draw(arrow_up);
      break;
    case 0x08: // Left Arrow - Turn Left
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_left);
      break;
    default: // Stop on unassigned keys
      driveMotors(0, 0, 0, 0); 
      set_led4_color(false, false, false); 
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
      break;
  }
}

6. System Setup and Main Loop (setup & loop)

Configures input pins with internal pull-ups, initializes the display in 3-bit grayscale mode, and continuously polls the IR pin. The loop includes a fail-safe that stops the motors if no active transmission is detected.

void setup() {
  Serial.begin(115200);
  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); // Enable internal pull-up

  pinMode(LED4_R, OUTPUT); 
  pinMode(LED4_G, OUTPUT); 
  pinMode(LED4_B, OUTPUT);

  matrix.begin();
  matrix.setGrayscaleBits(3); // Enable 8-level brightness
  matrix.clear();
}

void loop() {
  if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
    }
  }

  // Safety mechanism: Stop motors if no IR signal is being transmitted
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    driveMotors(0, 0, 0, 0); 
  } else {
    executeCommand(lastCommand);
  }
  
  delay(10); 
}
Full code
The full code for this stage is as follows:
#include <Arduino.h>

#include <Arduino_LED_Matrix.h>   // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize the large LED matrix

// L9110S Motor Driver Pins on Arduino UNO Q
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin (Configured with PULLUP just like the manufacturer's code)
const int IR_RECEIVE_PIN = 2; 

// Motor speed (Scale 0-255 for Arduino analogWrite)
const int MOTOR_SPEED = 200; 

// Variable to store the last successfully decoded command
byte lastCommand = 0;



const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels for the LED matrix

// --- LED MATRIX ARROW ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 


// --- ONBOARD RGB LED CONTROL FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}


// IR decoding function based exactly on Elecrow's logic and timing
long readElecrowIR() {
  int count = 0;
  
  // 1. Wait for the leading LOW pulse
  while (digitalRead(IR_RECEIVE_PIN) == LOW && count < 200) {
    count++;
    delayMicroseconds(60);
  }
  if (count >= 200) return -1;

  // 2. Wait for the leading HIGH space
  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH && count < 80) {
    count++;
    delayMicroseconds(60);
  }
  if (count >= 80) return -1;

  // 3. Read 32 bits of payload data
  int idx = 0;
  int cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i < 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW && count < 15) {
      count++;
      delayMicroseconds(60);
    }
    
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH && count < 40) {
      count++;
      delayMicroseconds(60);
    }

    // If the HIGH pulse duration was long (count > 8), register bit as 1
    if (count > 8) {
      data[idx] |= (1 << cnt);
    }

    if (cnt == 7) {
      cnt = 0;
      idx++;
    } else {
      cnt++;
    }
  }

  // 4. Check checksum integrity (matches Elecrow's condition: data+data==0xFF)
  if ((byte)(data[0] + data[1]) == 0xFF && (byte)(data[2] + data[3]) == 0xFF) {
    return data[2]; // Returns the valid command byte
  }

  return -1;
}

void driveMotors(int a1, int a2, int b1, int b2) {
  // Omit pinMode() in setup to keep PWM functional on the UNO Q Zephyr core
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

void executeCommand(byte command) {
  switch (command) {
    case 0x18: // Up Arrow - Forward
      driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
      set_led4_color(false, true, false);  
      set_led3_color(0, 200, 0);           
      matrix.draw(arrow_up);
      break;
    case 0x08: // Left Arrow - Turn Left
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_left);
      break;
    case 0x5A: // Right Arrow - Turn Right
      driveMotors(0, MOTOR_SPEED, MOTOR_SPEED, 0);
    
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_right);
      
      break;
    case 0x52: // Down Arrow - Backward
      driveMotors(MOTOR_SPEED, 0, MOTOR_SPEED, 0);
      set_led4_color(true, false, false);  
      set_led3_color(200, 0, 0);           
      matrix.draw(arrow_down);
      break;
    default:
      driveMotors(0, 0, 0, 0); // Stop for unassigned keys
      set_led4_color(false, false, false); 
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
      
      break;
  }
}

void setup() {
  Serial.begin(115200);
  
  // Enable internal pull-up resistor (Equivalent to Pin.PULL_UP in MicroPython)
  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
  
  Serial.println("Elecrow IR Decoder for Arduino UNO Q Ready.");

  pinMode(LED4_R, OUTPUT); pinMode(LED4_G, OUTPUT); pinMode(LED4_B, OUTPUT);
  set_led3_color(0, 0, 0);
  set_led4_color(false, false, false);

  matrix.begin();
  matrix.setGrayscaleBits(3);
  matrix.clear();
  
}

void loop() {
  // Main loop logic translated 1:1 from the manufacturer's MicroPython code
  if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
    long result = readElecrowIR();
    
    if (result != -1) {
      lastCommand = (byte)result;
      Serial.print("Retrieve key: 0x");
      Serial.println(lastCommand, HEX);
    }
  }

  // Safety and control: if the IR pin is HIGH (no transmission), stop the robot.
  // Otherwise, continue executing the last received command.
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    driveMotors(0, 0, 0, 0); // Stop
  } else {
    executeCommand(lastCommand);
  }
  
  delay(10); // Small delay for main loop stabilization
}
  • Sign in to reply
  • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube