Microcontroller peripherals can be memory mapped using pointer arrays as demonstrated in the previous post. This method allows the peripheral to be viewed as a collection of channels that can be configured through an index in a loop. By taking this generic approach to memory mapping, a technique is needed to control exactly what is put into the registers. Configuration tables serve as a useful tool for this exact purpose.
A configuration table is exactly what it sounds like; it is a collection of channels and values that are used to configure a peripheral. The most useful way in which to define a configuration table is to create a typedef structure that contains all of the needed fields to setup each channel. Start by examining the peripheral registers of interest. For example, examining the timer peripheral may result in determining that the necessary fields that should be included in the configuration table are channel, period and control fields. The table elements can then be defined by the structure shown in Listing 1.
Listing 1: Configuration Table Definition
The Tmr_ConfigType defines all of the data that is required in order to setup a single timer peripheral. Since most microcontrollers contain more than a single timer, an array of Tmr_ConfigType would be created with each index of the array representing a channel (a single timer module). Before a configuration table can be defined it is useful to first define channel types for the table. The channel will be used to access indices in an array that belong to that channel and in turn will allow the application code to manipulate that particular timer.
Listing 2: Timer Channel Definitions
In Listing 2, a typedef enumeration is used to create the channel names. Since enumerations start at 0 (in C anyways), TIMER1 can be used to access index 0 of an array that contains information about TIMER1. NUM_TIMERS then holds the value for the number of timers that are available. This can be used in the driver initialization to loop through and configure each channel up to NUM_TIMERS. Once the channel type has been defined it is possible to fill in the configuration table with the values that will be used to configure the timers. Listing 3 shows an example configuration table based on the Tmr_ConfigType structure. The configuration table is defined as a const since the configuration data will not be changing during run-time. Each channel is listed along with a period and a control register value.
Listing 3: Configuration Table Example for 2 timers
If Listing 3 was being used within a real project, the period values would correspond to the number of ticks of the timer required before an interrupt or some other useful system event would occur. The control register attributes would be representative of other registers that would require setup. It would be possible to include enabling and disabling of interrupts for each timer in addition to controlling the interrupt priority. Items included in the configuration table may vary from peripheral to peripheral based on what features are supported by the manufacturer.
At this point it would be useful to review the code in each of the listings. There is an error in one of the definitions. Within Listing 1, the TmrChannel is defined as a uint8. However, in the configuration table, we have populated this structure variable with a Tmr_ChannelType. Once this enumeration is created Listing 1 should be modified to the code in Listing 4.
Listing 4: Updated Configuration Table Definition
The next post will build upon the configuration table and pointer array concept by exploring in detail the behavior of timers. Subsequent posts will show how to complete the driver. What an initialization function for a timer would look like will be explored and how the configuration table is used to initialize the registers.