SAMD51 ARM Serial1 Read in an ISR - Better Approaches?

Using a SAMD51 Grand Central chipset and trying to determine how best to send a serial command over Serial1 such that it is done effectively in real time. Or rather, have the Arduino always able to pass along the message and get a response back at least as the Serial1 here is going to RS485 which is then going to an outside set of devices, which if sent the right ID as part of the command being sent will respond to the single Arduino that is sending.

The point, I think, is to be able to use Serial Monitor to be able to send commands to multiple devices downstream (through RS485) and have it be able to do so just about anywhere in the code that the current Arduino is running. Even if it is at a wait or if block or so on as right now that disables it from being able to send any serial data out and therefore get a response back as the only way messages are sent here is either TX commands are sent out by the sketch directly or they are sent out manually through the serial monitor. Downstream RS485 devices are only able to respond over RS485 with a single RX command to that initial TX, assuming that the ID number in the message lines up with that specific device number. So messages sent do ID 1 are only responded to by that single device and ID 2 would only be responded to by the device with ID 2 and so on.

I think that we currently have the RX set as a real time item then since that could come in at any time but the current method of using Serial.read means that anytime something on the main Arduino is in a while loop or anything else, it effectively turns off the serial monitor completely and nothing you send into it is actually sent! It actually winds up freezing the main Arduino program after 3 times (including no longer updating the serial monitor), requiring you to move the USB cable out in order to shut the software down without force closing it. At least the main Arduino hardware sketch keeps running.

Anyway, this is the code currently being used and it very much seems like the wrong approach here given what is trying to be done. Can someone please point me in the right direction for how to approach this such that the 120 MHz SAMD51 Arduino can send and receive UART serial commands through, well, the PC serial monitor in the Arduino software which will be passed on to RS485 and then listened to by other upstream hardware which will send back a RX command to the Arduino if it is the correct serial ID in the command is being sent.

if(Serial.available()){
    comm.consoleComm(Serial.read());
}

From what I am starting to understand a bit better, ISR doesn't sound quite like the typical (or correct) approach here exactly and the idea here is not to use ISR to send serial commands exactly anyway since that's not how it is meant to work but the entire point of this is to have some kind of conduit to be able to real time send and receive serial commands through the serial monitor irrespective of what the main sketch is doing. Still learning here and reading a fair bit but both ARM and these various levels of interrupts are both much more flexible but also much more complicated as well!

I wanted to also add what I have found about this so far as well.

Has some good thoughts about this and talks about it some and I agree that you don't want to have things that freeze your code in interrupts as that's poor programming but there seem to be methods of approaching this that should work well?

If the interrupt is caused by Data Ready on the UART (serial port input), then of course you can use a dedicated ISR to read the character and store it somewhere safe.

Not sure I am completely following this suggestion made there and I have also come across two ISR type libraries that seem helpful as well but none of them seem to be designed to do this exactly?

It is also worth referencing this post from MartinL as well since the SAMD51 method of approaching this is different.

Hi razor101,

The SERCOM peripheral on the SAMD51 and SAMD21 generates a number of interrupts, such as DRE (DataRegister Empty), TXC (Transmit Complete) and RXC (Receive Complete), to name just three of the most commonly used.

The SAMD21's SERCOMx module has only a single Interrupt Service Routine (ISR) called SERCOMx_Handler(), therefore within the ISR it's necessary to test each of the interrupt flags to find which interrupt has occurred when the handler function is called.

The SAMD51's SERCOMx module on the other hand has separate ISR's for each interrupt, with the ISR number relating to the position of the interrupt in the Interrupt Flag (INTFLAG) register, for example SERCOM2_0_Handler() is called for DRE, SERCOM2_1_Handler() is called for TXC and SERCOM2_2_Handler() for RXC, etc.... Having separate ISRs for each interrupt flag removes the need test which interrupt has occurred within the handler function, thereby providing the SAMD51 with a small speed optimization over the SAMD21.

It is likely worth pointing out that the RS485 here is two wire since it is isolated. So there is a TXC and a RXC pin but there isn't a physical DRE pin?

I have come across updated Serial Input Basics forum post as well which does a great job for easier things but doesn't really address serial data being sent out and doesn't really address the newer ARM SAMD51 methods of serial data handling either. In this case, the serial data is important that it be sent out quickly at any point in the sketch and more importantly completely since it is needing to be more of a pass through type of communication method when it is sent through the serial monitor because you are ultimately sending these commands to a downstream device over RS485, rather than to the Arduino.

Hello arm_isr_serial,
Welcome.

I have not read your entire post and question because there is one bit that immediately stood out as needing addressing, and, to be honest, it's far too long to read and answer.

Using a SAMD51 Grand Central chipset and trying to determine how best to send a serial command over Serial1 such that it is done effectively in real time.

The SAMD processors are seriously powerful bits of kit, serial is really, really slow in comparison and doing serial in real time should hardly wake a SAMD processor from it's slumber. If you are having problems with this then this:

that anytime something on the main Arduino is in a while loop or anything else

Is a serious red flag. The processor should not be in a while loop for an amount of time, certainly not long enough to interfere with a slow serial port.

I infer from this that you have while loops, delays and other stuff slowing down your code. If this is the case you need to learn to write collaborative non-blocking code. The place to start is:

http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html

Demonstration for several things at the same time

Maybe if I read your whole 3 section question I'd see something else, but I'll leave that pleasure for someone else.

Good luck.

Thank you for your response and links. I will go through them here more here shortly since they seem very helpful.

What is the best way to do things like "wait for user to proceed" in terms of don't start any motion commands until a start button is pressed for example? The void loop() can still be running but right now it is being setup more linearly where it waits for something to happen before proceeding (in this case a start button).

Code can certainly be running continually but are you saying it is better to use a start button more as an interrupt than a pause type of approach there as it is currently being done? I do see examples of hardware interrupts being used for things like button presses.

Many, many commands and many cycles are being sent and completed during the time a single Serial1 command is being sent. Hardware serial is very slow by comparison. I am mostly just trying to figure out how to have it forward commands that come in the serial monitor downstream and then display the rx command that is sent back also in serial monitor, even if the Arduino SAMD51 is otherwise occupied. Meaning waiting for a user to do something or otherwise "paused", even if not truly paused if that makes sense.

Code can certainly be running continually but are you saying it is better to use a start button more as an interrupt than a pause type of approach there as it is currently being done? I do see examples of hardware interrupts being used for things like button presses.

No, absolutely not. Interrupts are not the right tools for button presses. You need to read and follow the tutorials and learn how to write multitasking code. Reading buttons receiving serial data and a whole load of other things are well within the capabilities of far less powerful processors than the SAMD family,all without interrupts. You just need to write the code correctly. I am not going to give you personal tuition, but I and lots of other people on here will help you if you do the exercises, try to apply them and get stuck.

Meaning waiting for a user to do something or otherwise "paused", even if not truly paused if that makes sense.

You don't have to stop while waiting for the user. Follow the tutorials. This is a whole different approach to the one you are probably used to.

Good luck

You need to stop programming your Arduino solving tasks the way you would solve them. Focusing on one thing and finishing before you move on. Unfortunately, that is how many examples show you one feature of a library or functions. This allows them to be simple but often shows beginners bad practices like using delay().

For a real application you want to make sure loop runs as often as possible. Every task should only handle a little bit at a time and then move on. Never stop, do not use delays or while loops.

As PerryBebbington said, if you get stuck with the exercises we are here to help. Post your code (use code tags).

arm_isr_serial:
What is the best way to do things like "wait for user to proceed" in terms of don't start any motion commands until a start button is pressed for example? The void loop() can still be running but right now it is being setup more linearly where it waits for something to happen before proceeding (in this case a start button).

Consider the following sketch written for an Uno.

The code continuously blinks an LED, toggling its state at 125mS intervals. It also waits for a user to press a switch connected to pin to and, when pressed, sends a brief message to the console.

Notice how the code doesn't linger and block waiting for the switch press. Every 50mS it takes a look at the switch to see if it has changed state. If it hasn't, it goes back to process other things, like blinking the LED.

When a change of state is seen, it just sets a flag and logic in loop() acts on that flag to send the serial message.

This is one way to "multitask" on this platform: Don't use blocking logic (e.g. while( button not pressed), don't use blocking function calls (e.g. delay(...). Leverage the speed of the device to quickly peek in on something while waiting for an event and act only if it's ready.

const uint8_t
    pinLED = LED_BUILTIN,
    pinInput = 2;

typedef struct
{
    bool        state;
    uint8_t     pin;
    uint32_t    timeLED;
    uint32_t    timeDelay;
}LEDCtl_t;

LEDCtl_t
    LEDCtl;

typedef struct
{
    bool        evtFlag;
    bool        lastState;
    uint8_t     pin;
    uint32_t    timeSwitch;
    uint32_t    timeSwRead;
}Switch_t;

Switch_t
    Switch;
    
void setup() 
{
    Serial.begin(115200);
    
    pinMode( pinLED, OUTPUT );
    pinMode( pinInput, INPUT_PULLUP );
    
    LEDCtl.state = false;
    LEDCtl.pin = pinLED;
    LEDCtl.timeDelay = 125ul;
    LEDCtl.timeLED = 0ul;

    Switch.evtFlag = false;
    Switch.pin = pinInput;
    Switch.lastState = digitalRead( Switch.pin );
    Switch.timeSwitch = 0ul;
    Switch.timeSwRead = 50ul;
        
}//setup

void loop() 
{
    BlinkLED();
    ReadSwitch();
    if( Switch.evtFlag == true )
    {
        Serial.println( "Do something" );
        Switch.evtFlag = false;
    }//if

}//loop

void BlinkLED( void )
{
    uint32_t
        timeNow = millis();

    if( (timeNow - LEDCtl.timeLED) >= LEDCtl.timeDelay )
    {
        LEDCtl.state ^= true;
        digitalWrite( LEDCtl.pin, LEDCtl.state );
        LEDCtl.timeLED = timeNow;
        
    }//if
            
}//BlinkLED

void ReadSwitch( void )
{
    uint32_t
        timeNow = millis();

    if( (timeNow - Switch.timeSwitch) >= Switch.timeSwRead )
    {
        Switch.timeSwitch = timeNow;
        
        uint8_t swNow = digitalRead( Switch.pin );
        if( swNow != Switch.lastState )
        {
            Switch.lastState = swNow;
            if( swNow == LOW )
            {
                Switch.evtFlag = true;
                
            }//if
            
        }//if
        
    }//if
    
}//ReadSwitch

These suggestions are very helpful, thank you. I will look further into this as I am newer to coding in general and these seem like really good points and good coding practices.

It is still somewhat unclear how best to approach this problem though of how to basically pass off serial TX and RX commands in real time to downstream devices when sent by the operator through the Arduino serial monitor.

Are there any broad suggestions there on what to look for by chance to solve that issue? How to always have the Arduino sending out and getting back commands sent to Serial1 in this case which will send anything the user types into the Arduino Serial Monitor downstream and into the RS485 shield so the messages are correctly transmitted no matter where in the code the Arduino is at the time.

You said:

The point, I think, is to be able to use Serial Monitor to be able to send commands to multiple devices downstream (through RS485) [b]and have it be able to do so just about anywhere in the code that the current Arduino is running.[/b]

The code should never be “far” - in temporal terms – from being able to handle higher-priority tasks so this shouldn’t present an issue.

You’ve got to design the code so that the processor runs in a model similar to a star topology, with control passing from the hub to the satellite nodes for only small fragments of time before returning to the hub. Within the hub is running time-critical stuff that will be run/checked frequently while the satellites represent stuff that only needs periodic attention.

So your serial logic might run in the “hub” and be serviced frequently while things like, say, checking a pushbutton switch could be placed in “satellite” code that runs once every 50mS. If you look at my LED code example you could replace the LED blinking code with serial port service that checks serial statuses every time it’s called from loop() and compare that to the code waiting for user input every 50mS from the user button.

As Perry notes, you have a super-fast core in that SAMD51 so this should be trivial, even in bare-metal (non-OS) code.

Do you have a minimally viable example of your code that demonstrates the problem you’re facing?

The main problem I am facing is that I have this RS485 topology:

Arduino (Master) ---> RS485 Shield   --->  Device 1 Slave (RS485)
                                      |->  Device 2 Slave (RS485)
                                      |->  Device 3 Slave (RS485)

I want to be able to ensure that while the main Arduino loop is running, that it is also able to send RS485 commands out and then hear back from each downstream RS485 device in real (enough) time.

Commands to slave 01 might be in the format of something like this:
TX: 01 FF FF 1A 50 20
RX: 01 FF FF 00 00 50

With the 01 being what you send if you want to send a message to the slave device identifying as 01.
TX: 02 FF FF 1A 50 20
RX: 02 FF FF 00 00 50

Being what is sent to and back from device 02 and so on.

So basically anywhere in the void loop() that the Arduino is at any given time, it needs to make sure it is able to send a complete message and then hear back from a device. The good news is there is only one master, the Arduino, so only one set of messages are really being sent at a time (the slaves can only send out a single message in direct response to a single message from the master) but the master is able to send a message to any of the three slaves in this case by sending to a unique ID as part of the slave message. They slaves all get every message but only respond to ones that contain the ID for that slave.

So it needs to be a core thing in that sending TX commands to slaves needs to happen as quickly as serial at 115,200 or so bits per second can manage. Glacially slow in the world of a 120 MHz SAMD51 chip but if the Arduino is set to basically pause for a button press or "pause" to wait for hardware movement or delay, it needs to still be able to send out TX commands and listen to the RX response.

I think that's the part that isn't clear and why interrupts seemed to be useful? Maybe there are better ways of doing this though and if you know of what things might be worth looking into, please let me know what else to explore that might be a better fit. Thank you!

arm_isr_serial:
The main problem I am facing is that I have this RS485 topology:

Arduino (Master) ---> RS485 Shield   --->  Device 1 Slave (RS485)

|->  Device 2 Slave (RS485)
                                      |->  Device 3 Slave (RS485)




I want to be able to ensure that while the main Arduino loop is running, that it is also able to send RS485 commands out and then hear back from each downstream RS485 device in real (enough) time.

Commands to slave 01 might be in the format of something like this:
TX: 01 FF FF 1A 50 20
RX: 01 FF FF 00 00 50

With the 01 being what you send if you want to send a message to the slave device identifying as 01.
TX: 02 FF FF 1A 50 20
RX: 02 FF FF 00 00 50

Being what is sent to and back from device 02 and so on.

So basically anywhere in the void loop() that the Arduino is at any given time, it needs to make sure it is able to send a complete message and then hear back from a device. The good news is there is only one master, the Arduino, so only one set of messages are really being sent at a time (the slaves can only send out a single message in direct response to a single message from the master) but the master is able to send a message to any of the three slaves in this case by sending to a unique ID as part of the slave message. They slaves all get every message but only respond to ones that contain the ID for that slave.

So it needs to be a core thing in that sending TX commands to slaves needs to happen as quickly as serial at 115,200 or so bits per second can manage. Glacially slow in the world of a 120 MHz SAMD51 chip but if the Arduino is set to basically pause for a button press or "pause" to wait for hardware movement or delay, it needs to still be able to send out TX commands and listen to the RX response.

I think that's the part that isn't clear and why interrupts seemed to be useful? Maybe there are better ways of doing this though and if you know of what things might be worth looking into, please let me know what else to explore that might be a better fit. Thank you!

Do you have a program/sketch/project you can post for additional reference?

Yes, but it is hundreds of lines and contains a ton of code to handle things that are irrelevant here like sanitizing RS485 inputs or handling movement commands.

Not sure it is taking the best approach to things and I feel like I am already posting too much already. Rather than giving someone a book and asking them to locate the single relevant page, I am trying to post just the relevant page as a best practice.

Right now the literal code that seems relevant is the main sketch has:

if(Serial.available()){
    comm.consoleComm(Serial.read());
}

And then inside the communication header:

void COMM::consoleComm(char _data){
  if(_data=='\n'){
    directCommand(console);
    console = String();
  }else{
    console += String(_data);
  }
}

I remain unclear that this is even close to the best way to handle this. I am happy to post more code if that would be helpful though as well but the main problem is how to approach finding out the right code or approach to use to be sure that serial commands are able to be passed along from the serial monitor to downstream RS485 devices as soon as they are sent to the serial monitor, irregardless of what the main sketch might be doing.

I came across this which speaks to SAMD21 in particular but seems very relevant here for this application.

Arduino M0 PRO: interrupt driven serial interface

It's literally talking about interrupt driven serial and how there are specific best practices and existing methods of doing so with the SAMD21 hardware, specifically meant for serial communication "interrupts".

So somewhat more unclear than before now, because interrupts were something that several people here have said to avoid but serial data could be sent and needs to be able to be sent (and then responded to by downstream controller units before being rx: back to the Arduino serial monitor at literally any time.

So basically anywhere in the void loop() that the Arduino is at any given time, it needs to make sure it is able to send a complete message and then hear back from a device.

It take an absolute AGE in processor terms, especially one as powerful as the SAMD range, to send a message over serial and get the reply. You don't have you code sitting waiting for the reply, your code should be off checking other things, then come back to see if anything has come back yet. This will probably happen thousands of times before the complete reply is received.

Not sure it is taking the best approach to things and I feel like I am already posting too much already. Rather than giving someone a book and asking them to locate the single relevant page, I am trying to post just the relevant page as a best practice.

While I appreciate your wish not to post vast quantities of code this is not just a case of fixing your serial code in isolation, it is a case of fixing ALL your code so as to allow the bit that handles the serial interface to do so easily and in a timely manner as you desire. I infer from what you have said that your code is as we suspect; full of delays and while loops and other stuff that makes it get stuck and unable to handle the serial port. That being the case there is no point trying to fix the serial code you have to re-write the whole thing based on the principals I and others have explained. Once you do that the serial problem will go away. At the moment you are asking how to replace a single cracked tile in a rotten roof when what you actually need is a new roof.

I am here to give help and advice, not to tell you how to make your project. I've given my advice, it's up to you whether you take it. If you make a serious attempt to write non blocking code and get stuck with some part of it then I and others will be happy to help, but I not interested in a debate about whether or not you can fix your problems with interrupts. Maybe you can, maybe you can't, but either way I am not going to give you advice on how to do something in a way I know to be fundamentally flawed.

I will end with this illustration that I use occasionally to show the difference between blocking and non-blocking code:

You wouldn’t expect this when you go into a restaurant:
A waiter meets you at the door, takes you to a table, gives you a menu then waits by your table while you decide what to order. The waiter takes your order, goes to the kitchen and waits there while the chef cooks your food. However, as the staff in this restaurant only ever deal with one customer at a time your waiter has to wait with other waiters while the chef cooks 5 other meals before starting on yours. When the food is eventually ready the waiter brings it to your table then waits by your table while you eat it. When you’ve finished eating the waiter takes your plates away and returns to ask if you want anything else. This continues until you leave. No one else gets served. This is how your code is working at the moment.
I’m not going to describe what really happens in a restaurant as you already know. A waiter uses exactly the same system as a state machine to serve people when they need serving and check to see who needs serving next between dealing with customers. You can build functions for the different tasks a waiter does such as:

void takeOrder();
void bringFoodToTable();

You call these from loop(); While in loop the waiter checks to see if any tables need attention, and if they do s/he goes to find out what they need. If not, then s/he keeps checking until someone needs something. Computer code should be written along the same principals.
See Demonstration for several things at the same time for how to implement a state machine in software rather than in a restaurant.
Also look at Using millis for timing, which is also relevant as frequently millis timing and a state machine are used together.

arm_isr_serial:
Yes, but it is hundreds of lines and contains a ton of code to handle things that are irrelevant here like sanitizing RS485 inputs or handling movement commands.

If you're having trouble with doing multiple things at once, such as generating and responding to serial messages in a timely manner, the problem likely lies somewhere in that "ton of code" you deem irrelevant and won't show.

Good luck & I hope you find your answer.

I don't want to sound like I am not appreciative of your thoughts and explanations. I completely get your explanation about the server and that makes perfect sense.

What I need is a second entrance so customers who need to order food in a priority fashion can bypass going to sit down and put in an order and eat inside the restaurant. Basically adding a drive by food pickup.

I came across this VERY helpful resource that goes over serial interrupts over SAMD21 and shows many examples of existing code for this exact application here, which is more along the lines of really just listening for serial commands as an interrupt such that the serial monitor can hear anything sent to it even while it is doing other things, such as "paused" waiting for motion.

I am going to post much of it here in case it is helpful as Luca did an incredible job of going over and providing examples. I understand that the Serial on SAMD51 is even more complex with more options but this should hopefully be a really helpful starting point upon which to be able to add this functionality. I agree that the code needs to be revised as well but this core functionality seems to be very important with regards to being able to quickly send serial data tx and rx commands and while I do hear what you are saying and I do very much appreciate your explanations for the general code structure, I still do need some method of having serial commands being treated as interrupts such that they can be passed on at any time between the Arduino and slave devices as a secondary feature of the code and it seems like ARM has built this functionality into the SAMD21 and SAMD51 chipsets by design. They just do a terrible job of sharing that code, even if it is technically in the nearly 2000 page datasheet PDF.

void USART_send_wait(char data)
{
  while (!SERCOM5->USART.INTFLAG.bit.DRE)       // wait for the transmit buffer to be empty
    ;
  SERCOM5->USART.DATA.reg = data;
}

char USART_receive_wait(void)
{
  while (!SERCOM5->USART.INTFLAG.bit.RXC)       // wait for a character in the receive buffer
    ;
  return SERCOM5->USART.DATA.reg;
}

void USART_write_wait(const char *string)
{
  while (*string)
    USART_send(*string++);
}
void USART_writeln_wait(const char *string)
{
  USART_write(string);
  USART_write("\r\n");
}

void USART_read_wait(char *string)
{
  while ((*string = USART_receive()) != '\n')
    string++;
  *string = '\0';
}

These low level functions receive or transmit a single character at a time, polling the transmit and receive data registers status flags. Reading DATA register access the receive buffer and writing DATA register access the transmission buffer. INTFLAG Interrupt FLAG Status and Clear register contains a few bits (flags) that indicates the status of the peripheral:

  • RXCReceive Complete: is 1 when the reception data buffer contains at least one frame to be read, becomes 0 when all frames in the buffer has been read
  • DREData Register Empty: vis 1 when transmission data register is empty, 0 when the application writes data into the DATA register and the hardware loads the frame into the transmission buffer
  • TXCTransmit Complete: is 1 when the transmission buffer is empty and the shift register has done sending all the characters on the TX line, 0 when a frame is written into the transmission buffer

In order for the CPU to process instructions while characters are received on the serial without the risk of losing data, software buffers are used, generally FIFO queues also called ring buffers or circular arrays The application no longer writes on the serial but sends data to a transmission buffer (TX buffer in figure 6) and the peripheral fetches characters from the buffer when ready. USART_write() stores characters into the buffers and returns. Similarly the application doesn’t poll for data but it’s up to the peripheral to store characters into the receive buffer (RX buffer in figure 6) and the application reads the data when needed:

The size of the buffers are chosen based on the application at hand (how many characters must be sent/received and how often). The following code offers a simple implementation of a FIFO buffer; in this example buffers are implemented as two static arrays (but they could be dynamically allocated arrays or linked structures) and the array bounds are chosen at compile time defining the SIZE macro:

#define SIZE 100

 static char tx_buffer[SIZE], rx_buffer[SIZE];
 static int tx_head, rx_head;     
 static int tx_tail, rx_tail;

 void FIFO_init(void)
 {
  tx_head = tx_tail = 0;
  rx_head = rx_tail = 0;
 }

 int TX_FIFO_is_empty(void)
 {
  return tx_tail == tx_head;
 }

 int TX_FIFO_is_full(void)
 {
  return (tx_tail + 1) % SIZE == tx_head;
 }

 int TX_FIFO_put(char data)
 {
  if (TX_FIFO_is_full())
    return 0;
  tx_buffer[tx_tail] = data;
  tx_tail = (tx_tail + 1) % SIZE;
  return 1;
 }

 int TX_FIFO_get(char *data)
 {
  if (TX_FIFO_is_empty())
    return 0;
  *data = tx_buffer[tx_head];
  tx_head = (tx_head + 1) % SIZE;
  return 1;
 }
 
 int RX_FIFO_is_empty(void)
 {
   return rx_tail == rx_head;
 }

 int RX_FIFO_is_full(void)
 {
   return (rx_tail + 1) % SIZE == rx_head;
 }

 int RX_FIFO_put(char data)
 {
   if (RX_FIFO_is_full())
     return 0;
   rx_buffer[rx_tail] = data;
   rx_tail = (rx_tail + 1) % SIZE;
   return 1;
 }

 int RX_FIFO_get(char *data)
 {
   if (RX_FIFO_is_empty())
     return 0;
   *data = rx_buffer[rx_head];
   rx_head = (rx_head + 1) % SIZE;
   return 1;
 }

FIFO_put() and FIFO_get() returns an integer that signals the success or failure of the operation. Functions that write or read data don’t interface with the peripheral registers but with the software FIFO module:

void USART_send(char data)
{
	while (!TX_FIFO_put(data))
		;
	if (!(SERCOM5->USART.INTENSET.reg & SERCOM_USART_INTENSET_DRE))     // data put in tx buffer: re-enable DRE interrupt if disabled
		SERCOM5->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE;
}

char USART_receive(void)
{
  char data;
  while (!RX_FIFO_get(&data))
    ;
  if (!(SERCOM5->USART.INTENSET.reg & SERCOM_USART_INTENSET_RXC))     // data received from rx buffer: re-enable RXC interrupt if disabled
    SERCOM5->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC;
  return data;
}

void USART_write(const char *string)
{
  while (*string)
    USART_send(*string++);
}

void USART_writeln(const char *string)
{
  USART_write(string);
  USART_write("\r\n");
}

void USART_read(char *string)
{
  while ((*string = USART_receive()) != '\n')
    string++;
  *string = '\0';
}

void SERCOM5_Handler(void)
{
  char data;
  if (SERCOM5->USART.INTFLAG.reg & SERCOM_USART_INTFLAG_DRE && SERCOM5->USART.INTENSET.reg & SERCOM_USART_INTENSET_DRE)  // if interrupt flag is set AND interrupt is enabled
  {
    if (!TX_FIFO_get(&data))   // if tx buffer is empty disable DRE interrupt
      SERCOM5->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE;
    else
      SERCOM5->USART.DATA.reg = data;		
  }
  if (SERCOM5->USART.INTFLAG.reg & SERCOM_USART_INTFLAG_RXC && SERCOM5->USART.INTENSET.reg & SERCOM_USART_INTENSET_RXC)  // if interrupt flag is set AND interrupt is enabled
  {
    data = SERCOM5->USART.DATA.reg;
    if (!RX_FIFO_put(data))   // if rx buffer is full disable RXC interrupt
      SERCOM5->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_RXC;
  }
}

Functions that deal with the reception and transmission of characters modify the flags in the INTENSET register: when the reception buffer is full the ISR disables the RXC interrupt (it’s no use servicing the interrupt when there’s no room in the buffer), while USART_receive() re-enables it, since it makes room for other data to be stored into the buffer. If the transmission buffer is empty the ISR disables the DRE interrupt (we don’t service the interrupt if there’s no data to be sent) and USART_send() re-enables it, because now the buffer is no longer empty. Now our application is free to read and write onto the serial whenever it needs to, and is free to process other instructions in the meantime (for example, to blink a LED):

int main(void)
{
  clock_init();
  port_init();
  FIFO_init();
  USART_init();
  NVIC_init();

  for (int i = 4 ; i > 0 ; i--)
    USART_writeln("all work and no play makes jack a dull boy");   // write to tx buffer and return (don't wait for the USART to be ready)
	
  char buffer[100]; 

  while (1)
  {
    blink();                             // do something else
    USART_read(buffer);                  // read from rx buffer when needed (without waiting for data) TODO: implement timeout
    USART_write("received: ");
    USART_writeln(buffer);
  }
}
int main(void)
{
	clock_init();  // initialize system and peripheral clocks
	port_init();   // configure serial GPIO pins
	USART_init();  // configure and enable USART

	USART_write_wait("hello from SAMD21!\r\n");

	char buf[100];  // 100 characters buffer (don't exceed)

	while (1)
	{
		USART_read_wait(buf);
		USART_write_wait("received: ");
		USART_writeln_wait(buf);
	}
}

USART_write_wait() writes a null-terminated character string on the serial while USART_read_wait() waits for a character string terminated by the ‘\n’ line feed character and stores it in a local buffer. USART_writeln_wait() appends a line teminator character to the string. All these functions use USART_send_wait() and USART_receive_wait() to send and receive single characters:

It's worth pointing out again the second post I made here since the above is talking about the SAMD21 and not the more advanced SAMD51 but the general use should be similar.

The SERCOM peripheral on the SAMD51 and SAMD21 generates a number of interrupts, such as DRE (DataRegister Empty), TXC (Transmit Complete) and RXC (Receive Complete), to name just three of the most commonly used.
The SAMD21's SERCOMx module has only a single Interrupt Service Routine (ISR) called SERCOMx_Handler(), therefore within the ISR it's necessary to test each of the interrupt flags to find which interrupt has occurred when the handler function is called.

The SAMD51's SERCOMx module on the other hand has separate ISR's for each interrupt, with the ISR number relating to the position of the interrupt in the Interrupt Flag (INTFLAG) register, for example SERCOM2_0_Handler() is called for DRE, SERCOM2_1_Handler() is called for TXC and SERCOM2_2_Handler() for RXC, etc.... Having separate ISRs for each interrupt flag removes the need test which interrupt has occurred within the handler function, thereby providing the SAMD51 with a small speed optimization over the SAMD21.

Also, in the SAMD21 all of the bits in INTFLAG are ORed together so that only one is connected to the NVIC. In the SAMD51 they are split over four lines.

Not entirely sure how that means the above code examples would need to be slightly modified to work correctly with the SAMD51 product line yet though it is probably related to the void SERCOM5_Handler(void) but much of the core functionality should already be there to handle this.

What I don't get is why Adafruit and ARM don't seem to offer example code to easily use RS485 over any of their SAMD51 chips. In fact, Adafruit makes it such that you literally cannot easily compile the code without modifying the actual variant.cpp file manually and each release or update means you have to do so again because they do not weakly define it. I have no idea why that's the case and it is helpful that they "define" the TX/RX of Serial1 by default since that's literally what the pins are typically used for (although you can change that around once you consult an elaborate third party chart of what actual pads go with what pins and what is actually available on given pins).

Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;

and so on is there too but in order to do much work at all with SERCOM0 or serial here, you have to basically also edit the variant.cpp file as well.

/*void SERCOM0_2_Handler()
{
 Serial1.IrqHandler();
}*

I did come across this official Arduino RS485 code as well, which at least provides some of this functionality to SAMD21 though I am not sure how in depth the provided software is. It does not seem to utilize interrupts or anything particularly fancy or lower level which is sort of what needs to be happening here for this to work.

Enables sending and receiving data using the RS485 standard with RS485 shields, like the MKR 485 Shield.

https://www.arduino.cc/en/Reference/ArduinoRS485

Anyway, going to dig more into this and see if I cannot better understand how Serial commands can be handled using existing lower level routines such that the Arduino is always listening and passing along instructions sent from the Serial Monitor to downstream devices. Any ideas on if there is example code for this or other related Serial use from ARM by chance? There might be some in their main software package or start.atmel.com? The Grand Central is using the SAMD51P20A chipset with the TQFP128 form factor.

All I initially see there is a driver example.

/**
 * Example of using USART_0 to write "Hello World" using the IO abstraction.
 */
void USART_0_example(void)
{
	struct io_descriptor *io;
	usart_sync_get_io_descriptor(&USART_0, &io);
	usart_sync_enable(&USART_0);

	io_write(io, (uint8_t *)"Hello World!", 12);
}

Asynchronous UART / USART Serial Communications on Atmel SAM D21 Xplained Pro - Tutorial
Is also really helpful with regards to setting up UART / USART Serial on a SAM D21.

Here are pad layouts for the SAMD51 Grand Central as well.

S0 P0 and S0 P1 here for Sercom and notably PB24 and PB25 if you are trying to use the start.atmel.com layout which doesn't seem to automatically assign those.

Here are pad layouts for the SAMD21. It's worth noting that the pads are distinct compared to the SAMD51 and I am pretty sure that also explains why the above code is using SERCOM5. It uses PA10 and PA11 and S0 P2 and S0 P3 instead as well.

Here are application notes for, again, the SAMD21 and not the SAMD51 but it seems helpful, plus there are some examples as well.

AT03256: SAM D/R/L/C Serial USART (SERCOMUSART) Driver

This driver for Atmel® | SMART ARM®-based microcontrollers provides an interface for the configuration and management of the SERCOM module inits USART mode to transfer or receive USART data frames. The following driver API modes are covered by this manual:

• Polled APIs
• Callback APIs

The following peripheral is used by this module:
• SERCOM (Serial Communication Interface)

The following devices can use this module:
• Atmel | SMART SAM D20/D21