Complete implementation of UART hardware flow control

doughboy

Well-known member
This change is based on the other thread on adding rts/cts flow control.
https://forum.pjrc.com/threads/29446-Teensy-Hardware-Flow-Control-RTS-CTS

I initially tried that and found out it still results in buffer overrun.
This code guarantees there is no data loss with rts/cts enabled, even without increasing the RX and TX buffer size.

I have tested this code heavily for about 2 weeks and will be using this code in my project.

This is based on revision c2f550d
https://github.com/PaulStoffregen/cores/blob/master/teensy3/serial1.c
plus additional defines in kinetis.h

Code:
/* Teensyduino Core Library
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2013 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.
 */

#include "kinetis.h"
#include "core_pins.h"
#include "HardwareSerial.h"

////////////////////////////////////////////////////////////////
// Tunable parameters (relatively safe to edit these numbers)
////////////////////////////////////////////////////////////////

#define TX_BUFFER_SIZE 64 // number of outgoing bytes to buffer
#define RX_BUFFER_SIZE 64 // number of incoming bytes to buffer
#define IRQ_PRIORITY  64  // 0 = highest priority, 255 = lowest


////////////////////////////////////////////////////////////////
// changes not recommended below this point....
////////////////////////////////////////////////////////////////

#ifdef SERIAL_9BIT_SUPPORT
static uint8_t use9Bits = 0;
#define BUFTYPE uint16_t
#else
#define BUFTYPE uint8_t
#define use9Bits 0
#endif

static volatile BUFTYPE tx_buffer[TX_BUFFER_SIZE];
static volatile BUFTYPE rx_buffer[RX_BUFFER_SIZE];
static volatile uint8_t transmitting = 0;
#if defined(KINETISK)
  static volatile uint8_t *transmit_pin=NULL;
  #define transmit_assert()   *transmit_pin = 1
  #define transmit_deassert() *transmit_pin = 0
#elif defined(KINETISL)
  static volatile uint8_t *transmit_pin=NULL;
  static uint8_t transmit_mask=0;
  #define transmit_assert()   *(transmit_pin+4) = transmit_mask;
  #define transmit_deassert() *(transmit_pin+8) = transmit_mask;
#endif
#if TX_BUFFER_SIZE > 255
static volatile uint16_t tx_buffer_head = 0;
static volatile uint16_t tx_buffer_tail = 0;
#else
static volatile uint8_t tx_buffer_head = 0;
static volatile uint8_t tx_buffer_tail = 0;
#endif
#if RX_BUFFER_SIZE > 255
static volatile uint16_t rx_buffer_head = 0;
static volatile uint16_t rx_buffer_tail = 0;
#else
static volatile uint8_t rx_buffer_head = 0;
static volatile uint8_t rx_buffer_tail = 0;
#endif

// UART0 and UART1 are clocked by F_CPU, UART2 is clocked by F_BUS
// UART0 has 8 byte fifo, UART1 and UART2 have 1 byte buffer

void serial_begin(uint32_t divisor)
{
	SIM_SCGC4 |= SIM_SCGC4_UART0;	// turn on clock, TODO: use bitband
	rx_buffer_head = 0;
	rx_buffer_tail = 0;
	tx_buffer_head = 0;
	tx_buffer_tail = 0;
	transmitting = 0;
	CORE_PIN0_CONFIG = PORT_PCR_PE | PORT_PCR_PS | PORT_PCR_PFE | PORT_PCR_MUX(3);
	CORE_PIN1_CONFIG = PORT_PCR_DSE | PORT_PCR_SRE | PORT_PCR_MUX(3);
#if defined(HAS_KINETISK_UART0)
	UART0_BDH = (divisor >> 13) & 0x1F;
	UART0_BDL = (divisor >> 5) & 0xFF;
	UART0_C4 = divisor & 0x1F;
#ifdef HAS_KINETISK_UART0_FIFO
	UART0_C1 = UART_C1_ILT;
	UART0_TWFIFO = 2; // tx watermark, causes S1_TDRE to set
	UART0_RWFIFO = 4; // rx watermark, causes S1_RDRF to set
	UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;
#else
	UART0_C1 = 0;
	UART0_PFIFO = 0;
#endif
#elif defined(HAS_KINETISL_UART0)
	UART0_BDH = (divisor >> 8) & 0x1F;
	UART0_BDL = divisor & 0xFF;
	UART0_C1 = 0;
#endif
	UART0_C2 = UART_C2_TE | UART_C2_RE | UART_C2_RIE | UART_C2_ILIE;
	NVIC_SET_PRIORITY(IRQ_UART0_STATUS, IRQ_PRIORITY);
	NVIC_ENABLE_IRQ(IRQ_UART0_STATUS);
}

void serial_format(uint32_t format)
{
        uint8_t c;

        c = UART0_C1;
        c = (c & ~0x13) | (format & 0x03);      // configure parity
        if (format & 0x04) c |= 0x10;           // 9 bits (might include parity)
        UART0_C1 = c;
        if ((format & 0x0F) == 0x04) UART0_C3 |= 0x40; // 8N2 is 9 bit with 9th bit always 1
        c = UART0_S2 & ~0x10;
        if (format & 0x10) c |= 0x10;           // rx invert
        UART0_S2 = c;
        c = UART0_C3 & ~0x10;
        if (format & 0x20) c |= 0x10;           // tx invert
        UART0_C3 = c;
#ifdef SERIAL_9BIT_SUPPORT
        c = UART0_C4 & 0x1F;
        if (format & 0x08) c |= 0x20;           // 9 bit mode with parity (requires 10 bits)
        UART0_C4 = c;
        use9Bits = format & 0x80;
#endif
}

void serial_end(void)
{
	if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return;
	while (transmitting) yield();  // wait for buffered data to send
	NVIC_DISABLE_IRQ(IRQ_UART0_STATUS);
	UART0_C2 = 0;
	CORE_PIN0_CONFIG = PORT_PCR_PE | PORT_PCR_PS | PORT_PCR_MUX(1);
	CORE_PIN1_CONFIG = PORT_PCR_PE | PORT_PCR_PS | PORT_PCR_MUX(1);
	rx_buffer_head = 0;
	rx_buffer_tail = 0;
}

void serial_set_transmit_pin(uint8_t pin)
{
	while (transmitting) ;
	pinMode(pin, OUTPUT);
	digitalWrite(pin, LOW);
	transmit_pin = portOutputRegister(pin);
	#if defined(KINETISL)
	transmit_mask = digitalPinToBitMask(pin);
	#endif
}

int serial_set_rts(uint8_t pin)
{
	if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return 0;
	if (pin == 6) {
		CORE_PIN6_CONFIG = PORT_PCR_MUX(3);
	} else if (pin == 19) {
		CORE_PIN19_CONFIG = PORT_PCR_MUX(3);
	} else {
		UART0_MODEM &= ~UART_MODEM_RXRTSE;
		return 0;
	}
	UART0_MODEM |= UART_MODEM_RXRTSE;
	return 1;
}

int serial_set_cts(uint8_t pin)
{
	if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return 0;
	if (pin == 18) {
		CORE_PIN18_CONFIG = PORT_PCR_MUX(3); // TODO: weak pullup or pulldown?
	} else if (pin == 20) {
		CORE_PIN20_CONFIG = PORT_PCR_MUX(3); // TODO: weak pullup or pulldown?
	} else {
		UART0_MODEM &= ~UART_MODEM_TXCTSE;
		return 0;
	}
	UART0_MODEM |= UART_MODEM_TXCTSE;
	return 1;
}

void serial_putchar(uint32_t c)
{
	uint32_t head, n;

	if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return;
	if (transmit_pin) transmit_assert();
	head = tx_buffer_head;
	if (++head >= TX_BUFFER_SIZE) head = 0;
	while (tx_buffer_tail == head) {
		int priority = nvic_execution_priority();
		if (priority <= IRQ_PRIORITY) {
			if ((UART0_S1 & UART_S1_TDRE)) {
				uint32_t tail = tx_buffer_tail;
				if (++tail >= TX_BUFFER_SIZE) tail = 0;
				n = tx_buffer[tail];
				if (use9Bits) UART0_C3 = (UART0_C3 & ~0x40) | ((n & 0x100) >> 2);
				UART0_D = n;
				tx_buffer_tail = tail;
			}
		} else if (priority >= 256) {
			yield();
		}
	}
	tx_buffer[head] = c;
	transmitting = 1;
	tx_buffer_head = head;
	UART0_C2 |= UART_C2_TIE;
	UART0_C2 &= ~UART_C2_TCIE;
}

#ifdef HAS_KINETISK_UART0_FIFO
void serial_write(const void *buf, unsigned int count)
{
	const uint8_t *p = (const uint8_t *)buf;
	const uint8_t *end = p + count;
        uint32_t head, n;

        if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return;
	if (transmit_pin) transmit_assert();
	while (p < end) {
        	head = tx_buffer_head;
        	if (++head >= TX_BUFFER_SIZE) head = 0;
		if (tx_buffer_tail == head) {
				UART0_C2 |= UART_C2_TIE;
				UART0_C2 &= ~UART_C2_TCIE;
			do {
				int priority = nvic_execution_priority();
				if (priority <= IRQ_PRIORITY) {
					if ((UART0_S1 & UART_S1_TDRE)) {
						uint32_t tail = tx_buffer_tail;
						if (++tail >= TX_BUFFER_SIZE) tail = 0;
						n = tx_buffer[tail];
						if (use9Bits) UART0_C3 = (UART0_C3 & ~0x40) | ((n & 0x100) >> 2);
						UART0_D = n;
						tx_buffer_tail = tail;
					}
				} else if (priority >= 256) {
					yield();
				}
			} while (tx_buffer_tail == head);
		}
        	tx_buffer[head] = *p++;
        	transmitting = 1;
        	tx_buffer_head = head;
	}
	UART0_C2 |= UART_C2_TIE;
	UART0_C2 &= ~UART_C2_TCIE;
}
#else
void serial_write(const void *buf, unsigned int count)
{
        const uint8_t *p = (const uint8_t *)buf;
        while (count-- > 0) serial_putchar(*p++);
}
#endif

void serial_flush(void)
{
	while (transmitting) yield(); // wait
}

int serial_write_buffer_free(void)
{
	uint32_t head, tail;

	head = tx_buffer_head;
	tail = tx_buffer_tail;
	if (head >= tail) return TX_BUFFER_SIZE - 1 - head + tail;
	return tail - head - 1;
}

int serial_available(void)
{
	uint32_t head, tail;

	head = rx_buffer_head;
	tail = rx_buffer_tail;
	if (head >= tail) return head - tail;
	return RX_BUFFER_SIZE + head - tail;
}

int serial_getchar(void)
{
	uint32_t head, tail;
	int c;

	head = rx_buffer_head;
	tail = rx_buffer_tail;
	if (head == tail) return -1;
	if (++tail >= RX_BUFFER_SIZE) tail = 0;
	c = rx_buffer[tail];
	rx_buffer_tail = tail;
#ifdef HAS_KINETISK_UART0_FIFO
	if ((UART0_C2 & (UART_C2_RIE | UART_C2_ILIE))==0) {//rx interrupt currently disabled
		int freespace;
		if (head >= tail) //rx head and tail would be unchanged from above if interrupts were disabled
			freespace = RX_BUFFER_SIZE -1 + tail - head;
		else
			freespace = tail - head - 1;
		if (freespace >= UART0_RCFIFO) {
			UART0_C2 |= (UART_C2_RIE | UART_C2_ILIE);//enable rx interrupts
		}
	}
#else
	UART0_C2 |= UART_C2_RIE;
#endif
	return c;
}

int serial_peek(void)
{
	uint32_t head, tail;

	head = rx_buffer_head;
	tail = rx_buffer_tail;
	if (head == tail) return -1;
	if (++tail >= RX_BUFFER_SIZE) tail = 0;
	return rx_buffer[tail];
}

void serial_clear(void)
{
#ifdef HAS_KINETISK_UART0_FIFO
	if (!(SIM_SCGC4 & SIM_SCGC4_UART0)) return;
	UART0_C2 &= ~(UART_C2_RE | UART_C2_RIE | UART_C2_ILIE);
	UART0_CFIFO = UART_CFIFO_RXFLUSH;
	UART0_C2 |= (UART_C2_RE | UART_C2_RIE | UART_C2_ILIE);
#endif
	rx_buffer_head = rx_buffer_tail;
}

// status interrupt combines 
//   Transmit data below watermark  UART_S1_TDRE
//   Transmit complete              UART_S1_TC
//   Idle line                      UART_S1_IDLE
//   Receive data above watermark   UART_S1_RDRF
//   LIN break detect               UART_S2_LBKDIF
//   RxD pin active edge            UART_S2_RXEDGIF

void uart0_status_isr(void)
{
	uint32_t head, tail, n;
	uint8_t c;
#ifdef HAS_KINETISK_UART0_FIFO
        uint32_t newhead;

	if (UART0_S1 & (UART_S1_RDRF | UART_S1_IDLE)) {
		if (UART0_RCFIFO == 0) {
			// The only way to clear the IDLE interrupt flag is
			// to read the data register.  But reading with no
			// data causes a FIFO underrun, which causes the
			// FIFO to return corrupted data.  If anyone from
			// Freescale reads this, what a poor design!  There
			// write should be a write-1-to-clear for IDLE.
			c = UART0_D;
			// flushing the fifo recovers from the underrun,
			// but there's a possible race condition where a
			// new character could be received between reading
			// RCFIFO == 0 and flushing the FIFO.  To minimize
			// the chance, interrupts are disabled so a higher
			// priority interrupt (hopefully) doesn't delay.
			// TODO: change this to disabling the IDLE interrupt
			// which won't be simple, since we already manage
			// which transmit interrupts are enabled.
			__disable_irq();
			UART0_CFIFO = UART_CFIFO_RXFLUSH;
			__enable_irq();
		} else {
			head = rx_buffer_head;
			tail = rx_buffer_tail;
			do {
				newhead = head + 1;
				if (newhead >= RX_BUFFER_SIZE) newhead = 0;
				if (UART0_MODEM & UART_MODEM_RXRTSE) {
					if (newhead == tail) {
						UART0_C2 &= ~(UART_C2_RIE | UART_C2_ILIE);//disable rx interrupts
						break;
					}
				}
				if (UART0_RCFIFO==1) UART0_S1; //as per page 1214 of datasheet regarding resetting of RDRF flag
				if (use9Bits && (UART0_C3 & 0x80)) {
					n = UART0_D | 0x100;
				} else {
					n = UART0_D;
				}
				head = newhead;
				rx_buffer[head] = n;
			} while (UART0_RCFIFO);
			rx_buffer_head = head;
		}
	}
	c = UART0_C2;
	if ((c & UART_C2_TIE) && (UART0_S1 & UART_S1_TDRE)) {
		head = tx_buffer_head;
		tail = tx_buffer_tail;
		do {
			if (tail == head) break;
			if (++tail >= TX_BUFFER_SIZE) tail = 0;
			UART0_S1;
			n = tx_buffer[tail];
			if (use9Bits) UART0_C3 = (UART0_C3 & ~0x40) | ((n & 0x100) >> 2);
			UART0_D = n;
		} while (UART0_TCFIFO < 8);
		tx_buffer_tail = tail;
		if (UART0_S1 & UART_S1_TDRE) {
			UART0_C2 |= UART_C2_TCIE;
			UART0_C2 &= ~UART_C2_TIE;
		}
	}
#else
        if (UART0_S1 & UART_S1_RDRF) {
        	do {
                head = rx_buffer_head + 1;
                if (head >= RX_BUFFER_SIZE) head = 0;
								if (UART0_MODEM & UART_MODEM_RXRTSE) {
									if (head == rx_buffer_tail) {
										UART0_C2 &= ~(UART_C2_RIE);//disable rx interrupts
										break;
									}
								}
                n = UART0_D;
   	            if (use9Bits && (UART0_C3 & 0x80)) n |= 0x100;
                rx_buffer[head] = n;
                rx_buffer_head = head;
                break;
            } while (true);
        }
        c = UART0_C2;
        if ((c & UART_C2_TIE) && (UART0_S1 & UART_S1_TDRE)) {
                head = tx_buffer_head;
                tail = tx_buffer_tail;
                if (head == tail) {
					UART0_C2 |= UART_C2_TCIE;
					UART0_C2 &= ~UART_C2_TIE;
                } else {
                        if (++tail >= TX_BUFFER_SIZE) tail = 0;
                        n = tx_buffer[tail];
                        if (use9Bits) UART0_C3 = (UART0_C3 & ~0x40) | ((n & 0x100) >> 2);
                        UART0_D = n;
                        tx_buffer_tail = tail;
                }
        }
#endif
	if ((c & UART_C2_TCIE) && (UART0_S1 & UART_S1_TC)) {
		transmitting = 0;
		if (transmit_pin) transmit_deassert();
		UART0_C2 &= ~(UART_C2_TCIE | UART_C2_TIE);
	}
}

void serial_print(const char *p)
{
	while (*p) {
		char c = *p++;
		if (c == '\n') serial_putchar('\r');
		serial_putchar(c);
	}
}

static void serial_phex1(uint32_t n)
{
	n &= 15;
	if (n < 10) {
		serial_putchar('0' + n);
	} else {
		serial_putchar('A' - 10 + n);
	}
}

void serial_phex(uint32_t n)
{
	serial_phex1(n >> 4);
	serial_phex1(n);
}

void serial_phex16(uint32_t n)
{
	serial_phex(n >> 8);
	serial_phex(n);
}

void serial_phex32(uint32_t n)
{
	serial_phex(n >> 24);
	serial_phex(n >> 16);
	serial_phex(n >> 8);
	serial_phex(n);
}

you can apply a similar change to serial2 and serial3.
 
This code guarantees there is no data loss with rts/cts enabled, even without increasing the RX and TX buffer size.

Is there a way to see what you have changed in your code to solve the overrun problems?

BTW: I have used Track which is a code repository + ticket system + Wiki all integrated : right now this forum and some of different repositories is very hard to follow but I have no idea yet how to get the source code into ie Eclipse compile it and run it. This should be very trivial I think. But hey: I am still a greenhorn so I might be totally wrong .... :)
 
@Spix - This <added in diffs> code is the solution - there is no UART Flow Control defined in standard hardware libraries. <oh and the OP's first link goes to a thread ... click>

also BTW: after you unwrap your Teensy - download and install Arduino and the Teensyloader per some notes somewhere - under pjrc.com teensy as on the pinout card that should be with it. When you install Teensyloader you can specify for all the libraries to be installed - that's the included sources. Start in there and use some of the samples and get a feel for it. When it comes to the forum rule and a problem you need to post a sample that demos the issue in the IDE for it to be looked at generally and PJRC specifically.

There are notes here or there about eclipse on the forum - you can make it work and maybe get tips as needed, but knowing the Arduino way is a first step - or last resort if you get stuck - because PJRC support and focus is on that common interface.

Somehow a WiKi has been overlooked - the Forum has a lot of history and details so moving it has been carefully considered as to supportability and such. GitHub is the General repository of this 'world' - Arduino and otherwise it seems - and I finally figured out some things to let me work with it. Interim updates and personal libraries all come and go from there as I've seen them.

Using the IDE will surely give you fits with editing - but it just works with two simple steps: download the Arduino ZIP and extract it to some nice place - then download Teensyduino and point it there. Then you plug in the Teensy and get to work.
 
Last edited:
Lots of discussion on the forum - that article was August 9, 2014 - I found the forum search awful and use Bing. Some sort of debugging is in the works. It has done well without it
 
Lots of discussion on the forum - that article was August 9, 2014 - I found the forum search awful and use Bing. Some sort of debugging is in the works. It has done well without it

using printfs for debugging is compared to using breakpoints in your source code like well mmm : painting your entrance hall through a letter opening/plate in your front door ... because you will be able to see the stack trace and the variable's values when a breakpoint is hit
 
Back
Top