Driver Implementation

Drivers consist of two parts; a static part and a dynamic or configurable part. The static part is hardwired code not intended to be changed or reconfigured by the application after being exported from Atmel START. The dynamic part allows the application to configure the behavior of the driver through e.g. callbacks, or provide resources such as ring buffers to drivers.

The dynamic part is normally configured during driver initialization by calling the driver's init-function. This is typically done during the device initialization and startup phase.

The functionality provided by the static and dynamic parts of the driver is unique to each driver, and documented separately for each driver.

Driver Initialization

All drivers configured in Atmel START are automatically initialized. Atmel START generates calls to the init()-functions of all drivers inside driver_init.c. The driver_init.c file is called by the main.c-file generated by Atmel START.

Each driver has an init()- and a deinit()-function. The init() function:

  • hooks up IRQ handler and enables the module's IRQs if necessary

  • configures the module as desired by writing the module descriptor contents to the correct control registers

  • enables the module

  • does NOT enable any clocks required by the module, this must be done separately

  • does NOT enable any I/O pins required by the module, this must be done separately

The deinit()-function:

  • disables and clears the module's interrupts, if enabled

  • disbles the module, typically by clearing the module's enable-bit in the module's control register

  • does NOT disable any clocks used by the module, this must be done separately

  • does NOT disable any I/O pins used by the module, this must be done separately

Descriptors

Descriptors are driver-specific data structures that are used to fine-tune the behavior of the driver. The application populates the descriptors with the desired configuration information, and passes the descriptors into the driver. Typical information contained in descriptors can be:

  • pointer to and size of ring buffer to be used by the driver

  • desired behavior or configuration of the driver hardware

  • description of interrupt routines

  • pointers to callback functions

Configuring the Dynamic Part of Drivers

The dynamic part of drivers is normally configured during system initialization. The most commonly configured resources are interrupt routine callback functions and memory buffers. How this is done is best illustrated with an example, using the ADC in synchronous mode as an example. All the following code is found in driver_init.c.

The following code declares and defines various resources that will be made available to the ADC:

          /* The channel amount for ADC */
          #define ADC_0_CH_AMOUNT 1
          /* The buffer size for ADC */
          #define ADC_0_BUFFER_SIZE 16
          /* The maximal channel number of enabled channels */
          #define ADC_0_CH_MAX 0
          extern struct _irq_descriptor *_irq_table[PERIPH_COUNT_IRQn];
          extern void Default_Handler(void);
          struct adc_async_descriptor ADC_0;
          struct adc_async_channel_descriptor ADC_0_ch[ADC_0_CH_AMOUNT];
          static uint8_t ADC_0_buffer[ADC_0_BUFFER_SIZE];
          static uint8_t ADC_0_map[ADC_0_CH_MAX+1];
        

The system_init()-function is called by main and initializes the peripheral driver(s), in this case the ADC:

          void system_init(void)
          {
              ADC_0_init();
          }
        
The ADC_0_init() function initializes ADC0. The two first lines enable the clock to the ADC.
static void ADC_0_init(void){    _pm_enable_bus_clock(PM_BUS_APBC, ADC);    _gclk_enable_channel(ADC_GCLK_ID, CONF_GCLK_ADC_SRC);    adc_async_init(&ADC_0, ADC, ADC_0_map, ADC_0_CH_MAX, ADC_0_CH_AMOUNT, &ADC_0_ch[0]);    adc_async_register_channel_buffer(&ADC_0, 0, ADC_0_buffer, ADC_0_BUFFER_SIZE);}

The adc_async_init-function updates the ADC_0 descriptor with information found in the other parameters of the function, and writes this configuration to the ADC hardware. The configured data includes:

  • hooking up any callback functions

  • configuring the number of channels to use, in this case one channel (channel 0)

  • configuring the behavior of each channel used

The adc_async_register_channel_buffer-function hooks the ring buffer ADC_0_buffer with size ADC_0_BUFFER_SIZE up to the ADC descriptor ADC_0, to be used for channel 0.

The ADC_Handler()-function hooks the driver's ISR up to the IRQ vector table. _irq_table[] is a table of interrupt handler descriptors handled by the HPL layer of the IRQ driver.

          void ADC_Handler(void)
          {
              if (_irq_table[ ADC_IRQn + 0 ]) {
                  _irq_table[ ADC_IRQn + 0 ]->handler(_irq_table[ ADC_IRQn + 0 ]->parameter);
              } else {
                  Default_Handler();
              }
          }
        

Compile-time Configuration and Parameterization from Atmel START

The user is normally able to configure driver parameters in Atmel START. A typical example is setting of baudrate and parity for a USART. The files in the config-folder are generated by Atmel START in order to transfer the configurations the user made in Atmel START into the HPL layer. The files use the CMSIS wizard annotation format. Example of contents in hpl_sercom_v200_config.h:

          // <o> Frame parity
              // <0x0=>No parity
              // <0x1=>Even parity
              // <0x2=>Odd parity
          // <i> Parity bit mode for USART frame
          // <id> usart_parity
          #ifndef CONF_SERCOM_0_USART_PARITY
          #   define CONF_SERCOM_0_USART_PARITY  0x0
          #endif
        

Utility Drivers

The utility drivers support other drivers with commonly used software concepts and macros such as ring buffers, linked lists, state machines, etc. Moving this commonly used code into utility drivers makes it easier to maintain the code and help the compiler to optimize the code because the same code is used by different drivers, but only one copy is needed. The utility drivers are fully abstracted and not bound to any particular hardware platform or type. Utility drivers are located in the hal/utils folder with public declaration files in a include folder and function declaration in a src folder. All filenames starts with the prefix utils_. See the API reference in the file "utils.h" for more information on the utility functions.