Архитектура ядер семейства Cortex-M, на котором также основываются многие микроконтроллеры STM32, аппаратно поддерживает интересную технологию. Называется данная технология bit banding.
Это такая возможность модели памяти, при которой биты определённых участков данной памяти отображаются на целые слова другого участка памяти. То есть если в первом участке какой-нибудь бит установлен, то в области, куда он отображается, также будет установлен нулевой бит в отображаемом слове. А если бит сброшен, то и слово в области отображении тоже будет в нуле. Ну и также наоборот, если слово в области отображения равно 1, то и бит установлен, а если равно 0, то бит сброшен.
Вот так выглядит данная схема в описании модели памяти в документации STM32F10xxx-20xxx-21xxx-L1xxxx-Programming-Manual (и не только, в документации на ядро также это есть)
Схема не совсем удобная для восприятия, но тем не менее здесь можно разглядеть, какая область памяти поддерживает bit-banding, и также в какую область памяти данная область отображается.
Bit-banding работает для области памяти SRAM, начиная с адреса 0x20000000 размером в 1 мегабайт и данная область отображается в область памяти с адреса 0x22000000 размером, соответственно в 32 мегабайта (оно и понятно, раз для каждого бита нужно слово, это же в 32 раза больше). Также bit-banding поддерживается для области памяти периферии, начиная с адреса 0x40000000 размером в 1 мегабайт и данная область уже отображается в область памяти с адреса 0x42000000 размером в 32 мегабайта.
Всё это очень интересно, но для чего это может потребоваться?
А может потребоваться для того, чтобы снизить время операций установки и сброса определённых битов памяти. В принципе, для ножек портов периферии GPIO есть для этого специальные биты BS и BR в регистре BSRR, но тем не менее есть регистры другой периферии, в которых нужно бывает срочно установить или сбросить какой-нибудь бит, также может возникнуть такая надобность и в области памяти SRAM.
Каким образом экономится время операции установки или сброса бита с помощью bit-banding?
Если мы решили установить бит в каком-нибудь участке памяти, то для того, чтобы не изменить состояние других битов, мы должны считать весь регистр (всё слово) в регистр общего назначения (РОН), ну или хотя бы байт, затем мы должны применить логическую операцию OR если нам нужно установить бит либо логическую операцию AND, чтобы бит сбросить, а на третьем этапе мы должны это слово или байт записать обратно в его родную ячейку. А если мы воспользуемся технологией bit-banding, то нам достаточно будет записать единицу или ноль в отображаемое место нашего бита, для того, чтобы его установить или сбросить. На этапе инициализации периферии это не так важно, потому что это происходит либо однократно либо очень редко, а вот в каком-то периодическом процессе это может очень помочь в оптимизации данного процесса.
Поэтому, я думаю, данную технологию не стоит оставлять в стороне, и мы просто обязаны с ней познакомиться.
Как рассчитать адрес той ячейки памяти, в которую отображается тот или иной бит?
Для этого есть определённая формула
То есть нам нужны для расчёта следующие величины:
byte_offset — адрес регистра или ячейки памяти, с битами которой мы собираемся работать,
byte_number — номер бита в данном регистре или ячейки памяти,
bit_band_base — базовый адрес области памяти — 0x22000000, если это SRAM, 0x42000000, если это Peripheral.
Данные две формулы для простоты расчёта можно объединить в одну:
bit_word_addr = bit_band_base + byte_offset x 32 + bit_number x 4
Вот, в принципе, и вся теория битбэндинга.
Теперь можно приступить и к практике.
Попробуем помигать светодиодами с применением битбэндига.
Проект за основу мы возьмём из урока 165 с именем BLINK01_CMSIS и назовём его CMSIS_BLINK_BITBAND.
Схема будет та же как и в 165 уроке
Откроем наш проект в Keil, затем откроем файл main.c, соберём его и в бесконечном цикле функции main() распределим команды каждую в свою строку, до этого они были объединены по 3 команды в строке. Это потребуется для того, чтобы лучше было смотреть ассемблерный код в отладчике
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
while(1) { LED10_OFF(); LED1_ON(); delay(500000); LED1_OFF(); LED2_ON(); delay(500000); LED2_OFF(); LED3_ON(); delay(500000); LED3_OFF(); LED4_ON(); delay(500000); LED4_OFF(); LED5_ON(); delay(500000); LED5_OFF(); LED6_ON(); delay(500000); LED6_OFF(); LED7_ON(); delay(500000); LED7_OFF(); LED8_ON(); delay(500000); LED8_OFF(); LED9_ON(); delay(500000); LED9_OFF(); LED10_ON(); delay(500000); |
Соберём код, прошьём контроллер и установим точку останова, например вот здесь
Запустим отладку, остановившись на данном брейкпоинте, и посмотрим участок кода, отвечающий за включение второго светодиода
Как я и отметил выше, процесс установки бита разворачивается на 3 этапа: загрузка из памяти в РОН, установка бита при помощи операции OR и загрузка данных с установленным битом обратно в память.
Операция по установке в низкий уровень ещё загадочнее
Хотя здесь применяется инструкция сброса битов по маске, процесс тем не менее затянулся вон на сколько инструкций. Это, видимо, какие-то издержки языка C.
Теперь посмотрим как это всё будет выглядеть после того, когда мы применим бит-бэндинг.
Закомментируем макросы зажигания п гашения светодиодов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/* #define LED1_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR2) #define LED1_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR2) #define LED2_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR3) #define LED2_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR3) #define LED3_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR4) #define LED3_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR4) #define LED4_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR5) #define LED4_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR5) #define LED5_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR6) #define LED5_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR6) #define LED6_ON() SET_BIT(GPIOA->ODR,GPIO_ODR_ODR7) #define LED6_OFF() CLEAR_BIT(GPIOA->ODR,GPIO_ODR_ODR7) #define LED7_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR0) #define LED7_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR0) #define LED8_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR1) #define LED8_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR1) #define LED9_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR10) #define LED9_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR10) #define LED10_ON() SET_BIT(GPIOB->ODR,GPIO_ODR_ODR11) #define LED10_OFF() CLEAR_BIT(GPIOB->ODR,GPIO_ODR_ODR11) */ |
Аналогичные макросы мы теперь должны написать с применением технологии bit-banding.
Для этого нам надо сначала узнать все необходимые адреса и смещения.
Первый адрес — это базовый адрес памяти, куда отображаются адреса периферии. Он хранится в специальном макросе библиотеки
#define PERIPH_BB_BASE ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
Также нам нужен реальный адрес регистра. Для перового светодиода это регистр ODR порта A. В данной таблице в документации Reference Manual на наш контроллер есть информация об адресах различной периферии, найдём в ней наш порт
А адрес регистра мы можем вычислить. Вот здесь его смещение относительно базового адреса порта
Значит нужный нам адрес — 0x40010800 + 0x0C = 0x4004080C.
Номер бита для первого светодиода — 2
Также адрес порта можно вычислить, используя следующие макросы библиотеки
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
Величина смещения порта относительно базового адреса периферии будет равна GPIOA_BASE-PERIPH_BASE, а адрес регистра — GPIOA_BASE-PERIPH_BASE+0xC.
Вот теперь у нас есть всё для нашего первого макроса, который будет выглядеть вот таким образом
1 |
#define LED1_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 2*4)) = 1 |
Соответственно, для того, чтобы нам светодиод отключить, мы всего лишь 1 меняем на 0, следовательно, у нас готов и второй макрос
1 2 |
#define LED1_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 2*4)) = 1 #define LED1_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 2*4)) = 0 |
Также используя те же принципы вычислений, добавим макросы и для других светодиодов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#define LED1_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 2*4)) = 0 #define LED2_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 3*4)) = 1 #define LED2_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 3*4)) = 0 #define LED3_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 4*4)) = 1 #define LED3_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 4*4)) = 0 #define LED4_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 5*4)) = 1 #define LED4_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 5*4)) = 0 #define LED5_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 6*4)) = 1 #define LED5_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 6*4)) = 0 #define LED6_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 7*4)) = 1 #define LED6_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOA_BASE-PERIPH_BASE+0xC) + 7*4)) = 0 #define LED7_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 0*4)) = 1 #define LED7_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 0*4)) = 0 #define LED8_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 1*4)) = 1 #define LED8_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 1*4)) = 0 #define LED9_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 10*4)) = 1 #define LED9_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 10*4)) = 0 #define LED10_ON() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 11*4)) = 1 #define LED10_OFF() *((uint32_t *)(PERIPH_BB_BASE+32*(GPIOB_BASE-PERIPH_BASE+0xC) + 11*4)) = 0 |
Всё готово! Можно проверять работу нашего кода.
Соберём код, прошьём контроллер, у нас по-прежнему всё работает
Посмотрим, что изменилось в машинном коде
Запись единицы в бит регистра теперь происходит за одну инструкцию, если не считать запись в r0 единицы и загрузки в r1 адреса регистра.
Аналогично происходит и операция записи нуля в тот же бит
Первые две операции не считаются, так как в них происходит всего лишь вычисление адреса регистра.
Сама операция записи нуля или единицы в бит у нас теперь длится только одну операцию, вот этого и позволяет нам достичь технология отображения битов в слова.
Итак, на данном уроке мы познакомились с технологией bit banding, аппаратно реализованной в ядрах Cortex-M3 и Cortex-M4, и, надеюсь, знание, как использовать данную технологию, нам не раз пригодится в будущем.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Калькулятор для Bit Banding
http://wow-only.ru/programs/bitband_ports_STM32F10x.html
Если в слове надо сразу установить не один, а пару бит, либо более, технология насколько понимаю выигрыша не даст?