Robot vehicle with BLYNK and Odometry control

Welcome to a new blog from our series about Robot Cars. This time it is about determining the path covered by the number of revolutions of the wheels. This method is called odometry. That comes from the Greek and means path measurement.

Geometric basics

For straight ahead, that's easy. If both wheels turn fast, the distance traveled after a revolution is equal to the circumference of the wheel with diameter D

The revolutions can be determined with a slot disc and a fork light barrier. If the slot disc N slots we get N impulses per revolution. To determine the way between two pulses, we must divide the scope through the pulses per revolution. We get the route factor.

Also, the determination of the rotation angle is simple at a two-wheeled vehicle when it assumes that both wheels are rotating at the same time at the same speed. In this case, both wheels describe a circle around the unchanged point of view with the wheel spacing A as a diameter. To rotate the vehicle by 360 degrees, a distance of

be covered. For an angle α results in a route of

Both wheels rest back the same route, but in the opposite direction. To get the number of pulses, you have to divide through the route factor.

From the reciprocal, the angle factor in the degree / pulse results


Required hardware

In contrast to the first part, we need the ESP32 module in this case because additional inputs for the fork light barriers are needed. It should also be better used the chassis from the 3D printer, so that the assembly of forabolisers is greatly facilitated.





2 engines with wheels



Roll ball as a follow-up wheel



Screws M3 x 30 with mother



Screws M3 x 12 with mother



Chassis from the 3D printer



PowerPack holder from the 3D printer






Set of forabolis barriers with slit washers



Tin screws 2.2 x 6.5 mm



In order for the motor driver Shield to be used with the ESP controllers, small conversion work is necessary because the ESP controllers work with 3.3V supply voltage.

From the connection D7, a 10kohm resistance to GND should be connected. This is necessary because the PIN D7 is connected to the Shield via a pullup resistance of 10kohm with 5V.

Motor driver shieldThe connections A0 to A5, as well as the associated supply connections, should be equipped with pin strips. In order for these connections to be used for sensors, the supply voltage must also be connected instead of 5V to 3.3V. This simply happens in that on the underside, the connecting line to the pins is separated. Then you can connect the pins to the 3.3V pin. The connections for the supply voltage should also be equipped.

For connection of the two motors, the outputs M2 and M4 are used. The motor connections are connected to the corresponding screw terminals on the engine hield. If the vehicle is supplied via the USB jack on the microcontroller, the jumper (red circle) must be removed and the input M + is connected to the 5V connection of the microcontroller (green drawn).

Motors at the driver Shield

For the measurement of the rotational speed, a slot washer is inserted on the inner end of the motor axes and the fork cells each with a sheet metal screw is screwed on the mounted holder.

Slot slices

Now Vcc, GND and D0 are connected to the connections A2 and A3 at the motorshield via a three-pole cable. VCC with + 5V, GND with GND and D0 with A2 or A3.


The MotorShield is then simply plugged on the microcontroller board. Further wiring measures are not required.
For mechanical fastening of the boards, corresponding holes are present at the chassis from the 3D printer, on which the microcontroller board can be screwed on with spacers. At the kit you may have to drill suitable holes.


For remote control of the vehicle, the freely available software BLYNK should be used. BLYNK is a Kickstarter project that allows the most simple control of microcontrollers with a smartphone. Essential element is the app with which an application can be created via a kind of construction kit. All information about this application is stored in the BLYNK server. Each application receives a unique identification number. On the microcontroller side, a library ensures that the microcontroller communicates with the BLYNK server and that the application on the smartphone can directly read or control the pins of the microcontroller. No further programming on the microcontroller is required. The only condition is that the microcontroller must have access to the BLYNK server. Since no pins of the microcontroller can be controlled directly for the remote control of the vehicle, a little programming will be necessary.

First, however, the BLYNK app must be installed on the smartphone. After the app has been installed and started, the login screen appears. To use BLYNK, you need an account for the BLYNK server. This requires only an e-mail address and any password.

After creating an account, a message about energy appears. To create a BLYNK project, you need a certain amount of energy for each screen element. With a new account you automatically get 2000 power points that you can work with. If you need more energy points for a project, you can buy such purchases.

After this message window, the home screen appears. Here the Robocar project can now be loaded via the following QR code.

You now see the project window. If you click on the nut symbol, the project settings open. In the project settings you will find the link "Email All". When you click on it, you will receive an e-mail with the key that your microcontroller needs sketch to communicate with the BLYNK server. With the triangle icon on the project window at the top right you start the application.


More information about BLYNK, can be found on the Internet or in SmartHome book. The book also describes how to install your own private BLYNK server on a Raspberry PI.

The control is done by setting the desired route and the desired angle. By clicking on the buttons forward or backwards the vehicle drives the set track. By clicking on the buttons on the left or right, the vehicle rotates around the set angle.

If you click the Learn button, then switches to the learning mode. Each entered command is now recorded and can be performed automatically later. Recently clicking the Learn button finishes the learning mode. Now you can automatically run the stored commands by clicking on the RUN button.

At the bottom, it is displayed as many command lines were stored. A maximum of 30 command lines can be stored. When switching to the learning mode, the command-line counter is reset to 0.

The sketch

Besides the ESP32 package, you need the BLYNK library that can be installed through library management.

 // you should get auth token in the blynk app. // go to the Project Settings (Nut Icon).
 // blynk authentication key
 #define AUTH "*********************************"
 // SSID of your WLAN
 #define SSID "*******************************"
 // Passkey for your WLAN
 #define PASS "***********************************"
 // Name of private BLYNK server, or "" if you use public BLYNK server
 #define SRV "raspberrypi4"
 #define MAXCOMMANDS 30 //Anzahl der Befehlszeilen, die gespeichert werden können
 #define BLYNK_PRINT Serial
 //Bits im Schieberegister zu Motor Zuordnung
 #define M1A 2 //Motor1 A
 #define M1B 3 //Motor1 B
 #define M2A 1 //Motor2 A
 #define M2B 4 //Motor2 B
 #define M3A 5 //Motor3 A
 #define M3B 7 //Motor3 B
 #define M4A 0 //Motor4 A
 #define M4B 6 //Motor4 B
 //Pins für das Drehrichtungs Schieberegister
 #define SHIFT_IN 12 //Dateneingang Arduino D8
 #define SHIFT_CLK 17//Schiebetackt Arduino D4
 #define LATCH_CLK 19 //Speichertakt Arduino D12
 #define OUT_ENABLE 14//Mit LOW Ausgang aktivieren Arduino D7
 //PWM für Motoren Geschwindigkeit zwischen 0 und 1023
 #define M1PWM 23 //Pin für Motor1 Arduino D11
 #define MRIGHT 25//Pin für Motor2 Arduino D3
 #define M3PWM 27 //Pin für Motor3 Arduino D6
 #define MLEFT 16 //Pin für Motor4 Arduino D5
 //Servo Anschlüsse
 #define SERVO1 5//Pin für Servo1 D10
 #define SERVO2 13 //Pin für Servo2 D9
 //Motor Zuordnung
 #define DRIGHT 2
 #define DLEFT 4
 //Konstanten für Drehrichtung
 #define STOP 0
 #define FORWARD 1
 #define BACKWARD 2
 #define SPEEDLEFT 35
 #define SPEEDRIGHT 34
 //Aktueller Inhalt des Richtungs-Schieberegisters
 uint32_t directions = 0;
 struct {
   char cmd;
   uint16_t val;
 uint8_t cmdCnt = 0;
 int32_t cntleft = 0;
 int32_t cntright = 0;
 uint16_t strecke = 0;
 uint16_t winkel = 0;
 boolean button = 0;
 float winkelfaktor, streckenfaktor;
 boolean learning = false;
 boolean program = false;
 uint8_t prgStep;
 //Für Interrupt Synchronisation
 //Interrupt Service Routine für linken Speedsensor
 void IRAM_ATTR isrLeft() {
   if ((cntleft <= 0) && (cntright<=0)) {
     if (program) nextStep();
 //Interrupt Service Routine für rechten Speedsensor
 void IRAM_ATTR isrRight() {
   if ((cntleft <= 0) && (cntright<=0)) {
     if (program) nextStep();
 //Füllt das Schieberegister zur Motorsteuerung
 //mit dem Inhalt von directions
 void sendDirections() {
   uint8_t i;
   digitalWrite(LATCH_CLK, LOW);  //Speicher sperren
   digitalWrite(SHIFT_IN, LOW);   //Eingang auf 0
   for (i=0; i<8; i++) {
     digitalWrite(SHIFT_CLK, LOW);//Bit für Bit einlesen
     if (directions & bit(7-i)) {
       digitalWrite(SHIFT_IN, HIGH);
    } else {
       digitalWrite(SHIFT_IN, LOW);
     digitalWrite(SHIFT_CLK, HIGH);
   digitalWrite(LATCH_CLK, HIGH); //Mit der positiven Flanke speichern
 void nextStep() {
   if (prgStep < cmdCnt) {
      switch (commands[prgStep].cmd) {
        case 'F' :
        case 'B' : drive(commands[prgStep].cmd,commands[prgStep].val);
        case 'L' :
        case 'R' : turn(commands[prgStep].cmd,commands[prgStep].val);
  } else {
     program = false;
 //Die Drehrichtung für einen Motor festlegen
 void setDirection(uint8_t motor, uint8_t direction) {
   uint8_t a=0, b=0;
   //Bitnummern für den gewählten Motor bestimmen
   switch (motor) {
     case 1: a=M1A; b=M1B; break;
     case 2: a=M2A; b=M2B; break;
     case 3: a=M3A; b=M3B; break;
     case 4: a=M4A; b=M4B; break;
   // first put both bits for the engine to 0 means STOP
   Directions &= ~ bit(a) & ~ bit(b);
   switch (direction) {
     cube Forward: Directions |= bit(a); break; // for forward bit A to 1
     cube Backward: Directions |= bit(b); break;// for backward bit b on 1
   //Serial.printf ("Directions =% x \ n ", directions);
   SendDirections(); // Send new setting to the shift register
 int Z;
 void motor(INT16_T left, INT16_T Right) {
   //Serial.PrintF("Links% I, right% I \ N ", LEFT, RIGHT);
   IF (left<0) {
  } Else IF (left == 0){
  } Else {
   IF (Right<0) {
  } Else IF (Right == 0){
  } Else {
 void drive(Char direction, uint16_t Val) {
   IF (learning) {
     IF (CMDCNT < MaxCommands) {
       commands[CMDCNT].CMD = direction;
       commands[CMDCNT].Val = Val;
  } Else {
     cntleft = Val;
     cntright = Val;
     IF (direction == 'F') {
    } Else {
 void turn(Char direction, uint16_t Val) {
   IF (learning) {
     IF (CMDCNT < MaxCommands) {
       commands[CMDCNT].CMD = direction;
       commands[CMDCNT].Val = Val;
  } Else {
     cntleft = Val;
     cntright = Val;
     IF (direction == "L ') {
    } Else {
 void set up() {
   // put all pins used as output
   Ledcsetup(Daily, 100, 10);
   Ledcsetup(Drick,100, 10);
   // All engines STOP
   Directions = 0;
   // Calculate odometry parameters
   // Stretch factor = wheel diameter * PI / number_schlitze
   stretch factor = 67 * 3.14 /20; // = 10.524 mm / pulse
   // Angle factor = 360 degrees / (axis distance * PI) * Stretch factor
   angle factor = (360 * 67)/(130 * 20); // = 9,277 degree / pulse
   SendDirections();  // send to the shift register
   digitalWrite(Out_enable,0); // Release outputs of the shift register
   attachine rupture(Speedleft,Isrleft,Falling);
   attachine rupture(Speedright,Isrright,Falling);
   Serial.Println("Start Blynk");
   #Ifdef srv
     Blenk.Begin(Auth, SSID, PASSPORT, SRV, 8080);
     Blenk.Begin(Auth, SSID, PASSPORT);
   button = 0;
 // small test loop
 void loop() {;
    { route = param[0].asine(); }
    { angle = param[0].asine(); }
 { IF (param[0].asine() == 0) {
       button = false;
    } Else {
         IF (!button) {
             button = true;
             drive('F',route / stretch factor);
 { IF (param[0].asine() == 0) {
       button = false;
    } Else {
         IF (!button) {
             button = true;
             drive("B ',route /stretch factor);
 { IF (param[0].asine() == 0) {
       button = false;
    } Else {
       IF (!button) {
         button = true;
         turn("L ',angle / angle factor);
 { IF (param[0].asine() == 0) {
     button = false;
    } Else {
         IF (!button) {
             button = true;
             turn("R ',angle /angle factor);
     IF (!learning) {
         program = true;
     learning = (param[0].asine() != 0);
     IF (learning) CMDCNT = 0;

Source code as a download

Have fun with the robot car


Esp-32Projects for beginners


Gerald Lechner

Gerald Lechner

Wenn in Zeile 72 und 73 die Strecke auf 300 und der Winkel auf 90 voreingestellt werden, dann stimmen die Werte nach dem Start mit den Schiebern in der Blynk-App überein. Das Problem mit der Verbindung kommt daher, dass das Board eine relativ schlechte Wlan Empfindlichkeit hat



Sehr schönes Projekt, und einfacher als erst gedacht. Vor allem die Odometrie ist so ein Kinderspiel.
Umgesetzt mit ein ESP32 Dev Board, motordriver L289N und lokalen Blynkserver.

Ein offenes Problem ist, dass der Blynkserver keine initiale Werte gibt ( oder ich nicht weis wie ). Was bedeutet das erst die Schieber für Distanz und Winkel betätigt werden müssen, sonst ist der Wert der den man im Programm festlegt ( = 0 und dann passiert nichts ).
Der Blynkserver ist für mich sowie so eine Blackbox, wobei manchmal keine Verbindung zu Stande kommt, scheinbar ohne Ursache. Nach einigen Reboots funktioniert es dann auf einmal .

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