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