Using Serial3 when using audio library

bramke

New member
Hi,
I have a costum pcb and have Serial3 buffered to 5v connected to a MAX-485 dmx output. I was having some problems with my code only working after recompiling and using the reset button on the teensy 4.1 and when I re-apply power to the teensy the code will freeze at the start of my code.

Code:
//faulty code
#include <TeensyDMX.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

//dmx
namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx{Serial3};
#define DMX_AANTAL    10

//audio shield
AudioPlaySdWav           playWav1;
AudioOutputI2S           audioOutput;

AudioConnection          patchCord1(playWav1, 0, audioOutput, 0);
AudioConnection          patchCord2(playWav1, 1, audioOutput, 1);
AudioControlSGTL5000     sgtl5000_1;

void setup() {
  Serial.begin(115200);
  Serial.println("Serialbegin");

  sgtl5000_1.enable(); // Initalize SGT audio
  Serial.println("enable audioshield");

  dmxTx.begin();
  Serial.println("DmxTX BEGIN");

  //test dmx
  for(int i=1; i<DMX_AANTAL; i++){
    Serial.print(i);
    dmxTx.set(i, 255);
    delay(100);
    dmxTx.set(i,0);
  }
  Serial.println();
  Serial.println("Done testing DMX");
}

void loop() {

}

After allot of troubleshooting I think it is not possible to use Serial3 when the audio shield is connected, even when I only need TX from Serial3(which isn't used by the audio shield).
Is it possible to make the audio shield reserve less pins in software?
I think if the audio library doesn't use the pin15(which I don't use) than that frees up the Serial3 pins that I need.

I don't get any error messages.
Hardware:
Teensy4.1
Teensy audio shield rev D
MAX-485 DMX driver

Thanks for the help,
Kind regards,
Bram
 
I'm running it here on a Teensy 4.1 with audio shield, SD card in the audio shield, and oscilloscope connected to pin 14 (TX3).

This is what I see after uploading and opening the serial monitor.

screenshot.jpg

Here is the waveform my oscilloscope sees on pin 14.

file.png

I'm guessing you're seeing something very different?
 
About pin 15...

After allot of troubleshooting I think it is not possible to use Serial3 when the audio shield is connected, even when I only need TX from Serial3(which isn't used by the audio shield).
Is it possible to make the audio shield reserve less pins in software?
I think if the audio library doesn't use the pin15(which I don't use) than that frees up the Serial3 pins that I need.

The audio shield connects pin 15 to a capacitor, and to the location where you could solder a thumbwheel pot. If you haven't added the pot, then pin 15 is only connected to a capacitor.

You CAN use Serial3 to transmit.

But if you wanted to receive on Serial3, the capacitor may interfere with your signal. You would need to desolder the capacitor, or just not connect pin 15 on Teensy to that pin on the audio shield. From the software side, Serial3 would still "work" but if the capacitor interferes too much, you would receive corrupted data.

The audio library and SD library do not make any use of this pin. There should be no software conflict.

For people who wish to use the pot, their programs would use analogRead(A2) to read the thumbwheel position and then use that info to control an audio parameter, like volume. This is NOT does automatically by the audio library. It is use of analogRead(A2) in the Arduino sketch which reads the pot. Some of the audio library examples, and the audio library tutorial, demonstrate this.
 
Hi Paul,
Thank you for your fast reply.

I remove my teensy 4.1 from my pcb and test it barbone with only a teensy audio shield attached. The teensy won't go into boot mode automatically and I have to press the reset button to upload the code. It reboots with my code and I get the same Serial monitor output as your screenshot above. Only when I remove the usb cable and connect it again to my laptop(without uploading the code again), it will hang the code (as seen in screenshot).
ScreenshotTeensy.jpg
I first thought it was a problem with my teensy that I might have destroyed somehow during testing, but I have the same result on 3 different teensy 4.1.
If I upload the same code but with Serial8 instead of Serial3, the teensy does not get stuck after replugging the cable and the teensy will go in bootmode automatically after uploading new code again. That is why I tough it was a problem with the audio shield, testing the same code again but then without the audio shield and there does not seem to be a problem.

Than maybe the teensyDMX library gets confused by the capacitor on the RX line somehow?
Other than that I have no idea..
 
Last edited:
update:
I have desoldered the tiny capacitor connected to pin 15 on the audio shield and the code works correctly now.
Thanks!
 
Quick Side notes:

Paul - did you try running the sketch after powering it off and powering it back on? From his post sounded like it worked on right after programming it.

If there is an issue with this. Wondering in this case what would happen if you setup the Serial port to be half duplex? Yes when the TX is empty, would shift
the TX pin into RX mode...

There have been times when I have thought about trying to add in options to easily allow TX or RX only Serial. Should not be very difficult.
If we did so, I could see a couple of ways to indicate it to Serial system.

Could add something like: Serial3.begin(115200, SERIAL_8N1 | SERIAL_TX_ONLY);
and/or could do something like Serial3.setRX((uint8_t)-1); //(before calling begin)

Code changes would be to check for this state where the pins are configured (T4.x)
Mainly in the begin code:

Code:
	if (!half_duplex_mode_)  {
		*(portControlRegister(hardware->rx_pins[rx_pin_index_].pin)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
		*(portConfigRegister(hardware->rx_pins[rx_pin_index_].pin)) = hardware->rx_pins[rx_pin_index_].mux_val;
		if (hardware->rx_pins[rx_pin_index_].select_input_register) {
		 	*(hardware->rx_pins[rx_pin_index_].select_input_register) =  hardware->rx_pins[rx_pin_index_].select_val;		
		}	

		*(portControlRegister(hardware->tx_pins[tx_pin_index_].pin)) =  IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3);
		*(portConfigRegister(hardware->tx_pins[tx_pin_index_].pin)) = hardware->tx_pins[tx_pin_index_].mux_val;
	} else {
		// Half duplex maybe different pin pad config like PU...		
		*(portControlRegister(hardware->tx_pins[tx_pin_index_].pin)) =  IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3) 
				| IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3);
		*(portConfigRegister(hardware->tx_pins[tx_pin_index_].pin)) = hardware->tx_pins[tx_pin_index_].mux_val;
	}
	if (hardware->tx_pins[tx_pin_index_].select_input_register) {
	 	*(hardware->tx_pins[tx_pin_index_].select_input_register) =  hardware->tx_pins[tx_pin_index_].select_val;		
	}
May or may not need to change some of the settings like WATER and FIFO , but maybe not.

Then the other thing we may need to do, is to update some of the code that sets the CTRL register.
That is at the beginning we set it to: CTRL_TX_INACTIVE (plus maybe a few other bits for things like parity, and ...)
In particular, how these defines are set and used:
Code:
#define CTRL_ENABLE 		(LPUART_CTRL_TE | LPUART_CTRL_RE | LPUART_CTRL_RIE | LPUART_CTRL_ILIE)
#define CTRL_TX_ACTIVE		(CTRL_ENABLE | LPUART_CTRL_TIE)
#define CTRL_TX_COMPLETING	(CTRL_ENABLE | LPUART_CTRL_TCIE)
#define CTRL_TX_INACTIVE	CTRL_ENABLE

Optional part would be to also update several of the methods. For example Serial3.write(0) on an RX only should fail.
Dito for Serial3.read()...
 
@bramke and @Paul

The sketch went completly nuts on my machine. Caused things like TyCommander to not want to run...

I could be completely wrong, but don't you need to add Audio memory? to this?
At least it appeared on my machine to be much happier when I added it.
Code:
//faulty code
#include <TeensyDMX.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

//dmx
namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx{Serial3};
#define DMX_AANTAL    10

//audio shield
AudioPlaySdWav           playWav1;
AudioOutputI2S           audioOutput;

AudioConnection          patchCord1(playWav1, 0, audioOutput, 0);
AudioConnection          patchCord2(playWav1, 1, audioOutput, 1);
AudioControlSGTL5000     sgtl5000_1;

void setup() {
  Serial.begin(115200);
  Serial.println("Serialbegin");

[COLOR="#FF0000"]  AudioMemory(10);[/COLOR]
  sgtl5000_1.enable(); // Initalize SGT audio
  Serial.println("enable audioshield");

  dmxTx.begin();
  Serial.println("DmxTX BEGIN");

  //test dmx
  for(int i=1; i<DMX_AANTAL; i++){
    Serial.print(i);
    dmxTx.set(i, 255);
    delay(100);
    dmxTx.set(i,0);
  }
  Serial.println();
  Serial.println("Done testing DMX");
}

void loop() {

}
Not sure how much, but...
 
Continuing to look into this issue. Trimmed it down to smaller program which reproduces the problem (after power cycle).

Code:
#include <TeensyDMX.h>
#include <Audio.h>

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx{Serial3};

AudioOutputI2S           audioOutput;

void setup() {
  Serial.begin(115200);
  Serial.println("setup begin");

  dmxTx.begin();

  for (int i=0; i<25; i++) {
    Serial.println(i);
    delayMicroseconds(250);
  }
  Serial.println("setup end");
}

void loop() {
}
 
Still not sure why it's crashing, but pretty sure it's a bug somewhere in TeensyDMX.

This simple program shows Serial3 does indeed work fine with Audio running.

Code:
#include <Audio.h>

AudioOutputI2S           audioOutput;

void setup() {
  Serial.begin(115200);
  Serial.println("setup begin");

  Serial3.begin(115200);

  for (int i=0; i<25; i++) {
    Serial.println(i);
    delayMicroseconds(250);
  }
  Serial.println("setup end");
}

static unsigned int loopcount=0;

void loop() {
  Serial.print("loop ");
  Serial.println(loopcount);
  Serial3.println(loopcount);
  loopcount++;
  delay(1000);
}

Here's what my scope sees on pin 14 (TX3):

file.png

I can power cycle the board and this always starts running. There is no hardware conflict between Serial3 and Audio.
 
I've been doing some experiments with trying to figure out how to trigger the bug. (There's going to be some repetition here, but I figured I'd document what I'm doing.) Some details I'm learning:
1. The bug occurs even without the audio code; it's just with TeensyDMX. I commented out the two Audio lines in Paul's first program.
2. The bug does not occur if I don't connect the Audio Shield (Rev D), but re-occurs if I simply connect the shield again.
3. The bug does not occur after a program upload, but it does occur after a power cycle.

Looking at the schematic of the Audio Shield (https://www.pjrc.com/store/teensy3_audio.html), it looks like only a capacitor bridges pin 15 to GND (labelled as AGND, but I'm pretty sure it goes to GND on Teensy 4.1). There's no potentiometer connected to pin 15.

So from first glance, if I put a 0.1uF capacitor from pin 15 to GND, I should see the crash happen without the Audio Shield connected. Since @bramke removed the capacitor and it worked fine, and since Paul's second program with Serial3 appears to work fine, something weird is going on inside TeensyDMX. I'm going to try to figure out where it freezes...
 
Update: I used a scope to verify that the DMX part of the program is actually still running. DMX is continuously being transmitted (via serial interrupts), but control somehow doesn't return to setup() nor loop(). Also, after the apparent freeze after a power cycle, the loader can't load a program without the program button being pushed. Why is there no freeze when the program has just been loaded? What's different about the power cycle startup with that capacitor? How is that affecting serial and USB interrupts? What's different about the TeensyDMX serial interrupts? Onwards...

Oh, little capacitor
on the RX pin so fine.
You are my Matador
in red, holding the line.
 
Last edited:
Wow, you're right, doesn't depend on audio at all.

Was able to reproduce it with a 0.1uF capacitor from pin 0 to GND and this program using Serial1 rather than Serial3.

Code:
#include <TeensyDMX.h>

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx(Serial1);

void setup() {
  Serial.begin(115200);
  Serial.println("setup begin");
  dmxTx.begin();
  for (int i=0; i<25; i++) {
    Serial.println(i);
    delayMicroseconds(250);
  }
  Serial.println("setup end");
}

void loop() {
}

Very mysterious....
 
Startup behavior test

Code:
#include <TeensyDMX.h>

namespace teensydmx = ::qindesign::teensydmx;
teensydmx::Sender dmxTx(Serial1);

void setup() {
  Serial2.begin(115200);
  Serial.begin(115200);
  Serial.println("setup begin");
  dmxTx.begin();
  for (int i=0; i<12; i++) {
    Serial2.write(i);
    Serial.println(i);
    delayMicroseconds(250);
  }
  Serial.println("setup end");
}

void loop() {
}

Green = TX2 (pin 8)
Yellow = RX1 (pin 0)

file.png
 
I added digitalWriteFast() on lpuart6_tx_isr and lpuart6_rx_isr inside the library, like this

Code:
void lpuart6_rx_isr() {
digitalWriteFast(33, HIGH); // Red trace on scope
  Receiver *r = rxInstances[0];
  if (r != nullptr) {
    r->receiveHandler_->irqHandler();
  }
digitalWriteFast(33, LOW);
}

Code:
void lpuart6_tx_isr() {
digitalWriteFast(32, HIGH); // Blue trace on scope
  Sender *s = txInstances[0];
  if (s != nullptr) {
    s->sendHandler_->irqHandler();
  }
digitalWriteFast(32, LOW);
}

Really interesting part is even though the capacitor is on the receive pin, lpuart6_rx_isr() doesn't seem to run at all. But when things go wrong, sure looks like lpuart6_tx_isr() is eating up all the CPU time, probably because one of the interrupt flags isn't getting cleared by the interrupt handler.

file.png
 
Agreed about some interrupt possibly not getting cleared. That’s my next task: to look for anything amiss in that department.
 
Good luck!

Sorry, I sort of stopped looking after I saw that it is not using the standard Serial Interrupt handler.

You probably still need to look at some of the RX states and clear them.

And/or wondering if you might get away with some simple hacks, like see it is Serial3 and maybe before the begin, do something like:
pinMode(15, OUTPUT); digitalWriteFast(15, HIGH); delay(10);
And see if the pin starts at a high state that maybe it will avoid the I am guessing it goes from a low to high and triggers a state...

Again good luck
 
Writing a serial driver from scratch like this is no easy task. So many subtle corner cases exist. We've had years of discovering and fixing extremely rare issues, like data or OR & FE status bits lingering in the receive FIFO between use of end() and begin() to change baud rates.

I spent at least an hour digging through the code, but didn't see anything obvious. Must admit, this sort of C++ approach isn't the way I usually like to approach the lowest level code. I find plain C to be a lot easier to think about what sort of code the compiler is actually generating.

The OR, NF, FE flags in the STAT register deserve special attention. My hunch is one of those may be at play with this issue.
 
Thanks for that. I appreciate the guidance.

This might be somewhat slow-going because it’s one of those “I’ll dive into it more deeply when I have more bandwidth” things.

Edit: Also, thank you for those plots above.
 
Last edited:
I fixed it via the discussion in this report: https://github.com/ssilverman/TeensyDMX/issues/20

In summary, some of the state management was only doing bit changes and not completely setting the CTRL register. This was significant because, when using serial parameters to create the BREAK and MAB instead of a timer (this is the default option), the program uses some saved parameters without having first disabled the RX option bits (including interrupt-enables). The solution was to disable the RX options before saving the parameters.

@bramke, please retry using the latest code on GitHub. The fix is in commit `69926c2`.
 
Back
Top