Что такое Bit Banding на примере stm32

Опубликовано lamazavr - сб, 03/01/2014 - 20:14
Body

Прежде чем объяснить, что такое Bit-Banding, позвольте мне написать немного о том, как работает ЦПУ. Обычно ядро процессора не может читать/писать отдельные биты регистров или ячеек памяти. Вместо этого ЦПУ должен записывать/считывать целые байты или даже слова одновременно. Если процессор должен изменить значение всего одного бита, и может только считать/записать байт, он должен сначала прочитать текущее значение во временный регистр, изменять это значение при помощи логической операции, а затем записать конечный результат. Эти три этапа метко названы чтение-модификация-запись.

Использование последовательности чтение-модификация-запись, чтобы установить биты отлично работает, когда вы делаете одну вещь за один раз, но проблемы могут возникнуть, когда приложение делает несколько вещей одновременно. Например, что произойдет, если прерывание происходит между чтением байта и операцией изменения? Изменение значения в регистре? Новое значение будет утеряно. Эта «гонка» может привести к нежелательным последствиям.

Как работает Bit Banding?

Bit Banding – термин, который использует ARM для обозначения механизма доступного в Cortex M3 and M4 ядрах. Если говорить просто, то в устройстве берется участок памяти и каждый бит этого участка связывается со словом в другом участке памяти.
«Магия» Bit Banding в том, что при записи слов данных во второй участок, изменяются биты в первом. Операция установки, снятия бита занимает одну машинную инструкцию, что делает невозможным потерю данных. Т.е. такой подход обеспечивает атомарный доступ к памяти.

В Cortex M4 это уже не так полезно, из-за наличия регистров BSSRH и BSSRL, которые позволяют устанавливать биты портов ввода-вывода. Но Bit Banding можно применять и к участкам SRAM памяти.

Пример использования

Рассмотрим пример на базе отладочной платы STM32F4 discovery. Просто будем моргать светодиодом при помощи Bit Banding.
Светодиоды подключены на выводы 12-15 порта D.

Из карты памяти видно, что GPIOD находится по адресу 0x40020C00.
Регистр ODR, который позволяет устанавливать биты порта имеет смешение 0x14.
Для включения диода стандартным образом необходима такая конструкция:

#define LED_BLUE (1 << 15) /* port D, pin 15 */
GPIOD->ODR |= LED_BLUE;

Или же:

*((uint32_t *)(0x40020C00+0x14)) |= LED_ORANGE;

Что является записью без использования структур объявленных в stm32f4xx.h
Подобной конструкцией будем пользоваться и для Bit Banding.
Писать будем в память Bit Banding области. Адрес вычисляется так:

bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)

где:
• bit_band_base - Базовый адрес области доступа к битам
• byte_offset - Смещение в байтах по отношению базовому адресу исходной области памяти
• bit_number – номер изменяемого бита

Например, для доступа к регистру ODR порта D:

bit_band_base = 0x42000000

Смещение:
0x40020C14 - 0x40000000 = 0x20C14

• 0x40000000 – начало области памяти периферии

Получаем адрес нулевого бита:

0x42418280 = (0x42000000  + ((0x40020C14)-0x40000000)*32)

Теперь применим немного магии. Что такое массив?..
Объявим такой литерал:

#define PORTD ((uint32_t *)((0x42000000  + ((0x40020C14)-0x40000000)*32)))

И теперь для включения светодиода на ноге 12 нужно написать вот такую строку:

PORTD[12] = 1;

Для выключения:

PORTD[12] = 0;

Удобно не правда ли?
А вся магия в том, что используя конструкцию массива. Т.к. при объявлении указателя мы использовали 32 битное без знаковое целое, то указав в скобках число вы указываете смещение относительно начала (0x42418280) массива в «штуках», т.е. 4*bit_number, если в скобках указан номер бита, что нам и нужно.

Комментарии

Здравствуйте,
Мне не понятно от куда взялось bit_band_base = 0x42000000.

Ответ на от katangawise

Это можно прочитать в документации на Cortex-M3/M4.
SRAM 0x20000000-0x200FFFFF битбандится в 0x22000000-0x23FFFFFF.
Периферия 0x40000000-0x400FFFFF в 0x42000000-0x43FFFFFF.
Т.е. для оперативной памяти база будет - 0x22000000, а для периферийных регистров - 0x42000000.

Подскажите, что означает такая интересная конструкция "(uint32_t *)(..." и почему после нее можно обращаться к 12 ноге так, как будто мы имеем дело с массивом "[12]"?? Я к сожалению Си знаю плохо и сам ответа не нашел. Для меня очень странно, почему указатель * идет после переменной. Видел примеры, где (*(uint32_t*)(... )). Очень был бы благодарен, если бы кто-нибудь объяснил. Ещё интересный вопрос, если работать не со всем портом, а только с одним пином, то в формуле расчета адреса области доступа к битам появляется умножение на 4. Однако, если я хочу работать с 0-ым пином, то после умножения получится 0 или надо как-то по другому?

(uint32_t *) - это приведение указателя, учитывая что в си название массива - это указатель на первый элемент, можно обращаться с ним как с массивом.
(*(uint32_t*) - приведение и разыменование
по поводу формулы: нет разницы 1 пин или много

А в RTOS не проще запилить mutex и не вычислять все эти смещения?

Предложенный метод выполняется за три такта. Первый такт - загрузка указателя на адрес в РОН
Второй такт - загрузка значения (1 или 0) в РОН
Третий такт - запись значения по адресу

Добавить комментарий

Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.