Introduction
If you have ever taken a part of developing a firmware for a complex device, then you should have seen or used bit-field variables. In my previous job, I was in the development team of a complex tracking device for vehicles, where the firmware was written from the ground-up using bare metal C. The firmware was responsible for controlling the communication between the GSM, GPS, Zigbee/WiFi, RS-232 Serial, and the RTC module with the MCU, besides sampling analog data and controlling the device digital outputs. With this level of complexity and interleaved tasks, you’ll either use an off-the-shelf RTOS with scheduling between tasks feature, or use bare metal C, like my case here. You have to develop a flag-driven firmware, where most events in the device have a special 1-bit-long flag variable (true or false).Now, if you allocate a dedicated integer variable for each flag, then you will end up with running out the free memory space. In this case bit-field variables can save a lot of space for you. Using this method, the same location in the memory is shared among more than one variable in the struct.
Another usage of the bit-field variables is implementing some protocol with a variable bit-long fields frame. In this case building a struct with bit-field variables also saves you a good amount of memory space. Please revise the article about struct and union in embedded C if you need to know more.
As any other solution in engineering world, it’s not always a win-win situation. Bit-fields save place in data memory, and they also provide a simplified way to set and get values (that aren’t byte-aligned) rather than using bitwise operations. On the other hand, this means that whenever you use a bit-field variable, the processor/compiler will perform READ-MODIFY-WRITE operations. As the memory location is shared with others, then the compiler will read the entire variable to store it in a temporary place, mask other fields, change the value and then restore the value of unchanged fields (this is what is called READ-MODIFY-WRITE operation).
So it’s true that we saved some space in the SRAM, but we will need more instructions for each reading/writing operation in the Flash memory.
Accessing a bit-field variable is another concern when using bit-field. It is not an atomic operation, which could lead to faults in some critical code sections. Especially for shared bit-field variables between interrupts and processes.
In a nutshell, using bit-field variables is more efficient for SRAM but less for flash and the speed of the code execution. To this end, the feature of bit-banding was introduced in ARM Cortex-M3 MCUs to enhance the process considerably. So let’s know more about it!
What Is Bit-Banding Feature?
Writing a portable code is one of the concerns for developers, and while dealing with bit-fields is not standard in all compilers, it is not very advisable to use.When a feature is available in the hardware itself, you will not have any issues in porting the code from vendor to vendor while both are using the same ARM Cortex-M3 core.
ARM Cortex-M3 features a 1 MB area in SRAM memory called bit-band region. In this region each bit can be accessed individually. To access to bit-band region bits you need to do so via an aliased region, where each word in this region is an alias to one bit in the bit-band region.
ARM Cortex-M3 Memory Map: The bit-band region starts with 0x20000000 address and the alias starts with 0x22000000. Adapted from: Cortex-M3 Technical Reference Manual
To map each bit in bit-band region you need 1 word in the alias region. Apparently, the size of bit-band alias will be 32-MB.
As a condensed explanation, to set the value of a bit from bit-band region you need to change the value of the LSB in the alias address. However, you still can deal with variables in bit-band region as normal.
Access to this region is carried out via the system interface bus. An atomic READ-MODIFY-WRITE operation to the bit-band region will be executed when a data write access is done to the bit-band alias memory range, but this time all are done by the hardware and no extra code will be produced from the compiler.
Moreover, the same feature is available for another 1 MB region for the peripheral region.
What was in SRAM bit-band region applies here. To access the 1 MB bits you need to access them using the alias region. This is very useful to make an easier way to access and program the control and status registers.
Cortex-M3 does not have special instructions for bit operations. We need to use direct access to the address in the bit-band region. We will see later how to do that using C code.
Advantages of Bit-Banding (Applications)
- Changing the value of register’s bits using atomic operation (the READ-MODIFY-WRITE operation cannot be interrupted by other activities).
- Make flags variables instead of packed bit fields in C (which is not advised for a portable code).
- Implement serial data transfers using GPIO ports to serial devices (AKA bit-banging).
Example 1
First, you need to know the following formula to calculate each bit (from bit-band region) alias address. This formula is adapted from Cortex-M3 technical reference manual:
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset
Where:
Bit_word_offset is the position of the target bit in the bit-band memory region.
Bit_word_addr is the address of the word in the alias memory region that maps to
the targeted bit.
Bit_band_base is the starting address of the alias region.
Byte_offset is the number of the byte in the bit-band region that contains the targeted bit.
Bit_number is the bit position (0-7) of the targeted bit.
We will use bit-band feature to set a pin high if a button is pressed and set a pin low if it’s released.
I will use EFM32LG starter kit (EFM32LG STK3600) to program the ARM Cortex-M3 CPU.
In this application, we need to write a pin connected to an LED high/low and read another pin from GPIO connected with a push button.
I will add a set of defines to do calculations needed for bit-banding, let’s break them down:
#define PERI_BASE 0x40000000 #define BITBAND_PERI_BASE 0x42000000
PERI_BASE is the base address of bit-band region for peripherals.
BITBAND_PERI_BASE is the base address of bit-band alias region for peripherals.
#define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + (a-PERI_BASE)*32 + (b*4)))
This define is a C micro processor function to calculate the address of word alias of the bit from bit-band region. According to the previously mentioned formula:
bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)
#define PE_DOUT_SET_BASE 0x400060A0 #define PE_DOUT_CLEAR_BASE 0x400060A4 #define PB_DIN_BASE 0x40006040
These are the base addresses for the related registers to set the pin high/low from portE (one register to set the pin and one to clear it) and to read a pin value from the portB.
Note: I used C Preprocessor #if #else condition to switch between normal and bit-banding options.
#include "em_device.h" #include "em_cmu.h" #include "em_emu.h" #include "em_gpio.h" #include "em_chip.h" #define bitband 0 #define PERI_BASE 0x40000000 #define BITBAND_PERI_BASE 0x42000000 #define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + (a-PERI_BASE)*32 + (b*4))) #define PE_DOUT_SET_BASE 0x400060A0 #define PE_DOUT_CLEAR_BASE 0x400060A4 #define PB_DIN_BASE 0x40006040 #define PE_b2_SET *((volatile unsigned int *)(BITBAND_PERI(PE_DOUT_SET_BASE,2))) #define PE_b2_CLEAR *((volatile unsigned int *)(BITBAND_PERI(PE_DOUT_CLEAR_BASE,2))) #define PB_b9 *((volatile unsigned int *)(BITBAND_PERI(PB_DIN_BASE,9))) int main(void) { /* Initialize chip */ CHIP_Init(); /* Enable clock for GPIO module */ CMU_ClockEnable(cmuClock_GPIO, true); /* Configure PB with alternate drive strength of 0.5mA */ GPIO_DriveModeSet(gpioPortB, gpioDriveModeLowest); /* Configure PB9 as an input for PB0 button with filter enable (out = 1)*/ GPIO_PinModeSet(gpioPortB, 9, gpioModeInput, 1); /* Configure PE2 as a push pull with alternate strength for LED drive */ GPIO_PinModeSet(gpioPortE, 2, gpioModePushPullDrive, 0); while (1) { #if bitband ==1 if(PB_b9) #else if (GPIO_PinInGet(gpioPortB, 9)) #endif { #if bitband ==1 PE_b2_SET=1; #else GPIO_PinOutSet(gpioPortE, 2); #endif } else { #if bitband ==1 PE_b2_CLEAR = 1; #else GPIO_PinOutClear(gpioPortE, 2); #endif } } }
Example 2
1- Let’s set the address 0x20000000 to a value of 0x00000AAC using direct access (without using bit-banding).
2- If we read the address 0x22000008 (The alias address of bit[2] in 0x20000000 word), the return value will be
3- Now let’s write 1 to 0x22000010.
4- The value of 0x20000000 word will become 0x00000ABC.
References and Read More
- The Definitive Guide to ARM Cortex-M3 by Joseph Yiu
- ARM Application Note 179.
- ARM Cortex-M3 Technical Reference Manual.