826 example: async capture
From Sensoray Technical Wiki
/* 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); } }