#include <Arduino.h>
#define VERSION_EXAMPLE "AMG-v3"
/*
* Pin layout, may be adapted to your requirements
*/
#define JK_BMS_RX_PIN 0 // We use the Serial RX pin. Not used in program, only for documentation
#if !defined(JK_BMS_TX_PIN) // Allow override by global symbol
#define JK_BMS_TX_PIN 4
#endif
#define STANDALONE_TEST
/*
* Program timing, may be adapted to your requirements
*/
#if defined(STANDALONE_TEST)
#define MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS 1000
#define SECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS "1" // Only for display on LCD
#else
#define MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS 2000
#define SECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS "2" // Only for display on LCD
#endif
uint16_t sTimeoutFrameCounter = 0; // Counts BMS frame timeouts, (every 2 seconds)
bool readJK_BMSStatusFrame();
void processJK_BMSStatusFrame();
void handleFrameReceiveTimeout();
/*
* Software serial for JK-BMS stuff
*/
#if !defined(MAXIMUM_NUMBER_OF_CELLS)
#define MAXIMUM_NUMBER_OF_CELLS 24 // Maximum number of cell info which can be converted. Must be before #include "JK-BMS.hpp".
#endif
#include "JK-BMS.hpp"
/*
* Software serial for JK-BMS request frame sending
*/
#include "SoftwareSerialTX.h"
/*
* Use a 115200 baud software serial for the short request frame.
* If available, we also can use a second hardware serial here :-).
*/
SoftwareSerialTX TxToJKBMS(JK_BMS_TX_PIN);
bool sFrameIsRequested = false; // If true, request was recently sent so now check for serial input
uint32_t sMillisOfLastRequestedJKDataFrame = -MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS; // Initial value to start first request immediately
uint32_t sMillisOfLastReceivedByte = 0; // For timeout
bool sBMSFrameProcessingComplete = false; // True if one status frame was received and processed or timeout happened. Then we can do a sleep at the end of the loop.
/*
* Miscellaneous
*/
#define TIMEOUT_MILLIS_FOR_FRAME_REPLY 100 // I measured 26 ms between request end and end of received 273 byte result
#if TIMEOUT_MILLIS_FOR_FRAME_REPLY > MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS
#error "TIMEOUT_MILLIS_FOR_FRAME_REPLY must be smaller than MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS to detect timeouts"
#endif
bool sStaticInfoWasSent = false; // Flag to send static Info only once after reset.
void processReceivedData();
void printReceivedData();
#if defined(STANDALONE_TEST)
const uint8_t TestJKReplyStatusFrame[] PROGMEM = { /* Header*/0x4E, 0x57, 0x01, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01,
/*Length of Cell voltages*/
0x79, 0x30,
/*Cell voltages*/
0x01, 0x0C, 0xC6, 0x02, 0x0C, 0xBE, 0x03, 0x0C, 0xC7, 0x04, 0x0C, 0xC7, 0x05, 0x0C, 0xC7, 0x06, 0x0C, 0xC5, 0x07, 0x0C, 0xC6, 0x08,
0x0C, 0xC7, 0x09, 0x0C, 0xC2, 0x0A, 0x0C, 0xC2, 0x0B, 0x0C, 0xC2, 0x0C, 0x0C, 0xC2, 0x0D, 0x0C, 0xC1, 0x0E, 0x0C, 0xBE,
0x0F, 0x0C, 0xC1, 0x10, 0x0C, 0xC1,
/*JKFrameAllDataStruct*/
0x80, 0x00, 0x16, 0x81, 0x00, 0x15, 0x82, 0x00, 0x15, /*Voltage*/0x83, 0x14, 0x6C, /*Current*/0x84, 0x80, 0xD0, /*SOC*/0x85,
0x47, 0x86, 0x02, 0x87, 0x00, 0x04, 0x89, 0x00, 0x00, 0x01, 0xE0, 0x8A, 0x00, 0x0E, /*Warnings*/0x8B, 0x00, 0x00, 0x8C,
0x00, 0x07, 0x8E, 0x16, 0x26, 0x8F, 0x10, 0xAE, 0x90, 0x0F, 0xD2, 0x91, 0x0F, 0xA0, 0x92, 0x00, 0x05, 0x93, 0x0B, 0xEA,
0x94, 0x0C, 0x1C, 0x95, 0x00, 0x05, 0x96, 0x01, 0x2C, 0x97, 0x00, 0x07, 0x98, 0x00, 0x03, 0x99, 0x00, 0x05, 0x9A, 0x00,
0x05, 0x9B, 0x0C, 0xE4, 0x9C, 0x00, 0x08, 0x9D, 0x01, 0x9E, 0x00, 0x5A, 0x9F, 0x00, 0x46, 0xA0, 0x00, 0x64, 0xA1, 0x00,
0x64, 0xA2, 0x00, 0x14, 0xA3, 0x00, 0x46, 0xA4, 0x00, 0x46, 0xA5, 0xFF, 0xEC, 0xA6, 0xFF, 0xF6, 0xA7, 0xFF, 0xEC, 0xA8,
0xFF, 0xF6, 0xA9, 0x0E, 0xAA, 0x00, 0x00, 0x01, 0x40, 0xAB, 0x01, 0xAC, 0x01, 0xAD, 0x04, 0x11, 0xAE, 0x01, 0xAF, 0x01,
0xB0, 0x00, 0x0A, 0xB1, 0x14, 0xB2, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00, 0x00, 0x00, 0x00, 0xB3, 0x00, 0xB4, 0x49,
0x6E, 0x70, 0x75, 0x74, 0x20, 0x55, 0x73, 0xB5, 0x32, 0x31, 0x30, 0x31, 0xB6, 0x00, 0x00, 0xE2, 0x00, 0xB7, 0x31, 0x31,
0x2E, 0x58, 0x57, 0x5F, 0x53, 0x31, 0x31, 0x2E, 0x32, 0x36, 0x5F, 0x5F, 0x5F, 0xB8, 0x00, 0xB9, 0x00, 0x00, 0x04, 0x00,
0xBA, 0x49, 0x6E, 0x70, 0x75, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x64, 0x61, 0x4A, 0x4B, 0x5F, 0x42, 0x32, 0x41, 0x32,
0x30, 0x53, 0x32, 0x30, 0x50, 0xC0, 0x01,
/*Trailer*/
0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x51, 0xC2 };
#endif
void setup() {
Serial.begin(115200);
#if defined(__AVR_ATmega32U4__) || defined(SERIAL_PORT_USBVIRTUAL) || defined(SERIAL_USB) /*stm32duino*/|| defined(USBCON) /*STM32_stm32*/|| defined(SERIALUSB_PID) || defined(ARDUINO_attiny3217)
delay(4000); // To be able to connect Serial monitor after reset or power up and before first print out. Do not wait for an attached Serial Monitor!
#endif
// Just to know which program is running on my Arduino
Serial.println(F("START " __FILE__ "\r\nVersion " VERSION_EXAMPLE " from " __DATE__));
/*
* 115200 baud soft serial to JK-BMS. For serial from BMS we use the hardware Serial RX.
*/
TxToJKBMS.begin(115200);
Serial.println(F("Serial to JK-BMS started with 115200 bit/s!"));
#if defined(STANDALONE_TEST)
/*
* Copy test data to receive buffer
*/
Serial.println(F("Standalone test. Use fixed demo data"));
Serial.println();
memcpy_P(JKReplyFrameBuffer, TestJKReplyStatusFrame, sizeof(TestJKReplyStatusFrame));
sReplyFrameBufferIndex = sizeof(TestJKReplyStatusFrame) - 1;
printJKReplyFrameBuffer();
Serial.println();
processReceivedData();
printReceivedData();
/*
* Copy complete reply and computed values for change determination
*/
lastJKComputedData = JKComputedData;
lastJKReply = *sJKFAllReplyPointer; // 221 bytes
#endif
}
void loop() {
/*
* Request status frame every 2 seconds
*/
if (millis() - sMillisOfLastRequestedJKDataFrame >= MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) {
sMillisOfLastRequestedJKDataFrame = millis(); // set for next check
/*
* Flush input buffer and send request to JK-BMS
*/
while (Serial.available()) {
Serial.read();
}
requestJK_BMSStatusFrame(&TxToJKBMS, false); // 1.85 ms
sFrameIsRequested = true; // enable check for serial input
initJKReplyFrameBuffer();
sMillisOfLastReceivedByte = millis(); // initialize reply timeout
}
#if defined(STANDALONE_TEST)
sBMSFrameProcessingComplete = true; // for LCD timeout etc.
processReceivedData(); // for statistics
delay(MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS); // do it simple :-)
#else
/*
* Get reply from BMS and check timeout
*/
if (sFrameIsRequested) {
if (Serial.available()) {
if (readJK_BMSStatusFrame()) {
processJK_BMSStatusFrame(); // Process the complete receiving of the status frame and set the appropriate flags
}
} else if (millis() - sMillisOfLastReceivedByte >= TIMEOUT_MILLIS_FOR_FRAME_REPLY) {
/*
* Here we have requested frame, but serial was not available fore a longer time => timeout at receiving
* If no bytes received before (because of BMS disconnected), print it only once
*/
handleFrameReceiveTimeout();
}
}
#endif // !defined(STANDALONE_TEST)
}
/*
* Process the complete receiving of the status frame and set the appropriate flags
*/
void processJK_BMSStatusFrame() {
sFrameIsRequested = false; // Everything OK, do not try to receive more
sBMSFrameProcessingComplete = true;
sJKBMSFrameHasTimeout = false;
if (sTimeoutFrameCounter > 0) {
// First frame after timeout
sTimeoutFrameCounter = 0;
}
processReceivedData();
printReceivedData();
/*
* Copy complete reply and computed values for change determination
*/
lastJKComputedData = JKComputedData;
lastJKReply = *sJKFAllReplyPointer; // 221 bytes
}
/*
* Reads all bytes of the requested frame into buffer and prints errors
* Sets sFrameIsRequested to false, if frame was complete and manages other flags too
* @return true if frame was completely received
*/
bool readJK_BMSStatusFrame() {
sMillisOfLastReceivedByte = millis();
uint8_t tReceiveResultCode = readJK_BMSStatusFrameByte();
if (tReceiveResultCode == JK_BMS_RECEIVE_FINISHED) {
/*
* All JK-BMS status frame data received
*/
return true;
} else if (tReceiveResultCode != JK_BMS_RECEIVE_OK) {
/*
* Error here
*/
Serial.print(F("Receive error="));
Serial.print(tReceiveResultCode);
Serial.print(F(" at index"));
Serial.println(sReplyFrameBufferIndex);
sFrameIsRequested = false; // do not try to receive more
sBMSFrameProcessingComplete = true;
printJKReplyFrameBuffer();
}
return false;
}
/*
* Here we have requested frame, but serial was not available fore a longer time => timeout at receiving
* If no bytes received before (because of BMS disconnected), print it only once
*/
void handleFrameReceiveTimeout() {
sFrameIsRequested = false; // Do not try to receive more
sBMSFrameProcessingComplete = true;
sJKBMSFrameHasTimeout = true;
if (sReplyFrameBufferIndex != 0 || sTimeoutFrameCounter == 0) {
/*
* No byte received here -BMS may be off or disconnected
* Do it only once if we receive 0 bytes
*/
Serial.print(F("Receive timeout at ReplyFrameBufferIndex="));
Serial.println(sReplyFrameBufferIndex);
if (sReplyFrameBufferIndex != 0) {
printJKReplyFrameBuffer();
}
}
sTimeoutFrameCounter++;
if (sTimeoutFrameCounter == 0) {
sTimeoutFrameCounter--; // To avoid overflow, we have an unsigned integer here
}
}
void processReceivedData() {
/*
* Set the static pointer to the start of the reply data which depends on the number of cell voltage entries
* The JKFrameAllDataStruct starts behind the header + cell data header 0x79 + CellInfoSize + the variable length cell data (CellInfoSize is contained in JKReplyFrameBuffer[12])
*/
sJKFAllReplyPointer = reinterpret_cast<JKReplyStruct*>(&JKReplyFrameBuffer[JK_BMS_FRAME_HEADER_LENGTH + 2
+ JKReplyFrameBuffer[JK_BMS_FRAME_INDEX_OF_CELL_INFO_LENGTH]]);
fillJKConvertedCellInfo();
fillJKComputedData();
handleAndPrintAlarmInfo();
computeUpTimeString();
}
void printReceivedData() {
if (!sStaticInfoWasSent) {
sStaticInfoWasSent = true;
printJKStaticInfo();
}
printJKDynamicInfo();
}
Page button.
Optional serial 2004 LCD