Hi everyone,
I'm working on building a LIN controller that uses an SD card to store a JSON-parsed LDF file. I retrieve the necessary information from this file and use it to send frames via LIN.
The issue I'm facing is that when I try to send the parsed frameID via LIN, only the initially defined value is being transmitted. I’ve added serial prints to debug, and frameID prints correctly both before and after sending, yet the LIN bus still sends the wrong value.
For example, based on my current code, I expect to see 0x15 on the LIN bus, but instead, I keep getting 0x10.
Has anyone encountered a similar issue? Could there be something preventing the updated frameID from being used in the transmission? Any insights would be greatly appreciated!
Thanks in advance!
Script:
#include "SDCardHelper.h"
#include "LIN_stack.h"
//Creating instances of SDCardHelper and LIN_stack classes
SDCardHelper sdhelper;
#define LIN_SERIAL Serial0
#define LIN_BAUD 19200
LIN_stack lin(LIN_SERIAL, LIN_BAUD);
int ErrorLED = 26;
uint8_t frameID = 0x10;
//List of tuples containing frames and delays
std::vector<std::pair<std::string, float>> scheduleList;
void setup(){
Serial.begin(115200);
delay(10);
pinMode(ErrorLED, OUTPUT);
digitalWrite(ErrorLED, LOW);
//Init SD card reader
if(!SD.begin(5)){
Serial.println("SD card Initalization failed!");
digitalWrite(ErrorLED, HIGH);
return;
}
//List files in /ldfFiles directory
Serial.println("\n--- Opening Directory /ldfFiles ---");
if(!sdhelper.listDir(SD, "/ldfFiles", 1)){
Serial.println("Failed to open directory!");
digitalWrite(ErrorLED, HIGH);
return;
}
// Load LDF from SD card
const char *ldfPath = "/ldfFiles/MQB_NI_LIN.json";
if (!sdhelper.loadLDF(ldfPath)) {
Serial.println("Failed to load LDF");
digitalWrite(ErrorLED, HIGH);
return;
} else {
Serial.println("LDF loaded and parsed successfully");
}
// Retrieve the Schedule Table (ST1)
std::vector<Schedule> scheduleTable;
if (sdhelper.getST("ST1", scheduleTable)) {
Serial.println("Schedule Table ST1 retrieved successfully");
// Populate the scheduleList with frame names and delays
for (auto& schedule : scheduleTable) {
// Convert String to std::string
std::string frameName = schedule.frameName.c_str(); // Convert String to std::string
// Add the frame and delay as a tuple (std::pair) to the list
scheduleList.push_back(std::make_pair(frameName, schedule.delay*1000));
}
} else {
Serial.println("Failed to retrieve schedule table ST1");
digitalWrite(ErrorLED, HIGH);
}
if (scheduleList.empty()) {
Serial.println("Error: Schedule list is empty!");
digitalWrite(ErrorLED, HIGH);
return;
}
auto entry = scheduleList.front(); // Get the first frame in the list
std::string frameName = entry.first;
float delayMs = entry.second;
Frame frame;
sdhelper.getFrame(frameName.c_str(), frame);
frameID = frame.id;
}
void loop() {
const uint8_t ID = frameID;
lin.sendEmptyFrame(ID, false);
delay(500);
}
SDCardhelper.cpp:
// SDCardHelper.cpp
#include "SDCardHelper.h"
bool SDCardHelper::loadLDF(const char *path){
File file = SD.open(path, FILE_READ);
if (!file) {
Serial.printf("Failed to open LDF file: %s\n", path);
return false;
}
//Allocate a buffer for JSON parsing
const size_t bufferSize= 16*1024; //Adjust after need
DynamicJsonDocument doc(bufferSize);
DeserializationError error = deserializeJson(doc, file);
if(error){
Serial.print(F("Failed to parse JSON: "));
Serial.println(error.f_str());
return false;
}
file.close();
// Parse nodes
JsonObject nodesObj = doc["nodes"];
if (!nodesObj.isNull()) {
// Extract the master node
String masterNode = nodesObj["master"].as<String>();
Node master;
master.name = masterNode;
master.isMaster = true;
nodeList.push_back(master);
// Extract the slave nodes
JsonArray slavesArray = nodesObj["slaves"];
if (!slavesArray.isNull()) {
for (JsonVariant slave : slavesArray) {
Node slaveNode;
slaveNode.name = slave.as<String>();
slaveNode.isMaster = false;
nodeList.push_back(slaveNode);
}
} else {
Serial.println("No slaves found.");
}
} else {
Serial.println("Nodes object is missing or null.");
return false;
}
//parse signal
JsonArray signalsArray = doc["signals"];
for (JsonVariant signal : signalsArray) {
Signal s;
// Parse Signal Attributes
s.name = signal["name"].as<String>();
//Serial.printf("Parsed name: %s\n", s.name.c_str());
s.size = signal["size"].as<int>();
//Serial.printf("Parsed size: %d\n", s.size);
s.initValue = signal["init_value"].as<int>();
//Serial.printf("Parsed init_value: %d\n", s.initValue);
s.publisher = signal["publisher"].as<String>();
//Serial.printf("Parsed publisher: %s\n", s.publisher.c_str());
JsonArray subs = signal["subscribers"];
for (JsonVariant sub : subs) {
String subscriber = sub.as<String>();
s.subscribers.push_back(subscriber);
// Serial.printf("Parsed subscriber: %s\n", subscriber.c_str());
}
signalMap[s.name.c_str()] = s;
}
//Parse frames
JsonArray framesArray = doc["frames"];
for(JsonVariant frame: framesArray){
Frame f;
f.name = frame["name"].as<String>();
f.id = frame["id"];
f.size = frame["size"];
f.publisher = frame["publisher"].as<String>();
JsonArray signalsArray = frame["signals"];
for(JsonVariant signal: signalsArray){
frameSignal s;
s.name = signal["name"].as<String>();
s.offset = signal["offset"];
f.signals.push_back(s);
}
frameMap[f.name] = f;
}
// Parse schedule tables
JsonObject scheduleTablesObj = doc["schedule_tables"];
for (JsonPair scheduleTable : scheduleTablesObj) {
String tableName = scheduleTable.key().c_str();
JsonArray scheduleArray = scheduleTable.value().as<JsonArray>();
std::vector<Schedule> scheduleList;
for (JsonVariant entry : scheduleArray) {
String entryStr = entry.as<String>();
int framePos = entryStr.indexOf("Frame: ");
int delayPos = entryStr.indexOf("delay");
if (framePos != -1 && delayPos != -1) {
String frameName = entryStr.substring(framePos + 7, delayPos - 1);
String delayStr = entryStr.substring(delayPos + 6, entryStr.indexOf(" s"));
float delay = delayStr.toFloat(); // Convert delay to float
Schedule schedule = { frameName, delay };
scheduleList.push_back(schedule);
}
}
scheduleMap[tableName] = scheduleList;
}
Serial.println("LDF indexing completed successfully.");
return true;
}
bool SDCardHelper::getFrame(const String& frameName, Frame& frameOut){
if(frameMap.find(frameName) != frameMap.end()){
frameOut = frameMap[frameName];
return true;
}
Serial.printf("Frame not found, Frame: %s\n", frameName);
return false; //frame not found
}
bool SDCardHelper::getSignal(const String& signalName, Signal& SignalOut){
std::string key = signalName.c_str();
// Debug print to verify the key being searched
Serial.printf("Searching for Signal: %s (key: %s)\n", signalName.c_str(), key.c_str());
if(signalMap.find(key) != signalMap.end()){
SignalOut = signalMap[key];
return true;
}
Serial.print("Signal not found in SignalMap");
return false;
}
std::vector<Node> SDCardHelper::getNodes() const {
if (nodeList.empty()) {
Serial.println("Node list is empty!");
} else {
Serial.printf("Returning %d nodes.\n", nodeList.size());
}
return nodeList;
}
bool SDCardHelper::listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root || !root.isDirectory()){
Serial.println("Failed to open directory or it's not a directory.");
return false;
}
File file = root.openNextFile();
while (file) {
Serial.print(file.isDirectory() ? " DIR : " : " FILE: ");
Serial.print(file.name());
if (!file.isDirectory()) {
Serial.print(" SIZE: ");
Serial.println(file.size());
}
if (file.isDirectory() && levels) listDir(fs, file.path(), levels - 1);
file = root.openNextFile();
}
return true;
}
bool SDCardHelper::getST(const String& tableName, std::vector<Schedule>& scheduleOut) {
if (scheduleMap.find(tableName) != scheduleMap.end()) {
scheduleOut = scheduleMap[tableName];
return true;
}
Serial.printf("Schedule table not found, Table: %s\n", tableName.c_str());
return false; // Schedule table not found
}
bool SDCardHelper::createDir(fs::FS &fs, const char *path) {
Serial.printf("Creating Dir: %s\n", path);
// Attempt to create the directory and return the result
bool success = fs.mkdir(path);
if (success) {
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
return success;
}
bool SDCardHelper::removeDir(fs::FS &fs, const char *path) {
Serial.printf("Removing Dir: %s\n", path);
// Attempt to remove the directory and store the result
bool success = fs.rmdir(path);
if (success) {
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
return success;
}
bool SDCardHelper::readFile(fs::FS &fs, const char *path) {
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return false;
}
Serial.print("Read from file: ");
while (file.available()) Serial.write(file.read());
file.close();
return true;
}
bool SDCardHelper::writeFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (file && file.print(message)){
Serial.println("File written");
file.close();
return true;
}else {
Serial.println("Write failed");
file.close();
return false;
}
}
bool SDCardHelper::appendFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (file){
if(file.print(message)){
Serial.println("Message appended");
file.close();
return true;
}
}else{
Serial.println("Append failed");
file.close();
return false;
}
}
bool SDCardHelper::renameFile(fs::FS &fs, const char *path1, const char *path2) {
Serial.printf("Renaming file %s to %s\n", path1, path2);
Serial.println(fs.rename(path1, path2) ? "File renamed" : "Rename failed");
}
bool SDCardHelper::deleteFile(fs::FS &fs, const char *path) {
Serial.printf("Deleting file: %s\n", path);
// Attempt to rename the file
bool success = fs.remove(path);
// Print the result and return the success status
if (success) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
return success; // Return true if successful, false if failed
}
bool SDCardHelper::testFileIO(fs::FS &fs, const char *path) {
static uint8_t buf[512];
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return false;
}
size_t len = 0, flen = len = file.size();
uint32_t start = millis();
while (len >0){
size_t toRead = (len > 512) ? 512 : len;
file.read(buf, toRead);
len -= toRead;
}
Serial.printf("%u bytes read in %lu ms\n", flen, millis() - start);
file.close();
//write test
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("failed to open file for writing");
return false;
}
for (size_t i = 0; i < 2048; i++) {
file.write(buf, 512);
}
Serial.printf("2048 * 512 bytes written in %lu ms\n", millis() - start);
file.close();
return true;
}
String SDCardHelper::createFilename(fs::FS &fs, const char *baseName, int fileCounter) {
String filename;
int count = fileCounter;
do {
filename = String(baseName) + "_" + String(count++) + ".txt";
} while (fs.exists(filename.c_str()));
return filename;
}
LIN_stack.cpp:
/*
*
* LIN STACK for TJA1021&TJA1028
* v2.0
*
* Short description:
* Comunication stack for LIN and TJA1028 LIN transceiver.
* Can be modified for any Arduino board with UART available and any LIN slave.
*/
#include <Arduino.h>
#include <LIN_stack.h>
#if defined(__AVR__)
#include <avr/sfr_defs.h>
#endif
#if defined(__ESP32__)
#include "driver/uart.h" // Include UART driver
#include "soc/uart_reg.h" // Include UART register definitions
#include "soc/uart_struct.h" // Include the UART structures
#endif
/* LIN PACKET:
It consist of:
___________ __________ _______ ____________ _________
| | | | | |
|Synch Break|Synch Byte|ID byte| Data Bytes |Checksum |
|___________|__________|_______|____________|_________|
Every byte have start bit and stop bit and it is send LSB first.
Synch Break - min 13 bits of dominant state ("0"), followed by 1 bit recesive state ("1")
Synch Byte - Byte for Bound rate syncronization, always 0x55
ID Byte - consist of parity, length and address; parity is determined by LIN standard and depends from
address and message length
Data Bytes - user defined; depend on devices on LIN bus
Checksum - inverted 256 checksum; data bytes are summed up and then inverted
*/
// CONSTRUCTORS
LIN_stack::LIN_stack(HardwareSerial &_channel, uint16_t _baud) :
baud(_baud), channel(_channel)
{
#ifdef OVERRIDE_GPIO_FUNCS
setPinMode(pinMode);
setDigitalWrite(digitalWrite);
#endif
}
void LIN_stack::begin(uint8_t _ident)
{
ident = _ident; //Assigns the identifier
}
// PUBLIC METHODS
// Creates a LIN packet and then send it via USART(Serial) interface. (master-side write)
void LIN_stack::write(const uint8_t ident, const void *data, size_t len, bool enhanced) {
// Synch Break
lin_break(); //Send synch break
// Send data via Serial interface
channel.begin(baud); //Set baud rate
channel.write(0x55); //Send sync field (0x55 pre.def in LIN protocol)
if(enhanced){
channel.write(generateIdent(ident));
}else{
channel.write(ident); //Send identifier
}
channel.write((const char *)(data), len); //Send data payload
channel.write(calcChecksum(data, len, ident, enhanced)); //Calculate and send Checksum
channel.flush(); //flush channel to ensure all data is sent
}
// Creates an empty with only Sync and IDLIN packet and then send it via USART(Serial) interface. (master-side write)
void LIN_stack::sendEmptyFrame(const uint8_t ident, bool enhanced) {
//channel.begin(baud);
//channel.write(generateIdent(ident));
/*
Serial.print("ID: ");
Serial.println(ident, HEX);
Serial.print("Sizeof: ");
Serial.println(sizeof(ident));
Serial.print("PID: ");
Serial.println(generateIdent(ident), HEX);
*/
uint8_t pid = enhanced ? generateIdent(ident) : ident;
// Synch Break
lin_break(); //Send synch break
// Send data via Serial interface
channel.begin(baud); //Set baud rate
channel.write(0x55); //Send sync field (0x55 pre.def in LIN protocol)
channel.write(pid);
channel.flush(); //flush channel to ensure all data is sent
}
// Setup a master-side read.
void LIN_stack::writeRequest(const uint8_t ident) {
// Synch Break
lin_break(); //Init Synch break
// Send data via Serial interface
channel.begin(baud); //Init Serial channel with baud rate
channel.write(0x55); //Send sync field (0x55 for LIN)
channel.write(ident); //Send identifier
channel.flush(); //Ensure all data is sent
}
// Send the slave-side response to a master-side read
void LIN_stack::writeResponse(const void *data, size_t len) {
channel.begin(baud); //Init Serial channel with baud rate
channel.write((const char *)(data), len); //Send data payload
channel.write(calcChecksum(data, len, ident, true)); //Calculate and send Checksum
channel.flush(); //Flush channel to ensure all data is sent
}
void LIN_stack::writeStream(const void *data, size_t len) {
// Synch Break
lin_break(); //Init Synch break
// Send data via Serial interface
//channel.begin(baud); //Init Serial channel with baud rate
channel.write(0x55); //Send Sync field (0x55 in LIN)
channel.write(ident); //Send identfier
channel.write((const char *)(data), len); //Send data payload
channel.flush(); //Flush channel to ensure all data is sent
}
// slave-side receive from master
bool LIN_stack::read(uint8_t *data, const size_t len, size_t *read_) {
size_t loc; //Variable to hold read count
uint8_t header[2]; //Array to store header from master
bool retval = true; //Return value to indicate success or fail
//Check if read_pointer is null and assign address
if(!read_){
read_ = &loc;
}
//Read header (2bytes from channel)
*read_ = channel.readBytes(header, 2);
//validate that 2bytes are read
if (*read_ != 2 || header[0] != 0x55 || !validateParity(header[1])) {
channel.flush();
return false;
}
if (!channel.available()) {
*read_ = 0;
return true;
}
//Indicate that a write request from master
*read_ = channel.readBytes(data, len); //Read data from channel
channel.flush(); //Clear buffer after reading
return validateChecksum(data, *read_); //Validate and return checksum
}
//Init Serial channel with specific baud rate
void LIN_stack::setupSerial(void) {
channel.begin(baud); //Begin Serial comm
}
//Check break signal on serial channel
bool LIN_stack::breakDetected(void) {
#ifdef __AVR__
// AVR-specific code for detecting a break condition
return bit_is_set(UCSR0A, FE0);
#elif defined(ARDUINO_ARCH_RP2040)
// For Raspberry Pi Pico (RP2040) architecture
return channel.getBreakReceived(); // Use RP2040's break detection function
#elif defined(ESP32)
// ESP32-specific code using HardwareSerial API
if (channel.available()) {
// Read the next byte to check for framing error
uint8_t byte = channel.read();
if (byte == 0x00) {
// A framing error likely indicates a break signal
return true;
}
}
return false;
#elif defined(STM32F0xx)
// STM32-specific code, FE bit in USART_ISR (Frame Error flag)
return (USART1->ISR & USART_ISR_FE) != 0; // Example for STM32 (adjust depending on your setup)
#else
// Default case: You might not have break detection support
return false;
#endif
}
//Wait for break signal to be detected, with max timout
bool LIN_stack::waitBreak(uint32_t maxTimeout) {
const auto enterTime = millis(); //Begin recording time
while(!breakDetected()) { //Check while no break detected
const auto now = millis(); //Get current time
if(maxTimeout < UINT32_MAX && now - enterTime > maxTimeout) {
// we timed out
return false;
}
}
return true; //Break detected
}
//Read certain number of bytes from Serial into data buffer
int LIN_stack::readStream(uint8_t *data, size_t len){
return channel.readBytes(data, len); //Return number of bytes read
}
// PRIVATE METHODS
// send the break field. Since LIN only specifies min 13bit, we'll send 0x00 at half baud
void LIN_stack::lin_break(void) {
channel.flush(); //Flush to clear any prev data
channel.begin(baud / 2); //Init Serial channel at half rate for sending break field
channel.write(0x00); // send the break field(0x00)
channel.flush(); //Ensure all is sent
}
void LIN_stack::sleep_config(void) {
pinModeFunc(wake_pin, OUTPUT); //Set wake pin as output
pinModeFunc(sleep_pin, OUTPUT); //Set sleep pin as output
digitalWriteFunc(wake_pin, HIGH); //Init wake pin to HIGH
digitalWriteFunc(sleep_pin, LOW); //Init sleep pin to LOW
current_sleep_state = STATE_SLEEP; //Set current sleep state to STATE_SLEEP
}
//Validate parity of identifier
bool LIN_stack::validateParity(uint8_t _ident) {
return (_ident == ident);
}
//Calculate checksum
uint8_t LIN_stack::calcChecksum(const void *data, size_t len, const uint8_t ident, bool enhanced) {
const uint8_t *p = static_cast<const uint8_t *>(data);
uint16_t ret = 0x00; // Init checksum to 0
// If using Enhanced Checksum (LIN 2.0+), include protected ID
if (enhanced) {
uint8_t pid = generateIdent(ident);
ret += pid;
}
// Loop and sum data bytes
for (size_t i = 0; i < (len); i++) {
ret += p[i];
// Carry handling (ensure sum remains within 8-bit)
if (ret > 0xFF){
ret = (ret & 0xFF) + 1;
}
}
return (uint8_t)(~ret);
}
//Validate checksum
bool LIN_stack::validateChecksum(const void *data, size_t len) {
uint8_t crc = calcChecksum(data, len - 1, ident, true);
return (crc == ((const uint8_t *)(data))[len]);
}
void LIN_stack::busWakeUp(void) {
// generate a wakeup pattern by sending 9 zero bits, we use 19200 baud to generate a 480us pulse
channel.flush();
channel.begin(19200);
channel.write(0x00);
channel.flush();
channel.begin(baud);
}
uint8_t LIN_stack::generateIdent(const uint8_t addr) const {
return (addr & 0x3f) | calcIdentParity(addr);
}
/* Create the Lin ID parity */
#define BIT(data, shift) ((ident & (1 << shift)) >> shift)
uint8_t LIN_stack::calcIdentParity(const uint8_t ident) const {
uint8_t p0 = BIT(ident, 0) ^ BIT(ident, 1) ^ BIT(ident, 2) ^ BIT(ident, 4);
uint8_t p1 = ~(BIT(ident, 1) ^ BIT(ident, 3) ^ BIT(ident, 4) ^ BIT(ident, 5));
return (p0 | (p1 << 1)) << 6;
}
void LIN_stack::sendFrame(uint8_t frameID, const uint8_t *signalData, size_t signalLength, bool enhanced){
uint8_t frame[signalLength + 1]; //Frame = [ID] + [Signal Data]
frame[0] = frameID;
memcpy(&frame[1], signalData, signalLength);
write(frameID, signalData, signalLength, enhanced);
}
bool LIN_stack::receiveFrame(uint8_t frameID, uint8_t *signalData, size_t signalLength){
uint8_t frame[signalLength + 1]; //Frame = [ID] + [Signal Data]
size_t bytesRead;
if(!read(frame, signalLength + 1, &bytesRead)){
return false; //Read failed
}
if(frame[0] != frameID){
return false; //Mismatched frame ID
}
memcpy(signalData, &frame[1], signalLength); //extract signal data
return true;
}