I used to perform well at many sports: walking, running, riding bicycle, playing badminton etc. I love riding to travel around before long. Well, look at my portly belly… Well, anyway, I decide to restart to exercise. What equipment should I prepare? Besides sports facility, yes! I need an instrument! I believe with it, I can keep appropriate amount of exercise. Here the instrument comes into being.
Let’s begin with a video.
The instrument not just can record steps (and calories) at real time, but show time. What is special is that the format of showing is pointer~ so cool! I really, really like it!
You can upload your records to the Internet only by one click. All records can be displayed by Blynk (a smart-phone software introduced before). The same as wearable smart watch, the instrument get time on line (So you need not afraid of the power and time updating).
Hardware
FireBeetle ESP32 IOT Microcontroller (Supports Wi-Fi & Bluetooth)
3.7V battery ( bought online, the volume is around 600mAH)
3 bottoms ( bought online)
It is very convenient to build this project by Blybk.
1. Create a Blynk project Add two controls:
Value Display * 1
Real-time clock * 1
The name of Value Display should be set to steps, but no setting for the properties of Real-time clock. Choose V1 as the input pin to asjust the layout of controls, shown as below.
Download programs to FireBeetle Board-ESP32 . The source code consists library files and 3D printing files. You should save the library file to lib of arduino. And the 3D files can print crusts directly.
Below is the main program:
#include <TimeLib.h>
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306.h" // alias for `#include "SSD1306Wire.h"`
#include "OLEDDisplayUi.h"
#include "images.h"
#include <SimpleTimer.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <WidgetRTC.h>
#define POWER_KEY 1
#define MENU_KEY 2
#define UPLOAD_KEY 3
boolean upload = false;
SSD1306 display(0x3c, 18, 0);
OLEDDisplayUi ui ( &display );
SimpleTimer timer;
WidgetRTC rtc;
int screenW = 128;
int screenH = 64;
int clockCenterX = screenW/2;
int clockCenterY = ((screenH-16)/2)+16; // top yellow part is 16 px height
int clockRadius = 23;
#define DEVICE (0x53) //ADXL345 device address
#define TO_READ (6) //num of bytes we are going to read each time (two bytes for each axis)
byte buff[TO_READ] ; //6 bytes buffer for saving data read from the device
char str[100]; //string buffer to transform data before sending it to the serial port
int regAddress = 0x32; //first axis-acceleration-data register on the ADXL345
int xx, yy, zz; //three axis acceleration data
static int currentValue = 0;
static unsigned long stepsSum=0;
char auth[] = "YourAuthToken";
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";
const char running_Logo_bits[] PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x64,0x03,0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0x00,0xFC,
0x01,0x00,0x00,0x00,0xFC,0x05,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0x00,0xFC,0x00,
0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0x00,0xE0,0x03,0x00,
0x00,0x60,0xF1,0x07,0x00,0x00,0x20,0xF8,0x17,0x00,0x00,0xC0,0xF8,0x0F,0x00,0x00,
0xE0,0xFB,0x17,0x00,0x00,0xC0,0xFF,0x13,0x00,0x00,0x00,0xFF,0x03,0x00,0x00,0x80,
0xFE,0x03,0x00,0x00,0x00,0xF9,0x03,0x00,0x00,0x00,0xFA,0x03,0x00,0x00,0x00,0xF8,
0x03,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0x00,0xF4,0x07,0x00,0x00,0x00,0xF4,0x0F,
0x00,0x00,0x00,0xF9,0x0F,0x00,0x00,0x00,0xFC,0x1F,0x00,0x00,0x80,0xFE,0x1F,0x00,
0x00,0x00,0xFF,0x1F,0x00,0x00,0xA0,0xFF,0x5F,0x00,0x00,0xC0,0x3F,0x3F,0x00,0x00,
0xE8,0x1F,0x3F,0x00,0x00,0xE8,0xA7,0x3E,0x00,0x00,0xF0,0x03,0x7C,0x00,0x00,0xE0,
0x05,0x7C,0x00,0x00,0xE0,0x05,0xF8,0x01,0x00,0xC0,0x01,0xF0,0x03,0x00,0xC0,0x03,
0xE8,0x07,0x00,0xC0,0x03,0x88,0x6F,0x00,0x80,0x03,0x40,0x1E,0x00,0xA0,0x03,0x40,
0xFC,0x00,0x80,0x03,0x00,0xF8,0x01,0x00,0x07,0x00,0xF4,0x00,0x00,0x07,0x00,0xE8,
0x00,0x80,0x0F,0x00,0xE8,0x00,0x90,0x0F,0x00,0xE0,0x00,0xE8,0x0F,0x00,0xE8,0x00,
0xF0,0x09,0x00,0x60,0x01,0xF0,0x04,0x00,0x00,0x00,
};
// utility function for digital clock display: prints leading 0
String twoDigits(int digits){
if(digits < 10) {
String i = '0'+String(digits);
return i;
}
else {
return String(digits);
}
}
void clockOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
if((hour()==0) && (minute()==0) && (second()==0))
stepsSum = 0;
}
void analogClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->drawCircle(clockCenterX + x, clockCenterY + y, 2);
//hour ticks
for( int z=0; z < 360;z= z + 30 ){
float angle = z ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
int x2 = ( clockCenterX + ( sin(angle) * clockRadius ) );
int y2 = ( clockCenterY - ( cos(angle) * clockRadius ) );
int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
display->drawLine( x2 + x , y2 + y , x3 + x , y3 + y);
}
// display second hand
float angle = second() * 6 ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
// display minute hand
angle = minute() * 6 ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
// display hour hand
angle = hour() * 30 + int( ( minute() / 12 ) * 6 ) ;
angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
}
void digitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
String date = String(year())+"/"+twoDigits(month())+"/"+twoDigits(day());
String timenow = String(hour())+":"+twoDigits(minute())+":"+twoDigits(second());
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_24);
display->drawString(clockCenterX + x , 20, timenow);
display->setFont(ArialMT_Plain_16);
display->drawString(60 , 45, date);
}
void writeTo(int device, byte address, byte val) {
Wire.beginTransmission(device); //start transmission to device
Wire.write(address); // send register address
Wire.write(val); // send value to write
Wire.endTransmission(); //end transmission
}
//reads num bytes starting from address register on device in to buff array
void readFrom(int device, byte address, int num, byte buff[]) {
Wire.beginTransmission(device); //start transmission to device
Wire.write(address); //sends address to read from
Wire.endTransmission(); //end transmission
Wire.beginTransmission(device); //start transmission to device
Wire.requestFrom(device, num); // request 6 bytes from device
int i = 0;
while(Wire.available()) //device may send less than requested (abnormal)
{
buff= Wire.read(); // receive a byte
i++;
}
Wire.endTransmission(); //end transmission
}
void runningFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
float calValue = stepsSum*0.4487;
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_24);
display->drawString(clockCenterX , clockCenterY, str);
sprintf(str,"%.2fcal",calValue);
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(ArialMT_Plain_10);
display->drawString(100 , 20, str);
display->drawXbm(10, 14, 34, 50, running_Logo_bits);
}
void uploadFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
display->setFont(ArialMT_Plain_16);
display->drawString(60 , 45, "upload data ...");
}
// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback frames[] = { analogClockFrame, digitalClockFrame, runningFrame, uploadFrame};
// how many frames are there?
int frameCount = 4;
// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = { clockOverlay };
int overlaysCount = 1;
void uploadToBlynk(void){
if(upload == true){
Blynk.virtualWrite(V0,stepsSum);
Blynk.virtualWrite(V1,stepsSum);
}
}
void uiInit(void){
ui.setTargetFPS(30);
//ui.setActiveSymbol(activeSymbol);
//ui.setInactiveSymbol(inactiveSymbol);
ui.setIndicatorPosition(TOP);
ui.setIndicatorDirection(LEFT_RIGHT);
ui.setFrameAnimation(SLIDE_LEFT);
ui.setFrames(frames, frameCount);
ui.setOverlays(overlays, overlaysCount);
ui.disableAutoTransition();
ui.switchToFrame(2);
ui.init();
display.flipScreenVertically();
}
void adxl345Init(void){
writeTo(DEVICE, 0x2D, 0);
writeTo(DEVICE, 0x2D, 16);
writeTo(DEVICE, 0x2D, 8);
}
void updateAdxl345(void){
readFrom(DEVICE, regAddress, TO_READ, buff); //read the acceleration data from the ADXL345
xx = (((int)buff[1]) << 8) | buff[0];
yy = (((int)buff[3])<< 8) | buff[2];
zz = (((int)buff[5]) << 8) | buff[4];
if(xx < 100){
sprintf(str, "%d", stepsSum);
return;
}
if(fabs(xx - currentValue) > 80){
if(xx < currentValue){
stepsSum++;
}
currentValue = xx;
}
sprintf(str, "%d", stepsSum);
}
int getKeys(void){
if(digitalRead(D2) == LOW){
delay(5);
if(digitalRead(D2) == LOW){
while(digitalRead(D2) == LOW);
return POWER_KEY;
}
}
if(digitalRead(D3) == LOW){
delay(5);
if(digitalRead(D3) == LOW){
while(digitalRead(D3) == LOW);
return MENU_KEY;
}
}
if(digitalRead(D4) == LOW){
delay(5);
if(digitalRead(D4) == LOW){
while(digitalRead(D4) == LOW);
return UPLOAD_KEY;
}
}
return 0;
}
void doKeysFunction(void){
static int uiFrameIndex = 2;
int keys = getKeys();
if(keys == POWER_KEY){
static char i = 0;
if(i){
ui.init();
display.flipScreenVertically();
display.displayOn();
}else{
display.displayOff();
}
i = ~i;
}
if(keys == MENU_KEY){
if(upload == false){
uiFrameIndex++;
if(uiFrameIndex == 3)
uiFrameIndex = 0;
ui.switchToFrame(uiFrameIndex);
}else{
ui.switchToFrame(3);
}
}
if(keys == UPLOAD_KEY){
if(upload == true){
upload = false;
ui.switchToFrame(uiFrameIndex);
}else{
upload = true;
ui.switchToFrame(3);
}
}
}
void setup() {
pinMode(D2,INPUT);
pinMode(D3,INPUT);
pinMode(D4,INPUT);
Blynk.begin(auth, ssid, pass);
rtc.begin();
uiInit();
adxl345Init();
timer.setInterval(30,updateAdxl345);
timer.setInterval(100,uploadToBlynk);
}
void loop() {
int remainingTimeBudget = ui.update();
static int testSum = 0;
if((testSum < 100) || (upload == true)){
Blynk.run();
testSum++;
}
if (remainingTimeBudget > 0) {
delay(remainingTimeBudget);
}
doKeysFunction();
timer.run();
}
Caution: You should modify Wi-Fi setting, passport and AUTHTOKENS to yourself.
char auth[] = "YourAuthToken";
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";
Hardware connection
Connect OLED 128*64 and acceleration module to I2C, bottoms to D2, D3, D4. Moreover, add 51k pull-up resistors to bottoms to reach 3.3V, shown as below.
Caution: It’s wrong to connect pull-up resistors to AREF, the right one is to 3.3V .
The hardware soldering image, shown as below:
After the solder, assembling hardware module to crust, shown as below:
Comprehensive effect image:
Comments