Teensy 4.0 and single-wire serial

Status
Not open for further replies.

pawelsky

Well-known member
Hi,

I'm trying to switch Teensy 4.0 serials into single-wire mode but it doesn't quite work for me. I'm trying to follow the i.MX RT1060 Processor Reference Manual and do the following:

First I initialize Serial port 1 normally
Code:
Serial1.begin(57600);

then I switch the port into single-wire operation but setting the LOOPS and RSRC flags of the Serial 1 (UART6) CTRL register
Code:
LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
finally I switch the port into receive mode by making sure the TXDIR flag of the same CTRL register is cleared
Code:
LPUART6_CTRL &= ~LPUART_CTRL_TXDIR;

Then I connect the TX pin of Serial1 to TX pin of Serial2 and send some data in the loop from Serial2. Unfortunately none of that is received by Serial1.

Am I missing something?

Here is the complete code I use
Code:
void setup() {
  Serial.begin(115200);
  Serial1.begin(57600);
  LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
  LPUART6_CTRL &= ~LPUART_CTRL_TXDIR;
  Serial2.begin(57600);
  Serial.println("Teensy 4.0 Single-Wire Serial Test");
}

void loop() {
  Serial2.write(0x02);
  if(Serial1.available()) Serial.println(Serial1.read(), HEX);
}

I'm using Arduino 1.8.10 and Teensyduino 1.48
 
I keep meaning to try it, I have so far run my Dynamixel servos using a buffer like chip with a direction IO pin...

But I keep meaning to add the code to my BioloidSerial code to allow to use a single pin.

If/When I do it, I will probably start off similar to what you have, however some things I would check and need to address, include:

I believe you need to configure the IOMUXC input daisy chain... In this case Page 856 in reference manual, you probably need:
Code:
IOMUXC_LPUART6_TX_SELECT_INPUT = 1

And in addition I would probably update the TX pins configuration information.
Example on T3.x I do:
Code:
 volatile uint32_t *reg = portConfigRegister(tx_pin);
        *reg = PORT_PCR_DSE | PORT_PCR_SRE | PORT_PCR_MUX(3) | PORT_PCR_PE | PORT_PCR_PS; // pullup on output pin;
But of course different registers for T4... but same idea of enable the PU resistor...
 
AFAIK - didn't see any such testing on the Beta thread.

What pins are in use?

The Serial1 code will have expectations/config on Rx and Tx pins. Making above change after to the alter UART function may not be effective/complete with that standard config?

.. funny … detailed crosspost with the master :)
 
I don't think you can use the core Serialx library on a modified single wire bus, you'd need to implement another library to handle it.
 
Thank you all for the quick reply

I believe you need to configure the IOMUXC input daisy chain... In this case Page 856 in reference manual, you probably need:
Code:
IOMUXC_LPUART6_TX_SELECT_INPUT = 1

That did the trick, thanks a lot!
 
BTW I've noticed that in HadwareSerialX.cpp configuration of the IOMUXC for TX pins in omitted for most of the serials (except Serial5/LPUART8). Is there any particular reason for that?

Here is an example of Serial1

Code:
const HardwareSerial::hardware_t UART6_Hardware = {
    0, IRQ_LPUART6, &IRQHandler_Serial1, &serial_event_check_serial1,
    CCM_CCGR3, CCM_CCGR3_LPUART6(CCM_CCGR_ON),
    {{0,2, &IOMUXC_LPUART6_RX_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
    {[B][COLOR=#ff0000]{1,2, nullptr, 0}[/COLOR][/B], {0xff, 0xff, nullptr, 0}},
    0xff, // No CTS pin
    0, // No CTS
    IRQ_PRIORITY, 38, 24, // IRQ, rts_low_watermark, rts_high_watermark
 
The simple answer was because when I ported over HardwareSerial early in T4 beta and some Serial ports were not working, I finally figured out about the IOMUXC_..._SELECT_INPUT settings, and at the time I was unsure if I needed to also set the ones on the OUTPUT pins as well. Turns out no. It is only needed IF the pin is used as INPUT... I was probably testing on Serial5 when I figured out some of this... Now it may not hurt to someday fill in that table if for no other reason it documents it.

Also not sure if there would be any negative effect to set it anyway in normal HardwareSerial cases.

Also at one point I wanted to add in some new non standard APIs, something like:
Serial1.setDuplex(HardwareSerial::HALF_DUPLEX);

And either another API, like Serial1.setPinDirection(HardwareSerial::TX_PIN_INPUT);

Again these are off to top of the head apis. And actually preferred, would have the code to not need the second one, but to work off of the transmitterEnable code, which when you to a Serial1.write(), automatically switches to TX mode, and when the ISR detects that the queue is empty and last byte has output (transmit complete), to switch back to input mode...

Actually alternative to new api, and something I thought about hacking in, might to have something like:
#define HALF_DUPLEX_PIN 0xfe
Serial1.transmtterEnable(HALF_DUPLEX_PIN);

and/or alternatively if the pin passed in is the TX pin...

But I am not sure what @Paul would think of these additions. If I were to do this, would probably also put it into T3.x code base as well...
 
Thanks for clarification.

Another alternative to set the port into single-wire mode would be to add new defines for format that could be passed to begin function, but I think a new API for the single-wire operation would be cleaner (it could even be one function to enable/disable and set direction at the same time). And yes, that should deffinitely be done for T3.x as well.
 
Hi,
trying to do something similar, in 9 bit mode, single wire TX2 pin connected to TX1 pin, code working is below.
But I need to add a periodic reverse direction
- TX2 ----> TX1
- Reverse direction
- TX1 ----> TX2
...
How can the direction be changed ?
Thanks.

Code:
void setup() {
  Serial.begin(115200);
  Serial1.begin(9600);
  IOMUXC_LPUART6_TX_SELECT_INPUT = 1;
  //LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC); /* DONT USE, WILL FREZE */
  LPUART6_CTRL &= ~LPUART_CTRL_TXDIR;
  Serial2.begin(9600);
  Serial.println("Teensy 4.0 9bit Serial Test");
}

void loop() {
  static uint32_t x = 0x0;
  x++;
  if (x > 0x1FF)
    x = 0;
  Serial.print("x=");
  Serial.println(x, HEX);
  Serial2.write9bit(x);
  Serial2.flush();
  if (Serial1.available()) {
    Serial.print(millis());
    Serial.print(":");
    Serial.println(Serial1.read(), HEX);
  }
}
 
How can the direction be changed ?
You change direction by setting or clearing the LPUART_CTRL_TXDIR flag of the LPUARTx_CTRL register. For UART1 it means for receive mode:
Code:
LPUART6_CTRL &= ~LPUART_CTRL_TXDIR;
and for transmit mode:
Code:
LPUART6_CTRL |= LPUART_CTRL_TXDIR;

Of course you need to put UART2 into single wire mode as well.

BTW I believe that for 9 bit you should also use the SERIAL_9N1 format in begin.
 
Almost.
There is a remaining problem with either flush() that seems not being synchronous or a bug in the below code
Using 8bit or 9bit shows same behavior, here is 8bit.

Note
There is #define SERIAL_9BIT_SUPPORT in teensy4 HardwareSerial.h
and SERIAL1/2 _TX/2RX _BUFFER_SIZE are set to 128 in teensy4 HardwareSerial1.cpp and HardwareSerial2.cpp

Console: with BUFFERED 8

Code:
Teensy 4.0 9bit Serial Single Wire with reversal test
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:81:82:83:84:85:86:87:88
Read  TX1:81:82:83:84:85:86:count=6
setMode:Serial1:TX
setMode:Serial2:RX
Write TX1:1:2:3:4:5:6:7:8
Read  TX2:1:2:3:4:5:6:count=6
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:89:8A:8B:8C:8D:8E:8F:90
Read  TX1:87:88:89:8A:8B:8C:8D:8E:count=8
setMode:Serial1:TX
setMode:Serial2:RX
Write TX1:9:A:B:C:D:E:F:10
Read  TX2:7:8:9:A:B:C:D:E:F:10:count=10
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:91:92:93:94:95:96:97:98
Read  TX1:8F:90:91:92:93:94:95:96:count=8
setMode:Serial1:TX
setMode:Serial2:RX
Write TX1:11:12:13:14:15:16:17:18
Read  TX2:11:12:13:14:15:16:count=6

With BUFFERED 32
Code:
Teensy 4.0 9bit Serial Single Wire with reversal test
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:81:82:83:84:85:86:87:88:89:8A:8B:8C:8D:8E:8F:90:91:92:93:94:95:96:97:98:99:9A:9B:9C:9D:9E:9F:A0
Read  TX1:81:82:83:84:85:86:87:88:89:8A:8B:8C:8D:8E:8F:90:91:92:93:94:95:96:97:98:99:9A:9B:9C:9D:9E:count=30
setMode:Serial1:TX
setMode:Serial2:RX
Write TX1:1:2:3:4:5:6:7:8:9:A:B:C:D:E:F:10:11:12:13:14:15:16:17:18:19:1A:1B:1C:1D:1E:1F:20
Read  TX2:1:2:3:4:5:6:7:8:9:A:B:C:D:E:F:10:11:12:13:14:15:16:17:18:19:1A:1B:1C:1D:1E:count=30
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:A1:A2:A3:A4:A5:A6:A7:A8:A9:AA:AB:AC:AD:AE:AF:B0:B1:B2:B3:B4:B5:B6:B7:B8:B9:BA:BB:BC:BD:BE:BF:C0
Read  TX1:9F:A0:A1:A2:A3:A4:A5:A6:A7:A8:A9:AA:AB:AC:AD:AE:AF:B0:B1:B2:B3:B4:B5:B6:B7:B8:B9:BA:BB:BC:BD:BE:count=32



Code:
#define SPEED     9600
#define BUFFERED  8
//#define BIT9


#if defined(__IMXRT1062__) // Tensy 4.0
volatile uint32_t *uartCtrl1;
volatile uint32_t *uartCtrl2;
#else // Tensy 3.x x>=2
volatile uint8_t *uartCtrl1;
volatile uint8_t *uartCtrl2;
#endif

void setMode(uint8_t number, uint8_t tx)
{
#if defined(__IMXRT1062__)
  volatile uint32_t *uartCtrl = NULL;
#else
  volatile uint8_t *uartCtrl1 = NULL;
#endif
  Serial.print("setMode:Serial");
  Serial.print(number);
  if (tx)
    Serial.print(":TX");
  else
    Serial.print(":RX");
  if (number == 1)
    uartCtrl = uartCtrl1;
  if (number == 2)
    uartCtrl = uartCtrl2;
  if (!uartCtrl) {
    Serial.println("setMode error");
    return;
  }
  Serial.println("");
  if (tx) {
#if defined(__IMXRT1062__)
    *uartCtrl |= LPUART_CTRL_TXDIR;
#else
    *uartCtrl |= UART_C3_TXDIR;
#endif
  } else {
#if defined(__IMXRT1062__)
    *uartCtrl &= ~LPUART_CTRL_TXDIR;
#else
    *uartCtrl &= ~UART_C3_TXDIR;
#endif
  }
}

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

  // Single wire: TX2 connected to TX1
#ifdef BIT9
  Serial1.begin(SPEED, SERIAL_9O1);
#else
  Serial1.begin(SPEED);
#endif
#if defined(__IMXRT1062__)
  uartCtrl1 = &LPUART6_CTRL;
  LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
  IOMUXC_LPUART6_TX_SELECT_INPUT = 1;
#else
  uartCtrl1 = &UART0_C3;
  UART0_C1 |= (UART_C1_LOOPS | UART_C1_RSRC);
#endif

#ifdef BIT9
  Serial2.begin(SPEED, SERIAL_9O1);
#else
  Serial2.begin(SPEED);
#endif
#if defined(__IMXRT1062__)
  uartCtrl2 = &LPUART4_CTRL;
  LPUART4_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
  IOMUXC_LPUART4_TX_SELECT_INPUT = 2;
#else
  uartCtrl2 = &UART1_C3;
  UART1_C1 |= (UART_C1_LOOPS | UART_C1_RSRC);
#endif

#if defined(__IMXRT1062__)
  Serial.println("Teensy 4.0 9bit Serial Single Wire with reversal test");
#else
  Serial.println("Teensy 3.x x>=2 9bit Serial Single Wire with reversal test");
#endif
}

void loop() {
  static uint32_t data1 = 0x0;
#ifdef BIT9
  static uint32_t data2 = 0xff;
#else
  static uint32_t data2 = 0x80;
#endif
  static uint8_t reverse = 0;
  uint8_t cnt;
  int val1, val2;

  cnt = 0;
  if (reverse == 0) {
    setMode(2, 1); // Serial2 TX
    setMode(1, 0); // Serial1 RX
    Serial.print("Write TX2");
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data2 > 0x1FF)
        data2 = 0;
      Serial2.write9bit(data2);
#else
      if (++data2 > 0xFF)
        data2 = 0;
      Serial2.write(data2);
#endif
      Serial.print(":");
      Serial.print(data2, HEX);
    }
    Serial2.flush();
    Serial.println("");
    Serial.print("Read  TX1");
    while (Serial1.available()) {
      val1 = Serial1.read();
      Serial.print(":");
      Serial.print(val1, HEX);
      cnt++;
    }
    Serial.print(":count=");
    Serial.println(cnt);
  } else {
    setMode(1, 1); // Serial1 TX
    setMode(2, 0); // Serial2 RX
    Serial.print("Write TX1");
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data1 > 0x1FF)
        data1 = 0;
      Serial1.write9bit(data1);
#else
      if (++data1 > 0xFF)
        data1 = 0;
      Serial1.write(data1);
#endif
      Serial.print(":");
      Serial.print(data1, HEX);
    }
    Serial1.flush();
    Serial.println("");
    Serial.print("Read  TX2");
    while (Serial2.available()) {
      val2 = Serial2.read();
      Serial.print(":");
      Serial.print(val2, HEX);
      cnt++;
    }
    Serial.print(":count=");
    Serial.println(cnt);
  }
  reverse = ~reverse;
}
 
And in 9bit mode write and read do not match expected 0x101, read 0x301
Code:
Teensy 4.0 9bit Serial Single Wire with reversal test
setMode:Serial2:TX
setMode:Serial1:RX
Write TX2:100:101:102:103:104:105:106:107
Read  TX1:100:301:302:103:304:105:count=6
Using for val1 and val2 a additional &= 0x1ff;
Code:
val2 = Serial2.read();
#ifdef BIT9
      val2 &= 0x1ff;
#endif
gives correct values, the teensy 4.0 lacks a &= 0x1ff somewhere ?
 
Last edited:
Sorry I am not a 9 bits expert. To know the right answer.

That is you are asking for: SERIAL_9O1

If I understand correctly you are asking for 9 Data bits plus an ODD parity bit. So the system is probably returning 10 bits of data. If you are simply wanting/needing 9 bits of data, you might try: SERIAL_9N1

Again I am not sure logically what is correct. If I am guessing, with how the system is working:

If you do SERIAL_7O1 the system both T3.x and T4 will return 8 bits including the Parity bit
If you do SERIAL_8O1 My gut tells me neither system will preserve the parity bit (9th bit), unless the define:
Code:
//#define SERIAL_9BIT_SUPPORT
is uncommented.

Why: without the define the queue is only 8 bits. My guess is if the #define is there. My quick look through T3.x Serial1 code looks like, it will NOT preserve the parity bit in this case. But guessing T4 will.
Likewise I don't think T3.x will ever keep the parity bit on SERIAL_9x...

But question is should we?
 
It's SERIAL_9O1 because the real device is using this.
Using the &= 0x1ff is ok, but I think the parity bit(s) aren't supposed to be returned by read().
 
With
#define SPEED 9600
#define BUFFERED 64
#define BIT9
and adding a delay(100) after the Serial1 or 2 .flush() and the &= 0x1ff it's ok, just a bit puzzled by the need of the delay().

Code:
Write TX2:100:101: ... :13E:13F
Read  TX1:100:101: ... :13E:13F:count=64
setMode:Serial1:TX
setMode:Serial2:RX
Write TX1:1:2: ... :3F:40
Read  TX2:1:2: ... :3F:40:count=64
 
However with your setup, I am starting to get reasonable results when I switch to using Serial_9N1
i.e. not generate or use parity bits.

Also for double security, there are two places the 9th bit can come through, both in the data register as well as in the CTRL register (to sort of emulate the T3.x). So in your case of switching back and forth I would probably add:
Code:
#if defined(__IMXRT1062__)
[COLOR="#FF0000"]    *uartCtrl &= ~(LPUART_CTRL_R9T8 | LPUART_CTRL_R8T9);[/COLOR]
    *uartCtrl |= LPUART_CTRL_TXDIR;
#else
    *uartCtrl |= UART_C3_TXDIR;
#endif
Not sure if similar thing in T3.x as well...

As for only getting 30 out of 32... Probably have not yet processed the data in the RX

@Paul - Wondering? Right now we (I) set the RX water mark to be half full (2) in begin... I was able to get this program to work as expected by setting the water mark to 0....
Code:
#if defined(__IMXRT1062__)
  uartCtrl1 = &LPUART6_CTRL;
  LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
 [COLOR="#FF0000"] LPUART6_WATER &= 0xffff;[/COLOR]
  IOMUXC_LPUART6_TX_SELECT_INPUT = 1;

This leaves the Watermark for TX still at 2...

Wondering if we should change the default code to go ahead and set RX to 0?
AND/OR should functions like: Serail1.available() also look at the WATER register for the RX Count?
 
New version of code, better display, and
change int show = 1; to int show = 0;
shows only errors.

Code:
#define SPEED     9600
#define BUFFERED  8
#define BIT9
#define DELAY 10
#define DATA_PER_LINE 28

int check1[BUFFERED];
int check2[BUFFERED];

int show = 1;
#define DBG(...)   { if (show) Serial.print(__VA_ARGS__); }
#define DBGN(...)  { if (show) Serial.println(__VA_ARGS__); }


#if defined(__IMXRT1062__) // Tensy 4.0
volatile uint32_t *uartCtrl1;
volatile uint32_t *uartCtrl2;
#else // Tensy 3.x x>=2
volatile uint8_t *uartCtrl1;
volatile uint8_t *uartCtrl2;
#endif

void setMode(uint8_t number, uint8_t tx)
{
#if defined(__IMXRT1062__)
  volatile uint32_t *uartCtrl = NULL;
#else
  volatile uint8_t *uartCtrl1 = NULL;
#endif
  DBG("setMode:Serial");
  DBG(number);
  if (tx) {
    DBG(":TX");
  } else {
    DBG(":RX");
  }
  if (number == 1)
    uartCtrl = uartCtrl1;
  if (number == 2)
    uartCtrl = uartCtrl2;
  if (!uartCtrl) {
    DBGN("setMode error");
    return;
  }
  DBGN("");
  if (tx) {
#if defined(__IMXRT1062__)
    *uartCtrl |= LPUART_CTRL_TXDIR;
    //*uartCtrl &= ~(LPUART_CTRL_R9T8 | LPUART_CTRL_R8T9);
#else
    *uartCtrl |= UART_C3_TXDIR;
#endif
  } else {
#if defined(__IMXRT1062__)
    *uartCtrl &= ~LPUART_CTRL_TXDIR;
    //*uartCtrl &= ~(LPUART_CTRL_R9T8 | LPUART_CTRL_R8T9);
#else
    *uartCtrl &= ~UART_C3_TXDIR;
#endif
  }
}

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

  // Single wire: TX2 connected to TX1
#ifdef BIT9
  Serial1.begin(SPEED, SERIAL_9O1);
#else
  Serial1.begin(SPEED);
#endif
#if defined(__IMXRT1062__)
  uartCtrl1 = &LPUART6_CTRL;
  LPUART6_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
  //LPUART6_WATER &= 0xffff;
  IOMUXC_LPUART6_TX_SELECT_INPUT = 1;
#else
  uartCtrl1 = &UART0_C3;
  UART0_C1 |= (UART_C1_LOOPS | UART_C1_RSRC);
#endif

#ifdef BIT9
  Serial2.begin(SPEED, SERIAL_9O1);
#else
  Serial2.begin(SPEED);
#endif
#if defined(__IMXRT1062__)
  uartCtrl2 = &LPUART4_CTRL;
  LPUART4_CTRL |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);
  //LPUART4_WATER &= 0xffff;
  IOMUXC_LPUART4_TX_SELECT_INPUT = 2;
#else
  uartCtrl2 = &UART1_C3;
  UART1_C1 |= (UART_C1_LOOPS | UART_C1_RSRC);
#endif

#if defined(__IMXRT1062__)
  Serial.println("Teensy 4.0 9bit Serial Single Wire with reversal test");
#else
  Serial.println("Teensy 3.x x>=2 9bit Serial Single Wire with reversal test");
#endif
}

void loop() {
  static uint32_t data1 = 0x0;
#ifdef BIT9
  static uint32_t data2 = 0xff;
#else
  static uint32_t data2 = 0x80;
#endif
  static uint8_t reverse = 0;
  uint8_t cnt;
  int val, numb;

  cnt = 0;
  if (reverse == 0) {
    setMode(2, 1); // Serial2 TX
    setMode(1, 0); // Serial1 RX
    DBG("Write TX2");
    numb = 0;
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data2 > 0x1FF)
        data2 = 0;
      Serial2.write9bit(data2);
#else
      if (++data2 > 0xFF)
        data2 = 0;
      Serial2.write(data2);
#endif
      check2[i] = data2;
      DBG(":");
      DBG(data2, HEX);
      if (++numb > DATA_PER_LINE) {
        DBGN("");
        numb = 0;
      }
    }
    Serial2.flush();
    delay(DELAY);
    DBGN("");
    DBG("Read  TX1");
    numb = 0;
    cnt = 0;
    while (Serial1.available()) {
      val = Serial1.read();
#ifdef BIT9
      val &= 0x1ff;
#endif
      DBG(":");
      DBG(val, HEX);
      if (val != check2[cnt]) {
        Serial.println("R:W Error");
      }
      cnt++;
      if (++numb > DATA_PER_LINE) {
        DBGN("");
        numb = 0;
      }
    }
    DBG(":count=");
    DBGN(cnt);
    if (cnt != BUFFERED) {
      Serial.print("Count2 Error ");
      Serial.println(cnt);
    }
  } else {
    setMode(1, 1); // Serial1 TX
    setMode(2, 0); // Serial2 RX
    DBG("Write TX1");
    numb = 0;
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data1 > 0x1FF)
        data1 = 0;
      Serial1.write9bit(data1);
#else
      if (++data1 > 0xFF)
        data1 = 0;
      Serial1.write(data1);
#endif
      check1[i] = data1;
      if (++numb > DATA_PER_LINE) {
        DBGN("");
        numb = 0;
      }
      DBG(":");
      DBG(data1, HEX);
    }
    Serial1.flush();
    delay(DELAY);
    DBGN("");
    DBG("Read  TX2");
    numb = 0;
    cnt = 0;
    while (Serial2.available()) {
      val = Serial2.read();
#ifdef BIT9
      val &= 0x1ff;
#endif
      if (val != check1[cnt]) {
        Serial.println("Error");
      }
      DBG(":");
      DBG(val, HEX);
      cnt++;
      if (++numb > DATA_PER_LINE) {
        DBGN("");
        numb = 0;
      }
    }
    DBG(":count=");
    DBGN(cnt);
    if (cnt != BUFFERED) {
      Serial.print("Count1 Error ");
      Serial.println(cnt);
    }
  }
  reverse = ~reverse;
}
 
@phijun - for the heck of it I have sent email to @PaulStoffregen about some of these issues and asked about the possibility of resolving some of this internal to the Hardware Serial port code.

I pushed up a new Fork/branch: https://github.com/KurtE/cores/tree/T4_UART_ENHANCE

Which set the RX watermark to 0, plus added a half duplex mode, which knows about most of the stuff you muck with on T4. That is the Serial port tables were updated with the proper TX Input Select values (if appropriate) and it automatically set it.

I also defined a new FORMAT value that I currently require to OR in with our other formats to say half duplex. By default the TX pin would be configured to be in RX mode. Only when you do writes to the Serial port will it be converted to TX mode and it will then write out the stuff you say to output. When the last bits are transferred out the Serial ports interrupt handler will then switch it back to RX mode...

I hacked up your earlier test case, more or less to remove most of the switching and init stuff. Currently I have it running in 8 bit mode.

The example code looks like:
Code:
#define SPEED     9600
#define BUFFERED  32
//#define BIT9


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

  // Single wire: TX2 connected to TX1
#ifdef BIT9
  Serial1.begin(SPEED, SERIAL_9N1 | SERIAL_HALF_DUPLEX);
  Serial2.begin(SPEED, SERIAL_9N1 | SERIAL_HALF_DUPLEX);
#else
  Serial1.begin(SPEED, SERIAL_8N1 | SERIAL_HALF_DUPLEX);
  Serial2.begin(SPEED, SERIAL_8N1 | SERIAL_HALF_DUPLEX);
#endif

#if defined(__IMXRT1062__)
  Serial.println("Teensy 4.0 9bit Serial Single Wire with reversal test");
#else
  Serial.println("Teensy 3.x x>=2 9bit Serial Single Wire with reversal test");
#endif
}

void loop() {
  static uint32_t data1 = 0x0;
#ifdef BIT9
  static uint32_t data2 = 0xff;
#else
  static uint32_t data2 = 0x80;
#endif
  static uint8_t reverse = 0;
  uint8_t cnt;
  int val1, val2;

  cnt = 0;
  if (reverse == 0) {
    Serial.print("Write TX2");
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data2 > 0x1FF)
        data2 = 0;
      Serial2.write9bit(data2);
#else
      if (++data2 > 0xFF)
        data2 = 0;
      Serial2.write(data2);
#endif
      Serial.print(":");
      Serial.print(data2, HEX);
    }
    Serial2.flush();
    Serial.println("");
    Serial.print("Read  TX1");
    while (Serial1.available()) {
      val1 = Serial1.read();
      Serial.print(":");
      Serial.print(val1, HEX);
      cnt++;
    }
    Serial.print(":count=");
    Serial.println(cnt);
  } else {
    Serial.print("Write TX1");
    for (uint8_t i = 0; i  < BUFFERED; i++) {
#ifdef BIT9
      if (++data1 > 0x1FF)
        data1 = 0;
      Serial1.write9bit(data1);
#else
      if (++data1 > 0xFF)
        data1 = 0;
      Serial1.write(data1);
#endif
      Serial.print(":");
      Serial.print(data1, HEX);
    }
    Serial1.flush();
    Serial.println("");
    Serial.print("Read  TX2");
    while (Serial2.available()) {
      val2 = Serial2.read();
      Serial.print(":");
      Serial.print(val2, HEX);
      cnt++;
    }
    Serial.print(":count=");
    Serial.println(cnt);
  }
  reverse = ~reverse;
}
But again note: I am not sure if these changes will make it into the project code base or not. And if so, if I need to add it to T3.x as well. Note in T3.x code cases, it is a little more complex as the code for each serial port is separate, in their own source files
so would have to replicate the changes several times.
 
Perhaps a word about why a reliable single wire serial with tx/rx reversal API
in Teensyduino 'out of the box' could be a plus versus Arduino.

Many hobby Remote Control manufacturers (Jeti, FRSky, Multiplex SLRX, Graupner, ...)
have open their protocols so people can build their own sensors. There are even 'bridges'
Arduino based to use a sensor from brand X on a RC Radio system brand Y. There are
numerous libraries Arduino based available for that for many RC brands.
But they require Single Wire Serial at various speed and some need TX/RX reversal,
which isn't available out of the box on Arduino.

The brand I use is 'JETI model'. They have 2 open protocols
- JETI Telemetry Protocol 9600 baud 9O2 (an unusual format, pff, but Teensy 9O1 works)
- JETI EX Bus Communication Protocol 125000/250000 baud 8N1 (cool, a standard format)
Both need Single Wire Serial and TX/RX reversal. Regarding the speed of the second one a fast cpu is required.
Teensy is a good choice for me because out of the box optionnal 9bit support, numerous Serials/I2C, HW floating ops,
fast cpu, memory, ease of use and Single wire serial with tx/rx reversal available in the HW.
But it requires some extra non trivial work.

I've added the teensy 4.0 half-duplex uart registers handling from the test in my 2 JETI protocol libs for the Teensy 3.x>=5 and now Teensy 4.0.
It works perfectly with a Teensy 3.5 and a Teensy 4.0 home made sensor with a real Jeti RX Receivers and a JETI DC-24 Radio, for both protocols.

Even with the available(),flush(),read() (& 0x1ff required) behavior shown in the test.

Thanks to all of you.
 
FYI
The flush/available behavior can also be shown with a standard Serial test program.
Without DELAY defined, there are errors en Teensy 4.0, for example
Count Error 6
At that speed with default 8N1 there is 1 byte each 9600/10/1000=0.96 milliseconds.
A slow AVR MEGA 2560 doesn't need the delay.
On the 600MHz fast teensy the delay is needed because the CPU is faster than bytes inflight after the flush
are buffered in RX due to low baud ? (a logic error in the program)



Code:
#define SPEED         9600
#define BUFFERED      8
//#define DELAY         5
#define DATA_PER_LINE 20

int show = 0;
#define DBG(...)   { if (show) Serial.print(__VA_ARGS__); }
#define DBGN(...)  { if (show) Serial.println(__VA_ARGS__); }

void setup() {
  Serial.begin(115200);
  // TX2 connected to RX1
  Serial1.begin(SPEED);
  Serial2.begin(SPEED);
}

void loop() {
  static uint8_t data2 = 0x40;
  uint8_t cnt;
  uint8_t numb;
  int val;

  DBGN("Write TX2");
  numb = 0;
  for (uint8_t i = 0; i  < BUFFERED; i++) {
    data2++;
    Serial2.write(data2);
    DBG(":");
    DBG(data2, HEX);
    if (++numb >= DATA_PER_LINE) {
      DBGN("");
      numb = 0;
    }
  }
  Serial2.flush();
#ifdef DELAY
  delay(DELAY);
#endif
  DBGN("");
  DBGN("Read  TX1");
  numb = 0;
  cnt = 0;
  while (Serial1.available()) {
    val = Serial1.read();
    DBG(":");
    DBG(val, HEX);
    cnt++;
    if (++numb >= DATA_PER_LINE) {
      DBGN("");
      numb = 0;
    }
  }
  DBG(":count=");
  DBGN(cnt);
  if (cnt != BUFFERED) {
    Serial.print("Count Error ");
    Serial.println(cnt);
  }
}
 
Last edited:
Yes as mentioned yesterday - This has to do with setting the RX WATER mark value > 0... In my branch a couple of posts up I set it to 0...
 
I will change my test program so speed doesn't matter

Code:
  cnt = 0;
  while (cnt < BUFFERED) {
    if (Serial1.available()) {
      val = Serial1.read();
      DBG(":");
      DBG(val, HEX);
      cnt++;
      if (++numb >= DATA_PER_LINE) {
        DBGN("");
        numb = 0;
      }
    }
  }
 
Hi Kurt/phijun,

Was any progress made on this? I have a definite need for half duplex serial for one of my projects. I looked at https://github.com/KurtE/cores/tree/T4_UART_ENHANCE - seems the last commit removed the half duplex support?

I was thinking of using a software serial implementation in the absence of a hardware serial - the necessary baud rate is very low, so performance won't be an issue, although I don't think I'll be able to use an OOB implementation of Software Serial as my signals are inverted!

Thanks,

Andrew
 
Status
Not open for further replies.
Back
Top