Table of Contents
Today we will build a robot car with a difference – in the wheels, anyway. Mecanum wheels are unique because they can propel a vehicle in any direction, not just forwards or backward.
Driving a vehicle with Mecanum wheels is different from standard wheels, and today we’ll see exactly how to do that. We’ll build a robot car that uses an ESP32, one featuring a remote control that uses another ESP32.
Introduction
When we design a simple robot or “robot car,” we need to choose a steering method. There are a couple of popular choices:
- Skid Steering – Probably the most popular for robot cars, with skid steering two wheels are used. By changing the speed and direction of each wheel, the robot car can be steered in any direction.
- Standard Steering – This is the type of steering commonly used in automobiles, when used with cars and trucks, it can also be called rack and pinion steering. With this type of steering, the front (or all 4) wheels are moved in tandem to change their direction of travel. When used with small models and robot cars, the front wheels can be moved using a servo or stepper motor.
Today we will build a robot car using a third choice – Mecanum Wheels.
Mecanum Wheels
Mecanum wheels were invented in 1972 by Swedish engineer Bengt Erland Ilon, who worked for a design firm named Mecanum AB.
Mecanum wheels are omnidirectional wheels, they can be used to propel a vehicle in any direction. Instead of tires, they have a series of rubberized external rollers mounted at 45-degree angles around the wheel’s circumference.
Mecanum Wheels need to be used in groups of four or more to allow omnidirectional movement, although you can achieve some control using just three wheels.
The rollers on these wheels can be oriented in two different directions, and you need two of each type for complete control.
One set of wheels has the rollers mounted at a 45-degree angle to the axle. The other type uses rollers mounted at a 45-degree angle to the plane of the wheel.
These four wheels (two of each type) are mounted on the robot car chassis as shown here:
By mounting the wheels in this manner, we can move the vehicle in six different directions.
Move Straight
In this mode, our Mecanum Wheel vehicle operates just like a standard robot car. We can go forwards or backward by alternating the direction we spin the wheels.
This mode has all four wheels driven in the same direction and at the same speed.
Move Sideways
By spinning the wheels on each side in opposite directions and reversing the order between sides, we can make our vehicle move sideways.
Reversing the directions of all four motors will cause the vehicle to go sideways in the opposite direction.
Move Diagonally
To move diagonally, we only use two motors, ones on opposite corners that have the same wheel roller orientation.
Changing the direction of rotation will cause the vehicle to move at the same angle but in the opposite direction. Using the other two Mecanum wheels will cause the vehicle to travel diagonally as well, 90 degrees opposite to the original configuration.
Pivot (on one side)
Running two wheels on the same side of the vehicle in the same direction will cause it to pivot.
Reversing the rotation of the wheels will make the vehicle pivot in the opposite direction, and using the wheels on the other side of the vehicle will cause it to pivot on the other side.
Rotate
By spinning the wheels on one side of the chassis in one direction and the ones on the other side in the opposite direction, we can make the vehicle rotate.
This is the same operation as a standard 4-wheel robot car. Reversing the direction of wheel rotation will cause the car to spin in the opposite direction.
Pivot Sideways
The final Mecanum wheel movement is a variation of the Pivot. This is the Pivot Sideways, which works in the same fashion but uses two wheels on the same axle, as opposed to on the same side.
Once again, reversing the direction of wheel rotation will reverse the pivot direction.
Testing Mecanum Wheels
All that theory is fine, but the real fun is putting it to the test.
To do that, I installed the motors and wheels onto the bottom plate of the chassis that I purchased (my chassis has a lower and upper section). I didn’t do a complete assembly, and I didn’t even remove the plastic film from the chassis, as this is just a temporary test.
I also put the chassis “up on blocks,” meaning that I took a couple of scraps of wood to raise it above my workbench. Didn’t want it running all over the place while I tested it!
After I did a few tests up on blocks, I wanted to see how it ran on the floor – watching wheels move is interesting, but the real fun comes in watching it actually go through the maneuvers on the ground. To accomplish this, I extended the connections between my motor and my solderless breadboard by using a flexible Ethernet cable.
ESP32 Motor Hookup
Four motors mean four motor drivers, and the TB6612FNG is the obvious choice. Its MOSFET design is vastly superior to the old L298N dual motor driver, plus it is a lot smaller.
I hooked up the drivers and motors as follows:
The “Motor Power” will, of course, depend upon which motors you are using. The small yellow motors commonly supplied with “robot car bases” generally like 6 to 9 volts as a power supply, and for my tests, I used a 6-volt battery pack.
Simple Motor Test Sketch
To test the motors, I used a very simple sketch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
/* 4WD Mecanum Wheel Robot Car Base - Motor Test 4wd-test.ino Cycles all 4 Mecanum wheels to verify proper operation Uses ESP32 DevKitC (other ESP32 boards will work) Uses 2 TB6612FNG Motor Drivers, also compatible with L298N DroneBot Workshop 2022 https://dronebotworkshop.com */ // Define Motor Connections // Right Front Motor #define MF_PWMA 19 #define MF_AI1 32 #define MF_AI2 23 // Left Front Motor #define MF_PWMB 26 #define MF_BI1 33 #define MF_BI2 25 // Right Rear Motor #define MR_PWMA 27 #define MR_AI1 12 #define MR_AI2 14 // Left Rear Motor #define MR_PWMB 4 #define MR_BI1 13 #define MR_BI2 2 // Define some preset motor speeds (0 - 255, adjust as desired) int speed_slow = 100; int speed_fast = 250; void setup() { // Set all connections as outputs pinMode(MF_PWMA, OUTPUT); pinMode(MF_AI1, OUTPUT); pinMode(MF_AI2, OUTPUT); pinMode(MF_PWMB, OUTPUT); pinMode(MF_BI1, OUTPUT); pinMode(MF_BI2, OUTPUT); pinMode(MR_PWMA, OUTPUT); pinMode(MR_AI1, OUTPUT); pinMode(MR_AI2, OUTPUT); pinMode(MR_PWMB, OUTPUT); pinMode(MR_BI1, OUTPUT); pinMode(MR_BI2, OUTPUT); } void loop() { // Front Right Motor ***************************************************************** // FR - Forward Slow Speed digitalWrite(MF_AI1, HIGH); digitalWrite(MF_AI2, LOW); analogWrite(MF_PWMA, speed_slow); delay(2000); // FR - Forward Fast Speed analogWrite(MF_PWMA, speed_fast); delay(2000); // FR - Stop 1 second analogWrite(MF_PWMA, 0); delay(1000); // FR - Reverse Fast Speed digitalWrite(MF_AI1, LOW); digitalWrite(MF_AI2, HIGH); analogWrite(MF_PWMA, speed_fast); delay(2000); // FR - Reverse Slow Speed analogWrite(MF_PWMA, speed_slow); delay(2000); // FR - Stop 1 second analogWrite(MF_PWMA, 0); delay(1000); // Front Left Motor ***************************************************************** // FL - Forward Slow Speed digitalWrite(MF_BI1, HIGH); digitalWrite(MF_BI2, LOW); analogWrite(MF_PWMB, speed_slow); delay(2000); // FL - Forward Fast Speed analogWrite(MF_PWMB, speed_fast); delay(2000); // FL - Stop 1 second analogWrite(MF_PWMB, 0); delay(1000); // FL - Reverse Fast Speed digitalWrite(MF_BI1, LOW); digitalWrite(MF_BI2, HIGH); analogWrite(MF_PWMB, speed_fast); delay(2000); // FL - Reverse Slow Speed analogWrite(MF_PWMB, speed_slow); delay(2000); // FL - Stop 1 second analogWrite(MF_PWMB, 0); delay(1000); // Rear Right Motor ***************************************************************** // RR - Forward Slow Speed digitalWrite(MR_AI1, HIGH); digitalWrite(MR_AI2, LOW); analogWrite(MR_PWMA, speed_slow); delay(2000); // RR - Forward Fast Speed analogWrite(MR_PWMA, speed_fast); delay(2000); // RR - Stop 1 second analogWrite(MR_PWMA, 0); delay(1000); // RR - Reverse Fast Speed digitalWrite(MR_AI1, LOW); digitalWrite(MR_AI2, HIGH); analogWrite(MR_PWMA, speed_fast); delay(2000); // RR - Reverse Slow Speed analogWrite(MR_PWMA, speed_slow); delay(2000); // RR - Stop 1 second analogWrite(MR_PWMA, 0); delay(1000); // Rear Left Motor ***************************************************************** // RL - Forward Slow Speed digitalWrite(MR_BI1, HIGH); digitalWrite(MR_BI2, LOW); analogWrite(MR_PWMB, speed_slow); delay(2000); // RL - Forward Fast Speed analogWrite(MR_PWMB, speed_fast); delay(2000); // RL - Stop 1 second analogWrite(MR_PWMB, 0); delay(1000); // RL - Reverse Fast Speed digitalWrite(MR_BI1, LOW); digitalWrite(MR_BI2, HIGH); analogWrite(MR_PWMB, speed_fast); delay(2000); // RL - Reverse Slow Speed analogWrite(MR_PWMB, speed_slow); delay(2000); // RL - Stop 1 second analogWrite(MR_PWMB, 0); delay(1000); } |
Our motors are driven using PWM (Pulse Width Modulation), so this sketch uses the simple analogWrite command that most Arduino users are familiar with. It works, but with the ESP32, there are better ways of generating PWM for motors.
This is a pretty basic sketch, we simply set the control inputs to the motor driver to set direction and apply a PWM signal to the PWM input. We deal with each motor in sequence, moving it, stopping it, and then reversing it.
Load it up to your ESP32 and watch it work. It’s a good way to determine if everything is hooked up correctly.
Functions for Controlling Motors
As we have already seen, Mecanum wheels can move a vehicle in any direction by manipulating the direction of each wheel.
With the TB6612FNG motor driver, you change the motor rotation direction by changing the state of the input signals. These are labeled AI1 and AI2 for channel A and BI1 and BI2 for channel B. The truth table for the motor inputs is shown below:
Since we require two bits per motor for discretion control and since we have four motors, this means that a total of eight bits is required to specify every possible motor direction movement. And, of course, eight bits is a byte!
This means that each of the Mecanum “modes” can be specified with a single byte. Let’s go over them:
Straight Modes
There are two straight modes, one forward and one backward.
Sideways Modes
There are also two sideways modes, one to the right and one to the left.
Diagonal Modes
There are four diagonal modes, as indicated in the above diagram. Note that the vehicle is not confined to these four directions; these are just the directions it would travel if the wheels were all moving at the same speed. By varying the speed between the active wheels, you can move the car in any direction.
Pivot Modes
And there are also four pivot modes, as shown here.
Pivot Sideways
We can also pivot sideways as well.
Rotate Mode
Finally, we have two rotation modes, one clockwise and one counterclockwise.
Mode Summary
The above chart summarizes all the modes, as well as the corresponding byte pattern required to execute them.
Mecanum Functions Test
Now that we have determined that we can direct our Mecanum Motors using a single byte, we can create a sketch to test it out.
We will also use a different method of producing and controlling PWM signals for our motors.
ESP32 PWM
Although the analogWrite command works with the ESP32, it is not the preferred method of creating PWM with this microcontroller. It does give you control over the pulse width, but to modify the base frequency, you need to modify a few registers.
The ESP32 has two other methods of PWM control:
- Motor Control Pulse Width Modulator (MCPWM)
- LED PWM Controller (LEDC)
At first glance, it would appear that the MCPWM method would be the most suitable; after all, it is literally called “Motor Control Pulse Width Modulator”! However, there are only two MCPWM channels in an ESP32, and we need four. In addition, it is somewhat difficult to code for this method using the Arduino IDE.
In contrast, there are 16 LEDC channels in the ESP32, which can easily be used to control motors. Don’t let the “LED” in the name dissuade you!
One thing about those 16 channels that you need to be aware of is that the frequency that they operate at is controlled by two registers, with each register controlling eight channels. So you can only have two different frequencies. In our application, this is irrelevant, as we want all of our channels operating at the same frequency.
Mecanum Functions Test Sketch
Here is a sketch to test the Mecanum Wheels in several different “modes.” I wrote it to use both the sideways and straight modes, but you can expand upon it to use the other ones if you wish.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
/* 4WD Mecanum Wheel Robot Car Base - Simple Maneuver Test mecanum-test.ino Cycles 4WD Mecanum Wheel Robot Car through simple manuuvers Uses ESP32 DevKitC (other ESP32 boards will work) Uses 2 TB6612FNG Motor Drivers, also compatible with L298N DroneBot Workshop 2022 https://dronebotworkshop.com */ // Define Motor Connections // Right Front Motor #define MF_PWMA 19 #define MF_AI1 32 #define MF_AI2 23 // Left Front Motor #define MF_PWMB 26 #define MF_BI1 33 #define MF_BI2 25 // Right Rear Motor #define MR_PWMA 27 #define MR_AI1 12 #define MR_AI2 14 // Left Rear Motor #define MR_PWMB 4 #define MR_BI1 13 #define MR_BI2 2 // Define PWM Motor Speed Variables int rf_PWM = 0; // Right Front Motor int lf_PWM = 0; // Left Front Motor int rr_PWM = 0; // Right Rear Motor int lr_PWM = 0; // Left Front Motor // Define Bytes to represent Mecannum Wheel Modes // Individual bits define TB6612FNG motor driver module input states // B7 = MF_AI1, B6 = MF_AI2, B5 = MF_BI1, B4 = MF_BI2, B3 = MR_AI1, B2 = MR_AI2, B1 = MR_BI1, B0 = MR_BI2 const byte MEC_STRAIGHT_FORWARD = B10101010; const byte MEC_STRAIGHT_BACKWARD = B01010101; const byte MEC_SIDEWAYS_RIGHT = B01101001; const byte MEC_SIDEWAYS_LEFT = B10010110; const byte MEC_DIAGONAL_45 = B00101000; const byte MEC_DIAGONAL_135 = B10000010; const byte MEC_DIAGONAL_225 = B00010100; const byte MEC_DIAGONAL_315 = B01000001; const byte MEC_PIVOT_RIGHT_FORWARD = B00100010; const byte MEC_PIVOT_RIGHT_BACKWARD = B00010001; const byte MEC_PIVOT_LEFT_FORWARD = B10001000; const byte MEC_PIVOT_LEFT_BACKWARD = B01000100; const byte MEC_ROTATE_CLOCKWISE = B01100110; const byte MEC_ROTATE_COUNTERCLOCKWISE = B10011001; const byte MEC_PIVOT_SIDEWAYS_FRONT_RIGHT = B01100000; const byte MEC_PIVOT_SIDEWAYS_FRONT_LEFT = B10010000; const byte MEC_PIVOT_SIDEWAYS_REAR_RIGHT = B00001001; const byte MEC_PIVOT_SIDEWAYS_REAR_LEFT = B00000110; // Variable for test time delay int timeDelay = 1000; // PWM Parameters for motor control // PWM Frequency = 1KHz const int mtrPWMFreq = 1000; // PWM Resolution const int mtrPWMResolution = 8; // Define PWM channels for each motor const int mtrRFpwmchannel = 4; const int mtrLFpwmchannel = 5; const int mtrRRpwmchannel = 6; const int mtrLRpwmchannel = 7; void moveMotors(int speedRF, int speedLF, int speedRR, int speedLR, byte dircontrol) { // Moves all 4 motors // Directions specified in direction byte // Right Front Motor digitalWrite(MF_AI1, bitRead(dircontrol, 7)); digitalWrite(MF_AI2, bitRead(dircontrol, 6)); ledcWrite(mtrRFpwmchannel, abs(speedRF)); // Left Front Motor digitalWrite(MF_BI1, bitRead(dircontrol, 5)); digitalWrite(MF_BI2, bitRead(dircontrol, 4)); ledcWrite(mtrLFpwmchannel, abs(speedLF)); // Right Rear Motor digitalWrite(MR_AI1, bitRead(dircontrol, 3)); digitalWrite(MR_AI2, bitRead(dircontrol, 2)); ledcWrite(mtrRRpwmchannel, abs(speedRR)); // Left Rear Motor digitalWrite(MR_BI1, bitRead(dircontrol, 1)); digitalWrite(MR_BI2, bitRead(dircontrol, 0)); ledcWrite(mtrLRpwmchannel, abs(speedLR)); } void stopMotors() { // Stops all motors and motor controllers ledcWrite(mtrRFpwmchannel, 0); ledcWrite(mtrLFpwmchannel, 0); ledcWrite(mtrRRpwmchannel, 0); ledcWrite(mtrLRpwmchannel, 0); digitalWrite(MF_AI1, 0); digitalWrite(MF_AI2, 0); digitalWrite(MF_BI1, 0); digitalWrite(MF_BI2, 0); digitalWrite(MR_AI1, 0); digitalWrite(MR_AI2, 0); digitalWrite(MR_BI1, 0); digitalWrite(MR_BI2, 0); } void setup() { // Set up Serial Monitor Serial.begin(9600); // Set all connections as outputs pinMode(MF_PWMA, OUTPUT); pinMode(MF_AI1, OUTPUT); pinMode(MF_AI2, OUTPUT); pinMode(MF_PWMB, OUTPUT); pinMode(MF_BI1, OUTPUT); pinMode(MF_BI2, OUTPUT); pinMode(MR_PWMA, OUTPUT); pinMode(MR_AI1, OUTPUT); pinMode(MR_AI2, OUTPUT); pinMode(MR_PWMB, OUTPUT); pinMode(MR_BI1, OUTPUT); pinMode(MR_BI2, OUTPUT); //Set up PWM channels with frequency and resolution ledcSetup(mtrRFpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrLFpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrRRpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrLRpwmchannel, mtrPWMFreq, mtrPWMResolution); // Attach channels to PWM output pins ledcAttachPin(MF_PWMA, mtrRFpwmchannel); ledcAttachPin(MF_PWMB, mtrLFpwmchannel); ledcAttachPin(MR_PWMA, mtrRRpwmchannel); ledcAttachPin(MR_PWMB, mtrLRpwmchannel); // Test speed for all motors (change as desired) rf_PWM = 200; // Right Front Motor lf_PWM = 200; // Left Front Motor rr_PWM = 200; // Right Rear Motor lr_PWM = 200; // Left Front Motor } void loop() { // Cycle through some Mecanum Wheel modes delay(3000); // Straight Forward Serial.println("Straight Forward"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_STRAIGHT_FORWARD); delay(timeDelay); stopMotors(); delay(500); // Straight Backward Serial.println("Straight Backward"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_STRAIGHT_BACKWARD); delay(timeDelay); stopMotors(); delay(500); // Sideways Right Serial.println("Sideways Right"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_SIDEWAYS_RIGHT); delay(timeDelay); stopMotors(); delay(500); // Sideways Left Serial.println("Sideways Left"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_SIDEWAYS_LEFT); delay(timeDelay); stopMotors(); delay(500); // Diagonal 45 Degrees Serial.println("Diagonal 45 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_45); delay(timeDelay); stopMotors(); delay(500); // Diagonal 225 Degrees Serial.println("Diagonal 225 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_225); delay(timeDelay); moveMotors(0, 0, 0, 0, MEC_DIAGONAL_225); delay(500); // Diagonal 135 Degrees Serial.println("Diagonal 135 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_135); delay(timeDelay); moveMotors(0, 0, 0, 0, MEC_DIAGONAL_135); delay(500); // Diagonal 315 Degrees Serial.println("Diagonal 315 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_315); delay(timeDelay); moveMotors(0, 0, 0, 0, MEC_STRAIGHT_FORWARD); delay(500); } |
You’ll note that each of the “Mecanum Modes” has been defined as a byte. This byte is passed to the moveMotors function, which accepts it, as well as the speed values for the four motors.
Note the statements in the Setup function that prepare the LEDC registers. In this example, we use a frequency of 1Khz, defined by the constant mtrPWMFreq, and a PWM resolution of 8-bits (0-255) as defined in the constant mtrPWMResolution.
In the Loop, we exercise the motors in several different modes.
Load it up and try it out. If you can, put the car on the floor and watch it move. In a perfect world, it will end up exactly where it started, but it is more likely to end up near where it started. A lot depends upon the floor surface.
Neopixels
Another key component in our car is the NeoPixels we use as indicators. There are five of these, one for each motor and a fifth Status LED.
We have used NeoPixels in several projects already, and I covered their operation in detail in the article “RGB LEDs – Colorful Arduino Experiments”. You may want to check that out if you are unfamiliar with how they operate.
The NeoPixels I’m using are 8mm individual NeoPixels from Adafruit.
Neopixels Hookup
NeoPixels are serial devices, and they each have four leads:
- The Data In. This is a 5-volt logic signal.
- 5V – NeoPixels are powered by 5 volts.
- Ground
- Data Out – This is the output, which will be connected to the next NeoPixel in the string.
It’s also a good idea to put a small capacitor across the 5V and GND leads on each NeoPixel. This capacitor should be as physically close to the LED as possible. Adafruit recommends a value of 0.1uf (100nf).
As we require 5-volt logic for our NeoPixels, I’m using a logic-level converter to handle the conversion. You could also use a transistor, but I have a drawer full of these!
Here is the hookup of our five NeoPixels. Note that this schematic is in addition to the motor driver hookup; I just removed those connections for clarity.
LED Test Script
To test the LEDs, we will use the Adafruit NeoPixel Library, which makes sense as we are using Adafruit NeoPixels. If you don’t have this library, you may use your Library Manager to install it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
/* 4WD Mecanum Wheel Robot Car Base - Simple Maneuver Test with status LEDs mecanum-led-test.ino Cycles 4WD Mecanum Wheel Robot Car through simple manuuvers Uses ESP32 DevKitC (other ESP32 boards will work) Uses 2 TB6612FNG Motor Drivers, also compatible with L298N Requires Adafruit NeoPixel Library (and dependant libraries) Updated for 5 LEDs DroneBot Workshop 2022 https://dronebotworkshop.com */ //Include NeoPixel library #include <Adafruit_NeoPixel.h> // Define Motor Connections // Right Front Motor #define MF_PWMA 19 #define MF_AI1 32 #define MF_AI2 23 // Left Front Motor #define MF_PWMB 26 #define MF_BI1 33 #define MF_BI2 25 // Right Rear Motor #define MR_PWMA 27 #define MR_AI1 12 #define MR_AI2 14 // Left Rear Motor #define MR_PWMB 4 #define MR_BI1 13 #define MR_BI2 2 // Define PWM Motor Speed Variables int rf_PWM = 0; // Right Front Motor int lf_PWM = 0; // Left Front Motor int rr_PWM = 0; // Right Rear Motor int lr_PWM = 0; // Left Front Motor // Define Bytes to represent Mecannum Wheel Modes // Individual bits define TB6612FNG motor driver module input states // B7 = MF_AI1, B6 = MF_AI2, B5 = MF_BI1, B4 = MF_BI2, B3 = MR_AI1, B2 = MR_AI2, B1 = MR_BI1, B0 = MR_BI2 const byte MEC_STRAIGHT_FORWARD = B10101010; const byte MEC_STRAIGHT_BACKWARD = B01010101; const byte MEC_SIDEWAYS_RIGHT = B01101001; const byte MEC_SIDEWAYS_LEFT = B10010110; const byte MEC_DIAGONAL_45 = B00101000; const byte MEC_DIAGONAL_135 = B10000010; const byte MEC_DIAGONAL_225 = B00010100; const byte MEC_DIAGONAL_315 = B01000001; const byte MEC_PIVOT_RIGHT_FORWARD = B00100010; const byte MEC_PIVOT_RIGHT_BACKWARD = B00010001; const byte MEC_PIVOT_LEFT_FORWARD = B10001000; const byte MEC_PIVOT_LEFT_BACKWARD = B01000100; const byte MEC_ROTATE_CLOCKWISE = B01100110; const byte MEC_ROTATE_COUNTERCLOCKWISE = B10011001; const byte MEC_PIVOT_SIDEWAYS_FRONT_RIGHT = B01100000; const byte MEC_PIVOT_SIDEWAYS_FRONT_LEFT = B10010000; const byte MEC_PIVOT_SIDEWAYS_REAR_RIGHT = B00001001; const byte MEC_PIVOT_SIDEWAYS_REAR_LEFT = B00000110; // Variable for test time delay int timeDelay = 1000; // PWM Parameters for motor control // PWM Frequency = 1KHz const int mtrPWMFreq = 1000; // PWM Resolution const int mtrPWMResolution = 8; // Define PWM channels for each motor const int mtrRFpwmchannel = 4; const int mtrLFpwmchannel = 5; const int mtrRRpwmchannel = 6; const int mtrLRpwmchannel = 7; // NeoPixel string output pin #define NEOPIX_PORT 5 // Number of NeoPixels in string #define NUMPIXELS 5 // Name each LED for easier reference #define NEO_STATUS 0 #define NEO_RF 1 #define NEO_LF 2 #define NEO_RR 3 #define NEO_LR 4 //LED Color Definitions #define LED_RED 255, 0, 0 #define LED_GREEN 0, 255, 0 #define LED_BLUE 0, 0, 255 #define LED_YELLOW 255, 255, 0 // Create "pixels" object representing NeoPixel string Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIX_PORT, NEO_RGB + NEO_KHZ800); void moveMotors(int speedRF, int speedLF, int speedRR, int speedLR, byte dircontrol) { // Moves all 4 motors // Directions specified in direction byte // Right Front Motor digitalWrite(MF_AI1, bitRead(dircontrol, 7)); digitalWrite(MF_AI2, bitRead(dircontrol, 6)); ledcWrite(mtrRFpwmchannel, abs(speedRF)); // Left Front Motor digitalWrite(MF_BI1, bitRead(dircontrol, 5)); digitalWrite(MF_BI2, bitRead(dircontrol, 4)); ledcWrite(mtrLFpwmchannel, abs(speedLF)); // Right Rear Motor digitalWrite(MR_AI1, bitRead(dircontrol, 3)); digitalWrite(MR_AI2, bitRead(dircontrol, 2)); ledcWrite(mtrRRpwmchannel, abs(speedRR)); // Left Rear Motor digitalWrite(MR_BI1, bitRead(dircontrol, 1)); digitalWrite(MR_BI2, bitRead(dircontrol, 0)); ledcWrite(mtrLRpwmchannel, abs(speedLR)); } void stopMotors() { // Stops all motors and motor controllers ledcWrite(mtrRFpwmchannel, 0); ledcWrite(mtrLFpwmchannel, 0); ledcWrite(mtrRRpwmchannel, 0); ledcWrite(mtrLRpwmchannel, 0); digitalWrite(MF_AI1, 0); digitalWrite(MF_AI2, 0); digitalWrite(MF_BI1, 0); digitalWrite(MF_BI2, 0); digitalWrite(MR_AI1, 0); digitalWrite(MR_AI2, 0); digitalWrite(MR_BI1, 0); digitalWrite(MR_BI2, 0); } void ledMotorStatus(byte dircontrol) { // Sets status LEDs to indicate motor direction // Right front if ((bitRead(dircontrol, 7) == 1) && (bitRead(dircontrol, 6) == 0)) { // Motor moves forward // Turn Right Front NeoPixel BLUE pixels.setPixelColor(NEO_RF, pixels.Color(LED_BLUE)); } else if ((bitRead(dircontrol, 7) == 0) && (bitRead(dircontrol, 6) == 1)) { // Motor moves in reverse // Turn Right Front NeoPixel YELLOW pixels.setPixelColor(NEO_RF, pixels.Color(LED_YELLOW)); } else { // Motor is stopped // Turn Right Front NeoPixel RED pixels.setPixelColor(NEO_RF, pixels.Color(LED_RED)); } // Left front if ((bitRead(dircontrol, 5) == 1) && (bitRead(dircontrol, 4) == 0)) { // Motor moves forward // Turn Left Front NeoPixel BLUE pixels.setPixelColor(NEO_LF, pixels.Color(LED_BLUE)); } else if ((bitRead(dircontrol, 5) == 0) && (bitRead(dircontrol, 4) == 1)) { // Motor moves in reverse // Turn Left Front NeoPixel YELLOW pixels.setPixelColor(NEO_LF, pixels.Color(LED_YELLOW)); } else { // Motor is stopped // Turn left Front NeoPixel RED pixels.setPixelColor(NEO_LF, pixels.Color(LED_RED)); } // Right rear if ((bitRead(dircontrol, 3) == 1) && (bitRead(dircontrol, 2) == 0)) { // Motor moves forward // Turn Right Rear NeoPixel BLUE pixels.setPixelColor(NEO_RR, pixels.Color(LED_BLUE)); } else if ((bitRead(dircontrol, 3) == 0) && (bitRead(dircontrol, 2) == 1)) { // Motor moves in reverse // Turn Right Rear NeoPixel YELLOW pixels.setPixelColor(NEO_RR, pixels.Color(LED_YELLOW)); } else { // Motor is stopped // Turn Right Rear NeoPixel RED pixels.setPixelColor(NEO_RR, pixels.Color(LED_RED)); } // Left rear if ((bitRead(dircontrol, 1) == 1) && (bitRead(dircontrol, 0) == 0)) { // Motor moves forward // Turn Left Rear NeoPixel BLUE pixels.setPixelColor(NEO_LR, pixels.Color(LED_BLUE)); } else if ((bitRead(dircontrol, 1) == 0) && (bitRead(dircontrol, 0) == 1)) { // Motor moves in reverse // Turn Left Rear NeoPixel YELLOW pixels.setPixelColor(NEO_LR, pixels.Color(LED_YELLOW)); } else { // Motor is stopped // Turn Left Rear NeoPixel RED pixels.setPixelColor(NEO_LR, pixels.Color(LED_RED)); } // Update pixels.show(); } void ledTurnOff() { // Turn off all NeoPixels pixels.clear(); pixels.show(); } void ledAllStop() { // Turn all NeoPixels RED pixels.clear(); pixels.setPixelColor(NEO_STATUS, pixels.Color(LED_RED)); pixels.setPixelColor(NEO_RF, pixels.Color(LED_RED)); pixels.setPixelColor(NEO_LF, pixels.Color(LED_RED)); pixels.setPixelColor(NEO_RR, pixels.Color(LED_RED)); pixels.setPixelColor(NEO_LR, pixels.Color(LED_RED)); pixels.show(); } void setup() { // Set up Serial Monitor Serial.begin(9600); // Set all connections as outputs pinMode(MF_PWMA, OUTPUT); pinMode(MF_AI1, OUTPUT); pinMode(MF_AI2, OUTPUT); pinMode(MF_PWMB, OUTPUT); pinMode(MF_BI1, OUTPUT); pinMode(MF_BI2, OUTPUT); pinMode(MR_PWMA, OUTPUT); pinMode(MR_AI1, OUTPUT); pinMode(MR_AI2, OUTPUT); pinMode(MR_PWMB, OUTPUT); pinMode(MR_BI1, OUTPUT); pinMode(MR_BI2, OUTPUT); //Set up PWM channels with frequency and resolution ledcSetup(mtrRFpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrLFpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrRRpwmchannel, mtrPWMFreq, mtrPWMResolution); ledcSetup(mtrLRpwmchannel, mtrPWMFreq, mtrPWMResolution); // Attach channels to PWM output pins ledcAttachPin(MF_PWMA, mtrRFpwmchannel); ledcAttachPin(MF_PWMB, mtrLFpwmchannel); ledcAttachPin(MR_PWMA, mtrRRpwmchannel); ledcAttachPin(MR_PWMB, mtrLRpwmchannel); // Test speed for all motors (change as desired) rf_PWM = 200; // Right Front Motor lf_PWM = 200; // Left Front Motor rr_PWM = 200; // Right Rear Motor lr_PWM = 200; // Left Front Motor // Initialize NeoPixels pixels.begin(); } void loop() { // Cycle through some Mecanum Wheel modes // Make sure we are stopped stopMotors(); ledAllStop(); delay(3000); // Straight Forward Serial.println("Straight Forward"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_STRAIGHT_FORWARD); ledMotorStatus(MEC_STRAIGHT_FORWARD); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Straight Backward Serial.println("Straight Backward"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_STRAIGHT_BACKWARD); ledMotorStatus(MEC_STRAIGHT_BACKWARD); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Sideways Right Serial.println("Sideways Right"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_SIDEWAYS_RIGHT); ledMotorStatus(MEC_SIDEWAYS_RIGHT); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Sideways Left Serial.println("Sideways Left"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_SIDEWAYS_LEFT); ledMotorStatus(MEC_SIDEWAYS_LEFT); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Diagonal 45 Degrees Serial.println("Diagonal 45 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_45); ledMotorStatus(MEC_DIAGONAL_45); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Diagonal 225 Degrees Serial.println("Diagonal 225 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_225); ledMotorStatus(MEC_DIAGONAL_225); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Diagonal 135 Degrees Serial.println("Diagonal 135 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_135); ledMotorStatus(MEC_DIAGONAL_135); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); // Diagonal 315 Degrees Serial.println("Diagonal 315 Degrees"); moveMotors(rf_PWM, lf_PWM, rr_PWM, lr_PWM, MEC_DIAGONAL_315); ledMotorStatus(MEC_DIAGONAL_315); delay(timeDelay); stopMotors(); ledAllStop(); delay(500); } |
The script builds upon the last one, adding the NeoPixel functionality. The LEDs will change color depending on the motor direction:
- Green for Forward
- Blue for Reverse
- Red for Stop
In this sketch, the Status LED will also illuminate when the motor is stopped.
Neopixel Library Concerns
Unfortunately, after I put together the last sketch, I ran into an issue, the Adafruit NeoPixel Library conflicts with the ESP-NOW Library.
Rather than trying to resolve the conflict (which can be done), I decided to look for another NeoPixel library. And I found one, and as a bonus, it’s a lot smaller and faster than its Adafruit counterpart.
The NeoPixelBus Library by Michael C. Miller is also available from your Library Manager. You’ll want to install it in preparation for the final robot code.
Robot Construction
After all of our experimentation, it’s finally time to build our Mecanum Wheel Robot Car!
Any base capable of holding four motors can be used for the car; in my case, I purchased a kit with the wheels and the base. You could also just buy the wheels and use them on an existing 4-wheel robot car base. Or you could be really creative and design your own.
My Robot Build
The base I used has two levels, which I like, as it has a lot of room for expansion.
The bottom level has the motors, as you expect it would, and it could be used alone without a top level (in fact, another model is available without the top level). The chassis has cutouts for speed sensors, and in the future, I might want to add them, so I left it empty.
The only other components mounted on the lower level are the four NeoPixel LEDs, which I mounted on small pieces of perfboard along with their respective 0.1uf filter capacitors.
Everything else is on the top layer. The main circuitry (i.e., the ESP32, motor drivers, and level converter) are mounted on a perfboard. The two 18650 batteries I used are also on the top so that I can easily remove them for recharging – sometime down the road I’ll update the design to include a built-in charger, but not today.
I also included another small perfboard upon which I mounted a power switch and the Status NeoPixel. I used some brass standoffs to raise this to allow the wires from the motors and the front LEDs to run underneath.
Power Hookup
As I just mentioned, I used a couple of 18650 batteries as a power source. These are used to power the motors directly and are also fed into a 5-volt regulator to supply 5 volts for the ESP32 and the NeoPixels.
This is only one of several arrangements you can use to power up your project, there are many different ways of building the robot power supply:
- Use a 6-volt battery pack (such as 4 AA calls) for the motors and a USB power bank for the 5-volt supply.
- Use the same design I used, but replace the buck converter with a Low Dropout (LDO) regulator.
Bottom line – you need to supply your motors with their required voltage (and current), as well as 5 volts for the ESP32 and the NeoPixels.
Controller Construction
Now we will switch our focus to the controller.
Our controller is constructed around a LilyGo TTGO T-Display module. This ESP32 module has an integrated TFT color display, two GPIO-connected pushbuttons, and a connection for a small LiPo battery that can be recharged from the module.
The only other component (besides a power switch for the battery) that we require is a standard analog joystick. These devices have two potentiometers (usually 5 or 10K) for the X and Y axis and a momentary contact pushbutton switch that is activated by pushing down on the joystick handle.
Joystick Functions
The joystick is connected to two of the analog inputs on the ESP32. An analogRead can display each coordinated output in a range of 0 to 255 or 0 to 4095 if using the A/D converter in 10-bit mode.
However, for our purposes, it would be a lot better if we could get the readings on each coordinate to read from -255 to 255, with zero representing the center point, as follows:
This arrangement will allow us to determine not only motor speed requirements but also which of the Mecanum modes we will need to send the car in the direction of the joystick.
The following illustrations show which mode is activated when the joystick is moved into different positions:
This chart summarizes the movements:
Controller Hookup
Since the LilyGo TTGO T-Display module already contains the display, pushbuttons, and battery charger, all that is required is to connect the joystick to the module.
Note that some joysticks have the X and Y axis 90 degrees off of what you would expect, so you may need to reverse those leads.
TFT-eSPI Library Modifications
The sketch we will be writing for the controller will use the TFT-eSPI Library by Bodmer. You can install this library using the Library Manager in the Arduino IDE. You might have already installed it if you followed my instructions in the article about using Round LCD Modules, but you may need to update it, as it needs to be at least version 2.4.79 to work with the TTGO displays.
After installing it, you will need to modify a file in the library to work with the TTGO T-Display. Here is how you do this:
- Navigate to the TFT_eSPI folder in your libraries folder (which usually lives under your Arduino folder).
- Look for User_Setup_Select.h and open it with a text editor.
- Comment out line 30, which reads #include <User_Setup.h>
- Uncomment line 61, which reads #include <User_Setups/Setup25_TTGO_T_Display.h>
- Save the file.
Once you do this, the library will work with the TTGO module.
My Controller Build
Putting together the controller was very simple.
I used the plastic case that the TTGO T-Display module was packaged in as my case. Of course, you could use another case, perhaps even a 3D-printed one.
For a battery, I used a small 800ma LiPo, the type you would use for a small quadcopter. It fits very nicely inside the case.
I used a few nylon standoffs to raise the perfboard high enough to allow the battery leads to connect to the connector on the module.
ESP32 Sketches
Now that all the hardware is assembled, it is time to focus on the software.
Before we can create the scripts that we will need for both the car and its controller, we will need to get one very important piece of information from the ESP32s used in each device – the MAC Address.
MAC Address Script
The Media Access Control, or MAC, Address is a unique address assigned to every network device on the planet. Both ESP32 boards have their own MAC addresses.
The ESP-NOW protocol that we will use to communicate between the devices requires each unit’s MAC Address.
Here is a simple sketch to get the MAC address. You’ll need to run it on both the car and the controller. Make sure to save the results!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* Print ESP32 MAC Address getmac.ino Prints MAC address to serial monitor Use to record address for use with ESP-NOW DroneBot Workshop 2022 https://dronebotworkshop.com */ #include "WiFi.h" void setup(){ Serial.begin(115200); WiFi.mode(WIFI_MODE_STA); Serial.println(WiFi.macAddress()); } void loop(){ // Nothing in the Loop } |
As you can see, it’s a very simple sketch. Run it and look on your serial monitor; the MAC address will be a series of hexadecimal numbers printed at the end of the listing.
Programming Requirements
Now we are ready to build the sketches for both the robot and controller. Let’s see what we want to accomplish here.
As we already know, we want the controller and robot to communicate via ESP-NOW. To do this, they need to set up a “peer-to-peer” network with one another. Each unit will use the other units’ MAC address to do this.
Once the network is set up, we will establish a “data structure” in both directions. This data structure specifies what information we will exchange between units.
The following diagram illustrates this, along with the data that we are exchanging.
Our car can execute all the Mecanum Wheel “modes,” and we can control it using a joystick on the remote. The car will send back information about the speed and direction of the wheels, and we will display this on the TFT screen of our remote.
As you recall, we defined 18 different Mecanum “modes” earlier. We can further divide these into smaller groups, which, to confuse things a bit more, I will also call “modes” as I just can’t think of a better name for them! For each mode, we will also have a color, which will be displayed on both the status NeoPixel LED on the car and as the background color on our controllers’ display.
- Standard Mecanum Mode – The straight, sideways, and diagonal movements. In this mode, we use both the X and Y axis of the joystick to guide the vehicle. This mode has a status color of Light Blue. This is the only mode in which the X-axis of the joystick affects the car.
- Rotate Mode – Pretty self-explanatory; in this mode, the car rotates. The Y-axis of the joystick controls both the speed and the direction of rotation. The status color for this mode is Green.
- Pivot Right Mode – The car will do a right-side pivot, so only the wheels on the right are driven. The mode status color is Orange.
- Pivot Left Mode – This time, the pivot is on the left side. The status color for pivot left is Violet.
- Pivot Front Mode – In this mode, only the front wheels are controlled. Once again, the Y-axis controls the speed and direction of the pivot. The status color for this mode is Pale Blue (which is slightly different from Light Blue, trust me!).
- Pivot Rear Mode – In this final mode, the rear wheels are used to pivot. The status color for this mode is Yellow.
We also have a few other conditions that we can use the status NeoPixel and/or the TFT screen to display:
- No Controller Found – If there is no signal from the controller, the car will have a Red status LED. In addition, all the motor direction NeoPixels will turn Violet.
- No Car Found – If the controller cannot see the car, it will show a red error message on the TFT display.
Robot Sketch
I’m going to break with tradition in this article, and I’m not going to show you all the code, as it is very, very long! Instead, I’ll explain the functions of the individual files. You can download all the code in the Resources section below, and you can also look at the video for a more detailed walk-through.
Because of the length of the code, it is divided into several files. This will make it easier to edit should you wish to expand upon it or improve it.
The files, and their descriptions, are as follows:
- mec-robot-car.ino – The master file for the project. The declarations, the Setup, and the Loop.
- a_car-functions.ino – Functions to move and stop the motors, also functions to control the NeoPixel LEDs.
- b_callbacks.ino – The ESP-NOW callback functions, called when data is sent or received.
- c_mecanum-functions.ino – Functions that toggle and select the Mecanum Mode.
- d_mode-0.ino – The function that drives the car in Standard Mecanum Mode.
- e_mode-1.ino – The function that drives the car in Rotate Mode.
- f_mode-2-5.ino – The functions that drive the car in the Pivot Modes.
Controller Sketch
As with the car, our controller also has its code divided into separate files for ease of use:
- mec-robot-remote.ino – The master file for the project. The declarations, the Setup, and the Loop. Also has two Interrupt Service Routines used with the pushbutton switches.
- a_remote-functions.ino – A function to convert the joystick values to the proper range, allowing for error near the center of travel.
- b_callbacks.ino – The ESP-NOW callback functions, called when data is sent or received.
- c_graphs.ino – Functions to create the two displays (bar graph and car wheels).
- d_screens.ino – Function to display the splash screen, which can also act as an error display screen.
ESP-NOW Communications
The key component in this design is the ESP-NOW peer-to-peer network that is established between the controller and the car. If you want to modify the design and add additional functionality (i.e., sensors, servo control), then you will need to know how to work with this component.
Structured Data
The data that is exchanged between the car and controller resides in a data structure that is defined on both ends.
Here is how it is defined on the car side:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
typedef struct struct_message_rcv { int xAxis; int yAxis; bool pbSwitch; } struct_message_rcv; // Create a structured object for received data struct_message_rcv rcvData; // Define a data structure for sent data typedef struct struct_message_xmit { byte motorMode; int mecanumMode; int mtrRF_PWM; int mtrLF_PWM; int mtrRR_PWM; int mtrLR_PWM; } struct_message_xmit; // Create a structured object for sent data struct_message_xmit xmitData; |
On the controller side, it is the same, except receive and transmit are reversed.
If you wanted to send additional data, you would need to modify the structure on both sides. Keep in mind that the amount of data you can send in a data structure with ESP-NOW is small, so try and reduce your data as much as possible before sending it.
You can learn more about ESP-NOW in the article ESP NOW – Peer to Peer ESP32 Communications.
Callbacks
A key concept with ESP-NOW is callbacks. There are two callback functions, one that is called when data is sent and another that is called when data is received.
On both ends, the receive callback is used to get the data from the data structure and pass it to local variables.
On the car side, the send callback is used to check the status of the sent data. If it has an error, then the car is stopped and goes into an error mode, as this indicates it has lost connection with the controller.
There are no send callback functions for the controller.
Sending Data
On both ends, the data is sent within the loop. It is gathered from local variables, added to the data structure, and sent with this line of code:
1 |
esp_err_t result2 = esp_now_send(broadcastAddress, (uint8_t *)&xmitData, sizeof(xmitData)); |
Testing the Mecanum Wheel Robot & Remote
Once you have all the code loaded, it’s time to give your car a test drive.
Start by turning on the controller, and leave the car off for a moment. The controller should boot up with a splash screen, then it will show a screen saying that ESP-NOW has started. Finally, it will show an error screen indicating that the car cannot be found, which makes sense as it isn’t powered up!
Now turn on the car. It should go through a quick boot sequence and end up with all four motor NeoPixels turning red (which indicates that the motor has stopped) and the status light turning Light Blue, indicating that we are in Standard Mecanum Mode. The controller will be in bar graph mode with a Light Blue background.
Now press down upon the joystick to change Mecanum Modes. Both the car status light and the display background should turn Orange, indicating that we are now in Rotate Mode.
You can cycle through all the other modes, observing both the status LED and the background color on the controller.
Now we can drive the car! I suggest starting in Standard Mecanum Mode. Use the joystick to maneuver the car and observe the display on the controller, as well as the motor LEDs. Try changing the display to “car mode” and see how it correlates with the car LEDs.
You’ll probably find that driving the car is addictive, so be sure to keep your batteries charged!
Conclusion
Mecanum wheels have many practical uses. Aside from that, they are fun to work with.
You can certainly expand upon this design; in fact, I intend to expand it to include speed sensors soon. I’ll post an update when I do.
So have fun driving in every direction!
Parts List
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
- LilyGo TTGO ESP32 – Amazon
- Espresif ESP32 DEVKIT C – Digikey
- TB6612FNG Motor Driver – Sparkfun
- Mecanum Car Base – Abra Electronics
Resources
All the Code – The code for the robot car and the remote control, as well as the test code discussed in this article
Bill, can you make all of your articles available in PDF format along with this web-based format? I don’t always have a decent Internet connection out in my workshop (which is in another location) and a PDF would allow me to “play along at home” when I have poor connectivity. Thank you very much!
I’m updating teh site in a few weeks, and PDF documents (and parts lists) are on the agenda!
thank you, dear
Hi Bill,
What ESP32-wroom model are you using?
d,n or ve?
Thanks
Hi Steven
It’s an ESP32_DevKitC_V4, which uses the ESP32-WROVER-E chip.
Nothing in the design is specific to this module, so you could probably use any ESP32 board.
Thank you. I know your busy so I will refer to Forum.
look forward to the updated version.
Could not flash the remote software to the TTGO using Arduino 2.03 version, used 1.8.19 and it then would turn on the display. Great project.
Sir, I just can’t thank you enough for hosting this tutorial. It has solved all the headaches I am having on designing my robot car. Now my work is so much easier. Please stay well.
Hi sir
I am a subscriber of your channel and I really appreciate your work. One of the major problems I face is sourcing of items like the nodemcu resisters capacitors etc . Can you please provide links where you source components from.
Hi Bill. This is a wonderful build. I noticed at the 41:30-minute mark in your video explaining this project you show several filter capacitors (5 of them). Two of them are across the motor voltage line. Another capacitor is across the motor voltage near the power input.
The last two are across the 5 V line (I’m assuming those are for the logic level converter and the ESP32 itself). Could you perhaps tell me the values you used for the capacitors? They look fairly hefty. Thank you.
I would love to know the values used as well. My build keeps dropping connection if I go full throttle too quick. I put a 16V 470 micro fared across the 5V going to the ESP32, but that didn’t seem to help. I searched online and couldn’t really find a good explanation of how to size a filter capacitor… so possibly a new video idea?
When it comes to filter caps bigger is often better (within reason). I don’t think it’d be unreasonable to go as high as 10000 uF but I’d use 3300uF minimum. It’s possible to find electrolytic caps rated at 6.3V, 10000uF that are quite compact. You should also add a low ESR film cap in the range 0.1-1uF in parallel with your electrolytic reservoir cap to handle the high frequency transients. Finally, be sure to connect your caps as directly to the ESP32 as possible and to the 3.3V pin instead of the 5V pin since the chip itself is running… Read more »
This is a great project. I have bought chasse and wheels and have most of the other parts. I like to fritzes my projects but can’t fine the parts for fritzing you use. Is there a way to get them?
Hi Bill, what program language you use? Please let me know.
Thanks
Francisco Dourado
Arduino uses a variant of the C++ programming language.
Hi Bill, this is a great project and so are the other ones that you have been hosting. I just received (almost) all the components to get this project moving with my 9yr old son. We have a question on the LED in use. You have indicated Adafruit LED 8mm – can I use 5mm RGB leds? are the pins similar ? unfortunately I dont have a data sheet.
Please disregard above comment – I rewatched the video and you have already addressed why it is important to use Neopixel as against generic 5mm RGB.
It would really help if you could list the parts along with a link – some of the parts such as the motorshield, Logic level converter and buck converters have too many choices on Amazon. Thanks
I bought one of these kits off Amazon for myself Christmas this past year..
Am just now finishing mine up. Wonder project idea.
I cant pump out projects, let alone fully edited hour (plus) long videos, as fast as you. 😉
This was a lot of work, and I appreciate the information. I have ordered the parts. I plan to put FPV Camera Foxeer Micro Cat 3 Night Camera Low latency 1200TVL OSD, InfiRay Thermal Camera, and GPS and convert some of the code to use a Radio Master 16S TX and Receiver. I have plans for this equipment.
Using this information to integrate the camera with the remote control vehicle.
DIY Thermal Cameras for Drones, RC & FPV – Tutorial, Schematics, Tests – drones thermal imaging
Good morning, Bill. I am from Brazil. I would like to ask you a question. I'm new to this area of robotics and electronics. The question is: why did you place the negative on the project chassis? Is the battery no longer negative? Can I connect the battery negative to the same wiring as the chassis negative? Could you explain it to me? Thanks in advance. Att, 123 Good morning, Bill. I am from Brazil. I would like to ask you a question. I'm new to this area of robotics and electronics. The question is: why did you place the… Read more »
Hi,
I am trying to get this working using a HUZZAH feather or ESP32 devkit V1, the only ESP’s I have.
For some reason neither recognises analogWrite(), any idea what I can use instead?
Hi, Bill, Not sure if you monitor the comments on old projects, but still… First – thank you for the wonderful site and videos! I enjoy following and building some of the projects as well as adopting them to my own needs. Is there any way to get the full parts list you used on this one, like what capacitors and where you added? Also, detailed pictures of the two levels of build car would be very helpful. I am not an electrical engineer, just learning this stuff from examples. This level of details would be very helpful. Thank you… Read more »
THANK YOU SO MUCH for your in depth tutorials! Im an old Mech Engr who helps kids with technology. My robotics club built one of these and it works pretty well, Im working on a PCB for and ESP32DevKit, and 3 TB6612, so we might be able to also run some sort of auxiliary motor(s) and I am struggling with IO Pins. Where can I learn more about them?? It looks like GPIO 34, 35, 36, 39 are input only? GPIO 21, 23 i2c. GPIO 1, 3 are UART TxRx, GPIO 19,23 are MISO/ MOSI. So, maybe there is not… Read more »
Well, I should have know that you, sir, would have done a good tut on ESP32 – I think I scanned it some time ago. thank you… https://dronebotworkshop.com/esp32-2024/