826 example: async capture

From Sensoray Technical Wiki
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.


Contents

Acquisition library

/*

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);
    }
}

Library header

/*

  File      : serial_acq.h
  Function  : Serial data acquisition interface using counter channel on Sensoray model 826 board
  Copyright : (C) 2021 Sensoray

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/>.

*/

#ifndef _SERIAL_ACQ_H_
#define _SERIAL_ACQ_H_

// Types ----------------

typedef unsigned char   u8;
typedef unsigned short  u16;
typedef unsigned int    u32;
typedef void            *SER_IF;

typedef struct SYNC_EVENT_MSG {     // Sync events specify eventType and captured serial data word.
    int     eventType;
    u32     value;
} SYNC_EVENT_MSG;

typedef struct ASYNC_EVENT_MSG {    // Async events specify eventType and event duration.
    int     eventType;
    u32     duration;
} ASYNC_EVENT_MSG;

// SYNC_EVENT_MSG eventType values. Positive and large negative values are listed here; smaller negative values are 826 API error codes.
#define SYNC_FRAMEDATA      0           // Frame data
#define SYNC_UNKNOWN        (-3001)     // Unexpected snapshot trigger
#define SYNC_OVERFLOW       (-3002)     // Event message queue overflowed
#define SYNC_TERMINATE      (-3003)     // Acquisition thread terminated

// Error codes returned by StartSerialAcq() 
#define START_ERR_OK        0
#define START_ERR_API       (-2000)     // Can't open 826 API
#define START_ERR_NOTFOUND  (-2001)     // 826 board was not found
#define START_ERR_MALLOC    (-2002)
#define START_ERR_SEM       (-2003)
#define START_ERR_THREAD    (-2004)

// ASYNC_EVENT_MSG eventType values. Positive and large negative values are listed here; smaller negative values are 826 API error codes.
#define ASYNC_VAL_0         0           // Logic '0'
#define ASYNC_VAL_1         1           // Logic '1'
#define ASYNC_IDLE          2           // Timed out waiting for serial data edge
#define ASYNC_NOTFOUND      (-3000)     // 826 board was not found
#define ASYNC_UNKNOWN       (-3001)     // Unexpected snapshot trigger
#define ASYNC_OVERFLOW      (-3002)     // Event message queue overflowed
#define ASYNC_TERMINATE     (-3003)     // Acquisition thread terminated


// SYNC SERIAL RECEIVE FUNCTIONS ========================================================================

////////////////////////////////////////////////////////
// Start sync serial data acquisition.
// Opens and configures the 826 board and starts the serial data acquisition thread.
// Imports:
//  board           - 826 board number, which must match switch settings on the board.
//  ctr             - Counter channel to use on the 826 board.
//  dio             - DIO channel to use on the 826 board. Connect the serial clock signal to this DIO (connect serial data signal to ClkA).
//  burstSize       - Size of serial data word.
//  queueSize       - Event buffer capacity.
//  maxBitTime_us   - Maximum serial bit time. A data word boundary is assumed when no serial clocks occur within this interval.
//  clockEdge       - Clock edge (at ExtIn) corresponding to valid data. Set to S826_SSRMASK_EXTFALL or S826_SSRMASK_EXTRISE.
// Exports:
//  serif           - Handle to serial data acquisition interface. 
// Returns START_ERR_x code: START_ERR_OK = success, other error code = fail.

int StartSyncSerialAcq(u32 board, u32 ctr, u32 dio, u32 burstSize, u32 queueSize, u32 maxBitTime_us, u32 clockEdge, SER_IF *serif);

////////////////////////////////////////////////////////
// Halt sync serial data acquisition.
// Terminates data acquisition thread and closes the 826 board.
// Imports:
//  serif       - Handle to serial data acquisition interface.

void StopSyncSerialAcq(SER_IF serif);

////////////////////////////////////////////////////////
// Read sync serial data acquisition interface.
// Blocks until a data burst has been received or maxwait has elapsed, whichever comes first.
// Returns data burst in user buffer if available.
// Imports:
//  serif       - Handle to serial data acquisition interface.
//  buf         - Pointer to buffer that will receive event message.
//  maxwait     - Maximum time (milliseconds) to wait for message to arrive.
// Exports:
//  len         - Number of data edges received in buf.
// Returns 826 error code or one of these values:

// Returns one of the following codes, or 826 error code.

#define SYNC_BURST_OVERFLOW  0   // Incomplete burst: user buffer full before end of burst.
#define SYNC_BURST_COMPLETE  1   // Serial data word received in user buffer.
#define SYNC_BURST_TIMEOUT   2   // No burst available (timed out waiting for burst).
#define SYNC_BURST_ERROR     3   // Fatal error.

int ReadSyncSerialData(SER_IF serif, SYNC_EVENT_MSG *buf, u32 maxwait);


// ASYNC SERIAL RECEIVE FUNCTIONS ========================================================================

////////////////////////////////////////////////////////
// Start async serial data acquisition.
// Opens and configures the 826 board and starts the serial data acquisition thread.
// Imports:
//  board       - 826 board number, which must match switch settings on the board.
//  ctr         - Counter channel to use on the 826 board.
//  dio         - DIO channel to use on the 826 board. Connect the serial data signal to this DIO.
//  queueSize   - Buffer capacity. Make this large enough to hold all serial edges from the largest expected data burst, plus a margin for other events.
//  idleTime_ms - After a serial data burst has started, wait this long for the next data edge before logging a BURST_TIMEOUT.
// Returns handle to serial data acquisition interface.

SER_IF StartAsyncSerialAcq(u32 board, u32 ctr, u32 dio, u32 queueSize, u32 idleTime_ms);

////////////////////////////////////////////////////////
// Halt async serial data acquisition.
// Terminates data acquisition thread and closes the 826 board.
// Imports:
//  serif       - Handle to serial data acquisition interface.

void StopAsyncSerialAcq(SER_IF serif);

////////////////////////////////////////////////////////
// Read async serial data acquisition interface.
// Blocks until a data burst has been received or maxwait has elapsed, whichever comes first.
// Returns data burst in user buffer if available.
// Imports:
//  serif       - Handle to serial data acquisition interface.
//  buf         - Pointer to buffer that will receive event messages.
//  len         - Maximum number of events to receive in buf.
//  maxwait     - Maximum time (milliseconds) to wait for a message to arrive.
// Exports:
//  len         - Number of event messages received in buf.
// Returns 826 error code or one of these values:
#define ASYNC_BURST_OVERFLOW  0   // Incomplete burst: user buffer full before end of burst.
#define ASYNC_BURST_COMPLETE  1   // Complete burst in user buffer (received IDLE message).
#define ASYNC_BURST_TIMEOUT   2   // No burst available (timed out waiting for burst).
#define ASYNC_BURST_ERROR     3   // Fatal error.

int ReadAsyncSerialData(SER_IF serif, ASYNC_EVENT_MSG *buf, u32 *len, u32 maxwait);


#endif // #ifndef _SERIAL_ACQ_H_
Personal tools
Namespaces

Variants
Actions
Toolbox