Real-Time Data Sources
Applications that use a real-time data source


Abstract:

Some applications use real-time data, such as those in audio, radar, and sonar processing. For real-time applications, blocks of data must be processed within a set amount of time. This data must also be received from some source. To input real-time data in a Gedae flow graph, a function box must be created which encapsulates the driver for the appropriate I/O device.

Problem:

For real-time applications, Gedae has to collect and process a block of data within a certain time constraint avoiding errors or dropping data.

Solution:

  1. Overview:
    The following solution is based on a specific application created on a Sharc processor system and uses interrupts and background DMA's. When the I/O device has an available block of data, it sends an interrupt to the Sharc system. The interrupt handler starts the background DMA, which transfers the data from the I/O device to memory. When the DMA transfer is complete, another interrupt is sent to indicate that the data can now be processed. While processing, another block can be received from the I/O device. This is illustrated in the following time line diagram:
  2. Maximizing processor usage results in minimizing system cost. By overlapping DMA data transfers and processing, processor usage is kept high.

    This example works only on specific embedded systems. Slight system specific changes were made. Parts of the code examples were replaced with pseudo code.

  3. Encapsulating the I/O device in a Gedae function box.
    An embeddable Gedae function box that reads real-time input is defined like any other function box. Parameters and other data inputs can be defined to process the real-time data. Users can build custom function boxes which provide a binding between interrupts and function calls via the primitive's Reset and Apply methods. The interrupt handlers are registered in the Reset method, and the data is received in the Apply method. The following is an example:
  4. File location: FGlibraries/boxes/real_time/v_dev_input
    Name: v_dev_input
    Type: static
    Comment: "Receive real-time input from an device.
            N_out == the vector size of the outputs.
            N_input == the vector size of the I/O data block"
    Input: {
        int N_out;
        int N_input;
    }
    Local: {
        int needs_to_fire; /* number of vectors of real-time data to collect */
        int fired; /* current vector being collected */
    }
    Output: {
        stream float out1[N_out];
        stream float out2[N_out];
    }
    Include: {
    #include < e_dev_input.h > /* found in ~/gedae/include/embeddable */
    }
    Reset: {
        init_dev_input(N_input); /* register interrupt handlers */
        needs_to_fire = 0;
        fired = 0;
    }
    Apply: {
      int progress = 0;
      if (needs_to_fire == 0) {
        /**** BEGINNING NEW EXECUTION OF FUNCTION ****/
        needs_to_fire = size(out1)/N_out; /* calculate the firing granularity */
        fired = 0;
    }

      while (needs_to_fire) {
        /*
        ** if data is available then place it in out1 and out2,
        ** else return failure and try again later.
        */
        float *addr1 = out1 + (N_out * fired);
        float *addr2 = out2 + (N_out * fired);
        if (!read_dev_input(addr1, addr2, N_out)) {
        if (progress) OStaticProgressMade();
        else OStaticFailed("Not enough data available from the I/O device yet");
        break;
        }
        fired++;
        progress = 1;
        if (needs_to_fire == fired) {
            needs_to_fire = 0;
          }
        }
    }

     

    Notice there are no direct references to the Sharc system. Hiding all the Sharc specific calls in the underlying functions (init_dev_input() and read_dev_input()) is recommended. These functions are prototyped in the header file e_dev_input.h (from the Include method). Also, in the Apply method the firing granularity is checked to see if multiple real-time data sets are to be processed as one large block. This allows for transparent scalability.

     

    File location: ~/gedae/include/embeddable/e_dev_input.h
    #ifndef __e_dev_input_h_
    #define __e_dev_input_h_

    void init_dev_input(int N_input);
    int read_dev_input(float *out1, float *out2, int N_out);

    #endif

     

  5. The Reset Method
    The Reset method registers the interrupt handlers and sets up any buffers and flags. The following code is used to register the interrupt handlers, allocate a double buffer where the data will be placed, and set a flag that represents the state of the real-time data. Global variables are used for handles to shared buffers between the interrupt handler and the Apply function. Double buffering is used so while one buffer is being processed another may be filled with new data.
  6.  

    File location: ~/gedae/source/embeddable/e_dev_input.c
    static int BUFFER_SIZE;
    static float *CURRENT_BUFFER;   /* handle to global buffer */
    static float *DEVICE_INPUT_BUFFER1;   /* global buffer1 */
    static float *DEVICE_INPUT_BUFFER2;  /* global buffer2 */
    static int DATA_READY;   /* used to indicate if data is ready */

    void init_dev_input(int N_input) {
      /**** ALLOCATE A DOUBLE BUFFER FOR OUTPUT OF THE INPUT DEVICE
    ****/
    DEVICE_INPUT_BUFFER1 = (float *)calloc(N_input, sizeof(float));
    DEVICE_INPUT_BUFFER2 = (float *)calloc(N_input, sizeof(float));
    CURRENT_BUFFER = DEVICE_INPUT_BUFFER1;
    BUFFER_SIZE = N_input;

    /**** SET TO: NO DATA IS AVAILABLE YET ****/
    DATA_READY = 0;

    /**** REGISTER "I/O HAS DATA READY" INTERRUPT HANDLER ****/
    interrupt_handler(SIG_DEV_DATA_READY, read_iodev_handler);

    /**** REGISTER "DMA COMPLETE" INTERRUPT HANDLER ****/
    interrupt_handler(SIG_DMA_COMPLETE, dma_done_handler);
    }

    When the "SIG_DEV_DATA_READY" interrupt occurs, "read_iodev_handler" gets called. The handler starts a DMA that copies the real-time data into one of the double buffers. Since the DMA processes in the background, the handler can exit right away, allowing normal processing to continue. When the DMA transfers complete, the interrupt "SIG_DMA_COMPLETE" occurs. The "dma_done_handler" sets the "DATA_READY_FLAG". The following is the code for the interrupt handler:

    /**** READ DATA FROM THE REAL-TIME INPUT DEVICE ****/
    static void read_iodev_handler(void) {
    /**** HANDLE THE DOUBLE BUFFERING ****/
    if (CURRENT_BUFFER == DEVICE_INPUT_BUFFER1) {
    CURRENT_BUFFER = DEVICE_INPUT_BUFFER2;
    } else {
    CURRENT_BUFFER = DEVICE_INPUT_BUFFER1;
    }

    /**** GET INPUT FROM REAL TIME IO DEVICE ****/
    start_dma(input_device_addr, CURRENT_BUFFER, BUFFER_SIZE);
    }

    /**** DMA DONE INTERRUPT HANDLER ****/
    static void dma_done_handler(void) {
    /**** SET TO: DATA IS AVAILABLE ****/
    DATA_READY = 1;
    }

  7. The Apply Method
    The Apply method checks to see if the real-time data is ready. If not it returns a failure, giving any other schedules a chance to execute. If ready, the data is moved to the outputs. This function should be kept simple. It should just place the new data on the outputs, and set flags to indicate that the data has been consumed. Preliminary processing should be minimized. The following is the code of the function called by the Apply method:
  8. /**** CALLED FROM THE RESET METHOD ****/
    int read_dev_input(float *out1, float *out2, int N_out) {
      if (!DATA_READY) {
       /**** DATA NOT READY ****/
       return 0;
       } else {
       float *buffer = CURRENT_BUFFER;

       /**** DATA IS READY ****/
       DATA_READY = 0;

      /**** DO ANY PRELIMINARY PROCESSING HERE ****/
     
    < preprocessing and place in out1 and out2 >

       return 1; /* success */
      }
    }

List of pseudo functions:

The follow functions and data are definitions of the pseudo code used above.

interrupt_handler(SIGNAL, (*handler()));
       Register an interrupt handler based on a signal.

SIG_DEV_DATA_READY
       This is an interrupt signal flag indicating when the I/O device has data that is ready to be transferred to memory.


SIG_DMA_COMPLETE
       This is an interrupt signal value that represent when the DMA engine has finished moving the requested data.

start_dma(source_addr, dest_addr, size);
       This is like memcpy() but uses a DMA in the background that runs in parallel with the Sharc processor.

input_device_addr
       This is a handle to the I/O device that contains the real-time data.

 

Example of a real-time algorithm designed with Gedae:

 

 

If you have Gedae installed, to view the flowgraph and this trace table type from the <user_gedae> / <host> directory:

        gedae -file appnotes/real_time/real_time -events demo


Application Note last updated 3/24/98