LIN bus Rx Interrupt - Cannot clear interrupt - Teensy4.1

Dimitri

Well-known member
Hello all,

I have been working on LIN implementations on Teensy4.1, basing most everything I do from the LIN libraries from Markus Lange https://github.com/MarkusLange/Teensy_3.x_4.x_and_LC_LIN_Master

What I am trying to do is trigger an interrupt when the Break field of the LIN bus is detected. In my software below, when I enable the ISR and do not transmit any LIN frames from my external LIN tool, the ISR is never called.

As soon as I transmit a single LIN frame, the ISR gets repeated called and never stops. This infinite ISR the jams up the entire µC and stops the board LED heartbeat.

In the Setup() function, I output the values of the registers LPUART4_STAT, LPUART4_CTRL, and LPUART4_BAUD as a reference. The initial results are:

LPUART4_STAT = 0x04800000
LPUART4_CTRL = 0x003C0000
LPUART4_BAUD = 0x1F008048

Once I transmit out one LIN frame, then I receive this infinite stream of data - please look at the ISR code for the byte order
LIN2 Rx ISR Count: 0 3 45E2C000 5E00000 3C0000 1F008048
LIN2 Rx ISR Count: 1 3 5E00000 5E00000 3C0000 1F008048
LIN2 Rx ISR Count: 2 3 5E00000 5E00000 3C0000 1F008048
LIN2 Rx ISR Count: 3 3 5E00000 5E00000 3C0000 1F008048
LIN2 Rx ISR Count: 4 3 5E00000 5E00000 3C0000 1F008048

From this, I can see that LPUART4_CTRL, and LPUART4_BAUD do not change when the interrupt is called.

Over time, LPUART4_STAT goes from 0x05E00000 to 0x04E00000 - no clue why.

Does anyone know how to stop this infinite interrupt? For example, in the QuadTimer interrupts, you must manually clear the affected interrupt bit - I just cant seem to find that here for the LPUART.

Thank You!

Code:
unsigned long LIN_Baudrate = 19200;
unsigned long LIN2_Rx_Counter = 0;
const int pin_LED = 13;
boolean LED_St = 0;

unsigned long t1,t2,t3;
///////////////////////////////////////////////////////////////////////////////////////////////
void setup() 
{ 
  pinMode(pin_LED,OUTPUT);
  digitalWrite(pin_LED,LED_St);  
  LIN_Setup();

  //Just for reference sake
  Serial.print(LPUART4_STAT,HEX);
  Serial.print(" ");
  Serial.print(LPUART4_CTRL,HEX);
  Serial.print(" ");
  Serial.println(LPUART4_BAUD,HEX);

  t1 = millis();
  t2 = t1;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void loop() 
{
  //Heartbeat Function
  if((t1-t2) >= 500)
  {
    LED_St = !LED_St;
    digitalWrite(pin_LED,LED_St);
    t2 = t1;
  }  
}
///////////////////////////////////////////////////////////////////////////////////////////////
void LIN_Setup()
{
  //Set Baudrate
  Serial2.begin(LIN_Baudate);   

  //Allows for LIN Break detection
  LPUART4_BAUD |= LPUART_BAUD_LBKDIE; 

  //Transmit Setup - LIN Master
  LPUART4_STAT |= LPUART_STAT_BRK13;
  LPUART4_BAUD &= ~LPUART_BAUD_SBNS;
  LPUART4_CTRL &= ~LPUART_CTRL_M;
  LPUART4_BAUD &= ~LPUART_BAUD_M10;
  LPUART4_CTRL &= ~LPUART_CTRL_M7;

  //LIN Rx Interrupt Enable
  attachInterruptVector(IRQ_LPUART4, LIN_Rx_ISR);
  NVIC_ENABLE_IRQ(IRQ_LPUART4);
}
///////////////////////////////////////////////////////////////////////////////////////////////
void LIN_Rx_ISR()
{
  int NumBytes = 0;

  //Count number of times this ISR has been visited
  Serial.print("LIN2 Rx ISR Count: ");
  Serial.print(LIN2_Rx_Counter);
  Serial.print(" ");
  LIN2_Rx_Counter = LIN2_Rx_Counter + 1;
  
  NumBytes = Serial2.available();
  Serial.print(NumBytes);
  Serial.print(" ");
  Serial.print(LPUART4_STAT,HEX);
  Serial.print(" ");

  //If the LBKDIF bit set within STAT register, write a 1 to this location to clear it
  if((LPUART4_STAT & LPUART_STAT_LBKDIF) > 0)
  {
    LPUART4_STAT |= LPUART_STAT_LBKDIF; //Resets this bit back to 0    
  }

  //Here I am just statically setting this to zero
  LPUART4_STAT &= ~(LPUART_STAT_LBKDIF); 

  //At this point, I am out of ideas
  LPUART4_STAT = 0x04800000;
  Serial.print(LPUART4_STAT,HEX);
  Serial.print(" ");

  //The 2 registers below are just for reference
  Serial.print(LPUART4_CTRL,HEX);
  Serial.print(" ");
  Serial.print(LPUART4_BAUD,HEX);
  Serial.println();
  __asm volatile ("dsb"); 
}
 
Just guessing, as I know nothing about LIN, but if the interrupt is something other than LKBDIF, I don't see that you're checking or doing anything to clear it.
 
t1 for heartbeat timing is only updated once in setup? It can only enter once then setting t2=t1 with no change in t1 in loop() it will never enter/beat/toggle again: t1 = millis();

Calling Serial2.begin(LIN_Baudate); will put the PJRC CORE code as interrupt responder to : attachInterruptVector(IRQ_LPUART4

Having LIN_Setup calling that again with an alternate LIN_Rx_ISR would seem to prevent any use of Serial2.available(); or other CORE code functions? That doesn't seem to be desired.

Posted code once where the Rx pin was monitored for interrupt from GPS unit. The pin would go HIGH on STOP, interrupt on LOW was start of GPS transmission and the interrupt received was then disabled until that message read was complete and then enabled again. This was to get a sync timestamp on Teensy for start of recurring update message from GPS, and to know when to look for incoming 'string' to complete.
 
t1 for heartbeat timing is only updated once in setup? It can only enter once then setting t2=t1 with no change in t1 in loop() it will never enter/beat/toggle again: t1 = millis();

Calling Serial2.begin(LIN_Baudate); will put the PJRC CORE code as interrupt responder to : attachInterruptVector(IRQ_LPUART4

Having LIN_Setup calling that again with an alternate LIN_Rx_ISR would seem to prevent any use of Serial2.available(); or other CORE code functions? That doesn't seem to be desired.

Posted code once where the Rx pin was monitored for interrupt from GPS unit. The pin would go HIGH on STOP, interrupt on LOW was start of GPS transmission and the interrupt received was then disabled until that message read was complete and then enabled again. This was to get a sync timestamp on Teensy for start of recurring update message from GPS, and to know when to look for incoming 'string' to complete.

You wouldn't have that code handy would you?
I have been struggling with this exact same issue for a long time. Usually I just use a long delay to wait for the Ublox to finish the 100 or so bytes that it sends over serial 1.
A long time ago I was looking in the core for teensy 3.x and the data sheet for the processor, I found an internal interrupt that can be enabled to do exactly this function, but this interrupt was not implemented in the core code and at the time I did not have sufficient mental horsepower to implement it.

Regards,
Ed
 
@defragster - you are correct about updating t1 - the code I posted here is a snippet of my actual (large) project. In my main project, I have
Code:
void loop()
{
    t1 = millis();

Are you saying the Serial2 will take over functionality of the IRQ_LPUART4? I am not exactly following.... In the end, I am able to process the Rx LIN frames, but as standard serial messages that begin with 0x00 followed by 0x55. The thought was to actually trigger the reception of LIN message with an official 13-bit Break.


@sbfreddie - In my normal code for LIN Rx, in the loop() function, i always poll Serial2.available().....

Code:
unsigned long LIN_Baudrate = 19200;
unsigned long LIN2_Rx_Counter = 0;
unsigned long LIN_AnchorPoint_ms = 0;
const unsigned long LIN_MinDelayTime_ms = 14;
byte LIN_Data_Raw[12];
byte LIN_Data_Act[8];
const int LIN_FrameID = 0x2C;
const int LIN_ProtectedID = 0xEC;
const int pin_LED = 13;
boolean LED_St = 0;
int NumBytes = 0;

unsigned long t1,t2;
///////////////////////////////////////////////////////////////////////////////////////////////
void setup() 
{ 
  pinMode(pin_LED,OUTPUT);
  digitalWrite(pin_LED,LED_St);  

  //Set Baudrate
  Serial2.begin(LIN_Baudate); 

  t1 = millis();
  t2 = t1;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void loop() 
{
  t1 = millis();

  NumBytes = Serial2.available();
  if(NumBytes > 0)
  {
    Process_LIN2_Rx();
  }  
  
  //Heartbeat Function
  if((t1-t2) >= 500)
  {
    LED_St = !LED_St;
    digitalWrite(pin_LED,LED_St);
    t2 = t1;
  }  
}
///////////////////////////////////////////////////////////////////////////////////////////////
void Process_LIN2_Rx()
{
  int NumBytes_B = 0;
  unsigned long CurrentTime;
  int i = 0;
  byte Calc_CRC;

  CurrentTime = millis();

  NumBytes_B = Serial2.available();
  if(NumBytes_B == 1)
  {
    //Keep updating anchor point until more than 1 byte available
    LIN_AnchorPoint_ms = millis();
  }
  else 
  {
    //If minimum amount of time available, then we can process data
    if((CurrentTime - LIN_AnchorPoint_ms) >= LIN_MinDelayTime_ms)
    {
      for(i=0;i<NumBytes_B;i++)
      {
        LIN_Data_Raw[i] = Serial2.read();      
      }

      if((LIN_Data_Raw[0] == 0x00) && (LIN_Data_Raw[1] == 0x55) && (LIN_Data_Raw[2] == LIN_ProtectedID))
      {
        //If the frame looks correct, we transfer the raw data to the "actual" data
        Calc_CRC = CalcCRC(LIN_Data_Raw);
        if(Calc_CRC == LIN_Data_Raw[NumBytes_B-1])
        {
          for(i=0;i<(NumBytes_B-4);i++)
          {
            LIN_Data_Act[i] = LIN_Data_Raw[i+3];
          }
        }       
      }
    }
  } 
}
///////////////////////////////////////////////////////////////////////////////////////////////
byte CalcCRC(byte InputData[])
{
  //Too lazy to insert all the code here
}
 
@defragster - you are correct about updating t1 - the code I posted here is a snippet of my actual (large) project. In my main project, I have
Code:
void loop()
{
    t1 = millis();

Are you saying the Serial2 will take over functionality of the IRQ_LPUART4? I am not exactly following.... In the end, I am able to process the Rx LIN frames, but as standard serial messages that begin with 0x00 followed by 0x55. The thought was to actually trigger the reception of LIN message with an official 13-bit Break.


@sbfreddie - In my normal code for LIN Rx, in the loop() function, i always poll Serial2.available().....

Code:
unsigned long LIN_Baudrate = 19200;
unsigned long LIN2_Rx_Counter = 0;
unsigned long LIN_AnchorPoint_ms = 0;
const unsigned long LIN_MinDelayTime_ms = 14;
byte LIN_Data_Raw[12];
byte LIN_Data_Act[8];
const int LIN_FrameID = 0x2C;
const int LIN_ProtectedID = 0xEC;
const int pin_LED = 13;
boolean LED_St = 0;
int NumBytes = 0;

unsigned long t1,t2;
///////////////////////////////////////////////////////////////////////////////////////////////
void setup() 
{ 
  pinMode(pin_LED,OUTPUT);
  digitalWrite(pin_LED,LED_St);  

  //Set Baudrate
  Serial2.begin(LIN_Baudate); 

  t1 = millis();
  t2 = t1;
}
///////////////////////////////////////////////////////////////////////////////////////////////
void loop() 
{
  t1 = millis();

  NumBytes = Serial2.available();
  if(NumBytes > 0)
  {
    Process_LIN2_Rx();
  }  
  
  //Heartbeat Function
  if((t1-t2) >= 500)
  {
    LED_St = !LED_St;
    digitalWrite(pin_LED,LED_St);
    t2 = t1;
  }  
}
///////////////////////////////////////////////////////////////////////////////////////////////
void Process_LIN2_Rx()
{
  int NumBytes_B = 0;
  unsigned long CurrentTime;
  int i = 0;
  byte Calc_CRC;

  CurrentTime = millis();

  NumBytes_B = Serial2.available();
  if(NumBytes_B == 1)
  {
    //Keep updating anchor point until more than 1 byte available
    LIN_AnchorPoint_ms = millis();
  }
  else 
  {
    //If minimum amount of time available, then we can process data
    if((CurrentTime - LIN_AnchorPoint_ms) >= LIN_MinDelayTime_ms)
    {
      for(i=0;i<NumBytes_B;i++)
      {
        LIN_Data_Raw[i] = Serial2.read();      
      }

      if((LIN_Data_Raw[0] == 0x00) && (LIN_Data_Raw[1] == 0x55) && (LIN_Data_Raw[2] == LIN_ProtectedID))
      {
        //If the frame looks correct, we transfer the raw data to the "actual" data
        Calc_CRC = CalcCRC(LIN_Data_Raw);
        if(Calc_CRC == LIN_Data_Raw[NumBytes_B-1])
        {
          for(i=0;i<(NumBytes_B-4);i++)
          {
            LIN_Data_Act[i] = LIN_Data_Raw[i+3];
          }
        }       
      }
    }
  } 
}
///////////////////////////////////////////////////////////////////////////////////////////////
byte CalcCRC(byte InputData[])
{
  //Too lazy to insert all the code here
}

Dimitry:
You are using the GPS in a different mode than I am. I use mine in a command to take measurement only mode, where you tell it to take readings and then wait for it to respond with the GPS data.
My system is a real time system, so I don't want to waste processor cycles waiting for the GPS to respond. This is a perfect case for an interrupt driven GPS response.

Regards,
Ed
 
@Dimitri - Indeed, Serial2 { or Serial1 as below } configures UART hardware and connects software to interrupts to handle interrupt driven data transfer. And it seems PIN control is altered from what worked on T_3.x.

You wouldn't have that code handy would you?
I have been struggling with this exact same issue for a long time. Usually I just use a long delay to wait for the Ublox to finish the 100 or so bytes that it sends over serial 1.
A long time ago I was looking in the core for teensy 3.x and the data sheet for the processor, I found an internal interrupt that can be enabled to do exactly this function, but this interrupt was not implemented in the core code and at the time I did not have sufficient mental horsepower to implement it.

Regards,
Ed

I have the code sample from larger old uNav thread - updated and working on T_3.5, but the same code fails to execute the same on T_4.1 ???? :confused:

>> Can anyone give a clue why T_3.x code won't work the same on T_4.1? It seems Serial1 use on T_4.x prevents independent use of that pin as it worked on T_3.x. And doing the attach also breaks the UART function so no Serial1 data arrives.

It uses Serial1 - with Pin 0 wired to Pin 1 to loop the Serial RX to TX.

It was originally conceived and written on T_3.6 and worked there. But looking at it now, it won't run the same on a T_4.1 using the same _isr() process?

Code below - added debug pins to follow operation and the _isr() fires ONCE - but never again on T_4.1?
>> Note: only ran ONCE when the attachInterrupt() is before Serial1.begin() - if moved to end of setup() it never toggles PIN_DEBUG_AVAIL 18 as no data ever comes in on Serial1.
Code:
 // T_4.1 shows first '>' char recevied - but never again?
C:\T_Drive\tCode\TIME\ISR_serRx\ISR_serRx.ino Oct  3 2022 14:07:20
>DELAY TIME Cycle Counts =4248135880 :: DELAY TIME AVG =354011323

Versus T_3.5 running as expected:
Code:
C:\T_Drive\tCode\TIME\ISR_serRx\ISR_serRx.ino Oct  3 2022 14:05:18
>DELAY TIME Cycle Counts =3583339571 :: DELAY TIME AVG =298611630

>DELAY TIME Cycle Counts =2544
>DELAY TIME Cycle Counts =1839
>DELAY TIME Cycle Counts =3214
>DELAY TIME Cycle Counts =2514
>DELAY TIME Cycle Counts =1699
>DELAY TIME Cycle Counts =3154
>DELAY TIME Cycle Counts =2349
>DELAY TIME Cycle Counts =1639
>DELAY TIME Cycle Counts =3054
>DELAY TIME Cycle Counts =2324 :: DELAY TIME AVG =2433

Setup enabled FALLING interrupt on Rx pin, when _isr() is called it disables the interrupt. The interrupt is enabled again after message is received and no more .available():
Code:
volatile uint32_t ccTimeRcv;
#define SOME_BAUD 58600
#define PIN_SRX 0
#define PIN_DEBUG_SEND 28
#define PIN_DEBUG_AVAIL 18


// REQUIRED CODE
void serialrx_isr() {
  ccTimeRcv = ARM_DWT_CYCCNT; // Record time as desired
  // Detach _isr() to avoid chatter/repeats during character transmission
  detachInterrupt(PIN_SRX); // must re-attach interrupt on receive complete
  /*  disable the IRQ - would be for the PORT
   *   NVIC_DISABLE_IRQ(IRQ_FTM0);
  delay(1000);
  NVIC_ENABLE_IRQ(IRQ_FTM0);
   */
  digitalToggle( LED_BUILTIN ); // TOGGLE LED to show functionality
}

// Code to trigger _isr() when incoming Serial message starts
// code marked as follows is all that is needed ::   // REQUIRED CODE
// This sample uses Serial1 and requires a wire jumper pin 0 to pin 1
elapsedMillis WhenSend;
void setup( ) {

  if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) { // Enable CPU Cycle Counter - Already active on T_4.x
    ARM_DEMCR |= ARM_DEMCR_TRCENA;
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  }
  // REQUIRED CODE
  attachInterrupt( PIN_SRX, serialrx_isr, FALLING); // Attach Serial_RX pin for interrupt

  // Do app specific setup here including Serial port .begin
  Serial1.begin(SOME_BAUD);   // REQUIRED CODE
  pinMode( LED_BUILTIN, OUTPUT );
  pinMode( PIN_DEBUG_AVAIL, OUTPUT ); // debug monitor
  pinMode( PIN_DEBUG_SEND, OUTPUT ); // debug monitor
  while (!Serial && millis() < 5000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  while ( Serial1.available() ) Serial1.read(); // discard test char
  WhenSend = 0;
}

uint32_t ccTimeSend;
uint32_t ccTimeSum = 0;
uint32_t ccii = 11; // set to discard first avg after Serial1 first xfer
void loop() {
  if ( WhenSend > 1000 ) { // REMOVE this for external Serial1 connection
    WhenSend = 0;
    //  attachInterrupt( PIN_SRX, serialrx_isr, FALLING); // (RE)Attach Serial_RX pin for interrupt
    ccTimeSend = ARM_DWT_CYCCNT;
    // This sample sends to itself to show functionality :: WIRE PIN 0 Rx to PIN 1 Tx for Serial1
    digitalToggle( PIN_DEBUG_SEND ); // TOGGLE LED to show functionality
    Serial1.print( ">" );
  }

  if ( Serial1.available() ) {
    digitalToggle( PIN_DEBUG_AVAIL ); // TOGGLE LED to show functionality
    while ( Serial1.available() ) {
      Serial.print( (char)Serial1.read() ); // discard test char
    }
    DoStats(); // extraneous timing to monitor Tx to Rx time, varies with buad

    // REQUIRED CODE
    if ( 1 ) // Do this when data transmission is done to be ready for next Start
    { 
      attachInterrupt( PIN_SRX, serialrx_isr, FALLING); // (RE)Attach Serial_RX pin for interrupt
    }
  }
}

void DoStats() {
  Serial.print( "DELAY TIME Cycle Counts =" );
  Serial.print( (ccTimeRcv - ccTimeSend));
  ccii++;
  ccTimeSum += (ccTimeRcv - ccTimeSend);
  if ( ccii >= 10 ) {
    Serial.print( " :: DELAY TIME AVG =" );
    Serial.print( ccTimeSum / ccii );
    ccii = 0;
    ccTimeSum = 0;
    Serial.println();
  }
  Serial.println();
}
 
defragster:
It appears that the serial port has builtin to its logic a couple of interrupts to use to detect begin transmission and end transmission. From the manual:
Screen Shot 2022-10-03 at 7.20.36 PM.jpg

I think that its possible to use one of these defined interrupts in the UART logic. It's just a little above my pay grade to do it.

Regards,
Ed
 
defragster:
Attached is the interrupt enable bit in the LPUART Baud Rate Register (BAUD). It seems that when you set this bit you get an interrupt when the first start bit comes in:

Screen Shot 2022-10-03 at 8.01.04 PM.jpg

Regards,
Ed
 
@Dimitri : For LIN use the CORE Serial code may not be appropriate given the special needs/features of LIN. Glanced at the linked github lin_bus.cpp code and for that it seems to set baud rate and other registers directly - so use of Serial2.begin() would overwrite that? Glancing at the examples and source it has it's own interface functions for setup and transfer as noted in the ReadMe.

Thanks sbfreddie, Never looked at the Manual - esp not that LIN specific table.

Had the Idea doing the T_3.6 uNav GPS data receive code and it works as above for normal UART Serial transfers to detect falling pin at start of transfer.

In both T_3.x and T_4.x the UART data are processed with interrupt attention by UART hardware. Seems the 1062 in the T_4.x changes how UART interrupts connect and process. Stealing the Rx pin interrupt didn't affect UART operation on T_3.x. But the 1062 MCU at 600 MHz is quite different - even powering up the pins in 'slow' IO mode and being switched to 'FAST' mode for better pin response which affects the interrupt handling from notice on each of 4 ports to only one interrupt for all 4 ports to notify and then parse the indicated port to find the pin.
 
Back
Top