Teensy 4.1 software Serial for one wire bi-directional communication

Status
Not open for further replies.

JaredReabow

Well-known member
I have a situation when I only have one wire and ground to implement two way comms between two teensy 4.1 microcontrollers.
I wanted to do this by dynamically switching software serial ports from rx to tx on times intervals however after testing and experimentation I am unable to get software serial to function.
as such I wanted to ask for alternative one wire bi-directional communication options and also does the T4.1 support soft serial?

currently i have pin 35 of unit a connected to pin 34 of unit b.
I can send via hardware Serial 8 just fine but if i use soft serial it does not function for send or rx.
 
Would a RS232 Half duplex driver work? https://www.analog.com/media/en/technical-documentation/data-sheets/2801234fe.pdf? On page 12, fig15 you find an application which should do what you want. You would connect the rx/tx to one of the serialX ports (e.g. pin 0/1). Use SerialX.transmitterEnable(pin) to define a pin which will be set automatically if you write to the port. Then, you connect this pin to the mode input of the LTC2802. (check if it needs an inverter...). Of course you need to implement some sort of protocol to avoid both parties talking at the same time.

You need one of the drivers for each Teensy of course.
 
Would a RS232 Half duplex driver work? https://www.analog.com/media/en/technical-documentation/data-sheets/2801234fe.pdf? On page 12, fig15 you find an application which should do what you want. You would connect the rx/tx to one of the serialX ports (e.g. pin 0/1). Use SerialX.transmitterEnable(pin) to define a pin which will be set automatically if you write to the port. Then, you connect this pin to the mode input of the LTC2802. (check if it needs an inverter...). Of course you need to implement some sort of protocol to avoid both parties talking at the same time.

You need one of the drivers for each Teensy of course.

I appreciate the input however I would prefer to not use extra hardware
 
You can do half duplex with the Hardware Serial ports such as Serial8. However you must connect up to both boards on a Serial TX pin. You can likewise do it with T3.x boards as well, but the register names are different.

Here is an example sketch I did recently that echoed Serial data between two Serial ports on the same T4.x...
Code:
#define DEBUG_PIN 8
#define LOOP_DELAY_MS 25

// Serial3 information - Say this is our Main Serial port
#define SERIALM Serial3
#define SERIALM_TX_PIN 14
IMXRT_LPUART_t *s_pkuartM = &IMXRT_LPUART2;  // underlying hardware UART for Serial3

// Lets define this as our Echo Serial port...
#define SERIALE Serial4
#define SERIALE_TX_PIN 17
IMXRT_LPUART_t *s_pkuartE = &IMXRT_LPUART3;  // underlying hardware UART for Serial3

elapsedMillis  em_last_recv;
uint8_t receive_buffer[80];
uint8_t receive_index = 0;

void setup() {
  while (!Serial && millis() < 5000) ; // wait up to 5 seconds for terminal monitor
  pinMode(DEBUG_PIN, OUTPUT);
  pinMode(13, OUTPUT);

  // Setup Serial3 for Half duplex:
  SERIALM.begin(416000); // would probably want to see how close we are
  // Lets setup that IO pin to be in half duplex
  s_pkuartM->CTRL |= LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC;

  // Lets try to enable PU resistor on that pin...
  *(portControlRegister(SERIALM_TX_PIN)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
  IOMUXC_LPUART2_TX_SELECT_INPUT = 1; // need to get to right one...
  setPortRX();

  // Also Setup Serial4 for Half duplex:
  SERIALE.begin(416000); // would probably want to see how close we are
  // Lets setup that IO pin to be in half duplex
  s_pkuartE->CTRL |= LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC;

  // Lets try to enable PU resistor on that pin...
  *(portControlRegister(SERIALE_TX_PIN)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;
  IOMUXC_LPUART3_TX_SELECT_INPUT = 0; // need to get to right one...
  s_pkuartE->CTRL &= ~LPUART_CTRL_TXDIR;  // Set in to RX Mode...


  em_last_recv = 0;
}

void setPortTX() {
  digitalWrite(DEBUG_PIN, HIGH);
  s_pkuartM->CTRL |= LPUART_CTRL_TXDIR;  // Set in to TX Mode...
}

void setPortRX() {
  SERIALM.flush();  // Make sure we output everything first before changing state.
  s_pkuartM->CTRL &= ~LPUART_CTRL_TXDIR;  // Set in to RX Mode...
  digitalWrite(DEBUG_PIN, LOW);
}

uint32_t loop_count = 0;

void loop() {
  // Lets see if we have anything coming in on the Echo PORT
  while (SERIALE.available()) {
    uint8_t ch = SERIALE.read();
    receive_buffer[receive_index++] = ch;
    if (ch == '\n') {
      receive_buffer[receive_index] = 0;  // Null terminate.
      // BUGBUG:: Put in line instead of calls
      s_pkuartE->CTRL |= LPUART_CTRL_TXDIR;  // Set in to TX Mode...
      SERIALE.write((char*)receive_buffer);
      SERIALE.flush();
      s_pkuartE->CTRL &= ~LPUART_CTRL_TXDIR;  // Set in to RX Mode...
      receive_index = 0;
    }
    em_last_recv = 0;
  }

  // Lets see if we have anything coming in on the main port
  while (SERIALM.available()) {
    uint8_t ch = SERIALM.read();
    Serial.write(ch);
    em_last_recv = 0;
  }

  if (em_last_recv > LOOP_DELAY_MS) {
    digitalToggleFast(13);
    // output new packet
    setPortTX(); // put port in TX mode
    SERIALM.printf("Loop: %d\n", loop_count++);
    setPortRX();  // put back into RX mode.
    em_last_recv = 0;
  }
}
But again this is Serial3 to Serial4. The actual registers you need to change are different for Serial8 as it is on different pins and different underlying hardware Serial port. All of the data for which registers to set and values is actually stored in the Serial ports Hardware Structures. I did this awhile ago, when I was trying to build the half duplex capabilities into the Serial class. So in the cores\teensy4\HardwareSerialX.cpp files are hardware definition structures to define the stuff for that instance. In the case of Serial8
Code:
static HardwareSerial::hardware_t UART5_Hardware = {
	7, IRQ_LPUART5, &IRQHandler_Serial8, 
	&serialEvent8, &_serialEvent8_default,
	CCM_CCGR3, CCM_CCGR3_LPUART5(CCM_CCGR_ON),
    {{34,1, &IOMUXC_LPUART5_RX_SELECT_INPUT, 1}, {48, 2, &IOMUXC_LPUART5_RX_SELECT_INPUT, 0}},
    {[COLOR="#FF0000"]{35,1, &IOMUXC_LPUART5_TX_SELECT_INPUT, 1}[/COLOR], {0xff, 0xff, nullptr, 0}},

	50, // CTS pin
	2, //  CTS
	IRQ_PRIORITY, 38, 24, // IRQ, rts_low_watermark, rts_high_watermark
	XBARA1_OUT_LPUART5_TRG_INPUT
};
HardwareSerial Serial8([COLOR="#FF0000"]&IMXRT_LPUART5[/COLOR], &UART5_Hardware, tx_buffer8, SERIAL8_TX_BUFFER_SIZE,
	rx_buffer8,  SERIAL8_RX_BUFFER_SIZE);
So in the above If you want to use Serial8 instead of Serial3 some of the sections would change:
Code:
// Serial3 information - Say this is our Main Serial port
//#define SERIALM Serial3
//#define SERIALM_TX_PIN 14
//IMXRT_LPUART_t *s_pkuartM = &IMXRT_LPUART2;  // underlying hardware UART for Serial3
// Serial8 information - Say this is our Main Serial port
#define SERIALM Serial8
#define SERIALM_TX_PIN 14
IMXRT_LPUART_t *s_pkuartM = &IMXRT_LPUART5;  // underlying hardware UART for Serial3
...
  //IOMUXC_LPUART2_TX_SELECT_INPUT = 1; // need to get to right one...
  IOMUXC_LPUART5_TX_SELECT_INPUT = 1; // need to get to right one...

The difficulty is trying to make sure both are not writing at the same time. The example above one acts like the master and other the slave. That is one will only send data if the other talks to it first.

At one point as part of a Pull Request(https://github.com/PaulStoffregen/cores/pull/419)
I proposed a change to the class to allow the user do this easier. Where you would simply specify something in the Serial.begin format field to say half duplex... Something like:
Code:
Serial8.begin(115200, SERIAL_HALF_DUPLEX);
And the underlying code would set it up. It would also do the switch to TX mode when you did a SerialX.write() and would switch back when the TX completes...
I will probably go ahead and close this one out as it has other stuff in it as well, which conflicts with some later changes. But if @Paul wishes to take in the changes, I will create a new PR, with just the needed changes.
 
that is very interesting, half duplex would be totally fine. I was planning with softwareSerial to have a master slave arrangement anway.
I am surprised that something like this had not already been implemented officially in the manner you suggested "Serial8.begin(115200, SERIAL_HALF_DUPLEX);".
 
Yes I have used half duplex for several years now on T3.x and later T4.x for quick and dirty setups to run Robotis Dynamixel Servos, typically at 1mbs. This includes using something like a T3.2 to emulate being a servo, which plugs into the DXL chain, such that for example on an Trossen Robotics HR-OS1 humanoid robot, we played around with adding a 3d printed hand with a T3.2 board that had a Neopixel on it, which we could then tell it to change colors...

Most of the time these days when I play around with DXLs, I usually add in extra hardware that does the half duplex, as to have it also level shift the signal to 5v, which is what the Servos are speced for... Although never ran into issues that I know of with feeding their TTL signals at 3.3v.
 
Yes I have used half duplex for several years now on T3.x and later T4.x for quick and dirty setups to run Robotis Dynamixel Servos, typically at 1mbs. This includes using something like a T3.2 to emulate being a servo, which plugs into the DXL chain, such that for example on an Trossen Robotics HR-OS1 humanoid robot, we played around with adding a 3d printed hand with a T3.2 board that had a Neopixel on it, which we could then tell it to change colors...

Most of the time these days when I play around with DXLs, I usually add in extra hardware that does the half duplex, as to have it also level shift the signal to 5v, which is what the Servos are speced for... Although never ran into issues that I know of with feeding their TTL signals at 3.3v.

for now i will have to find an alternative as I cannot afford to try and use your solution if they change something and break it due to in not being baked into the teensy official code.
I actually can get away with just 1 bit partially bi directional comms for now where both run with their pins in input mode and set to low. one can pull the pin High or low on input mode and the other can override it by pulling the pin high or low on output mode.

This way one can send a "go" command by pulling the line high and holding it high, but is able to check if the other has had a fault occur because the other can override it and pull the line low.
 
Status
Not open for further replies.
Back
Top