Robot Car – Jetson Nano and Arduino

Preamble

Notes from INTRODUCTION TO SELF DRIVING CAR USING JETSON NANO (2020) and the rest of the series

Related videos

Related Course Video

Someone else!

Other video

RPi4 car

Kit

Kit: Mecanum Wheel Arduino Robot Kit – 10021, $329

Nexus Automation store: 4WD 60mm Mecanum wheel arduino robot kit 10021, $361.90

Nexus robot

 

Dividing the logic

Arduino and Jetson. Uno with sensor shield

Motors

Hi-quality precision Motors with encoders (16009 Nexus according to this comment).

These motots seem to be actually (from Nexus Robot DC Gear Motor 12V, 120 rpm with encoder), $14.20, image  Namiki 8231344 (this could be a serial number, not the model, as it changes for every motor) – although the mounts and the encoder appear to be different.

Nexus Coreless DC Motor with encoder for Omnidirectional movement

  • Type: 22CL-3501PG
  • Stall Torque: 1.6Nm (16Kg · cm)
  • Continuous torque: 0.5Nm (5Kg · cm)
  • Voltage: 12VDC
  • Diameter: 22mm
  • Shaft length: 19mm (with a 90-degree double incision)
  • Shaft diameter: 4mm
  • Length: 67mm (not including encoders, gearbox output shaft)
  • Stall Current: 1.8A
  • Reduction ratio: 80: 1 (metal planetary reducer)
  • Output speed: 120 r / min (input voltage DC 12V)
  • Encoder: 2 pulses per revolution

From 4WD 60mm Mecanum wheel arduino robot kit 10021, $362, (image, image) a different motor/encoder is shown, Namiki 061010???, and ????112311, or 02800??? and ???2?64 – again these are the serial numbers I think, not the model number.

From dfrobot, the same motor, with a different encoder (DFRobot brand) is shown (original image)

16002

This actually looks more like the actual motors/encoder combination, or a closer match, Faulhaber 12V DC Coreless Motor with encoder( Aluminum alloy Shell)16002, $96 (for one!), image, image. However, this is listed as 16002 and not 16009, and doesn’t match exactly the motor in the image of the kit, although the encoder is the same. Faulhaber site – dc motors.

The Faulhaber motor in the above image, would seem to be a CR model. A 2342 CR to be precise, DC-Micromotors Series 2342 … CR.:

Graphite Commutation

Nominal voltage: 6 … 48 V
Torque up to (max.): 19 mNm
No-load speed: 9.000 min⁻¹
Stall torque: 91,4 mNm
Diameter: 23 mm
Length: 42 mm

Code 2342 ... 312CR and M134-Y2001 317.

dc-motor-m002.html and nexusrobot.com – 16002.pdf

 

From https://ozrobotics.com/shop/dc-drive-motor-for-robot-kits-boats-drones-and-electric-vehicles/, a motor with the correct encoders.  However, the three screw holes do not match the mounts on the car chassis

The images of the chassis, from 4WD 60mm Mecanum wheel arduino robot kit 10021, clearly shows Namiki branded motors, with 4 mount screws.

Encoder

The encoder on all three motors, the Namiki on the chassis (not separately), the Faulhaber and the ozrobotics motor is a Nexus v1.2

The DFRobot encoder

A seedstudio encoder (where? I saw one, but now can’t find it).

See https://components101.com/articles/different-types-of-encoder-motors-and-their-working

Encoders code

 

Wheels

Meccanum wheels, $18.70, 60mm Aluminum robot kits Compatible Mecanum Wheel basic Right 14158

The angle of the rollers must all go towards the center of the body.

Motor drivers

  • L293D – Dual max 600 mA – 4 motors
  • L293N – max 3A – 2 motors

Other parts

Battery 12V 1.2A

Switches (bulky)

Volt and current meter – to determine the current draw of the motors

 

Use the PS4 controller and the dongle (BT can be used but the dongle is better)

Lidar

RPi camera

ROS, SLAM, Navigation

Video – Assembly

Avoid R0 and R1, which are the serial pins. Avoid A4 and A5 which are used for I2C connection.

Each motor takes three pins – one for speed and two for direction. Also the encode takes two pins. So totla pins per motors is 5, so 20 in all. This fills the Uno sensor board, which has 12 digital and 4 analog pins (when the excluded pins are taken into account). See https://cotswoldarduino.files.wordpress.com/2015/07/arduino-sensor-shield.pdf

So use a Mega instead, and forego the shield.

  • Speed left pins 13 and 8 – front and rear
  • Speed right 7 and 2 – front and rear
  • Direction left 12/11 and 10/9 – front and rear
  • Direction right 6/5 ad 4/3 – front and rear

Video – Test

Code

// int pinEnable = 13;
int pinsMotorLeft[6] = {13, 12, 11, 10, 9, 8};
int pinsMotorRight[6] = {7, 6, 5, 4, 3, 2};
int pinsMotor[2][6] = {{13, 12, 11, 10, 9, 8}, {7, 6, 5, 4, 3, 2}};

void setup() {
  Serial.begin(9600);
  // pinMode(pinEnable, OUTPUT);
  for (int i = 0; i < 0; i++)
  {
    pinMode(pinsMotorLeft[i], OUTPUT);
    pinMode(pinsMotorRight[i], OUTPUT);
  }  
}

void loop() {
  moveForward();  
}

void moveForward() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 1);   // Direction
  digitalWrite(pinsMotorLeft[2], 0);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[1], 0);   // Direction
  digitalWrite(pinsMotorLeft[2], 1);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 1);
  digitalWrite(pinsMotorRight[2], 0);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[1], 0);
  digitalWrite(pinsMotorRight[2], 1);
  digitalWrite(pinsMotorRight[0], 25);
}
void moveBack() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 0);   // Direction
  digitalWrite(pinsMotorLeft[2], 1);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[1], 1);   // Direction
  digitalWrite(pinsMotorLeft[2], 0);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 0);
  digitalWrite(pinsMotorRight[2], 1);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[1], 1);
  digitalWrite(pinsMotorRight[2], 0);
  digitalWrite(pinsMotorRight[0], 25);
}

Video – The right way

Speed between -1 and 1

Turn between -1 and 1

int type = forward back, left, right, diagonal

 

analogWrite doesn’t understand negative values, so use abs()

The sign of the speed is used for the direction

Code

// int pinEnable = 13;
int pinsMotorLeft[6] = {13, 12, 11, 10, 9, 8};
int pinsMotorRight[6] = {7, 6, 5, 4, 3, 2};
int pinsMotor[2][6] = {{13, 12, 11, 10, 9, 8}, {7, 6, 5, 4, 3, 2}};

int maxFactor = 255;

void moveRobot(float rspeed, float rturn, int type, int t);


void setup() {
  Serial.begin(9600);
  // pinMode(pinEnable, OUTPUT);
  for (int i = 0; i < 0; i++) { 
    pinMode(pinsMotorLeft[i], OUTPUT); 
    pinMode(pinsMotorRight[i], OUTPUT); 
  } 
} 

void loop() { 
  moveForward(); 
  moveBack(); 
  moveRobot(0.2, 0, 0, 2000); 
  moveRobot(-0.2, 0, 0, 2000); 
  moveRobot(0, 0.5, 0, 2000); 
  moveRobot(0, -0.5, 0, 2000); 
} 

void moveRobot(float rspeed, float rturn, int type = 0, int t = 0) { 
  int theSpeed; 
  int theTurn; 
  int leftSpeed; 
  int rightSpeed; 

  theSpeed = rspeed *= maxFactor; // convert from -1 to 1 to 255 
  theTurn = rturn *= maxFactor; // convert from -1 to 1 to 255 
  // Individual speeds 
  leftSpeed = int(theSpeed - theTurn); 
  rightSpeed = int(theSpeed + theTurn); 
  // Limit 
  leftSpeed = constrain(leftSpeed, -maxFactor, maxFactor); 
  rightSpeed = constrain(rightSpeed, -maxFactor, maxFactor); 
  // Send speed 
  analogWrite(pinsMotorLeft[0], abs(leftSpeed)); 
  analogWrite(pinsMotorLeft[5], abs(leftSpeed)); 
  analogWrite(pinsMotorLeft[0], abs(rightSpeed)); 
  analogWrite(pinsMotorLeft[5], abs(rightSpeed)); 
  // Direction 
  if (leftSpeed > 0) {
    digitalWrite(pinsMotorLeft[1], 1);   // Direction
    digitalWrite(pinsMotorLeft[2], 0);   // Direction
    digitalWrite(pinsMotorLeft[3], 0);   // Direction
    digitalWrite(pinsMotorLeft[4], 1);   // Direction
  } else {
    digitalWrite(pinsMotorLeft[1], 0);   // Direction
    digitalWrite(pinsMotorLeft[2], 1);   // Direction
    digitalWrite(pinsMotorLeft[3], 1);   // Direction
    digitalWrite(pinsMotorLeft[4], 0);   // Direction
  }
  if (rightSpeed > 0) {
    digitalWrite(pinsMotorRight[1], 1);   // Direction
    digitalWrite(pinsMotorRight[2], 0);   // Direction
    digitalWrite(pinsMotorRight[3], 0);   // Direction
    digitalWrite(pinsMotorRight[4], 1);   // Direction
  } else {
    digitalWrite(pinsMotorRight[1], 0);   // Direction
    digitalWrite(pinsMotorRight[2], 1);   // Direction
    digitalWrite(pinsMotorRight[3], 1);   // Direction
    digitalWrite(pinsMotorRight[4], 0);   // Direction
  }
  // Time
  delay(t);
}

void moveForward() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 1);   // Direction
  digitalWrite(pinsMotorLeft[2], 0);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[3], 0);   // Direction
  digitalWrite(pinsMotorLeft[4], 1);   // Direction
  digitalWrite(pinsMotorLeft[5], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 1);
  digitalWrite(pinsMotorRight[2], 0);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[3], 0);
  digitalWrite(pinsMotorRight[4], 1);
  digitalWrite(pinsMotorRight[5], 25);
}

void moveBack() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 0);   // Direction
  digitalWrite(pinsMotorLeft[2], 1);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[3], 1);   // Direction
  digitalWrite(pinsMotorLeft[4], 0);   // Direction
  digitalWrite(pinsMotorLeft[5], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 0);
  digitalWrite(pinsMotorRight[2], 1);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[3], 1);
  digitalWrite(pinsMotorRight[4], 0);
  digitalWrite(pinsMotorRight[5], 25);
}

Video – RC

Using two channels out of the possible four. So there are four leads to connect:

  • positive
  • negative
  • speed (14), and
  • direction (15)

Receives high signals. difference in the terms of time (microseconds). Need to determine the time between pulses.

The values return vary from around ~1000 – ~2000. Need to map to -1 to +1

 remoteSpeed = map(durationSpeed, 1000, 2000, -1, 1);

Make user define-able:

const int rfLimits[4] = {1000, 2000, 1000, 2000}

Note map() can not map to float decimals, so change to -100 and 100

 remoteSpeed = (map(durationSpeed, rfLimits[0], rfLimits[1], -100, 100)) / 100.0;
remoteDirection = (map(durationDirection, rfLimits[2], rfLimits[3], -100, 100)) / 100.0;

Constraint

remoteSpeed = constrain(remoteSpeed, -1.0, 1.0);
remoteDirection = constrain(remoteDirection, -1.0, 1.0);

 

Issue, the motors will always seem to turn a little bit, no matter how much we zero. So add a “dead zone”

 

if (remoteSpeed < 0.1 || remoteSpeed > 0.1) {
  remoteSpeed = 0;
}

Full code

// int pinEnable = 13;
const int pinsMotorLeft[6] = {13, 12, 11, 10, 9, 8};
const int pinsMotorRight[6] = {7, 6, 5, 4, 3, 2};
const int pinsMotor[2][6] = {{13, 12, 11, 10, 9, 8}, {7, 6, 5, 4, 3, 2}};

const int maxFactor = 255;
const int pinSpeed = 14;
const int pinDirection = 15;

const int rfLimits[4] = {1000, 2000, 1000, 2000};

float remoteSpeed;
float remoteDirection;
void moveRobot(float rspeed, float rturn, int type, int t);


void setup() {
  Serial.begin(9600);
  // pinMode(pinEnable, OUTPUT);
  for (int i = 0; i < 0; i++)
  {
    pinMode(pinsMotorLeft[i], OUTPUT);
    pinMode(pinsMotorRight[i], OUTPUT);
  }
}

void loop() {
  //moveForward();
  //moveBack();

  //moveRobot(0.2, 0, 0, 2000);
  //moveRobot(-0.2, 0, 0, 2000);
  //moveRobot(0, 0.5, 0, 2000);
  //moveRobot(0, -0.5, 0, 2000);

  getRFCommands();
  if (remoteSpeed < 0.1 || remoteSpeed > 0.1) {
    remoteSpeed = 0;
  }
  if (remoteDirection < 0.1 || remoteDirection > 0.1) {
    remoteDirection = 0;
  }
  moveRobot(remoteSpeed, remoteDirection, 0, 50);

}

void moveRobot(float rspeed, float rturn, int type = 0, int t = 0) {
  int theSpeed;
  int theTurn;
  int leftSpeed;
  int rightSpeed;

  theSpeed = rspeed *= maxFactor;  // convert from -1 to 1 to 255
  theTurn = rturn *= maxFactor;  // convert from -1 to 1 to 255
  // Individual speeds
  leftSpeed = int(theSpeed - theTurn);
  rightSpeed = int(theSpeed + theTurn);
  // Limit
  leftSpeed = constrain(leftSpeed, -maxFactor, maxFactor);
  rightSpeed = constrain(rightSpeed, -maxFactor, maxFactor);
  // Send speed
  analogWrite(pinsMotorLeft[0], abs(leftSpeed));
  analogWrite(pinsMotorLeft[5], abs(leftSpeed));
  analogWrite(pinsMotorLeft[0], abs(rightSpeed));
  analogWrite(pinsMotorLeft[5], abs(rightSpeed));
  // Direction
  if (leftSpeed > 0) {
    digitalWrite(pinsMotorLeft[1], 1);   // Direction
    digitalWrite(pinsMotorLeft[2], 0);   // Direction
    digitalWrite(pinsMotorLeft[3], 0);   // Direction
    digitalWrite(pinsMotorLeft[4], 1);   // Direction
  } else {
    digitalWrite(pinsMotorLeft[1], 0);   // Direction
    digitalWrite(pinsMotorLeft[2], 1);   // Direction
    digitalWrite(pinsMotorLeft[3], 1);   // Direction
    digitalWrite(pinsMotorLeft[4], 0);   // Direction
  }
  if (rightSpeed > 0) {
    digitalWrite(pinsMotorRight[1], 1);   // Direction
    digitalWrite(pinsMotorRight[2], 0);   // Direction
    digitalWrite(pinsMotorRight[3], 0);   // Direction
    digitalWrite(pinsMotorRight[4], 1);   // Direction
  } else {
    digitalWrite(pinsMotorRight[1], 0);   // Direction
    digitalWrite(pinsMotorRight[2], 1);   // Direction
    digitalWrite(pinsMotorRight[3], 1);   // Direction
    digitalWrite(pinsMotorRight[4], 0);   // Direction
  }
  // Time
  delay(t);
}

void moveForward() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 1);   // Direction
  digitalWrite(pinsMotorLeft[2], 0);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[3], 0);   // Direction
  digitalWrite(pinsMotorLeft[4], 1);   // Direction
  digitalWrite(pinsMotorLeft[5], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 1);
  digitalWrite(pinsMotorRight[2], 0);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[3], 0);
  digitalWrite(pinsMotorRight[4], 1);
  digitalWrite(pinsMotorRight[5], 25);
}

void moveBack() {
  // Left Front motor
  digitalWrite(pinsMotorLeft[1], 0);   // Direction
  digitalWrite(pinsMotorLeft[2], 1);   // Direction
  digitalWrite(pinsMotorLeft[0], 25);  // Speed
  // Left Back  motor
  digitalWrite(pinsMotorLeft[3], 1);   // Direction
  digitalWrite(pinsMotorLeft[4], 0);   // Direction
  digitalWrite(pinsMotorLeft[5], 25);  // Speed
  // Right Front motor
  digitalWrite(pinsMotorRight[1], 0);
  digitalWrite(pinsMotorRight[2], 1);
  digitalWrite(pinsMotorRight[0], 25);
  // Right Back Front motor
  digitalWrite(pinsMotorRight[3], 1);
  digitalWrite(pinsMotorRight[4], 0);
  digitalWrite(pinsMotorRight[5], 25);
}

void getRFCommands() {
  unsigned long durationSpeed;
  unsigned long durationDirection;


  durationSpeed = pulseIn(pinSpeed, HIGH);
  durationDirection = pulseIn(pinDirection, HIGH);

  //remoteSpeed = map(durationSpeed, 1000, 2000, -1, 1);
  remoteSpeed = (map(durationSpeed, rfLimits[0], rfLimits[1], -100, 100)) / 100.0;
  remoteDirection = (map(durationDirection, rfLimits[2], rfLimits[3], -100, 100)) / 100.0;

  remoteSpeed = constrain(remoteSpeed, -1.0, 1.0);
  remoteDirection = constrain(remoteDirection, -1.0, 1.0);

  Serial.println(durationSpeed);
  Serial.println(durationDirection);
  Serial.println(remoteSpeed);
  Serial.println(remoteDirection);
}

Video – 3D

Fillet the angles for strength

LCD screen – printed case, see Raspberry Pi 4 Display with 3D printed casing | Perfect for Programming

Anker power bank for Jetson

Fusion360

Dead End

From 4 months ago:

Reason why project is a dead end:

1) arduino mega does not have sufficient interrupts to handle 4x mecanum wheels with encoders independently…. Solution daisy chain L/R to make it emulate 2wd robot.

2) Jetson nano, Ubuntu, and ROS is simplest way to get autonomous motion using SLAM. However with the slanting race-car plate, placement of Lidar needs to be horizontal and this design is sloping… That’s a no-no.

3) Using wheel encoders on its own to obtain POSE of the robot is a recipe for failure… Wheels do slip and encoder readings will drift confusing the robot about its actual position and orientation relative to its map. Solution: add an IMU and fuse its data with wheel encoder data using EKF algorithm to improve POSE of the robot.

4) alternatively one can scrap encoders and imu and use intel realsense T265 camera to get 3d POSE of the robot to feed the SLAM of the ROS navigation stack running on the Jetson nano.

5) if you need to drive all 4 mecanum wheels independently with encoders, consider using Teensy microcontroller instead of Arduino Mega as it has enough interrupt pins to read the encoders 4×2 +/- counters.

 

 

 

 

This is the end, my friend

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s