Merge pull request #357 from smaresca/smaresca/CHADEMO-Shunt-Refactor

CHAdeMO - shunt refactor
This commit is contained in:
Daniel Öster 2024-07-08 20:36:02 +03:00 committed by GitHub
commit 43c06180eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 404 additions and 752 deletions

View file

@ -22,7 +22,6 @@
#include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h"
#include "src/lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "src/lib/smaresca-SimpleISA/SimpleISA.h"
#include "src/datalayer/datalayer.h"
@ -65,10 +64,6 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
ModbusServerRTU MBserver(Serial2, 2000);
#endif
#ifdef ISA_SHUNT
ISA sensor;
#endif
// Common charger parameters
volatile float charger_setpoint_HV_VDC = 0.0f;
volatile float charger_setpoint_HV_IDC = 0.0f;
@ -522,10 +517,6 @@ void receive_can() { // This section checks if we have a complete CAN message i
CAN_frame_t rx_frame;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 0) == pdTRUE) {
//ISA Shunt
#ifdef ISA_SHUNT
sensor.handleFrame(&rx_frame);
#endif
// Battery
#ifndef SERIAL_LINK_RECEIVER // Only needs to see inverter
receive_can_battery(rx_frame);

View file

@ -54,7 +54,6 @@
#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings.
//#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides any battery settings set in USER_SETTINGS.cpp)
//#define FUNCTION_TIME_MEASUREMENT // Enable this to record execution times and present them in the web UI (WARNING, raises CPU load, do not use for production)
//#define ISA_SHUNT //Enable this line to build support for ISA IVT shunts
/* MQTT options */
// #define MQTT // Enable this line to enable MQTT

View file

@ -4,11 +4,9 @@
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef ISA_SHUNT
#include "../lib/smaresca-SimpleISA/SimpleISA.h"
#endif
#include "CHADEMO-BATTERY-INTERNAL.h"
#include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
/* CHADEMO handling runs at 6.25 times the rate of most other code, so, rather than the
* default value of 12 (for 12 iterations of the 5s value update loop) * 5 for a 60s timeout,
@ -26,7 +24,6 @@ static unsigned long handlerAfterMillis = 0;
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis5000 =
0; // will store last time a 5s threshold was reached for display during debug
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
bool plug_inserted = false;
bool vehicle_can_initialized = false;
@ -42,10 +39,6 @@ uint8_t framecount = 0;
uint8_t max_discharge_current = 0; //TODO not sure on this one, but really influenced by inverter capability
#ifdef ISA_SHUNT
extern ISA sensor;
#endif
bool high_current_control_enabled = false; // set to true when high current control is operating
// if true, values from 110.1 and 110.2 should be used instead of 102.3
// and 118 should be used for evse responses
@ -134,7 +127,7 @@ void update_values_battery() {
datalayer.battery.status.max_discharge_power_W =
(x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P
datalayer.battery.status.voltage_dV = sensor.Voltage * 10;
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
datalayer.battery.info.total_capacity_Wh =
((x101_chg_est.RatedBatteryCapacity / 0.11) *
@ -340,14 +333,12 @@ inline void process_vehicle_charging_limits(CAN_frame_t rx_frame) {
*/
#endif
#ifdef ISA_SHUNT
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
#endif
}
/* Vehicle 0x201, peer to EVSE 0x209
@ -544,18 +535,18 @@ void update_evse_status(CAN_frame_t& f) {
x109_evse_state.remaining_time_1m = 60;
} else if (EVSE_mode == CHADEMO_CHARGE) {
#ifdef ISA_SENSOR
x109_evse_state.setpoint_HV_VDC = sensor.Voltage;
x109_evse_state.setpoint_HV_IDC = sensor.Amperes;
#else
//NOTE: these are supposed to be measured values, e.g., from a shunt
//If a sensor is not used, we are literally asserting that the measured value is exactly equivalent to the request or max charger capability
//this is pretty likely to fail on most vehicles
x109_evse_state.setpoint_HV_VDC = get_measured_voltage();
x109_evse_state.setpoint_HV_IDC = get_measured_current();
/*For posterity if anyone is forced to simulate a shunt
NOTE: these are supposed to be measured values, e.g., from a shunt
If a sensor is not used, we are literally asserting that the measured value is exactly equivalent to the request or max charger capability
this is pretty likely to fail on most vehicles
x109_evse_state.setpoint_HV_VDC =
min(x102_chg_session.TargetBatteryVoltage, x108_evse_cap.available_output_voltage);
x109_evse_state.setpoint_HV_IDC =
min(x102_chg_session.ChargingCurrentRequest, x108_evse_cap.available_output_current);
#endif
*/
/* The spec suggests throwing a 109.5.4 = 1 if vehicle curr request 102.3 > evse curr available 108.3,
* but realistically many chargers seem to act tolerant here and stay under limits and supply whatever they are able
@ -577,7 +568,7 @@ void update_evse_status(CAN_frame_t& f) {
*/
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
//Toggl battery incompatibility flag 109.5.3
//Toggle battery incompatibility flag 109.5.3
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.battery_incompatible = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -626,16 +617,15 @@ void update_evse_discharge_estimate(CAN_frame_t& f) {
/* x208 EVSE, peer to 0x200 Vehicle */
void update_evse_discharge_capabilities(CAN_frame_t& f) {
#ifdef ISA_SHUNT
//present discharge current is a measured value
x208_evse_dischg_cap.present_discharge_current = 0xFF - sensor.Amperes;
#else
//Present discharge current is a measured value. In the absence of
// a shunt, the evse here is quite literally lying to the vehicle. The spec
// seems to suggest this is tolerated unless the current measured on the EV
// side continualy exceeds the maximum discharge current by 10amps
x208_evse_dischg_cap.present_discharge_current = 0xFF - get_measured_current();
/* Present discharge current is a measured value. In the absence of
a shunt, the evse here is quite literally lying to the vehicle. The spec
seems to suggest this is tolerated unless the current measured on the EV
side continualy exceeds the maximum discharge current by 10amps
x208_evse_dischg_cap.present_discharge_current = 0xFF - 6;
#endif
*/
//EVSE maximum current input is partly an inverter-influenced value i.e., min(inverter, vehicle_max_discharge)
//use max_discharge_current variable if nonzero, otherwise tell the vehicle the EVSE will take everything it can give
@ -764,7 +754,6 @@ void send_can_battery() {
*/
void handle_chademo_sequence() {
unsigned long currentMillis = millis();
precharge_low = digitalRead(PRECHARGE_PIN) == LOW;
positive_high = digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH;
contactors_ready = precharge_low && positive_high;
@ -894,7 +883,7 @@ void handle_chademo_sequence() {
}
*/
if (x102_chg_session.s.status.StatusVehicleChargingEnabled) {
if (sensor.Voltage < 20) {
if (get_measured_voltage() < 20) {
digitalWrite(CHADEMO_PIN_10, HIGH);
evse_permission = true;
@ -942,7 +931,7 @@ void handle_chademo_sequence() {
#ifdef DEBUG_VIA_USB
Serial.println("Contactors ready");
Serial.print("Voltage: ");
Serial.println(sensor.Voltage);
Serial.println(get_measured_voltage());
#endif
/* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
@ -980,14 +969,14 @@ void handle_chademo_sequence() {
//TODO flag error and do not calculate power in EVSE response?
// probably unnecessary as other flags will be set causing this to be caught
}
#ifdef ISA_SHUNT
if (sensor.Voltage <= x200_discharge_limits.MinimumDischargeVoltage) {
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
#endif
// Potentially unnecessary (set in CHADEMO_EVSE_CONTACTORS_ENABLED stanza), but just in case
x109_evse_state.s.status.ChgDischStopControl = 0;
x109_evse_state.s.status.EVSE_status = 1;
@ -1012,7 +1001,7 @@ void handle_chademo_sequence() {
* We will re-enter the handler until the amperage drops sufficiently
* and then transition to CHADEMO_IDLE
*/
if (sensor.Amperes <= 5 && sensor.Voltage <= 10) {
if (get_measured_current() <= 5 && get_measured_voltage() <= 10) {
/* welding detection ideally here */
digitalWrite(CHADEMO_PIN_10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW);

View file

@ -0,0 +1,354 @@
/* Portions of this file are an adaptation of the SimpleISA library, originally authored by Jack Rickard.
*
* At present, this code supports the Scale IVT Modular current/voltage sensor device.
* These devices measure current, up to three voltages, and provide temperature compensation.
* Additional sensors are planned to provide flexibility/lower BOM costs.
*
* Original license/copyright header of SimpleISA is shown below:
* This library was written by Jack Rickard of EVtv - http://www.evtv.me
* copyright 2014
* You are licensed to use this library for any purpose, commercial or private,
* without restriction.
*
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
*
*/
#include "../include.h"
#ifdef CHADEMO_BATTERY
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "CHADEMO-BATTERY-INTERNAL.h"
#include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
/* Initial frames received from ISA shunts provide invalid during initialization */
static int framecount = 0;
/* original variables/names/types from SimpleISA. These warrant refinement */
float Amperes; // Floating point with current in Amperes
double AH; //Floating point with accumulated ampere-hours
double KW;
double KWH;
double Voltage;
double Voltage1;
double Voltage2;
double Voltage3;
double VoltageHI;
double Voltage1HI;
double Voltage2HI;
double Voltage3HI;
double VoltageLO;
double Voltage1LO;
double Voltage2LO;
double Voltage3LO;
double Temperature;
bool firstframe;
double milliamps;
long watt;
long As;
long lastAs;
long wh;
long lastWh;
/* Output command frame used to alter or initialize ISA shunt behavior
* Please note that all delay/sleep operations are solely in this section of code,
* not used during normal operation. Such delays are currently commented out.
*/
CAN_frame_t outframe = {.FIR = {.B =
{
.DLC = 8,
.unknown_2 = 0,
.RTR = CAN_no_RTR,
.FF = CAN_frame_std,
}},
.MsgID = 0x411,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t get_measured_voltage() {
return (uint16_t)Voltage;
}
uint16_t get_measured_current() {
return (uint16_t)Amperes;
}
//This is our CAN interrupt service routine to catch inbound frames
inline void ISA_handleFrame(CAN_frame_t* frame) {
if (frame->MsgID < 0x521 || frame->MsgID > 0x528) {
return;
}
framecount++;
switch (frame->MsgID) {
case 0x511:
break;
case 0x521:
ISA_handle521(frame);
break;
case 0x522:
ISA_handle522(frame);
break;
case 0x523:
ISA_handle523(frame);
break;
case 0x524:
ISA_handle524(frame);
break;
case 0x525:
ISA_handle525(frame);
break;
case 0x526:
ISA_handle526(frame);
break;
case 0x527:
ISA_handle527(frame);
break;
case 0x528:
ISA_handle528(frame);
break;
}
return;
}
//handle frame for Amperes
inline void ISA_handle521(CAN_frame_t* frame) {
long current = 0;
current =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
milliamps = current;
Amperes = current / 1000.0f;
}
//handle frame for Voltage
inline void ISA_handle522(CAN_frame_t* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage = volt / 1000.0f;
Voltage1 = Voltage - (Voltage2 + Voltage3);
if (framecount < 150) {
VoltageLO = Voltage;
Voltage1LO = Voltage1;
} else {
if (Voltage < VoltageLO)
VoltageLO = Voltage;
if (Voltage > VoltageHI)
VoltageHI = Voltage;
if (Voltage1 < Voltage1LO)
Voltage1LO = Voltage1;
if (Voltage1 > Voltage1HI)
Voltage1HI = Voltage1;
}
}
//handle frame for Voltage 2
inline void ISA_handle523(CAN_frame_t* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage2 = volt / 1000.0f;
if (Voltage2 > 3)
Voltage2 -= Voltage3;
if (framecount < 150) {
Voltage2LO = Voltage2;
} else {
if (Voltage2 < Voltage2LO)
Voltage2LO = Voltage2;
if (Voltage2 > Voltage2HI)
Voltage2HI = Voltage2;
}
}
//handle frame for Voltage3
inline void ISA_handle524(CAN_frame_t* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage3 = volt / 1000.0f;
if (framecount < 150) {
Voltage3LO = Voltage3;
} else {
if (Voltage3 < Voltage3LO && Voltage3 > 10)
Voltage3LO = Voltage3;
if (Voltage3 > Voltage3HI)
Voltage3HI = Voltage3;
}
}
//handle frame for Temperature
inline void ISA_handle525(CAN_frame_t* frame) {
long temp = 0;
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Temperature = temp / 10;
}
//handle frame for Kilowatts
inline void ISA_handle526(CAN_frame_t* frame) {
watt = 0;
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KW = watt / 1000.0f;
}
//handle frame for Ampere-Hours
inline void ISA_handle527(CAN_frame_t* frame) {
As = 0;
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
AH += (As - lastAs) / 3600.0f;
lastAs = As;
}
//handle frame for kiloWatt-hours
inline void ISA_handle528(CAN_frame_t* frame) {
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KWH += (wh - lastWh) / 1000.0f;
lastWh = wh;
}
/*
void ISA_initialize() {
firstframe=false;
STOP();
delay(700);
for(int i=0;i<9;i++) {
Serial.println("initialization \n");
outframe.data.u8[0]=(0x20+i);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x02;
outframe.data.u8[3]=(0x60+(i*18));
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
delay(500);
sendSTORE();
delay(500);
}
START();
delay(500);
lastAs=As;
lastWh=wh;
}
void ISA_STOP() {
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
}
void ISA_sendSTORE() {
outframe.data.u8[0]=0x32;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
}
void ISA_START() {
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x01;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
}
void ISA_RESTART() {
//Has the effect of zeroing AH and KWH
outframe.data.u8[0]=0x3F;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
}
void ISA_deFAULT() {
//Returns module to original defaults
outframe.data.u8[0]=0x3D;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
}
void ISA_initCurrent() {
STOP();
delay(500);
Serial.println("initialization \n");
outframe.data.u8[0]=0x21;
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x61;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
delay(500);
sendSTORE();
delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
}
*/
#endif

View file

@ -0,0 +1,16 @@
#ifndef CHADEMO_SHUNTS_H
#define CHADEMO_SHUNTS_H
uint16_t get_measured_voltage();
uint16_t get_measured_current();
inline void ISA_handler(CAN_frame_t* frame);
inline void ISA_handle521(CAN_frame_t* frame);
inline void ISA_handle522(CAN_frame_t* frame);
inline void ISA_handle523(CAN_frame_t* frame);
inline void ISA_handle524(CAN_frame_t* frame);
inline void ISA_handle525(CAN_frame_t* frame);
inline void ISA_handle526(CAN_frame_t* frame);
inline void ISA_handle527(CAN_frame_t* frame);
inline void ISA_handle528(CAN_frame_t* frame);
#endif

View file

@ -103,6 +103,13 @@ typedef struct {
DATALAYER_BATTERY_SETTINGS_TYPE settings;
} DATALAYER_BATTERY_TYPE;
typedef struct {
/** measured voltage in deciVolts. 4200 = 420.0 V */
uint16_t measured_voltage_dV = 0;
/** measured amperage in deciAmperes. 300 = 30.0 A */
uint16_t measured_amperage_dA = 0;
} DATALAYER_SHUNT_TYPE;
typedef struct {
// TODO
} DATALAYER_SYSTEM_INFO_TYPE;
@ -170,6 +177,7 @@ typedef struct {
class DataLayer {
public:
DATALAYER_BATTERY_TYPE battery;
DATALAYER_SHUNT_TYPE shunt;
DATALAYER_SYSTEM_TYPE system;
};

View file

@ -1,4 +0,0 @@
http://www.digikey.com/short/3c2wwr
This digikey shopping cart contains all the connectors and pins
for the ISA IVT-1K-U3-TOI-CAN2-12 Current Sensor

View file

@ -1,9 +0,0 @@
# SimpleISA
Simple library for IVT shunts.
Based on the EVTV library of 2016, revised for use with CHAdeMO.
Originally intended to integrate with Arduino Due.
Adapted for ESP32 and ESP32-Arduino-CAN for use in the Battery-Emulator project https://github.com/dalathegreat/Battery-Emulator
hosted at https://github.com/smaresca/SimpleISA-ESP32-Arduino-CAN
Derived from https://github.com/isaac96/simpleISA/ and https://github.com/damienmaguire/SimpleISA/

View file

@ -1,396 +0,0 @@
/* This library supports ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
This library was written by Jack Rickard of EVtv - http://www.evtv.me
copyright 2014
You are licensed to use this library for any purpose, commercial or private,
without restriction.
2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
*/
#include "SimpleISA.h"
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }
ISA::ISA() // Define the constructor.
{
timestamp = millis();
debug=false;
debug2=false;
framecount=0;
firstframe=true;
}
ISA::~ISA() //Define destructor
{
}
void ISA::begin(int Port, int speed)
{
}
void ISA::handleFrame(CAN_frame_t *frame)
//This is our CAN interrupt service routine to catch inbound frames
{
switch (frame->MsgID)
{
case 0x511:
break;
case 0x521:
handle521(frame);
break;
case 0x522:
handle522(frame);
break;
case 0x523:
handle523(frame);
break;
case 0x524:
handle524(frame);
break;
case 0x525:
handle525(frame);
break;
case 0x526:
handle526(frame);
break;
case 0x527:
handle527(frame);
break;
case 0x528:
handle528(frame);
break;
}
if(debug)printCAN(frame);
}
void ISA::handle521(CAN_frame_t *frame) //AMperes
{
framecount++;
long current=0;
current = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
milliamps=current;
Amperes=current/1000.0f;
if(debug2)Serial<<"Current: "<<Amperes<<" amperes "<<milliamps<<" ma frames:"<<framecount<<"\n";
}
void ISA::handle522(CAN_frame_t *frame) //Voltage
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage=volt/1000.0f;
Voltage1=Voltage-(Voltage2+Voltage3);
if(framecount<150)
{
VoltageLO=Voltage;
Voltage1LO=Voltage1;
}
if(Voltage<VoltageLO && framecount>150)VoltageLO=Voltage;
if(Voltage>VoltageHI && framecount>150)VoltageHI=Voltage;
if(Voltage1<Voltage1LO && framecount>150)Voltage1LO=Voltage1;
if(Voltage1>Voltage1HI && framecount>150)Voltage1HI=Voltage1;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 1: "<<Voltage1<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle523(CAN_frame_t *frame) //Voltage2
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage2=volt/1000.0f;
if(Voltage2>3)Voltage2-=Voltage3;
if(framecount<150)Voltage2LO=Voltage2;
if(Voltage2<Voltage2LO && framecount>150)Voltage2LO=Voltage2;
if(Voltage2>Voltage2HI&& framecount>150)Voltage2HI=Voltage2;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 2: "<<Voltage2<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle524(CAN_frame_t *frame) //Voltage3
{
framecount++;
long volt=0;
volt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Voltage3=volt/1000.0f;
if(framecount<150)Voltage3LO=Voltage3;
if(Voltage3<Voltage3LO && framecount>150 && Voltage3>10)Voltage3LO=Voltage3;
if(Voltage3>Voltage3HI && framecount>150)Voltage3HI=Voltage3;
if(debug2)Serial<<"Voltage: "<<Voltage<<" vdc Voltage 3: "<<Voltage3<<" vdc "<<volt<<" mVdc frames:"<<framecount<<"\n";
}
void ISA::handle525(CAN_frame_t *frame) //Temperature
{
framecount++;
long temp=0;
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
Temperature=temp/10;
if(debug2)Serial<<"Temperature: "<<Temperature<<" C frames:"<<framecount<<"\n";
}
void ISA::handle526(CAN_frame_t *frame) //Kilowatts
{
framecount++;
watt=0;
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KW=watt/1000.0f;
if(debug2)Serial<<"Power: "<<watt<<" Watts "<<KW<<" kW frames:"<<framecount<<"\n";
}
void ISA::handle527(CAN_frame_t *frame) //Ampere-Hours
{
framecount++;
As=0;
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
AH+=(As-lastAs)/3600.0f;
lastAs=As;
if(debug2)Serial<<"Amphours: "<<AH<<" Ampseconds: "<<As<<" frames:"<<framecount<<"\n";
}
void ISA::handle528(CAN_frame_t *frame) //kiloWatt-hours
{
framecount++;
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
KWH+=(wh-lastWh)/1000.0f;
lastWh=wh;
if(debug2)Serial<<"KiloWattHours: "<<KWH<<" Watt Hours: "<<wh<<" frames:"<<framecount<<"\n";
}
void ISA::printCAN(CAN_frame_t *frame)
{
//This routine simply prints a timestamp and the contents of the
//incoming CAN message
milliseconds = (int) (millis()/1) %1000 ;
seconds = (int) (millis() / 1000) % 60 ;
minutes = (int) ((millis() / (1000*60)) % 60);
hours = (int) ((millis() / (1000*60*60)) % 24);
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
Serial<<buffer<<" ";
sprintf(bigbuffer,"%02X %02X %02X %02X %02X %02X %02X %02X %02X",
frame->MsgID, frame->data.u8[0],frame->data.u8[1],frame->data.u8[2],
frame->data.u8[3],frame->data.u8[4],frame->data.u8[5],frame->data.u8[6],frame->data.u8[7],0);
Serial<<"Rcvd ISA frame: 0x"<<bigbuffer<<"\n";
}
void ISA::initialize()
{
firstframe=false;
STOP();
delay(700);
for(int i=0;i<9;i++)
{
Serial.println("initialization \n");
outframe.data.u8[0]=(0x20+i);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x02;
outframe.data.u8[3]=(0x60+(i*18));
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe);
delay(500);
sendSTORE();
delay(500);
}
// delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
}
void ISA::STOP()
{
//SEND STOP///////
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug) {printCAN(&outframe);} //If the debug variable is set, show our transmitted frame
}
void ISA::sendSTORE()
{
//SEND STORE///////
outframe.data.u8[0]=0x32;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::START()
{
//SEND START///////
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x01;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::RESTART()
{
//Has the effect of zeroing AH and KWH
outframe.data.u8[0]=0x3F;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::deFAULT()
{
//Returns module to original defaults
outframe.data.u8[0]=0x3D;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe); //If the debug variable is set, show our transmitted frame
}
void ISA::initCurrent()
{
STOP();
delay(500);
Serial.println("initialization \n");
outframe.data.u8[0]=(0x21);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=(0x61);
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ESP32Can.CANWriteFrame(&outframe);
if(debug)printCAN(&outframe);
delay(500);
sendSTORE();
delay(500);
// delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
}

View file

@ -1,108 +0,0 @@
#ifndef SimpleISA_h
#define SimpleISA_h
/* This library supports the ISA Scale IVT Modular current/voltage sensor device. These devices measure current, up to three voltages, and provide temperature compensation.
This library was written by Jack Rickard of EVtv - http://www.evtv.me copyright 2016
You are licensed to use this library for any purpose, commercial or private,
without restriction.
Note for posterity: IVT-MOD has X1 pinout: vcc gnd CAN-L CAN-H
IVT-S has X1 pinout: vcc CAN-L CAN-H GND
*/
#include <Arduino.h>
#include "../miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
class ISA
{
public:
ISA();
~ISA();
void initialize();
void begin(int Port, int speed);
void initCurrent();
void sendSTORE();
void STOP();
void START();
void RESTART();
void deFAULT();
float Amperes; // Floating point with current in Amperes
double AH; //Floating point with accumulated ampere-hours
double KW;
double KWH;
double Voltage;
double Voltage1;
double Voltage2;
double Voltage3;
double VoltageHI;
double Voltage1HI;
double Voltage2HI;
double Voltage3HI;
double VoltageLO;
double Voltage1LO;
double Voltage2LO;
double Voltage3LO;
double Temperature;
bool debug;
bool debug2;
bool firstframe;
int framecount;
unsigned long timestamp;
double milliamps;
long watt;
long As;
long lastAs;
long wh;
long lastWh;
void handleFrame(CAN_frame_t *frame); // CAN handler
uint8_t page;
private:
CAN_frame_t frame;
unsigned long elapsedtime;
double ampseconds;
int milliseconds ;
int seconds;
int minutes;
int hours;
char buffer[9];
char bigbuffer[90];
uint32_t inbox;
CAN_frame_t outframe = {
.FIR = {.B = {
.DLC = 8,
.unknown_2 = 0,
.RTR = CAN_no_RTR,
.FF = CAN_frame_std,
}},
.MsgID = 0x411,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void printCAN(CAN_frame_t *frame);
void handle521(CAN_frame_t *frame);
void handle522(CAN_frame_t *frame);
void handle523(CAN_frame_t *frame);
void handle524(CAN_frame_t *frame);
void handle525(CAN_frame_t *frame);
void handle526(CAN_frame_t *frame);
void handle527(CAN_frame_t *frame);
void handle528(CAN_frame_t *frame);
};
#endif /* SimpleISA_h */

View file

@ -1,188 +0,0 @@
#include <due_can.h>
#include "variant.h"
#include <SimpleISA.h>
#define Serial SerialUSB //Use native port
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; } //Allow streaming
float Version=2.00;
uint16_t loopcount=0;
unsigned long startime=0;
unsigned long elapsedtime=0;
uint port=0;
uint16_t datarate=500;
ISA Sensor; //Instantiate ISA Module Sensor object to measure current and voltage
void setup()
{
Serial.begin(115200);
Sensor.begin(port,datarate); //Start ISA object on CAN 0 at 500 kbps
Serial<<"\nISA Scale Startup Successful \n";
printMenu();
}
void loop()
{
if(loopcount++==40000)
{
printStatus();
loopcount-0;
}
checkforinput(); //Check keyboard for user input
}
void printStatus()
{
char buffer[40];
//printimestamp();
sprintf(buffer,"%4.2f",Sensor.Voltage);
Serial<<"Volt:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage1);
Serial<<"V1:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage2);
Serial<<"V2:"<<buffer<<"v ";
sprintf(buffer,"%4.2f",Sensor.Voltage3);
Serial<<"V3:"<<buffer<<"v ";
sprintf(buffer,"%4.3f",Sensor.Amperes);
Serial<<"Amps:"<<buffer<<"A ";
sprintf(buffer,"%4.3f",Sensor.KW);
Serial<<buffer<<"kW ";
sprintf(buffer,"%4.3f",Sensor.AH);
Serial<<buffer<<"Ah ";
sprintf(buffer,"%4.3f",Sensor.KWH);
Serial<<buffer<<"kWh";
sprintf(buffer,"%4.0f",Sensor.Temperature);
Serial<<buffer<<"C ";
Serial<<"Frame:"<<Sensor.framecount<<" \n";
}
void printimestamp()
{
//Prints a timestamp to the serial port
elapsedtime=millis() - startime;
int milliseconds = (elapsedtime/1) %1000 ;
int seconds = (elapsedtime / 1000) % 60 ;
int minutes = ((elapsedtime / (1000*60)) % 60);
int hours = ((elapsedtime / (1000*60*60)) % 24);
char buffer[19];
sprintf(buffer,"%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
Serial<<buffer<<" ";
}
void printMenu()
{
Serial<<"\f\n=========== ISA Scale Sample Program Version "<<Version<<" ==============\n************ List of Available Commands ************\n\n";
Serial<<" ? - Print this menu\n ";
Serial<<" d - toggles Debug off and on to print recieved CAN data traffic\n";
Serial<<" D - toggles Debug2 off and on to print derived values\n";
Serial<<" f - zero frame count\n ";
Serial<<" i - initialize new sensor\n ";
Serial<<" p - Select new CAN port\n ";
Serial<<" r - Set new datarate\n ";
Serial<<" z - zero ampere-hours\n ";
Serial<<"**************************************************************\n==============================================================\n\n";
}
void checkforinput()
{
//Checks for keyboard input from Native port
if (Serial.available())
{
int inByte = Serial.read();
switch (inByte)
{
case 'z': //Zeroes ampere-hours
Sensor.KWH=0;
Sensor.AH=0;
Sensor.RESTART();
break;
case 'p':
getPort();
break;
case 'r':
getRate();
break;
case 'f':
Sensor.framecount=0;
break;
case 'd': //Causes ISA object to print incoming CAN messages for debugging
Sensor.debug=!Sensor.debug;
break;
case 'D': //Causes ISA object to print derived values for debugging
Sensor.debug2=!Sensor.debug2;
break;
case 'i':
Sensor.initialize();
break;
case '?': //Print a menu describing these functions
printMenu();
break;
case '1':
Sensor.STOP();
break;
case '3':
Sensor.START();
break;
}
}
}
void getRate()
{
Serial<<"\n Enter the Data Rate in Kbps you want for CAN : ";
while(Serial.available() == 0){}
float V = Serial.parseFloat();
if(V>0)
{
Serial<<"Datarate:"<<V<<"\n\n";
uint8_t rate=V;
datarate=V*1000;
Sensor.begin(port,datarate);
}
}
void getPort()
{
Serial<<"\n Enter port selection: c0=CAN0 c1=CAN1 ";
while(Serial.available() == 0){}
int P = Serial.parseInt();
if(P>1) Serial<<"Entry out of range, enter 0 or 1 \n";
else
{
port=P;
Sensor.begin(port,datarate);
}
}