Asynchronous Serial Library with flow control and tag detection.

source/AsyncSerial.cpp

Committer:
marcuschang
Date:
2015-04-28
Revision:
21:bdc3c69a1cd7
Parent:
20:c36866a2d5a6

File content as of revision 21:bdc3c69a1cd7:

/* mbed Microcontroller Library
 * Copyright (c) 2006-2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "AsyncSerial/AsyncSerial.h"

#include "mbed.h"

#define MINIMUM_TIMEOUT 1


AsyncSerial::AsyncSerial(PinName tx, PinName rx, PinName rts, PinName cts)
    :   SerialBase(tx, rx),

        sendBuffer(NULL),
        sendLength(0),
        sendIndex(0),

        receiveBuffer(NULL),
        receiveMaxLength(0),
        receiveIndex(0),
        receiveStatus(AsyncSerial::RECEIVE_TIMEOUT),
        receiveResult(),
        timeout(),

        insideCondition(false),
        conditionStartBuffer(NULL),
        conditionStartLength(0),
        conditionEndBuffer(NULL),
        conditionEndLength(0),
        conditionIndex(0),

        sendHandler(),
        receiveHandler(),
        waitHandler()
{
#if DEVICE_SERIAL_FC
    if ((rts != NC) && (cts != NC))
    {
        SerialBase::set_flow_control(SerialBase::RTSCTS, rts, cts);
    }
#endif
}

AsyncSerial::~AsyncSerial()
{
    SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::RxIrq);
    SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::TxIrq);
}

/*  Tx ISR
*/
void AsyncSerial::putDone()
{
    // fill Tx buffers (in case the buffer can contain more than 1 byte)
    while(SerialBase::writeable())
    {
        if(sendIndex < sendLength)
        {
            // send next character if there is still more to send
            SerialBase::_base_putc(sendBuffer[sendIndex]);

            // sendIndex points to the next byte to send
            sendIndex++;
        }
        else
        {
            // disable the TX interrupt when there is nothing left to send
            SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::TxIrq);

            // signal callback function
            sendHandler.call();
            break;
        }
    }
}

/*  Rx ISR
*/
void AsyncSerial::getReady()
{
    // read while there are characters in buffer
    while(SerialBase::readable())
    {
        // read single character from buffer
        uint8_t input = SerialBase::_base_getc();

        DEBUG("%c", input);

        // check if start condition has been met
        if (insideCondition)
        {
            /*  If stop condition has been set, check if the
                character matches. If it does increment counter
                to point to next character in sequence. Otherwise
                reset sequence search.
            */
            if (conditionEndBuffer != NULL)
            {
                if (input == conditionEndBuffer[conditionIndex])
                {
                    conditionIndex++;

                    /*  End condition has been met.
                        Set receive status to indicate sequence has been found
                        and re-arm timer. The timeout is responsible for
                        signaling the callback function and is useful for
                        decoupling the callback from the receive ISR.
                    */
                    if (conditionIndex == conditionEndLength)
                    {
                        // Disable Rx interrupt
                        SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::RxIrq);

                        // Fire timeout to signal callback
                        receiveStatus = AsyncSerial::RECEIVE_FOUND;
                        timeout.attach_us<AsyncSerial>(this, &AsyncSerial::receiveTimeout, MINIMUM_TIMEOUT);
                        break;
                    }
                }
                else
                {
                    // Character didn't match sequence. Start over.
                    conditionIndex = 0;
                }
            }

            /*  A receive buffer is available.
                Store character in buffer and check if buffer is full,
                set receive status and re-arm timeout if it is.
            */
            if (receiveBuffer != NULL)
            {
                receiveBuffer[receiveIndex] = input;
                receiveIndex++;

                /*  If end condition has been met we still store the character
                    but we do not change the receive status nor re-arm the timer.
                */
                if ((receiveIndex == receiveMaxLength) && (receiveStatus != AsyncSerial::RECEIVE_FOUND))
                {
                    // Disable Rx interrupt
                    SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::RxIrq);

                    // Fire timeout to signal callback
                    receiveStatus = AsyncSerial::RECEIVE_FULL;
                    timeout.attach_us<AsyncSerial>(this, &AsyncSerial::receiveTimeout, MINIMUM_TIMEOUT);
                    break;
                }
            }
        }
        /*  Start condition has not been met.
        */
        else
        {
            if (conditionStartBuffer != NULL)
            {
                if (input == conditionStartBuffer[conditionIndex])
                {
                    conditionIndex++;

                    /*  Set condition flag and reset index since it is reused.
                    */
                    if (conditionIndex == conditionStartLength)
                    {
                        insideCondition = true;
                        conditionIndex = 0;
                    }
                }
                else
                {
                    // Character didn't match sequence. Start over.
                    conditionIndex = 0;
                }
            }
        }
    }
}

/*  Common function for signaling receive done handler or wait done handler.
*/
void AsyncSerial::getDone(uint8_t status)
{
    DEBUG("getDone: %d\r\n", status);

    /*  Check whether to call the wait handler or the receive handler.
    */
    if (receiveBuffer == NULL)
    {
        waitHandler.call(status);
    }
    else
    {
        receiveResult.buffer = receiveBuffer;
        receiveResult.length = receiveIndex;
        receiveResult.status = status;

        receiveHandler.call(&receiveResult);
    }
}

/*  Send block of data. Function pointer interface.
*/
void AsyncSerial::send(send_done_t handler, const char* buffer, uint16_t length)
{
    sendHandler.attach(handler);
    send(buffer, length);
}

/*  Common send block of data function.
*/
void AsyncSerial::send(const char* buffer, uint16_t length)
{
    /*  Signal callback function immediately if there is nothing to send.
    */
    if ((buffer != NULL) && (length != 0))
    {
        // Store book keeping variables
        sendBuffer = buffer;
        sendLength = length;
        sendIndex = 0;

        if(SerialBase::writeable())
        {
            // make sure not to cause contention in the irq
            SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::TxIrq);

            // only write to hardware in one place
            AsyncSerial::putDone();

            // enable TX interrupts
            SerialBase::attach<AsyncSerial>(this, &AsyncSerial::putDone, SerialBase::TxIrq);
        }

        DEBUG("send: %p %d\r\n", buffer, length);
    }
    else
    {
        sendHandler.call();
    }
}


/*  Receiving block of data. Function pointer interface.
*/
void AsyncSerial::receive(receive_done_t _handler,
                          uint8_t* _receiveBuffer, uint16_t _maxLength,
                          const char* _conditionStartBuffer, uint16_t _conditionStartLength,
                          const char* _conditionEndBuffer, uint16_t _conditionEndLength,
                          uint32_t _timeoutMilli)
{
    receiveHandler.attach(_handler);

    /*  Signal callback function immediately if buffer and maxLength are invalid.
    */
    if ((_receiveBuffer == NULL) || (_maxLength == 0))
    {
        receiveResult.buffer = NULL;
        receiveResult.length = 0;
        receiveResult.status = AsyncSerial::RECEIVE_FULL;

        receiveHandler.call(&receiveResult);
    }
    else
    {
        receive(_receiveBuffer, _maxLength,
                _conditionStartBuffer, _conditionStartLength,
                _conditionEndBuffer, _conditionEndLength,
                _timeoutMilli);
    }
}

/*  Common receive function.
*/
void AsyncSerial::receive(uint8_t* _receiveBuffer, uint16_t _maxLength,
                          const char* _conditionStartBuffer, uint16_t _conditionStartLength,
                          const char* _conditionEndBuffer, uint16_t _conditionEndLength,
                          uint32_t _timeoutMilli)
{
    // Book keeping variables for reception
    receiveBuffer = _receiveBuffer;
    receiveMaxLength = _maxLength;
    receiveIndex = 0;
    receiveStatus = AsyncSerial::RECEIVE_TIMEOUT;

    // Book keeping variables for conditions
    conditionStartBuffer = _conditionStartBuffer;
    conditionStartLength = _conditionStartLength;
    conditionEndBuffer = _conditionEndBuffer;
    conditionEndLength = _conditionEndLength;
    conditionIndex = 0;

    // Check if optional start condition is set
    if ((_conditionStartBuffer != NULL) && (_conditionStartLength != 0))
    {
        insideCondition = false;
    }
    else
    {
        insideCondition = true;
    }

    // Arm timer
    timeout.attach_us<AsyncSerial>(this, &AsyncSerial::receiveTimeout, _timeoutMilli * 1000);

    // Arm Rx interrupts and start receiving
    SerialBase::attach<AsyncSerial>(this, &AsyncSerial::getReady, SerialBase::RxIrq);

    DEBUG("receive: %p\r\n", _receiveBuffer);
}

/*  Wait until timeout or sequence is detected.
*/
void AsyncSerial::wait(wait_done_t handler,
                       const char* conditionEndBuffer, uint16_t conditionEndLength,
                       uint32_t timeoutMilli)
{
    waitHandler.attach(handler);

    receive(NULL, 0,
            NULL, 0,
            conditionEndBuffer, conditionEndLength,
            timeoutMilli);
}

/*  Timeout fired. Call common receive done function.
*/
void AsyncSerial::receiveTimeout()
{
    DEBUG("timeout\r\n");

    // Disable Rx interrupt (interrupts are not disabled if Rx timeouts)
    if (receiveStatus == AsyncSerial::RECEIVE_TIMEOUT)
    {
        SerialBase::attach<AsyncSerial>(NULL, NULL, SerialBase::RxIrq);
    }

    getDone(receiveStatus);
}