Hey, I don't know, if it is just me or what?.I never get satisfied with anything I have built. So, technically speaking, every project is in my "junk drawer" 🫣. After working on My project A2R3 Robotic platform then, the motivational "junk drawer" challenge appeared on hackster.io . Chi...ching...it is time to clean up some junk with motivation. Yolu is based on the A2R3 platform but with some personality.
It wonders around the house, makes some noises, show some moods and attitudes.
My A2R3 project has evolved so much and I am building it in parts (A2R3-1 , A2R3-2 , A2R3-3 coming soon...). Each part has been a learning experience as well as a fun one.
Yolu's anatomyHere is Yolu's design and each CAD parts preview. You can find the full CAD and every 3D parts down below in the attachment section.
Yolu had gone through some evolutions and here you can find detailed build versions:
In the above links you can find, improvements I have made, and challenges I have faced with detailed document supported with pictures and videos.
Now let's have some fun...
Yolu's Personality and featuresYolu is a bot with moods and moves. It is packed with sensors, actuators, and 20V 4.5Ah juicy lithium-ion battery.
Motor ControlThe motors are controlled using the TB6612_ESP32 library, enabling movements like straight driving, backward movement, and turns.Dynamic behaviors like zigzag patterns and dramatic spins add personality to the robot. The core hardware motor driver TB6612FNG is awesome in both power saving and efficiency.
OLED DisplayAn SSD1306 OLED screen displays the system's status, including TOF distance, obstacle detection, and mood state. Text animations enhance the user experience.
SensorsVL53L0X Time-of-Flight Sensor: Measures distances and detects obstacles. The system reacts when objects are closer than the defined threshold.
MPU6050: Detects if the robot is stuck based on accelerometer readings. Here is the function to detect this situation:
void stuckDetectionTask(void *parameter) {
for (;;) {
tca9548aSelect(7);
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
if (abs(ax) > 1000 || abs(ay) > 1000 || abs(az) > 1000) {
lastMovementTime = millis();
stuckDetected = false;
} else if (millis() - lastMovementTime > 2000) {
stuckDetected = true;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
If there is a reading in the gyro x, y, z that is at least greater than 1000 then the robot is moving else wait for 2000ms (2 seconds) then flag the robot is "StuckDetected". That is a very simple algorithm I used for stuck detection and it simply works every time.
Mood SystemA random mood generator changes the robot's behavior dynamically.
this is the function that handles all moods of the robot and is driven by the random number generator function random (0, 4); and the switch statement routes to different mood routine functions.
void handleMood() {
if (resumingRoutine) {
currentMood = previousMood; // Resume the previous mood
resumingRoutine = false; // Reset flag
} else {
previousMood = currentMood; // Save the current mood
currentMood = static_cast<Mood>(random(0, 4)); // Randomize new mood
}
switch (currentMood) {
case HAPPY:
excitedRoutine();
break;
case CURIOUS:
curiousRoutine();
break;
case BORED:
OLED_AnimateText("Hmmm...");
break;
case EXCITED:
dynamicZigzag();
break;
}
}
Specific routines like "curious" and "excited" add a playful element.
//Mood States
enum Mood { HAPPY, CURIOUS, BORED, EXCITED };
Mood currentMood = HAPPY;
Buzzer soundTones and melodies signal different events, like successful setup, obstacle detection, and mood-based actions.
// Function to play tones
void playSuccessTone() { tone(BUZZER_PIN, 2000, 100); delay(150); tone(BUZZER_PIN, 2500, 100); }
void playObstacleTone() { tone(BUZZER_PIN, 1500, 50); delay(100); }
void playStuckTone() { tone(BUZZER_PIN, 1000, 200); delay(200); tone(BUZZER_PIN, 1200, 200); }
void playHappyMelody() { tone(BUZZER_PIN, 500, 200); delay(250); tone(BUZZER_PIN, 700, 200); delay(250); tone(BUZZER_PIN, 900, 200); delay(250); }
void playCuriousBeeps() { for (int i = 0; i < 3; i++) { tone(BUZZER_PIN, random(1000, 1500), 100); delay(150); } }
I used the built in function tone(pin, frequency, delay) to play and make some noises through a buzzer connected to pin 15 of the robot esp32 ( refere the schematic below). So the tone function accepts pin number, the PWM frequency at which the tone sounds and the length of time the tone plays at the last parameter. Simply Playing with this values will give you an interesting sounds that is robotic.
FreeRTOS IntegrationTasks are efficiently divided:
Sensor Task: Reads distance measurements. This FreeRTOS task takes care of sensor reading from the TOF VL53L0X sensor. If the measurement is less than the threshold value in this case 500 mm then obstacle is detected do a random turn or left. This task is given a priority and is always in a thread without blocking any other actions of the robot by FreeRTOS.
void sensorTask(void *parameter) {
for (;;) {
measurement = sensor.readRangeSingleMillimeters();
if (sensor.timeoutOccurred()) {
OLED_AnimateText("Sensor Timeout");
continue;
}
obstacleDetected = (measurement < DISTANCE_THRESHOLD);
vTaskDelay(50 / portTICK_PERIOD_MS); // Ensure task runs periodically
}
}
Motor Control Task: Drives behavior and reacts to obstacles. This function is also handled by FreeRTOS and its primary function is to control the movement of the robot. If not obstacle is detected the the robot moves in straight line then randomly changes mood and "handlemood" function that I have described before will run otherwise (obstacle is detected then the robot stops, plays tone, moves backward a bit and does the "turnRandomDirection" routine. Of course in every situiation the robot let you know what it is up too by displaying quick info on the OLED screen.
void motorControlTask(void *parameter) {
unsigned long lastPersonalityTime = millis();
for (;;) {
if (stuckDetected) {
stopMotors();
OLED_AnimateText("Recovering from Stuck!");
playStuckTone();
moveBackward();
turnRandomDirection();
stuckDetected = false; // Reset stuck flag
resumingRoutine = true; // Mark to resume the previous routine
} else if (obstacleDetected) {
stopMotors();
playObstacleTone();
moveBackward();
turnRandomDirection();
OLED_AnimateText("Obstacle Avoided!");
resumingRoutine = true;
} else {
if (resumingRoutine) {
handleMood(); // Resume the mood routine
resumingRoutine = false;
} else {
moveStraight(speed);
if (millis() - lastPersonalityTime > random(5000, 10000)) {
stopMotors();
handleMood();
lastPersonalityTime = millis();
}
}
}
OLED_DisplayStatus(); // Update OLED display
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
Stuck Detection Task: Monitors for a lack of motion. I have already described the function of this task in the sensors section. It is also handled by the freeRTOS which is the OS of the ESP32.
Multiplexer SupportThe TCA9548A I2C multiplexer ensures seamless communication with multiple I2C devices. Yolu uses different I2C modules even if the ones I used for this project are unique in terms of I2C address. It would not hurt to run them over a multiplexer for resolving address conflict. For example: let us just say, n the future we needed to add more TOF sensors then same TOF sensors with same address conflict. So this solves the problem.
Demo timeOutsideYolu wondered too much and went outside too. I am amazed by how much tire grip and motor's torque it has in the snow. Not bad! for such a low cost budget project!.
To be continue . . .
Comments
Please log in or sign up to comment.