Emotibot Midterm
Concept:
With a new update, my phone now has the function to send so called bitmojis that will recognize my face expression and turn it into an avatar so that I can express my feelings in an image rather than in words. Even though the avatar looks vaguely like me, it is actually an animated cartoon. I found it fascinating how humans so easily “recognize” emotional expressions in non-human things. This was the inspiration for my midterm project, in which I aim to create a physical face that can express a variety of human emotions without looking distinctively human, but more like a robot.
First idea:
My plan was to recreate the three main components of a human face: Eyes, mouth, and eyebrows. Initially I wanted to control the eyebrows using servos, eyes using motors, and the mouth using an array of LEDs. However, I discovered the LCD display and thought it would be better to represent the mouth using that, as the problem with the LEDs is also that they can still be seen even when they are off, which would not fit the purpose. At this point, I had no idea how much time and effort it would take me to display three different but simple mouth images.
I found this very helpful tutorial to use the LCD display here:
https://lastminuteengineers.com/arduino-1602-character-lcd-tutorial/
I first thought I could somehow upload the image of a mouth, or convert it, and it would just show up on the screen. But I quickly realized that the LCD display doesn’t work that way. It can display alphanumeric elements on 16*2 fields that are each 8*5 pixels. In addition to that it can store a maximum of eight special characters that can be defined by the user. So, this meant that I could only use eight different 8*5 pixel bits to make three different mouths, and I was not sure if that would work at all.
I used an excel sheet to design the images. These are the eight special elements I eventually came up with to use them to draw.
As you can see, all mouths consist of only these elements.
Neutral Mouth:
Sad Mouth:
Happy mouth:
I planned to make complete lips that are curving up and down, but due to the limitations it ended up being a simple curve up and down. However, since emoticons and smileys have become so universal in representing emotions it might even be more fitting to use curves rather than mouth images for the purpose of this project, after all it’s a “robot” and not a human.
This is the first prototype of the face, made using cardboard:
This is the final face (in happy mode):
Circuit Diagram:
There are three buttons that can be pressed. They are indistinguishable, because I want the user to be surprised by the reaction of the robot. The three emotions that the robot can express are sadness, slight happiness, and anger. These are created by adjusting servo positions and the LCD display. In addition, if all three buttons are pressed simultaneously, it goes into “confusion mode” which makes the eyes (motors) turn and the eyebrows (servos) loop to move up and down. The LCD, instead of showing a mouth, shows the words “SAD HAPPY ANGRY” in a loop. I added this mode because in robot movies, when robots have to deal with conflicting commands or dilemmas, they often react by dysfunctioning or exploding, and I thought it would be fun to add that feature.
Video Demonstrations:
confusion mode
happy sad angry
Problems and Solutions:
I had multiple problems with the motors, which represent the eyes. I initially wanted them to move forward and backward together with the movement of the servos. However, the movements were unpredictable both due to the immense friction to the acrylic plate, and the natural deviations in rotational speed of the motors. To fix the first problem, I wrapped the tires in cloth, to decrease the friction, which worked. But I couldn’t figure out how to make the tires move 30 degrees forward when button 1 is pressed, and 30 degrees backward when button 2 is pressed. It was not precise enough to keep the eyes in place. In hindsight, I should have used servos for the eyes as they can be set to precise angles, and I later found out that there are even servos that can rotate completely.
Code:
#include <LiquidCrystal.h>
#include <Servo.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(A2, A1, 5, 4, 3, 2);
const int button1 = A5; //initializing buttons
const int button2 = A4;
const int button3 = A3;
int buttonState1 = 0; //variables to read the buttons
int buttonState2 = 0;
int buttonState3 = 0;
int servoPin1 = 6; //servo variables
int servoPin2 = 7;
// Create a servo objects
Servo Servo1;
Servo Servo2;
//the right motor will be controlled by the motor A pins on the motor driver
const int rightcontrol1 = 13; //control pin 1 on the motor driver for the right motor
const int rightcontrol2 = 12; //control pin 2 on the motor driver for the right motor
const int rightspeed = 11; //speed control pin on the motor driver for the right motor
//the left motor will be controlled by the motor B pins on the motor driver
const int leftspeed = 10; //speed control pin on the motor driver for the left motor
const int leftcontrol2 = 9; //control pin 2 on the motor driver for the left motor
const int leftcontrol1 = 8; //control pin 1 on the motor driver for the left motor
int clearscreen = 0; //variable to store state of lcd screen
// the custom characters
byte smalluphigh[8] =
{
0b10111,
0b01111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111
};
byte smalldownhigh[8] =
{
0b11101,
0b11110,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111
};
byte linehigh[8] =
{
0b11111,
0b11111,
0b00000,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111
};
byte linelow[8] =
{
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b00000,
0b11111,
0b11111
};
byte smalldownlow[8] =
{
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b01111,
0b10111
};
byte smalluplow[8] =
{
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11111,
0b11110,
0b11101
};
byte up[8] =
{
0b11111,
0b11111,
0b11110,
0b11101,
0b10011,
0b01111,
0b11111,
0b11111
};
byte down[8] =
{
0b11111,
0b11111,
0b01111,
0b10111,
0b11001,
0b11110,
0b11111,
0b11111
};
void setup()
{
Servo1.attach(servoPin1);
Servo2.attach(servoPin2);
// initializing LCD and setting up the number of columns and rows:
lcd.begin(16, 2);
//create the new characters
lcd.createChar(0, smalluphigh);
lcd.createChar(1, smalldownhigh);
lcd.createChar(2, linehigh);
lcd.createChar(3, linelow);
lcd.createChar(4, smalldownlow);
lcd.createChar(5, smalluplow);
lcd.createChar(6, up);
lcd.createChar(7, down);
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(button3, INPUT);
//set the motor control pins as outputs
pinMode(rightcontrol1, OUTPUT);
pinMode(rightcontrol2, OUTPUT);
pinMode(rightspeed, OUTPUT);
pinMode(leftcontrol1, OUTPUT);
pinMode(leftcontrol2, OUTPUT);
pinMode(leftspeed, OUTPUT);
Serial.begin(9600);
// Clears the LCD screen
lcd.clear();
}
void loop()
{
buttonState1 = digitalRead(button1);
buttonState2 = digitalRead(button2);
buttonState3 = digitalRead(button3);
if (buttonState1 == HIGH && buttonState2 == HIGH && buttonState3 == HIGH) { //display craze face
clearscreen = 1; \\indicate that the screen as to be cleared when switching to another mode
lcd.print("HAPPY SAD ANGRY ");
lcd.scrollDisplayLeft();
//make servos go crazy
for (int pos = 60; pos <= 100; pos += 1) { //increase the position of the servo
Servo1.write(pos);
Servo2.write(pos);
delay(15);
}
for (int pos = 100; pos >= 60; pos -= 1) { //decrease the position of the servo
Servo1.write(pos);
Servo2.write(pos);
delay(10);
//turning the eyes (motors)
digitalWrite(rightcontrol1, HIGH); //set pin 1 to high
digitalWrite(rightcontrol2, LOW); //set pin 2 to low
digitalWrite(leftcontrol1, LOW);
digitalWrite(leftcontrol2, HIGH);
analogWrite(leftspeed, 100);
analogWrite(rightspeed, 100);
}
}
else if (buttonState1 == HIGH) { //displays sad face
Servo1.write(45);
Servo2.write(135);
if (clearscreen == 1) {
lcd.clear();
clearscreen = 0;
}
lcd.setCursor(4, 0);
lcd.print(" ");
lcd.setCursor(5, 0);
lcd.print(" ");
lcd.setCursor(6, 0);
lcd.write(byte(5));
lcd.setCursor(7, 0);
lcd.write(byte(3));
lcd.setCursor(8, 0);
lcd.write(byte(3));
lcd.setCursor(9, 0);
lcd.write(byte(3));
lcd.setCursor(10, 0);
lcd.write(byte(4));
lcd.setCursor(11, 0);
lcd.print(" ");
lcd.setCursor(12, 0);
lcd.print(" ");
lcd.setCursor(4, 1);
lcd.print(" ");
lcd.setCursor(5, 1);
lcd.write(byte(6));
lcd.setCursor(6, 1);
lcd.write(byte(0));
lcd.setCursor(7, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(9, 1);
lcd.print(" ");
lcd.setCursor(10, 1);
lcd.write(byte(1));
lcd.setCursor(11, 1);
lcd.write(byte(7));
analogWrite(leftspeed, 0);
analogWrite(rightspeed, 0);
}
else if (buttonState2 == HIGH) { //displays happy face
Servo1.write(80);
Servo2.write(100);
if (clearscreen == 1) {
lcd.clear();
clearscreen = 0;
}
lcd.setCursor(4, 0);
lcd.print(" ");
lcd.setCursor(5, 0);
lcd.write(byte(4));
lcd.setCursor(6, 0);
lcd.print(" ");
lcd.setCursor(7, 0);
lcd.print(" ");
lcd.setCursor(8, 0);
lcd.print(" ");
lcd.setCursor(9, 0);
lcd.print(" ");
lcd.setCursor(10, 0);
lcd.print(" ");
lcd.setCursor(11, 0);
lcd.write(byte(5));
lcd.setCursor(4, 1);
lcd.print(" ");
lcd.setCursor(5, 1);
lcd.write(byte(1));
lcd.setCursor(6, 1);
lcd.write(byte(7));
lcd.setCursor(7, 1);
lcd.write(byte(3));
lcd.setCursor(8, 1);
lcd.write(byte(3));
lcd.setCursor(9, 1);
lcd.write(byte(3));
lcd.setCursor(10, 1);
lcd.write(byte(6));
lcd.setCursor(11, 1);
lcd.write(byte(0));
analogWrite(leftspeed, 0);
analogWrite(rightspeed, 0);
}
else if (buttonState3 == HIGH) { //displays angry face
Servo1.write(135);
Servo2.write(45);
if (clearscreen == 1) {
lcd.clear();
clearscreen = 0;
}
lcd.setCursor(4, 1);
lcd.print(" ");
lcd.setCursor(5, 1);
lcd.print(" ");
lcd.setCursor(6, 1);
lcd.print(" ");
lcd.setCursor(7, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(9, 1);
lcd.print(" ");
lcd.setCursor(10, 1);
lcd.print(" ");
lcd.setCursor(11, 1);
lcd.print(" ");
lcd.setCursor(4, 0);
lcd.write(byte(5));
lcd.setCursor(5, 0);
lcd.write(byte(6));
lcd.setCursor(6, 0);
lcd.write(byte(7));
lcd.setCursor(7, 0);
lcd.write(byte(6));
lcd.setCursor(8, 0);
lcd.write(byte(7));
lcd.setCursor(9, 0);
lcd.write(byte(6));
lcd.setCursor(10, 0);
lcd.write(byte(7));
lcd.setCursor(11, 0);
lcd.write(byte(4));
analogWrite(leftspeed, 0);
analogWrite(rightspeed, 0);
}
else { //displays neutral mouth
Servo1.write(90);
Servo2.write(90);
if (clearscreen == 1) {
lcd.clear();
clearscreen = 0;
}
lcd.setCursor(4, 0);
lcd.write(byte(5));
lcd.setCursor(5, 0);
lcd.write(byte(6));
lcd.setCursor(6, 0);
lcd.write(byte(2));
lcd.setCursor(7, 0);
lcd.write(byte(7));
lcd.setCursor(8, 0);
lcd.write(byte(6));
lcd.setCursor(9, 0);
lcd.write(byte(2));
lcd.setCursor(10, 0);
lcd.write(byte(7));
lcd.setCursor(11, 0);
lcd.write(byte(4));
lcd.setCursor(4, 1);
lcd.write(byte(1));
lcd.setCursor(5, 1);
lcd.write(byte(7));
lcd.setCursor(6, 1);
lcd.write(byte(3));
lcd.setCursor(7, 1);
lcd.write(byte(3));
lcd.setCursor(8, 1);
lcd.write(byte(3));
lcd.setCursor(9, 1);
lcd.write(byte(3));
lcd.setCursor(10, 1);
lcd.write(byte(6));
lcd.setCursor(11, 1);
lcd.write(byte(0));
analogWrite(leftspeed, 0);
analogWrite(rightspeed, 0);
}
}