ADC Support Overview Overview eCos Support for Analog/Digital Converters Introduction ADC support in eCos is based around the standard character device interface. Hence all device IO function, or file IO functions may be used to access ADC devices. ADC devices are presented as read-only serial channels that generate samples at a given rate. The size of each sample is hardware specific and is defined by the cyg_adc_sample_t type. The sample rate may be set at runtime by the application. Most ADC devices support several channels which are all sampled at the same rate. Therefore setting the rate for one channel will usually change the rate for all channels on that device. Examples The use of the ADC devices is best shown by example. The following is a simple example of using the eCos device interface to access the ADC: int res; cyg_io_handle_t handle; // Get a handle for ADC device 0 channel 0 res = cyg_io_lookup( "/dev/adc00", &handle ); if( res != ENOERR ) handle_error(err); for(;;) { cyg_adc_sample_t sample; cyg_uint32 len = sizeof(sample); // read a sample from the channel res = cyg_io_read( handle, &sample, &len ); if( res != ENOERR ) handle_error(err); use_sample( sample ); } In this example, the required channel is looked up and a handle on it acquired. Conventionally ADC devices are named "/dev/adcXY" where X is the device number and Y the channel within that device. Following this, samples are read from the device sequentially. ADC devices may also be accessed using FILEIO operations. These allow more sophisticated usage. The following example shows select() being used to gather samples from several devices. int fd1, fd2; // open channels, non-blocking fd1 = open( "/dev/adc01", O_RDONLY|O_NONBLOCK ); fd2 = open( "/dev/adc02", O_RDONLY|O_NONBLOCK ); if( fd1 < 0 || fd2 < 0 ) handle_error( errno ); for(;;) { fd_set rd; int maxfd = 0; int err; cyg_adc_sample_t samples[128]; int len; FD_ZERO( &rd ); FD_SET( fd1, &rd ); FD_SET( fd2, &rd ); maxfd = max(fd1,fd2); // select on available data on each channel. err = select( maxfd+1, &rd, NULL, NULL, NULL ); if( err < 0 ) handle_error(errno); // If channel 1 has data, handle it if( FD_ISSET( fd1, &rd ) ) { len = read( fd1, &samples, sizeof(samples) ); if( len > 0 ) handle_samples_chan1( &samples, len/sizeof(sample[0]) ); } // If channel 2 has data, handle it if( FD_ISSET( fd2, &rd ) ) { len = read( fd2, &samples, sizeof(samples) ); if( len > 0 ) handle_samples_chan2( &samples, len/sizeof(sample[0]) ); } } This test uses FILEIO operations to access ADC channels. It starts by opening two channels for reading only and with blocking disabled. It then falls into a loop using select to wake up whenever either channel has samples available. Details As indicated, the main interface to ADC devices is via the standard character device interface. However, there are a number of aspects that are ADC specific. Sample Type Samples can vary in size depending on the underlying hardware and is often a non-standard number of bits. The actual number of bits is defined by the hardware driver package, and the generic ADC package uses this to define a type cyg_adc_sample_t which can contain at least the required number of bits. All reads from an ADC channel should be expressed in multiples of this type, and actual bytes read will also always be a multiple. Sample Rate The sample rate of an ADC device can be varied by calling a set_config function, either at the device IO API level or at the FILEIO level. The following two functions show how this is done at each: int set_rate_io( cyg_io_handle_t handle, int rate ) { cyg_adc_info_t info; cyg_uint32 len = sizeof(info); info.rate = rate; return cyg_io_set_config( handle, CYG_IO_SET_CONFIG_ADC_RATE, &info, &len); } int set_rate_fileio( int fd, int rate ) { cyg_adc_info_t info; info.rate = rate; return cyg_fs_fsetinfo( fd, CYG_IO_SET_CONFIG_ADC_RATE, &info, sizeof(info) ); } Enabling a Channel Channels are initialized in a disabled state and generate no samples. When a channel is first looked up or opened, then it is automatically enabled and samples start to accumulate. A channel may then be disable or re-enabled via a set_config function: int disable_io( cyg_io_handle_t handle ) { return cyg_io_set_config( handle, CYG_IO_SET_CONFIG_ADC_DISABLE, NULL, NULL); } int enable_io( cyg_io_handle_t handle ) { return cyg_io_set_config( handle, CYG_IO_SET_CONFIG_ADC_ENABLE, NULL, NULL); } Flushing a Channel In some particular cases, user might require to flush the ADC data buffer. Flushing may be perfomed via a set_config function: int flush_io( cyg_io_handle_t handle ) { return cyg_io_set_config( handle, CYG_IO_SET_CONFIG_ADC_DATA_FLUSH, NULL, NULL); } Configuration The ADC package defines a number of generic configuration options that apply to all ADC implementations: cdl_component CYGPKG_IO_ADC_DEVICES This option enables the hardware device drivers for the current platform. ADC devices will only be enabled if this option is itself enabled. cdl_option CYGNUM_IO_ADC_SAMPLE_SIZE This option defines the sample size for the ADC devices. Given in bits, it will be rounded up to 8, 16 or 32 to define the cyg_adc_sample_t type. This option is usually set by the hardware device driver. cdl_option CYGPKG_IO_ADC_SELECT_SUPPORT This option enables support for the select() API function on all ADC devices. This option can be disabled if the select() is not used, saving some code and data space. In addition to the generic options, each hardware device driver defines some parameters for each device and channel. The exact names of the following option depends on the hardware device driver, but options of this form should be available in all drivers. cdl_option CYGDAT_IO_ADC_EXAMPLE_CHANNELN_NAME This option specifies the name of the device for an ADC channel. Channel names should be of the form "/dev/adcXY" where X is the device number and Y the channel within that device. cdl_option CYGNUM_IO_ADC_EXAMPLE_CHANNELN_BUFSIZE This option specifies the buffer size for an ADC channel. The value is expressed in multiples of cyg_adc_sample_t rather than bytes. The default value is 128. cdl_option CYGNUM_IO_ADC_EXAMPLE_DEFAULT_RATE This option defines the initial default sample rate for all channels. The hardware driver may place constraints on the range of values this option may take. ADC Device Drivers 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] ); } }