// simple project using Arduino UNO 128x64 SSD1306 OLED display to show a charging indicator
// this is meant to run on the Seeed XIAO board, but it also runs on the Arduino UNO
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// FULL TUTORIAL: https://youtu.be/4GfPQoIRqW8
// Links from the video:
// Seeed XIAO: https://s.click.aliexpress.com/e/_DnOVFHH
// Seeed XIAO expansion board: https://s.click.aliexpress.com/e/_DmZeeQ3
// Seeed XIAO documentation: https://wiki.seeedstudio.com/XIAO_BLE/
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Arduino breadboard prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// 128x64 SSD1306 OLED Display: https://s.click.aliexpress.com/e/_DCKdvnh
// Transparent OLED display: https://s.click.aliexpress.com/e/_Dns6eLz
// XBMP online generator: https://xbm.jazzychad.net/
// Related videos:
// Turbo pressure gauge with Arduino and OLED display - https://youtu.be/JXmw1xOlBdk
// Arduino Car Cluster with OLED Display - https://youtu.be/El5SJelwV_0
// Knob over OLED Display - https://youtu.be/SmbcNx7tbX8
// Arduino + OLED = 3D ? - https://youtu.be/kBAcaA7NAlA
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // initalize the display
static const unsigned char bitmap_upir_logo[] PROGMEM = { // upir logo bitmap, created using the XMBP online generator (link above)
0xa8,0xeb,0xa8,0xa2,0xa8,0x69,0x98,0xa8};
const int NUM_PARTICLES = 20; // number of particles - more than 20 particles will not fit into Arduino UNO RAM
float particle_x[NUM_PARTICLES]; // particle X position
float particle_y[NUM_PARTICLES]; // particle Y position
int particle_speed_x[NUM_PARTICLES]; // particle X speed
int particle_speed_y[NUM_PARTICLES]; // particle Y speed
float particle_size[NUM_PARTICLES]; // particle size (radius)
int init_spread = 15; // how much could be new particle away from the center
int time_value = 0; // time value to animate some elements and update the charge value slower
int charge_value = 0; // charging value displayed in the middle of the screen
char charge_value_buffer[10]; // charging integer value converted to C style string
unsigned long millis_time; // fps
unsigned long millis_time_last; // fps
int fps; // actual FPS value
char fps_buffer[10]; // buffer for converting FPS to string
void setup(void) {
u8g2.setBusClock(800000);
u8g2.begin(); // initialize u8g2 drawing
u8g2.setFont(u8g2_font_chargen_92_tr); // set U8G font
for (int i=0; i < NUM_PARTICLES; i++) { // initialize all the particles
particle_x[i] = random(64-init_spread,64+init_spread); // set the position to the center of the screen +- spread
particle_y[i] = random(32-init_spread,32+init_spread); // set the position to the center of the screen +- spread
particle_speed_x[i] = random(-5, 6) * 3; // set some random speed for X
particle_speed_y[i] = random(-5, 6) * 3; // set some random speed for Y
particle_size[i] = 8; // set a big enough radius
}
}
void loop(void) {
time_value = time_value+1; // increment the time value
if (time_value % 10 == 0) { // only perform some calculations every Nth frame
// increment the charge value
charge_value = charge_value + 1;
if (charge_value > 100) {
charge_value = 0; // set charge value back to 0
}
sprintf(charge_value_buffer, "%d%%", charge_value); // convert charge value to string with the % symbol
}
u8g2.clearBuffer(); // clear U8G2 drawing buffer
u8g2.setDrawColor(1); // white color
// draw particles --------------
for (int i=0; i < NUM_PARTICLES; i++) { // go over all the particles
particle_x[i] = particle_x[i] + particle_speed_x[i]/10.0; // update the X position based on the speed
particle_y[i] = particle_y[i] + particle_speed_y[i]/10.0; // update the Y position based on the speed
particle_size[i] = particle_size[i] * 0.95; // make the size slightly smaller
u8g2.drawDisc(particle_x[i], particle_y[i], particle_size[i]); // draw the particle
// if the particle is outside the screen or small enough, re-initialize it again
if ((particle_x[i] > 128) || (particle_x[i] < 0) || (particle_y[i] > 64) || (particle_y[i] < 0) || (particle_size[i] < 0.5)) {
particle_x[i] = random(64-init_spread,64+init_spread); // set the position to the center of the screen +- spread
particle_y[i] = random(32-init_spread,32+init_spread); // set the position to the center of the screen +- spread
particle_speed_x[i] = random(-5, 6) * 3; // set some random speed for X
particle_speed_y[i] = random(-5, 6) * 3; // set some random speed for Y
particle_size[i] = 8; // set a big enough radius
}
}
// draw big circle in the middle --------------
byte radius = round(25.0 + sin(time_value / 10.0)*2.0); // radius for the big circle in the middle
u8g2.drawDisc(64, 32, radius); // draw the big circle
// draw upir logo --------------
u8g2.drawXBMP(128-16, 64-4, 16, 4, bitmap_upir_logo); // draw upir logo in the corner of the screen
// draw FPS --------------
u8g2.setFont(u8g2_font_6x10_tf); // set very small font
itoa(fps, fps_buffer, 10); // convert FPS number to string
u8g2.drawStr(0, 10, fps_buffer); // draw the FPS number
// draw big value in the center --------------
u8g2.setDrawColor(0); // black color
u8g2.setFont(u8g2_font_chargen_92_tr); // set U8G font
byte string_width = u8g2.getStrWidth(charge_value_buffer); // calculate the string width
u8g2.drawStr(64 - (string_width / 2), 40, charge_value_buffer); // draw the charge value string in the middle of the screen
u8g2.sendBuffer(); // send U8G2 buffer to the display
// calculate the FPS
millis_time_last = millis_time; // store last millisecond value
millis_time = millis(); // get millisecond value from the start of the program
fps = round(1000.0/ (millis_time*1.0-millis_time_last)); // calculate FPS (frames per second) value
}