If you are supporting two usarts, do you clone the code for them or...

Go To Last Post
25 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Come up with a way to supply a port option to select between them:

 

(APort?UCSR0A:UCSR1A)=0x12;

 

Fails with a lvalue required as left operand of assignment.

 

or use

if (APort==0)

  UCSR0A=0x12;

else UCSR1A=0x12;

 

Obviously you can't do this with an ISR because they are different vectors.

 

Any thoughts?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The following modification to your approach should work (though I wouldn't recommend doing things this way):

*(APort?UCSR0A:UCSR1A)=0x12;

A better approach is to use pointers to the struct oriented peripheral definitions. If they aren't available for whatever reason, your can always roll your own.

github.com/apcountryman/build-avr-gcc: a script for building avr-gcc

github.com/apcountryman/toolchain-avr-gcc: a CMake toolchain for cross compiling for the Atmel AVR family of microcontrollers

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

do you clone the code for them

Yes, that way you can make 100 miles of real progress and avoid 100 miles of frustration.  While you can spend a huge amount of time fiddling with things to allow multiple incarnations without cloning, is it worth it?  Perhaps for a system that "may" grow and grow into a full datacenter with more uart channels than you can count. 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:
is it worth it? 

For just two, I'd probably say not ....

 

you can spend a huge amount of time fiddling with things to allow multiple incarnations

... for exactly that reason.

 

Very little actually needs to be cloned - stuff like ring buffers, etc can easily be generic.

 

Also, in some (many?) cases, the multiple "UART" peripherals are not identical - eg, STM32 might have "UART", "USART", and "low-power UART"; some with and some without flow control, etc, etc, ...

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A side-effect of this arrangement is that you'll need to pass a UART ID into your TX/RX functions. So what was:

uart_TxByte('A');

now becomes:

uart_TxByte(1, 'A');

I suppose you could have two small façade functions:


void uart0_TxByte(uint8_t b)
{
    uart_TxByte(0, b);
}

void uart1_TxByte(uint8_t b)
{
    uart_TxByte(1, b);
}

But ask yourself : What do I actually gain by doing this on a twin USART AVR. Having said that it may actually be useful on ATmega2560 where you have four high spec USARTS.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is why God invented C++ ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
This is why God invented C++ ;-)
Even with C++, things might get hairy.

(unsigned)(&IOREG) are not valid template arguments.

As already noted, a real issue is how much benefit will one get from the necessary effort.

An important determiner is how how trusted and trustworthy one can make the code.

A thing can be hideously complex,

but if it only has to be written and understood once,

the effort might be worth it.

Even two might be worth the effort if one can use it on multiple projects.

 

Were I to do this, I'd be inclined to use the preprocessor.

 

Edit: have to has

uarta.h:
#define UART_SUFFIX A
#include "uarts.h"

Fear of the preprocessor or MISRA rules might prevent this.

Iluvatar is the better part of Valar.

Last Edited: Mon. Feb 22, 2021 - 10:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm glad to see it isn't some simple answer I was just missing!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Even with C++, things might get hairy.

 

a harmless kitten-

https://godbolt.org/z/1K31G9

 

The advantage of C++ is its better at solving these type of problems. Can be done in C also, but you simply run up against the language limitations no matter which direction you go. Unless you know you will never use more than one uart, I would probably resign myself to creating a single version and passing the uart number to its functions. You then have a single set of functions to look after and can use all the uarts you have in a single source file if needed. 

 

C version, may not be correct or optimal-

https://godbolt.org/z/4aYTK5

The online compile is c++ no matter what is chosen, so this is a c++ compiler compiling what I think is c.

 

As simple as possible in C, taken from above example-

https://godbolt.org/z/P3he3e

Not much there, and you get multiple uarts to use in a single set of code without defines needed. Also not much extra code as a uart doesn't have much to do, plus you are already on a 2+ uart mcu which will not be the lowest end mcu. It is also easy to get the printf family to use these as seen in the previous example.

Last Edited: Mon. Feb 22, 2021 - 09:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

For just two, I'd probably say not ....

For me I just have 4 driver files, one for each USART (C and h or .asm) and add whatever I need for the project.

 

Tried to be smarter but I realised I wasn't that smart. sad Takes a few seconds to change the USART number and create a new file.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:
it may actually be useful on ATmega2560 where you have four high spec USARTS.

I'd say four is about the margin between favouring cloning and going "generic" ...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have refactored the UART definitions in AVR from:

UCSR0A=0x12;

to

   UART0->SRA = 0x12;

and then it just becomes a matter of passing the correct top-level structure pointer to the functions.    "Abstraction", you know.

This has already been done on the newer AVRs, as well as most "larger" chips.

C++ lets you do similar things even if the individual peripherals are different.

 

I do something similar for "one of several" of a class of peripherals at the macro level:

#if UART == 0
# define UART_SRA UCSR0A
# define UART_SRB UCSR0B
# define UART_SRC UCSR0C
# define UART_SRL UBRR0L
# define UART_UDR UDR0
#elif UART == 1
#if !defined(UDR1)
#error UART == 1, but no UART1 on device
#endif
# define UART_SRA UCSR1A
# define UART_SRB UCSR1B
# define UART_SRC UCSR1C
# define UART_SRL UBRR1L
# define UART_UDR UDR1
  :
  

(It becomes something of a game to make the new names "abstract, but close enough to the datasheet that they're easily understandable to others."  Thus "UART_SRA" rather than "USTATUS")

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Let's take Windows as an example. It supports up to 256 COM ports.

All you have to do is store all the necessary information in a DCB structure for each port and call a common function with a pointer to the structure.

This is a struct advantage and it shouldn't be difficult to do the same with AVR.

All you have to do is make the port address, data address, etc. that you access now a member of the structure.

 

If this is already difficult for you, it doesn't make sense for me to be here.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
Takes a few seconds to change the USART number and create a new file.
Emphasis added.

Exactly what I would be trying to avoid: separate code for each USART.

 

If each USART has the same layout for its registers,

they can be represented by pointers and generic code can be written without preprocessor games.

If they only have corresponding names,

the preprocessor is likely the way to go.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Exactly what I would be trying to avoid: separate code for each USART.

Why? Would the code overhead for the "abstraction" or whatever is needed for multi USART be a lot less if one has another USART file?

 

Most of the time I only use one USART, at times 2, so I just add the file/s for that USART/S.

 

EDIT just checked and every USART takes 106 bytes of flash with this code.

//USART0 driver file

#include "project_definitions.h"
#include "USART0_s.h"

// Initialize USART0 , tx/rx, 8N1.

void usart0_init(void)
{
	UBRR0L = (uint8_t) (F_CPU / (16UL * USART0_BAUD)) - 1;
	UBRR0H = (uint8_t) ((F_CPU / (16UL * USART0_BAUD)) - 1) >>8;
	UCSR0B = (1<<TXEN0 | 1<<RXEN0); 	// tx/rx enable
}

int usart0_putchar(char c, FILE *stream)
{
	while (!(UCSR0A & (1<<UDRE0)));
	UDR0 = c;

	return 0;
}


int usart0_getchar(FILE *stream)
{
	while (!(UCSR0A & (1<<RXC0)));
	if (UCSR0A & (1<<FE0))
		return _FDEV_EOF;
	if (UCSR0A & (1<<DOR0))
		return _FDEV_ERR;

	return UDR0;
}

int	usart0_unbuffered_getchar(void)
{
	uint8_t c=0;	// Default return value if no char received

	if (UCSR0A & (1<<RXC0))
		{
		c=UDR0;
		}

	return c;
}

 

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Last Edited: Mon. Feb 22, 2021 - 11:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As an example, you can look at the way the Arduino code handles Serial, Serial1, Serial2, etc.

IMO, it's not a very GOOD example, but they do reasonably things with inheritance and so on such that "Serial" can be a USB virtual uart.

(and yeah, they probably add more overhead doing the abstraction than it would have taken to have separate Serial0...4 implementations (on ATmega2560) (well, their not-so-good abstraction is worse than 5 GOOD separate UARTs, perhaps.  Which is not quite the same thing.)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another good option is header only since there is little reason these simple functions wouldn't be better off being inline code-

 

https://godbolt.org/z/nKf6T7

oops, need to read err before reading udr

https://godbolt.org/z/PbMjnj

Just include the header, no source code files to add/remove, and the compiler is going to optimize away all the instance usage. The stdio FILE things can also be used by storing the needed uart_t in the FILE udata, so you then do get the uart_t instance in use but is still minimal and only affects a single function (inline) when needed.

 

 

Most of my code ends up as header only code (in c++), which is kind of nice to use. The following has 2 source files- main.cpp and Isr.cpp (the c isr functions need to be in a source file, and in this case they just call the ::isr() function of the peripheral, which is inline code). This code works in any avr0/1, and if it was for a mega4809 all uarts can be used with the single Uart.hpp header (and the correct pins taken care of also- tx output, rx input/pullup).

 

#include "MyAvr.hpp"
#include "Boards.hpp"
#include "Uart.hpp"
#include "Rtc.hpp" //also provides instance 'rtc'
#include "Buffer.hpp"
#include "StackMonitor.hpp"

 

Boards::ATtiny3217CuriosityNano board;

BufferStorage<64> txBuf;
Uart<board.uartN> uart{ board.uartConfig };

 

int main(){

    StackMonitor sm;
    sm.init();  //init stack monitor (mark stack)

 

    rtc.init(); //rtc 1 second isr also checks stack
    uart.txBuffer( txBuf ); //use a buffer for tx

 

    while(true){

        rtc.waitMS( 100 );
        u8 v;
        if( uart.read(v) and (v == ' ') ){ //print when spacebar pressed
            auto t = rtc.now();
            //either uart.print or Print( uart,
            uart.print( "[%010lu.%03u]" NEWLINE, t.sec, t.ms );              
            Print( uart, "stack lowAddress: 0x%02X  used: %u  free: %u" NEWLINE,
                    sm.lowAddress(), sm.used(), sm.free() );
            Print( uart, "txBuf  used: %u  free: %u  max: %u" NEWLINE,
                    txBuf.sizeUsed(), txBuf.sizeFree(), txBuf.maxUsed() );
          }

    }

}

Last Edited: Tue. Feb 23, 2021 - 11:41 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have made 3 uart file sets ( c & h ) a 'uart"  for chips were there is only a single uart and the register names do not have a trailing '0'  and a uart0 and uart1  I look at the datasheet and depending on what the names are there I take one of the 3.

I think almost all chip I have use uart0 and uart1 cannot recall having used the uart file in a long time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
Why? Would the code overhead for the "abstraction" or whatever is needed for multi USART be a lot less if one has another USART file?
The problem with block copying code and then changing all the 0's to 1's to cater for the other uart is that it's simply too easy to make a copy/paste typing error and have the supposedly "1" code still writing to UCSR0B in the middle of things or something like that.  Also if you realise you missed the bit checking for framing error and need to add that in you have to do it for 0, 1, ... and so on in multiple places. That's why it's better to have "one code" then make it work for 0, 1, 2, etc with some simple switch.

 

I think the fleury approach which is kind  of:

#define SELECTED_UART 2

#if SELECTED_UART == 0
    #define UART_DATA UDR0
    #define UARTB_REG UCSR0B
    etc.
#elif SELECTED_UART == 1
    #define UART_DATA UDR1
    #define UARTB_REG UCSR1B
    etc.
#elif SELECTED_UART == 2
    #define UART_DATA UDR2
    #define UARTB_REG UCSR2B
    etc.
#endif

void UART_init() {
    UARTB_REG = (1 << TXEN)
}

void UART_sendchar(char c) {
    UART_DATA = c;
}

Now it's true that to create the bit at the top I did the classic copy/paste thing then changed the 0's to 1's or 2's but this is farily localised, manageable and only done once. After that the code just interacts with the generic names like UART_DATA not knowing if that is really UDR0, UDR1, UDR2 etc,

 

Of course all this changes when you get to Xmega and it's struct based (very C++ like) register structures in which case the only thing that changes is the base address of the struct (UART0, UART1, UART2) and then the code interacts with the very same struct members.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> After that the code just interacts with the generic names like UART_DATA not knowing if that is really UDR0, UDR1, UDR2 etc,

 

That's all sorta fine until you want to use more than one uart in the same source file. If you have 2+ uarts, odds are one of them will be sending debug info from anywhere (any source file), and now you either come up with some other define scheme or start over and do something else.

 

 

> Of course all this changes when you get to Xmega and it's struct based (very C++ like) register structures

 

As seen in the #17 linked code, you do not have to wait for an xmega to arrive in your mailbox to create/use a struct, and you can even do better than the xmega (bitfields). Inline code in headers is often overlooked, and you can let the compiler deal with turning it into something simple. It doesn't even matter if you have only 1 uart (and already have this code), the same code can be used and the compiler takes care of it. Compiler doing work = good, pre-processor doing compiler work = not good. Pre-processor is a word processor that wants to be a compiler- give it a little compiler-like work and its still not happy until it takes over your project.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

curtvm wrote:
That's all sorta fine until you want to use more than one uart in the same source file.
well I think the idea is that you just add two copies of uart.c as uart1.c and uart2.c and define SELECTED_UART as 1 in one of them and 2 in the other.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was trying some stuff with this last night.  My first thought is that I am no worse off by putting the variables I use in an array, even with separate functions they will still boil down to some address.  Then I thought I would try to integrate the port as an option.  It isn't so bad here so far.  The question remains though, if I do the same thing to UCSR0B and UCSR0C, will each have to evaluate port instead of me just doing a single test about APort in the first place that calls all 3 instructions that don't need the test each.

void usart0_init(uint8_t APort, uint8_t AUCSRC, uint16_t AUBRR)
  {
    //init

    *(volatile uint16_t *)(APort?(&UBRR1):(&UBRR0))=AUBRR;

    //UBRR0=AUBRR;
    UCSR0B=_BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);
    UCSR0C=AUCSRC;
  }
    3306:	81 11       	cpse	r24, r1
    3308:	03 c0       	rjmp	.+6      	; 0x3310 <usart0_init+0xa>
    330a:	e4 ec       	ldi	r30, 0xC4	; 196
    330c:	f0 e0       	ldi	r31, 0x00	; 0
    330e:	02 c0       	rjmp	.+4      	; 0x3314 <usart0_init+0xe>
    3310:	ec ec       	ldi	r30, 0xCC	; 204
    3312:	f0 e0       	ldi	r31, 0x00	; 0
    3314:	51 83       	std	Z+1, r21	; 0x01
    3316:	40 83       	st	Z, r20

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

or

 

    *(volatile uint16_t *)(APort?(&UBRR1):(&UBRR0))=AUBRR;
    3306:	81 11       	cpse	r24, r1
    3308:	03 c0       	rjmp	.+6      	; 0x3310 <usart0_init+0xa>
    330a:	e4 ec       	ldi	r30, 0xC4	; 196
    330c:	f0 e0       	ldi	r31, 0x00	; 0
    330e:	02 c0       	rjmp	.+4      	; 0x3314 <usart0_init+0xe>
    3310:	ec ec       	ldi	r30, 0xCC	; 204
    3312:	f0 e0       	ldi	r31, 0x00	; 0
    3314:	51 83       	std	Z+1, r21	; 0x01
    3316:	40 83       	st	Z, r20
    *(volatile uint8_t *)(APort?(&UCSR1B):(&UCSR0B))=_BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);
    3318:	81 11       	cpse	r24, r1
    331a:	03 c0       	rjmp	.+6      	; 0x3322 <usart0_init+0x1c>
    331c:	e1 ec       	ldi	r30, 0xC1	; 193
    331e:	f0 e0       	ldi	r31, 0x00	; 0
    3320:	02 c0       	rjmp	.+4      	; 0x3326 <usart0_init+0x20>
    3322:	e9 ec       	ldi	r30, 0xC9	; 201
    3324:	f0 e0       	ldi	r31, 0x00	; 0
    3326:	98 e9       	ldi	r25, 0x98	; 152
    3328:	90 83       	st	Z, r25
    *(volatile uint8_t *)(APort?(&UCSR1C):(&UCSR0C))=AUCSRC;
    332a:	81 11       	cpse	r24, r1
    332c:	03 c0       	rjmp	.+6      	; 0x3334 <usart0_init+0x2e>
    332e:	e2 ec       	ldi	r30, 0xC2	; 194
    3330:	f0 e0       	ldi	r31, 0x00	; 0
    3332:	02 c0       	rjmp	.+4      	; 0x3338 <usart0_init+0x32>
    3334:	ea ec       	ldi	r30, 0xCA	; 202
    3336:	f0 e0       	ldi	r31, 0x00	; 0
    3338:	60 83       	st	Z, r22
    333a:	08 95       	ret

vs

    if (APort==0)
    3306:	81 11       	cpse	r24, r1
    3308:	0a c0       	rjmp	.+20     	; 0x331e <usart0_init+0x18>
      {
        UBRR0=AUBRR;
    330a:	50 93 c5 00 	sts	0x00C5, r21	; 0x8000c5 <__TEXT_REGION_LENGTH__+0x7e00c5>
    330e:	40 93 c4 00 	sts	0x00C4, r20	; 0x8000c4 <__TEXT_REGION_LENGTH__+0x7e00c4>
        UCSR0B=_BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);
    3312:	88 e9       	ldi	r24, 0x98	; 152
    3314:	80 93 c1 00 	sts	0x00C1, r24	; 0x8000c1 <__TEXT_REGION_LENGTH__+0x7e00c1>
        UCSR0C=AUCSRC;
    3318:	60 93 c2 00 	sts	0x00C2, r22	; 0x8000c2 <__TEXT_REGION_LENGTH__+0x7e00c2>
    331c:	08 95       	ret
      }
    else
      {
        UBRR1=AUBRR;
    331e:	50 93 cd 00 	sts	0x00CD, r21	; 0x8000cd <__TEXT_REGION_LENGTH__+0x7e00cd>
    3322:	40 93 cc 00 	sts	0x00CC, r20	; 0x8000cc <__TEXT_REGION_LENGTH__+0x7e00cc>
        UCSR1B=_BV(RXCIE1) | _BV(RXEN1) | _BV(TXEN1);
    3326:	88 e9       	ldi	r24, 0x98	; 152
    3328:	80 93 c9 00 	sts	0x00C9, r24	; 0x8000c9 <__TEXT_REGION_LENGTH__+0x7e00c9>
        UCSR1C=AUCSRC;
    332c:	60 93 ca 00 	sts	0x00CA, r22	; 0x8000ca <__TEXT_REGION_LENGTH__+0x7e00ca>
    3330:	08 95       	ret

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    3306:	81 11       	cpse	r24, r1
    3308:	03 c0       	rjmp	.+6      	; 0x3310 <usart0_init+0xa>

I guess with something like UART_init() it doesn't really matter how "efficient" the code is because this is a one time function that only runs once, for microseconds, as the code starts up. But the problem you have is that it's still making a run-time, not compile-time decision as to which set of registers to access. As I say the run-time decision may not matter for the init() as it's one-off code. But are you really going to do the same with UART_sendchar() so that it is passed a parameter to determine whether it's UDR0 or UDR1 so that for every time it sends a character this same decision is wastefully made again? Again you might say that even at a fast baud rate (like 115,200) the worst use case of the function might be 10,000 times per second or something along those lines so a few extra opcodes for each character sent "doesn't matter"? But the "best" solutions to this kind of thing are the ones where a compile time choice is made (you know at build time whether you have wired up UART0 or UART1!) and then it only generates code to either access UART0 or UART1 registers but does not make a run-time choice.

Last Edited: Tue. Feb 23, 2021 - 03:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It wasn't as painful as I thought it might be.  The interrupts have to be individual anyway and are not worth trying to use a single function for, so they are cloned, but the rest isn't.  SH = software handshaking which can be optionally enabled/disabled.  If SH is enabled, it is transparently handled by the usart functions.

 

usart.h

#ifndef usartH
#define usartH

#include <stdint.h>

#define USART_PORTS   2

#define TOKEN_XON     0x11
#define TOKEN_XOFF    0x13

#define USART_RX_BUFFER_SIZE 144
#define USART_TX_BUFFER_SIZE 16

#define USART_RX_BUFFER_POINT_5  (USART_RX_BUFFER_SIZE*5/100)
#define USART_RX_BUFFER_POINT_25 (USART_RX_BUFFER_SIZE*25/100)
#define USART_RX_BUFFER_POINT_75 (USART_RX_BUFFER_SIZE*75/100)
#define USART_RX_BUFFER_POINT_95 (USART_RX_BUFFER_SIZE*95/100)

struct sh_t
  {
    unsigned int enabled : 1;
    unsigned int rxenabled : 1;
    unsigned int txenabled : 1;
    unsigned int sendxoff : 1;
    unsigned int sendxon : 1;
  };

extern struct sh_t usart_sh[USART_PORTS];

extern volatile uint8_t usart_rxbuffer[USART_PORTS][USART_RX_BUFFER_SIZE], usart_rxin[USART_PORTS], usart_rxout[USART_PORTS], usart_rxcount[USART_PORTS];
extern volatile uint8_t usart_txbuffer[USART_PORTS][USART_TX_BUFFER_SIZE], usart_txin[USART_PORTS], usart_txout[USART_PORTS], usart_txcount[USART_PORTS];
extern volatile uint16_t usart_lastrxtime[USART_PORTS];

void usart_init(uint8_t APort, uint8_t AUCSRC, uint16_t AUBRR);
uint8_t usart_getc(uint8_t APort);
void usart_putc(uint8_t APort, uint8_t AChar);
void usart_clearrxbuffer(uint8_t APort);
void usart_shenablerx(uint8_t APort);
void usart_cleartxbuffer(uint8_t APort);
void usart_shenabletx(uint8_t APort);
void usart_reset_all(uint8_t APort);

#endif
usart.c

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

#include "usart.h"
#include "main.h"

/*
  //status

  rx char available            if (usart_rxcount)
  tx char available            if (usart_txcount<USART_TX_BUFFER_SIZE)
  tx idle                      (UCSR0A & _BV(TXC0))
  sh rx enabled
  sh tx enabled
  FEn
  DORn
  UPEn

  //command
  rx char available            write 1 to clear rx buffer
  tx char available            write 1 to clear tx buffer
  tx idle
  sh rx enabled                write 1 to enable rx (sends an xon)
  sh tx enabled                write 1 to enable tx
  FEn                          write 1 to clear
  DORn                         write 1 to clear
  UPEn                         write 1 to clear

*/

//usart functions

struct sh_t usart_sh[USART_PORTS] = { { 0, 1, 1, 0, 0 }, { 0, 1, 1, 0, 0 }, };

volatile uint8_t usart_rxbuffer[USART_PORTS][USART_RX_BUFFER_SIZE], usart_rxin[USART_PORTS], usart_rxout[USART_PORTS], usart_rxcount[USART_PORTS];
volatile uint8_t usart_txbuffer[USART_PORTS][USART_TX_BUFFER_SIZE], usart_txin[USART_PORTS], usart_txout[USART_PORTS], usart_txcount[USART_PORTS];
volatile uint16_t usart_lastrxtime[USART_PORTS];

void usart_init(uint8_t APort, uint8_t AUCSRC, uint16_t AUBRR)
  {
    //init
    if (APort==0)
      {
        UBRR0=AUBRR;
        UCSR0B=_BV(RXCIE0) | _BV(RXEN0) | _BV(TXEN0);
        UCSR0C=AUCSRC;
      }
    else
      {
        UBRR1=AUBRR;
        UCSR1B=_BV(RXCIE1) | _BV(RXEN1) | _BV(TXEN1);
        UCSR1C=AUCSRC;
      }
  }

ISR(USART0_UDRE_vect)
  {
    //sh
    if (usart_sh[0].enabled)
      {
        if (usart_sh[0].sendxon) //send xon if triggered
          {
            //clear txc flag
            UCSR0A|=_BV(TXC0);

            //send xon
            UDR0=TOKEN_XON;

            //clear flag
            usart_sh[0].sendxon=0;
            return;
          }
        else
        if (usart_sh[0].sendxoff) //send xoff if triggered
          {
            //clear txc flag
            UCSR0A|=_BV(TXC0);

            //send xon
            UDR0=TOKEN_XOFF;

            //clear flag
            usart_sh[0].sendxoff=0;
            return;
          }
        else
        if (!usart_sh[0].txenabled) //stop sending if tx is not enabled
          {
            UCSR0B&=~_BV(UDRIE0);
            return;
          }
      }

    //do we have something to send
    if (usart_txcount[0])
      {
        //clear txc flag
        UCSR0A|=_BV(TXC0);

        //tx char
        UDR0=usart_txbuffer[0][usart_txout[0]];

        //remove from buffer
        usart_txout[0]++;
        if (usart_txout[0]==USART_TX_BUFFER_SIZE)
          usart_txout[0]=0;
        usart_txcount[0]--;
      }

    //stop sending if buffer empty
    if (usart_txcount[0]==0)
      UCSR0B&=~_BV(UDRIE0);
  }

ISR(USART1_UDRE_vect)
  {
    //sh
    if (usart_sh[1].enabled)
      {
        if (usart_sh[1].sendxon) //send xon if triggered
          {
            //clear txc flag
            UCSR1A|=_BV(TXC1);

            //send xon
            UDR1=TOKEN_XON;

            //clear flag
            usart_sh[1].sendxon=0;
            return;
          }
        else
        if (usart_sh[1].sendxoff) //send xoff if triggered
          {
            //clear txc flag
            UCSR1A|=_BV(TXC1);

            //send xon
            UDR1=TOKEN_XOFF;

            //clear flag
            usart_sh[1].sendxoff=0;
            return;
          }
        else
        if (!usart_sh[1].txenabled) //stop sending if tx is not enabled
          {
            UCSR1B&=~_BV(UDRIE1);
            return;
          }
      }

    //do we have something to send
    if (usart_txcount[1])
      {
        //clear txc flag
        UCSR1A|=_BV(TXC1);

        //tx char
        UDR1=usart_txbuffer[1][usart_txout[1]];

        //remove from buffer
        usart_txout[1]++;
        if (usart_txout[1]==USART_TX_BUFFER_SIZE)
          usart_txout[1]=0;
        usart_txcount[1]--;
      }

    //stop sending if buffer empty
    if (usart_txcount[1]==0)
      UCSR1B&=~_BV(UDRIE1);
  }

ISR(USART0_RX_vect)
  {
    char c1;

    //rx char
    c1=UDR0;

    //get lastrxtime
    usart_lastrxtime[0]=timelow;

    //sh
    if (usart_sh[0].enabled)
      {
        if (c1==TOKEN_XOFF)
          {
            usart_sh[0].txenabled=0;
            return;
          }
        else
        if (c1==TOKEN_XON)
          {
            usart_sh[0].txenabled=1;
            UCSR0B|=_BV(UDRIE0);
            return;
          }
      }

    //drop char if buffer full
    if (usart_rxcount[0]==USART_RX_BUFFER_SIZE)
      return;

    //add to buffer
    usart_rxbuffer[0][usart_rxin[0]]=c1;
    usart_rxin[0]++;
    if (usart_rxin[0]==USART_RX_BUFFER_SIZE)
      usart_rxin[0]=0;
    usart_rxcount[0]++;

    //sh
    if (usart_sh[0].enabled && ((usart_rxcount[0]==USART_RX_BUFFER_POINT_75) || (usart_rxcount[0]==USART_RX_BUFFER_POINT_95)))
      {
        usart_sh[0].sendxoff=1;
        usart_sh[0].rxenabled=0;
        UCSR0B|=_BV(UDRIE0);
      }
  }

ISR(USART1_RX_vect)
  {
    char c1;

    //rx char
    c1=UDR1;

    //get lastrxtime
    usart_lastrxtime[1]=timelow;

    //sh
    if (usart_sh[1].enabled)
      {
        if (c1==TOKEN_XOFF)
          {
            usart_sh[1].txenabled=0;
            return;
          }
        else
        if (c1==TOKEN_XON)
          {
            usart_sh[1].txenabled=1;
            UCSR1B|=_BV(UDRIE1);
            return;
          }
      }

    //drop char if buffer full
    if (usart_rxcount[1]==USART_RX_BUFFER_SIZE)
      return;

    //add to buffer
    usart_rxbuffer[1][usart_rxin[1]]=c1;
    usart_rxin[1]++;
    if (usart_rxin[1]==USART_RX_BUFFER_SIZE)
      usart_rxin[1]=0;
    usart_rxcount[1]++;

    //sh
    if (usart_sh[1].enabled && ((usart_rxcount[1]==USART_RX_BUFFER_POINT_75) || (usart_rxcount[1]==USART_RX_BUFFER_POINT_95)))
      {
        usart_sh[1].sendxoff=1;
        usart_sh[1].rxenabled=0;
        UCSR1B|=_BV(UDRIE1);
      }
  }

uint8_t usart_getc(uint8_t APort)
  {
    uint8_t c1;

    //wait if necessary
    while (usart_rxcount[APort]==0)
      ;

    //read from buffer
    c1=usart_rxbuffer[APort][usart_rxout[APort]];
    ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
        usart_rxout[APort]++;
        if (usart_rxout[APort]==USART_RX_BUFFER_SIZE)
          usart_rxout[APort]=0;
        usart_rxcount[APort]--;
      }

    //sh
    if (usart_sh[APort].enabled && ((usart_rxcount[APort]==USART_RX_BUFFER_POINT_25) || (usart_rxcount[APort]==USART_RX_BUFFER_POINT_5)))
      {
        usart_sh[APort].sendxon=1;
        usart_sh[APort].rxenabled=1;
        if (APort==0)
          UCSR0B|=_BV(UDRIE0);
        else UCSR1B|=_BV(UDRIE1);
      }

    return c1;
  }

void usart_putc(uint8_t APort, uint8_t AChar)
  {
    //wait if necessary
    while (usart_txcount[APort]==USART_TX_BUFFER_SIZE)
      ;

    //do not send xon/xoff if in sh mode
    if (usart_sh[APort].enabled && (AChar==TOKEN_XOFF || AChar==TOKEN_XON))
      return;

    //add char to buffer
    usart_txbuffer[APort][usart_txin[APort]]=AChar;
    ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
        usart_txin[APort]++;
        if (usart_txin[APort]==USART_TX_BUFFER_SIZE)
          usart_txin[APort]=0;
        usart_txcount[APort]++;
        if (APort==0)
          UCSR0B|=_BV(UDRIE0);
        else UCSR1B|=_BV(UDRIE1);
      }
  }

void usart_clearrxbuffer(uint8_t APort)
  {
    //reset rx buffer
    ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
        usart_rxin[APort]=0;
        usart_rxout[APort]=0;
        usart_rxcount[APort]=0;
      }
  }

void usart_shenablerx(uint8_t APort)
  {
    if (usart_sh[APort].enabled)
      {
        usart_sh[APort].sendxon=1;
        usart_sh[APort].rxenabled=1;
        if (APort==0)
          UCSR0B|=_BV(UDRIE0);
        else UCSR1B|=_BV(UDRIE1);
      }
  }

void usart_cleartxbuffer(uint8_t APort)
  {
    //reset tx buffer
    ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
        usart_txin[APort]=0;
        usart_txout[APort]=0;
        usart_txcount[APort]=0;
      }
  }

void usart_shenabletx(uint8_t APort)
  {
    if (usart_sh[APort].enabled)
      usart_sh[APort].txenabled=1;
  }

void usart_reset_all(uint8_t APort)
  {
    usart_clearrxbuffer(APort);
    usart_shenablerx(APort);
    usart_cleartxbuffer(APort);
    usart_shenabletx(APort);
  }