// Ethanol Analyzer v2.1 with BLE & optional DAC analog out(MCP4725 required).. This code only works with AVR based boards(ATmega/Keywish/eMakeFun). Will NOT work with Nano 33 BLE!
// GOFi2cOLED - Version: Latest
// v1.4 change notes: Analog out range 0-5v, DAC
// v1.5 change notes: dacOut 5.15v adjustment
// v1.6 change notes: averaging A1 code to reduce noise
// v2.0 change notes: Enable/disable certain features to make it easier for yall, also merged all code branches into this one
// v2.1 change notes: Slight memory efficiency, re-init screen if gets disconnected w/o having to key off/on, watchdog reset if board freezes, dacOut 5.00v
#include <Wire.h>
#include <Adafruit_SleepyDog.h>
#include <GOFi2cOLED.h>
#include <Adafruit_MCP4725.h>
GOFi2cOLED LCD;
Adafruit_MCP4725 dac;
#define A1 1
#define SENSOR_PIN 10
#define OUTPUT_PIN 9
float verz = 2.1; //code release
///////// Features can be enabled or disabled below ///////
bool featureDAC = 1; //DAC analog output (MCP4725 required) default=1 (enabled)
bool featurePWMout = 1; //PWM Analog output (pin D9) default=1 (enabled)
int voltOUTRange = 0; //PWM&DAC Output Range (0=0-5v , 1=0.5-4.5v) default=0 (0-5v) Setting to 1 will output 0.5v-4.5v and also enable Cobb error conditions
///// If you choose to use input A1 pin to attach to a MAP sensor, or some other sensor to monitor input voltage (voltage follower needed for resistance circuits (ie ECT/IAT) //////////
bool featureAnalogInA1 = 0; //0-5v Analog Input(Pin A1) for bar graph default=0 (disabled) (setting this to zero essentially turns off monitoring A1 input pin)
float DangerToManifoldA1 = 5.1; //Danger to Manifold Warning voltage threshold default=5.1 (5.1v - basically should never cross this, thus will never show)
bool ShowBarGraphA1 = 0; //Show Analog Input Bar Graph default=0 (must have A1 input pin attached to a 0-5v input source)
bool ShowTriangleA1 = 1; //Show Analog Input Triangle Gauge default=1 (must have A1 input pin attached to a 0-5v input source)
bool ReverseGraphA1 = 0; //Swap left/right start point default=0 (by default, graph starts from the left side of the screen when detecting 0 voltage)
bool ShowTickMarksA1 = 1; //Show tick marks for A1 gauge default=1 (to show 1/4, 1/2, 3/4 tick marks, like a fuel gauge)
//// To adjust Triangle/Graph gauge, for example: Min(195) Max(530) monitors Fiesta IAT sensor at two given points: 2.6v(59F) to .96v(138F), triangle stays pegged below 59F and above 138F.
int GraphScalerP1A1 = 0; //A1 Scaler start point default=0 (view range window start point, ex. set to 512 to set start point at half of full range)
int GraphScalerP2A1 = 1024; //A1 Scaler end point default=1024(view range window end point)
//// Below is if you decide to swap the fuel temp Celsius metric with whatever is attached to A1 pin, for example Fuel/IAT temps side by side
bool SwapFuelCwithA1 = 0; //Replace Celsius field with A1 input default=0
int AlternateScalerP1A1 = 0; //Alternate Scaler start point IAT example default=0 (138 for tapping into Fiesta IAT)
int AlternateScalerP2A1 = 1024; //Alternate Scaler end point IAT example default=1024(59 for tapping into Fiesta IAT)
int eContentSkew = 50; //Skew Ethanol Content readings default=50 (50 is neutral, set to 57 to subtract "-7%" ethanol or 45 to add "5%", I know it sounds backwards)
int splashScreenDelay = 2000; //show splash screen in miliseconds default=2000 OR default=0 for Cobb (helps to get ethanol content immediately after key on start
int refreshDelay = 0; //delay between refresing new ethanol content default=0 (zero = refresh as quickly as possible)
int eContentOverride = 0; //Set to above 0 to override ethanol content default=0 (for testing purposes ONLY)
unsigned int counttick = 0; //for skipping/retry invalid data
bool resetDisplay = false; //if oled gets disconnected, resync without key-off/on again
void setup()
{ /////////// INTRO SCREEN & initializations ///////////
Serial.begin(9600);
if (featurePWMout){
// Setup for analog out, pin 9 to fast mode.
pinMode(OUTPUT_PIN, OUTPUT);
TCCR1B = TCCR1B & 0b11111000 | 0x01;
int analogOut = 255 * (0.1 / 5.0); // setting analog to 0.1v until content can be calculated
analogWrite(OUTPUT_PIN, int(analogOut));
}
if (featureDAC){
dac.begin(0x60);
int dacOut = 4095 * (0.1 / 5.0); // setting DAC to 0.1v until content can be calculated
dac.setVoltage(dacOut, false);
}
LCD.init(0x3C);
LCD.setTextWrap(false);
LCD.clearDisplay();
LCD.setCursor(0, 0);
LCD.setTextSize(1);
LCD.print(featureDAC);
LCD.print(featurePWMout);
LCD.print(voltOUTRange);
LCD.println(featureAnalogInA1);
LCD.print(" v");
LCD.println(verz,1);
LCD.setTextSize(2);
LCD.println();
LCD.println("Ethanol"); // -> Change the text of these two lines if you want a different startup screen
LCD.println("Analyzer");
LCD.display();
delay(splashScreenDelay);
Watchdog.enable(8000);
}
void loop()
{
Watchdog.reset();
//////////// Detect OLED Disconnect & ReSync ////////////
byte OLEDerror = false;
Wire.beginTransmission(60);
OLEDerror = Wire.endTransmission();
if (OLEDerror != 0){
resetDisplay=true;
}
if (OLEDerror == 0 && resetDisplay==true){
delay(1000);
Watchdog.reset();
LCD.init(0x3C);
LCD.setTextWrap(false);
LCD.clearDisplay();
LCD.setCursor(0, 0);
LCD.setTextSize(1);
LCD.print(featureDAC);
LCD.print(featurePWMout);
LCD.print(voltOUTRange);
LCD.println(featureAnalogInA1);
LCD.print(" v");
LCD.println(verz,1);
LCD.setTextSize(2);
LCD.println();
LCD.println("Ethanol"); // -> Change the text of these two lines if you want a different startup screen
LCD.println("Analyzer");
LCD.display();
delay(splashScreenDelay);
Watchdog.reset();
resetDisplay=false;
}
/////// READ FLEX-FUEL SENSOR DATA /////////
unsigned long highTime = pulseIn(SENSOR_PIN, HIGH);
unsigned long lowTime = pulseIn(SENSOR_PIN, LOW);
unsigned long pulsetime = highTime + lowTime;
/////// READ 0-5V INPUT VOLTAGE from A1 PIN ////////
float VInA1;
int AverageVoltagebitsA1;
int AltA1;
if (featureAnalogInA1) {
// start averaging analog A1 input
AverageVoltagebitsA1 = 0;
int MeasurementsToAverage = 16;
for(int i = 0; i < MeasurementsToAverage; ++i)
{
AverageVoltagebitsA1 += MeasureVoltage();
delay(1);
}
AverageVoltagebitsA1 /= MeasurementsToAverage;
//end averaging input
VInA1 = AverageVoltagebitsA1 * (5.0 / 1023.0);
AltA1 = mapf(AverageVoltagebitsA1, GraphScalerP1A1, GraphScalerP2A1, AlternateScalerP1A1, AlternateScalerP2A1);
} else {
VInA1 = 0.0; //We are setting Analog In to zero because feature is disabled.
}
//////// IF FLEX-FUEL SENSOR DATA OUT OF RANGE, SET VARIOUS Vout ERROR CONDITIONS on DAC & ANALOG ///////
// 20000 uS = 50 HZ - ~6667 uS = 150 HZ
if (pulsetime > 20100 || pulsetime < 6400) {
// If the value falls outside the range just skip it for this iteration, perhaps from noise?
if (counttick >= 2){ // if flex-fuel sensor data is invalid twice in a row, then display debug content - otherwise skip and use old calculations
if (voltOUTRange){
//below are for Cobb specific error codes when using 0.5-4.5v DAC analog out
if (pulsetime == 0 ) {
// setting analog to 0.1v because sensor got disconnected
if (featurePWMout){
int analogOut = 255 * (0.1 / 5.0);
analogWrite(OUTPUT_PIN, int(analogOut));
}
if (featureDAC){
int dacOut = 4095 * (0.1 / 5.0);
dac.setVoltage(dacOut, false);
}
}
if (pulsetime >= 20100 ) {
// setting analog to 4.8v because fuel is probably contaminated (less than 0% ethanol)
if (featurePWMout){
int analogOut = 255 * (4.8 / 5.0);
analogWrite(OUTPUT_PIN, int(analogOut));
}
if (featureDAC){
int dacOut = 4095 * (4.8 / 5.0);
dac.setVoltage(dacOut, false);
}
}
if ((pulsetime <= 6400) && (pulsetime >= 1)) {
// setting analog to 4.9v because fuel is water contaminated (greater than 100% ethanol)
if (featurePWMout){
int analogOut = 255 * (4.9 / 5.0);
analogWrite(OUTPUT_PIN, int(analogOut));
}
if (featureDAC){
int dacOut = 4095 * (4.9 / 5.0);
dac.setVoltage(dacOut, false);
}
}
}
//////// ALSO DISPLAY DEBUG ERROR DATA //////////
LCD.clearDisplay();
LCD.setCursor(0, 0);
LCD.setTextSize(0);
LCD.print("Sensor Data = ");
LCD.println(pulsetime);
LCD.print("VoltsIn = ");
LCD.print(VInA1,1);
LCD.print(" v");
LCD.print(verz,1);
LCD.setTextSize(2);
LCD.println("");
LCD.println("Is Sensor");
LCD.println("Connected?");
LCD.display();
/////// ALSO SEND ERROR DATA TO BLUETOOTH CORDLESS TELEPHONES ////////
Serial.print("[");
Serial.print("=");
Serial.print(pulsetime);
Serial.print(",");
Serial.print("-,");
Serial.print("-,");
Serial.print(verz,1);
Serial.print(",");
Serial.print(VInA1,1);
Serial.print("]");
} else if (counttick < 2) {
//uncomment below to display debug ticks
//LCD.clearDisplay();
//LCD.setCursor(0, 0);
//LCD.setTextSize(4);
//LCD.println(counttick);
//LCD.display();
counttick++;
}
return;
}
//////////// CALCULATE ETHANOL CONTENT FROM PULSETIME & TEMP FROM DUTY CYCLE ///////////
// 50 HZ = 0% ethanol, 150 HZ = 100% ethanol
long eContent = int((1000000 / pulsetime) - eContentSkew);
if (eContentOverride){
eContent = eContentOverride;
}
if (eContent < 0) {
eContent = 0;
}
else if (eContent > 100) {
eContent = 100;
}
// 1 millisecond = -40C, 5 milliseconds = 125C
float frequency = float(1000000 / pulsetime);
float dutyCycle = 100 * (highTime / float(lowTime + highTime));
float totalTime = float(1.0 / frequency);
float period = float(100 - dutyCycle) * totalTime;
int temperature = 40.25 * 10 * period - 81.25;
int temperatureF = temperature * 1.8 + 32;
if (temperatureF < -39 || temperatureF > 250) {
// throw these results out, perhaps from noise?
return;
}
////////// SEND Vout to ANALOG & DAC /////////////
float desiredVoltage;
if (voltOUTRange){
desiredVoltage = mapf(eContent, 0, 100, 0.5, 4.5); // DAC out: (0.5 volts = 0%, 4.5 volts = 100%)
} else {
desiredVoltage = mapf(eContent, 0, 100, 0, 5); // Analog out: (0 volts = 0%, 5 volts = 100%)
}
if (featurePWMout){
int analogOut = 255 * (desiredVoltage / 5.0);
analogWrite(OUTPUT_PIN, int(analogOut));
}
if (featureDAC){
int dacOut = 4095 * (desiredVoltage / 5.0);
dac.setVoltage(dacOut, false);
}
////////// SEND DATA TO OLED DISPLAY ///////////////
LCD.clearDisplay();
LCD.setCursor(0, 0);
LCD.setTextSize(2);
if (temperatureF >= 100 || temperatureF <= -10) {
LCD.setCursor(0, 0);
} else {
LCD.setCursor(10, 0);
}
LCD.print(temperatureF);
LCD.print((char)247);
LCD.print("F/");
if (SwapFuelCwithA1){
LCD.print(AltA1);
LCD.print((char)247);
LCD.println("F");
} else {
LCD.print(temperature);
LCD.print((char)247);
LCD.println("C");
}
LCD.setTextSize(5);
LCD.setCursor(40, 20);
if ((eContent >= 10) && (eContent <= 99)) {
LCD.setCursor(20, 20);
} else if (eContent == 100) {
LCD.setCursor(4, 20);
}
LCD.print(eContent);
LCD.println("%");
////////////// IF ANALOG INPUT IS ENABLED, DISPLAY BAR GRAPH AND/OR TRIANGLE THINGIE ////////////
if (featureAnalogInA1) {
if (VInA1 >= 0.00 && VInA1 <= DangerToManifoldA1){
//////////////// TRIANGLE GAUGE //////////////
if (ShowTriangleA1) {
int bitsA1 = mapf(AverageVoltagebitsA1, GraphScalerP1A1, GraphScalerP2A1, 0, 1024);
int midpoint;
if (ReverseGraphA1) {
midpoint = 127 - (bitsA1 / 8); //reverses triangle graph
} else {
midpoint = (bitsA1 / 8); //normal triangle graph
}
int leftfoot = 0;
int rightfoot = 0;
if (midpoint > 127){
midpoint = 127;
}
if (midpoint < 0){
midpoint = 0;
}
if (midpoint < 5){
leftfoot = 0;
} else {
leftfoot = midpoint - 5;
}
if (midpoint > 122){
rightfoot = 127;
} else {
rightfoot = midpoint + 5;
}
LCD.fillTriangle(leftfoot,63,midpoint,57,rightfoot,63,WHITE); //triangle graph
}
//////// RECTANGLE BAR GRAPH ////////////
if (ShowBarGraphA1) {
int A1barsize;
int bitsA1 = mapf(AverageVoltagebitsA1, GraphScalerP1A1, GraphScalerP2A1, 0, 1024);
if (ReverseGraphA1) {
A1barsize = 127 - (bitsA1 / 8);
} else {
A1barsize = bitsA1 / 8;
}
LCD.fillRect(0,60,A1barsize,4,WHITE); //rectangle bar graph
}
///////// TICK MARKS /////////
if (ShowTickMarksA1) {
LCD.fillRect(0,58,1,6,WHITE); //tick mark
LCD.fillRect(31,61,1,3,WHITE); //tick mark
LCD.fillRect(63,58,1,6,WHITE); //tick mark
LCD.fillRect(95,61,1,3,WHITE); //tick mark
LCD.fillRect(127,58,1,6,WHITE); //tick mark
}
} else if (VInA1 > DangerToManifoldA1){
LCD.setTextSize(0);
LCD.setCursor(0, 57);
LCD.print(" !DANGER TO MANIFOLD!");
}
}
LCD.display();
/////////// SEND DATA TO BLUETOOTH CORDLESS TELEPHONES ////////////
Serial.print("[");
Serial.print(temperature);
Serial.print(",");
Serial.print(eContent);
Serial.print(",");
Serial.print(desiredVoltage,1);
Serial.print(",");
Serial.print(verz,1);
Serial.print(",");
Serial.print(VInA1,1);
Serial.print("]");
Watchdog.reset();
delay(refreshDelay);
Watchdog.reset();
counttick = 0;
}
//////////// FANCY SCALER ////////////
double mapf(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/////////// READ A1 INPUT ///////////
int MeasureVoltage()
{
int rawbitsA1 = analogRead(A1);
return rawbitsA1;
}
Loading
ssd1306
ssd1306