HardwareSerial.write and parsed variable

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;
}

Your topic does not indicated a problem with the Arduino Command Line Tools and therefore has been moved to a more suitable location on the forum.

You have defined lin to use Serial0 which is the same as Serial - that is going to be a problem.

I do not see a call to lin.begin() which seems to be a problem.

What arduino board are you using? If it is an Uno, you are using a lot on Strings which could easily lead to memory issues.

No it is an arduino nano esp32. What kind of problems can the Serial0 lead to, i have till now not seen any problems regarding the Serial or LIN. I am able to send data and read it on my oscilloscope. You are correct there is no lin.begin() I use lin.setupSerial() instead. It must have been lost when i copied over the code.

No problems for the nano esp32. Serial is the native USB and not the same as Serial0 for that board.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.