Microcontrollers: Registers 2

Last time, we looked at reading and writing registers in an AVR microcontroller. This time, lets look at KL46Z256, which is an ARM Cortex-M0+ microcontroller.

Previously, we saw that Atmel provided us with a header file that allows us to read and write individual registers, like DDRD, PORTD and PIND. This works fine, but isn’t it a bit odd that these registers are completely separate from each other? In other words, wouldn’t it be convenient if these registers were grouped together? NXP does this in their header files, and we’ll be looking at that in this blog post.

From KL46 datasheet

Similar to AVRs, we see we have a data direction register (PDDR), input register (PDIR) and output register (PDOR). Conveniently, we also have set, clear and toggle output registers. If you look in the header file for this microcontroller, you’ll see the following:

Various definitions, shown using Platform I/O

As you can see, we have a GPIO_Type structure that contains fields PDOR, PSOR, PCOR, PTOR, PDIR and PDDR, which are 32 bit, volatile data. According to the datasheet, GPIOA has a base address of 0x400FF000, which is indicated by the definition of GPIOA_BASE. The definition GPIOA is the base address, GPIOA_BASE, cast as a pointer to a GPIO_Type.

What does this mean? Well, since GPIOA is a pointer to a structure, we can access the fields of GPIOA using the arrow operator. Therefore, we can write to registers of interest using the following code:

Set Port A pin 1 as output, then set Port A pin 1 output high

Here, thanks to GPIOA, the code clearly shows that PDDR and PDOR are both part of the GPIO peripheral. This is in contrast to the AVR header file: if you didn’t know off the top of your head what PDDR and PORTD were, then it’s not obvious they are registers for the same peripheral.

Let’s look at a more complex peripheral: the ADC.

From KL46 datasheet; not all registers shown

Again, we can see that there are many registers. The corresponding structure in the header file is shown below:

Structure for ADC in header file

Like the GPIO module, we have an ADC_Type structure. Interestingly, this structure contains arrays within its fields. This provides further grouping, as (for example) ADC0_SC1A and ADC0_SC1B are now grouped together within an array.

Just like GPIO, we have ADC0, which is the ADC module’s base address cast as a pointer to the ADC_Type structure. In order to write to the registers, you use the same approach, now only slightly more complicated due to the array in one of the fields:

Writing all 0’s to ADC0_SC1A and ADC0_CFG1

Note that it is possible to address registers like you did in AVR if you prefer; some may find it inconvenient to use the arrow operator. The header file provides macros for this specific case:

Register accessing macros for ADC
Note GPIO and other peripherals also have macros like this

The definitions on the left simply plug ADC0 (which is a pointer to the ADC0 base address) into macros (defined on the right) which perform the arrow operator. If the register definition is part of an array within the structure, the macro will also perform indexing as well. Additionally, since the names of the definitions on the left all have the same prefix, the programmer will know right away that all of these definitions are for the same peripheral.

Besides grouping registers into structures and arrays, the NXP header file also provides macros for reading and writing bits within registers:

From KL46 datasheet

We can see that this particular register has 5 distinct fields: ADLPC, ADIV, ADLSMP, MODE and ADICLK. One way to go about setting up this register would be to memorize the position and bit width of each of these fields ; for example, you could memorize ADIV is bits 5 and 6. A more elegant solution is provided by the header file using macros:

The macros for bit manipulation for each register are in groups of four:

  1. Mask: masks are used to clear or set specific bits. For example, ADC_CFG1_ADIV_MASK is 0x6, which means only bits 5 and 6 are high.
  2. Shift: this number is used to indicate how many times the bits should be shifted. For ADIV, you’re interested in bits 5 and 6, so you’d have a shift of 5 (from bit 0).
  3. Width: this number indicates how many bits are within a section. ADIV is composed of two bits.
  4. Macro: this macro uses an input as well as the corresponding shift and bit mask to create the data you want.

Let’s do an example:

Based on the datasheet, let’s say you want a divide ratio of 4. That means you want ADIV to contain 0b10. If you don’t use the macros provided in the header file, then the process is painful and clumsy. Worse, you’re hard-coding numbers into your code:

Working with one bit at a time is inconvenient. Another way to do this is to take 0b10 and shift it into the right position. A good practice is to use masks so that you don’t accidentally read or write where you don’t mean to; in this case, you only want to manipulate bits 5 and 6. The macro ADC_CFG1_ADIV(x) will do all of this for you:

Remember that ADC_CFG1_ADIV(x) doesn’t write to the register for you! All it does is shift and mask the input argument. Now, in order to actually write to the register, you use the assign operator:

The beauty of this macro is that you can write to multiple fields at once using the OR operator. Say you want MODE to be 0b11 while ADIV is 0b10:

As you can see, the combination of register macros and bit manipulation macros provide convenient and abstract tools to configure peripherals. Even without knowing the address of ADC0_CFG1, or knowing ADIV and MODE’s position within ADC0_CFG1, you’re done!

Leave a comment

Design a site like this with WordPress.com
Get started