Serial port and interrupts issue with Teensy LC

Status
Not open for further replies.

binux

Member
Hello everybody,

I have a little project running which uses the serial connection of the Teensy LC (Serial1) to read out data from an ELM327 device connected to my car.
I use a slightly modified version of irvine's great elm327 library for arduino. https://github.com/irvined1982/arduino-ELM327

My project runs very fine I'm displaying data that i collect from my car in realtime on a led display. The only downside of the ELM library is that it is blocking the whole process until it received an answer from the ELM device. This can take up to 60ms so far. So i decided to update the LEDs with an interrupt which runs with an IntervalTimer at 33ms. I have a LDR which is dimming my lights thats why it was senseful to do so.
But once I update the LEDs with the interrupt it seems like my serial has big issues. After is sent a command to the ELM device, the code waits for an response. This is a simple while loop and it has a timeout of 9 sec so if there is no answer it will automatically return. However when I enable the 33ms interrupt led update routine it works for around 1 sec and then get stuck inside the while loop. Now the timeout still works, but I will never again receive any more answer from my ELM device.

Reading the arduino reference it says
incoming communication may be ignored
when enabling interrupts (https://www.arduino.cc/en/Reference/Interrupts). And it seems like this is my problem. What I ask myself now is how I can solve this without changing the library too much. (I know i could just remove the while loop and somehow make it non-blocking without using interrupts but this seems to be far more complicated to me since I would need some kind of command stack).
Can anyone give me some hints or advices which allow me to update the leds properly at 33ms while receiving data over the serial1 port in the main loop?

The corresponding send/receive code is shown below. The serial print of millis() works fine. The Serial.available() however returns 0 after a very short time.
Just for clarification the code below is executed "normally" in the loop(). My leds get updated in a seperate fucntion that is called by the IntervalTimer.

Code:
        byte cmdLength;
	
	// Flush any leftover data from the last command.
	
	// Send the specified command to the controller.
	flush();
	ELM_PORT.print(cmd);
	ELM_PORT.print('\r');
    
	unsigned long timeOut;
        int counter;
	bool found;
	
	// Start reading the data right away and don't stop 
	// until either the requested number of bytes has 
	// been read or the timeout is reached, or the >
	// has been returned.
	//
	counter=0;
	timeOut=millis()+ELM_TIMEOUT;
	found=false;
	while (!found && counter<( dataLength ) && millis()<timeOut)
        {
		Serial.print("Millis: ");Serial.println(millis());
        if ( ELM_PORT.available() ){
			Serial.print("Bytes available: "); Serial.println(ELM_PORT.available());
			data[counter]=ELM_PORT.read();
			if (  data[counter] == '>' ){
				found=true;
				data[counter]='\0';
			}else{
				++counter;
			}
        }
    }
 
or changing the serial interrupt priority may improve things
The Serial port interrupts have a default priority of 64 which is higher than the IntervalTimer default priority of 128. So the serial port interrupts can interrupt the IntervalTimer ISR, if interrupts are not disabled.

@binux:
You should post your ISR. Are you changing interrupt priorities or disabling interrupts? You haven't mentioned how you control your LEDs. A lot of library calls are not safe to use from interrupts.
 
Teensy LC has a small serial buffer, depending on where things are going astray either making the buffer larger
Interesting. I didn't know that - the max size of the response the ELM device can send is 64 bytes so this could be very possible.

or changing the serial interrupt priority may improve things

As tni said, I would expect that the serial receive has a higher priority. I'm not changing priorities of the interval timer. So this seems rather unlikely.

You should post your ISR. Are you changing interrupt priorities or disabling interrupts? You haven't mentioned how you control your LEDs. A lot of library calls are not safe to use from interrupts.

Okay so for the ELM request in the loop section i have the following function "getValues" which polls the ELM for new data:

Code:
volatile int cRPM = 0;
volatile int cThr = 0;
volatile bool firstReceivedData = false;
int errors = 0;

byte st;
Elm327 Elm;

void getValues(){
  //read the current RPM from OBD-II
  int rpm;
  st = Elm.engineRPM(rpm);
  if (st == ELM_SUCCESS)
  {
    noInterrupts();
    cRPM = rpm;
    errors = 0;
    firstReceivedData = true; 
    interrupts();
  }else{
    errors++;
    if(errors > MAX_OBD_ERRORS){
      cRPM = -1;
      Serial.print("Error:");
      Serial.println(st);
    }
  }
  //read the current engine load from OBD-II
  byte thr;
  st = Elm.engineLoad(thr);
  if (st == ELM_SUCCESS)
  {
    noInterrupts();
    cThr = thr;
    errors = 0;
    firstReceivedData = true;
    interrupts();
  }else{
    errors++;
    if(errors > MAX_OBD_ERRORS){
      cThr = -1;
      Serial.print("Error:");
      Serial.println(st);
    }
  }
}

So basically I poll the engineRPM first and save the value (engineRPM as you see in the github repo does simply call the earlier shown code with the proper pid set), then I poll for the engineLoad. engineRPM is a 2byte and engineLoad a 1byte result. Though the values received at the serial port are bigger since the elm port always sends the polled pid and a ">" as response. The only time I disable interrupts is while storing the values in their corresponding variables which are accessible for the interrupt.

The interrupt is initiated after the elm port successfully sent a first valid response. Then it runs the "doDisplay" method every 33ms:

Code:
float getCurrentRPM(){
  if(!SIM){
    return (float)cRPM;
  }
  return sRPM;
}

float getCurrentEngineLoad(){
  if(!SIM){
    return (float)cThr;
  }
  return sThr;
}

void doDisplay(){
  if(!firstReceivedData){
    return;
  }
  if(cRPM == -1 || cThr == -1){
    for(int i = RPM_LED_START; i <= RPM_LED_END; i++){
      leds[i] = CRGB(getBrightness(255),getBrightness(255),0);
    }
  }else{
    float rpm = getCurrentRPM();
    int thrLED = round(((getCurrentEngineLoad())/100.0f) * (float)(RPM_LED_END - RPM_LED_START)) + RPM_LED_START;
    float colorStep = 255.0f / float(RPM_LED_END - RPM_LED_START);
    for(int i = RPM_LED_START; i <= RPM_LED_END; i++){
      if(i <= thrLED){
        leds[i] = CRGB(getBrightness(colorStep * float(i - RPM_LED_START)), getBrightness(255) - getBrightness(colorStep*float(i - RPM_LED_START)),0);
      }else{
        leds[i] = CRGB::Black;
      }
      if(rpm >= MAX_BHP_RPM_START && rpm <= MAX_BHP_RPM_END){
        leds[i] = CRGB(0,0,getBrightness(255*4));
      }
    }
    for(int i = 0; i< RPM_LED_START; i++){
      leds[i] = CRGB(getBrightness(255*3),0,0);
    }
  }
  FastLED.show();
}

The leds are updated with FastLED (I use a WS2812 led strip) and the method getBrightness returns a mapped version (with the ldr measured value) of the given brightness. RPM_LED_START/END are giving the LED range to display the values.
 
FastLED is potentially quite problematic. Teensy LC doesn't have a FIFO for the UART, so it needs to run an interrupt for each byte. So you can easily loose bytes. It may be worthwhile to try to lower the UART baud rate (if it's configurable).

Have you looked at the OctoWS2811 library?
 
@tni, does octo have LC support?

I've used the Adafruit neopixel library on LC over fastled, but it's bit banging everything so very much blocking
 
Maybe try using FreqMeasure for the engine RPM? It uses timer capture with interrupts, so it won't use much CPU time, and it's able to tolerate some interrupt latency, up to about 1 pulse time.

If the WS2812 update, 30 us per LED is causing trouble, OctoWS2812 is probably the only real solution. Someday I'm going to implement a PWM based version with DMA, and I recently talked with Phil B about contributing it into Adafruit's NeoPixel library. When/if that work happens, it should support Teensy LC. But so far, OctoWS2811 only works on Teensy 3.x. If you look in the code, you'll see I put some work into attempting LC support. It almost works, but LC just isn't fast enough without the more sophisticated bus structure of the 3.x chips.
 
FastLED is potentially quite problematic. Teensy LC doesn't have a FIFO for the UART, so it needs to run an interrupt for each byte. So you can easily loose bytes. It may be worthwhile to try to lower the UART baud rate (if it's configurable).?

Okay that sounds logical. I have a Teensy3.2 laying around here, but i will have to do some resoldering before i can test it. Otherwise option 2

In an ideal world, you would move to a 4-wire led chipset, like the APA102 or LPD8806.

would be to switch to a four pin led strip (am I right)?

If the WS2812 update, 30 us per LED is causing trouble, OctoWS2812 is probably the only real solution. Someday I'm going to implement a PWM based version with DMA, and I recently talked with Phil B about contributing it into Adafruit's NeoPixel library. When/if that work happens, it should support Teensy LC. But so far, OctoWS2811 only works on Teensy 3.x. If you look in the code, you'll see I put some work into attempting LC support. It almost works, but LC just isn't fast enough without the more sophisticated bus structure of the 3.x chips.

So looking to both solutions, which one would be the prefered one? From cost side surely it's the first one, but as I read in the wiki FastLED says it would be a smarter choice to use 4-wire led strips.
 
Well, you can try both and see which works better. All these LEDs involve just transmitting. Teensy doesn't know if there are actually any LEDs connected, it just transmits the data.

The FastLED folks almost always suggest APA102, because it solves these interrupt blocking issues on all boards. They support a lot of different hardware. I believe Teensy may be the only one where nonblocking WS2812 is really a viable option. Their standard answer to these issues is to buy the more expensive APA102.
 
Well, you can try both and see which works better. All these LEDs involve just transmitting. Teensy doesn't know if there are actually any LEDs connected, it just transmits the data.

The FastLED folks almost always suggest APA102, because it solves these interrupt blocking issues on all boards. They support a lot of different hardware. I believe Teensy may be the only one where nonblocking WS2812 is really a viable option. Their standard answer to these issues is to buy the more expensive APA102.

OKay thank you - I will then go with the teensy 3.2 and the Octo library and see if that will solve my issues.
 
Well, you can try both and see which works better. All these LEDs involve just transmitting. Teensy doesn't know if there are actually any LEDs connected, it just transmits the data.

The FastLED folks almost always suggest APA102, because it solves these interrupt blocking issues on all boards. They support a lot of different hardware. I believe Teensy may be the only one where nonblocking WS2812 is really a viable option. Their standard answer to these issues is to buy the more expensive APA102.

I have one more question concerning your Octo library. As far as I see you hardcoded the pins for each led strip. I already have a finished PCB where data of my strip goes into pin 3. Do you think it's safe to change the pin in your lib?
 
Status
Not open for further replies.
Back
Top