363 lines
14 KiB
C++
363 lines
14 KiB
C++
// IBS_Sensor.cpp
|
|
// Interfaces a Hella IBS-200X Battery Sensor via Lin-BUS
|
|
// Provides voltage, curret, State of Charge, etc.
|
|
//
|
|
// Copyright mestrode <ardlib@mestro.de>
|
|
// Original Source: https://github.com/mestrode/IBS-Sensor-Library
|
|
//
|
|
// Requires class Lin_Interface: https://github.com/mestrode/Lin-Interface-Library
|
|
//
|
|
// Includes informations provided by breezer
|
|
// https://www.kastenwagenforum.de/forum/threads/diy-hella-ibs-batteriecomputer.31724/page-2
|
|
// Includes informations from the code created by Frank Schöniger
|
|
// https://github.com/frankschoeniger/LIN_Interface
|
|
|
|
#include "IBS_Sensor.hpp"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LIN Specification said abaout FrameIDs:
|
|
// 0-50 (0x00-0x3B) are used for normal Signal/data carrying frames.
|
|
// 60 (0x3C) and 61 (0x3D) are used to carry diagnostic and configuration data.
|
|
// 62 (0x3E) and 63 (0x3F) are reserved for future protocol enhancements.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Frame IDs depends on SensorNo ("Sensor 1" or "Sensor 2" is marked on the label)
|
|
|
|
#define IBS_FRM_STA 0 /* 0x27 */
|
|
#define IBS_FRM_CUR 1 /* 0x28 */
|
|
#define IBS_FRM_ERR 2 /* 0x29 */
|
|
#define IBS_FRM_tb3 3 /* 0x2A */
|
|
#define IBS_FRM_SOx 4 /* 0x2B */
|
|
#define IBS_FRM_CAP 5 /* 0x2C */
|
|
|
|
// 0 1 2 3 4 5
|
|
// STA CUR ERR tb3 SOx CAP
|
|
const uint8_t IBS_FrameID[2][6] = {{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, // "Sensor 1"
|
|
{0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C}}; // "Sensor 2"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// There are additional Frames, the Sensor is responding to
|
|
|
|
// If you send one of those Frames to "Sensor 2", it will response:
|
|
// ID 34h - Resonse: 20:4E:20:4E:00:00:00:00
|
|
// ID 35h - Resonse: 00:00:00:00:00:00:00:00
|
|
// ID 36h - Resonse: 00:00:08:00:F8:FF
|
|
// ID 37h - Resonse: F4:2E:2D:00:00:00:00:00
|
|
// What does it mean?
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// constructor
|
|
|
|
// constructor of class IBS_Sensor
|
|
IBS_Sensor::IBS_Sensor(int SensorNo) {
|
|
|
|
// "Sensor 1" = 1 => _SensorNo = 0;
|
|
// "Sensor 2" = 2 => _SensorNo = 1;
|
|
_SensorNo = SensorNo-1;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Request current valus form sensor
|
|
|
|
/// Read current sensor data (at least all usefull frames)
|
|
/// @returns success of _all_ operations
|
|
bool IBS_Sensor::readFrames()
|
|
{
|
|
//ensure not work with received data (in case of a chksum error at the first run)
|
|
StatusReady = false;
|
|
|
|
//Read max 10 times sensor status / wait for valid data
|
|
for (int i=10; i>=0; i--) {
|
|
// StatusReady Flag is included in Frame "Status"
|
|
readFrameStatus();
|
|
if (StatusReady)
|
|
break;
|
|
}
|
|
|
|
if (!StatusReady) {
|
|
return false;
|
|
}
|
|
|
|
bool success = readFrameCurrent();
|
|
success = success && readFrameSOx();
|
|
success = success && readFrameCapacity();
|
|
|
|
// Don't know that to do with the results of Frame Error and TB3
|
|
// success = success && readFrameError();
|
|
// success = success && readFrameTB3();
|
|
|
|
return success;
|
|
}
|
|
|
|
// Request Frame "Status": only Ready Flag is asumed
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameStatus()
|
|
{
|
|
// FrameID "Sensor 1" = 21h
|
|
// "Sensor 2" = 27h
|
|
|
|
// ID 27h - STA - D1 = identification, Bereitschaft des Sensors?
|
|
// LL = Statusbyte
|
|
// 01 = Sensor ready, data available flag ?
|
|
// Sends usually data like 192 194 208=linked to Cap_Max?
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_STA]);
|
|
if (chkSumValid)
|
|
{
|
|
StatusByte = LinBus->LinMessage[0];
|
|
// Bit 0 seemed to be a kind of Data Ready Flag
|
|
StatusReady = ~(LinBus->LinMessage[0] & 0x01);
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
/// Request Frame CUR: Ubat, Ibat, Tbat and unknown1
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameCurrent()
|
|
{
|
|
// FrameID "Sensor 1" = 22h
|
|
// "Sensor 2" = 28h
|
|
|
|
// ID 28h - CUR - AB:84:1E:F4:2E:84:7A
|
|
// IL IM IH = Ibat (x-2000000)/1000 Ampere, positive Werte = Batterie laden
|
|
// UL UH = Ubat x/1000 Volt
|
|
// TT = Tbat x/2-40 °C
|
|
// ?? = 0x7A and 0c7C observed, changes with event on unknown6?
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_CUR]);
|
|
if (chkSumValid)
|
|
{
|
|
Ibat = (float((long(LinBus->LinMessage[2]) << 16) + (long(LinBus->LinMessage[1]) << 8) + long(LinBus->LinMessage[0]) - 2000000L)) / 1000.0;
|
|
Ubat = (float((LinBus->LinMessage[4] << 8) + LinBus->LinMessage[3])) / 1000.0;
|
|
Tbat = float(LinBus->LinMessage[5]) / 2 - 40;
|
|
unknown1 = LinBus->LinMessage[6];
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
/// Request Frame "Error": only one Byte maybe with some Error Flags
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameError()
|
|
{
|
|
// FrameID "Sensor 1" = 23h
|
|
// "Sensor 2" = 29h
|
|
|
|
// ID 29h - ERR - 00
|
|
// LL = Error flags or code
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_ERR]);
|
|
if (chkSumValid)
|
|
{
|
|
ErrorByte = LinBus->LinMessage[0]; // IBS_Error bit code?
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
/// Request Frame tb3: 4 bytes, but unknown content
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameTB3()
|
|
{
|
|
// FrameID "Sensor 1" = 24h
|
|
// "Sensor 2" = 2Ah
|
|
|
|
// ID 2Ah - tb3 - 00:00:00:00
|
|
// HH LL = unknown2
|
|
// HH LL = unknown3
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_tb3]);
|
|
if (chkSumValid)
|
|
{
|
|
unknown2 = (LinBus->LinMessage[1] << 8) + LinBus->LinMessage[0];
|
|
unknown3 = (LinBus->LinMessage[3] << 8) + LinBus->LinMessage[2];
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
/// Request Frame SOH: SOH, SOC and 2 unknown Bytes and a unknown Word
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameSOx()
|
|
{
|
|
// FrameID "Sensor 1" = 25h
|
|
// "Sensor 2" = 2Bh
|
|
|
|
// ID 2Bh - SOx - 2D:C8:FF:BB:00:00
|
|
// HH = State Of Charge x/2 in Prozent
|
|
// CC = State Of Health x/2 in Prozent
|
|
// ?? = unknown4 / has correlation to Cap_Available or SOC?
|
|
// ?? = unknown5 / no direkt link to unkown 4?
|
|
// H?:L? = unknown6 / maybe some corelation to Cap_Available or SOC?
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_SOx]);
|
|
if (chkSumValid)
|
|
{
|
|
SOC = float(LinBus->LinMessage[0]) / 2; // state of health
|
|
SOH = float(LinBus->LinMessage[1]) / 2; // state of charge
|
|
unknown4 = LinBus->LinMessage[2]; // seemed to be a byte value
|
|
unknown5 = LinBus->LinMessage[3]; // seemed to be a byte value
|
|
unknown6 = (LinBus->LinMessage[5] << 8) + LinBus->LinMessage[4]; // word or 2 bytes, not verified
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
/// Request Frame "Capacity": max seen, available and configured Capacity, Calibration
|
|
/// @returns checksum of frame is valid, data accepted
|
|
bool IBS_Sensor::readFrameCapacity()
|
|
{
|
|
// FrameID "Sensor 1" = 26h
|
|
// "Sensor 2" = 2Ch
|
|
|
|
// ID 2Ch - CAP - 20:03:B4:00:50:FE
|
|
// HH LL = Max seen Capacity x/10 Ah (=SOH ?)
|
|
// AL AH = Available Capacity x/10 Ah (=SOC)
|
|
// AH = Configured Capacity
|
|
// FF = CalibByte, maybe filled with stuffing bits?
|
|
// 01 = CalibrationDone flag, 1=ok, 0=uncalibrated
|
|
|
|
bool chkSumValid = LinBus->readFrame(IBS_FrameID[_SensorNo][IBS_FRM_CAP]);
|
|
if (chkSumValid)
|
|
{
|
|
Cap_Max = (float((LinBus->LinMessage[1] << 8) + LinBus->LinMessage[0])) / 10; // max. seen available Cap
|
|
Cap_Available = (float((LinBus->LinMessage[3] << 8) + LinBus->LinMessage[2])) / 10; // Available Capacity
|
|
Cap_Configured = LinBus->LinMessage[4]; // configured Cap
|
|
CalibByte = LinBus->LinMessage[5]; // eigentlich ist nur das Bit0 wichtig?
|
|
CalibrationDone = bitRead(LinBus->LinMessage[5], 0); // 1=calibration done 0=not finished yet
|
|
}
|
|
return chkSumValid;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// configuration of Sensor - only needed once: data will be stored in sensor
|
|
|
|
/// Configure BatSensor by sending 3 Configuration-Frames
|
|
void IBS_Sensor::writeConfiguration(IBS_BatteryTypes BatType, uint8_t BatCapacity)
|
|
{
|
|
// snapshot
|
|
readFrameCapacity();
|
|
Serial.print("Old capacity: ");
|
|
Serial.print(Cap_Configured);
|
|
Serial.print("Ah, Calibration Flag: ");
|
|
Serial.print(CalibrationDone ? "true" : "false");
|
|
Serial.println();
|
|
|
|
// configure
|
|
Serial.print(" (1/3) Write unknown Param...");
|
|
writeUnknownParam(); // Not sure why
|
|
Serial.print(" (2/3) Write capacity...\n");
|
|
writeBatCapacity(BatCapacity); // nominal capacity (Ah)
|
|
Serial.print(" (3/3) Write battery type...\n");
|
|
writeBatType(BatType); // battery type (AGM, GEL or STARTER)
|
|
|
|
// verify
|
|
readFrameCapacity();
|
|
Serial.print("New capacity: ");
|
|
Serial.print(Cap_Configured);
|
|
Serial.print("Ah, Calibration Flag: ");
|
|
Serial.print(CalibrationDone ? "true" : "false");
|
|
Serial.println();
|
|
}
|
|
|
|
/// Write configuration Parameter "Unknown"
|
|
/// Parameter will be written by the configuration procedure used by the main panel
|
|
/// so I don't want to break tradition...
|
|
/// @returns no verification of success!!!
|
|
void IBS_Sensor::writeUnknownParam()
|
|
{
|
|
// Function of ths configuration Frame is unknown!
|
|
// Frame is send in configuration procedure by control panel, so why miss this out?
|
|
|
|
// guess this frame does
|
|
// - general Reset of the sensor?
|
|
// - configure initial battery status? (0x7F = 50% charge state)
|
|
|
|
// Request for configuration by main panel
|
|
// 00005.731 3c 02 06 b2 3a ff 7f ff ff 8b ERR
|
|
// ^^
|
|
// Response of sensor
|
|
// 00005.780 7d 02 02 f2 0a ff ff ff ff fe ERR
|
|
// ^^ guess this was not successful
|
|
|
|
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
|
|
LinBus->LinMessage[1] = 0x06; // Data Len
|
|
LinBus->LinMessage[2] = 0xB2; // CMD Config Read
|
|
LinBus->LinMessage[3] = 0x3A; // Config Type
|
|
LinBus->LinMessage[4] = 0xFF; // the unknown message = reset configuration?
|
|
LinBus->LinMessage[5] = 0x7F; // obviously not the first but the second byte will be written
|
|
LinBus->LinMessage[6] = 0xFF;
|
|
LinBus->LinMessage[7] = 0xFF;
|
|
|
|
LinBus->writeFrame(0x3C, 8);
|
|
LinBus->readFrame(0x3D);
|
|
}
|
|
|
|
/// Write configuration Parameter "Capacity" = Value of Ah of the Battery, default factory value = "80"Ah
|
|
/// @param BatCapacity labeled capacity (in Ah) of the lead battery
|
|
/// @returns no verification of success!!!
|
|
void IBS_Sensor::writeBatCapacity(uint8_t BatCapacity)
|
|
{
|
|
// Configuration (Of Sensor Type 1)
|
|
// Battery capacity can be read back on 0x2C Byte 4
|
|
// ID 3Ch - Capacity 02:03:B5:39: BatCap :FF:FF:FF - BatCap in Ah
|
|
|
|
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
|
|
LinBus->LinMessage[1] = 0x03; // Data Len
|
|
LinBus->LinMessage[2] = 0xB5; // CMD Config Read
|
|
LinBus->LinMessage[3] = 0x39; // Config Type
|
|
LinBus->LinMessage[4] = BatCapacity; // e.g. 70 Ah
|
|
LinBus->LinMessage[5] = 0xFF; // filling bytes
|
|
LinBus->LinMessage[6] = 0xFF;
|
|
LinBus->LinMessage[7] = 0xFF;
|
|
|
|
LinBus->writeFrame(0x3C, 8);
|
|
LinBus->readFrame(0x3D);
|
|
}
|
|
|
|
/// Write configuration Parameter "Battery Type" and read answer back (not judged)
|
|
/// @param BatType
|
|
/// @returns no verification of success!!!
|
|
void IBS_Sensor::writeBatType(IBS_BatteryTypes BatType)
|
|
{
|
|
// Battery Type can not be verified by using another Frame, since no Frame includes this data
|
|
// But recalibration is obviously needed, so CalibrationDone Flag should be an indicator for success
|
|
/* Procedure
|
|
00012.919 ec bc 02 dc 01 46 FF 30 CAP Sensor 2, is calibrated (FF)
|
|
00012.935 3c 02 03 b5 3a 1e ff ff ff ec ERR Config Sensor 2 Len 3 Conf =B5 Type 3a Data 1e
|
|
^^ = AGM parameter to be written
|
|
00012.988 7d 02 03 f5 3a 1e ff ff ff ac ERR Answer Sensor 2 Len 3 Conf+40=F5 Type 3a Data 1e
|
|
^^ = AGM readback --> confirmation of parameter from device
|
|
^^ = len+40 --> valid
|
|
00013.115 ec bc 02 dc 01 46 FE 31 CAP Sensor 2, is not calibrated (FE)
|
|
^ need to calibrate
|
|
*/
|
|
|
|
// ID 3Ch - STARTER Bat 02:03:B5:3A: 0A :FF:FF:FF|01
|
|
// GEL Bat 02:03:B5:3A: 14 :FF:FF:FF|F6
|
|
// AGM Bat 02:03:B5:3A: 1E :FF:FF:FF|EC
|
|
|
|
LinBus->LinMessage[0] = 0x01 + _SensorNo; // Sensor No
|
|
LinBus->LinMessage[1] = 0x03; // Data Len
|
|
LinBus->LinMessage[2] = 0xB5; // CMD Config Read
|
|
LinBus->LinMessage[3] = 0x3A; // Config Type
|
|
|
|
LinBus->LinMessage[5] = 0xFF; // filling bytes
|
|
LinBus->LinMessage[6] = 0xFF;
|
|
LinBus->LinMessage[7] = 0xFF;
|
|
|
|
switch (BatType)
|
|
{
|
|
case BAT_TYPE_STARTER:
|
|
LinBus->LinMessage[4] = 0x0a; // Starter Battery
|
|
break;
|
|
|
|
case BAT_TYPE_GEL:
|
|
LinBus->LinMessage[4] = 0x14; // GEL Battery
|
|
break;
|
|
|
|
case BAT_TYPE_AGM:
|
|
LinBus->LinMessage[4] = 0x1e; // AGM Battery
|
|
break;
|
|
}
|
|
|
|
LinBus->writeFrame(0x3C, 8);
|
|
LinBus->readFrame(0x3D);
|
|
}
|