Bit-banding, a fast and legible GPIOx output

This forum is dedicated to feedback, discussions about ongoing or future developments, ideas and suggestions regarding the ChibiOS projects are welcome. This forum is NOT for support.
inca
Posts: 37
Joined: Mon Apr 22, 2013 12:08 am

Bit-banding, a fast and legible GPIOx output

Postby inca » Fri Apr 26, 2013 1:07 pm

Greetings,

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) {}
}

genosensor
Posts: 65
Joined: Thu Oct 03, 2013 1:06 am
Location: Santa Cruz, California
Has thanked: 1 time
Been thanked: 1 time

Re: Bit-banding, a fast and legible GPIOx output

Postby genosensor » Mon Nov 05, 2018 8:24 am

Found this old post while searching for support for Bit Banding in ChibiOS.
This is a generic macro that seems to do the job:

Code: Select all

#define BB_(periph) (&(periph) + (PERIPH_BB_BASE - PERIPH_BASE)/sizeof(uint32_t))


Here's an example of its usage:

Code: Select all

  void stepperReadyCB(EXTDriver *extp, expchannel_t pad)
/*
  update stepper trajectory whenever Trianamic 236 chip is selected
*/
{
  (void) extp;
    //atomic extChannelDisableI(extp, pad) -- mask Trinamic chip select IRQ
  BB_(EXTI->IMR)[pad] = 0;
  stepperUpdate();
 }


The above BB_() is a much faster equivalent to extChannelDisableI(extp, pad);

In my case, these interrupts are occurring at 50kHz, so it's well worth this low-level hacking to minimize overhead.
- brent


Return to “Development and Feedback”

Who is online

Users browsing this forum: No registered users and 12 guests