Where are the hardware RTS_B pins for UART (Teensy 4.1)?

xerxes

New member
I'm working on a project where I need to poll four encoders (CUI AMT212B) over half-duplex RS-485 UART at 10kHz, while also performing other CPU intensive tasks.

To poll each encoder I need to send a one byte command, and then receive a 2-byte response. My plan is to configure the DMA channels of the Teensy so that one channel triggers off the PIT timer to write the command to the send FIFO buffer, and then configure another DMA channel to read the two-byte response from the receive FIFO buffer back into memory; all without CPU intervention. I have this working well between two ports of the Teensy itself, with one port simulating the encoder.

Where I am having trouble is configuring the LPUART peripheral to trigger the transmit pin of my RS-485 transceiver. The reference manual suggests that this is possible by configuring the RTS_B pin of the corresponding pin, but I haven't been able to figure out which physical pins on the Teensy are the RTS_B pins for each serial port. Is what I am describing possible, or am I missing something?

Here's the code I have so far. It parses a 16-bit integer from the Serial monitor, then writes that integer to the transmit FIFO of Serial2, which is connected to Serial1. Serial1 is configured such that when its recieve FIFO has 2 bytes, it triggers a DMA transfer to memory. Since I'm not really using the HarwareSerial API, I don't expect the transmitterEnable call to work, but I do expect setting the 1st bit of LPUART4_MODIR to cause some pin somewhere to assert while LPUART4 (Serial2) is transmitting. I just can't seem to find that pin.

Code:
#include "imxrt.h"
#include "DMAChannel.h"
#define LPUART6 Serial1 
#define LPUART4 Serial2

volatile uint16_t recieve = 0;
uint16_t last = 0;
uint16_t new_send = 0;

DMAChannel dma;

void setup() {

  // Configure DMA channel that triggers the encoder reading

  Serial.begin(9600);
  LPUART6.begin(9600);
  LPUART4.begin(9600);
  LPUART4.transmitterEnable(9);
  

  Serial.print("Config dma0....");

  DMA_TCD0_SADDR = &LPUART6_DATA; // Source address
  DMA_TCD0_SOFF = 0; // Adjustment to the source address made after every minor loop iteration
  DMA_TCD0_ATTR = (0b000000 << 11) | (0b000 << 8) | (0b000000 << 3) | (0b000); // SMOD | SSIZE | DMOD | DSIZE
  DMA_TCD0_NBYTES = 2;  // Set number of bytes to transfer per minor loop iteration
  
  DMA_TCD0_SLAST = 0; // Source address adjustment after major loop completion
  DMA_TCD0_DADDR = &recieve;  // Destination address
  DMA_TCD0_DOFF = 1;

  DMA_TCD0_DLASTSGA = -2; 
  DMA_TCD0_CSR = (0b00 << 14) | (0b00000 << 8) | (0b0 << 5) | (0b0 << 4) | (0b0 << 3) | (0b0 << 2); // BWC | MAJORLINKCH | ESG | DREQ | INTHALF | INTMAJOR
  DMA_TCD0_BITER = (0b0 << 15) | 1; // Channel linking disabled | single service request (NO major looping)
  DMA_TCD0_CITER = DMA_TCD0_BITER; // CITER and BITER must be the same when the TCD memory is loaded by software. CITER will decrement with each minor loop
  
  DMAMUX_CHCFG0 = (0b1 << 31) | (0b0 << 30) | (0b0 << 29) | DMAMUX_SOURCE_LPUART6_RX; // Enable DMA mux | enable periodic trigger | disable always on | set hardware trigger source
  
  LPUART6_BAUD |= 1 << 21;         // enables DMA request generation when RX FIFO buffer contains n > watermark bytes
  LPUART6_WATER = 0b1 << 16;   // set RX watermark to 1  (fires DMA request when there are >1 bytes in the RX FIFO )

  LPUART4_MODIR |= 0b1 << 1;

  DMA_SERQ = 0;

  Serial.println("Done.");

  Serial.setTimeout(50);
  

}

void loop() {

  if (Serial.available()) {
    
    new_send = (uint16_t) Serial.parseInt();

    if (new_send != 0) {
      Serial.println();
      Serial.print("Serial2 sending: ");
      Serial.println(new_send);
      LPUART4_DATA = (0x00FF & new_send);
      LPUART4_DATA = ((0xFF00 & new_send) >> 8);
    }
  }

  if (recieve != last) {
    last = recieve;
    Serial.println();
    Serial.print("Serial1 recieved: ");
    Serial.println(recieve);
  }

}
 
I've not used RTS/CTS, particularly with Teensy 4.0/4.1, but the Teensy library section on Serial UART devices has a description on what CTS/RTS pins can be used:

Basically any pin can be used for RTS, and the XBAR pins (Primary XBAR Pins 1, 2, 3, 4, 5, 7, 8, 30, 31, 32, 33 - Alt Pins: 0, 36, 37, 42, 43, 44, 45, 46, 47) can be used for CTS. Note, the documentation says that CTS is inverted (i.e. you need to drive the CTS pin high instead of low to allow the Teensy to transmit the data).
 
Thanks for the prompt reply!

From what I understand, the functions on the page you linked (attachRts, attachCts, transmitterEnable) all require processor intervention, which I am trying to avoid by configuring the peripheral control registers directly. The reference manual page for the LPUART_MODIR register suggests that there is an RTS pin controlled by the peripheral which could be used as a transmitter enable without requiring CPU intervention. Looking a bit more through the IOMUXC section of the reference manual, the LPUART4_RTS_B pin is routed to the GPIO_EMC_18 output pad, which I can't find in the schematic. Is that pad made available on the Teensy 4.1, and if not is there a way I can route that signal (maybe using XBAR) to an accessible pin?
 
If you're going to directly access the LPUART registers, you probably shouldn't use functions like Serial2.begin(). You really should just copy the pieces of HardwareSerial.cpp you need (if any) so you're fully in control of how the hardware works. Mixing the interrupt-based code from the core library with your own direct access to registers which works in a very different way is just asking for all sorts of trouble. Even if you get it working well, future versions of the core library may change.

Only some of the RTS_B signals are accessible. Indeed GPIO_EMC_18 pin (BGA location B2) is not routed to any accessible pin.

Only 5 of the 8 serial ports have a RTS_B pin accessible, and only 2 of those 5 are on easily accessible pins. The other 3 can be accessed by soldering a wire to the SD card or QSPI pins.

Code:
LPUART1  Serial6  GPIO_AD_B0_15  not routed
LPUART2  Serial3  GPIO_AD_B1_01  pin 18
LPUART3  Serial2  GPIO_AD_B1_05  pin 41
LPUART3a Serial2  GPIO_EMC_16    not routed
LPUART4  Serial4  GPIO_EMC_18    not routed
LPUART5  Serial8  GPIO_EMC_27    pin 49  (on QSPI)
LPUART6  Serial1  GPIO_EMC_29    pin 54  (on QSPI)
LPUART7  Serial7  GPIO_SD_B1_07  not routed
LPUART8  Serial5  GPIO_SD_B0_03  pin 42  (on SD Card)

XBAR isn't an option. It only can connect to either RX or CTS on each LPUART.

For DMA, you really should use DMAChannel instances to access the TCD registers. For an example, see input_i2s.cpp in the audio library. You still have direct control over the DMA hardware, but using the TCD pointer from a DMAChannel instance will access a DMA channel which isn't used by any other DMA-based code.

If you really want to directly access registers like DMA_TCD0_SADDR directly, accepting that your code will conflict with DMA-based libraries and maybe even future core library when/if we ever use DMA, there is a known bug in imxrt.h published in Teensyduino 1.56 and all prior versions. Grab the latest from github. The bug went unnoticed for years, because all libraries using DMA do so through DMAChannel instances.

In your code on msg #1, when writing 2 bytes directly to the LPUART data register, you might wish to disable interrupts. If a USB or other interrupt occurs, it could delay the 2nd write. At only 9600, difficult to imagine how any of the normal interrupts could matter, but I'm assuming 9600 is only for testing and you probably will use a very high baud rate if you're going to so much trouble to have a fully DMA-based approach with zero CPU overhead to use the serial ports.

When you get this rather unique way of using the serial ports working, I hope you'll consider sharing.
 
Thank you for all the information!

I only need four Serial ports with RTS_B, so that should work fine.

I had originally used the DMAChannel library, but it seemed like the API doesn't offer any guarantees about which DMA channel is being used for which application. Since I am using the PIT timer to trigger my encoder queries, I need to be sure that DMA0-DMA3 are the ones assigned to the outbound writes to the encoder serial ports, and I didn't see an easy way to do that while using DMAChannel, especially if other libraries are reserving DMA channels. Also, in the final implementation of this system, I think I will end up using 12 DMA channels, and since (to my knowledge) DMAChannel only supports 16, compatibility with other DMA libraries could be somewhat moot. Would you still use DMAChannel with this in mind?

And lastly, are the Serialx to LPUARTy correspondences you listed for the Teensy 4.1 or the Teensy MicroMod? In my usage LPUART4 seems to control the pins for Serial2, although I admit that I don't fully understand how those pin assignments work and could be changing them unintentionally.
 
Back
Top