ADC Device Drivers

Name

Overview -- ADC Device Drivers

Introduction

This section describes how to write an ADC hardware device. While users of ADC devices do not need to read it, it may provide added insight into how the devices work.

Data Structures

An ADC hardware driver is represented by a number of data structures. These are generic device and channel data structures, a driver private device data structure, a generic character device table entry and a driver function table. Most of these structures are instantiated using macros, which will be described here.

The data structure instantiation for a typical single device, four channel ADC would look like this:

//==========================================================================
// Instantiate data structures

// -------------------------------------------------------------------------
// Driver functions:

CYG_ADC_FUNCTIONS( example_adc_funs,
                   example_adc_enable,
                   example_adc_disable,
                   example_adc_set_rate );

// -------------------------------------------------------------------------
// Device instance:

static example_adc_info example_adc_info0 =
{
    .base               = CYGARC_HAL_EXAMPLE_ADC_BASE,
    .vector             = CYGNUM_HAL_INTERRUPT_ADC
};

CYG_ADC_DEVICE( example_adc_device,
                &example_adc_funs,
                &example_adc_info0,
                CYGNUM_IO_ADC_EXAMPLE_DEFAULT_RATE );

// -------------------------------------------------------------------------
// Channel instances:

#define EXAMPLE_ADC_CHANNEL( __chan )                                    \
CYG_ADC_CHANNEL( example_adc_channel##__chan,                            \
                 __chan,                                                 \
                 CYGNUM_IO_ADC_EXAMPLE_CHANNEL##__chan##_BUFSIZE,        \
                 &example_adc_device );                                  \
                                                                         \
DEVTAB_ENTRY( example_adc_channel##__chan##_device,                      \
              CYGDAT_IO_ADC_EXAMPLE_CHANNEL##__chan##_NAME,              \
              0,                                                         \
              &cyg_io_adc_devio,                                         \
              example_adc_init,                                          \
              example_adc_lookup,                                        \
              &example_adc_channel##__chan );

EXAMPLE_ADC_CHANNEL( 0 );
EXAMPLE_ADC_CHANNEL( 1 );
EXAMPLE_ADC_CHANNEL( 2 );
EXAMPLE_ADC_CHANNEL( 3 );

The macro CYG_ADC_FUNCTIONS() instantiates a function table called example_adc_funs and populates it with the ADC driver functions (see later for details).

Then an instance of the driver private device data structure is instantiated. In addition to the device base address and interrupt vector shown here, this stucture should contain the interrupt object and handle for attaching to the vector. It may also contain any other variables needed to manage the device.

The macro CYG_ADC_DEVICE() instantiates a cyg_adc_device structure, named example_adc_device which will contain pointers to the function table and private data structure. The initial sample rate is also supplied here.

For each channel, an ADC channel structure and a device table entry must be created. The macro EXAMPLE_ADC_CHANNEL() is defined to simplify this process. The macro CYG_ADC_CHANNEL defines a cyg_adc_channel structure, which contains the channel number, the buffer size, and a pointer to the device object defined earlier. The call to DEVTAB_ENTRY() generates a device table entry containing the configured channel name, a pointer to a device function table defined in the generic ADC driver, pointers to init and lookup functions implemented here, and a pointer to the channel data structure just defined.

Finally, four channels, numbered 0 to 3 are created.

Functions

There are several classes of function that need to be defined in an ADC driver. These are those function that go into the channel's device table, those that go into the ADC device's function table, calls that the driver makes into the generic ADC package, and interrupt handling functions.

Device Table Functions

These functions are placed in the standard device table entry for each channel and handle initialization and location of the device within the generic driver infrastructure.

static bool example_adc_init(struct cyg_devtab_entry *tab) This function is called from the device IO infrastructure to initialize the device. It should perform any work needed to start up the device, short of actually starting the generation of samples. This function will be called for each channel, so if there is initialization that only needs to be done once, such as creating an interrupt object, then care should be taken to do this. This function should also call cyg_adc_device_init() to initialize the generic parts of the driver.

static Cyg_ErrNo example_adc_lookup(struct cyg_devtab_entry **tab, struct cyg_devtab_entry *sub_tab, const char *name) This function is called when a client looks up or opens a channel. It should call cyg_adc_channel_init() to initialize the generic part of the channel. It should also perform any operations needed to start the channel generating samples.

Driver Functions

These are the functions installed into the driver function table by the CYG_ADC_FUNCTIONS() macro.

static void example_adc_enable( cyg_adc_channel *chan ) This function is called from the generic ADC package to enable the channel in response to a CYG_IO_SET_CONFIG_ADC_ENABLE config operation. It should take any steps needed to start the channel generating samples.

static void example_adc_disable( cyg_adc_channel *chan ) This function is called from the generic ADC package to enable the channel in response to a CYG_IO_SET_CONFIG_ADC_DISABLE config operation. It should take any steps needed to stop the channel generating samples.

static void example_adc_set_rate( cyg_adc_channel *chan, cyg_uint32 rate ) This function is called from the generic ADC package to enable the channel in response to a CYG_IO_SET_CONFIG_ADC_RATE config operation. It should take any steps needed to change the sample rate of the channel, or of the entire device.

Generic Package Functions

These functions are called by a hardware ADC device driver to perform operations in the generic ADC package.

__externC void cyg_adc_device_init( cyg_adc_device *device ) This function is called from the driver's init function and is used to initialize the cyg_adc_device object.

__externC void cyg_adc_channel_init(cyg_adc_channel *chan) This function is called from the driver's lookup function and is used to initialize the cyg_adc_channel object.

__externC cyg_uint32 cyg_adc_receive_sample(cyg_adc_channel *chan, cyg_adc_sample_t sample) This function is called from the driver's ISR to add a new sample to the buffer. The return value will be either zero, or CYG_ISR_CALL_DSR and should be ORed with the return value of the ISR.

__externC void cyg_adc_wakeup(cyg_adc_channel *chan ) This function is called from the driver's DSR to cause any threads waiting for data to wake up when a new sample is available. It should only be called if the wakeup field of the channel object is true.

Interrupt Functions

These functions are internal to the driver, but make calls on generic package functions. Typically an ADC device will have a single interrupt vector with which it signals available samples on the channels and any error conditions such as overruns.

static cyg_uint32 example_adc_isr(cyg_vector_t vector, cyg_addrword_t data) This function is the ISR attached to the ADC device's interrupt vector. It is responsible for reading samples from the channels and passing them on to the generic layer. It needs to check each channel for data, and call cyg_adc_receive_sample() for each new sample available, and then ready the device for the next interrupt. It's activities are best explained by example:

static cyg_uint32 example_adc_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_adc_device *example_device = (cyg_adc_device *) data;
    example_adc_info *example_info = example_device->dev_priv;
    cyg_uint32 res = 0;
    int i;

    // Deal with errors if necessary
    DEVICE_CHECK_ERRORS( example_info );

    // Look for all channels with data available
    for( i = 0; i < CHANNEL_COUNT; i++ )
    {
        if( CHANNEL_SAMPLE_AVALIABLE(i) )
        {
            // Fetch data from this channel and pass up to higher
            // level.

            cyg_adc_sample_t data = CHANNEL_GET_SAMPLE(i);
            
            res |= CYG_ISR_HANDLED | cyg_adc_receive_sample( example_info->channel[i], data );
        }
    }

   // Clear any interrupt conditions
    DEVICE_CLEAR_INTERRUPTS( example_info );

    cyg_drv_interrupt_acknowledge(example_info->vector);
    
    return res;
}

static void example_adc_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) This function is the DSR attached to the ADC device's interrupt vector. It is called by the kernel if the ISR return value contains the CYG_ISR_HANDLED bit. It needs to call cyg_adc_wakeup() for each channel that has its wakeup field set. Again, and example should make it all clear:

static void example_adc_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    cyg_adc_device *example_device = (cyg_adc_device *) data;
    example_adc_info *example_info = example_device->dev_priv;
    int i;

    // Look for all channels with pending wakeups
    for( i = 0; i < CHANNEL_COUNT; i++ )
    {
        if( example_info->channel[i]->wakeup )
            cyg_adc_wakeup( example_info->channel[i] );
    }
}