826 example: async capture

From Sensoray Technical Wiki
Revision as of 14:01, 5 April 2021 by JL (Talk | contribs)

Jump to: navigation, search

The example code below can serve as a foundation for capturing data from bar code scanners, UARTs, and other devices that generate asynchronous serial data signals.

/*

File      : async_serial_acq.c
Function  : Asynchronous serial data acquisition using counter channel on Sensoray model 826 board
Target OS : Windows
Copyright : (C) 2021 Sensoray

OVERVIEW ------------------------------------------------------------

This module uses a counter channel to capture asynchronous serial data (i.e., serial data that is not
accompanied by a clock signal). It's assumed that serial data occurs in bursts, with the idle time
between bursts indicating boundaries between messages.

Any counter channel may be used, and the serial data signal may be connected to any of the board's 48
general purpose digital I/Os (DIOs). The DIO signal is internally routed to the counter's ExtIn input
by the board's software-controlled signal router.

The counter hardware captures and accumulates DIO edge events in its snapshot FIFO in real-time while,
at the same time, a serial data acquisition thread (created by this module) copies essential snapshot
information from the FIFO to a queue. The thread blocks while waiting for each snapshot so that no CPU
time is wasted by polling. When an idle line condition is detected, the thread will signal that a
complete message (data burst) is available in the queue.

An external application thread receives and processes the enqueued serial messages as time permits.
The application thread must first call StartAsnycSerialAcq() to route the DIO and launch the serial
data acquisition thread. It can then block while waiting for each serial data message to arrive by
calling ReadAsyncSerialData(). To terminate serial data acquisition, call StopAsyncSerialAcq().

LICENSE -------------------------------------------------------------

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

----------------------------------------------------------------------
*/

#include "826api.h"
#include "serial_acq.h"

#include <windows.h>

typedef struct ASIF {                            // SERIAL ACQUISITION INTERFACE (PRIVATE) ----------
    u32                 bd;                     //  826 board number.
    u32                 ctr;                    //  826 counter channel to use for serial data acquisition.
    u32                 dio;                    //  Serial data signal will be applied to this 826 DIO channel number.
    u32                 idleTime_us;            //  Time (in microseconds) serial data signal must be idle before declaring end-of-scan.
    ASYNC_EVENT_MSG     *eventQ;                //  Pointer to serial data event queue.
    u32                 queueSize;              //  Event queue sample capacity.
    u32                 nSamples;               //  Number of samples in queue.
    int                 queueOverflowed;        //  Boolean: BufferOverflow.
    u32                 nextRead;               //  Ring buffer NextRead index.
    u32                 nextWrite;              //  Ring buffer NextWrite index.
    HANDLE              hSampleBurstSem;        //  Semaphore used to notify data client about number of data bursts available in queue.
    HANDLE              hSerialAcqThread;       //  Handle to serial data acquisition thread.
    CRITICAL_SECTION    queue_cs;               //  Queue owner's mutex.
} ASIF;


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////  SERIAL DATA EVENT MESSAGE QUEUE  ////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////
// Append message to the event queue.

static void WriteEventQueue(ASIF *sif, int val, u32 duration)
{
    if (!sif->queueOverflowed)
    {
        EnterCriticalSection(&sif->queue_cs);

        if (sif->nSamples < sif->queueSize)
        {
            sif->eventQ[sif->nextWrite].eventType = val;            // Append event to queue.
            sif->eventQ[sif->nextWrite].duration  = duration;
            if (++(sif->nextWrite) == sif->queueSize)               // Handle queue wrap-around.
                sif->nextWrite = 0;
            sif->nSamples++;

            if (val & ~1)                                           // If event is anything other than serial data edge
                ReleaseSemaphore(sif->hSampleBurstSem, 1, NULL);    //   signal end of burst.
        }
        else
        {
            sif->queueOverflowed = 1;
            ReleaseSemaphore(sif->hSampleBurstSem, 1, NULL);        // Signal end of burst.
        }

        LeaveCriticalSection(&sif->queue_cs);
    }
}

///////////////////////////////////////////////////////////////////////////////
// Copy serial data events from event queue to user buffer.
// Imports:
//  buf - user buffer that will receive events.
//  len - maximum number of events to be received.
// Exports:
//  len - number of events received into buf.
// Returns: 826 error code or BURST_x code indicating why data ended.

static int ReadEventsLoop(ASIF *sif, ASYNC_EVENT_MSG *buf, u32 *len)
{
    int eventType;
    u32 capacity = *len;
    *len = 0;

    while (sif->nSamples > 0)                       // While queue is not empty ----------------
    {
        sif->nSamples--;                                // Remove item from queue.

        eventType = sif->eventQ[sif->nextRead].eventType;

        switch (eventType)                              // Decode item type:
        {
        case ASYNC_VAL_0:                                   // DATA EDGE
        case ASYNC_VAL_1:
            *buf++ = sif->eventQ[sif->nextRead++];              // Remove event from queue and write it to user buf.
            if (sif->nextRead == sif->queueSize)                // Handle queue wrap-around.
                sif->nextRead = 0;
            if (++(*len) == capacity)                           // If user buffer is full
                return ASYNC_BURST_OVERFLOW;                    //   abort and indicate user buf filled before end of burst.
            break;

        case ASYNC_IDLE:                                    // IDLE DATA TIMEOUT
            sif->nextRead++;                                    // Remove event from queue and drop it.
            return ASYNC_BURST_COMPLETE;                        // Abort and indicate idle line receiver.

        default:                                            // ERROR
            sif->nextRead++;                                    // Remove event from queue and drop it.
            return eventType;                                   // Abort and raise error.
        }
    }

    return ASYNC_BURST_ERROR;   // empty queue -- this should never happen
}

// Read next serial burst; called by thread controller.
int ReadAsyncSerialData(SER_IF serif, ASYNC_EVENT_MSG *buf, u32 *len, u32 maxwait)
{
    int rtnval;
    ASIF *sif = (ASIF *)serif;   // cast client's serial interface handle to our internal type

    if (sif->queueOverflowed) {         // If event queue overflowed
        *len = 0;
        return ASYNC_BURST_ERROR;
    }

    switch (WaitForSingleObject(sif->hSampleBurstSem, maxwait)) // Wait for serial data burst.
    {
    case WAIT_OBJECT_0:                             // SUCCESS
        EnterCriticalSection(&sif->queue_cs);
        rtnval = ReadEventsLoop(sif, buf, len);
        LeaveCriticalSection(&sif->queue_cs);
        return rtnval;

    case WAIT_TIMEOUT:                              // WAIT TIMED OUT
        *len = 0;
        return ASYNC_BURST_TIMEOUT;

    default:                                        // ERROR
        *len = 0;
        return ASYNC_BURST_ERROR;
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////  SERIAL DATA ACQUISITION THREAD  /////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define LEADING_EDGE     S826_SSRMASK_EXTFALL
#define TRAILING_EDGE    S826_SSRMASK_EXTRISE

//////////////////////////////////////////////////////////
// Acquire serial data until terminated.

static int WINAPI SerialAcqLoop(ASIF *sif)
{
    u32     tstamp;
    u32     reason;
    u32     prev_tstamp;
    u32     twait = S826_WAIT_INFINITE;

    while (1)                                                   // Repeat until Terminate signal or fatal error -----------
    {
        int errcode = S826_CounterSnapshotRead(                     // Wait for next serial data edge.
            sif->bd,    // 826 board ID
            sif->ctr,   // Counter channel
            NULL,       // Ignore counts
            &tstamp,    // Timestamp
            &reason,    // Reason code
            twait);     // Max time to wait

        if (errcode == S826_ERR_NOTREADY) {                         // If wait timed out
            WriteEventQueue(sif, ASYNC_IDLE, 0);                    //   report idle line condition to data client,
            twait = S826_WAIT_INFINITE;                             //   disable timeout for next wait.
        }
        else if (errcode != S826_ERR_OK)                            // Else if fatal error (e.g., counter fifo overflowed)
            return errcode;                                         //   quit and report error to data client,
        else if (reason & S826_SSRMASK_SOFT)                        // Else if Terminate signal was issued by thread controller
            return ASYNC_TERMINATE;                                 //   quit and report thread shutdown to data client.
        else if (twait == S826_WAIT_INFINITE)                       // Else if this is first edge of a data burst
            twait = sif->idleTime_us;                               //   enable timeout for subsequent waits.
        else if (reason & LEADING_EDGE)                             // Else if detected signal leading edge
            WriteEventQueue(sif, ASYNC_VAL_0, tstamp - prev_tstamp);//   report end of serial data '0' to data client.
        else if (reason & TRAILING_EDGE)                            // Else if detected signal trailing edge
            WriteEventQueue(sif, ASYNC_VAL_1, tstamp - prev_tstamp);//   report end of serial data '1' to data client.
        else                                                        // Else
            return ASYNC_UNKNOWN;                                   //   quit and report unexpected snapshot trigger to data client.

        prev_tstamp = tstamp;                                       // Remember timestamp so next bit duration can be calculated.
    }
}

/////////////////////////////////////////////////////
// Serial data acquisition thread.
// This opens and configures the 826 board and then receives serial data in eventQ.

static DWORD WINAPI SerialAcqThread(void *saq_obj)
{
    DWORD   rtnval = 0;
    ASIF     *sif = (ASIF *)saq_obj;

    int boardflags = S826_SystemOpen();                         // Open 826 API and detect all boards.
    if (boardflags < 0) {                                       // If problem opening API
        WriteEventQueue(sif, boardflags, 0);                    //   report problem to data client,
        return -1;                                              //   terminate app.
    }

    if ((boardflags & (1 << sif->bd)) == 0) {                   // If specified board was not detected
        S826_SystemClose();                                     //   close 826 API,
        WriteEventQueue(sif, ASYNC_NOTFOUND, 0);                //   report BoardNotDetected to data client,
        return -2;                                              //   terminate app.
    }

    S826_CounterExtInRoutingWrite(sif->bd, sif->ctr, sif->dio); // Route DIO to counter ExtIn.
    S826_CounterModeWrite(sif->bd, sif->ctr, 0);                // Use default counter mode settings.
    S826_CounterSnapshotConfigWrite(sif->bd, sif->ctr,          // Capture snapshots
        S826_SSRMASK_EXTRISE | S826_SSRMASK_EXTFALL,            //   upon ExtIn rising or falling edges.
        S826_BITWRITE);
    S826_CounterStateWrite(sif->bd, sif->ctr, 1);               // Enable snapshots.

    WriteEventQueue(sif, SerialAcqLoop(sif), 0);                // Process snapshots until halted, then report reason for halt to data client.

    S826_CounterStateWrite(sif->bd, sif->ctr, 0);               // Disable snapshots and empty snapshot FIFO.
    S826_SystemClose();                                         // Close API.

    return rtnval;
}

//////////////////////////////////////////////////////////////////////////
// Start serial interface running; called by thread controller.
// Returns interface handle (pointer to our internal ASIF struct).

SER_IF StartAsyncSerialAcq(u32 board, u32 ctr, u32 dio, u32 queueSize, u32 idleTime_ms)
{
    ASIF *sif = (ASIF *)malloc(sizeof(ASIF));
    if (sif != NULL)
    {
        InitializeCriticalSection(&sif->queue_cs);

        sif->bd                 = board;
        sif->ctr                = ctr;
        sif->dio                = dio;
        sif->idleTime_us        = idleTime_ms * 1000;  // convert to microseconds (units used by 826 API)
        sif->nextRead           = 0;
        sif->nextWrite          = 0;
        sif->nSamples           = 0;
        sif->queueOverflowed    = 0;
        sif->queueSize          = queueSize;
        sif->eventQ             = (ASYNC_EVENT_MSG *)malloc(queueSize * sizeof(ASYNC_EVENT_MSG));
        sif->hSampleBurstSem    = CreateSemaphore(NULL, 0, queueSize, NULL);                // Create semaphore for serial data buffer
        sif->hSerialAcqThread   = CreateThread(NULL, 0, SerialAcqThread, sif, 0, NULL);     // Launch serial data acquisition thread
    }

    return (SER_IF)sif;     // conceal internal struct from client
}

//////////////////////////////////////////////////////////////////////////
// Shut down serial interface; called by thread controller.

void StopAsyncSerialAcq(SER_IF serif)
{
    if (serif != NULL)
    {
        ASIF *sif = (ASIF *)serif;       // cast client's interface handle to our internal struct pointer.
        if (sif->hSerialAcqThread != NULL)
        {
            S826_CounterSnapshot(sif->bd, sif->ctr);                // Signal Stop to serial data acq thread.
            WaitForSingleObject(sif->hSerialAcqThread, INFINITE);   // Wait for thread to terminate.
            CloseHandle(sif->hSerialAcqThread);                     // Close thread handle.
        }

        if (sif->hSampleBurstSem != NULL)
            CloseHandle(sif->hSampleBurstSem);          // Close event queue

        if (sif->eventQ != NULL)                        // Free event queue
            free(sif->eventQ);

        DeleteCriticalSection(&sif->queue_cs);

        free(sif);
    }
}
Personal tools
Namespaces

Variants
Actions
Toolbox