CodeHub
HomeUploadContact
Back to Hub

Arduino eye animation with button

April 11, 2026 Watch Tutorial

Wiring Schematic

Arduino eye animation with button wiring diagram

Hardware Required

  • Arduino board
    Buy on Amazon

Source Code

Arduino / C++
1#include <Wire.h>
2#include <Adafruit_GFX.h>
3#include <Adafruit_SSD1306.h>
4
5#define SCREEN_WIDTH 128
6#define SCREEN_HEIGHT 64
7#define OLED_RESET    -1 
8Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
9
10// --- BUTTON CONFIG ---
11const int buttonPin = 7;     // Changed to Pin D7 for Arduino Uno
12int buttonState = HIGH;      
13int lastButtonState = HIGH;  
14unsigned long lastDebounceTime = 0;  
15unsigned long debounceDelay = 50;    
16
17// --- EMOTION PARTICLES (16x16) ---
18const unsigned char bmp_heart[] PROGMEM = { 0x00, 0x00, 0x0c, 0x60, 0x1e, 0xf0, 0x3f, 0xf8, 0x7f, 0xfc, 0x7f, 0xfc, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
19const unsigned char bmp_zzz[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7e, 0x00, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00 };
20const unsigned char bmp_anger[] PROGMEM = { 0x00, 0x00, 0x11, 0x10, 0x2a, 0x90, 0x44, 0x40, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x2a, 0x90, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
21
22// --- MOODS ---
23#define MOOD_NORMAL 0
24#define MOOD_HAPPY 1
25#define MOOD_SURPRISED 2
26#define MOOD_SLEEPY 3
27#define MOOD_ANGRY 4
28#define MOOD_SAD 5
29#define MOOD_EXCITED 6
30#define MOOD_LOVE 7
31#define MOOD_SUSPICIOUS 8
32
33int currentMood = MOOD_NORMAL;
34const int maxMoods = 8;
35
36// --- PHYSICS ENGINE VARIABLES ---
37unsigned long lastSaccade = 0;
38unsigned long saccadeInterval = 3000;
39float breathVal = 0;
40
41struct Eye {
42  float x, y;  
43  float w, h;  
44  float targetX, targetY, targetW, targetH;
45  float pupilX, pupilY;
46  float targetPupilX, targetPupilY;
47  float velX, velY, velW, velH;
48  float pVelX, pVelY;
49  
50  float k = 0.12;   
51  float d = 0.60;   
52  float pk = 0.08;  
53  float pd = 0.50;  
54
55  bool blinking;
56  unsigned long lastBlink;
57  unsigned long nextBlinkTime;
58
59  void init(float _x, float _y, float _w, float _h) {
60    x = targetX = _x;
61    y = targetY = _y;
62    w = targetW = _w;
63    h = targetH = _h;
64    pupilX = targetPupilX = 0;
65    pupilY = targetPupilY = 0;
66    velX = velY = velW = velH = 0;
67    pVelX = pVelY = 0;
68    nextBlinkTime = millis() + random(1000, 4000);
69    blinking = false;
70  }
71
72  void update() {
73    float ax = (targetX - x) * k;
74    float ay = (targetY - y) * k;
75    float aw = (targetW - w) * k;
76    float ah = (targetH - h) * k;
77
78    velX = (velX + ax) * d;
79    velY = (velY + ay) * d;
80    velW = (velW + aw) * d;
81    velH = (velH + ah) * d;
82
83    x += velX;
84    y += velY;
85    w += velW;
86    h += velH;
87
88    float pax = (targetPupilX - pupilX) * pk;
89    float pay = (targetPupilY - pupilY) * pk;
90    pVelX = (pVelX + pax) * pd;
91    pVelY = (pVelY + pay) * pd;
92    pupilX += pVelX;
93    pupilY += pVelY;
94  }
95};
96
97Eye leftEye, rightEye;
98
99void setup() {
100  Serial.begin(9600);
101  pinMode(buttonPin, INPUT_PULLUP);
102
103  // Start I2C (Pins A4 and A5 on Uno are handled automatically by Wire.begin)
104  Wire.begin();
105  Wire.setClock(400000); // 400kHz Fast Mode for zero lag
106
107  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
108    Serial.println(F("SSD1306 allocation failed"));
109    for(;;); 
110  }
111  
112  display.setTextColor(SSD1306_WHITE);
113  leftEye.init(18, 14, 36, 36);
114  rightEye.init(74, 14, 36, 36);
115}
116
117void loop() {
118  handleButton();
119  updatePhysicsAndMood();
120
121  display.clearDisplay();
122  drawEmoPage();
123  display.display();
124}
125
126void handleButton() {
127  int reading = digitalRead(buttonPin);
128  if (reading != lastButtonState) {
129    lastDebounceTime = millis();
130  }
131  if ((millis() - lastDebounceTime) > debounceDelay) {
132    if (reading != buttonState) {
133      buttonState = reading;
134      if (buttonState == LOW) {
135        currentMood++;
136        if (currentMood > maxMoods) currentMood = 0; 
137        lastSaccade = 0; 
138      }
139    }
140  }
141  lastButtonState = reading;
142}
143
144void updatePhysicsAndMood() {
145  unsigned long now = millis();
146  breathVal = sin(now / 800.0) * 1.5; 
147
148  if (now > leftEye.nextBlinkTime) {
149    leftEye.blinking = true;
150    leftEye.lastBlink = now;
151    rightEye.blinking = true;
152    leftEye.nextBlinkTime = now + random(2000, 6000);
153  }
154  
155  if (leftEye.blinking) {
156    leftEye.targetH = 2;
157    rightEye.targetH = 2; 
158    if (now - leftEye.lastBlink > 120) {
159      leftEye.blinking = false;
160      rightEye.blinking = false;
161    }
162  }
163
164  if (!leftEye.blinking && now - lastSaccade > saccadeInterval) {
165    lastSaccade = now;
166    saccadeInterval = random(500, 3000);
167
168    int dir = random(0, 10);
169    float lx = 0, ly = 0;
170
171    if (dir < 4) { lx = 0; ly = 0; } 
172    else if (dir == 4) { lx = -6; ly = -4; } 
173    else if (dir == 5) { lx = 6; ly = -4; }  
174    else if (dir == 6) { lx = -6; ly = 4; }  
175    else if (dir == 7) { lx = 6; ly = 4; }   
176    else if (dir == 8) { lx = 8; ly = 0; }   
177    else if (dir == 9) { lx = -8; ly = 0; }  
178
179    leftEye.targetPupilX = lx;
180    leftEye.targetPupilY = ly;
181    rightEye.targetPupilX = lx;
182    rightEye.targetPupilY = ly;
183
184    leftEye.targetX = 18 + (lx * 0.3);
185    leftEye.targetY = 14 + (ly * 0.3);
186    rightEye.targetX = 74 + (lx * 0.3);
187    rightEye.targetY = 14 + (ly * 0.3);
188  }
189
190  if (!leftEye.blinking) {
191    float baseW = 36;
192    float baseH = 36;
193    baseH += breathVal;
194
195    switch (currentMood) {
196      case MOOD_NORMAL:
197        leftEye.targetW = baseW; leftEye.targetH = baseH;
198        rightEye.targetW = baseW; rightEye.targetH = baseH;
199        break;
200      case MOOD_HAPPY:
201      case MOOD_LOVE:
202      case MOOD_EXCITED:
203        leftEye.targetW = 40; leftEye.targetH = 32;
204        rightEye.targetW = 40; rightEye.targetH = 32;
205        break;
206      case MOOD_SURPRISED:
207        leftEye.targetW = 30; leftEye.targetH = 45;
208        rightEye.targetW = 30; rightEye.targetH = 45;
209        break;
210      case MOOD_SLEEPY:
211        leftEye.targetW = 38; leftEye.targetH = 30;
212        rightEye.targetW = 38; rightEye.targetH = 30;
213        break;
214      case MOOD_ANGRY:
215        leftEye.targetW = 34; leftEye.targetH = 32;
216        rightEye.targetW = 34; rightEye.targetH = 32;
217        break;
218      case MOOD_SAD:
219        leftEye.targetW = 34; leftEye.targetH = 40;
220        rightEye.targetW = 34; rightEye.targetH = 40;
221        break;
222      case MOOD_SUSPICIOUS:
223        leftEye.targetW = 36; leftEye.targetH = 20; 
224        rightEye.targetW = 36; rightEye.targetH = 42; 
225        break;
226    }
227  }
228
229  leftEye.update();
230  rightEye.update();
231}
232
233void drawEmoPage() {
234  if (currentMood == MOOD_LOVE) {
235    display.drawBitmap(56, 0, bmp_heart, 16, 16, SSD1306_WHITE);
236  } else if (currentMood == MOOD_SLEEPY) {
237    display.drawBitmap(110, 0, bmp_zzz, 16, 16, SSD1306_WHITE);
238  } else if (currentMood == MOOD_ANGRY) {
239    display.drawBitmap(56, 0, bmp_anger, 16, 16, SSD1306_WHITE);
240  }
241
242  drawUltraProEye(leftEye, true);
243  drawUltraProEye(rightEye, false);
244}
245
246void drawUltraProEye(Eye& e, bool isLeft) {
247  int ix = (int)e.x;
248  int iy = (int)e.y;
249  int iw = (int)e.w;
250  int ih = (int)e.h;
251
252  int r = 8;
253  if (iw < 20) r = 3;
254  
255  display.fillRoundRect(ix, iy, iw, ih, r, SSD1306_WHITE);
256
257  int cx = ix + iw / 2;
258  int cy = iy + ih / 2;
259  int pw = iw / 2.2;
260  int ph = ih / 2.2;
261
262  int px = cx + (int)e.pupilX - (pw / 2);
263  int py = cy + (int)e.pupilY - (ph / 2);
264
265  if (px < ix) px = ix;
266  if (px + pw > ix + iw) px = ix + iw - pw;
267  if (py < iy) py = iy;
268  if (py + ph > iy + ih) py = iy + ih - ph;
269
270  display.fillRoundRect(px, py, pw, ph, r / 2, SSD1306_BLACK);
271
272  if (iw > 15 && ih > 15) {
273    display.fillCircle(px + pw - 4, py + 4, 2, SSD1306_WHITE);
274  }
275
276  drawEyelidMask(e.x, e.y, e.w, e.h, currentMood, isLeft);
277}
278
279void drawEyelidMask(float x, float y, float w, float h, int mood, bool isLeft) {
280  int ix = (int)x;
281  int iy = (int)y;
282  int iw = (int)w;
283  int ih = (int)h;
284
285  if (mood == MOOD_ANGRY) {
286    if (isLeft) {
287      display.fillTriangle(ix - 5, iy - 5, ix + iw + 5, iy + 10, ix - 5, iy + 15, SSD1306_BLACK);
288    } else {
289      display.fillTriangle(ix + iw + 5, iy - 5, ix - 5, iy + 10, ix + iw + 5, iy + 15, SSD1306_BLACK);
290    }
291  }
292  else if (mood == MOOD_SAD) {
293    if (isLeft) {
294      display.fillTriangle(ix + iw + 5, iy - 5, ix - 5, iy + 10, ix + iw + 5, iy + 15, SSD1306_BLACK);
295    } else {
296      display.fillTriangle(ix - 5, iy - 5, ix + iw + 5, iy + 10, ix - 5, iy + 15, SSD1306_BLACK);
297    }
298  }
299  else if (mood == MOOD_HAPPY || mood == MOOD_LOVE || mood == MOOD_EXCITED) {
300    display.fillRect(ix, iy + ih - 12, iw, 14, SSD1306_BLACK);
301    display.fillCircle(ix + iw / 2, iy + ih + 6, iw / 1.3, SSD1306_BLACK); 
302  }
303  else if (mood == MOOD_SLEEPY) {
304    display.fillRect(ix, iy, iw, ih / 2 + 2, SSD1306_BLACK);
305  }
306  else if (mood == MOOD_SUSPICIOUS) {
307    if (isLeft) display.fillRect(ix, iy, iw, ih / 2 - 2, SSD1306_BLACK);
308    else display.fillRect(ix, iy + ih - 8, iw, 8, SSD1306_BLACK);
309  }
310}
Pushbutton
Buy on Amazon
  • Breadboard
    Buy on Amazon
  • OLED Display
    Buy on Amazon
  • Jumper wires
    Buy on Amazon
  • USB Cable
    Buy on Amazon