STM32 - Bit Banding

STM32 — Bit Banding


Предлагаю вашему вниманию модуль, позволяющий легко обращаться к битам устройств ввода-вывода и оперативной памяти с помощью области bit banding


#include "bitbanding.h"
...
BIT_BAND_PER(RCC->CIR,RCC_CIR_HSERDYIE)=SET;


Эта функция вырождается в три инструкции процессора (при использовании константных значений естественно), напрямую сбрасывающая описанный бит. При использовании переменных появляется математика.
Ниже приведу текст хедера bitbanding.h

#ifndef BITBANDING_H
#define BITBANDING_H

#define MASK_TO_BIT31(A)	(A==0x80000000)? 31 : 0
#define MASK_TO_BIT30(A)	(A==0x40000000)? 30 : MASK_TO_BIT31(A)
#define MASK_TO_BIT29(A)	(A==0x20000000)? 29 : MASK_TO_BIT30(A)
#define MASK_TO_BIT28(A)	(A==0x10000000)? 28 : MASK_TO_BIT29(A)
#define MASK_TO_BIT27(A)	(A==0x08000000)? 27 : MASK_TO_BIT28(A)
#define MASK_TO_BIT26(A)	(A==0x04000000)? 26 : MASK_TO_BIT27(A)
#define MASK_TO_BIT25(A)	(A==0x02000000)? 25 : MASK_TO_BIT26(A)
#define MASK_TO_BIT24(A)	(A==0x01000000)? 24 : MASK_TO_BIT25(A)
#define MASK_TO_BIT23(A)	(A==0x00800000)? 23 : MASK_TO_BIT24(A)
#define MASK_TO_BIT22(A)	(A==0x00400000)? 22 : MASK_TO_BIT23(A)
#define MASK_TO_BIT21(A)	(A==0x00200000)? 21 : MASK_TO_BIT22(A)
#define MASK_TO_BIT20(A)	(A==0x00100000)? 20 : MASK_TO_BIT21(A)
#define MASK_TO_BIT19(A)	(A==0x00080000)? 19 : MASK_TO_BIT20(A)
#define MASK_TO_BIT18(A)	(A==0x00040000)? 18 : MASK_TO_BIT19(A)
#define MASK_TO_BIT17(A)	(A==0x00020000)? 17 : MASK_TO_BIT18(A)
#define MASK_TO_BIT16(A)	(A==0x00010000)? 16 : MASK_TO_BIT17(A)
#define MASK_TO_BIT15(A)	(A==0x00008000)? 15 : MASK_TO_BIT16(A)
#define MASK_TO_BIT14(A)	(A==0x00004000)? 14 : MASK_TO_BIT15(A)
#define MASK_TO_BIT13(A)	(A==0x00002000)? 13 : MASK_TO_BIT14(A)
#define MASK_TO_BIT12(A)	(A==0x00001000)? 12 : MASK_TO_BIT13(A)
#define MASK_TO_BIT11(A)	(A==0x00000800)? 11 : MASK_TO_BIT12(A)
#define MASK_TO_BIT10(A)	(A==0x00000400)? 10 : MASK_TO_BIT11(A)
#define MASK_TO_BIT09(A)	(A==0x00000200)? 9  : MASK_TO_BIT10(A)
#define MASK_TO_BIT08(A)	(A==0x00000100)? 8  : MASK_TO_BIT09(A)
#define MASK_TO_BIT07(A)	(A==0x00000080)? 7  : MASK_TO_BIT08(A)
#define MASK_TO_BIT06(A)	(A==0x00000040)? 6  : MASK_TO_BIT07(A)
#define MASK_TO_BIT05(A)	(A==0x00000020)? 5  : MASK_TO_BIT06(A)
#define MASK_TO_BIT04(A)	(A==0x00000010)? 4  : MASK_TO_BIT05(A)
#define MASK_TO_BIT03(A)	(A==0x00000008)? 3  : MASK_TO_BIT04(A)
#define MASK_TO_BIT02(A)	(A==0x00000004)? 2  : MASK_TO_BIT03(A)
#define MASK_TO_BIT01(A)	(A==0x00000002)? 1  : MASK_TO_BIT02(A)
#define MASK_TO_BIT(A)    	(A==0x00000001)? 0  : MASK_TO_BIT01(A)

#ifndef SET
#define SET                      1
#define RESET                    0
#endif

#define BIT_BAND_PER(REG,BIT_MASK) (*(volatile uint32_t*)(PERIPH_BB_BASE+32*((uint32_t)(&(REG))-PERIPH_BASE)+4*((uint32_t)(MASK_TO_BIT(BIT_MASK)))))

#define BIT_BAND_SRAM(RAM,BIT) (*(volatile uint32_t*)(SRAM_BB_BASE+32*((uint32_t)((void*)(RAM))-SRAM_BASE)+4*((uint32_t)(BIT))))

//Example: BIT_BAND_PER(TIM1->SR, TIM_SR_UIF) = 0; //сбросить бит TIM_SR_UIF в TIM1->SR
//Example2: BIT_BAND_SRAM(&a, 13) = 1; //установить 13-й бит в переменной "a"
//Example3: BIT_BAND_SRAM(&a, 13) ^= 1; //инвертировать 13-й бит в "a", не задевая другие биты переменной (псевдо-атомарность)
#endif


Теперь немного истории и для чего это нужно.
Устанавливать и сбрасывать биты — довольно частая операция, и обычно это выполняется инструкциями вида:

TIM1->SR |= TIM_SR_UIF;// - устанавливаем бит
TIM1->SR &= ~TIM_SR_UIF; // - сбрасываем.


Эти команды хорошо работают, хотя и имеют несколько недостатков:
Первое: При компиляции код имеет минимум 6 инструкций процессора. Казалось бы, вещь не особо волнующая программистов, и с этим можно согласиться в принципе. В век развивающихся контроллеров, когда размер памяти зачастую многократно превышает потребность — уже нет необходимости следить за каждым байтом. А скорость тактирования процессора «сожрет» эти инструкции мгновенно.

Второе: Это куда более важно. Описанные выше инструкции имеют существенный недостаток — неатомарность операции, что приводит к большим проблемам в том случае, когда одни и те же регистры используются в разных прерываниях.

Давайте рассмотрим подробнее:

Например, у нас имеется регистр REG

если мы хотим выполнить инструкцию установки бита в нем обычным способом (операторы |=, &=~), то компилятор генерирует несколько инструкций, которые, в сущности, будут выполнять три логические операции:

1. Чтение из REG
     <<  ----   прерывание 1
2. Модификация значения (установка/сброс бита)
     <<  ----   прерывание 2
3. Запись в REG


Если за это время возникнет прерывание, которое использует этот же регистр для модификации, то возникнет нештатная ситуация. Все, что изменит прерывание- будет затерто последней операцией блока. Чтобы этого не случилось, нужно приводить работу этого блока к атомарной


1. Запрет прерываний
2. Чтение из REG
3. Модификация значения (установка/сброс бита)
4. Запись в REG
5. Разрешение прерываний


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

Из последнего примера видно, что простой сброс/установка бита обрастает до неприличия большим кодом. И особо важные прерывания не смогут вовремя ответить (конечно если есть такая критичность) из-за блокировки всех прерываний.

Выход из сложившейся ситуации нам дает производитель — сброс и установка битов с помощью прямой записи значения в адрес сектора bit-banding.
В стандартном описании можем увидеть формулу для расчета адреса области:

BB_ADDR_PER = PERIPH_BB_BASE + ((REG_ADDR - PERIPH_BASE) * 32) + (BIT * 4)

где: PERIPH_BASE — начальный адрес области устройств ввода-вывода (УВВ) (0x40000000);
PERIPH_BB_BASE — начальный адрес области доступа к битам регистров УВВ (0x42000000);
REG_ADDR – адрес изменяемого регистра УВВ;
BIT – номер изменяемого бита в регистре.

Чтобы установить требуемый бит в УВВ, нужно записать 0x01 в адрес BB_ADDR_PER
Чтобы сбросить бит — пишем 0x00 в адрес BB_ADDR_PER

Подобные операции можно производить и с областью оперативной памяти:

BB_ADDR_SRAM = SRAM_BB_BASE + ((SRAM_ADDR - SRAM_BASE) *32) + (BIT * 4)

где: SRAM_BASE — начальный адрес области ОЗУ (0x20000000);
SRAM_BB_BASE — начальный адрес области доступа к битам ОЗУ (0x22000000);
SRAM_ADDR – адрес изменяемого ОЗУ;
BIT – номер изменяемого бита в адресе.

Чтобы установить требуемый бит в ОЗУ, нужно записать 0x01 в адрес BB_ADDR_SRAM
Чтобы сбросить бит — пишем 0x00 в адрес BB_ADDR_SRAM

Все необходимые преобразования выполняет вышеописанный модуль, полностью освобождая программиста от подобных вычислений.
При константных значениях все преобразования происходят при обработке компилятором, поэтому даже при самой слабой оптимизации создадутся 3 инструкции процессора.

Описание функции BIT_BAND_PER:



BIT_BAND_PER(REG,BIT_MASK)=VALUE; //запись бита в регистр REG
VALUE=BIT_BAND_PER(REG,BIT_MASK); //чтение бита из регистра REG

где
REG — регистр УВВ (не указатель на него!)
BIT_MASK — маска бита. (2 в степени номера бита (1 << bit)). Именно так описаны биты регистров в стандартных библиотеках. Например, RCC_CIR_HSERDYIE описан так:

#define  RCC_CIR_HSIRDYIE                    ((uint32_t)0x00000400)        /*!< HSI Ready Interrupt Enable */

Хоть в реальности это 10-й бит, описан как 0x00000400 (1<<10) в виде маски для упрощения вызова стандартного вида |=, &=~
Функция MASK_TO_BIT(A), вызываемая из BIT_BAND_PER обратно преобразовывает данное число к номеру бита путем последовательного сравнения.

Важно отдавать себе отчет, что не все константы в CMSIS можно использовать, а только те, которые описывают конкретный бит, но не группу битов. Например,
#define  ADC_SQR2_SQ11    ((uint32_t)0x01F00000)  /*!<SQ11[4:0] bits (11th conversion in regular sequence) */
использовать нельзя, так как ADC_SQR2_SQ11 — это группа битов.
Если есть желание сразу установить/сбросить группу битов, то bit banding вам не поможет, и, добро пожаловать, старый добрый метод |=, &=~ со всеми вытекающими последствиями

VALUE может принимать значения 0 или 1, а так же RESET или SET

Описание функции BIT_BAND_SRAM:


BIT_BAND_SRAM(SRAM_ADDR,BIT)=VALUE; // запись бита в SRAM
VALUE=BIT_BAND_SRAM(SRAM_ADDR,BIT); // чтение бита из SRAM

где
SRAM_ADDR — адрес в ОЗУ, либо указатель на переменную
BIT — номера бита
VALUE может принимать значения 0 или 1, а так же RESET или SET

Подробно о bit banding можно почитать в других постах этого сайта, да и вообще во всем интернете, например, здесь.
Однако перерыв всю библиотеку CMSIS, официальные сайты STM, и русскоязычный контент я так и не нашел инструментария, обеспечивающего удобную работу с bit banding без преобразования стандартных констант.

Посидев и поразмыслив немного, у меня родился вот такой, в принципе, небольшой файл хедер.

Update:
Добавлено volatile
  • +7
  • 19 июля 2013, 14:05
  • Mihail
  • 1
Файлы в топике: bitbanding.zip

Комментарии (90)

RSS свернуть / развернуть
Ух. Надо попробовать )
0
все это уже есть в CMSIS, зачем генерировать такую простыню?
0
что именно есть в CMSIS? я не нашел
0
ну так там метод неуниверсальный. Чтобы обратиться к какому-то биту регистра — нужно сперва дефайн свой написать. и так на каждый регистр. а здесь поставил #include«bitbanding.h» в начале проги и забыл про все остальное.
0
эта функция вырождается в три инструкции процессора
А последовательные операции оптимизируются? Хотя всё равно медленно. И этим должен заниматься компилятор…
0
При константных значениях (а при описании УВВ именно так) операции оптимизируются на уровне препроцессора. (часть из них по преобразованию BIT) поэтому компилятор с легкостью упрощает до 3-х инструкций процессора
0
насчет инструкций процессора и т.п.…
пробовал компилить такую рыбу на STM32F100:

#include "bitbanding.h"
volatile uint32_t f;
void test1()
{
    f |= 1;
}

#define F       BIT_BAND_SRAM(f, 0)
void test2()
{
    F = 1;
}

int main()
{
    test1();
    test2();
    return 0;
}

Использовал компиляторы (gcc, IAR), оптимизация по размеру.
Результат: размер test1() меньше, чем test2()
Так что из плюсов размер кода наверное нужно убрать, а остается только то для чего все это задумывалось — атомарность
0
если в качестве аргумента присваивать переменную в адресе, то действительно, код немного распухает, так как там имеется математика. Если указать жестко адрес в SRAM — то код становится меньше.

#define f (*(uint32_t*)0x20004000) //адрес в памяти SRAM
#define F BIT_BAND_SRAM(&f,0)

F=1;
0
Атомарность — это уже офигенно. Размер можно уменьшить в асме весьма, в си с этим посложнее.
Насчёт прямого указания адреса — это очень грубый «хак», так не надо делать.

Размер кода, конечно, при обычном доступе в среднем получается меньше, а вот время выполнения — не знаю (шина ж тоже может давать задержку, но я туда не лез, потому не очень в курсе, велики ли они и от чего зависят).
0
согласен с тобой, что жестко прописывать адреса только ради экономии вдух трех байт во флеш — это безумие. Просто показал, в чем причина… Кстати, это вопрос к компиляторам, они почему-то не распознают статическую переменную от динамической, чтобы выполнить лучшую оптимизацию. Так что как-то так… А для периферии там все красиво как раз получается, потому что все структуры, регистры четко по адресам разбиты. Там идет максимальная оптимизация…
0
Да, для периферии всё ок, там адреса как раз так и прописаны…

А компиляторы не могут, так как адреса на этапе компиляции не знают — их назначает уже потом линкёр. Потому и вычисляют в контроллере.

Кстати, я пока глядел, что компилятор выдаёт, заметил, что Кейл статические функции при должном уровне оптимизации может инлайнить.
0
они почему-то не распознают статическую переменную от динамической, чтобы выполнить лучшую оптимизацию.
Что-то это странное:

int A;
....
int * ptr=&A;

тут что-то вычислять надо?
0
Для битбенда надо вычислить адрес бита в специальной отражаемой памяти по формуле. Если адрес заранее известен, то вычисления может выполнить компилятор.
0
Так а о чём? Адрес-то переменной известен. Хотя… не на этапе компиляции. Проверять надо…
0
Именно что не на этапе компиляции. Это уже линкёр адреса определяет. А он уже ничего вычислить не может.
0
а разве нет обратной связи у линкера с компилятором? Чтобы компилировался код в несколько заходов? Типа «Релиз» и все такое… Упущение с их стороны…
0
Такой связи нет и это чуть ли не главные грабли традиционной схемы «компилятор+линкер». Сейчас некоторые из проблем связанные с этим пытаются обойти с помощью фишек типа LTO в gcc.
0
Поздравляю! КЭП+)
0
спасибо))) надеюсь всем пригодится!
0
Не совсем понял как юзать на практике, можете написать пример кода?
0
написал. пересмотрите статью. Пример кода в самом начале
0
Это куда более важно. Описанные выше инструкции имеют существенный недостаток — не атомарность операции, что приводит к большим проблемам в том случае, когда одни и те же регистры используются в разных прерываниях.
А мьютексы на что придуманы?
0
Во-первых, мьютексы это не бесплатно. Во-вторых, довольно его легко забыть залочить и разлочить если нужные биты щелкаются в большом количестве мес. В-третих, если что-то можно сделать атомарно без мьютексов, то это проще, эффективнее и надежнее, чем с ними.
+6
Только не поняло почему мьютексы не безплатно, используете например FreeRTOS там безплатно:)
0
Только не поняло почему мьютексы не безплатно, используете например FreeRTOS там безплатно:)
www.google.ru/search?q=lock-free+wait-free&ie=utf-8&oe=utf-8&rls=org.mozilla:ru:official&client=firefox-a&gws_rd=cr
Очень много полезного по ссылке выше. Помогает писать эффективные и надежные программы.
0
с ВВ меньше оверхед + не запрещаются прерывания
0
что вроде мьютекса у меня и описано ниже. Запрет прерываний иначе. Читайте статью до конца
0
Все преобразования происходят при обработке препроцессором, поэтому даже при самой слабой оптимизации компилятора после обработки создадутся 3 инструкции процессора.
В данном случае важно отметить «пока параметры константные». Как только адрес будет переменной:
void func(uint32_t* addr) {
  BIT_BAND_PER(*addr,BIT,VAL);
}
возвращается арифметика.
А как только BIT станет переменной… <censured>
0
да. мой модуль ориентирован в основном на использование констант. Так устройства ввода вывода — константные велечины.
С областью памяти конечно все иначе. Переменную в BIT при вызове BIT_BAND_PER лучше не писать, потому что функция преобразования не хилая получится. А при обращении к памяти нужно просто номер бита указывать. Сейчас подкорректирую исходник
0
Константные они когда используется один-два модуля периферии =) Написал и забыл.

А если это порты, коих штук десять, или таймеры, коих не меньше… Все по базовым функциям и регистрам одинаковы, но адреса у всех разные. Для общего случая надо функцию будет всё же отдельную делать. Ей хоть и больше тактов надо будет, но зато универсально и вполне себе атомарно:
bb_Set(void * Address, uint8_t Bit);
bb_Clear(void * Address, uint8_t Bit);

Типа того.
0
в этом случае тоже можно использовать мой шаблон, возможно слегка подправив определения функции, исключив обратное преобразования 32-битного слова в номер бита (BYTE_TO_BITBAND) и помещать ее в тело функции
bb_Set
0
бред…
зачем вычислять адреса битов в функциях, если их можно вычислить на этапе компиляции?
если происходит частая запись/чтение битов, при каждом обращении будет пересчитываться одно и то же, зачем весь этот оверхед?
0
А если это управление портом каким-нибудь, например? Там же хрен ты знаешь, каким захотят битом щёлкать.
void gpio_Set(uint8_t Pin)
{
    // По идее, нужно и порт указывать тоже, но хрен с ним
}


И как на этапе компиляции вычислить, что передадут? =) Впрочем, для таких случаем номер вычислять не нужно, так что он есть. Но этап компиляции в подобных случаях не помощник. Ведь и кроме портов есть периферия, где такой подход применим.
Вариант «везде используй чисто регистры для доступа к портам и т.д. и радуйся препроцессорным вычислениям» не рассматриваю из-за его громоздкости и трудностей поддержки. Он годен для особо скоростных применений, а не для общих.
0
опять бред.
#define BITP( addr, bit ) (BITBAND_PERI(addr,bit))

*BITP(&GPIOA->ODR, 6) = 0;
*BITP(&GPIOA->ODR, 6) = 1;

#define LED1 *BITP(&GPIOB->ODR, 0)
#define LED2 *BITP(&GPIOB->ODR, 1)

LED1 = 0;
LED2 = 1;
0
А если их не два, а штук тридцать с лишним?
#define BITP( addr, bit ) (BITBAND_PERI(addr,bit))

#define LED1 *BITP(&GPIOA->ODR, 0)
#define LED2 *BITP(&GPIOB->ODR, 1)
//…
#define LED32 *BITP(&GPIOF->ODR, 15)

void led_On(int Index)
{
switch(Index)
{
case 0: LED0 = 1; break;
case 1: LED1 = 1; break;
case 2: LED2 = 1; break;
case ...
case 32: LED32 = 1; break;
}
}

Так?

Какой-нибудь dmx-свитчер на множество каналов, щёлкающий релюшками. Или управление столбцами матрицы какой-нибудь, которая в силу каких-то причин раскидана как попало?

Хотя, конечно, порты не самый лучший пример, ибо в стм у них и так есть операции атомарного сброса/установки. Но не суть.
+1
А если их не два, а штук тридцать с лишним?
тогда не понятно что тут дадут функции вместо макросов?
0
определить ведь можно и так:
leds[] = {BITP(&GPIOA->ODR, 0), BITP(&GPIOA->ODR, 1), ... };
*leds[0] = 0;
*leds[1] = 1;


все равно оверхед вычисления адресов намного меньше.
+1
ну, например, можно сделать массив:
uint8_t* LEDS[32] = {BITBAND_PERI(&GPIOA->ODR,0),BITBAND_PERI(&GPIOA->ODR,1)...}

void led_on(int Index)
{ *(LEDS[Index])=1 };

 
LEDS[32]
0
Ну да, я к массиву и подвожу.
А теперь ещё добавить так же настройку портов на выход, настройку скорости работы/подтяжки/ещё чего-то. И радоваться, если вдруг что-то изменится в плате, и надо все пины перетасовать в этих нескольких гигантских массивах.
Это, конечно, можно сделать, но простота не на уровне.

Функции, хоть и имеют небольшой довесок по командам, ничем более не ограничены.
+1
если диоды на одном порту, можно и на основе базового адреса:
#define LEDS_BASE BITP(&GPIOA->ODR, 0)
#define led(n) *(LEDS_BASE + (n << 2))
led(0) = 0;
led(1) = 1;
-1
этот вариант все равно приведет к математике, если использовать i в качестве индекса
0
ну если речь идет о написании некой ОС, которая подразумевает сторонние программы, то да, наверное, нужно использовать математику, выделять ресурсы и всякое разное, чтобы программы между собой не конфликтовали.
0
Просто адрес порта и пин берутся извне:
— интеррактивный запрос пользователю;
— загрузка из фойла конфигурации;
— любое прочее указание извне.
0
Сейчас подкорректирую исходник
Сейчас вы допустили ошибку. У вас 2 «одинаково» названных макроса (разница периф/рам), которые принимают параметры разного харрактера. Для переферии передается маска бита, а для рам его порядковый номер, но это ни как не отражено.
P.S.: вероятно это была одна из причин отсутствия готовых «библиотек» с битбангом.
0
вообще, по хорошему производителю следовало бы сделать модуль, где были константы не только в виде масок, но и порядковый номер бита, чтобы можно было пользоваться стандартными макросами BITBAND_PERIF без дополнительных преобразований «ручками». Поэтому при использовании макроса BIT_BAND_PER происходит это преобразование автоматически, облегчающая жизнь. Функция BIT_BAND_SRAM появилась позже, в довесок, и своей оригинальностью не блещет.
0
можно, как вариант, использовать функцию преобразования маски в порядковый номер бита BYTE_TO_BITBAND (может переименовать покороче) и использовать
BITBAND_PERIF(& <регистр> BYTE_TO_BITBAND(<маска>));
0
Нууу, как-то немного громоздко получается.
Я когда-то писал программу, которая гененрировала нечто подобное:

#define GPIOA_IN_0 (*((volatile unsigned long *) 0x42210100 ))
#define GPIOA_IN_1 (*((volatile unsigned long *) 0x42210104 ))
#define GPIOA_IN_2 (*((volatile unsigned long *) 0x42210108 ))
#define GPIOA_IN_3 (*((volatile unsigned long *) 0x4221010C ))
#define GPIOA_IN_4 (*((volatile unsigned long *) 0x42210110 ))
#define GPIOA_IN_5 (*((volatile unsigned long *) 0x42210114 ))

И так для всех битов всех регистров ODR и IDR всех портов. Потом в программе это очень удобно использовать —
GPIOA_OUT_4 = 1;
0
простыню можно сократить если номер бита сделать параметром макроса и писать GPIOA_OUT(4) = 1
0
а теперь только представьте, какую простыню вам надо накатать, если захочется сбрасывать све флаги свех событий в УВВ. Прикинули? На неделю щелкать калькулятором и писать цифры 0х42… а здесь пожалуйста, пишешь регистр, пишешь СТАНДАРТНОЕ название этого бита в библиотеке stm32f10х.h и радуйся, что ВСЕ в «мире» биты регистров тебе подвластны
+1
Набивать циферки — глупое занятие, для этого существуют компьютеры.
0
А потом думать, что ж компилятор-то такой медленный, подумаешь, десяток хедеров по 10 МБ каждый?)

Но вообще, это плохой подход — препроцессор и сам может адреса посчитать прекрасно. Да ещё и не универсальный.
+1
Правильно, втопку все дефайны, пусть сам все считает! :)

На кой тогда вообще бит-бандинг нужен если ему еще что-то считать прижется?
0
Так дефайны и обрабатываются препроцессором вообще-то. А в код подставится высчитанное компилятором число.

Даже если и надо считать (контроллер тоже компьютер, он только этим и занимается как бы), доступ к отдельным битам оперативки и периферии, не затрагивающий остальные — это весьма и весьма. Весомый плюс к достижению атомарности операций снятия/установки бита без всяких регулирующих доступ конструкций.
+1
Ой, простите, спросонья прочитал «процессор» вместо «препроцессор» :)

А так это больше дело вкуса уже.
0
Хм =D Если честно, мне абсолютно не жалко, если это будет делать и процессор. По крайней мере в тех случаях, когда количество тактов роли не играет, а удобство поддержки кода — ещё как.

До тех пор, пока дело ограничивается парой-тройкой флагов, потом уже упорешься хедеры на каждый флаг лепить =)
+1
Ну лепление хэдеров разумеется должно быть автоматическим, разумеется :)

Но вы меня почти уговорили, особенно если использовать дополнительные дефайны, как выше писали:

#define LED1 *BITP(&GPIOB->ODR, 0)
#define LED2 *BITP(&GPIOB->ODR, 1)

LED1 = 0;
LED2 = 1;
0
Лучше снижать зависимость от внешних утилит, когда этого можно без особых трудностей избежать, а то потом аукнутся может =)
0
когда речь идет о таких регистрах как GPIOA->ODR это еще ничего, так как там есть математика в отношении всех GPIOX. Кстати, гораздо разумнее использовать регистры на установки.сброса бит BRR и BSRR, так как они позволяют атомарно сбрасывать и устанавливать сразу НЕСКОЛЬКО битов. Несомненно эти регистры «круче» BIT_BAND_PER. Однако такого нет в других устройствах УВВ, и вот здесь, чтобы сделать таблицу чисел — нужно будет попотеть. так как все УВВ по разному названы, по разному располагаются в памяти их регистры. да и зачем, когда есть вот такой сравнительно небольшой хедер, который можно использовать абсолютно для всех регистров УВВ. Пользуйтесь, я не жадный. Вместе будем повышать культуру программирования и выживать из себя быдлокодера
+1
STM32
не атомарность операции
Для начала: неатомарность операции.
Далее: Вы изучали прерывания STM32? Контекст при вызове прерывания сохраняется аппаратно, даже при вызове вложенных прерываний. Даже если писать программу на ассемблере, нет необходимости дополнительно сохранять регистры процессора.
Единственный вариант неатомарности, это когда программист напрямую меняет содержимое переменных, используемых в программе, из прерываний. Любой вменяемый программер будет применять хотя бы флаги, взводимые в прерываниях. Либо использовать двойную буфферизацию и опять же флаги.
ИМХО.
0
Далее:
Не понятна ваша притензия. Автор предлагает альтернативное решение, как можно обойтись без дополнительных флагов/блокировок, а вы тычите «Любой вменяемый программер будет применять хотя бы флаги...». Отсюда возникает вопрос: а Вы изучали область побитовой адресации в STM32, что это и зачем введено?
+1
нет необходимости дополнительно сохранять регистры процессора
При чём тут регистры?)

напрямую меняет содержимое переменных, используемых в программе, из прерываний
Так с ним и борются как бы. Кроме переменных есть и периферия, весьма обширная, с кучей регистров, к которым может быть доступ из кучи мест (всякое управление тактированием и питанием хотя бы взять). Бывают ещё ОС разного плана, где тоже обычно нет каких-то гарантий.

Флаги — хорошо, но отнюдь не панацея, не везде применимы, да и просто бывают лучшие способы добиться результата в отдельных случаях.
+1
Markusha, вы плаваете в понятиях «атомарная операция» и «автоматический стек». Поверьте, это совершенно разные вещи, как если бы сравнивать молоко и скорость автомобиля. Автоматический стек сохраняет некоторые регистры процессора в буфер стека, избавляя программиста (компилятор) от повторяющихся инструкций. Данная операция лишь уменьшает код, не более того. Атомарная (в значении слова нужно понимать НЕДЕЛИМАЯ) операция — эта такая операция, которая происходит либо за один такт процессора, либо за любое количество тактов, в промежутке которых НЕДОПУСТИМО выполнение каких либо прерываний или исполнение этого же куска кода поверх первого другим потоком (в многопоточных приложениях). Это как в слове «здесь» или тот же «атом» нельзя отделить часть букв и перенести на другую строчку. Согласитесь — это к стеку никакого отношения не имеет и предполагает совершенно другое. Кстати в статье и говорится, как атомарность эту самую придать, а именно запретом прерывания на момент выполнения всех инструкций операции вида |=, &=~. Читайте внимательнее статью, пользуйтесь ссылками в ней. Это полезно
+1
подкорректировал статью и файл выходной. сделал более универсальным и читабельным
0
BIT_MASK_ERROR_IN_BIT_BAND_PER — интересное решение, что то я не думал о таком.
0
я тоже не думал. Само пришло как-то))
+1
к сожалению, это не работает, пришлось вернуть на место. Просто я не до конца понимал, как работает условие в функциях и во что оно выливается в конечном итоге
0
По ссылке мне кажется более элегантное решение для размещение битов в оперативной памяти
http://www.micromouseonline.com/2013/01/13/an-improved-bit-banding-approach/
0
Результирующий машинный код краток, но сколько всякого обвеса и настроек надо запилить><
+1
я за 5 минут всё сделал. Дело не в краткости машинного кода, а в атомарности и удобстве использования.
0
в библиотеке для 32F0 есть определения битов и по номеру, и по маске.
пляски с бубном MASK_TO_BITх там не нужны.
0
  • avatar
  • zubb
  • 01 августа 2013, 17:33
это хорошо.
0
у STM32F0 нет Bitband, приходится делать так (управление 4-битной шиной LCD):
GPIOB->BSRR = (d & 1) ? GPIO_BSRR_BS_1 : GPIO_BSRR_BR_1;
GPIOA->BSRR = (d & 2) ? GPIO_BSRR_BS_7 : GPIO_BSRR_BR_7;
GPIOA->BSRR = (d & 4) ? GPIO_BSRR_BS_6 : GPIO_BSRR_BR_6;
GPIOA->BSRR = (d & 8) ? GPIO_BSRR_BS_5 : GPIO_BSRR_BR_5;
0
  • avatar
  • zubb
  • 03 августа 2013, 16:40
приходится делать так
Не нравится так, сделайте по-другому. Например через таблицу.
0
ДА, наверное это самый оптимальный код
0
с битбандом чуть полегче бы было, но ненамного:
#define D_0 BIT_BAND_PER(GPIOB,GPIO_ODR_1)
#define D_1 BIT_BAND_PER(GPIOA,GPIO_ODR_7)
...
D_0 = (d & 1);
D_1 = (d & 2)>0;
0
вместо (d & 2)>0 обычно пишут !!(d & 2).
компилер такое понимает и оптимизирует
0
кстати, битбанд понимает любое ненулевое число, так что вполне возможен такой вариант:
D_0 = (d & 1);
D_1 = (d & 2);
0
согласно programming manual, значение имеет только нулевой бит записываемого слова, если там 0 — то запишется 0, если 1 — то запишется 1. Биты с 1 по 31 игнорируются. Соответственно, запись числа 0х01 дает такой же эффект что и запись 0xFF, запись 0х00 дает такой же эффект что и запись 0x0E.
Поэтому D_1 = (d & 2) у вас запишет однозначно 0.
0
спасибо за разъяснение
0
малек подправил макрос BIT_BAND_SRAM. Чтобы ворнинги не мучали…
0
в исходник добавил примеры, как можно использовать сей модуль. На мой взгляд, интерес может представлять собой третий пример:

//Example3: BIT_BAND_SRAM(&a, 13) ^= 1; //инвертировать 13-й бит в "a", не задевая другие биты переменной (псевдо-атомарность)
0
здесь получается, что данный метод атомарен по отношению к другим битам переменной, однако неатомарен к изменяемому, так как здесь происходит Чтение->Модификация->Запись конкретного бита в ОЗУ
0
В макросы из статьи никак въехать не могу.
ИМХО здесь проще описано eugenemcu.ru/publ/13-1-0-77
Вопрос только, насколько верно?
0
  • avatar
  • Denya
  • 01 сентября 2014, 14:03
Если звездочку не понимаешь — то почитай про указатели. Это одно из самых сильных мест этого языка программирования.
0
Не, я про вычисления адресов для бит бандинга.
Только сел за STM32, сейчас потихоньку вкуриваю. В принципе в статье тоже самое, что и по моей ссылке.
Однако я тут прикинул, что бит бандинг юзать будет не универсально, т.к. в M0, например, этой фичи нет. Могут возникнуть косяки при последующем переносе кода.

По идее, чтобы осуществить атомарность выставления флагов в прерываниях, просто использовать для каждой группы свой регистр.
Или не?
0
т.к. в M0, например, этой фичи нет

Как так нет?
Ты не вкурил однако — дело не в том какой это МК… это просто работа с регистрами, памятью и прочее…
0
Ну как же, щелкать битиками можно и с помощью стандартных дефайнов из stm32f10x.h
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
#define READ_BIT(REG, BIT) ((REG) & (BIT))

А здесь фишка битбанда в атомарном обращении с битами в ячейках RAM.
Цитата: «Суть его заключается в том, что пространстве ОЗУ микроконтроллеров STM32, помимо области хранения данных имеется дополнительная область регистров каждый из которых связан с отдельным битом в области хранения или в области регистров ввода-вывода. При этом запись в указанный регистр нулевого значения приводит к аппаратному сбросу соответствующего бита в области хранения переменных, а запись ненулевого значения устанавливает указанные биты.»
Типа того как управлять GPIO через BSRR
0
О, прошу прощения — я пробежал по-быстрому — был неправ. Битбанг как явление есть везде, про атомарность — я пропустил.
0
Добавил volatile к макросам.
0
Обидно в H7 такого уже нет из-за кэшей.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.