Teensy 4.0 / High Voltage Heater

jsimonkeller

Well-known member
Hi. I was hoping someone could assist me with help on the serial plotter. I have been working with someone to sort code for LIN bus control of a HV heater on my EV build. The project was part of an STM32 programming, but we have converted over to Arduino and it compiles. I am wanting to use the serial plotter to determine is the signal is going out as it should. Below is the programming and I wondered what I need to change to get readings back on the serial plotter. Thanks!

Code:
  #include "lin_bus.h"

// Create an IntervalTimer object
IntervalTimer myTimer;

int ledState = LOW;                // ledState used to set the LED
unsigned long interval = 200000;   // interval at which to blinkLED to run every 0.2 seconds
uint16_t Power = 175; // set to required power
uint8_t Temperature = 45; //set to required temperature
uint16_t tmpheater = 0;
uint16_t udcheater = 0;
uint16_t powerheater = 0;

LIN lin;

int lin_cs = 32; // cs and serial port set for skpang LIN / FDCAN board

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);
 
  //Serial.begin(19200);
  //Serial.print("HVH50 Heater demo");
 
  myTimer.begin(blinkLED, interval);

  LIN l(&Serial3, 9600);
 // LIN l(&Serial3, 19200); /// Change to this for 19200 /////
  lin = l;
 
}

void loop() {
  // heater
  SendLin();
  delay(100); // wait 100ms
  //Serial.print(" Heater test\n");
}

void blinkLED() {
  ledState = !ledState;
 
  digitalWrite(LED_BUILTIN, ledState);
}

  static void SendLin()
{
   static bool read = true;
   uint8_t data[8];
  if (lin.response(22, data, 8) >=0) // -1 indicates crc error, 9600
//if (lin.response(24, data, 8) >=0) /// Change to this for 19200 /////
      {
         tmpheater = data[1] - 40;
         udcheater = data[4] | (data[5] & 3) << 8;
         powerheater =((data[5] >> 2) | (data[6] << 8)) * 20;
         Serial.print("\n Temp ");
         Serial.print(tmpheater);
         Serial.print("\n Udc ");
         Serial.print(udcheater);
         Serial.print("\n Power ");
         Serial.print(powerheater);
      }
   if (read)
   {
      lin.order(22, 0, 0); // 9600
      //lin.order(24, 0, 0); /// Change to this for 19200 /////
   }
   else
   {
        uint8_t lindata[] = {uint8_t(Power/40), uint8_t(Temperature+40), 0, 8};
          lin.order(21, lindata, 4); // this for 9600
      //lin.order(35, lindata, 4); /// Change to this for 19200 /////
      Serial.print("\n Sending power and temperature");
   }

   read = !read;
}
 
Not really sure whether I understand your question, but the following code (stripped down your code):
C++:
uint16_t tmpheater = 40;
uint16_t udcheater = 50;
uint16_t powerheater = 60;

void setup() {
}

void loop() {
  Serial.print("Temp:");
  Serial.print(tmpheater);
  Serial.print(",");
  Serial.print("Udc:");
  Serial.print(udcheater);
  Serial.print(",");
  Serial.print("Power:");
  Serial.println(powerheater);
  delay(1000);
}
generates this output on the serial plotter:

1707406707243.png


Found the info here.
Is this what you are looking for?

Paul
 
Last edited:
Hi Paul. Thank you for that reply and the link. This is really helpful to see those items show up in the plotter.

I may be confused, but I was trying to see those same items in MY CODE but showing them as feedback from the heater on the current Temp, UDC (HV) and Power (watts). I am not sure how to do that with MY CODE and what I would have to change for it to still send the LIN commands to activate the heater, but also return the feedback into the plotter so I can tell if things are working or, more importantly, what is NOT working.

Thanks!
 
Your code has this:

Code:
         Serial.print("\n Temp ");
         Serial.print(tmpheater);
         Serial.print("\n Udc ");
         Serial.print(udcheater);
         Serial.print("\n Power ");
         Serial.print(powerheater);

Paul's working example has this:

Code:
  Serial.print("Temp:");
  Serial.print(tmpheater);
  Serial.print(",");
  Serial.print("Udc:");
  Serial.print(udcheater);
  Serial.print(",");
  Serial.print("Power:");
  Serial.println(powerheater);

Perhaps try editing that portion of your code to be more like the working code. In particular, remove the "\n " at the beginning of each print, and add the extra print for a comma between each item, and change the last print to println. Hopefully that is not too difficult?
 
I am not sure how to do that with MY CODE and what I would have to change for it to still send the LIN commands to activate the heater, but also return the feedback into the plotter so I can tell if things are working or, more importantly, what is NOT working.
Your code has a function SendLin(). In that function I see a command lin.response(22, data, 8). I assume, since I don't exactly know which lin-bus library you are using, that that function returns something into data[8]. Then you are printing the different indexes of that data array. Those Serial.print's need to be changed to exactly what I showed. The colon after Temp, Udc and Power is important! So is what Paul Stoffregen stated above.
What I'm not sure about is whether your SendLin() function actually sends out something? Can you share the exact lin-bus library [e.g. GitHub link]?

Paul
 
OK. Made those changes and here is what the serial plotter shows, so not sure what is getting in the way still. Any thoughts?



1707425348536.png



1707425371309.png
 
Hmm, that's weird.
Seeing value 2, value 3, value 4 and value 5 makes me believe there is another Serial.print() in your code somewhere.
Did you perhaps try my exact code from message #2?

Another thing I noticed [but could be totally unrelated], is that my output screen shows 3 white lines of text below the 4 red lines of text:
1707426715059.png


Also, what Teensyduino version are you using? Mine shows 1.58.1, see screenshot above.

Paul
 
Allright, you may want to close the serial plotter window and re-open it again. Leaving it open while uploading new code may not update the labels.

Paul
 
At line 76, there is the following:

Serial.print("\n Sending power and temperature");

I added the \\ to grey this out, but it did not change the plotter result.

I did try your stripped down code and it works. So now I need to see what is causing it to glitch when included in all of my code.
 
I am not sure if it has to do with the coding above the serial print lines with all of those DATA numbers.
Nope, added those lines to my code and the serial plotter still looks fine with the correct 3 labels.

1707428980968.png

1707428999696.png


I guess it's up to you now to dig through your code...

Paul
 
I got it!!! It was the { } before and after the serial.print list that was blocking it out. Once I removed them, the serial plotter showed all of these items with values!

1707439456798.png



1707439477487.png



I am curious if this change will also help to make the heater work since this info was being blocked out by the brackets....
 
I cant give you a code snippet since you posted an image instead of text, but to fix the logic I think you need to include the braces but put the close brace between the powerheater = ... line and the first Serial.print
 
Here is the code. Sorry for that.

Code:
#include "lin_bus.h"

// Create an IntervalTimer object
IntervalTimer myTimer;

int ledState = LOW;                // ledState used to set the LED
unsigned long interval = 200000;   // interval at which to blinkLED to run every 0.2 seconds
uint16_t Power = 175; // set to required power
uint8_t Temperature = 45; //set to required temperature
uint16_t tmpheater = 0;
uint16_t udcheater = 0;
uint16_t powerheater = 0;

LIN lin;

int lin_cs = 32; // cs and serial port set for skpang LIN / FDCAN board

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(lin_cs, OUTPUT);
  digitalWrite(lin_cs, HIGH);
 
  //Serial.begin(19200);
  //Serial.print("HVH50 Heater demo");
 
  myTimer.begin(blinkLED, interval);

  //LIN l(&Serial3, 9600);
  LIN l(&Serial3, 19200); /// Change to this for 19200 /////
  lin = l;
 
}

void loop() {
  // heater
  SendLin();
  delay(100); // wait 100ms
  //Serial.print(" Heater test\n");
}

void blinkLED() {
  ledState = !ledState;
 
  digitalWrite(LED_BUILTIN, ledState);
}

  static void SendLin()
{
   static bool read = true;
   uint8_t data[8];
  //if (lin.response(22, data, 8) >=0) // -1 indicates crc error, 9600
  if (lin.response(24, data, 8) >=0) /// Change to this for 19200 /////
      
     tmpheater = data[1] - 40;
     udcheater = data[4] | (data[5] & 3) << 8;
     powerheater =((data[5] >> 2) | (data[6] << 8)) * 20;
     Serial.print("Temp:");
     Serial.print(tmpheater);
     Serial.print(",");
     Serial.print("Udc:");
     Serial.print(udcheater);
     Serial.print(",");
     Serial.print("Power:");
     Serial.println(powerheater);
      
   if (read)
   {
      //lin.order(22, 0, 0); // 9600
      lin.order(24, 0, 0); /// Change to this for 19200 /////
   }
   else
   {
        uint8_t lindata[] = {uint8_t(Power/40), uint8_t(Temperature+40), 0, 8};
          //lin.order(21, lindata, 4); // this for 9600
      lin.order(35, lindata, 4); /// Change to this for 19200 /////
      //Serial.print("\n Sending power and temperature");
   }

   read = !read;
}
 
I got it!!! It was the { } before and after the serial.print list that was blocking it out. Once I removed them, the serial plotter showed all of these items with values!
Glad to hear it's working!
But I don't understand why... As far as I know it is allowed to have only one statement after an if() without curly brackets, see this discussion .
Do I read correctly from that discussion that only the first statement is executed conditionally and rest is always executed regardless the condition?

Your initial code, with the curly brackets, should be prefectly acceptable.

Perhaps someone can shine a light on this?

Thanks,
Paul
 
Did some testing on "if() without curly brackets".
Sample code with curly brackets:
C++:
  if (condition) {
    Serial.println("this is line 1");
    Serial.println("this is line 2");
    Serial.println("this is line 3");
  }
If condition is false, then no lines are printed.
Now sample code without curly brackets:
C++:
  if (condition)
    Serial.println("this is line 1");
  Serial.println("this is line 2");
  Serial.println("this is line 3");
If condition is false, then lines #2 and #3 are printed.

Took your code from message #1, entered it into the Arduino IDE and hit <ctrl>+T (auto-format):
C++:
if (lin.response(24, data, 8) >= 0) {

  tmpheater = data[1] - 40;
  udcheater = data[4] | (data[5] & 3) << 8;
  powerheater = ((data[5] >> 2) | (data[6] << 8)) * 20;
  Serial.print("Temp:");
  Serial.print(tmpheater);
  Serial.print(",");
  Serial.print("Udc:");
  Serial.print(udcheater);
  Serial.print(",");
  Serial.print("Power:");
  Serial.println(powerheater);
}
Nothing is printed when the condition lin.response is false.

Then took your code from message #18, entered it into the Arduino IDE and hit <ctrl>+T (auto-format):
C++:
if (lin.response(24, data, 8) >= 0)

  tmpheater = data[1] - 40;
udcheater = data[4] | (data[5] & 3) << 8;
powerheater = ((data[5] >> 2) | (data[6] << 8)) * 20;
Serial.print("Temp:");
Serial.print(tmpheater);
Serial.print(",");
Serial.print("Udc:");
Serial.print(udcheater);
Serial.print(",");
Serial.print("Power:");
Serial.println(powerheater);
Without the curly brackets, it does print everything to the serial plotter even while the condition lin.response is false.
I'm pretty sure that was not your intention.

Paul
 
Hi Paul. Thank you for the response and taking the time to troubleshoot this. So, if I am understanding your post, the point you are making is that with the braces in place, it is intended to print nothing if when a FALSE is returned as a way to know it was FALSE. So if I was getting a FALSE response in testing, that is why I was not seeing the UDC, POWER and TEMP. Your earlier code to test that the plotter was working did not have the IF command, so you would see it in all instances.

SO . . . If I understand you correctly, I should return the braces to where they were originally because it is the only way to know if the command is returning as FALSE.

Is that correct?

In the original coding, we had the /n before each print item. Was that significant or can I leave things the way you worked it but with the braces back in place?
 
So, if I am understanding your post, the point you are making is that with the braces in place, it is intended to print nothing if when a FALSE is returned as a way to know it was FALSE.
Correct.
So if I was getting a FALSE response in testing, that is why I was not seeing the UDC, POWER and TEMP.
Correct again.
SO . . . If I understand you correctly, I should return the braces to where they were originally because it is the only way to know if the command is returning as FALSE.
Is that correct?
And correct again.
In the original coding, we had the /n before each print item. Was that significant or can I leave things the way you worked it but with the braces back in place?
Don't use the \n - just leave things the way I worked it, with the curly brackets in place.
I have noticed that the Arduino plotter is quit picky on the syntax of the serial data to correctly display the labels. Even the colon after "Temp:" makes a difference.

So, I think you need to focus on the (lin.response(24, data, 8) function to see why it is not returning TRUE with data.
Can you share the exact LIN-bus library you are using? There are several libraries out there.

Paul
 
Paul, once again THANK YOU for you time.


Here is the lin_bus.h I am using.

Code:
#ifndef LIN_H
#define LIN_H

#include "Stream.h"

class LIN
{
public:
  //Tbits Header bits
  #define breakdelimiter              1
  #define syncfieldPIDinterbytedelay  0
  #define breakfieldinterbytedelay    2
 
  //Tbit Response bits
  #define responsedelay               8
  #define interbytedelay              0
  //LIN supports from 2-8bit Data and an additional CRC bit
  #define response_nominal     (8+1)*10
  #define response_max_factor       1.4 
 
  byte SYNC = 0x55;
  unsigned long Tbit;
  int responsespace;
  int interbytespace;
  int syncfieldPIDinterbytespace;
  int breakfieldinterbytespace;
  unsigned long response_nominalspace;
  unsigned long response_maximalspace;
  uint8_t _breaklenght;
 
  // Teensy 3.0 & 3.1 & 3.2 & 3.5 & 3.6
  //#if defined (__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MKL26Z64__) || defined(__MK66FX1M0__)
    volatile uint8_t *PortRegister_C1;
    volatile uint8_t *PortRegister_C2;
    volatile uint8_t *PortRegister_C4;
    volatile uint8_t *PortRegister_S2;
    volatile uint8_t *PortRegister_BDH;
  //#elif defined(__IMXRT1062__) // Teensy 4.0 & 4.1
    volatile uint32_t *PortRegister_LPUART_STAT;
    volatile uint32_t *PortRegister_LPUART_BAUD;
    volatile uint32_t *PortRegister_LPUART_CTRL;
  //#endif
 
  #define lin1x 1
  #define lin2x 2
 
  // Constructor for Node
  LIN(HardwareSerial* stream, uint16_t baudrate, uint8_t break_characters = 13) {begin(stream, baudrate, break_characters);}
  LIN() {_stream = 0; }
  void begin(HardwareSerial* stream, uint16_t baudrate, uint8_t break_characters = 13);
  void order(byte PID, byte* message, int length, int checksumtype = 1);
  int response(byte PID, byte* message, int length, int checksumtype = 1);
 
private:
  Stream* _stream;
  void send_break();
  void breaklength(uint8_t length);
  void breaklength_35(uint8_t length);
  void breaklength_LP(uint8_t length);
  void breaklength_LC(uint8_t length, HardwareSerial* stream);
  int addrParity(int PID);
  volatile byte dataChecksum (volatile byte* message, int length, uint16_t sum);
  void write(byte PID, byte* message, int length, int checksumtype = 1);
  int read(byte PID, byte message[], int length, int checksumtype = 1);
};

#endif

If it helps, here is the linbus.h that was part of the stm32 version we were converting to arduino code.

Code:
/*
 * This file is part of the stm32-car project.
 *
 * Copyright (C) 2021 Johannes Huebner <dev@johanneshuebner.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef LINBUS_H
#define LINBUS_H


class LinBus
{
   public:
      /** Default constructor */
      LinBus(uint32_t usart, int baudrate);
      void Request(uint8_t id, uint8_t* data, uint8_t len);
      bool HasReceived(uint8_t pid, uint8_t requiredLen);
      uint8_t* GetReceivedBytes() { return &recvBuffer[payloadIndex]; }

   protected:

   private:
      struct HwInfo
      {
         uint32_t usart;
         uint8_t dmatx;
         uint8_t dmarx;
         uint32_t port;
         uint16_t pin;
      };

      static uint8_t Checksum(uint8_t pid, uint8_t* data, int len);
      static uint8_t Parity(uint8_t id);

      static const HwInfo hwInfo[];
      static const int payloadIndex = 3;
      static const int pidIndex = 2;
      uint32_t usart;
      const HwInfo* hw;
      uint8_t sendBuffer[11];
      uint8_t recvBuffer[12];
};

#endif // LINBUS_H

Maybe you will see something in this one that is missing from the one I AM using.

Lastly, if you are really wanting a deep dive, here is the code from the STM32 project (using the 9600 baud code options):

Code:
static void SendLin()
{
   static bool read = true;

   if (lin->HasReceived(22, 8))
   {
      uint8_t* data = lin->GetReceivedBytes();

      Param::SetInt(Param::tmpheater, data[1] - 40);
      Param::SetInt(Param::udcheater, data[4] | ((data[5] & 3) << 8));
      Param::SetFlt(Param::powerheater, FP_FROMINT(((data[5] >> 2) | (data[6] << 8)) * 20) / 1000);
   }

   if (read)
   {
      lin->Request(22, 0, 0);
   }
   else
   {
      uint8_t lindata[4];
      lindata[0] = Param::GetInt(Param::heatpowmax) / 40;
      lindata[1] = Param::GetInt(Param::heatmax) + 40;
      lindata[2] = 0;
      lindata[3] = Param::GetInt(Param::opmode) == MOD_RUN ? 8 : 0;

      lin->Request(21, lindata, sizeof(lindata));
   }

   read = !read;
}
 

Attachments

  • lin_bus.h
    2.2 KB · Views: 17
  • linbus.h
    1.6 KB · Views: 17
  • linbus.cpp
    5 KB · Views: 13
Back
Top