ZDev Interrupt Mode Serial Driver for LPC1343

The current version of ZDev has polling mode driver for the LPC1343 serial port. Since the serial controller in the LPC1343 has a in-built FIFO this is sufficient in simple applications. In applications, where the in-built FIFO is insufficient, this article shows how to implement a interrupt mode driver with a larger software FIFO.

Polling Mode

In the LPC1343, the Receiver Buffer Register is used to receive single byte data from UART. The hardware FIFO (16 bytes) can be used to store upto 16 bytes of received data. If the UART receives more then 16 bytes before the data is read from the hardware FIFO the data will be overwritten. This will cause loss of data.

Interrupt Mode

An interupt handler is registered to handle serial port interrutps, and the receive interrupt for the serial port is enabled. In the interrupt handler, the data is read from the serial controller and stored in a large software FIFO. The application when it requires data from the serial port, will read the data from the software FIFO.

http://www.zilogic.com/static/images/serial-fifo-mode.png
Figure 1. Interrupt Mode Design

Software

The C source file and the header file implementing the serial mode driver is provided below.

#include <stdlib.h>
#include <irq.h>
#include <fifo.h>
#include <mmio.h>
#include <lpc13xx/sysconfig.h>

#define UART_BASE_ADDRESS 0x40008000

enum serial_registers {
        U0RBR = UART_BASE_ADDRESS,        /* READ  */
        U0THR = UART_BASE_ADDRESS,        /* WRITE */
        U0IER = UART_BASE_ADDRESS + 0x04,
        U0IIR = UART_BASE_ADDRESS + 0x08, /* READ  */
        U0FCR = UART_BASE_ADDRESS + 0x08, /* WRITE */
        U0LCR = UART_BASE_ADDRESS + 0x0C,
        U0LSR = UART_BASE_ADDRESS + 0x14,
        U0FDR = UART_BASE_ADDRESS + 0x28,

        /* DLAB = 1 */
        U0DLL = UART_BASE_ADDRESS,
        U0DLM = UART_BASE_ADDRESS + 0x04,
};

enum line_status_values {
        LSR_THRE = (0x01<<5),
        LSR_TEMT = (0x01<<6),
        LSR_RDR  = (0x01<<0)
};

enum line_ctrl_values {
        DATA_8B = 0x3,

        STOP1B = 0,
        STOP2B = (1 << 2),

        PARITY_DIS = 0,
        PARITY_EN = (1 << 3),

        PARITY_ODD = 0,
        PARITY_EVEN = (1 << 4),

        CONFIG_8N1 = (DATA_8B | STOP1B | PARITY_DIS),

        BR_DIS = 0,
        BR_EN = (1 << 6),

        DLAB_DIS = 0,
        DLAB_EN = (1 << 7)
};

static fifo_t serial_cbuf;

/*
 * This function set as interrupt call back.
 */
static void serial_interrupt()
{
        uint32_t data;

        /* Read data form Receiver Buffer Register and store it to software FIFO */
        data = mmio_read(U0RBR);
        fifo_add(&serial_cbuf, data);
}

/*
 * Set baud-rate, and serial port parameters.
 */
void my_serial_set_baudrate(uint32_t baudrate)
{
        uint32_t uart_div, fdiv;

        mmio_write(U0LCR, (CONFIG_8N1 | DLAB_EN));

        uart_div = mmio_read(UARTCLKDIV);
        fdiv = ((CPU_CLOCK/uart_div) / 16) / baudrate;

        mmio_write(U0DLM, fdiv / 256);
        mmio_write(U0DLL, fdiv % 256);
}

/*
 * Initialize the serial transmission with Interrupt FIFO
 */
void my_serial_init_with_fifo(uint8_t *buf, uint8_t size)
{
        ioconfig_serial_en();
        sysconfig_clk_en(SYSAHBCLKCTRL_UART_EN);
        sysconfig_serial_clk_div(0x01);
        serial_set_baudrate(9600);

        fifo_init(&serial_cbuf, buf, size);

        /*  Set UART interrupt */
        irq_setcb(IRQ_UART, serial_interrupt);

        /* Enable UART interrupt */
        irq_enable(IRQ_UART);

        /* Enables the Receive Data Available interrupt for UART.*/
        mmio_set_bit(U0IER, 0, 1);
}

/*
 * Initialize the serial transmission port
 */
void my_serial_init()
{
        static char buf;
        my_serial_init_with_fifo(&buf, 1);
}

static void fifo_lock()
{
        /* Disables the Receive Data Available interrupt for UART.*/
        mmio_set_bit(U0IER, 0, 0);
}

static void fifo_unlock()
{
        /* Enables the Receive Data Available interrupt for UART.*/
        mmio_set_bit(U0IER, 0, 1);
}

/*
 * Check whether the FIFO has valid data or not
 */
bool my_serial_tstc()
{
        return !fifo_is_empty(&serial_cbuf);
}

/*
 * Pick the data from Software FIFO
 */
char my_serial_getc()
{
        char ret;

        while (!my_serial_tstc());

        fifo_lock();
        ret = fifo_remove(&serial_cbuf);
        fifo_unlock();

        return ret;
}

/*
 * Transmit a single byte data
 */
void my_serial_putc(char ch)
{
        while(!(mmio_read(U0LSR) & LSR_THRE));
        mmio_write(U0THR, ch);
}

Serial Initialize With FIFO

The steps to initialize the serial controller in interrupt mode is shown below.

/* Step 1: Initialize serial port, clock, IOs and serial params. */
ioconfig_serial_en();
sysconfig_clk_en(SYSAHBCLKCTRL_UART_EN);
sysconfig_serial_clk_div(0x01);
serial_set_baudrate(9600);

/* Step 2: Initialize software FIFO */
fifo_init(&serial_cbuf, buf, size);

/* Step 3: Set the interrupt handler. */
irq_setcb(IRQ_UART, serial_interrupt);

/* Step 4: Enable UART interrupt. */
irq_enable(IRQ_UART);

/* Step 5: Enable the RX interrupt of UART.*/
mmio_set_bit(U0IER, 0, 1);

Interrupt Handler

The steps to handle the interrupt is shown below.


/* Step 1: Read the data from the serial port. This also acknowledges
 * the interrupt.
 */
data = mmio_read(U0RBR);
/* Step 2: Store the data in the software FIFO. */
fifo_add(&serial_cbuf, data);

Serial Read Character

The steps to read the data from the software FIFO, when application wants to read data from the serial port, is shown below.

/* Step 1: Wait till data is availabe in software FIFO. */
while (!fifo_is_empty(&serial_cbuf););

/* Step 2: Disable receive interrupt, to avoid racing with interrupt handler .*/
fifo_lock();
/* Step 3: Remove a byte from the software FIFO. */
ret = fifo_remove(&serial_cbuf);
/* Step 4: Re-enable the receive interrupt. */
fifo_unlock();

References