Hand Gesture Controlled Car
For this project, we have designed an Arm Gesture Controlled Bot Car with wireless transmission capabilities using ESP32 technology. The car is capable of responding to predetermined hand gestures, allowing you to control its movements with ease.
Supplies
- ESP32 Module x2
- L298N Motor Driver x1
- MPU6050 x1
- 3.7V Lithium Ion Battery x2
- DC Gearbox Motor x2
- Wheels for DC Gearbox Motors x2
- Breadboard
- Caster Bearing Wheel x1
- 12V DC Battery x1
Setting Up Arduino IDE for ESP32
The ESP32 is a powerful and versatile wireless module designed for Internet of Things (IoT) applications. It is based on the ESP32 microcontroller, which features dual-core processing, Wi-Fi, Bluetooth, and various peripheral interfaces. The ESP32 module is widely used in projects that require wireless connectivity, such as home automation, industrial automation, and remote sensing. It is easy to program and can be programmed using a variety of languages, including C++, Python, and Lua. The ESP32 is also known for its low power consumption, making it ideal for battery-powered applications.
- Firstly, you need to download and install the Arduino IDE software on your computer. You may choose any version of the software, but we have used Arduino IDE 2.1.0 for this project.
- Once installed, open the Arduino IDE and navigate to the "File" menu. Select "Preferences" from the drop-down list.
- In the "Additional Boards Manager URLs" section, paste the following URL:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- If you already have another URL, you can separate them using a comma. Alternatively, you can open the windowed interface and add the link on a new line.
- Next, to download the ESP32 board, go to "Tools" -> "Board:" -> "Boards Manager..." and search for "ESP32" (the board manager interface will differ depending on the version of Arduino IDE you are using).
- Install the "ESP32 by Espressif Systems" board.
- After the installation is complete, go to "Tools" -> "Board:" -> "esp32" -> "ESP32 Dev Module" (or select your specific ESP32 module, if you have one).
- Now that the board has been selected, you can proceed to install the libraries required for this project. Go to "Tools" -> "Manage libraries..." and search for the "MPU6050_light" library by rfetick. Install it with all the dependencies.
If you find that the "Tools" -> "Port" option is still blocked even after connecting the ESP32 module, it could be due to missing drivers for serial communication.
Specifically, you may need to install the CP210x driver, which is used for USB-to-serial bridge communication.
Getting the MAC Address of ESP32
In this project, we have implemented wireless transmission of data using the ESP-NOW protocol.
ESP-NOW is a high-speed, low-power wireless communication protocol designed for IoT devices, particularly those based on the ESP32 module. With ESP-NOW, two or more ESP32 modules can communicate with each other directly, without the need for a Wi-Fi network or an access point.
In a one-way communication setup, one ESP32 is configured as the sender and the other as the receiver. The sender sends data packets to the receiver, which receives and processes them. To establish a connection between the two ESP32 modules, they must be paired using the ESP-NOW API, and a unique key must be shared between them. Once the pairing is established, the sender can start sending data packets to the receiver using the ESP-NOW API. The receiver can then process the data packets as needed.
One-way communication using ESP-NOW is helpful in many IoT applications, such as remote sensing, home automation, and industrial automation, where low-power, high-speed communication is required between devices without the need for a Wi-Fi network.
To establish a connection between two ESP32 modules, you need to know the MAC address of the receiver ESP32. You can obtain the MAC address by following these steps:
- Connect the ESP32 module to your computer using a USB-A to Micro USB cable.
- Open the Arduino IDE and create a new sketch.
- Upload the following sketch to the ESP32 board and open the serial monitor with a baud rate of 115200. The MAC address of the ESP32 module will be displayed on the serial monitor.
#include "WiFi.h"
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
Serial.print("MAC Address: ");
Serial.print(WiFi.macAddress());
}
void loop() {
}
Code Explanation: This code snippet is written in the Arduino programming language. Let's go through the code step by step:
- This line includes the necessary library "WiFi.h" for working with Wi-Fi functionality on the ESP32 board.
#include "WiFi.h"
- The setup() function is a special function that is executed only once when the board starts up or is reset. It is used for initializing variables, setting the board's modes, and configuring any necessary settings.
Serial.begin(115200);
- This statement initializes the serial communication at a baud rate of 115200 bits per second. This allows the Arduino to communicate with other devices, such as a computer, over the serial port.
WiFi.mode(WIFI_STA);
- It sets the Wi-Fi mode to station mode, which means the ESP32 will connect to an existing Wi-Fi network as a client.
WiFi.macAddress();
- This code retrieves the MAC address of the Wi-Fi module. The WiFi.macAddress() function returns a string representing the MAC address of the Wi-Fi module.
Serial.print("MAC Address: ");
Serial.print(WiFi.macAddress());
- This piece of code prints the text "MAC Address: " followed by the MAC address of the connected ESP32 board on the Serial monitor.
- The loop() function is another special function that runs repeatedly after the setup() function. In this code snippet, the loop() function is empty and does not contain any code.
Setting Up the Remote Controller (Transmitter)
The MPU6050 module is utilized to obtain gyroscope readings and determine the angles of the hand.
The MPU6050 is a compact 6-axis accelerometer and gyroscope sensor module with a built-in I2C interface. It can detect linear and rotational motion in three axes (x, y, and z) and is commonly used in motion sensing and orientation tracking applications. It is accurate, reliable, low-power, and can be easily integrated with microcontrollers like Arduino, ESP32, and Raspberry Pi.
For this project, we have performed data processing on the transmitter side. The MPU6050 gyroscope is employed to sense the roll and pitch of the hand, which are then processed into four boolean variables based on predetermined actions such as downward, upward, right, and left tilts. These boolean variables dictate the movement of the car, determining whether it moves forward, backward, turns left, or turns right
To make the remote transmitter, follow these steps:
- Connect the MPU6050 sensor module to the ESP32 module using jumper wires. You will only need to connect four pins of the MPU6050 module for this project, which are:
- VCC pin to the 3.3V pin on the ESP32 (3v3).
- GND pin to any of the four GND pins on the ESP32.
- SDA pin to GPIO 21 pin on the ESP32 (G21 or D21).
- SCL pin to GPIO 22 pin on the ESP32 (G22 or D22).
- (Note that the pin configuration may differ from the image, but the pinout will remain the same for every module)
- Connect the ESP32 module to your computer and upload the code to get raw data (roll and pitch) from the MPU6050, process it into its intended form, and transmit it to the added peer (receiving ESP32) over Wifi using the ESP-NOW Protocol.
//Include Libraries
#include "esp_now.h"
#include "WiFi.h"
#include "Wire.h"
#include "MPU6050_light.h"
//Reciever MAC Address
uint8_t broadcastAddress[] = {0xA0, 0xB7, 0x65, 0xDC, 0x4C, 0x38};
//Struct to send data
typedef struct{
bool f;
bool b;
bool l;
bool r;
} message;
message data;
//Create an object of predefined class MPU6050
MPU6050 mpu(Wire);
//Initialize global variables
unsigned long timer = 0;
float x, y;
bool front = false, back = false, left = false, right = false;
//Initialize a peer
esp_now_peer_info_t peerInfo;
//function to be called upon callback
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup() {
Serial.begin(9600);
WiFi.mode(WIFI_STA);
//initialize esp-now
if (esp_now_init() != 0){
Serial.println("Error Initializing ESP NOW");
return;
}
//Register callback function
esp_now_register_send_cb(OnDataSent);
//Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
//Add peer
if (esp_now_add_peer(&peerInfo) != 0){
Serial.println("Failed to add peer");
return;
}
// code for mpu6050 setup
Wire.setPins(21, 22);
Wire.begin();
byte status = mpu.begin();
Serial.print(F("MPU6050 status: "));
Serial.println(status);
while(status!=0){ } // stop everything if could not connect to MPU6050
Serial.println(F("Calculating offsets, do not move MPU6050"));
delay(1000);
mpu.calcOffsets(); // gyro and accelero
Serial.println("Done!\n");
}
void loop() {
//get data from mpu6050
mpu.update();
if((millis()-timer)>10){ //get data every 10ms
x = mpu.getAngleX();
y = mpu.getAngleY();
//process data
if(x >= 30)
back = true;
else
back = false;
if(x <= -30)
front = true;
else
front = false;
if(y >= 30)
right = true;
else
right = false;
if(y <= -30)
left = true;
else
left = false;
timer = millis();
data.f = front;
data.b = back;
data.l = left;
data.r = right;
//Send message
esp_now_send(broadcastAddress, (uint8_t *) &data, sizeof(data));
}
}
In addition, to power the ESP32 module, two 3.7V Lithium Ion batteries are connected to the Vin pin and grounded.
Code Explanation:
#include "esp_now.h"
#include "WiFi.h"
#include "Wire.h"
#include "MPU6050_light.h"
- This code block includes the necessary libraries for the program. It includes the "esp_now.h" library for ESP-NOW functionality, the "WiFi.h" library for configuring Wi-Fi mode, the "Wire.h" library for I2C communication, and the "MPU6050_light.h" library for interfacing with the MPU6050 sensor.
uint8_t broadcastAddress[] = {0xA0, 0xB7, 0x65, 0xDC, 0x4C, 0x38};
- This code block declares and initializes an array named `broadcastAddress` with the MAC address of the receiver device. The MAC address is a unique identifier for each device on a network.
typedef struct{
bool f;
bool b;
bool l;
bool r;
} message;
message data;
- This code block defines a structure named `message` that represents the data to be sent. The structure has four boolean fields: `f`, `b`, `l`, and `r`, which represent forward, backward, left, and right directions respectively. An instance of the `message` struct named `data` is also declared.
MPU6050 mpu(Wire);
unsigned long timer = 0;
float x, y;
bool front = false, back = false, left = false, right = false;
- This code block declares variables used for the MPU6050 sensor and other variables. It creates an instance of the `MPU6050` class named `mpu` with the `Wire` object for I2C communication. It also declares variables `timer`, `x`, `y`, `front`, `back`, `left`, and `right` used for storing sensor data and movement direction flags.
esp_now_peer_info_t peerInfo;
- This code block declares a variable `peerInfo` of type `esp_now_peer_info_t`. This structure is used to hold information about the peer device, including its MAC address, communication channel, and encryption settings.
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
- This code block defines a function named `OnDataSent` that serves as a callback function. It is called after a data packet is sent using ESP-NOW. The function takes two arguments: `mac_addr`, which is the MAC address of the recipient device, and `status`, which indicates the status of the data transmission. It prints the status of the transmission using `Serial.println()`.
- The Serial print statements are used for debugging purposes. It helps in determining whether the data is transmitted to the Receiving ESP32 Successfully.
Serial.begin(9600);
WiFi.mode(WIFI_STA);
- This code initializes serial communication with a baud rate of 9600, allowing communication with the computer via the Serial Monitor. It also sets the Wi-Fi mode to station mode, indicating that the ESP device will connect to an existing Wi-Fi network as a client.
if (esp_now_init() != 0){
Serial.println("Error Initializing ESP NOW");
return;
}
- This code initializes the ESP-NOW protocol. If the initialization fails (returns a non-zero value), it means there was an error, and the code prints an error message and exits the `setup()` function.
esp_now_register_send_cb(OnDataSent);
- This code registers the `OnDataSent()` function as the callback for ESP-NOW events. This function will be called when a data packet is sent and provides information about the status of the transmission.
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
- This code sets up the peer information for ESP-NOW communication. It copies the MAC address of the recipient device from `broadcastAddress` to `peerInfo.peer_addr`. It sets the communication channel to 0, indicating the default channel, and disables encryption by setting `peerInfo.encrypt` to `false`.
if (esp_now_add_peer(&peerInfo) != 0){
Serial.println("Failed to add peer");
return;
}
- This code adds the peer for ESP-NOW communication using `esp_now_add_peer()`. It passes the `peerInfo` structure to specify the details of the peer. If adding the peer fails (returns a non-zero value), it means there was an error, and the code prints a failure message and exits the `setup()` function.
Wire.setPins(21, 22);
Wire.begin();
byte status = mpu.begin();
Serial.print(F("MPU6050 status: "));
Serial.println(status);
while(status!=0){ }
Serial.println(F("Calculating offsets, do not move MPU6050"));
delay(1000);
mpu.calcOffsets();
Serial.println("Done!\n");
- This code sets up the I2C pins for the MPU6050 sensor using `Wire.setPins()` and initializes the I2C communication using `Wire.begin()`. It then initializes the MPU6050 sensor using `mpu.begin()`, which returns a status value. The status is printed to the Serial Monitor for debugging purposes. The code then waits in a loop until the sensor is successfully connected (status is 0). After that, it prints a message to indicate that the offsets calculation is starting and adds a delay of 1 second. Finally, it calls `mpu.calcOffsets()` to calculate the gyro and accelerometer offsets of the MPU6050 sensor.
Here's the explanation of the main `loop()` part:
mpu.update();
- This line updates the data from the MPU6050 sensor.
if((millis()-timer)>10){
//get data every 10ms
x = mpu.getAngleX();
y = mpu.getAngleY();
}
- This code block checks if 10 milliseconds have passed since the last data transmission. If the condition is true, it proceeds to process retrieve the angles in the X and Y directions from the MPU6050 sensor.
if(x >= 30)
back = true;
else
back = false;
if(x <= -30)
front = true;
else
front = false;
if(y >= 30)
right = true;
else
right = false;
if(y <= -30)
left = true;
else
left = false;
- Based on the angles, it sets the boolean variables `front`, `back`, `left`, and `right` to `true` or `false` to indicate the direction of movement.
timer = millis();
data.f = front;
data.b = back;
data.l = left;
data.r = right;
esp_now_send(broadcastAddress, (uint8_t *) &data, sizeof(data));
- This code updates the `timer` variable with the current timestamp, assigns the direction values to the corresponding fields in the `data` struct, and then sends the `data` struct wirelessly using the ESP-NOW protocol to the recipient device specified by `broadcastAddress`..
Setting Up the Car (Receiver)
As the data processing is done on the transmitter side itself, the only thing left to do on the receiver side is to control the actuators (motors) according to the data input. The ESP32 module operating on 3.3V cannot directly power the DC motors, an external power supply, and the L298N Motor Driver is used.
The L298N is a popular dual H-bridge motor driver module used to control the speed and direction of DC motors and stepper motors. It has two H-bridge circuits that can control two DC motors or one stepper motor, with a maximum current of 2A per channel. The L298N module has a built-in voltage regulator that can accept a wide range of input voltages (up to 46V) and provide a stable 5V output. It is commonly used in robotics, automation, and other projects that require motor control.
- +5V socket is connected to the Vin pin or V5 pin on the ESP32 module.
- GND socket is commonly grounded with the Negative terminal of the external 12V supply through the GND pin on the ESP32 module.
- +12V socket is connected to the Positive terminal of the External 12V supply.
- OUT1 and OUT2 are connected to the two terminals of the first motor.
- OUT3 and OUT4 are connected to the two terminals of the second motor.
- The ENA pin is connected to GPIO 5 pin (G5 or D5).
- IN1 and IN2 pins are connected to GPIO 18 and GPIO 19 pins respectively.
- The ENB pin is connected to GPIO 33 pin (G33 or D33).
- IN1 and IN2 pins are connected to GPIO 23 and GPIO 32 pins respectively.
The ENA and ENB pins must be PWM enabled for the code to function properly. If the pins are modified, the code must be adjusted accordingly.
The following code is loaded onto the ESP32 module to accept data from the transmitting ESP32 and direct the two motors based on the received data.
//Include Libraries
#include "esp_now.h"
#include "WiFi.h"
//Set Motor PINs
#define ENA 5 //PWM Enabled
#define IN1 18
#define IN2 19
#define IN3 23
#define IN4 32
#define ENB 33 //PWM Enabled
//Initialize global variables
bool front = 0;
bool back = 0;
bool left = 0;
bool right = 0;
//Struct to recieve data (same as on transmitter side)
typedef struct{
bool f;
bool b;
bool l;
bool r;
} message;
message data;
//Function to be called on callback
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len){
memcpy(&data, incomingData, sizeof(data));
front = data.f;
back = data.b;
left = data.l;
right = data.r;
}
void setup() {
//Initialize pins
pinMode(ENA, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENB, OUTPUT);
WiFi.mode(WIFI_STA);
//Initialize esp-now
if (esp_now_init() != ESP_OK){
return;
}
//Register callback function
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
if (front == 1) {
carforward();
}
else if (back == 1) {
carbackward();
}
else if (left == 1) {
carturnleft();
}
else if (right == 1) {
carturnright();
}
else if (front == 0 && back == 0 && left == 0 && right == 0) {
carStop();
}
}
//Functions for specific movements
void carforward() {
analogWrite(ENA, 255);
analogWrite(ENB, 255);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void carbackward() {
analogWrite(ENA, 255);
analogWrite(ENB, 255);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void carturnleft() {
analogWrite(ENA, 150);
analogWrite(ENB, 150);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void carturnright() {
analogWrite(ENA, 150);
analogWrite(ENB, 150);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void carStop() {
analogWrite(ENA, 0);
analogWrite(ENB, 0);
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
Code Explanation: This code is written for controlling a car using ESP32 microcontroller with the help of ESP-NOW communication protocol.
#include "esp_now.h"
#include "WiFi.h"
- The code includes the necessary libraries for ESP-NOW communication and Wi-Fi functionality.
#define ENA 5 //PWM Enabled
#define IN1 18
#define IN2 19
#define IN3 23
#define IN4 32
#define ENB 33 //PWM Enabled
- These lines define the GPIO pins that are connected to the motor driver to control the motors. ENA and ENB are the PWM-enabled pins for controlling the speed of the motors, while IN1, IN2, IN3, and IN4 are the direction control pins.
bool front = 0;
bool back = 0;
bool left = 0;
bool right = 0;
- These variables are used to store the current state of the car's movement. Each variable represents a specific direction (front, back, left, right) and is initially set to 0 (false).
typedef struct {
bool f;
bool b;
bool l;
bool r;
} message;
message data;
- This defines a structure `message` that will be used to receive data from the transmitter. It contains four boolean variables representing the states of the four directions (front, back, left, right). The variable `data` of type `message` is used to store the received data.
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
memcpy(&data, incomingData, sizeof(data));
front = data.f;
back = data.b;
left = data.l;
right = data.r;
}
- This function is a callback function that gets called whenever data is received from the transmitter. It copies the received data into the `data` variable and updates the global variables `front`, `back`, `left`, and `right` accordingly.
The setup() function performs the following tasks:
pinMode(ENA, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENB, OUTPUT);
- These lines of code set the GPIO pins connected to the motor driver as output pins using the `pinMode()` function. These pins control the speed and direction of the motors.
WiFi.mode(WIFI_STA);
- This line configures the ESP32 module to operate in Station mode, which allows it to connect to an existing Wi-Fi network as a client.
if (esp_now_init() != ESP_OK) {
return;
}
- This code initializes the ESP-NOW communication protocol using the `esp_now_init()` function. It checks if the initialization is successful by comparing the return value with `ESP_OK` (ESP_OK is defined with the value 0). If the initialization fails, the function returns early.
esp_now_register_recv_cb(OnDataRecv);
- This line registers the `OnDataRecv()` function as the callback function to handle received data. The callback function is invoked whenever data is received over the ESP-NOW network.
The loop() function continuously executes the following logic:
if (front == 1) {
carforward();
} else if (back == 1) {
carbackward();
} else if (left == 1) {
carturnleft();
} else if (right == 1) {
carturnright();
} else if (front == 0 && back == 0 && left == 0 && right == 0) {
carStop();
}
The code block uses conditional statements (if, else if) to check the values of the global variables `front`, `back`, `left`, and `right`. Based on the values, specific functions are called to control the car's movement.
- If `front` is equal to 1, the `carforward()` function is called to move the car forward.
- If `back` is equal to 1, the `carbackward()` function is called to move the car backward.
- If `left` is equal to 1, the `carturnleft()` function is called to make the car turn left.
- If `right` is equal to 1, the `carturnright()` function is called to make the car turn right.
- If all the variables (`front`, `back`, `left`, `right`) are equal to 0, the `carStop()` function is called to stop the car.
This logic allows the car to respond to the received data and perform the corresponding movements. The loop() function repeats this logic continuously, ensuring that the car's movement is updated based on the received data in real-time.
The subsequent code contains several functions that control the specific movements of the car.
void carforward() {
analogWrite(ENA, 255);
analogWrite(ENB, 255);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
- This function is responsible for moving the car forward. It sets the motor speeds to maximum by calling analogWrite() with a value of 255 for both ENA and ENB. It also sets the direction control pins (IN1, IN2, IN3, IN4) to the appropriate logic levels to drive the motors in the forward direction.
void carbackward() {
analogWrite(ENA, 255);
analogWrite(ENB, 255);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
- This function is responsible for moving the car backward. Similar to carforward(), it sets the motor speeds to maximum and sets the direction control pins in a different configuration to drive the motors in the backward direction.
void carturnleft() {
analogWrite(ENA, 150);
analogWrite(ENB, 150);
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
- This function is responsible for making the car turn left. It reduces the motor speeds to a value of 150 using analogWrite() to reduce the turning speed. It then sets the direction control pins in a configuration that turns the car to the left.
void carturnright() {
analogWrite(ENA, 150);
analogWrite(ENB, 150);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
- This function is responsible for making the car turn right. It follows a similar structure to carturnleft(), but the direction control pins are set in a configuration that turns the car to the right.
void carStop() {
analogWrite(ENA, 0);
analogWrite(ENB, 0);
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
- This function is responsible for stopping the car. It sets the motor speeds to 0 by calling analogWrite() with a value of 0 for both ENA and ENB. Additionally, it sets all the direction control pins to a logic level that stops the motors.
These functions provide a convenient way to control the car's movements by manipulating the motor speeds and direction control pins based on the desired action.