Created
August 4, 2020 17:29
-
-
Save ValentinFunk/177ad898d2f34e01eab283f37227e64f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This code is free software; you can redistribute it and/or | |
modify it under the terms of the GNU Lesser General Public | |
License as published by the Free Software Foundation; either | |
version 2.1 of the License, or (at your option) any later version. | |
This code is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
Lesser General Public License for more details. | |
You should have received a copy of the GNU Lesser General Public | |
License along with this library; if not, write to the Free Software | |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
*/ | |
// Version 0.5, 15.02.2020, AK-Homberger | |
#define ESP32_CAN_TX_PIN GPIO_NUM_2 | |
#define ESP32_CAN_RX_PIN GPIO_NUM_4 | |
#include <Arduino.h> | |
#include <NMEA2000_CAN.h> // This will automatically choose right CAN library and create suitable NMEA2000 object | |
#include <memory> | |
#include <N2kMessages.h> | |
#include <esp32-hal-adc.h> | |
#define ENABLE_DEBUG_LOG 0 // Debug log | |
#define ADC_Calibration_Value1 250.0 // For resistor measure 5 Volt and 180 Ohm equals 100% plus 1K resistor. | |
#define ADC_Calibration_Value2 34.3 // The real value depends on the true resistor values for the ADC input (100K / 27 K). | |
#include <driver/adc.h> | |
#include <esp_adc_cal.h> | |
// Set the information for other bus devices, which messages we support | |
const unsigned long TransmitMessages[] PROGMEM = {127505L, // Fluid Level | |
130311L, // Temperature (or alternatively 130312L or 130316L) | |
127488L, // Engine Rapid / RPM | |
127508L, // Battery Status | |
0 | |
}; | |
// RPM data. Generator RPM is measured on connector "W" | |
#define RPM_Calibration_Value 1.0 // Translates Generator RPM to Engine RPM | |
#define Eingine_RPM_Pin 23 // Engine RPM is measured as interrupt on pin 23 | |
volatile uint64_t StartValue; // First interrupt value | |
volatile uint64_t PeriodCount; // period in counts of 0.000001 of a second | |
unsigned long Last_int_time = 0; | |
hw_timer_t * timer = NULL; // pointer to a variable of type hw_timer_t | |
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // synchs between maon cose and interrupt? | |
// Send time offsets | |
#define TempSendOffset 0 | |
#define TankSendOffset 40 | |
#define RPM_SendOffset 80 | |
#define BatterySendOffset 100 | |
#define SlowDataUpdatePeriod 1000 // Time between CAN Messages sent | |
// Voltage measure is connected GPIO 35 (Analog ADC1_CH7) | |
const int ADCpin2 = 35; | |
// Tank fluid level measure is connected GPIO 34 (Analog ADC1_CH6) | |
const int ADCpin1 = 34; | |
// Global Data | |
float FuelLevel = 0; | |
float ExhaustTemp = 0; | |
double EngineRPM = 0; | |
float BatteryVolt =0; | |
// Task handle for OneWire read (Core 0 on ESP32) | |
TaskHandle_t Task1; | |
// Serial port 2 config (GPIO 16) | |
const int baudrate = 38400; | |
const int rs_config = SERIAL_8N1; | |
void debug_log(char* str) { | |
#if ENABLE_DEBUG_LOG == 1 | |
Serial.println(str); | |
#endif | |
} | |
// RPM Event Interrupt | |
// Enters on falling edge | |
//======================================= | |
volatile unsigned long rpmVals[1024] = {0}; | |
volatile unsigned long rpmIdx = 0; | |
const int RPM_VALS = 512; | |
void IRAM_ATTR handleInterrupt() | |
{ | |
portENTER_CRITICAL_ISR(&mux); | |
volatile uint64_t TempVal = timerRead(timer); // value of timer at interrupt 1000 1000000 / 1000 | |
if (TempVal - StartValue > 0) { | |
PeriodCount = TempVal - StartValue; // period count between rising edges in 0.000001 of a second | |
StartValue = TempVal; // puts latest reading as start for next calculation | |
} | |
rpmVals[(rpmIdx++) % RPM_VALS] = PeriodCount; | |
portEXIT_CRITICAL_ISR(&mux); | |
Last_int_time = millis(); | |
} | |
void setup() { | |
uint8_t chipid[6]; | |
uint32_t id =0; | |
int i=0; | |
// Init USB serial port | |
Serial.begin(115200); | |
// Init RPM measure | |
pinMode(Eingine_RPM_Pin, INPUT_PULLUP); // sets pin high | |
attachInterrupt(digitalPinToInterrupt(Eingine_RPM_Pin), handleInterrupt, FALLING); // attaches pin to interrupt on Falling Edge | |
timer = timerBegin(0, 80, true); // this returns a pointer to the hw_timer_t global variable | |
// 0 = first timer | |
// 80 is prescaler so 80MHZ divided by 80 = 1MHZ signal ie 0.000001 of a second | |
// true - counts up | |
timerStart(timer); // starts the timer | |
// Valle attenuation | |
adc1_config_width(ADC_WIDTH_12Bit); | |
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); | |
// Reserve enough buffer for sending all messages. This does not work on small memory devices like Uno or Mega | |
NMEA2000.SetN2kCANMsgBufSize(8); | |
NMEA2000.SetN2kCANReceiveFrameBufSize(250); | |
NMEA2000.SetN2kCANSendFrameBufSize(250); | |
esp_efuse_read_mac(chipid); | |
for (i=0;i<6;i++) id += (chipid[i]<<(7*i)); | |
// Set product information | |
NMEA2000.SetProductInformation("1", // Manufacturer's Model serial code | |
100, // Manufacturer's product code | |
"My Sensor Module", // Manufacturer's Model ID | |
"1.0.2.25 (2019-07-07)", // Manufacturer's Software version code | |
"1.0.2.0 (2019-07-07)" // Manufacturer's Model version | |
); | |
// Set device information | |
NMEA2000.SetDeviceInformation(id, // Unique number. Use e.g. Serial number. | |
132, // Device function=Analog to NMEA 2000 Gateway. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf | |
25, // Device class=Inter/Intranetwork Device. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf | |
2046 // Just choosen free from code list on http://www.nmea.org/Assets/20121020%20nmea%202000%20registration%20list.pdf | |
); | |
// If you also want to see all traffic on the bus use N2km_ListenAndNode instead of N2km_NodeOnly below | |
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); // Show in clear text. Leave uncommented for default Actisense format. | |
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, 32); | |
NMEA2000.ExtendTransmitMessages(TransmitMessages); | |
NMEA2000.Open(); | |
delay(200); | |
} | |
// Calculate engine RPM from number of interupts per time | |
double ReadRPM() { | |
double RPM=0; | |
portENTER_CRITICAL(&mux); | |
unsigned long smoothRpm = 0.f; | |
for (int i = 0; i < RPM_VALS; i++) { | |
smoothRpm += rpmVals[i]; | |
} | |
unsigned long periodSmooth = smoothRpm / RPM_VALS; | |
RPM = 1000000.00 / periodSmooth; // PeriodCount in 0.000001 of a second | |
portEXIT_CRITICAL(&mux); | |
if (millis() > Last_int_time + 200) RPM = 0; // No signals RPM=0; | |
return RPM * 60; | |
} | |
bool IsTimeToUpdate(unsigned long NextUpdate) { | |
return (NextUpdate < millis()); | |
} | |
unsigned long InitNextUpdate(unsigned long Period, unsigned long Offset = 0) { | |
return millis() + Period + Offset; | |
} | |
void SetNextUpdate(unsigned long &NextUpdate, unsigned long Period) { | |
while ( NextUpdate < millis() ) NextUpdate += Period; | |
} | |
void SendN2kBattery(double BatteryVoltage) { | |
static unsigned long SlowDataUpdated = InitNextUpdate(SlowDataUpdatePeriod, BatterySendOffset); | |
tN2kMsg N2kMsg; | |
if ( IsTimeToUpdate(SlowDataUpdated) ) { | |
SetNextUpdate(SlowDataUpdated, SlowDataUpdatePeriod); | |
//Serial.printf("Voltage : %3.0f ", BatteryVoltage); | |
//Serial.println("%"); | |
SetN2kDCBatStatus(N2kMsg, 0, BatteryVoltage, N2kDoubleNA, N2kDoubleNA, 1); | |
NMEA2000.SendMsg(N2kMsg); | |
} | |
} | |
void SendN2kTankLevel(double level, double capacity) { | |
static unsigned long SlowDataUpdated = InitNextUpdate(SlowDataUpdatePeriod, TankSendOffset); | |
tN2kMsg N2kMsg; | |
if ( IsTimeToUpdate(SlowDataUpdated) ) { | |
SetNextUpdate(SlowDataUpdated, SlowDataUpdatePeriod); | |
Serial.printf("Fuel Level : %3.0f ", level); | |
Serial.println("%"); | |
SetN2kFluidLevel(N2kMsg, 0, N2kft_Fuel, level, capacity ); | |
NMEA2000.SendMsg(N2kMsg); | |
} | |
} | |
void SendN2kExhaustTemp(double temp) { | |
static unsigned long SlowDataUpdated = InitNextUpdate(SlowDataUpdatePeriod, TempSendOffset); | |
tN2kMsg N2kMsg; | |
if ( IsTimeToUpdate(SlowDataUpdated) ) { | |
SetNextUpdate(SlowDataUpdated, SlowDataUpdatePeriod); | |
//Serial.printf("Exhaust Temp: %3.0f °C \n", temp); | |
// Select the right PGN for your MFD and set the PGN value also in "TransmitMessages[]" | |
SetN2kEnvironmentalParameters(N2kMsg, 0, N2kts_ExhaustGasTemperature, CToKelvin(temp), // PGN130311, uncomment the PGN to be used | |
N2khs_Undef, N2kDoubleNA, N2kDoubleNA); | |
// SetN2kTemperature(N2kMsg, 0, 0, N2kts_ExhaustGasTemperature, CToKelvin(temp), N2kDoubleNA); // PGN130312, uncomment the PGN to be used | |
// SetN2kTemperatureExt(N2kMsg, 0, 0, N2kts_ExhaustGasTemperature,CToKelvin(temp), N2kDoubleNA); // PGN130316, uncomment the PGN to be used | |
NMEA2000.SendMsg(N2kMsg); | |
} | |
} | |
void SendN2kEngineRPM(double RPM) { | |
static unsigned long SlowDataUpdated = InitNextUpdate(SlowDataUpdatePeriod, RPM_SendOffset); | |
tN2kMsg N2kMsg; | |
if ( IsTimeToUpdate(SlowDataUpdated) ) { | |
SetNextUpdate(SlowDataUpdated, SlowDataUpdatePeriod); | |
const double factorCalibrate = 14.85; | |
Serial.printf("Engine RPM :%f RPM \n", RPM / factorCalibrate); | |
SetN2kEngineParamRapid(N2kMsg, 0, RPM / factorCalibrate, N2kDoubleNA, N2kInt8NA); | |
NMEA2000.SendMsg(N2kMsg); | |
} | |
} | |
// ReadVoltage is used to improve the linearity of the ESP32 ADC see: https://github.com/G6EJD/ESP32-ADC-Accuracy-Improvement-function | |
double ReadVoltage(byte pin) { | |
double reading = analogRead(pin); // Reference voltage is 3v3 so maximum reading is 3v3 = 4095 in range 0 to 4095 | |
if (reading < 1 || reading > 4095) return 0; | |
// return -0.000000000009824 * pow(reading,3) + 0.000000016557283 * pow(reading,2) + 0.000854596860691 * reading + 0.065440348345433; | |
return (-0.000000000000016 * pow(reading, 4) + 0.000000000118171 * pow(reading, 3) - 0.000000301211691 * pow(reading, 2) + 0.001109019271794 * reading + 0.034143524634089) * 1000; | |
} // Added an improved polynomial, use either, comment out as required | |
float fuelReadings[256] = {0}; | |
float lastReading = 0; | |
unsigned char fuelReadingIdx = 0; | |
void loop() { | |
unsigned int size; | |
// Serial.printf("Voltage raw 1\t%3.0f\n", ReadVoltage(ADCpin2)); | |
float reading = ReadVoltage(ADCpin1); | |
// x = (y-244.43)/4.101518 | |
// float ohmRead = (lastReading - 244.43) / 4.101518; | |
// float batteryPercentage = ( ohmRead - 5.f ) / 175.f; | |
// Serial.printf("Raw:\t%3.0f\tSmooth:%3.0f\tPerc:%3.0f\n", reading, lastReading, ohmRead); | |
float fuelLevel = 0.08496117 * reading - 8.381138; | |
const char VALUES = 20; | |
fuelReadings[(fuelReadingIdx++) % VALUES] = fuelLevel; | |
float sum = 0; | |
for (unsigned char i = 0; i < VALUES; i++) { | |
sum += fuelReadings[i]; | |
} | |
FuelLevel = sum / VALUES - 13.; // ((FuelLevel * 15) + (ReadVoltage(ADCpin1) * ADC_Calibration_Value1 / 4096)) / 16; // This implements a low pass filter to eliminate spike for ADC readings | |
BatteryVolt = ((BatteryVolt * 15) + (ReadVoltage(ADCpin2) * ADC_Calibration_Value2 / 4096)) / 16; // This implements a low pass filter to eliminate spike for ADC readings | |
EngineRPM = ReadRPM(); // This implements a low pass filter to eliminate spike for RPM measurements | |
// if (FuelLevel>100) FuelLevel=100; | |
SendN2kTankLevel(FuelLevel, 200); // Adjust max tank capacity. Is it 200 ??? | |
SendN2kExhaustTemp(ExhaustTemp); | |
SendN2kEngineRPM(EngineRPM); | |
SendN2kBattery (BatteryVolt); | |
delay(50); | |
// Dummy to empty input buffer to avoid board to stuck with e.g. NMEA Reader | |
if ( Serial.available() ) { | |
Serial.read(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment