USART module reference

Header :
#include <usart.h>
Makefile :
MODULES+=usart

Description

An USART (Universal Synchronous/Asynchronous Receiver/Transmitter) is a peripheral used to send and receive data to/from another device using a standard and very simple serial communication scheme. This interface is sometimes abusively called "Serial" (even though SPI, I2C and USB are also serial protocols) or RS-232 (which is the older computer standard which used the same encoding scheme but with different voltage levels). In this protocol, there is no "master" or "slave" : the communication is symetrical, each device has a transmitter (Tx) and a receiver (Rx), and one device's transmitter sends data to the other device's receiver. If the communication is only required in one direction, the other line can be left unconnected. For more information and see how this interface compares to other protocols such as I2C and SPI, take a look at the Communication buses tutorial.

The most common use cases are :

  • Exchanging data with a computer using a "serial-to-USB adapter" (such as the infamous FTDI FT232) : this was the standard way to connect a microcontroller to a computer before microcontrollers had USB support; nowdays, it is usually better to use USB.
  • Setting up a simple communication between two microcontrollers, even of very different kind, because USART is usually the most well supported interface (and it is simpler than I2C or SPI).
  • Some kind of devices, especially GPS and GSM chipset, commonly have a USART interface.

Synchronous vs Asynchronous

A communication line is called "synchronous" when a clock signal is transfered along the data, or "asynchronous" otherwise. Synchronous protocols are somewhat easier to use and more reliable because the sender and the receiver are, as the name implies, synchronized; however, this requires another dedicated wire to carry the clock signal. With asynchronous protocols, the two devices must be correctly configured with the same clock setting in order to understand each other.

The USART protocol in general can be used either in synchronous or asynchronous mode, but in fact, the vast majority of devices do not have a clock line and therefore only work in asynchronous mode. Currently, this is the only mode supported by the driver. This is why the S is sometimes dropped from the name, and the peripheral (or, by extension, the protocol) is simply called UART.

API

void enable(Port port, unsigned long baudrate, bool hardwareFlowControl=false, CharLength charLength=CharLength::CHAR8, Parity parity=Parity::NONE, StopBit stopBit=StopBit::STOP1)
void disable()
void enableInterrupt(Port port, void (*handler)(), Interrupt interrupt)
int available(Port port)
bool contains(Port port, char byte)
char peek(Port port)

Hacking

Code

Header

#ifndef _USART_H_
#define _USART_H_

#include <stdint.h>
#include "gpio.h"

// Universal Synchronous Asynchronous Receiver Transmitter
// This module allows the chip to communicate on an RS232 link
// (also sometimes called Serial port)
namespace USART {

    // Peripheral memory space base addresses
    const uint32_t USART_BASE = 0x40024000;
    const uint32_t USART_REG_SIZE = 0x4000;

    // Register offsets
    const uint32_t OFFSET_CR =      0x00;
    const uint32_t OFFSET_MR =      0x04;
    const uint32_t OFFSET_IER =     0x08;
    const uint32_t OFFSET_IDR =     0x0C;
    const uint32_t OFFSET_IMR =     0x10;
    const uint32_t OFFSET_CSR =     0x14;
    const uint32_t OFFSET_RHR =     0x18;
    const uint32_t OFFSET_THR =     0x1C;
    const uint32_t OFFSET_BRGR =    0x20;
    const uint32_t OFFSET_RTOR =    0x24;
    const uint32_t OFFSET_TTGR =    0x28;
    const uint32_t OFFSET_FIDI =    0x40;
    const uint32_t OFFSET_NER =     0x44;
    const uint32_t OFFSET_IFR =     0x4C;
    const uint32_t OFFSET_MAN =     0x50;
    const uint32_t OFFSET_LINMR =   0x54;
    const uint32_t OFFSET_LINIR =   0x58;
    const uint32_t OFFSET_LINBR =   0x5C;
    const uint32_t OFFSET_WPMR =    0xE4;
    const uint32_t OFFSET_WPSR =    0xE8;
    const uint32_t OFFSET_VERSION = 0xFC;

    // Subregisters
    const uint32_t CR_RSTRX = 2;
    const uint32_t CR_RSTTX = 3;
    const uint32_t CR_RXEN = 4;
    const uint32_t CR_RXDIS = 5;
    const uint32_t CR_TXEN = 6;
    const uint32_t CR_TXDIS = 7;
    const uint32_t CR_RSTSTA = 8;
    const uint32_t MR_MODE = 0;
    const uint32_t MR_CHRL = 6;
    const uint32_t MR_PAR = 9;
    const uint32_t MR_NBSTOP = 12;
    const uint32_t MR_MODE9 = 17;
    const uint32_t BRGR_CD = 0;
    const uint32_t BRGR_FP = 16;
    const uint32_t CSR_RXRDY = 0;
    const uint32_t CSR_TXRDY = 1;
    const uint32_t CSR_RXBRK = 2;
    const uint32_t CSR_OVRE = 5;
    const uint32_t CSR_PARE = 7;
    const uint32_t CSR_TXEMPTY = 9;
    const uint32_t IER_RXRDY = 0;

    // Constants
    const uint32_t WPMR_KEY = 0x555341 << 8;
    const uint32_t WPMR_ENABLE = 1;
    const uint32_t WPMR_DISABLE = 0;
    const uint32_t MODE_NORMAL = 0b0000;
    const uint32_t MODE_HARDWARE_HANDSHAKE = 0b0010;

    const int N_PORTS = 4;
    enum class Port {
        USART0,
        USART1,
        USART2,
        USART3
    };

    const uint8_t BIN = 2;
    const uint8_t DEC = 10;
    const uint8_t HEX = 16;

    // Port configuration : character length (default 8)
    // Value refers to MR.CHRL and MR.MODE9
    enum class CharLength {
        CHAR5 = 0b000,
        CHAR6 = 0b001,
        CHAR7 = 0b010,
        CHAR8 = 0b011,
        CHAR9 = 0b100
    };

    // Port configuration : parity type (default NONE)
    // Value refers to MR.PAR
    enum class Parity {
        EVEN = 0b000,
        ODD = 0b001,
        SPACE = 0b010, // Parity forced to 0
        MARK = 0b011, // Parity forced to 1
        NONE = 0b100,
    };

    // Port configuration : length of stop bit (default 1)
    // Value refers to MR.NBSTOP
    enum class StopBit {
        STOP1 = 0b00,
        STOP15 = 0b01, // length 1.5 bit
        STOP2 = 0b10
    };

    const int N_INTERRUPTS = 4;
    enum class Interrupt {
        BYTE_RECEIVED,
        TRANSMIT_READY,
        OVERFLOW,
        PARITY_ERROR
    };

    enum class PinFunction {
        RX,
        TX,
        RTS,
        CTS
    };

    const int BUFFER_SIZE = 2048;


    // Error codes
    const Error::Code ERR_BAUDRATE_OUT_OF_RANGE = 0x0001;


    // Module API
    void enable(Port port, unsigned long baudrate, bool hardwareFlowControl=false, CharLength charLength=CharLength::CHAR8, Parity parity=Parity::NONE, StopBit stopBit=StopBit::STOP1);
    void disable(Port port);
    void enableInterrupt(Port port, void (*handler)(), Interrupt interrupt);
    int available(Port port);
    bool contains(Port port, char byte);
    char peek(Port port);
    bool peek(Port port, const char* test, int size);
    char read(Port port);
    int read(Port port, char* buffer, int size, bool readUntil=false, char end=0x00);
    int readUntil(Port port, char* buffer, int size, char end);
    unsigned long readInt(Port port, int nBytes, bool wait=true);
    int write(Port port, const char* buffer, int size=-1, bool async=false);
    int write(Port port, char byte, bool async=false);
    int write(Port port, int number, uint8_t base, bool async=false);
    int write(Port port, bool boolean, bool async=false);
    int writeLine(Port port, const char* buffer, int size=-1, bool async=false);
    int writeLine(Port port, char byte, bool async=false);
    int writeLine(Port port, int number, uint8_t base, bool async=false);
    int writeLine(Port port, bool boolean, bool async=false);
    bool isWriteFinished(Port port);
    void waitWriteFinished(Port port);
    void waitReadFinished(Port port, unsigned long timeout=100);
    void flush(Port port);
    void setPin(Port port, PinFunction function, GPIO::Pin pin);

}


#endif

Module

#include "usart.h"
#include "core.h"
#include "gpio.h"
#include "pm.h"
#include "dma.h"

namespace USART {

    // Internal functions
    void rxBufferFullHandler();

    // Clocks
    const int PM_CLK[] = {PM::CLK_USART0, PM::CLK_USART1, PM::CLK_USART2, PM::CLK_USART3};

    // Interrupt handlers
    extern uint8_t INTERRUPT_PRIORITY;
    uint32_t _interruptHandlers[N_PORTS][N_INTERRUPTS];
    const int _interruptBits[N_INTERRUPTS] = {CSR_RXRDY, CSR_TXRDY, CSR_OVRE, CSR_PARE};
    void interruptHandlerWrapper();

    // Package-dependant, defined in pins_sam4l_XX.cpp
    // Can be modified using setPin()
    extern struct GPIO::Pin PINS_RX[];
    extern struct GPIO::Pin PINS_TX[];
    extern struct GPIO::Pin PINS_CTS[];
    extern struct GPIO::Pin PINS_RTS[];

    // Ports
    struct USART {
        unsigned long baudrate;
        bool hardwareFlowControl;
        CharLength charLength;
        Parity parity;
        StopBit stopBit;
        uint8_t rxBuffer[BUFFER_SIZE];
        uint8_t txBuffer[BUFFER_SIZE];
        int rxBufferCursorR;
        int rxBufferCursorW;
        int txBufferCursor;
        int rxDMAChannel = -1;
        int txDMAChannel = -1;
    };
    struct USART _ports[N_PORTS];

    int _rxDMAChannelsToPorts[DMA::N_CHANNELS_MAX];

    bool _portsEnabled[N_PORTS] = {false, false, false, false};


    void enable(Port port, unsigned long baudrate, bool hardwareFlowControl, CharLength charLength, Parity parity, StopBit stopBit) {
        const uint32_t REG_BASE = USART_BASE + static_cast<int>(port) * USART_REG_SIZE;
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Port configuration
        p->hardwareFlowControl = hardwareFlowControl;
        p->charLength = charLength;
        p->parity = parity;
        p->stopBit = stopBit;

        // Initialize the buffers
        for (int i = 0; i < BUFFER_SIZE; i++) {
            p->rxBuffer[i] = 0;
            p->txBuffer[i] = 0;
        }
        p->rxBufferCursorR = 0;
        p->rxBufferCursorW = 0;
        p->txBufferCursor = 0;

        // Set the pins in peripheral mode
        GPIO::enablePeripheral(PINS_RX[static_cast<int>(port)]);
        GPIO::enablePeripheral(PINS_TX[static_cast<int>(port)]);
        if (hardwareFlowControl) {
            GPIO::enablePeripheral(PINS_RTS[static_cast<int>(port)]);
            GPIO::enablePeripheral(PINS_CTS[static_cast<int>(port)]);
        }

        // Enable the clock
        PM::enablePeripheralClock(PM_CLK[static_cast<int>(port)]);

        // WPMR (Write Protect Mode Register) : disable the Write Protect
        (*(volatile uint32_t*)(REG_BASE + OFFSET_WPMR)) = WPMR_KEY | WPMR_DISABLE;

        // MR (Mode Register) : set the USART configuration
        (*(volatile uint32_t*)(REG_BASE + OFFSET_MR))
            = (hardwareFlowControl ? MODE_HARDWARE_HANDSHAKE : MODE_NORMAL) << MR_MODE // Hardware flow control
            | (static_cast<int>(charLength) & 0b11) << MR_CHRL  // Character length <= 8
            | (static_cast<int>(charLength) >> 2) << MR_MODE9   // Character length == 9
            | static_cast<int>(parity) << MR_PAR                // Parity
            | static_cast<int>(stopBit) << MR_NBSTOP;           // Length of stop bit

        // BRGR (Baud Rate Generator Register) : configure the baudrate generator
        // Cf datasheet 24.6.4 : baudrate = mainclock / (16 * CD), with FP to fine-tune by steps of 1/8
        p->baudrate = baudrate;
        const unsigned long clk = PM::getModuleClockFrequency(PM_CLK[static_cast<int>(port)]);
        const uint64_t cd100 = 100 * clk / 16 / baudrate; // cd100 = cd * 100
        const uint32_t cd = cd100 / 100;
        const uint64_t fp100 = (cd100 % 100) * 8;
        uint8_t fp = fp100 / 100;
        if (fp100 - fp * 100 >= 50) {
            fp++; // Round
        }
        if (cd == 0 || cd > 0xFFFF) {
            Error::happened(Error::Module::USART, ERR_BAUDRATE_OUT_OF_RANGE, Error::Severity::CRITICAL);
            return;
        }
        (*(volatile uint32_t*)(REG_BASE + OFFSET_BRGR))
            = cd << BRGR_CD
            | fp << BRGR_FP;

        // CR (Control Register) : enable RX and TX
        (*(volatile uint32_t*)(REG_BASE + OFFSET_CR))
            = 1 << CR_RXEN
            | 1 << CR_TXEN;

        // WPMR (Write Protect Mode Register) : re-enable the Write Protect
        (*(volatile uint32_t*)(REG_BASE + OFFSET_WPMR)) = WPMR_KEY | WPMR_ENABLE;

        // Set up the DMA channels and related interrupts
        p->rxDMAChannel = DMA::setupChannel(p->rxDMAChannel, static_cast<DMA::Device>(static_cast<int>(DMA::Device::USART0_RX) + static_cast<int>(port)), DMA::Size::BYTE);
        p->txDMAChannel = DMA::setupChannel(p->txDMAChannel, static_cast<DMA::Device>(static_cast<int>(DMA::Device::USART0_TX) + static_cast<int>(port)), DMA::Size::BYTE);
        _rxDMAChannelsToPorts[p->rxDMAChannel] = static_cast<int>(port);
        DMA::startChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer), BUFFER_SIZE);
        //DMA::reloadChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer), BUFFER_SIZE);
        DMA::enableInterrupt(p->rxDMAChannel, &rxBufferFullHandler, DMA::Interrupt::TRANSFER_FINISHED);

        _portsEnabled[static_cast<int>(port)] = true;
    }

    void disable(Port port) {
        // Don't do anything if this port is not enabled
        if (!_portsEnabled[static_cast<int>(port)]) {
            return;
        }
        _portsEnabled[static_cast<int>(port)] = false;

        const uint32_t REG_BASE = USART_BASE + static_cast<int>(port) * USART_REG_SIZE;
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Disable the DMA channels
        DMA::stopChannel(p->rxDMAChannel);
        DMA::stopChannel(p->txDMAChannel);

        // WPMR (Write Protect Mode Register) : disable the Write Protect
        (*(volatile uint32_t*)(REG_BASE + OFFSET_WPMR)) = WPMR_KEY | WPMR_DISABLE;

        // CR (Control Register) : disable RX and TX
        (*(volatile uint32_t*)(REG_BASE + OFFSET_CR))
            = 1 << CR_RXDIS
            | 1 << CR_TXDIS;

        // WPMR (Write Protect Mode Register) : re-enable the Write Protect
        (*(volatile uint32_t*)(REG_BASE + OFFSET_WPMR)) = WPMR_KEY | WPMR_ENABLE;

        // Disable the clock
        PM::disablePeripheralClock(PM_CLK[static_cast<int>(port)]);

        // Free the pins
        GPIO::disablePeripheral(PINS_RX[static_cast<int>(port)]);
        GPIO::disablePeripheral(PINS_TX[static_cast<int>(port)]);
        if (p->hardwareFlowControl) {
            GPIO::disablePeripheral(PINS_RTS[static_cast<int>(port)]);
            GPIO::disablePeripheral(PINS_CTS[static_cast<int>(port)]);
        }
    }

    void enableInterrupt(Port port, void (*handler)(), Interrupt interrupt) {
        const uint32_t REG_BASE = USART_BASE + static_cast<int>(port) * USART_REG_SIZE;

        // Save the user handler
        _interruptHandlers[static_cast<int>(port)][static_cast<int>(interrupt)] = (uint32_t)handler;

        // IER (Interrupt Enable Register) : enable the requested interrupt
        (*(volatile uint32_t*)(REG_BASE + OFFSET_IER))
            = 1 << IER_RXRDY;

        // Enable the interrupt in the NVIC
        Core::Interrupt interruptChannel = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::USART0) + static_cast<int>(port));
        Core::setInterruptHandler(interruptChannel, interruptHandlerWrapper);
        Core::enableInterrupt(interruptChannel, INTERRUPT_PRIORITY);
    }

    void interruptHandlerWrapper() {
        // Get the port number through the current interrupt number
        int port = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::USART0);
        const uint32_t REG_BASE = USART_BASE + port * USART_REG_SIZE;

        // Call the user handler of every interrupt that is enabled and pending
        for (int i = 0; i < N_INTERRUPTS; i++) {
            if ((*(volatile uint32_t*)(REG_BASE + OFFSET_IMR)) & (1 << _interruptBits[i]) // Interrupt is enabled
                    && (*(volatile uint32_t*)(REG_BASE + OFFSET_CSR)) & (1 << _interruptBits[i])) { // Interrupt is pending
                void (*handler)() = (void (*)())_interruptHandlers[port][i];
                if (handler != nullptr) {
                    handler();
                }
            }
        }

        // Clear the interrupts
        (*(volatile uint32_t*)(REG_BASE + OFFSET_CR))
            = 1 << CR_RSTSTA; // Reset status bits
    }

    void rxBufferFullHandler() {
        // Get the port that provoqued this interrupt
        int channel = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::DMA0);
        int portNumber = _rxDMAChannelsToPorts[channel];
        struct USART* p = &(_ports[portNumber]);

        // Reload the DMA channel
        int length = 0;
        int cur = p->rxBufferCursorW;
        if (p->rxBufferCursorR == p->rxBufferCursorW) {
            // Buffer is full, the DMA channel will be disabled
            // If hardware flow control is enabled this will pause the transfer by raising RTS,
            // otherwise if more data arrives it will trigger an overflow
            length = 0;

        } else if (p->rxBufferCursorR > p->rxBufferCursorW) {
            // Reload until the cursor
            length = p->rxBufferCursorR - p->rxBufferCursorW;
            p->rxBufferCursorW = p->rxBufferCursorR;

        } else { // p->rxBufferCursorR < p->rxBufferCursorW
            // Reload until the end of the linear buffer
            length = BUFFER_SIZE - p->rxBufferCursorW;
            p->rxBufferCursorW = 0;
        }

        if (length == 0) {
            DMA::stopChannel(p->rxDMAChannel);
        } else {
            DMA::reloadChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer + cur), length);
        }
    }

    int available(Port port) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Get the position of the second cursors from the DMA
        int cursor2 = p->rxBufferCursorW - DMA::getCounter(p->rxDMAChannel);

        // Get the position of the last char inserted into the buffer by DMA
        int length = cursor2 - p->rxBufferCursorR;
        if (length == 0) {
            // The buffer is either complety empty or complety full
            if (DMA::getCounter(p->rxDMAChannel) == 0) {
                // Complety full
                return BUFFER_SIZE;
            } else {
                // Complety empty
                return 0;
            }
        } else if (length > 0) {
            return length;
        } else {
            return BUFFER_SIZE + length; // length is negative here
        }
    }

    bool contains(Port port, char byte) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Check if the received buffer contains the specified byte
        int avail = available(port);
        for (int i = 0; i < avail; i++) {
            if (p->rxBuffer[(p->rxBufferCursorR + i) % BUFFER_SIZE] == byte) {
                return true;
            }
        }
        return false;
    }

    char peek(Port port) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        if (available(port) > 0) {
            // Return the next char in the port's RX buffer
            return p->rxBuffer[p->rxBufferCursorR];
        } else {
            return 0;
        }
    }

    bool peek(Port port, const char* test, int size) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        if (available(port) >= size) {
            // Check whether the /size/ next chars in the buffer are equal to test
            for (int i = 0; i < size; i++) {
                if (p->rxBuffer[(p->rxBufferCursorR + i) % BUFFER_SIZE] != test[i]) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    // Read one byte
    char read(Port port) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        if (available(port) > 0) {
            // Read the next char in the port's Rx buffer
            char c = p->rxBuffer[p->rxBufferCursorR];

            // Increment the cursor
            p->rxBufferCursorR++;
            if (p->rxBufferCursorR == BUFFER_SIZE) {
                p->rxBufferCursorR = 0;
            }

            // If the Rx DMA channel was stopped because the buffer was full,
            // there is now room available, so it can be restarted
            if (!DMA::isEnabled(p->rxDMAChannel)) {
                int cur = p->rxBufferCursorW;
                p->rxBufferCursorW++;
                if (p->rxBufferCursorW == BUFFER_SIZE) {
                    p->rxBufferCursorW = 0;
                }
                DMA::startChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer + cur), 1);
            }
            
            // Return the character that was read
            return c;

        } else {
            return 0;
        }
    }

    // Read up to /size/ bytes
    // If buffer is nullptr, the bytes are simply poped from the buffer
    int read(Port port, char* buffer, int size, bool readUntil, char end) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        int avail = available(port);
        int n = 0;
        for (int i = 0; i < size && i < avail; i++) {
            // Copy the next char from the port's Rx buffer to the user buffer
            char c = p->rxBuffer[p->rxBufferCursorR];
            if (buffer != nullptr) {
                buffer[i] = c;
            }

            // Keep track of the number of bytes written
            n++;

            // Increment the cursor
            p->rxBufferCursorR++;
            if (p->rxBufferCursorR == BUFFER_SIZE) {
                p->rxBufferCursorR = 0;
            }
            
            // If the "read until" mode is selected, exit the loop if the selected byte is found
            if (readUntil && c == end) {
                break;
            }
        }

        // If the Rx DMA channel was stopped because the buffer was full,
        // there is now room available, so it can be restarted
        if (!DMA::isEnabled(p->rxDMAChannel)) {
            int cur = p->rxBufferCursorW;
            int length = 0;
            if (p->rxBufferCursorR > p->rxBufferCursorW) {
                length = p->rxBufferCursorR - p->rxBufferCursorW;
            } else {
                length = BUFFER_SIZE - p->rxBufferCursorW;
            }
            p->rxBufferCursorW += length;
            if (p->rxBufferCursorW >= BUFFER_SIZE) {
                p->rxBufferCursorW -= BUFFER_SIZE;
            }
            DMA::startChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer + cur), length);
        }

        return n;
    }

    // Read up to n bytes until the specified byte is found
    int readUntil(Port port, char* buffer, int size, char end) {
        return read(port, buffer, size, true, end);
    }

    // Read an int on n bytes (LSByte first, max n = 8 bytes)
    // and return it as an unsigned long
    unsigned long readInt(Port port, int nBytes, bool wait) {
        if (nBytes > 8) {
            nBytes = 8;
        }

        if (wait) {
            while (available(port) < nBytes);
        }

        unsigned long result = 0;
        for (int i = 0; i < nBytes; i++) {
            uint8_t c = read(port);
            result |= c << (i * 8);
        }
        return result;
    }

    int write(Port port, const char* buffer, int size, bool async) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Wait until any previous write is finished
        waitWriteFinished(port);

        // If size is not specified, write at most BUFFER_SIZE characters
        int n = size;
        if (n < 0 || n > BUFFER_SIZE) {
            n = BUFFER_SIZE;
        }

        // Copy the user buffer into the Tx buffer
        for (int i = 0; i < n; i++) {
            // If size is not specified, stops at the end of the string
            if (size < 0 && buffer[i] == 0) {
                n = i;
                break;
            }
            p->txBuffer[i] = buffer[i];
        }

        // Start the DMA
        DMA::startChannel(p->txDMAChannel, (uint32_t)(p->txBuffer), n);

        // In synchronous mode, wait until this transfer is finished
        if (!async) {
            waitWriteFinished(port);
        }

        // Return the number of bytes written
        return n;
    }

    int write(Port port, char byte, bool async) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Wait until any previous write is finished
        waitWriteFinished(port);
        
        // Write a single byte
        p->txBuffer[0] = byte;
        DMA::startChannel(p->txDMAChannel, (uint32_t)(p->txBuffer), 1);

        // In synchronous mode, wait until this transfer is finished
        if (!async) {
            waitWriteFinished(port);
        }

        return 1;
    }

    int write(Port port, int number, uint8_t base, bool async) {
        // Write a human-readable number in the given base
        const int bufferSize = 32; // Enough to write a 32-bits word in binary
        char buffer[bufferSize];
        int cursor = 0;
        if (base < 2) {
            return 0;
        }

        // Special case : number = 0
        if (number == 0) {
            return write(port, '0');
        }

        // Minus sign
        if (number < 0) {
            buffer[cursor] = '-';
            cursor++;
            number = -number;
        }

        // Compute the number in reverse
        int start = cursor;
        for (; cursor < bufferSize && number > 0; cursor++) {
            char c = number % base;
            if (c < 10) {
                c += '0';
            } else {
                c += 'A' - 10;
            }
            buffer[cursor] = c;
            number = number / base;
        }

        // Reverse the result
        for (int i = 0; i < (cursor - start) / 2; i++) {
            char c = buffer[start + i];
            buffer[start + i] = buffer[cursor - i - 1];
            buffer[cursor - i - 1] = c;
        }

        buffer[cursor] = 0;
        cursor++;
        return write(port, buffer, cursor, async);
    }

    int write(Port port, bool boolean, bool async) {
        // Write a boolean value
        if (boolean) {
            return write(port, "true", 4, async);
        } else {
            return write(port, "false", 5, async);
        }
    }

    int writeLine(Port port, const char* buffer, int size, bool async) {
        int written = write(port, buffer, size, async);
        written += write(port, "\r\n", 2, async);
        return written;
    }

    int writeLine(Port port, char byte, bool async) {
        int written = write(port, byte, async);
        written += write(port, "\r\n", 2, async);
        return written;
    }

    int writeLine(Port port, int number, uint8_t base, bool async) {
        int written = write(port, number, base, async);
        written += write(port, "\r\n", 2, async);
        return written;
    }

    int writeLine(Port port, bool boolean, bool async) {
        int written = write(port, boolean, async);
        written += write(port, "\r\n", 2, async);
        return written;
    }

    void flush(Port port) {
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Stop the Rx channel
        DMA::stopChannel(p->rxDMAChannel);

        // Empty the reception buffer
        p->rxBufferCursorR = 0;
        p->rxBufferCursorW = 0;

        // Start the channel again
        DMA::startChannel(p->rxDMAChannel, (uint32_t)(p->rxBuffer), BUFFER_SIZE);
    }

    bool isWriteFinished(Port port) {
        const uint32_t REG_BASE = USART_BASE + static_cast<int>(port) * USART_REG_SIZE;
        struct USART* p = &(_ports[static_cast<int>(port)]);

        // Check if the DMA has finished the transfer and if the Tx buffer is empty
        return DMA::isFinished(p->txDMAChannel) && (*(volatile uint32_t*)(REG_BASE + OFFSET_CSR)) & (1 << CSR_TXEMPTY);
    }

    void waitWriteFinished(Port port) {
        // Wait for the transfer to finish
        while (!isWriteFinished(port));
    }

    void waitReadFinished(Port port, unsigned long timeout) {
        // Wait until no more bytes is received
        struct USART* p = &(_ports[static_cast<int>(port)]);
        unsigned long tStart = Core::time();
        int n = available(port);
        while (n == 0) {
            n = available(port);
            if (timeout > 0 && Core::time() - tStart > timeout) {
                return;
            }
        }
        unsigned long byteDuration = 1000000UL / p->baudrate * 8; // in us
        while (1) {
            Core::waitMicroseconds(5 * byteDuration); // Wait for 5 times the duration of a byte
            int n2 = available(port);
            if (n2 == n) {
                // No byte received during the delay, the transfer looks finished
                return;
            }
            n = n2;
        }
    }

    void setPin(Port port, PinFunction function, GPIO::Pin pin) {
        switch (function) {
            case PinFunction::RX:
                PINS_RX[static_cast<int>(port)] = pin;
                break;

            case PinFunction::TX:
                PINS_TX[static_cast<int>(port)] = pin;
                break;

            case PinFunction::RTS:
                PINS_RTS[static_cast<int>(port)] = pin;
                break;

            case PinFunction::CTS:
                PINS_CTS[static_cast<int>(port)] = pin;
                break;
        }
    }

}