
It worked, but it looked kinda janky and I didn't have a good place to mount it. The next revision was using a small OLED screen that worked great.

It was hidden under the ashtray cover unless I needed to see it. Fast forward a few months, I had installed the RSNav 10.25" unit in my car and thought it'd be cool to display my ethanol content on the tablet through BT. I wrote an android app using MIT AppInventor (I'm not a code monkey) and picked up a BT module for use with arduino. This is my current iteration and it works great on the app.

I'm providing instructions on how to do each of these 3 variations. First, parts you'll need:
Arduino Nano - https://www.amazon.com/Gikfun-ATmega...dp/B00Q9YBO88/ (use this one if you want a cable with it: https://www.amazon.com/Arduino-Elego.../dp/B071NMBP4S)
Ethanol Sensor - https://www.amazon.com/Composition-S.../dp/B07413HD6K
Pigtail for sensor - https://www.amazon.com/Boost-Connect.../dp/B06XRDNZ4Z or http://www.oreillyauto.com/site/c/de...4785/N1590.oap
Fuel fittings - https://www.amazon.com/gp/product/B000E323JO
Optional:
OLED screen - https://www.amazon.com/Diymall-Seria.../dp/B00O2KDQBE
BlueTooth - https://www.amazon.com/gp/product/B00OLL9XH0
You'll need some way to tap into 12V somewhere, that's up to you on how. whiped made a great post on how he installed his Str8shot ECA:
http://www.audizine.com/forum/showth...ntent-Analyzer
The only thing I did differently was run mine on the passenger side. There's no hole in the firewall there, but I drilled a small one, used some paint to seal the hole and then put a rubber grommet in place. The wire comes through right by the blower motor so it's hidden. I then tapped into the passenger fuse panel for accessory power.
For the Android software, if you want to compile yourself you'll need to use MIT AppInventor
For the Arduino code, you can use their web portal for everything: https://create.arduino.cc/editor
You'll likely need to grab the libraries for Adafruit_SSD1306 and Adafruit_GFX if you're using the OLED.
1. ECA with 0-5v output for use with P3 gauge or whatever
Schematic:

The code was not originally written by me. I saw no reason to re-invent the wheel when this guy's code worked. This was written for use with an output pin for a gauge (or ECU as this guy did) and with an 16x2 LCD. If you're not using the LCD you can strip that code out or just leave it, it won't hurt anything. I haven't really debugged this one as I just used it for the basis of my other projects. The guy does suggest putting a 3.3k ohm resister on the output pin to help smooth out voltage readings.
Code:
Code:
/******************************************************* This program will sample a 50-150hz signal depending on ethanol content, and output a 0-5V signal via PWM. The LCD (for those using an Arduino Uno + LCD shield) will display ethanol content, hz input, mv output, fuel temp Connect PWM output to Output. 3.3kOhm resistor works fine. Input pin 8 (PB0) ICP1 on Atmega328 Output pin 3 or 11, defined below ********************************************************/ // include the library code: #include <LiquidCrystal.h> //LCD plugin // initialize the library with the numbers of the interface pins LiquidCrystal lcd(2, 9, 4, 5, 6, 7); //LCD Keypad Shield int inpPin = 8; //define input pin to 8 int outPin = 11; //define PWM output, possible pins with LCD and 32khz freq. are 3 and 11 (Nano and Uno) //Define global variables volatile uint16_t revTick; //Ticks per revolution uint16_t pwm_output = 0; //integer for storing PWM value (0-255 value) int HZ; //unsigned 16bit integer for storing HZ input int ethanol = 0; //Store ethanol percentage here float expectedv; //store expected voltage here - range for typical GM sensors is usually 0.5-4.5v int duty; //Duty cycle (0.0-100.0) float period; //Store period time here (eg.0.0025 s) float temperature = 0; //Store fuel temperature here int fahr = 0; int cels = 0; static long highTime = 0; static long lowTime = 0; static long tempPulse; void setupTimer() // setup timer1 { TCCR1A = 0; // normal mode TCCR1B = 132; // (10000100) Falling edge trigger, Timer = CPU Clock/256, noise cancellation on TCCR1C = 0; // normal mode TIMSK1 = 33; // (00100001) Input capture and overflow interupts enabled TCNT1 = 0; // start from 0 } ISR(TIMER1_CAPT_vect) // PULSE DETECTED! (interrupt automatically triggered, not called by main program) { revTick = ICR1; // save duration of last revolution TCNT1 = 0; // restart timer for next revolution } ISR(TIMER1_OVF_vect) // counter overflow/timeout { revTick = 0; } // Ticks per second = 0 void setup() { Serial.begin(9600); pinMode(inpPin,INPUT); setPwmFrequency(outPin,1); //Modify frequency on PWM output setupTimer(); // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Initial screen formatting lcd.setCursor(0, 0); lcd.print("Ethanol: %"); lcd.setCursor(0, 1); lcd.print(" Hz C"); } void loop() { getfueltemp(inpPin); //read fuel temp from input duty cycle if (revTick > 0) // Avoid dividing by zero, sample in the HZ {HZ = 62200 / revTick;} // 3456000ticks per minute, 57600 per second else {HZ = 0;} //needs real sensor test to determine correct tickrate //calculate ethanol percentage if (HZ > 50) // Avoid dividing by zero {ethanol = (HZ-50);} else {ethanol = 0;} if (ethanol > 99) // Avoid overflow in PWM {ethanol = 99;} expectedv = ((((HZ-50.0)*0.01)*4)+0.5); //Screen calculations pwm_output = 1.1 * (255 * (expectedv/5.0)); //calculate output PWM for ECU lcd.setCursor(10, 0); lcd.print(ethanol); lcd.setCursor(2, 1); lcd.print(HZ); lcd.setCursor(8, 1); lcd.print(temperature); //Use this for celsius //PWM output analogWrite(outPin, pwm_output); //write the PWM value to output pin delay(100); //make screen more easily readable by not updating it too often Serial.println(ethanol); Serial.println(pwm_output); Serial.println(expectedv); Serial.println(HZ); delay(1000); } void getfueltemp(int inpPin){ //read fuel temp from input duty cycle highTime = 0; lowTime = 0; tempPulse = pulseIn(inpPin,HIGH); if(tempPulse>highTime){ highTime = tempPulse; } tempPulse = pulseIn(inpPin,LOW); if(tempPulse>lowTime){ lowTime = tempPulse; } duty = ((100*(highTime/(double (lowTime+highTime))))); //Calculate duty cycle (integer extra decimal) float T = (float(1.0/float(HZ))); //Calculate total period time float period = float(100-duty)*T; //Calculate the active period time (100-duty)*T float temp2 = float(10) * float(period); //Convert ms to whole number temperature = ((40.25 * temp2)-81.25); //Calculate temperature for display (1ms = -40, 5ms = 80) int cels = int(temperature); float fahrtemp = ((temperature*1.8)+32); } void setPwmFrequency(int pin, int divisor) { //This code snippet raises the timers linked to the PWM outputs byte mode; //This way the PWM frequency can be raised or lowered. Prescaler of 1 sets PWM output to 32KHz (pin 3, 11) if(pin == 5 || pin == 6 || pin == 9 || pin == 10) { switch(divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 64: mode = 0x03; break; case 256: mode = 0x04; break; case 1024: mode = 0x05; break; default: return; } if(pin == 5 || pin == 6) { TCCR0B = TCCR0B & 0b11111000 | mode; } else { TCCR1B = TCCR1B & 0b11111000 | mode; } } else if(pin == 3 || pin == 11) { switch(divisor) { case 1: mode = 0x01; break; case 8: mode = 0x02; break; case 32: mode = 0x03; break; case 64: mode = 0x04; break; case 128: mode = 0x05; break; case 256: mode = 0x06; break; case 1024: mode = 0x7; break; default: return; } TCCR2B = TCCR2B & 0b11111000 | mode; } }
Schemetic:

Code:
Code:
// Adafruit_SSD1306 - Version: Latest #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 4 Adafruit_SSD1306 display(OLED_RESET); int inpPin = 8; //Define global variables volatile uint16_t revTick; //Ticks per revolution uint16_t pwm_output = 0; //integer for storing PWM value (0-255 value) int HZ = 0; //unsigned 16bit integer for storing HZ input int ethanol = 0; //Store ethanol percentage here float expectedv; //store expected voltage here - range for typical GM sensors is usually 0.5-4.5v uint16_t voltage = 0; //store display millivoltage here (0-5000) //temperature variables int duty; //Duty cycle (0.0-100.0) float period; //Store period time here (eg.0.0025 s) float temperature = 0; //Store fuel temperature here int fahr = 0; int cels = 0; int celstemp = 0; float fahrtemp = 0; static long highTime = 0; static long lowTime = 0; static long tempPulse; void setupTimer() // setup timer1 { TCCR1A = 0; // normal mode TCCR1B = 132; // (10000100) Falling edge trigger, Timer = CPU Clock/256, noise cancellation on TCCR1C = 0; // normal mode TIMSK1 = 33; // (00100001) Input capture and overflow interupts enabled TCNT1 = 0; // start from 0 } ISR(TIMER1_CAPT_vect) // PULSE DETECTED! (interrupt automatically triggered, not called by main program) { revTick = ICR1; // save duration of last revolution TCNT1 = 0; // restart timer for next revolution } ISR(TIMER1_OVF_vect) // counter overflow/timeout { revTick = 0; } // Ticks per second = 0 void setup() { setupTimer(); Serial.begin(9600); // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128x64) // init done } void loop() { getfueltemp(inpPin); //read fuel temp from input duty cycle if (revTick > 0) // Avoid dividing by zero, sample in the HZ {HZ = 62200 / revTick;} // 3456000ticks per minute, 57600 per second else // 62200 calibrated for more accuracy {HZ = 0;} //calculate ethanol percentage if (HZ > 50) // Avoid dividing by zero {ethanol = HZ-50;} else {ethanol = 0;} if (ethanol > 99) // Avoid overflow in PWM {ethanol = 99;} //Screen calculations display.display(); display.clearDisplay(); display.setCursor(0,16); display.setTextSize(2); display.setTextColor(WHITE); display.print("Eth: "); display.print(ethanol); display.print("%"); display.setCursor(0,36); display.print("Temp: "); display.print(cels); display.print("C"); delay(250); } void getfueltemp(int inpPin){ //read fuel temp from input duty cycle highTime = 0; lowTime = 0; tempPulse = pulseIn(inpPin,HIGH); if(tempPulse>highTime){ highTime = tempPulse; } tempPulse = pulseIn(inpPin,LOW); if(tempPulse>lowTime){ lowTime = tempPulse; } duty = ((100*(highTime/(double (lowTime+highTime))))); //Calculate duty cycle (integer extra decimal) float T = (float(1.0/float(HZ))); //Calculate total period time float period = float(100-duty)*T; //Calculate the active period time (100-duty)*T float temp2 = float(10) * float(period); //Convert ms to whole number temperature = ((40.25 * temp2)-81.25); //Calculate temperature for display (1ms = -40, 5ms = 80) celstemp = int(temperature); cels = celstemp; fahrtemp = ((temperature*1.8)+32); fahr = fahrtemp; }
Schematic:

A bunch of this code was also already written, but I pieced it together and made the timers work with each other. Here are links to the compiled APK and the AIA file for use with MIT AppInventor:
APK: https://drive.google.com/file/d/0B6y...mFLuQB42KlntSQ
AIA: https://drive.google.com/file/d/0B6y...ibqnQD2-lQ_Jqw
If you use the AIA and compile the APK yourself, you can set the BT MAC address so that it always autoconnects on start. If you don't, you'll just have to manually select the device each time.
Code:
Code:
/* Connect to Android via serial Bluetooth and demonstrate the use of interrupt A serial Bluetooth module is used to create a connection with an Android app (created with MIT AppInventor). Every n seconds (where n is a parameter set through the app) a status report is sent to the app. A simple command structure enables the app to send parameters and values to Arduino and the other way round. The circuit: * HC-06 Bluetooth Wireless Serial Port Module (slave) connected as follows: VCC <--> 3.3V GND <--> GND TXD <--> Pin 0 (Rx) RXD <--> Pin 1 (Tx) The Bluetooth module may interfere with PC to Arduino communication: disconnect VCC when programming the board created 2014 by Paolo Mosconi modified 2017 for use with Ethanol Content Analyzer by pabohoney1 This example code is in the public domain. */ // Serial Parameters: COM11 9600 8 N 1 // \r or \n to end command line // Bluetooth is on Pin 0 & 1 @ 9600 speed // Command structure // CMD SECONDS=value // CMD STATUS // Status message structure // STATUS ETH|TEMP=value //Ethanol Global Vars int inpPin = 8; volatile uint16_t revTick; //Ticks per revolution uint16_t pwm_output = 0; //integer for storing PWM value (0-255 value) int HZ = 0; //unsigned 16bit integer for storing HZ input int ethanol = 0; //Store ethanol percentage here float expectedv; //store expected voltage here - range for typical GM sensors is usually 0.5-4.5v uint16_t voltage = 0; //store display millivoltage here (0-5000) //temperature variables int duty; //Duty cycle (0.0-100.0) float period; //Store period time here (eg.0.0025 s) float temperature = 0; //Store fuel temperature here int fahr = 0; int cels = 0; int celstemp = 0; float fahrtemp = 0; static long highTime = 0; static long lowTime = 0; static long tempPulse; //BT Global Vars int maxSeconds = 1; // send status message every maxSeconds const int ledPin = 13; // temperature led volatile int seconds = 0; volatile boolean statusReport = false; String inputString = ""; String command = ""; String value = ""; boolean stringComplete = false; /* The following timer code is needed to initialize the timer interrupt and set it to fire every .016384 seconds, the slowest timer0 can go For detailed information see: http://www.instructables.com/id/Arduino-Timer-Interrupts/step1/Prescalers-and-the-Compare-Match-Register/ */ void setupBTTimer() //setup timer0 { // initialize Timer0 for 61Hz TCCR0A = 0; // set entire TCCR1A register to 0 TCCR0B = 0; // same for TCCR1B // set compare match register to desired timer count: OCR0A = 255; // turn on CTC mode: TCCR0B |= (1 << WGM12); // Set CS10 and CS12 bits for 1024 prescaler: TCCR0B |= (1 << CS10); TCCR0B |= (1 << CS12); // enable timer compare interrupt: TIMSK0 |= (1 << OCIE1A); } void setupECATimer() // setup timer1 { // initialize timer1 for 1HZ / 1s TCCR1A = 0; // normal mode TCCR1B = 132; // (10000100) Falling edge trigger, Timer = CPU Clock/256, noise cancellation on TCCR1C = 0; // normal mode TIMSK1 = 33; // (00100001) Input capture and overflow interupts enabled TCNT1 = 0; // start from 0 } void setup(){ //start serial connection Serial.begin(9600); inputString.reserve(50); command.reserve(50); value.reserve(50); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); cli(); // disable global interrupts setupECATimer(); setupBTTimer(); sei(); // enable global interrupts //Serial.print("AT+NAMEArduinoBT"); } ISR(TIMER0_COMPA_vect) { if (seconds++ >= (maxSeconds * 61)) { //multiply by 61 so that we can essentially get 1 per second statusReport = true; seconds = 0; } } ISR(TIMER1_CAPT_vect) // PULSE DETECTED! (interrupt automatically triggered, not called by main program) { revTick = ICR1; // save duration of last revolution TCNT1 = 0; // restart timer for next revolution } ISR(TIMER1_OVF_vect) // counter overflow/timeout { revTick = 0; } // Ticks per second = 0 // interpret and execute command when received // then report status if flag raised by timer interrupt void loop(){ int intValue = 0; getfueltemp(inpPin); //read fuel temp from input duty cycle if (revTick > 0) // Avoid dividing by zero, sample in the HZ {HZ = 62200 / revTick;} // 3456000ticks per minute, 57600 per second else // 62200 calibrated for more accuracy {HZ = 0;} //calculate ethanol percentage if (HZ > 50) // Avoid dividing by zero {ethanol = HZ-50;} else {ethanol = 0;} if (ethanol > 99) // Avoid overflow in PWM {ethanol = 99;} if (statusReport) { // Output ethanol% and temp in cels separated by comma Serial.print(ethanol); Serial.print(","); Serial.println(cels); statusReport = false; } } /* SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available. */ void serialEvent() { while (Serial.available()) { // get the new byte: char inChar = (char)Serial.read(); //Serial.write(inChar); // add it to the inputString: inputString += inChar; // if the incoming character is a newline or a carriage return, set a flag // so the main loop can do something about it: if (inChar == '\n' || inChar == '\r') { stringComplete = true; } } } void getfueltemp(int inpPin){ //read fuel temp from input duty cycle highTime = 0; lowTime = 0; tempPulse = pulseIn(inpPin,HIGH); if(tempPulse>highTime){ highTime = tempPulse; } tempPulse = pulseIn(inpPin,LOW); if(tempPulse>lowTime){ lowTime = tempPulse; } duty = ((100*(highTime/(double (lowTime+highTime))))); //Calculate duty cycle (integer extra decimal) float T = (float(1.0/float(HZ))); //Calculate total period time float period = float(100-duty)*T; //Calculate the active period time (100-duty)*T float temp2 = float(10) * float(period); //Convert ms to whole number temperature = ((40.25 * temp2)-81.25); //Calculate temperature for display (1ms = -40, 5ms = 80) celstemp = int(temperature); cels = celstemp; fahrtemp = ((temperature*1.8)+32); fahr = fahrtemp; }
Bookmarks