BLUETOOTH POSTURE CORRECTOR FOR $20. The code.
This program couldn't be made without open source code found on the internet. I assume anyone could find the libraries used or similar ones just by looking for keywords.
//#################################################################################################################################################################
//LIBRARIES:
#include <MPU6050.h> // Library needed to read the accelerometer and giroscope module MPU6050.
#include <I2Cdev.h> // Library needed to get I2C comms between MPU6050 and Arduino Pro Mini.
#include <Wire.h> // Library I2Cdev.h needs library Wire.h by default.
#include <LowPower.h> // This library might be useful in the future in case we need to save power (instead of changing the battery)
#include "SingleEMAFilterLib.h" // Library used for getting extra low pass filtering, I couldn´t managed tweaking just with complementary filter.
#include <SoftwareSerial.h> // Library for Serial comms (UART) between Arduino and PC (for Serial Monitor and Serial Plotter) and for AT-09 Bluetooth Module comms.
#include <timer.h> // Library used for easy implementation of Arduino Timers.
#include <timerManager.h> // Library used for easy implementation of Arduino Timers.
//CONSTANTS AND VARIABLES:
Timer timer1; //Timer set for "comparar_enviar" function (sends data to the Serial Port and then the AT-09 Bluetooth module sends such data to appear on Mobile Phone)
Timer timer2; // Spare timer, might be used to avoid delay of 10 ms set in the main program (loop)
Timer timer3; // Timer set for "serialEvent" function (gets the value that the mobile phone sends via Bluetooth, in this case just the vibration angle)
MPU6050 sensor;
const int D2_verde = 2, pote = A0;
int poteValue = 0, poteValuePWM = 0, PWMout = 0, poteValue_offset = 0;
int ax, ay, az, gx, gy, gz;
int valor = 245, inicio=0;
int analogOutPin = 3;
long tiempo_prev;
float dt, ang_y, ang_y_prev, accel_ang_y, giroscopo_y;
float resta = 0, ang_y_positivo = 0, scale_paso1 = 0;
float correct_scale = 0, filtered_scale = 0;
String frase;
//FUNCTIONS:
SingleEMAFilter<float> singleEMAFilter(0.02); //0.02 is the gain set for the Additional Low Pass Filter, in this case called EMA Filter.
//0.02 turned out after some tweaking using the Serial Plotter of the Arduino IDE.
void datos_sensor() { // Function might be used for future Data Adquisition of the MPU6050 using the Timer 2.
}
void comparar_enviar() { //Some adquisition from an ADC port (A0) and data sending to the Serial Port to be printed on the Terminal Screen.
poteValue = analogRead(pote); //Reads a 10 bit value from pin A0 (ADC).
if (poteValue >= 976){Serial.print("Bateria al 100% de carga (4.2 V)"); Serial.print(" \n");} //Prints Battery Levels
else if (poteValue >= 953 && poteValue <= 976 ){Serial.print("Bateria al 90% de carga (4.1 V)"); Serial.print(" \n");}
else if (poteValue >= 929 && poteValue <= 953 ){Serial.print("Bateria al 80% de carga (4.0 V)"); Serial.print(" \n");}
else if (poteValue >= 906 && poteValue <= 929 ){Serial.print("Bateria al 60% de carga (3.9 V)"); Serial.print(" \n");}
else if (poteValue >= 883 && poteValue <= 906 ){Serial.print("Bateria al 40% de carga (3.8 V)"); Serial.print(" \n");}
else if (poteValue >= 859 && poteValue <= 883 ){Serial.print("Bateria al 20% de carga (3.7 V)"); Serial.print(" \n");}
else if (poteValue >= 835 && poteValue <= 859 ){Serial.print("Bateria al 0% de carga (3.6 V)"); Serial.print(" \n");}
else if (poteValue >= 811 && poteValue <= 835 ){Serial.print("Bateria al 0% de carga (3.5 V)"); Serial.print(" \n");}
else if (poteValue >= 787 && poteValue <= 811 ){Serial.print("Bateria al 0% de carga (3.4 V)"); Serial.print(" \n");}
else if (poteValue >= 765 && poteValue <= 787 ){Serial.print("Bateria al 0% de carga (3.3 V)"); Serial.print(" \n");}
else if (poteValue >= 741 && poteValue <= 765 ){Serial.print("Bateria al 0% de carga (3.2 V)"); Serial.print(" \n");}
else if (poteValue >= 717 && poteValue <= 741 ){Serial.print("Bateria al 0% de carga (3.1 V)"); Serial.print(" \n");}
else {Serial.print("Bateria muy baja (menos de 3.1 V)"); Serial.print(" \n");}
Serial.print("El valor para vibracion es:\t");Serial.println(valor); // Shows on screen the vibration angle set.
Serial.println("Set vibration angle from value 0 to 1023"); // Indicates what the user should do in order to get the device working.
if (valor >= 1023) Serial.println("Valor fuera de rango"); // Indicates the value set is out of scope.
else if (valor <= 0) Serial.println("Valor fuera de rango"); // Indicates the value set is out of scope.
Serial.print(" \n");Serial.print(" \n");
}
void serialEvent() {
while (Serial.available()) {
if (Serial.available() > 0) { //Sees if the Serial Port is available.
char letra = Serial.read(); //Reads the Serial Port and stores a letter.
frase += letra; // Stores each letter one by one in "frase".
}
}
if (frase.length() > 0) {
valor = frase.toInt(); //Conversion to a more manageable "int" variable.
}
frase = ""; // Clears buffer...
}
// SETUP FUNCTION: (runs once when you press reset or power the board)
void setup() {
analogReference(INTERNAL); // Internal 1.1 V reference set for analog input A0 (Battery Level Indicator)
Serial.begin(9600); //Initialise Serial Port at 9600 bauds of speed.
Wire.begin(); //Initialise I2C comms.
sensor.initialize(); //Initialise the MPU6050 sensor.
timer1.setInterval(1000); // Timer 1 set to 1 second interval.
timer2.setInterval(10); // Timer 2 set to 10 milliseconds interval.
timer3.setInterval(1); // Timer 3 set to 1 millisecond interval.
// Timer 3 is for Vibration Angle value adquisition:
// Eventually we may need a smaller interval because it does not always get the value.
// But most of times It does get the Vibration Angle, that's why it remains at 1 ms.
//Set our callback function
timer1.setCallback(comparar_enviar);
timer2.setCallback(datos_sensor);
timer3.setCallback(serialEvent);
//Start all the timers
TimerManager::instance().start();
// Pin modes set in Arduino Microcontroller as Inputs and Outputs.
pinMode(LED_BUILTIN, OUTPUT);
pinMode(D2_verde, OUTPUT);
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(analogOutPin, OUTPUT);
}
//LOOP FUNCTION: (Main program that runs over and over indefinitely)
void loop() {
TimerManager::instance().update(); // Updates Timers.
//Optional implementation of Low Power Settings on Arduino and MPU6050.
//sensor.setSleepEnabled(false); // sensor sleep mode switched off
//sensor.setSleepEnabled(true); // sensor sleep mode switched on
// LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF); //Low power Settings for Arduino Microcontroller using LowPower.h
//sensor.setSleepEnabled(true); // sensor sleep mode switched on
//sensor.setSleepEnabled(false); // sensor sleep mode switched off
delay(10); // 10 milliseconds delay. Eventually might be replaced by a timer.
sensor.getAcceleration(&ax, &ay, &az); // Use the MPU6050.h to get Acceleration values of every axis (3) from the Accelerometer.
sensor.getRotation(&gx, &gy, &gz); // Use the MPU6050.h to get Angular Velocity values of every axis (3) from the Gyroscope.
//Calculates the accelerometer angles
accel_ang_y = atan(-ax/sqrt(pow(ay,2) + pow(az,2)))*(180.0/3.14);
dt = (millis()-tiempo_prev)/1000.0;
tiempo_prev=millis();
//Calculates the Rotation Angles from Gyroscope Angular Velocities and applies complementary filter to both gyro and accelerometer variable results.
ang_y = 0.8*(ang_y_prev+(gy/131)*dt) + 0.2*accel_ang_y; //Acceptable results with 0.8 and 0.2 complementary filter gains.
ang_y_prev=ang_y;
//Adjusts values to 10 bit resolution (because 10 bits ADCs are used for setting the setpoint value by potentiometers in the early testing stages)
if (ang_y > 0){ resta = 60 - ang_y; correct_scale = resta*8.4628; }
if (ang_y < 0){ ang_y_positivo = ang_y*(-1);
scale_paso1 = ang_y_positivo +61;
correct_scale = scale_paso1*8.4628;}
poteValue_offset = valor - 200; // An offset value needed to be added.
//Inserts "correct_scale" into the EMA Low Pass Filter and outputs a "filtered_scale"
singleEMAFilter.AddValue(correct_scale);
filtered_scale = singleEMAFilter.GetLowPass();
//Compares the filtered value from the MPU6050 with angle set and triggers (or not) a built in Arduino board LED and an additional one for testing purposes.
if (filtered_scale < poteValue_offset) { digitalWrite(D2_verde, HIGH); digitalWrite(LED_BUILTIN, HIGH);; }
else { digitalWrite(D2_verde, LOW); digitalWrite(LED_BUILTIN, LOW); }
poteValuePWM = 300; // Optimal value set after fine-tuning with a potentiometer via an Analog Input (consumption between 70 mA (3V3) and 100 mA (4V2))
PWMout = poteValuePWM/4; // Adapts Potentiometer values (10 bits) to the PWM output range.
//Compares the filtered value from the MPU6050 with angle set and triggers a PWM output or not.
if (filtered_scale < poteValue_offset) { analogWrite(analogOutPin, PWMout);} //PWM write
else { analogWrite(analogOutPin, 0);}
}
//#################################################################################################################################################################
The following screenshot shows the performance of the EMA Low Pass Filter. Notice there is some latency if we compare the Filtered Output with the Input Signal. Despite that, our actuator response (vibration motor) is fast enough for this application.
https://www.arduino.cc/reference/en/libraries/singleemafilterlib/
usefulelectron@gmail.com