Difference between revisions of "826 example: async capture"
From Sensoray Technical Wiki
(+header file) |
|||
Line 1: | Line 1: | ||
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. | 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. | ||
+ | |||
+ | ==Acquisition library== | ||
/* | /* | ||
Line 317: | Line 319: | ||
} | } | ||
− | + | ==Library header== | |
/* | /* |
Revision as of 13:07, 5 April 2021
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.
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_