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).
#include <Arduino.h>
#include <IRremote.hpp> // Requires installing the IRremote library in Arduino IDE
// L9110S Motor Driver Pins (All support hardware PWM)
const int motorPinA_1A = 5;
const int motorPinA_1B = 6;
const int motorPinB_1A = 9;
const int motorPinB_1B = 10;
// Infrared Receiver Pin (Requires interrupt-capable pin)
const int IR_RECEIVE_PIN = 2;
// Base motor speed (Scale 0-255)
const int motorSpeed = 200;
// Safety timeout tracking to stop the robot when button is released
unsigned long lastCommandTime = 0;
const unsigned long commandTimeout = 250; // milliseconds
void motor(int A1, int A2, int B1, int B2) {
analogWrite(motorPinA_1A, A1);
analogWrite(motorPinA_1B, A2);
analogWrite(motorPinB_1A, B1);
analogWrite(motorPinB_1B, B2);
}
void exec_cmd(byte key_val) {
switch (key_val) {
case 0x18: // Up Arrow - Drive Forward
motor(0, motorSpeed, 0, motorSpeed);
break;
case 0x08: // Left Arrow - Spin Left
motor(motorSpeed, 0, 0, motorSpeed);
break;
case 0x5A: // Right Arrow - Spin Right
motor(0, motorSpeed, motorSpeed, 0);
break;
case 0x52: // Down Arrow - Reverse Backward
motor(motorSpeed, 0, motorSpeed, 0);
break;
default:
motor(0, 0, 0, 0); // Any unassigned key dead-stops the robot
break;
}
}
void setup() {
Serial.begin(9600);
// pinMode(motorPinA_1A, OUTPUT);
// pinMode(motorPinA_1B, OUTPUT);
// pinMode(motorPinB_1A, OUTPUT);
// pinMode(motorPinB_1B, OUTPUT);
IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
Serial.println("Code 1: IR Remote Control Ready.");
}
void loop() {
if (IrReceiver.decode()) {
byte command = IrReceiver.decodedIRData.command;
Serial.print("Received IR Hex: 0x");
Serial.println(command, HEX);
exec_cmd(command);
lastCommandTime = millis(); // Refresh command timestamp
IrReceiver.resume();
}
// If the timeout window expires without a repeat command, cut power to motors
if (millis() - lastCommandTime > commandTimeout) {
motor(0, 0, 0, 0);
}
}