COKPOWEHEU

View on GitHub

Практическое применение RISC-V при программировании микроконтроллеров

(Оглавление)

Сначала пару слов о терминологии:

Битовая маска - число, задающее с какими битами идет работа. Обычно это двоичное число, состоящее из нулей и с единственной единицей на месте интересующего бита. Бывает наоборот - все единицы и единственный ноль. Реже вместо одной единицы идет последовательность из двух, трех и т.д. единиц. Совсем редко единицы идут вразнобой, но это используется либо для какой-то совсем странной периферии, либо если несколько масок накладываются одновременно.

Обозначение числа как abcdefgh - каждой буквой обозначается отдельный бит. Хотя контроллер у нас 32-битный, писать 32 буквы было бы долго и ненаглядно, поэтому здесь переменные 8-битные.

3.1. AND (побитовое умножение)

A B A & B
0 0 0
0 1 0
1 0 0
1 1 1

Среди битов результата 1 будет только на тех позициях, на которых в обоих аргументах были 1. Если хотя бы в одном аргументе был 0, в результате тоже будет 0.

A     abcdefgh
B     00001000  &
A & B 0000e000

Стирает (выставляет в 0) все биты аргумента за исключением тех, которые в маске выставлены в 1.

3.2. OR (побитовое сложение)

A B A & B
0 0 0
0 1 1
1 0 1
1 1 1

Среди битов результата 0 будет только на тех позициях, на которых в обоих аргументах был 0. Если хотя бы в одном аргументе была 1, в результате тоже будет 1.

A     abcdefgh
B     00001000  |
A | B abcd1fgh

Выставляет в 1 все биты, в которых в маске была 1.

3.3. Сдвиги

Аргумент сдвигается на нужное количество битов вправо или влево. Освободившиеся места дополняются нулями.

A       abcdefgh
A << 2  cdefgh00
A >> 2  00abcdef

Простейшая битовая маска

A=1     00000001
A << 2  00000100

Число (битовая маска), содержащее единственный бит на нужной позиции

A       00000abc
A << 2  000abc00

Число (битовая маска), содержащее последовательность битов начиная с нужной позиции

3.4. Побитовая инверсия

Меняет каждый бит на противоположный: 0->1, 1->0

A       10011110
~A      01100001

3.5. Запись значения начиная с нужного бита

Мы с этим уже сталкивались при настройке порта. Допустим, надо записать комбинацию из четырех битов WXYZ в 16-битный регистр начиная с 4-й позиции

0b1111       00000000 00001111
(0b1111<<4)  00000000 11110000
~(0b1111<<4) 11111111 00001111

Далее полученная маска накладывается на исходную переменную:

A            abcdefgh ijklmnpq
~(0b1111<<4) 11111111 00001111  &
B            abcdefgh 0000mnpq

Потом формируется значение для присваивания:

C          00000000 0000WXYZ
(C<<4)     00000000 WXYZ0000

Которое накладывается на обрезанную по маске переменную

B          abcdefgh 0000mnpq
(C<<4)     00000000 WXYZ0000  |
D          abcdefgh WXYZmnpq

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

3.6. XOR (Исключающее или)

A B A & B
0 0 0
0 1 1
1 0 1
1 1 0

Инвертирует те биты аргумента, в которых в маске были 1.

A     10011110
B     00100100 ^
A ^ B 10111010

3.7. Специальный регистр BOP

Рассмотренная ранее работа непосредственно с OCTL не является безопасной. Если между чтением и записью возникнет прерывание, пытающееся также поменять значение OCTL, после выхода результат его работы будет перезаписан основным кодом. Чтобы этого избежать, в GD32VF103 есть специальный регистр BOP, старшие 16 битов которого отвечают за очистку соответствующих битов OCTL, а младшие - за выставление в 1. Значение имеет только запись единицы, запись нуля ни на что не влияет

li t0, PORTB_BOP
li t1, (1<<5)<<16
sw t1, 0(t0)
# PB5 = 0

li t1, (1<<6)
sw t1, 0(t0)
# PB6 = 1

В данном случае код, реализующий мигалку через чтение OCTL и запись BOP, приводить не обязательно, поскольку задача абсолютно не ответственная, и можно обойтись xor’ом. К тому же в реальности куда чаще встречается задача сброса в 0 или выставления в 1 независимо от текущего состояния.

3.8. Логические операции

Большая путаница возникает у изучающих язык Си при знакомстве с побитовыми (| , &, ~ )и логическими (||, &&, ! ) операциями. Разница между ними в том, что побитовые работают с переменной как с массивом отдельных битов, никак не связанных друг с другом, а логические - как с единым целым, где лог.0 это ноль и только ноль, а лог.1 - все остальное. Причем результат логической операции строго 0 или 1. Например:

uint8_t x = 0b00001111
uint8_t y = 0b01010101
uint8_t z;
  z = x | y;  // 0b01011111
  z = x || y; // 0b00000001
  z = x & y;  // 0b00000101
  z = x && y; // 0b00000001
  z = ~x;     // 0b11110000
  z = !x;     // 0b00000000

3.9. Частые ошибки

Д/З:

  1. Задано число, надо определить является ли оно степенью двойки или нет. Подсказка: в двоичной записи степень двойки содержит единственную единицу.

  2. Реальная задача, которую мне когда-то пришлось решать. В заголовочных файлах от производителей часто встречаются битовые маски, покрывающие сразу все поле регистра, например 0b00111000. Биты 3, 4, 5 вместе отвечают за режим работы чего-нибудь. Одна из практических задач - выставить эти биты в нужное значение, например, в 0b001. На языке Си это может выглядеть как

.

reg = (reg &~(0b111<<3)) | (0b001<<3)

Но здесь мне пришлось указать и размер битового поля, и смещение. Как при помощи битовой магии добиться того же, зная только маску, покрывающую все поле?

CC BY 4.0