Ein robustes Robot Car mit Fernsteuerung

In the introduction to our blog series on Robot Cars we have already mentioned that in terms of power supply and beginner convenience our micro controller board with ATmega328P, ATmega16U2, compatible with Arduino UNO R3, is the first choice. Today we want to build our first robot car. With the help of a second MC board, we then want to develop a simple remote control with a 433MHz transmitter/receiver. Here we go.

Hardware needed in the first part

Number Component
1 Microcontroller board with ATmega328P, ATmega16U2, compatible with Arduino UNO R3
1 4-Channel L293D Shield Driver
1 chassis kit for 2- or 4-wheel robot car
Batteries/batteries, e.g. 2 x 18650 LiPo battery


Hardware needed in the second part

Number Component
1 Microcontroller board with ATmega328P, ATmega16U2, compatible with Arduino UNO R3
1 LCD keypad shield for Uno R3 and others.
2 433 MHz Sender/Receiver Module HC-12 (unfortunately no longer in the range)
Batteries/batteries, e.g. 2 x 18650 LiPo battery or 9V block


In addition to the construction instructions attached to the kits, a few tips for the installation of the micro controller and the battery compartment:

The motor driver shield is supplied with a small foam plate to protect the many contacts. I glued them with two-sided (carpet) tape under the micro controller and then onto the chassis. This saves me drilling new holes and screwing with spacers.

For the attachment of the battery holder I have taken self-adhesive adhesive tape to attach this.  If necessary, it can be removed quickly.

Application library for engines:

As is so often the case, Lady Ada (Limor Fried) has developed a very good library with the Adafruit team, which we can easily install in the integrated development environment (IDE) with the library manager. The IC 74HC595 (slider register) makes programming of the motor driver shield relatively complex, but this work has taken Adafruit away from us. Using the methods (functions) of the AFMotor.h library makes it easy for us to send driving orders to the Robot Car. Please note: Adafruit has the Shield V2 engine in its range today. We use the "old" library, so to speak, not V2. See next image "Library Manager", top entry.


The code fragment for the instantiation of the four engines is:


#include <AFMotor.h>
AF_DCMotor motor1(4);
AF_DCMotor motor2(3);
AF_DCMotor motor3(1);
AF_DCMotor motor4(2);

You're wondering about the discrepancies in the numbers? I did not want to change the connections for the engines in my configuration in the program library, but I also wanted to keep to my system for numbering the engines (odd left, even right, front to back). For two engines you can comment out or delete the unnecessary lines.

In the further program code, the four objects motor1 to motor4 can then be changed with the methods run() and setSpeed(), e.g.


motor1.run(FORWARD);
motor1.setSpeed(200);

The method run() knows the parameters FORWARD (forward), BACKWARD (back) and RELEASE (standstill).

The setSpeed() method accepts an integer number between 0 and 255, also as a variable that can have these values. At this point, however, I would like to issue a warning, the effects of which we will discuss later: the value 255 means full voltage supplied by the external voltage source, i.e. with my two LiPo batteries around 8V. However, the engines are designed for 5V. So my highest value can only be at approx. 150!


As a first step I recommend a functional test with the example sketch MotorTest installed with the program library. Try the numbers 1 to 4 in the line


AF/DCMotor motor(4);


to determine the connection mounting and think of the maximum value for setSpeed() in the for loops if you use a higher voltage than 5V.


You will agree with me that it is boring to use a pre-programmed driving profile. Somehow you want to steer the vehicle. With my Raspberry Pi I was able to connect a USB dongle for a cordless keyboard or use a WLAN connection, as with the ESP-Micro controllers. I'm afraid this can not be done with our simple Microcontroller board with ATmega328P, ATmega16U2, compatible with Arduino UNO R3. But a few months ago I had tested the 433MHz transmitter/receiver module HC-12 and written a three-part blog post (link to Part 1, Part 2, Part 3(b) about it. Perhaps I can use that knowledge again.

Preliminary:

  1. If I want to send a code for remote control, it needs to be clearly soluble, but not too long. In previous experiments with the HC-12 I had successfully used a four-digit code to turn on and off a LED.
  2. If I connect an analog joystick to the Micro Controller, a 10-bit analog input gives me values between 0 and 1023 with values for the x- and y-axis of about 511 in the middle position.

    For the y-direction (later forward and backward) I divide this value by 100 and thus get 11 driving stages from 0 to 10 with stop at 5. The idea of driving stages can be transferred to other controllers and offers other advantages (see below).
  1. The speed control is done with pulse width modulation (PWM), so the driving stages must be converted into the Duty Cycle values for the PWM in the sketch for the motor controller of the Robot Car. Only the driving stages are used in the remote control sketch.
  2. Turning at the Robot Car means that one engine turns faster and the other slower. Here I choose four stages, respectively the respective increase. Reduction of the values of the left or right engines.
  3. In order to realize several possibilities of entering (joystick, keypad with keys or keyboard input), I choose the code 505 for the rest position. The rear number (between 1 and 9) stands for the curve, the front number (between 0 and 10) for the speed. So for example, code 1005 for fastest straight exit, 505 for standstill, 707 for forward right.

y 0 x 

1

2

3

4

5

6

7

8

9

10

9

8

7

6

5

0

4

3

2

1

0


For the first time I choose the LCD keypad shield because it has five keys for my remote control and an integrated LCDisplay to display the sent code.

  1. The code limits the maximum number with if queries.
  2. For other functions (e.g. servos), codes are available above 1010.


The LCD keypad shield:

The five buttons of the LCD keypad shield are connected with voltage dividers at the A0 entrance. How this works Andreas Wolter has well explained in his blog Arduino: Multi-IO and EEPROM -[Part 1] .

In addition, the Pins A1 to A5 are easily accessible, which I would like to use for the 433 MHz transmitter HC-12. I'll solder a feather bar for that. I do the same with the engine driver shield; here too analog inputs are easily accessible.


Pin Assignment:

A1=GPIO15 for voltage supply 5V
A2=GPIO16 for GND
A3=GPIO17 and A4=GPIO18 for RX/TX using software serial

Two more comments on the engine shield:
1. While the cables are interchangeable at the motor terminals (left in the picture, right hidden by the HC-12), the polarity of the external voltage supply must be strictly observed.
2. The jumper (short circuit connector) PWRJMP in the center of the image produces the connection of the micro controller to the external voltage supply. Only when the pin is open, connect the MCU to the computer via USB. However, galvanic separation does not occur even then because of the entire circuit .


Sketch for remote control:

/*
LCD-Keypad-Shield as MotorController, stand still = 505
je 5 Stufen vor/zurück, je 3 Stufen rechts/links
*/

#include <SoftwareSerial.h>

//Pin assignment for HC-12 Transceiver
SoftwareSerial mySerial(18, 17); //RX, TX
int HC12PWR = 15;
int HC12GND = 16;
//int HC12SET = 19;

// LCD without I2C-Adapter
// Die Daten werden über die Pins D4 bis D7 übertragen
#include <LiquidCrystal.h>
//LCD pin to Arduino
const int pin_EN = 9;
const int pin_RS = 8;
const int pin_D4 = 4;
const int pin_D5 = 5;
const int pin_D6 = 6;
const int pin_D7 = 7;
LiquidCrystal lcd( pin_RS, pin_EN, pin_D4, pin_D5, pin_D6, pin_D7);

int x = 5;
int y = 5;
int delay4bounce = 250;

void setup() {
  pinMode(HC12PWR,OUTPUT);
  digitalWrite(HC12PWR,HIGH);
  pinMode(HC12GND,OUTPUT);
  digitalWrite(HC12GND,LOW);
  // pinMode(HC12SET,OUTPUT);
  // digitalWrite(HC12SET,LOW);
  mySerial.begin(9600);
  // Initialisierung des eingebauten LCD
  lcd.begin(16, 2); //LCD1602 mit 16 Zeichen und 2 Zeilen
  lcd.setCursor(0,0); //Zählung beginnt bei Null, erst Zeichen, dann Zeile
  lcd.print("AZ-Delivery.com");
  lcd.setCursor(0,1); // 0=Erstes Zeichen, 1=zweite Zeile
  lcd.print("Press Key:");
}

void sendcode() {
  mySerial.println(100*y + x);  //send code for motor
  delay(200); // little delay for next button press
}

void loop() {
  int A0;
  // alle Taster liegen über einen Spannungsteiler an A0
  // mit 3,9 kOhm Widerstand liegen die Spannungen zwischen 0 und 3,3 V
  // Werte des ADC liegen zwischen 0 und 1023
  // ggf. müssen die Werte angepasst werden
  A0 = analogRead (0); //
  lcd.setCursor(10,1); // Cursor beginnt hinter "Press Key"

  if (A0 < 60) {
    x = x+1;
    if (x>9) {x=9;}
    delay(delay4bounce);
    lcd.print (" ");
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
  else if (A0 < 250) {
    y = y+1;
    if (y>10) {y=10;}
    delay(delay4bounce);
    lcd.print (" ");
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
  else if (A0 < 400){
    y = y-1;
    if (y<0) {y=0;}
    delay(delay4bounce);
    lcd.print (" ");
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
  else if (A0 < 700){
    x = x-1;
    if (x<1) {x=1;}
    delay(delay4bounce);
    lcd.print (" ");
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
  else if (A0 < 900){
    x = 5;
    y = 5;
    delay(delay4bounce);
    lcd.print (" ");
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
  else {
    lcd.setCursor(10,1);
    lcd.print (100*y + x);
    sendcode();
  }
}



Before I present a possible sketch for the above Robot Car, I would like to pick up the idea behind the speed levels:

The assumption that the engine speed is proportional to the voltage is only conditional. Engines do not rotate at low voltages due to design. You hear a roar, but the torque is not enough to start. The first stage is therefore at a key level of at least 30-40%. And we had seen that depending on the voltage source used, we had to reduce the voltage to the highest level, in my case about 66%. So my five driving stages per direction are at a key level between 30 and 66. I have entered them in a list and use the index to access the respective value. I used the following values in the Arduino sketch with the above mentioned Adafruit library.

int speedLevel[11]={-65,-57,-49,-41,-33,0,33,41,49,57,65};


Attention: There are also differences in the Micro Controllers: for 8-bit PWM, the values between 0 and 255, for 10-bit PWM between 0 and 1023, for Raspberry Pi and the module gpiozero between -1 and +1.

With the multiplier factor I then adjust the values for the respective system. Of course, the values in the list can also be adjusted depending on the situation.

And also with the values for the curve travel you may have to tinker a little. For the four steps I use each direction I increase or reduce the values for the respective speed level on both sides.


Here the Sketch for the Robot Car with the remote control:


// Adafruit Motor shield library
// copyright Adafruit Industries LLC, 2009
// this code is public domain, enjoy!
// modified for AZ-Delivery

#include <AFMotor.h>
AF_DCMotor motor1(4);
AF_DCMotor motor2(3);
AF_DCMotor motor3(1);
AF_DCMotor motor4(2);
#include <SoftwareSerial.h>

// initialize HC-12
SoftwareSerial mySerial(18, 17); // RX, TX
int HC12PWR = 15;
int HC12GND = 16;
//int HC12SET = 19;

int x = 0;
int y = 0;
int left = 0;
int right = 0;
int code = 505;
int codeRead = 505;
int speedL = 0;
float faktor = 2.2; // Korrektur für Fahrtstufe

void setup() {
  Serial.begin(9600); // set up Serial Monitor at 9600 bps
  Serial.println("Motor test!");
  mySerial.begin(9600); // set up transmission speed for HC-12
  // initialize HC-12 power supply
  pinMode(HC12PWR,OUTPUT);
  digitalWrite(HC12PWR,HIGH);
  pinMode(HC12GND,OUTPUT);
  digitalWrite(HC12GND,LOW);
  // pinMode(HC12SET,OUTPUT);
  // digitalWrite(HC12SET,LOW);

  // turn on motor
  // motor4.setSpeed(200);
  // motor4.run(RELEASE);
}

void loop() {
  if (mySerial.available() > 1) {
    //read serial input and convert to integer (-32,768 to 32,767)
    codeRead = mySerial.parseInt();
    Serial.print("code received: ");
    Serial.println(codeRead);
    if (codeRead<=1010) {
      if (code != codeRead) {
        code = codeRead;
      }
    }
    else {
      Serial.print("wrong code received: ");
      code = 505;
    }
  }
  else {
    code = 505;
  }
  motor();

  mySerial.flush();//clear the serial buffer for unwanted inputs

  delay(20); //little delay for better serial communication
}

void motor(){
  int speedLevel[11]={-65,-57,-49,-41,-33,0,33,41,49,57,65};
  y = int(code /100);
  x = code - 100*y;
  speedL = speedLevel[y];
  Serial.print("code = ");
  Serial.print(code);
  Serial.print(" y = ");
  Serial.print(y);
  Serial.print(" x = ");
  Serial.print(x);
  Serial.print(" speedL = ");
  Serial.println(speedL);

  //Korrektur der Fahrtstufen für Kurvenfahrt
  if (x==0){
    right = speedL+16;
    left = speedL-16;
  }
  else if (x==1){
    right = speedL+16;
    left = speedL-16;
  }
  else if (x==2){
    right = speedL+13;
    left = speedL-13;
  }
  else if (x==3) {
    right = speedL+10;
    left = speedL-10;
  }
  else if (x==4) {
    right = speedL+7;
    left = speedL-7;
  }
  else if (x==6) {
    right = speedL -7;
    left = speedL+7;
  }
  else if (x==7) {
    right = speedL-10;
    left = speedL+10;
  }
  else if (x==8) {
    right = speedL-13;
    left = speedL+13;
  }
  else if (x==9) {
    right = speedL-16;
    left = speedL+16;
  }
  else if (x==10) {
    right = speedL-16;
    left = speedL+16;
  }
  else {
    right = speedL;
    left = speedL;
  }

  //Eingabe der Fahrtstufen für "left" und "right"
  Serial.print("left = ");
  Serial.print(left);
  Serial.print(" right = ");
  Serial.println(right);

  if (left < 25 & left > -25) {
    motor1.run(RELEASE);
    motor3.run(RELEASE);
  }

  if (right < 25 & right > -25) {
    motor2.run(RELEASE);
    motor4.run(RELEASE);
  }
  if (left>=25) {
    if (left>65) left=65;
      motor1.run(FORWARD);
      motor3.run(FORWARD);
      motor1.setSpeed(left * faktor);
      motor3.setSpeed(left * faktor);
  }
  if (right>=25) {
    if (right>65) right=65;
      motor2.run(FORWARD);
      motor4.run(FORWARD);
      motor2.setSpeed(right * faktor);
      motor4.setSpeed(right * faktor);
  }
  if (left<= -25) {
    if (left<-65) left=-65;
      motor1.run(BACKWARD);
      motor3.run(BACKWARD);
      motor1.setSpeed(-left * faktor);
      motor3.setSpeed(-left * faktor);
  }
  if (right<= -25) {
    if (right<-65) right=-65;
      motor2.run(BACKWARD);
      motor4.run(BACKWARD);
      motor2.setSpeed(-right * faktor);
      motor4.setSpeed(-right * faktor);
  }
}
 


With the Microcontroller board with ATmega328P, ATmega16U2, compatible with Arduino UNO R3 we have a robust, with the Arduino IDE easy to program MCU. Best not only for starting in the world of programming (new term: computational thinking), but also for access to electronic circuits (new term: physical computing).

With the 4-Channel L293D Shield Driver easy access to robotics with the help of existing program libraries such as the Adafruit Library .

In further blog posts, we will show alternative solutions with other micro controllers and other remote control options.


AmateurfunkFor arduino

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN