I recently read up on some bit banding for the STM32, so I thought I would share my main.c as a blinky-bitbanding example. Documentation, for the most part, is in the code. Feel free to ask questions or improve upon the methods used here.
Note: This implementation is for GPIOx outputs only (ODR) right now. Though it may be extended to any of the other bitwise GPIO registers using the general formula provided below, one may not read the current state from the aliased bit.
Code: Select all
#include "ch.h"
#include "hal.h"
#include "test.h"
#include "board.h"
/* STM32 bit-banding for ARM
Source: Hitex Definitive Guide to STM32, Section 2.3.7
The bit addressable regions of the Cortex memory map are composed of the bit band region (which is up to 1Mbyte of real memory or peripheral registers) and the bit band Alias region which takes up to 32Mbyte of the memory map. Bit banding works by mapping each bit in the bit band region to a word address in the Alias region. So by setting and clearing the aliased word address we can set and clear bits in the real memory.
This allows us to perform individual bit manipulation without the need for special instructions and keeps the overall size of the Cortex core as small as possible. In practice, we need to calculate the address of the bit band alias word for a given memory location in the peripheral or SRAM space. The formula to calculate the alias address is as follows:
Address in the bit band alias region = Bit band alias base address + bit word offset
Where bit word offset = Byte offset from bit band base X 0x20 + bit number x 4
For example, the GPIO output data register is written to in order to set and clear individual IO lines. The physical address of the Port B output register (GPIOB_ODR) is 0x40010C0C. In this example we want to be able to set and clear bit 8 of this word using the above formula.
Port B Word address = 0x40010C0C
Peripheral bit band base = 0x40000000
Peripheral bit band Alias base = 0x42000000
Byte offset from bit band base = 0x40010C0C – 0x40000000 = 10C0C
Bit word offset = (0x10C0C x 0x20) +(8x4) = 0x2181A0
Bit Alias address = 0x42000000 + 0x2181A0 = 0x422181A0
We can now create a pointer to this address using the following line of C:
#define PortBbit8 (*((volatile unsigned long *) 0x422181A0 ))
This pointer can then be used to set and clear the IO port bit:
PB8 = 1; //led on
Which generates the following assembly instructions:
MOVS r0,#0x01
LDR r1,[pc,#104]
STR r0,[r1,#0x00]
Switching the LED off:
PB8 = 0; //led off
Generates the following assembly instructions:
MOVS r0,#0x00
LDR r1,[pc,#88]
STR r0,[r1,#0x00]
Both the set and clear operations take three 16-bit instructions and on the STM32 running at 72 MHz these instructions are executed in 80nsec. Any word in the peripheral and SRAM bit band regions can also be directly addressed word-wide so we could perform the same set and clear using the more traditional AND and OR approach:
GPIOB->ODR |= 0x00000100; //LED on
LDR r0,[pc,#68]
ADDS r0,r0,#0x08
LDR r0,[r0,#0x00]
ORR r0,r0,#0x100
LDR r1,[pc,#64]
STR r0,[r1,#0xC0C]
GPIOB->ODR &=!0x00000100; //LED off
LDR r0,[pc,#40]
ADDS r0,r0,#0x08
LDR r0,[r0,#0x00]
MOVS r0,#0x00
LDR r1,[pc,#40]
STR r0,[r1,#0xC0C]
Now each set and clear operation takes a mixture of 16 and 32-bit operations, which take a minimum of 14 bytes for each operation and at the same clock frequency take a minimum of 180 nSec. If you consider the impact of bit banding on a typical embedded application that sets and clears lots of bits in the peripheral registers and uses semaphores and flags in the SRAM, you are very clearly going to make significant savings in both code size and execution time and it is all handled in the STM32 header file for you.
Source: Hitex Definitive Guide to STM32.
*/
/*
Notes on PortX Registers and the Bit-banding formula
GPIOx Register Spaces
A 0x4001 0800 - 0x4001 0BFF
B 0x4001 0C00 - 0x4001 0FFF
C 0x4001 1000 - 0x4001 13FF
D 0x4001 1400 - 0x4001 17FF
E 0x4001 1800 - 0x4001 1BFF
GPIOx_ODR Output Registers (Word Address
A 0x4001 080C
B 0x4001 0C0C
C 0x4001 100C
D 0x4001 140C
E 0x4001 180C
*/
/*******************************************************************************
Bit-banding demo
*******************************************************************************/
#define ODRA 0x4001080C // Output Data Registers by GPIOx
#define ODRB 0x40010C0C
#define ODRC 0x4001100C
#define ODRD 0x4001140C
#define ODRE 0x4001180C
#define PBBB 0x40000000 // Peripheral Bit-band Base
#define PBBA 0x42000000 // Aliased Peripheral Bit-band Base
#define PD(Pin) (*( (volatile unsigned long *)((ODRD - PBBB) * 0x20 + (4 * Pin) + PBBA) ))
#define pd14 PD(14) // Port D, Pin 14
#define pd15 PD(15) // Port D, Pin 15
#define doLED1 PD(14) // Digital Output LEDx
#define doLED2 PD(15)
/*
* Yellow LED blinker thread, times are in milliseconds.
*/
static WORKING_AREA(waThread1, 128);
static msg_t Thread1(void *arg) {
(void)arg;
chRegSetThreadName("blinker");
while (TRUE) {
//palClearPad(GPIOD, GPIOD_LED1);
doLED1 = 0;
chThdSleepMilliseconds(500);
//palSetPad(GPIOD, GPIOD_LED1);
doLED1 = 1;
chThdSleepMilliseconds(500);
}
return 0;
}
/*
* Application entry point.
*/
int main(void) {
/*
* System initializations.
* - HAL initialization, this also initializes the configured device drivers
* and performs the board-specific initializations.
* - Kernel initialization, the main() function becomes a thread and the
* RTOS is active.
*/
halInit();
chSysInit();
/*
* Activates the serial driver 1 using the driver default configuration.
*/
sdStart(&SD1, NULL);
/*
* Creates the blinker thread.
*/
chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL);
/*
* Normal main() thread activity, in this demo it does nothing except
* sleeping in a loop and check the button state.
*/
TestThread(&SD1);
while (TRUE) {}
}