Yet another UART implementation

Archived XCore tutorials.
User avatar
skoe
Experienced Member
Posts: 94
Joined: Tue Apr 27, 2010 10:55 pm

Yet another UART implementation

Post by skoe »

Hi,

this is another UART implementation I made to learn some basics about XMOS programming. It was made to be part of a command line interface for my software. Not all features of the CLI are fully working yet, that's why it's not open source yet.

I put the UART implementation here because it may be useful for others. It was made with low resource and code size in mind while having a reasonable performance. If you think that something should or may be improved, please post it here.

More details can be found in the comments.

Have fun,
Thomas

(wanted to attach it as file, but *.xc and *.txt are not allowed :/ )

Code: Select all

/* 
 * Low resource UART implementation.
 * Version 0.1
 * 
 * (c) 2010 Thomas 'skoe' Giesel
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * Thomas Giesel skoe@directbox.com
 *
 * Usage:
 *  Create a streaming channel between a thread (e.g. cli_thread) and
 *  uart_thread. Input from UART is always and only sent to cli_thread.
 *  Any other thread can send characters to UART by calling uart_putc().
 *  This function allocates a temporary channel end, sends the character
 *  and a stop token and releases the temporary channel end.
 *
 *  --------------                           ---------------
 *  | cli_thread | streaming chan            | uart_thread |
 *  |            |<=========================>|             |
 *  |            |                 =========>|             |
 *  --------------                /          ---------------
 *                           uart_putc()
 *                              /
 *                       --------------
 *                       | any_thread |-
 *                       |            ||
 *                       --------------|
 *                        --------------
 *
 *  cli_thread can send characters to uart_thread directly, each character
 *  must be sent as int and terminated with a stop token:
 *  {
 *      int c = 'a';
 *      uart_ce <: c;
 *      soutct(uart_ce, XS1_CT_END);
 *  }
 *
 *  Bytes received from UART are sent to the channel without stop tokens.
 *  The receiver thread can read them or generate events from them (select)
 *  directly:
 *  {
 *      int c;
 *      uart_ce :> c;
 *  }
 *
 *  This version was tested with up to 500 kbps with compiler optimizations
 *  enabled. When all 8 threads are in use, possibly only lower speeds can
 *  be reached.
 *
 *  I didn't fetch my logic analyser to check if everything is okay end where
 *  the timing could still be improved. Mhh, this could be done with the XMOS
 *  simulation and analysis tools, I guess.
 */

#include <xs1.h>
#include <autoconf.h>
#include <uart.h>

#define BIT_RATE 115200
//#define BIT_RATE 500000

#define BIT_TIME ((XS1_TIMER_HZ + BIT_RATE / 2) / BIT_RATE)

// Ports for RX and TX, must be 1 bit ports
out port txd = PORT_UART_TX;
in  port rxd = PORT_UART_RX;

// copy of channel end id for uart thread
static unsigned uart_chanend;


/*******************************************************************************
 * This is the UART thread. Really.
 *
 ******************************************************************************/
void uart_thread(streaming chanend ce)
{
    /* 
     * State for TX: We put also the stop bit into this sequence. We send and
     * shift this value until it becomes 0. Then all bits have been sent. When
     * the last bit is shifted out, we may start to send the next byte
     * imediately. That's why the last bit is set two times to get a whole
     * stop bit.
     */
    unsigned tx_value = 0;

    /*
     * State for RX: Wenn we receive a start bit, we put 1 << 31 into this
     * variable. Each time we get a bit we shift it into this value until the
     * the former 1 << 31 reached position 1 << 22. Then the RX byte incl. 
     * stop bit is complete.
     */
    unsigned rx_value = 0;

    int t_next;
    int t_next_rx;
    int t_next_tx;
    timer t;

    asm (
            "stw    %0, dp[uart_chanend]    \n"
            : : "r"(ce)
    );

    // Make sure the tx line is high (idle) for a while
    txd <: 1;
    t :> t_next;
    t_next += 10 * BIT_TIME;
    t when timerafter(t_next) :> void;

    for (;;)
    {
        select
        {
            case !tx_value => ce :> tx_value:
                // discard stop token
                sinct(ce);
                // add one stop bit, it is set two times 
                // because we have no pause after the last bit in the loop below
                tx_value = (tx_value & 0xff) | 0x300;
                // this order because the next bits will also have overhead
                t :> t_next_tx;
                t_next_tx += BIT_TIME;
                // start bit
                txd <: 0;

                // check which will be the next timed event
                if (rx_value && t_next_tx - t_next_rx > 0)
                    t_next = t_next_rx;
                else
                    t_next = t_next_tx;
                break;

            case !rx_value => rxd when pinsneq(1) :> void:
                t :> t_next_rx;
                t_next_rx += BIT_TIME + BIT_TIME / 2;
                // mark that rx is in progress
                rx_value = 1 << 31;
                
                // calculate which will be the next timed event
                if (tx_value && t_next_rx - t_next_tx > 0)
                    t_next = t_next_tx;
                else
                    t_next = t_next_rx;
                break;

            case tx_value || rx_value => t when timerafter(t_next) :> void:
                if (rx_value && t_next == t_next_rx)
                {
                    rxd :> >> rx_value;
                    // rx complete?
                    if (rx_value & (1 << 22)) 
                    {
                        ce <: (rx_value >> 23) & 0xff;
                        rx_value = 0;
                    }
                    else
                        t_next_rx += BIT_TIME;
                }
                if (tx_value && t_next == t_next_tx)
                {
                    txd <: >> tx_value;
                    t_next_tx += BIT_TIME;
                }

                // check which will be the next timed event
                if (rx_value)
                {
                    if (tx_value && t_next_rx - t_next_tx > 0)
                        t_next = t_next_tx;
                    else
                        t_next = t_next_rx;
                }
                else
                    t_next = t_next_tx;                    
                break;
        }
    }
}

/*******************************************************************************
 * Print the given character to UART. Wait if the queue is full.
 * May be called from different threads.
 *
 ******************************************************************************/
void uart_putc(char c)
{
    asm 
    (
        // allocate channel end
        // todo: check if it was available
        "getr    r2, 2                  \n" /* XS1_RES_TYPE_CHANEND */
    
        // set channel destination
        "ldw     r3, dp[uart_chanend]   \n"
        "setd    res[r2], r3            \n"
        // send byte and end token
        "out     res[r2], %0            \n"
        "outct   res[r2], 1             \n" /* XS1_CT_END */
        // release channel end
        "freer   res[r2]                \n"
        : /* no output */
        : "r"(c)
        : "r2", "r3"
    );

    // avoid warning
    (void)uart_chanend;
}
Last edited by skoe on Wed May 19, 2010 8:38 am, edited 2 times in total.
User avatar
TonyD
XCore Addict
Posts: 234
Joined: Thu Dec 10, 2009 11:11 pm
Location: Newcastle, UK

Post by TonyD »

Thanks for sharing Thomas.
User avatar
skoe
Experienced Member
Posts: 94
Joined: Tue Apr 27, 2010 10:55 pm

Post by skoe »

Seems I should add something:

This implementation does only use one thread for rx and tx. The advantage is that it doesn't occupy more threads, the disadvantage is that it may stall if the application thread (cli_thread in the example) is busy for a longer time and doesn't receive data for a while.

Refer to http://www.xcore.com/forum/viewtopic.php?f=12&t=445 for the reason.

To avoid this, make sure the application thread reads data faster than UART can send them. The application thread must not block while sending data to UART while the UART thread tries to send data (more than 2 values) to the application thread.

I'll add a software FIFO one day to ease this issue a bit.

btw: As far as I can see the "official" 1-thread UART implementation v1.1 has the same potential issue.

Regards,
Thomas
User avatar
jason
XCore Expert
Posts: 577
Joined: Tue Sep 08, 2009 5:15 pm

Post by jason »

Thanks for pointing this out - if you edit your post you should be able to upload that as xc or txt file. Let me know if it doesnt work!
User avatar
xcorific
Member++
Posts: 17
Joined: Fri Aug 12, 2011 2:36 pm

Post by xcorific »

Any updates on this project? I'm gradually going through the forum, picking up tidbits of information along the way, and your project is interesting. I am going to see (learn) whether or not it's possible to support 8 or more RS232 connections (w/o hardware flow control) with a single G4 processor, and perhaps your project is a good place to start. I have already done the official UART tutorial, which was also very helpful.