Usart interrupt technique on teensy4.1

Emanuele

Member
Hi everybody,
i would like an example of code where is used a uart interrupt
technique for receive data for teensy 4.1.
I would like that every time I receive a data the interrupt is triggered and that data is acquired and processed(buffered).

I do so normally with the micro that I currently use, I would like to do it also with the teensy 4.1.

thanks
 
Simply look at the sources for the Hardware Serial ports. In this case: in <arduino install>\hardware\teensy\avr\cores\teensy4\hardwareSerial.cpp (.h for class definition).
Look at the method HardwareSerial::IRQHandler()
 
Thx
i have see the file HardwareSerial.cpp
but there is a simply program example that demostrate the use of that library in a sketch?
 
Any use of Serial1 ( or other UART port ) will use the included library code - See :: <arduino install>\examples\04.Communication\SerialPassthrough\SerialPassthrough.ino

Using that for Serial1 - just jumper pin 0 to pin 1 and anything sent on USB Serial will cross over Serial1 and echo out Serial USB.
Code:
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
}

void loop() {
  if (Serial.available()) {      // If anything comes in Serial (USB),
    Serial1.write(Serial.read());   // read it and send it out Serial1 (pins 0 & 1)
  }

  if (Serial1.available()) {     // If anything comes in Serial1 (pins 0 & 1)
    Serial.write(Serial1.read());   // read it and send it out Serial (USB)
  }
}
 
Thx but i dont want check in Main always if a data is available in serial port,i want that if a data is received an interrupt handler start thx
 
Serial data from is captured under interrupts and stored in the serial input buffer.
When you get data from the serial port you are actually taking it from the serial buffer.
 
Sorry, it is really unclear to me what you are actually asking for. So it is hard to know how best to try to answer.

You say you did something like this with another micro. But it is unclear from this if your other micro was some form of Arduino setup? And what it is you are trying to do with a Teensy 4.1.

Have you installed a recent copy of Arduino and then installed a recent copy of Teensyduino? Personally would suggest the latest beta...

With Arduino like boards, hardware Serial ports are typically handled by the HardwareSerial class (some other boards call the class something different)
Details on the basics of it are up at: https://www.arduino.cc/reference/en/language/functions/communication/serial/

On a Teensy 4.1 there are something like 8 hardware Serial ports. You will see which pins are connected to them on the Teensy 4.1 card which is also on the product page: https://www.pjrc.com/store/teensy41.html#pins

So for example if you are using the Hardware Serial port connected to pins 0 and 1 this is Serial1.

If you do something like: Serial1.println("Hello world");
The HardwareSerial class will put these bytes out into a software queue, and try to return immediately. Only if the queue fills up will the code have to wait until enough characters are transferred out to make room for the rest of the data.

If data is received on the hardware Serial port. The ISR associated with this port, will retrieve the data and put it into a software buffer, which your code can retrieve when it desires to.
You read the data using something like: int mychar = Serial1.read();
You can at any time see if there data in the queue by calling Serial.available();

Arduino has also defined a sort of different way to know that you have data available in the Serial RX queue, which you can see up at: https://www.arduino.cc/reference/en/language/functions/communication/serial/serialevent/

But basically for Serial1 you can write a function named: serialEvent1
like: void serialEvent1() {....};

Which in some Arduino implementations, gets called every time you return from loop() and there is something in the Serial1's RX queue. On Teensy this is somewhat extended and it is called every time your code
does some form of yield (either direct calls, or calls to delay ....) Note: I typically don't use these.

As I mentioned in the first reply, if you are wanting to know how this all works, all of this code is in the source files: in this case HardwareSerial.cpp and HardwareSerial.h

To understand this code, you will probably need to refer to several chapters in the IMXRT reference manual, which if you have not already done so, there is links to it on the Teensy 4.1 product page.
The main chapter is the LPUART which is chapter 49.
 
Would be interesting to know *WHY* you need that.
As use of interrupts is not that easy (The forum is full of questions re: "not working" interrupt-code) and the given link to the code clearly show how you could do it (simply insert a call?) I don't think interrupts are the right way for you. As all received (and to send) data is buffered, there must be a very interesting reason why you need it :)
 
Thx
i have see the file HardwareSerial.cpp
but there is a simply program example that demostrate the use of that library in a sketch?

Would be interesting to know *WHY* you need that.
As use of interrupts is not that easy (The forum is full of questions re: "not working" interrupt-code) and the given link to the code clearly show how you could do it (simply insert a call?) I don't think interrupts are the right way for you. As all received (and to send) data is buffered, there must be a very interesting reason why you need it :)

Yes very unclear - as noted a demonstration asked for - simple as example in p#4. Given that the underlying code and interrupt usage can be observed ... it could be hooked or edited as desired - perhaps the interrupt could be chained - allowed to work and acted on when it returns ...

That sketch is all it takes to activate and demonstrate the underlying library code. it is already tested and works.
 
Sorry I try to clarify usually i am working with pic in C or Assembler i do not know in depth teensy 4.1 arduino and its libraries.
I am trying to learn now.

I try to explain myself better:
usually i programming the serial device by enabling the interrupt on reception.
In main I can do what I want (even do nothing) when
a serial data arrives, an interrupt is triggered in reception and I acquire in that interrupt the serial data.
I would like to do this also with teensy 4.1 and I would like a sketch that shows me this.
Code:
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
}

void loop() {
  if (Serial.available()) {      // If anything comes in Serial (USB),
    Serial1.write(Serial.read());   // read it and send it out Serial1 (pins 0 & 1)
  }

  if (Serial1.available()) {     // If anything comes in Serial1 (pins 0 & 1)
    Serial.write(Serial1.read());   // read it and send it out Serial (USB)
  }
}

this code is not correct because in the main it has to constantly check if a data is present.
I simply want to program the serial device so that as soon as data arrives in the reception buffer, an interrupt is triggered.
Thx
 
simply i want to be able to write some code inside the receiving interrupt...
i have to be able to do it otherwise I don't have control of the microcontroller....
Thx
 
This code does have full control over the MCU - just a different idea or purpose.

Not sure if the PIC in normal use has one tenth or 1/100th the speed/power of the T_4.1

PJRC has encapsulated the devices and resources under the banner of Arduino - all through normal C/cpp interfaces from gcc compiler toolset where the 3000++ pages of the manual details have wrapper code in place to use all the covered devices.

The above code would allow polling of the hardware buffered Serial1 data some 5 millions times a second.

As indicated in post #2 the source code is present with the installed TeensyDuino. That source can be edited or modified or repurposed as desired.

Also as noted the interrupt responding to the Serial1 could perhaps be chained through for desired purposes.

No limit to desired edits in accordance with the Ref Man and MCU capabilities.
 
simply i want to be able to write some code inside the receiving interrupt...
i have to be able to do it otherwise I don't have control of the microcontroller....
Thx

The Teensy core is already handling the reception of serial via interrupt and loading into a buffer for you.

If you wish to do something more than that in the interrupt, you should be able to modify the HardwareSerial files in the core. I don't think there are any examples of that, so please share if you get it working.
 
Frank B had an interrupt chain for Teensy 3.x - I'm assuming that this would still work on Teensy 4.x.

Code:
class interruptChain {
  public:
    void chainInterrupt(int intNumber, void (*interruptPtr)(void)) {
			prevInterruptPtr = _VectorsRam[intNumber];
			_VectorsRam[intNumber] = interruptPtr;
    }
    inline void callNext(void) {
      prevInterruptPtr();
    }
  protected:
    void (*prevInterruptPtr)(void);
};

So, using this for Serial1, I could create an instance, like:

Code:
interruptChain _serial1Interrupt;

And then I would initialize it by passing the IRQ and an interrupt handler:

Code:
_serial1Interrupt.chainInterrupt(IRQ_UART0_STATUS + 16, serial1_int_handler);

Where serial1_int_handler is the interrupt handler. This would get called every time a byte is received by Serial1. Inside of the interrupt handler, you need to call next right away to not break the interrupt chain:

Code:
void serial1_int_handler(void)
{
  _serial1Interrupt.callNext();
  // do stuff
}
 
Well, if someone is searching for this in the future and stumbles across the thread, they’ll have some solutions to use.

Indeed! Good job looking it up ... hopefully the OP will appreciate not having to find it.

Of course doing anything with that can be risky - except setting a flag to poll and exiting to use the existing interface ... stalling the interrupt can break the processing.
 
Personally I think this is a wrong approach, unless there is a real strong reason on why one needs to setup their code this way. It is sort of like the old days on limited processors I used to write self modifying code.
But these days would not even think of doing so.

That is you are hard coding you sketch to some specific hardware and some specific configuration... And understanding of what the underlying ISR does.

For example what interrupt are you actually trying to hook? For example on Serial1 of a T4.x currently all of them use LPUART6 and Serial2 uses LPUART4, but maybe that might change with the
Teensy MicroMod as maybe we could decide to reorder the Serial objects on that board to better match their naming of Pins/signals... Maybe it will be a different LPUART under it.

Also this ISR is not just called to receive data, it is also setup to send data as well and to know when the TX has fully completed... And as your code will be called on an ISR, you run into all of the
normal issues of ISRs, timing...

But if it gets your job done....
 
Can someone post a Teensy 4.x example sketch? Tried the post #14 code ... "IRQ_UART0_STATUS" is unknown.

Sorry I have never needed/wanted to do this, so I don't have any examples.

But code for T3.x will not be a directly work for T4.x... Or even necessarily between different T4.x boards.

As I mentioned on previous post, you need to look at source code or pin mappings to figure out which actual LPUART is used for each serial port.
For example Serial4(HardwareSerial4.cpp), on T4, T4.x this is LPUART4 on Micromod this is LPUART3. The IRQ numbers (IRQ_LPUART4 or ...)

Once in your callback the requirements, register layout, .... will be different than T3.x
 
I believe Kurt's original suggestion (back in msg #2, over 2 years ago) is probably your best path for full control over the hardware serial interrupt.

Here is a link to the code on github.

https://github.com/PaulStoffregen/cores/blob/master/teensy4/HardwareSerial.cpp

It is also installed as files on your PC.

This code is over 700 lines because it offers a lot of features, like half duplex mode and automatic control of a transmitter enable pin for RS485 chips. It also have support for calling an event handler function. Maybe you can reduce the size if you delete code for these optional features.

But just to be clear, the path to getting the full control over serial interrupt you want is to either copy all this code into your own program, or copy similar code from NXP's SDK, or try to write this code by yourself. But creating this sort of code from scratch is not easy. Even on simple chips like Microchip PIC, a lot of effort is needed to fully understand the hardware. So I believe you are better off by just copying the HardwareSerial.cpp into your program and then modify it any way you like.


When Kurt suggested this 2 years ago, you asked:

i have see the file HardwareSerial.cpp
but there is a simply program example that demostrate the use of that library in a sketch?

You have said you do *NOT* wish to simply use the HardwareSerial library code in the normal way, where it receives into a buffer and then your program polls the status, or you use serial event to run automatically (still just polling, but moved into the yield function).

The HardwareSerial library code does not support directly processing data inside the interrupt. It only stores incoming data to a buffer, which is meant to be consumed from main program code.

This is why, you meet your desire to process data within the interrupt, that you copy the HardwareSerial.cpp code and then modify it yo suit your needs.

But I should also give a word of warning. Or rather, I'll just point to Frank's msg #8. Processing serial data this way risks creating race condition bugs if your data receiving code needs to interact or share data with anything else not done inside the same interrupt function. This is the reason why HardwareSerial.cpp does not have the feature you have requested, because the small benefit of lower latency response comes with large risk of difficult race condition bugs. Perhaps you are an exceptional programmer who can always avoid those type of bugs?
 
Now it is one thing to say to "simply" copy HardwareSerial.cpp and modify as you like. The reality is the HardwareSerial.cpp code was never meant to be copied into user code. It has a few global scope items, beyond the obvious issue of the class name.

To try to help you, here is a working program which I copied 3 files into a program: HardwareSerial.h, HardwareSerial.cpp, and HardwareSerial1.cpp. I renamed the class to myHardwareSerial and nvic_execution_priority() to my_nvic_execution_priority(void). I also commented out the xbar pin mapping table, so please be aware this program depends on that global scope table from the original HardwareSerial.

To demonstrate running something for each byte received, I put this inside the interrupt.

Code:
/*********************************************************************************/
/*      Example code to do something inside the interrupt for each byte received */
/*********************************************************************************/
        digitalToggle(13); // just toggle the orange LED - simplest possible example!
/*********************************************************************************/

I ran this on a Teensy 4.1 with a FTDI serial cable connected to RX1 (pin 0) and TX1 (pin 1). When I type into a terminal emulator on my PC, the LED indeed does turn on and off with each keystroke.

Here is the full code to just copy into Arduino IDE.

Code:
// copied from HardwareSerial.h
class myHardwareSerial : public Stream
{
public:
  static const uint8_t cnt_tx_pins = 2;
  static const uint8_t cnt_rx_pins = 2;
  typedef struct {
    const uint8_t     pin;    // The pin number
    const uint32_t    mux_val;  // Value to set for mux;
    volatile uint32_t *select_input_register; // Which register controls the selection
    const uint32_t    select_val; // Value for that selection
  } pin_info_t;

  typedef struct {
    uint8_t serial_index; // which object are we? 0 based
    IRQ_NUMBER_t irq;
    void (*irq_handler)(void);
    void (* _serialEvent)(void);
    volatile uint32_t &ccm_register;
    const uint32_t ccm_value;
    pin_info_t rx_pins[cnt_rx_pins];
    pin_info_t tx_pins[cnt_tx_pins];
    const uint8_t cts_pin;
    const uint8_t cts_mux_val;
    const uint16_t irq_priority;
    const uint16_t rts_low_watermark;
    const uint16_t rts_high_watermark;
    const uint8_t xbar_out_lpuartX_trig_input;
  } hardware_t;
public:
  constexpr myHardwareSerial(IMXRT_LPUART_t *myport, const hardware_t *myhardware, 
    volatile BUFTYPE *_tx_buffer, size_t _tx_buffer_size, 
    volatile BUFTYPE *_rx_buffer, size_t _rx_buffer_size) :
    port(myport), hardware(myhardware),
    tx_buffer_(_tx_buffer), rx_buffer_(_rx_buffer), tx_buffer_size_(_tx_buffer_size),  rx_buffer_size_(_rx_buffer_size),
    tx_buffer_total_size_(_tx_buffer_size), rx_buffer_total_size_(_rx_buffer_size) {
  }
  // Initialize hardware serial port with baud rate and data format.  For a list
  // of all supported formats, see https://www.pjrc.com/teensy/td_uart.html
  void begin(uint32_t baud, uint16_t format=0);
  void end(void);
  // Returns the number of bytes which have been received and
  // can be fetched with read() or readBytes().
  virtual int available(void);
  // Returns the next received byte, but does not remove it from the receive
  // buffer.  Returns -1 if nothing has been received.
  virtual int peek(void);
  // Wait for all data written by print() or write() to actually transmit.
  virtual void flush(void);
  // Transmit a single byte
  virtual size_t write(uint8_t c);
  // Reads the next received byte, or returns -1 if nothing has been received.
  virtual int read(void);
  // Configures a digital pin to be HIGH while transmitting.  Typically this
  // pin is used to control the DE and RE' pins of an 8 pin RS485 transceiver
  // chip, which transmits when DE is high and receives when RE' is low.
  void transmitterEnable(uint8_t pin);
  // Configure the serial hardware to receive with an alternate pin.  This
  // function may be called before begin(baud) so the default receive pin
  // is never used, or may be called while the serial hardware is running.  Only
  // specific pins are supported.  https://www.pjrc.com/teensy/td_uart.html
  void setRX(uint8_t pin);
  // Configure the serial hardware to transmit with an alternate pin.  This
  // function may be called before begin(baud) so the default transmit pin
  // is never used, or may be called while the serial hardware is running.  Only
  // specific pins are supported.  https://www.pjrc.com/teensy/td_uart.html
  void setTX(uint8_t pin, bool opendrain=false);
  // Configure RTS flow control.  The pin will be LOW when Teensy is able to
  // receive more data, or HIGH when the serial device should pause transmission.
  // All digital pins are supported.
  bool attachRts(uint8_t pin);
  // Configure CTS flow control.  Teensy will transmit when this pin is LOw
  // and will pause transmission when the pin is HIGH.  Only specific pins are
  // supported.  See https://www.pjrc.com/teensy/td_uart.html
  bool attachCts(uint8_t pin);
  // // Discard all received data which has not been read.
  void clear(void);
  // Returns the number of bytes which may be transmitted by write() or print()
  // without waiting.  Typically programs which must maintain rapid checking
  // and response to sensors use availableForWrite() to decide whether to
  // transmit.
  int availableForWrite(void);
  // Increase the amount of buffer memory between reception of bytes by the
  // serial hardware and the available() and read() functions. This is useful
  // when your program must spend lengthy times performing other work, like
  // writing to a SD card, before it can return to reading the incoming serial
  // data.  The buffer array must be a global or static variable.
  void addMemoryForRead(void *buffer, size_t length);
  // Increase the amount of buffer memory between print(), write() and actual
  // hardware serial transmission. This can be useful when your program needs
  // to print or write a large amount of data, without waiting.  The buffer
  // array must be a global or static variable.
  void addMemoryForWrite(void *buffer, size_t length);
  void addStorageForRead(void *buffer, size_t length) __attribute__((deprecated("addStorageForRead was renamed to addMemoryForRead"))){
    addMemoryForRead(buffer, length);
  }
  void addStorageForWrite(void *buffer, size_t length) __attribute__((deprecated("addStorageForWrite was renamed to addMemoryForWrite"))){
    addMemoryForWrite(buffer, length);
  }
  size_t write9bit(uint32_t c);
  
  // Event Handler functions and data
  static uint8_t serial_event_handlers_active;

  using Print::write; 
  // Transmit a single byte
  size_t write(unsigned long n) { return write((uint8_t)n); }
  // Transmit a single byte
  size_t write(long n) { return write((uint8_t)n); }
  // Transmit a single byte
  size_t write(unsigned int n) { return write((uint8_t)n); }
  // Transmit a single byte
  size_t write(int n) { return write((uint8_t)n); }

  // Only overwrite some of the virtualWrite functions if we are going to optimize them over Print version

  /*
  virtual void begin(uint32_t baud) { serial_begin(BAUD2DIV(baud)); }
  virtual void begin(uint32_t baud, uint32_t format) {
            serial_begin(BAUD2DIV(baud));
            serial_format(format); }
  */

  operator bool()     { return true; }

  static inline void processSerialEventsList() {
    for (uint8_t i = 0; i < s_count_serials_with_serial_events; i++) {
      s_serials_with_serial_events[i]->doYieldCode();
    }
  }
private:
  IMXRT_LPUART_t * const port;
  const hardware_t * const hardware;
  uint8_t       rx_pin_index_ = 0x0;  // default is always first item
  uint8_t       tx_pin_index_ = 0x0;
  uint8_t       half_duplex_mode_ = 0; // are we in half duplex mode?

  volatile BUFTYPE  *tx_buffer_;
  volatile BUFTYPE  *rx_buffer_;
  volatile BUFTYPE  *rx_buffer_storage_ = nullptr;
  volatile BUFTYPE  *tx_buffer_storage_ = nullptr;
  size_t        tx_buffer_size_;
  size_t        rx_buffer_size_;
  size_t        tx_buffer_total_size_;
  size_t        rx_buffer_total_size_;
  size_t        rts_low_watermark_ = 0;
  size_t        rts_high_watermark_ = 0;
  volatile uint8_t  transmitting_ = 0;
  volatile uint16_t   tx_buffer_head_ = 0;
  volatile uint16_t   tx_buffer_tail_ = 0;
  volatile uint16_t   rx_buffer_head_ = 0;
  volatile uint16_t   rx_buffer_tail_ = 0;

  volatile uint32_t   *transmit_pin_baseReg_ = 0;
  uint32_t      transmit_pin_bitmask_ = 0;

  volatile uint32_t   *rts_pin_baseReg_ = 0;
  uint32_t      rts_pin_bitmask_ = 0;

    inline void rts_assert();
    inline void rts_deassert();

  void IRQHandler();
  friend void IRQHandler_Serial1();
  friend void IRQHandler_Serial2();
  friend void IRQHandler_Serial3();
  friend void IRQHandler_Serial4();
  friend void IRQHandler_Serial5();
  friend void IRQHandler_Serial6();
  friend void IRQHandler_Serial7();
  #if defined(ARDUINO_TEENSY41)   
  friend void IRQHandler_Serial8();
  static myHardwareSerial   *s_serials_with_serial_events[8];
  #else 
  static myHardwareSerial   *s_serials_with_serial_events[7];
  #endif
  static uint8_t      s_count_serials_with_serial_events;
  void addToSerialEventsList(); 
  inline void doYieldCode()  {
    if (available()) (*hardware->_serialEvent)();
  }



};



// copied from HardwareSerial.cpp

/* Teensyduino Core Library
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2019 PJRC.COM, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * 2. If the Software is incorporated into a build system that allows
 * selection among a list of target devices, then similar target
 * devices manufactured by PJRC.COM must be included in the list of
 * target devices and selectable in the same manner.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// Arduino IDE automatically includes for normal programs
//#include "myHardwareSerial.h" 
//#include "core_pins.h"
//#include "Arduino.h"
//#include "debug/printf.h"

/*typedef struct {
        const uint32_t VERID;
        const uint32_t PARAM;
        volatile uint32_t GLOBAL;
        volatile uint32_t PINCFG;
        volatile uint32_t BAUD;
        volatile uint32_t STAT;
        volatile uint32_t CTRL;
        volatile uint32_t DATA;
        volatile uint32_t MATCH;
        volatile uint32_t MODIR;
        volatile uint32_t FIFO;
        volatile uint32_t WATER;
} IMXRT_LPUART_t; */

//. From Onewire utility files
#define PIN_TO_BASEREG(pin)             (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_BASE_ATTR
#define IO_REG_MASK_ATTR
#define DIRECT_READ(base, mask)         ((*((base)+2) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask)   (*((base)+1) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask)  (*((base)+1) |= (mask))
#define DIRECT_WRITE_LOW(base, mask)    (*((base)+34) = (mask))
#define DIRECT_WRITE_HIGH(base, mask)   (*((base)+33) = (mask))

#define UART_CLOCK 24000000

extern "C" {
    extern void xbar_connect(unsigned int input, unsigned int output);
}

#if defined(ARDUINO_TEENSY41)   
myHardwareSerial   *myHardwareSerial::s_serials_with_serial_events[8];
#else
myHardwareSerial  *myHardwareSerial::s_serials_with_serial_events[7];
#endif

// define our static objects
uint8_t     myHardwareSerial::s_count_serials_with_serial_events = 0;



#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 


// Copied from T3.x - probably should move to other location.
int my_nvic_execution_priority(void)
{
  uint32_t priority=256;
  uint32_t primask, faultmask, basepri, ipsr;

  // full algorithm in ARM DDI0403D, page B1-639
  // this isn't quite complete, but hopefully good enough
  __asm__ volatile("mrs %0, faultmask\n" : "=r" (faultmask)::);
  if (faultmask) return -1;
  __asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
  if (primask) return 0;
  __asm__ volatile("mrs %0, ipsr\n" : "=r" (ipsr)::);
  if (ipsr) {
    if (ipsr < 16) priority = 0; // could be non-zero
    else priority = NVIC_GET_PRIORITY(ipsr - 16);
  }
  __asm__ volatile("mrs %0, basepri\n" : "=r" (basepri)::);
  if (basepri > 0 && basepri < priority) priority = basepri;
  return priority;
}


void myHardwareSerial::begin(uint32_t baud, uint16_t format)
{
  //printf("myHardwareSerial begin\n");
  float base = (float)UART_CLOCK / (float)baud;
  float besterr = 1e20;
  int bestdiv = 1;
  int bestosr = 4;
  for (int osr=4; osr <= 32; osr++) {
    float div = base / (float)osr;
    int divint = (int)(div + 0.5f);
    if (divint < 1) divint = 1;
    else if (divint > 8191) divint = 8191;
    float err = ((float)divint - div) / div;
    if (err < 0.0f) err = -err;
    if (err <= besterr) {
      besterr = err;
      bestdiv = divint;
      bestosr = osr;
    }
  }
  //printf(" baud %d: osr=%d, div=%d\n", baud, bestosr, bestdiv);
  rx_buffer_head_ = 0;
  rx_buffer_tail_ = 0;
  tx_buffer_head_ = 0;
  tx_buffer_tail_ = 0;
  rts_low_watermark_ = rx_buffer_total_size_ - hardware->rts_low_watermark;
  rts_high_watermark_ = rx_buffer_total_size_ - hardware->rts_high_watermark;

  transmitting_ = 0;

  hardware->ccm_register |= hardware->ccm_value;

//  uint32_t fastio = IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3);

  // Maybe different pin configs if half duplex
  half_duplex_mode_ = (format & SERIAL_HALF_DUPLEX) != 0;
  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;   
  } 
  //hardware->rx_mux_register = hardware->rx_mux_val;
  //hardware->tx_mux_register = hardware->tx_mux_val;

  port->BAUD = LPUART_BAUD_OSR(bestosr - 1) | LPUART_BAUD_SBR(bestdiv)
    | (bestosr <= 8 ? LPUART_BAUD_BOTHEDGE : 0);
  port->PINCFG = 0;

  // Enable the transmitter, receiver and enable receiver interrupt
  attachInterruptVector(hardware->irq, hardware->irq_handler);
  NVIC_SET_PRIORITY(hardware->irq, hardware->irq_priority); // maybe should put into hardware...
  NVIC_ENABLE_IRQ(hardware->irq);

  // FIFO size
  // According to IMXRT1060RM_rev2.pdf, page 2875, Section 49.4.1.12.3 Diagram,
  // both TXFIFOSIZE and RXFIFOSIZE are fixed at 4 (register value == 1)
  //uint16_t tx_fifo_size = 4;
  uint8_t tx_water = 2;
  //uint16_t rx_fifo_size = 4;
  uint8_t rx_water = 2;
  // Original FIFO size calculation:
  // uint16_t tx_fifo_size = (1 << (((port->FIFO >> 4) & 0x7) + 1));
  // if (tx_fifo_size == 2) {  // The only case that doesn't fit the pattern
  //  tx_fifo_size = 1;
  // }
  // uint8_t tx_water = (tx_fifo_size < 16) ? tx_fifo_size >> 1 : 7;
  // uint16_t rx_fifo_size = (1 << (((port->FIFO >> 0) & 0x7) + 1));
  // if (rx_fifo_size == 2) {  // The only case that doesn't fit the pattern
  //  rx_fifo_size = 1;
  // }
  // uint8_t rx_water = (rx_fifo_size < 16) ? rx_fifo_size >> 1 : 7;

  /*
  Serial.printf("SerialX::begin stat:%x ctrl:%x fifo:%x water:%x\n", port->STAT, port->CTRL, port->FIFO, port->WATER );
  Serial.printf("  FIFO sizes: tx:%d rx:%d\n",tx_fifo_size, rx_fifo_size);  
  Serial.printf("  Watermark tx:%d, rx: %d\n", tx_water, rx_water);
  */
  port->WATER = LPUART_WATER_RXWATER(rx_water) | LPUART_WATER_TXWATER(tx_water);
  port->FIFO |= LPUART_FIFO_TXFE | LPUART_FIFO_RXFE;


  // lets configure up our CTRL register value
  uint32_t ctrl = CTRL_TX_INACTIVE;

  // Now process the bits in the Format value passed in
  // Bits 0-2 - Parity plus 9  bit. 
  ctrl |= (format & (LPUART_CTRL_PT | LPUART_CTRL_PE) );  // configure parity - turn off PT, PE, M and configure PT, PE
  if (format & 0x04) ctrl |= LPUART_CTRL_M;   // 9 bits (might include parity)
  if ((format & 0x0F) == 0x04) ctrl |=  LPUART_CTRL_R9T8; // 8N2 is 9 bit with 9th bit always 1

  // Bit 5 TXINVERT
  if (format & 0x20) ctrl |= LPUART_CTRL_TXINV;   // tx invert

  // Now see if the user asked for Half duplex:
  if (half_duplex_mode_) ctrl |= (LPUART_CTRL_LOOPS | LPUART_CTRL_RSRC);

  // write out computed CTRL
  port->CTRL = ctrl;

  // Bit 3 10 bit - Will assume that begin already cleared it.
  // process some other bits which change other registers.
  if (format & 0x08)  port->BAUD |= LPUART_BAUD_M10;

  // Bit 4 RXINVERT 
  uint32_t c = port->STAT & ~LPUART_STAT_RXINV;
  if (format & 0x10) c |= LPUART_STAT_RXINV;    // rx invert
  port->STAT = c;

  // bit 8 can turn on 2 stop bit mote
  if ( format & 0x100) port->BAUD |= LPUART_BAUD_SBNS;  

  //Serial.printf("    stat:%x ctrl:%x fifo:%x water:%x\n", port->STAT, port->CTRL, port->FIFO, port->WATER );

  // Enable the processing of serialEvent for this object, if user function exists.
  // Linker will assign NULL for a weak function which isn't implemented.
  if (hardware->_serialEvent) addToSerialEventsList();
};

inline void myHardwareSerial::rts_assert() 
{
  DIRECT_WRITE_LOW(rts_pin_baseReg_, rts_pin_bitmask_);
}

inline void myHardwareSerial::rts_deassert()
{
  DIRECT_WRITE_HIGH(rts_pin_baseReg_, rts_pin_bitmask_);
}


void myHardwareSerial::end(void)
{
  if (!(hardware->ccm_register & hardware->ccm_value)) return;
  while (transmitting_) yield();  // wait for buffered data to send
  port->CTRL = 0; // disable the TX and RX ...

  // Not sure if this is best, but I think most IO pins default to Mode 5? which appears to be digital IO? 
  *(portConfigRegister(hardware->rx_pins[rx_pin_index_].pin)) = 5;
  *(portConfigRegister(hardware->tx_pins[tx_pin_index_].pin)) = 5;


  // Might need to clear out other areas as well? 
  rx_buffer_head_ = 0;
  rx_buffer_tail_ = 0;
  if (rts_pin_baseReg_) rts_deassert();
  // 
}

void myHardwareSerial::transmitterEnable(uint8_t pin)
{
  while (transmitting_) ;
  pinMode(pin, OUTPUT);
  transmit_pin_baseReg_ = PIN_TO_BASEREG(pin);
  transmit_pin_bitmask_ = PIN_TO_BITMASK(pin);
  DIRECT_WRITE_LOW(transmit_pin_baseReg_, transmit_pin_bitmask_);
}

void myHardwareSerial::setRX(uint8_t pin)
{
  if (pin != hardware->rx_pins[rx_pin_index_].pin) {
    for (uint8_t rx_pin_new_index = 0; rx_pin_new_index < cnt_rx_pins; rx_pin_new_index++) {
      if (pin == hardware->rx_pins[rx_pin_new_index].pin) {
        // new pin - so lets maybe reset the old pin to INPUT? and then set new pin parameters
        // only change IO pins if done after begin has been called. 
        if ((hardware->ccm_register & hardware->ccm_value)) {
          *(portConfigRegister(hardware->rx_pins[rx_pin_index_].pin)) = 5;

          // now set new pin info.
          *(portControlRegister(hardware->rx_pins[rx_pin_new_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_new_index].pin)) = hardware->rx_pins[rx_pin_new_index].mux_val;
          if (hardware->rx_pins[rx_pin_new_index].select_input_register) {
            *(hardware->rx_pins[rx_pin_new_index].select_input_register) =  hardware->rx_pins[rx_pin_new_index].select_val;   
          }
        }   
        rx_pin_index_ = rx_pin_new_index;
        return;  // done. 
      }
    }
    // If we got to here and did not find a valid pin there.  Maybe see if it is an XBar pin... 
    for (uint8_t i = 0; i < count_pin_to_xbar_info; i++) {
      if (pin_to_xbar_info[i].pin == pin) {
        // So it is an XBAR pin set the XBAR..
        //Serial.printf("ACTS XB(%d), X(%u %u), MUX:%x\n", i, pin_to_xbar_info[i].xbar_in_index, 
        //      hardware->xbar_out_lpuartX_trig_input,  pin_to_xbar_info[i].mux_val);
        CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);
        xbar_connect(pin_to_xbar_info[i].xbar_in_index, hardware->xbar_out_lpuartX_trig_input);

        // We need to update port register to use this as the trigger
        port->PINCFG = LPUART_PINCFG_TRGSEL(1);  // Trigger select as alternate RX

        //  configure the pin. 
        *(portControlRegister(pin)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(3) | IOMUXC_PAD_HYS;;
        *(portConfigRegister(pin)) = pin_to_xbar_info[i].mux_val;
        port->MODIR |= LPUART_MODIR_TXCTSE;
        if (pin_to_xbar_info[i].select_input_register) *(pin_to_xbar_info[i].select_input_register) = pin_to_xbar_info[i].select_val;
        //Serial.printf("SerialX::begin stat:%x ctrl:%x fifo:%x water:%x\n", port->STAT, port->CTRL, port->FIFO, port->WATER );
        //Serial.printf("  PINCFG: %x MODIR: %x\n", port->PINCFG, port->MODIR); 
        return;
      }
    }
  }
}

void myHardwareSerial::setTX(uint8_t pin, bool opendrain)
{
  uint8_t tx_pin_new_index = tx_pin_index_;

  if (pin != hardware->tx_pins[tx_pin_index_].pin) {
    for (tx_pin_new_index = 0; tx_pin_new_index < cnt_tx_pins; tx_pin_new_index++) {
      if (pin == hardware->tx_pins[tx_pin_new_index].pin) {
        break;
      }
    }
    if (tx_pin_new_index == cnt_tx_pins) return;  // not a new valid pid... 
  }

  // turn on or off opendrain mode.
  // new pin - so lets maybe reset the old pin to INPUT? and then set new pin parameters
  if ((hardware->ccm_register & hardware->ccm_value)) {  // only do if we are already active. 
  if (tx_pin_new_index != tx_pin_index_) {
    *(portConfigRegister(hardware->tx_pins[tx_pin_index_].pin)) = 5;
  
    *(portConfigRegister(hardware->tx_pins[tx_pin_new_index].pin)) = hardware->tx_pins[tx_pin_new_index].mux_val;
  }
  }
  // now set new pin info.
  tx_pin_index_ = tx_pin_new_index;
  if (opendrain) 
    *(portControlRegister(pin)) = IOMUXC_PAD_ODE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3);
  else  
    *(portControlRegister(pin)) = IOMUXC_PAD_SRE | IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3);
}


bool myHardwareSerial::attachRts(uint8_t pin)
{
  if (!(hardware->ccm_register & hardware->ccm_value)) return 0;
  if (pin < CORE_NUM_DIGITAL) {
    rts_pin_baseReg_ = PIN_TO_BASEREG(pin);
    rts_pin_bitmask_ = PIN_TO_BITMASK(pin);
    pinMode(pin, OUTPUT);
    rts_assert();
  } else {
    rts_pin_baseReg_ = NULL;
    return 0;
  }
  return 1;
}

bool myHardwareSerial::attachCts(uint8_t pin)
{
  if (!(hardware->ccm_register & hardware->ccm_value)) return false;
  if ((pin != 0xff) && (pin == hardware->cts_pin)) {
    // Setup the IO pin as weak PULL down. 
    *(portControlRegister(pin)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | IOMUXC_PAD_HYS;
    *(portConfigRegister(hardware->cts_pin)) = hardware->cts_mux_val;
    port->MODIR |= LPUART_MODIR_TXCTSE;
    return true;
  } else {
    // See maybe this a pin we can use XBAR for.
    for (uint8_t i = 0; i < count_pin_to_xbar_info; i++) {
      if (pin_to_xbar_info[i].pin == pin) {
        // So it is an XBAR pin set the XBAR..
        //Serial.printf("ACTS XB(%d), X(%u %u), MUX:%x\n", i, pin_to_xbar_info[i].xbar_in_index, 
        //      hardware->xbar_out_lpuartX_trig_input,  pin_to_xbar_info[i].mux_val);
        CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);
        xbar_connect(pin_to_xbar_info[i].xbar_in_index, hardware->xbar_out_lpuartX_trig_input);

        // We need to update port register to use this as the trigger
        port->PINCFG = LPUART_PINCFG_TRGSEL(2);  // Trigger select as alternate CTS pin

        //  configure the pin. 
        *(portControlRegister(pin)) = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_PKE | IOMUXC_PAD_PUE | IOMUXC_PAD_PUS(0) | IOMUXC_PAD_HYS;
        *(portConfigRegister(pin)) = pin_to_xbar_info[i].mux_val;
        if (pin_to_xbar_info[i].select_input_register) *(pin_to_xbar_info[i].select_input_register) = pin_to_xbar_info[i].select_val;
        port->MODIR |= LPUART_MODIR_TXCTSE;

        //Serial.printf("SerialX::begin stat:%x ctrl:%x fifo:%x water:%x\n", port->STAT, port->CTRL, port->FIFO, port->WATER );
        //Serial.printf("  PINCFG: %x MODIR: %x\n", port->PINCFG, port->MODIR); 
        return true;
      }
    }
    // Fell through so not valid pin for this. 
    port->MODIR &= ~LPUART_MODIR_TXCTSE;
    return false;
  }
}

void myHardwareSerial::clear(void)
{
  // BUGBUG:: deal with FIFO
  rx_buffer_head_ = rx_buffer_tail_;
  if (rts_pin_baseReg_) rts_assert();
}

int myHardwareSerial::availableForWrite(void)
{
  uint32_t head, tail;

  head = tx_buffer_head_;
  tail = tx_buffer_tail_;
  if (head >= tail) return tx_buffer_total_size_ - 1 - head + tail;
  return tail - head - 1;
}




int myHardwareSerial::available(void)
{
  uint32_t head, tail;

  // WATER> 0 so IDLE involved may want to check if port has already has RX data to retrieve
  __disable_irq();
  head = rx_buffer_head_;
  tail = rx_buffer_tail_;
  int avail;
  if (head >= tail) avail = head - tail;
  else avail = rx_buffer_total_size_ + head - tail; 
  avail += (port->WATER >> 24) & 0x7;
  __enable_irq();
  return avail;
}

void myHardwareSerial::addMemoryForRead(void *buffer, size_t length)
{
  rx_buffer_storage_ = (BUFTYPE*)buffer;
  if (buffer) {
    rx_buffer_total_size_ = rx_buffer_size_ + length;
  } else {
    rx_buffer_total_size_ = rx_buffer_size_;
  } 

  // Make sure we don't end up indexing into no mans land. 
  rx_buffer_head_ = 0;
  rx_buffer_tail_ = 0;
  rts_low_watermark_ = rx_buffer_total_size_ - hardware->rts_low_watermark;
  rts_high_watermark_ = rx_buffer_total_size_ - hardware->rts_high_watermark;
}

void myHardwareSerial::addMemoryForWrite(void *buffer, size_t length)
{
  tx_buffer_storage_ = (BUFTYPE*)buffer;
  if (buffer) {
    tx_buffer_total_size_ = tx_buffer_size_ + length;
  } else {
    tx_buffer_total_size_ = tx_buffer_size_;
  } 
  // Make sure we don't end up indexing into no mans land. 
  tx_buffer_head_ = 0;
  tx_buffer_tail_ = 0;
}

int myHardwareSerial::peek(void)
{
  uint32_t head, tail;

  head = rx_buffer_head_;
  tail = rx_buffer_tail_;
  if (head == tail) {
    __disable_irq();
    head = rx_buffer_head_;  // reread head to make sure no ISR happened
    if (head == tail) {
      // Still empty Now check for stuff in FIFO Queue.
      int c = -1; // assume nothing to return
      if (port->WATER & 0x7000000) {
        c = port->DATA & 0x3ff;   // Use only up to 10 bits of data
        // But we don't want to throw it away...
        // since queue is empty, just going to reset to front of queue...
        rx_buffer_head_ = 1;
        rx_buffer_tail_ = 0; 
        rx_buffer_[1] = c;
      }
      __enable_irq();
      return c;
    }
    __enable_irq();

  } 
  if (++tail >= rx_buffer_total_size_) tail = 0;
  if (tail < rx_buffer_size_) {
    return rx_buffer_[tail];
  } else {
    return rx_buffer_storage_[tail-rx_buffer_size_];
  }
}

int myHardwareSerial::read(void)
{
  uint32_t head, tail;
  int c;

  head = rx_buffer_head_;
  tail = rx_buffer_tail_;
  if (head == tail) {
    __disable_irq();
    head = rx_buffer_head_;  // reread head to make sure no ISR happened
    if (head == tail) {
      // Still empty Now check for stuff in FIFO Queue.
      c = -1; // assume nothing to return
      if (port->WATER & 0x7000000) {
        c = port->DATA & 0x3ff;   // Use only up to 10 bits of data
      }
      __enable_irq();
      return c;
    }
    __enable_irq();

  }
  if (++tail >= rx_buffer_total_size_) tail = 0;
  if (tail < rx_buffer_size_) {
    c = rx_buffer_[tail];
  } else {
    c = rx_buffer_storage_[tail-rx_buffer_size_];
  }
  rx_buffer_tail_ = tail;
  if (rts_pin_baseReg_) {
    uint32_t avail;
    if (head >= tail) avail = head - tail;
    else avail = rx_buffer_total_size_ + head - tail;

    if (avail <= rts_low_watermark_) rts_assert();
  }
  return c;
} 

void myHardwareSerial::flush(void)
{
  while (transmitting_) yield(); // wait
}

size_t myHardwareSerial::write(uint8_t c)
{
  // use the 9 bit version (maybe 10 bit) do do the work. 
  return write9bit(c);
}

size_t myHardwareSerial::write9bit(uint32_t c)
{
  uint32_t head, n;
  //digitalWrite(3, HIGH);
  //digitalWrite(5, HIGH);
  if (transmit_pin_baseReg_) DIRECT_WRITE_HIGH(transmit_pin_baseReg_, transmit_pin_bitmask_);
  if(half_duplex_mode_) {   
    __disable_irq();
      port->CTRL |= LPUART_CTRL_TXDIR;
    __enable_irq();
    //digitalWriteFast(2, HIGH);
  }

  head = tx_buffer_head_;
  if (++head >= tx_buffer_total_size_) head = 0;
  while (tx_buffer_tail_ == head) {
    int priority = my_nvic_execution_priority();
    if (priority <= hardware->irq_priority) {
      if ((port->STAT & LPUART_STAT_TDRE)) {
        uint32_t tail = tx_buffer_tail_;
        if (++tail >= tx_buffer_total_size_) tail = 0;
        if (tail < tx_buffer_size_) {
          n = tx_buffer_[tail];
        } else {
          n = tx_buffer_storage_[tail-tx_buffer_size_];
        }
        port->DATA  = n;
        tx_buffer_tail_ = tail;
      }
    } else if (priority >= 256) 
    {
      yield(); // wait
    } 
  }
  //digitalWrite(5, LOW);
  //Serial.printf("WR %x %d %d %d %x %x\n", c, head, tx_buffer_size_,  tx_buffer_total_size_, (uint32_t)tx_buffer_, (uint32_t)tx_buffer_storage_);
  if (head < tx_buffer_size_) {
    tx_buffer_[head] = c;
  } else {
    tx_buffer_storage_[head - tx_buffer_size_] = c;
  }
  __disable_irq();
  transmitting_ = 1;
  tx_buffer_head_ = head;
  port->CTRL |= LPUART_CTRL_TIE; // (may need to handle this issue)BITBAND_SET_BIT(LPUART0_CTRL, TIE_BIT);
  __enable_irq();
  //digitalWrite(3, LOW);
  return 1;
}

void myHardwareSerial::IRQHandler() 
{
  //digitalWrite(4, HIGH);
  uint32_t head, tail, n;
  uint32_t ctrl;

  // See if we have stuff to read in.
  // Todo - Check idle. 
  if (port->STAT & (LPUART_STAT_RDRF | LPUART_STAT_IDLE)) {
    // See how many bytes or pending. 
    //digitalWrite(5, HIGH);
    uint8_t avail = (port->WATER >> 24) & 0x7;
    if (avail) {
      uint32_t newhead;
      head = rx_buffer_head_;
      tail = rx_buffer_tail_;
      do {
        n = port->DATA & 0x3ff;   // Use only up to 10 bits of data

/*********************************************************************************/
/*      Example code to do something inside the interrupt for each byte received */
/*********************************************************************************/
        digitalToggle(13); // just toggle the orange LED - simplest possible example!
/*********************************************************************************/
        
        newhead = head + 1;

        if (newhead >= rx_buffer_total_size_) newhead = 0;
        if (newhead != rx_buffer_tail_) {
          head = newhead;
          if (newhead < rx_buffer_size_) {
            rx_buffer_[head] = n;
          } else {
            rx_buffer_storage_[head-rx_buffer_size_] = n;
          }
        }
      } while (--avail > 0) ;
      rx_buffer_head_ = head;
      if (rts_pin_baseReg_) {
        uint32_t avail;
        if (head >= tail) avail = head - tail;
        else avail = rx_buffer_total_size_ + head - tail;
        if (avail >= rts_high_watermark_) rts_deassert();
      }
    }

    // If it was an idle status clear the idle
    if (port->STAT & LPUART_STAT_IDLE) {
      port->STAT |= LPUART_STAT_IDLE; // writing a 1 to idle should clear it. 
    }
    //digitalWrite(5, LOW);

  }

  // See if we are transmitting and room in buffer. 
  ctrl = port->CTRL;
  if ((ctrl & LPUART_CTRL_TIE) && (port->STAT & LPUART_STAT_TDRE))
  {
    //digitalWrite(3, HIGH);

    head = tx_buffer_head_;
    tail = tx_buffer_tail_;
    do {
      if (head == tail) break;
      if (++tail >= tx_buffer_total_size_) tail = 0;
      if (tail < tx_buffer_size_) {
        n = tx_buffer_[tail];
      } else {
        n = tx_buffer_storage_[tail-tx_buffer_size_];
      }
      port->DATA = n;
    } while (((port->WATER >> 8) & 0x7) < 4);   // need to computer properly
    tx_buffer_tail_ = tail;
    if (head == tail) {
      port->CTRL &= ~LPUART_CTRL_TIE; 
        port->CTRL |= LPUART_CTRL_TCIE; // Actually wondering if we can just leave this one on...
    }
    //digitalWrite(3, LOW);
  }

  if ((ctrl & LPUART_CTRL_TCIE) && (port->STAT & LPUART_STAT_TC))
  {
    transmitting_ = 0;
    if (transmit_pin_baseReg_) DIRECT_WRITE_LOW(transmit_pin_baseReg_, transmit_pin_bitmask_);
    if(half_duplex_mode_) {   
      __disable_irq();
        port->CTRL &= ~LPUART_CTRL_TXDIR;
      __enable_irq();
      //digitalWriteFast(2, LOW);
    }

    port->CTRL &= ~LPUART_CTRL_TCIE;
  }
  //digitalWrite(4, LOW);
}


void myHardwareSerial::addToSerialEventsList() {
  for (uint8_t i = 0; i < s_count_serials_with_serial_events; i++) {
    if (s_serials_with_serial_events[i] == this) return; // already in the list.
  }
  s_serials_with_serial_events[s_count_serials_with_serial_events++] = this;
  yield_active_check_flags |= YIELD_CHECK_HARDWARE_SERIAL;
}

/*
const pin_to_xbar_info_t PROGMEM pin_to_xbar_info[] = {
  {0,  17, 1, &IOMUXC_XBAR1_IN17_SELECT_INPUT, 0x1},
  {1,  16, 1, nullptr, 0},
  {2,   6, 3, &IOMUXC_XBAR1_IN06_SELECT_INPUT, 0x0},
  {3,   7, 3, &IOMUXC_XBAR1_IN07_SELECT_INPUT, 0x0},
  {4,   8, 3, &IOMUXC_XBAR1_IN08_SELECT_INPUT, 0x0},
  {5,  17, 3, &IOMUXC_XBAR1_IN17_SELECT_INPUT, 0x0},
  {7,  15, 1, nullptr, 0 },
  {8,  14, 1, nullptr, 0},
  {30, 23, 1, &IOMUXC_XBAR1_IN23_SELECT_INPUT, 0x0},
  {31, 22, 1, &IOMUXC_XBAR1_IN22_SELECT_INPUT, 0x0},
  {32, 10, 1, nullptr, 0},
  {33,  9, 3, &IOMUXC_XBAR1_IN09_SELECT_INPUT, 0x0},

#ifdef ARDUINO_TEENSY41
  {36, 16, 1, nullptr, 0},
  {37, 17, 1, &IOMUXC_XBAR1_IN17_SELECT_INPUT, 0x3},
  {42,  7, 3, &IOMUXC_XBAR1_IN07_SELECT_INPUT, 0x1},
  {43,  6, 3, &IOMUXC_XBAR1_IN06_SELECT_INPUT, 0x1},
  {44,  5, 3, &IOMUXC_XBAR1_IN05_SELECT_INPUT, 0x1},
  {45,  4, 3, &IOMUXC_XBAR1_IN04_SELECT_INPUT, 0x1},
  {46,  9, 3, &IOMUXC_XBAR1_IN09_SELECT_INPUT, 0x1},
  {47,  8, 3, &IOMUXC_XBAR1_IN08_SELECT_INPUT, 0x1}
#elif defined(ARDUINO_TEENSY_MICROMOD)
  {34,  7, 3, &IOMUXC_XBAR1_IN07_SELECT_INPUT, 0x1},
  {35,  6, 3, &IOMUXC_XBAR1_IN06_SELECT_INPUT, 0x1},
  {36,  5, 3, &IOMUXC_XBAR1_IN05_SELECT_INPUT, 0x1},
  {37,  4, 3, &IOMUXC_XBAR1_IN04_SELECT_INPUT, 0x1},
  {38,  8, 3, &IOMUXC_XBAR1_IN08_SELECT_INPUT, 0x1},
  {39,  9, 3, &IOMUXC_XBAR1_IN09_SELECT_INPUT, 0x1}
#else 
  {34,  7, 3, &IOMUXC_XBAR1_IN07_SELECT_INPUT, 0x1},
  {35,  6, 3, &IOMUXC_XBAR1_IN06_SELECT_INPUT, 0x1},
  {36,  5, 3, &IOMUXC_XBAR1_IN05_SELECT_INPUT, 0x1},
  {37,  4, 3, &IOMUXC_XBAR1_IN04_SELECT_INPUT, 0x1},
  {38,  9, 3, &IOMUXC_XBAR1_IN09_SELECT_INPUT, 0x1},
  {39,  8, 3, &IOMUXC_XBAR1_IN08_SELECT_INPUT, 0x1}
#endif
};
*/

//const uint8_t PROGMEM count_pin_to_xbar_info = sizeof(pin_to_xbar_info)/sizeof(pin_to_xbar_info[0]);





// copied from HardwareSerial1.cpp

#ifndef SERIAL1_TX_BUFFER_SIZE
#define SERIAL1_TX_BUFFER_SIZE     64 // number of outgoing bytes to buffer
#endif
#ifndef SERIAL1_RX_BUFFER_SIZE
#define SERIAL1_RX_BUFFER_SIZE     64 // number of incoming bytes to buffer
#endif
#define IRQ_PRIORITY  64  // 0 = highest priority, 255 = lowest


// Serial1
static BUFTYPE tx_buffer1[SERIAL1_TX_BUFFER_SIZE];
static BUFTYPE rx_buffer1[SERIAL1_RX_BUFFER_SIZE];

const myHardwareSerial::hardware_t UART6_Hardware = {
  0, IRQ_LPUART6, &IRQHandler_Serial1, 
  &serialEvent1,
  CCM_CCGR3, CCM_CCGR3_LPUART6(CCM_CCGR_ON),
  #if defined(ARDUINO_TEENSY41)
  {{0,2, &IOMUXC_LPUART6_RX_SELECT_INPUT, 1}, {52, 2, &IOMUXC_LPUART6_RX_SELECT_INPUT, 0}},
  {{1,2, &IOMUXC_LPUART6_TX_SELECT_INPUT, 1}, {53, 2, &IOMUXC_LPUART6_TX_SELECT_INPUT, 0}},
  #else
  {{0,2, &IOMUXC_LPUART6_RX_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
  {{1,2, &IOMUXC_LPUART6_TX_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
  #endif
  0xff, // No CTS pin
  0, // No CTS
  IRQ_PRIORITY, 38, 24, // IRQ, rts_low_watermark, rts_high_watermark
  XBARA1_OUT_LPUART6_TRG_INPUT  // XBar Tigger 
};
myHardwareSerial mySerial1(&IMXRT_LPUART6, &UART6_Hardware, tx_buffer1, SERIAL1_TX_BUFFER_SIZE,
  rx_buffer1,  SERIAL1_RX_BUFFER_SIZE);

void IRQHandler_Serial1()
{
  mySerial1.IRQHandler();
}


void setup() {
  pinMode(13, HIGH);
  digitalWrite(13, LOW);
  mySerial1.begin(115200);
}

void loop() {
  // no polling - the interrupt code will run for each byte receive to toggle the LED
}

I created this program to try to help you. I also did it to see how difficult this process really is. Indeed just copying the code initially gives a lot of errors because of the global scope items conflicting with the copy of HardwareSerial in the core library. But the compiler error messages say the names of the things which conflict, so for an experieced C / C++ programmer it's pretty simple to just find all the place the conflicting name appear and prepend "my" to make the copied code have unique global scope names. But since I've done it and posted the full code, you won't have to worry about that part.

Again, a word of warning, something that seems simple at first but is among the most difficult and troublesome of programming problems is race conditions between interrupts and main program, or even among different interrupt if using nested priority levels as is done with Teensy 4. But perhaps this is not a problem for you, since you have done it before on PIC microcontrollers?
 
I ran this on a Teensy 4.1 with a FTDI serial cable connected to RX1 (pin 0) and TX1 (pin 1). When I type into a terminal emulator on my PC, the LED indeed does turn on and off with each keystroke.

Thank you very much for your efforts. This is a very good start to try something with it
 
Back
Top