Tuesday, September 1, 2015

More Soldering and Drivers

I think I just might be done with soldering. I soldered in a 12 pin header, the 5 connections from the timer CPLD to the header, and 2 posts (pulled from the 12 pin headers that I turned into 11 pin headers) so that I have out of the way ground points for the oscilloscope probes.

I have also been working on the UART drivers and the intel hex (*.ihx) drivers for flash programming.

Now, I'm in the process of a paper self code review.

The UART driver is fairly complex. It has to take a lot of stuff into account: interrupts, initialization, buffers, hardware flow control, support for timeouts and blocking calls.

I wrote this up, but haven't tested it or even compiled it. Also it has not gone through my initial self code review. But here are the uart.h, uart.c, ringBuffer.h, ringBuffer.c, spi.h, spi.c, gpio.h and gpio.c.

NOTE! Not tested or even compiled. The code below is guaranteed to fail. Do NOT copy and paste this thinking this is a working solution. You are free to copy and paste it--just don't expect it to work. When it's working, I'll post more. It is just to give you an idea of what I'm working on at the moment.

uart.h:

#ifndef INCLUDE_DRIVER_UART_H
#define INCLUDE_DRIVER_UART_H

#include "common.h"

// Initialize the UART. Note that the MAX3110E may take 25ms
// to stablize. So it may fail when called to early. Call until
// it returns true.
bool uartInit(uint16_t config);

// Reconfigure the UART
bool uartReconfig(uint16_t config);

// Adds b to the transmit buffer. Then attempts to send.
// Return true if successful or false if an error was
// encountered. If the error has a new UART_ERROR_TX_OVERFLOW bit,
// the the byte was not added.
// It is not a blocking function.
bool send(uint8_t b);

// Removes a byte from the receive buffer and returns in *pByte.
// returns true if successful or false an error was encountered
// If *pByte is -1, the buffer was empty but this does NOT indicate
// an error.
bool recv(int16_t* pByte);

// Block until something interesting happens. It won't necessarily be the desired event so the
// user still has to check. Also, released on system tick.
void pend();

// Sets/clears RTS. Use to stop transmission before doing something that may cause buffer overflow
bool setRTS(bool ready);

// Bit locations in the error status
#define UART_ERROR_INITIALIZATION_BIT       (0)
#define UART_ERROR_RX_OVERFLOW_BIT          (1)
#define UART_ERROR_FRAMING_BIT              (2)
#define UART_ERROR_PARITY_BIT               (3)
#define UART_ERROR_TX_OVERFLOW_BIT          (4)

// Gets the current errors. Also, clears any error bits sent in clear.
// If any of the UART calls return false, then this should be called to clear the error
// state before another call.
uint16_t uartErrorReadAndClear(uint16_t clear);

// UART Config
// Use these bits and BAUD rates with uartInit and uartReconfig
#define UART_BAUD_300               (0x0f)
#define UART_BAUD_600               (0x0e)
#define UART_BAUD_1200              (0x0d)
#define UART_BAUD_2400              (0x0c)
#define UART_BAUD_4800              (0x0b)
#define UART_BAUD_9600              (0x0a)
#define UART_BAUD_19200             (0x09)
#define UART_BAUD_14400             (0x03)
#define UART_BAUD_28800             (0x02)
#define UART_BAUD_38400             (0x08)
#define UART_BAUD_57600             (0x01)
#define UART_BAUD_115200            (0x00)
#define UART_7_BIT_WORDS_BIT        (4)
#define UART_PARITY_ENABLE_BIT      (5)
#define UART_TWO_STOP_BITS_BIT      (6)
//#define UART_IRDA_MODE_BIT          (7)
#define UART_RAM_INTERRUPT_BIT      (8)
#define UART_PM_INTERRUPT_BIT       (9)
#define UART_RM_INTERRUPT_BIT       (10)
#define UART_TM_INTERRUPT_BIT       (11)
//#define UART_FIFO_DISABLE_BIT       (13)

#define RESET_BIT(x, b) x &= ~(1<<b)
#define SET_BIT(x, b) x |= (1<<b)
#define SET_BIT_BY(x, b, v) x = (v ? (x | (1<<b)) : (x & ~(1<<b)))
#define IS_BIT(x, b) ((x & (1<<b)) != 0)

#endif


uart.c:

#include "gpio.h"
#include "spi.h"
#include "uart.h"
#include "ringBuffer.h"

// The UART code handles communication between the board and a PC
// It provides open/close/read/write. The read and write functions are
// blocking, but ring buffers enable some degree of buffering.
// The hardware supports CTS/RTS flow control
// Possible errors are read overflow, framing, parity
// Supported baud rates are 115.2K, 57.6K, 38.4K 19.2K 9600, 4800, 1200, 300
// Initialization may require several calls as the HW takes up to 25 ms 
// to stabilize.

#define UART_RECV_RING_BUFFER_SIZE      (64)
#define UART_XMIT_RING_BUFFER_SIZE      (64)

// For hardware flow control, set these:
// Stop threshold is the number of bytes used in the receive buffer above
// which RTS is deasserted. It must be less than UART_RECV_RING_BUFFER_SIZE
// Note that there is an 8 byte FIFO in the MAX3110E and that every write
// necessetates a read because of the exchange way the chip works
#define UART_RECV_RTS_STOP_THRESHOLD    (UART_RECV_RING_BUFFER_SIZE - 12)

// The restart provides a little bit of hysteresis for RTS control
// It must be less than UART_RECV_RTS_STOP_THRESHOLD
#define UART_RECV_RTS_RESTART_THRESHOLD (UART_RECV_RTS_STOP_THRESHOLD - 8)

// UART Read/Write
#define UART_PARITY_BIT_BIT             (8)
#define UART_RTS_CTS_BIT                (9)
#define UART_FRAMING_ERROR_BIT          (10)
#define UART_TRANSMIT_ENABLE_BIT        (10)

// UART Common
#define UART_TRANSMIT_EMPTY_BIT         (14)
#define UART_DATA_READY_BIT             (15)

uint16_t uartConfig = 0;

uint8_t recvRingBuffer[UART_RECV_RING_BUFFER_SIZE];
uint8_t xmitRingBuffer[UART_XMIT_RING_BUFFER_SIZE];

struct RingBuffer rxBuffer;
struct RingBuffer txBuffer;

#define UART_STATE_WAITING_ON_CTS       (0)
//#define UART_STATE_WAITING_ON_RX_HW     (1)
#define UART_STATE_WAITING_ON_TX_HW     (2)
#define UART_STATE_REQ_RTS_CHANGE       (3)
#define UART_STATE_REQ_RTS_LEVEL        (4)
#define UART_STATE_REQ_RM_CHANGE        (5)
#define UART_STATE_REQ_RM_LEVEL         (6)
#define UART_STATE_REQ_RAM_CHANGE       (7)
#define UART_STATE_REQ_RAM_LEVEL        (8)
//#define UART_STATE_WAITING_ON_RX_RB     (9)
//#define UART_STATE_WAITING_ON_TX_RB     (10)
#define UART_STATE_USER_RTS             (11)
#define UART_STATE_PENDING              (12)
uint16_t uartState;

#define UART_ERROR_TO_REPORT_BIT        (15)
int uartError;

uint16_t writeUartConfig(uint16_t config);
uint16_t readUartConfig();
uint16_t writeAndReadUart(uint8_t b);
uint16_t readUart();

///////////////////////////////////////////////////
//
//      Interface functions
//


// Initialize the UART. Note that the MAX3110E may take 25ms
// to stablize. So it may fail when called to early. Call until
// it returns true
bool uartInit(uint16_t config)
{
    bool success;

    // initial state
    uartState = (1 << UART_STATE_WAITING_ON_TX_HW);

    // create reing buffers
    ringBufferInit(&rxBuffer, recvRingBuffer, sizeof(recvRingBuffer));
    ringBufferInit(&txBuffer, xmitRingBuffer, sizeof(xmitRingBuffer));

    // Config part
    return uartReconfig(config);
}

// Reconfigure the UART (baud, stop bits, parity, bits per word, 
bool uartReconfig(uint16_t config)
{
    bool success;
    uartConfig = config & 0xc000;
    writeUartConfig(config);
    success = ((config ^ readUartConfig()) & ~0xc000) == 0;
    SET_BIT_BY(uartError, UART_ERROR_INITIALIZATION_BIT, success);
    RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting now
    return success;
}

// Sends b out the serial port.
// If there are bytes in the xmit buffer, it is added.
// IF the xmit buffer is empty and the UARt can take a byte,
// then the byte is written out.
// If the byte cannot be written out, it is added to the xmit
// buffer.
bool send(uint8_t b)
{
    if (!ringBufferFull(&txBuffer))
    {
        ringBufferEnqueue(&txBuffer, b);
    }
    else
    {
        SET_BIT(uartError, UART_ERROR_TX_OVERFLOW_BIT);
    }
    RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting
    return (uartError & 0x7fff) != 0;
}

// A non-blocking recv. It returns true if OK, false if an error
// The pByte will get (int)-1 if there is nothing to receive
// If an error has occured, return false // TODO
// If the recv buffer is not empty, dequeue and return
// Else set the read pending bit and spin wait
bool recv(int16_t *pByte)
{
    if (!ringBufferEmpty(&rxBuffer))
    {
        *pByte = ringBufferDequeue(&rxBuffer);
        
        // if falls below RTS threshold, then turn RTS back on
        if (IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL) && !IS_BIT(uartState, UART_STATE_USER_RTS))
        {
            if (ringBufferCount(&rxBuffer) < UART_RECV_RTS_RESTART_THRESHOLD)
            {
                SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
                SET_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
            }
        }
    }
    else
    {
        *pByte = -1;
    }
    RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting
    return (uartError & 0x7fff) != 0;
}

// Use for creating blocking calls. The user can pass an idle function and a pointer that will be
// continually called until the call ends
void pend(void(*cbIdleFn)(void* p), void* p)
{
    SET_BIT(uartState, UART_STATE_PENDING);
    while (IS_BIT(uartState, UART_STATE_PENDING))
    {
        // Do nothing or do idle
        if (cbIdleFn)
        {
            cbIdleFn(p);
        }
    }
}

void configRTS(bool config)
{
    // if turning back on, only request change if off and threshold is not met
    SET_BIT(uartState, UART_STATE_USER_RTS, config);

    bool reqLevel = config && ringBufferCount(&txBuffer) < UART_RECV_RTS_STOP_THRESHOLD;
    bool curLevel = IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
    if (reqLevel != curLevel)
    {
        SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
        SET_BIT_BY(uartState, UART_STATE_REQ_RTS_LEVEL, reqLevel);
        readWrite();
    }
}

uint16_t uartErrorReadAndClear(uint16_t clear)
{
    uartError &= ~clear;
    return uartError;
}

///////////////////////////////////////////////////
//
//      Internal functions
//

// This is the work horse function that does the actual reading and writing through the UART.
// It handles the RX, TX, RTS, CTS, errors, interrupt enables, 
bool readWrite()
{
    uint16_t read;
    uint16_t write = 0;
    bool te = false; // use readWrite instead of read, but if nothing to write, use TE#
    bool loop = true;
    bool wrote = false;
    bool errorOccured = false;
    while (loop)
    {
        loop = false;

        // See if reconfig is necesary
        if (uartState & ((1 << UART_STATE_REQ_RM_CHANGE) | (1 << UART_STATE_REQ_RAM_CHANGE)))
        {
            // clear requests
            uartState &= ~((1 << UART_STATE_REQ_RM_CHANGE) | (1 << UART_STATE_REQ_RAM_CHANGE));

            if (uartState & (1 << UART_STATE_REQ_RM_CHANGE))
            {
                SET_BIT_BY(uartConfig, UART_RM_INTERRUPT_BIT, IS_BIT(uartState, UART_STATE_REQ_RM_LEVEL));
            }
            if (uartState & (1 << UART_STATE_REQ_RAM_CHANGE))
            {
                SET_BIT_BY(uartConfig, UART_RAM_INTERRUPT_BIT, IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL));
            }
            writeUartConfig(uartConfig);
        }

        // See if reconfig is necesary
        write = 0x0000;
        if (IS_BIT(uartState, UART_STATE_REQ_RTS_CHANGE))
        {
            SET_BIT_BY(write, UART_RTS_CTS_BIT, IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL));
            te = true;
        }
        if (!wrote && (uartState & ((1 << UART_STATE_WAITING_ON_CTS) | (1 << UART_STATE_WAITING_ON_TX_HW))) == 0)
        {
            wrote = true; // We'll write once per call to this function because the HW has a read FIFO but not a write FIFO, so may read multiples
            if (!ringBufferEmpty(&txBuffer))
            {
                uint8_t b = ringBufferDequeue(&txBuffer);
                write |= 0x8000 | b;
                te = false; // if requestiung a TE# write, no need because we're writing anyway
            }
        }

        // When there is nithing to write, but we want to update RTS, we need to set the
        // UART_TRANSMIT_ENABLE_BIT in the write side exchange
        if (te)
        {
            te = false;
            write |= 0x8000 | (1 << UART_TRANSMIT_ENABLE_BIT);
        }
        read = writeAndReadUart(write);

        if (IS_BIT(read, UART_FRAMING_ERROR_BIT))
        {
            if (IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL))
            {
                SET_BIT(uartState, UART_STATE_REQ_RAM_CHANGE);
                SET_BIT(uartState, UART_STATE_REQ_RAM_LEVEL);
                SET_BIT(uartError, UART_ERROR_FRAMING_BIT);
                loop = true; // need to change RAM
                errorOccured = true;
            }
        }
        else if (IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL))
        {
            SET_BIT(uartState, UART_STATE_REQ_RAM_CHANGE);
            RESET_BIT(uartState, UART_STATE_REQ_RAM_LEVEL);
            loop = true; // need to change RAM
        }

        // keep track of last T bit
        SET_BIT_BY(uartState, UART_STATE_WAITING_ON_TX_HW, IS_BIT(read, UART_TRANSMIT_EMPTY_BIT));

        // keep track of CTS
        SET_BIT_BY(uartError, UART_STATE_WAITING_ON_CTS, IS_BIT(read, UART_RTS_CTS_BIT));

        if (IS_BIT(read, UART_PARITY_BIT_BIT))
        {
            SET_BIT(uartError, UART_ERROR_PARITY_BIT);
            errorOccured = true;
        }


        if (IS_BIT(read, UART_DATA_READY_BIT))
        {
            if (!ringBufferFull(&rxBuffer))
            {
                ringBufferEnqueue(&rxBuffer, (uint8_t)read);
                if (!IS_BIT(uartConfig, UART_STATE_REQ_RTS_LEVEL) && ringBufferCount(&rxBuffer) >= UART_RECV_RTS_STOP_THRESHOLD)
                {
                    // buffer getting too full, lets hold off on 
                    SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
                    RESET_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
                    SET_BIT(uartState, UART_STATE_REQ_RM_CHANGE);
                    RESET_BIT(uartState, UART_STATE_REQ_RM_LEVEL);
                    loop = true; // need to take down RTS
                }
            }
            else
            {
                SET_BIT(uartError, UART_ERROR_RX_OVERFLOW_BIT);
                errorOccured = true;
            }
        }
    }
    if (errorOccured)
    {
        SET_BIT(uartError, UART_ERROR_TO_REPORT_BIT);
    }
}


// UART ISR
// called from the actual ISR, so this one doesn't end in reti
void uartIsr()
{
    // Do the read/write which will clear R&RM, T&TM, and RA&RAM, and update the RX buffers, and
    // send a byte if there is one on the TX buffer
    readWrite();

    // This ISR might be called on any ISR, so it's possible that nothing interesting happened.
    // It is also called on system tick, so even though the UART has no information, it
    // is possible to take advantage of this to implement timeouts
    RESET_BIT(uartState, UART_STATE_PENDING);
}

///////////////////////////////////////////////////
//
//      Low Level functions
//

uint16_t writeUartConfig(uint16_t config)
{
    uartConfig = config & 0x3fff;
    return uartExchange(0xC000 | uartConfig);
}

uint16_t readUartConfig()
{
    return uartExchange(0x4000 | uartConfig);
}

uint16_t writeAndReadUart(uint16_t write)
{
    return uartExchange(0x8000 | write);
}

uint16_t readUart()
{
    return uartExchange(0x0000);
}

// Low level function that used the SPI/GPIO module to exchange a 16 word
// with the MAX3110E
uint16_t uartExchange(uint16_t write)
{
    uint16_t read;
    gpioWriteLevel(0, false);
    read = spiExchange((uint8_t)(write >> 8), SPI_SHIFT_TO_MSB, 8);
    read <<= 8;
    read |= spiExchange((uint8_t)(write), SPI_SHIFT_TO_MSB, 8);
    gpioWriteLevel(1, false);
}


ringBuffer.h:

#ifndef INCLUDE_RING_BUFFER_H
#define INCLUDE_RING_BUFFER_H

#include "common.h"

struct RingBuffer
{
    uint8_t* buffer;
    int count;
    int enqueuePoint;
    int dequeuePoint;
};

// Prepare a ring buffer for use
void ringBufferInit(struct RingBuffer* rb, uint8_t* buffer, int count);

// Query if ring buffer is empty
bool ringBufferIsEmpty(struct RingBuffer* rb);

// Query if ring buffer is full
bool ringBufferIsFull(struct RingBuffer* rb);

// Query  number of items in ring buffer
int ringBufferCount(struct RingBuffer* rb);

// Add a byte into the ring buffer
void ringBufferEnqueue(struct RingBuffer* rb, uint8_t b);

// Gets an item if available. The user can send an optional
// pointer to bool which will be true if data was retrieved
uint8_t ringBufferDequeue(struct RingBuffer* rb);
#endif


ringBuffer.c:

#include "ringBuffer.h"


// This is a ring buffer for uint8_t of arbitrary length
// It is empty when enqueuePoint == dequeuePoint


// Initialize a new ring buffer. User supplies
void ringBufferInit(struct RingBuffer* rb, uint8_t* buffer, int count)
{
    rb->buffer = buffer;
    rb->count = count;
    rb->enqueuePoint = 0;
    rb->dequeuePoint = 0;
}

bool ringBufferIsEmpty(struct RingBuffer* rb)
{
    return rb->enqueuePoint == rb->dequeuePoint;
}

// The buffer is full if the enqueuePoint is one less than the dequeue
// point. If the dequeue point is 0, then the 
bool ringBufferIsFull(struct RingBuffer* rb) __critical
{
    int diff = rb->dequeuePoint - rb->enqueuePoint;
    return diff == 1 || ((diff + rb->count) == 1);
}

// Gets the number of items in the ring buffer
int ringBufferCount(struct RingBuffer* rb) __critical
{
    int diff = rb->dequeuePoint - rb->enqueuePoint;
    if (diff < 0)
    {
        diff += rb->count;
    }
    return diff;
}

// Adds a point to the ring buffer if space is available
// Returns true if the item was successfully added
void ringBufferEnqueue(struct RingBuffer* rb, uint8_t b) __critical
{
    assert(!ringBufferIsFull(rb));
    int p = rb->enqueuePoint;
    rb->buffer[p] = b;
    if (p == (rb->count - 1))
    {
        p = 0;
    }
    else
    {
        p++;
    }
    rb->enqueuePoint = p;
}

// Gets an item if available. The user can send an optional
// pointer to bool which will be true if data was retrieved
uint8_t ringBufferDequeue(struct RingBuffer* rb) __critical
{
    uint8_t b = 0;
    assert(!ringBufferIsEmpty(rb));
    int p = rb->dequeuePoint;
    b = rb->buffer[p];
    if (p == (rb->count - 1))
    {
        p = 0;
    }
    else
    {
        p++;
    }
    rb->dequeuePoint = p;

    return b;
}


spi.h:

#ifndef INCLUDE_DRIVER_SPI_H
#define INCLUDE_DRIVER_SPI_H

#include "common.h"

#define SPI_SHIFT_TO_MSB (0x20)
#define SPI_SHIFT_TO_LSB (0x00)

#define SPI_DEFAULT_HIGH (0x00)
#define SPI_DEFAULT_LOW (0x00)

#define SPI_INVERT_SCK (0x00)
#define SPI_DIRECT_SCK (0x00)

// Exchange 0-8 bits on SPI 
uint8_t spiExchange(uint8_t b, uint8_t flags, uint8_t count);

#endif


spi.c:

#include "spi.h"

Z80_IO_PORT(SPI_REG, 0x83);
Z80_IO_PORT(SPI_CTRL, 0x84);

// Exchange 0-8 bits on SPI 
uint8_t spiExchange(uint8_t b, uint8_t flags, uint8_t count)
{
    SPI_REG = b;
    SPI_CTRL = flags | count;

    // delay for SPI clock
    __asm__("nop");
    __asm__("nop");
    __asm__("nop");
    __asm__("nop");

    return SPI_REG;
}


gpio.h:

#ifndef INCLUDE_DRIVER_GPIO_H
#define INCLUDE_DRIVER_GPIO_H

#include "common.h"

// Gets the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
bool gpioReadDirection(int bit);

// Writes the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
void gpioWriteDirection(int bit, bool out);

// Gets the level to drive if GPIO 'bit' is an output
bool gpioReadOutLevel(int bit);

// Sets the level to drive if GPIO 'bit' is an output
void gpioWriteLevel(int bit, bool out);

// Gets the actual level of GPIO 'bit'
bool gpioReadInLevel(int bit);

#endif


gpio.c:

#include "gpio.h"

Z80_IO_PORT(GPIO_DIR, 0x80);
Z80_IO_PORT(GPIO_OUT, 0x81);
Z80_IO_PORT(GPIO_IN, 0x82);

// Gets the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
bool gpioReadDirection(int bit)
{
    assert(bit >= 0 && bit < 7);
    return (GPIO_DIR & (1 << bit)) != 0;
}

// Writes the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
void gpioWriteDirection(int bit, bool out)
{
    int b;
    assert(bit >= 0 && bit < 7);
    b = GPIO_DIR;
    if (out)
    {
        b |= (1 << bit);
    }
    else
    {
        b &= ~(1 << bit);
    }
    GPIO_DIR = b;
}

// Gets the level to drive if GPIO 'bit' is an output
bool gpioReadOutLevel(int bit)
{
    assert(bit >= 0 && bit < 7);
    return (GPIO_OUT & (1 << bit)) != 0;
}

// Sets the level to drive if GPIO 'bit' is an output
void gpioWriteLevel(int bit, bool out)
{
    int b;
    assert(bit >= 0 && bit < 7);
    b = GPIO_OUT;
    if (out)
    {
        b |= (1 << bit);
    }
    else
    {
        b &= ~(1 << bit);
    }
    GPIO_OUT = b;
}

// Gets the actual level of GPIO 'bit'
bool gpioReadInLevel(int bit)
{
    assert(bit >= 0 && bit < 7);
    return (GPIO_IN & (1 << bit)) != 0;
}


No comments:

Post a Comment