123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- [FAQ] Изменение и чтение отдельных битов
- имеем порт J, в codevision работать с ним как PORTJ.n нельзя,
- поэтому для установки бита N в единицу не трогая другие биты делаем так:
- PORTJ = PORTJ | (1<<N); или короче PORTJ |= (1<<N);
- установка бита N в ноль тогда будет:
- PORTJ = PORTJ & ~(1 << N);
- -------------------
- #ifndef _BV // If !WinAVR
- #define _BV(n) (1 << (n))
- #endif
- #define set_bit(p, n) p |= _BV(n)
- #define clear_bit(p, n) p &= ~_BV(n)
- #define invert_bit(p, n) p ^= _BV(n)
- -------------------
- ПРОВЕРЕНО в компиляторах IAR, CVAVR, ImageCraft ICC
- 1) вариант для любого компилятора:
- // объявление:
- #define SET_B(x) |= (1<<x)
- #define CLR_B(x) &=~(1<<x)
- #define INV_B(x) ^=(1<<x)
- // x - номер бита в регистре
- // использование:
- PORTB SET_B(5); // "установить" бит5
- PORTB CLR_B(2); // "очистить" бит2
- PORTB INV_B(6); // инвертировать бит6
- "установить" значит сделать "1"
- "очистить" значит сделать "0"
- "инвертировать" - сделать "0" если был "1" и наоборот.
- ===============================================================================
- Вот классический алгоритм реверса битов в байте:
- unsigned char b = 0x72; // b = 01110010b
- // Меняем местами соседние биты в парах
- b = (b & 0x55) << 1 | (b & 0xAA) >> 1;
- // Меняем местами пары битов в тетрадах
- b = (b & 0x33) << 2 | (b & 0xCC) >> 2;
- // Меняем местами тетрады битов в байтах числа
- b = (b & 0x0F) << 4 | (b & 0xF0) >> 4;
- ===============================================================================
- // Тип, описывающий глобальные флаги программы
- typedef struct _SFLAGS {
-
- uint8_t KeyModePressed: 1; // Состояние клавиши MODE
- uint8_t KeyColorPressed: 1; // Состояние клавиши COLOR
- uint8_t GotoSleep: 1; // Команда "Заснуть" для основного потока (main)
- uint8_t RxComplete: 1; // Принята посылка в USART0
- uint8_t: 4; // дополнение до 8 бит (резерв)
-
- } SFLAGS;
- volatile struct //flags
- {
- unsigned char Direction : 1;
- unsigned char SensorFree : 1;
- unsigned char Tick : 1;
- unsigned char DirChd : 1;
- } Flag;
- использование:
- Flag.Tick = TRUE;
- ===============================================================================
- Си для микроконтроллеров и чайников.
- Часть 2. Операции с переменными и регистрами микроконтроллера.
- § > Обзор стандартных операций с регистрами.
- Настало время перейти к более серьёзным операциям над регистрами и программными переменными. Управление работой микроконтроллера в большинстве случаев сводится к следующему простому набору действий с его регистрами:
- 1. Запись в регистр необходимого значения.
- 2. Чтение значения регистра.
- 3. Установка в единицу нужных разрядов регистра.
- 4. Сброс разрядов регистра в ноль.
- 5. Проверка разряда на логическую единицу или логический ноль.
- 6. Изменение логического состояния разряда регистра на противоположное.
- Во всех указанных действиях принимает участие оператор присваивания языка Си, записываемый в виде знака равенства. Принцип действия оператора примитивно прост - он записывает в регистр или переменную расположенную слева от него, значение того, что записано справа. Справа может находится константа, другой регистр, переменная либо состоящее из них выражение, например:
- A = 16; // Присвоить переменной A значение 16;
- A = B; // Считать значение переменной B и присвоить это значение переменной A;
- A = B+10; // Считать значение переменной B, прибавить к считанному значению 10, результат присвоить переменной A (значение переменной B при этом не изменяется).
- § > Запись и чтение регистров.
- Из рассмотренных примеров видно, что оператор присваивания сам по себе решает две первые задачи — запись и чтение значений регистров. Например для отправки микроконтроллером AVR байта по шине UART достаточно записать его в передающий регистр с именем UDR:
- UDR = 8; // Отправить по UART число 8;
- Чтобы получить принятый по UART байт достаточно считать его из регистра UDR:
- A = UDR; // Считать принятый байт из UART и переписать в переменную A.
- § > Установка битов регистров.
- Язык Си не имеет в своём составе команд непосредственного сброса или установки разрядов переменной, однако присутствуют побитовые логические операции "И" и "ИЛИ", которые успешно используются для этих целей.
- Оператор побитовой логической операции "ИЛИ" записывается в виде вертикальной черты - "|" и может выполнятся между двумя переменными, а так же между переменной и константой. Напомню, что операция "ИЛИ" над двумя битами даёт в результате единичный бит, если хотя бы один из исходных битов находится с состоянии единицы. Таким образом для любого бита логическое "ИЛИ" с "1" даст в результате "1", независимо от состояния этого бита, а "ИЛИ" с логическим "0" оставит в результате состояние исходного бита без изменения. Это свойство позволяет использовать операцию "ИЛИ" для установки N-ого разряда в регистре. Для этого необходимо вычислить константу с единичным N-ным битом по формуле 2^N, которая называется битовой маской и выполнить логическое "ИЛИ" между ней и регистром, например для установки бита №7 в регистре SREG:
- (SREG | 128) — это выражение считывает регистр SREG и устанавливает в считанном значении седьмой бит, далее достаточно изменённое значение снова поместить в регистр SREG:
- SREG = SREG | 128; // Установить бит №7 регистра SREG.
- Такую работу с регистром принято называть "чтение - модификация - запись", в отличие от простого присваивания она сохраняет состояние остальных битов без именения.
- Приведённый программный код, устанавливая седьмой бит в регистре SREG, выполняет вполне осмысленную работу - разрешает микроконтроллеру обработку программных прерываний. Единственный недостаток такой записи — в константе 128 не легко угадать установленный седьмой бит, поэтому чаще маску для N-ного бита записывают в следующем виде:
- (1<<N) - это выражение на языке Си означает, число один, сдвинутое на N разрядов влево, это и есть маска с установленным N-ным битом. Тогда предыдущий код в более читабельном виде:
- REG = SREG | (1<<7);
- или ещё проще с использование краткой формы записи языка Си:
- REG |= (1<<7);
- которая означает - взять содержимое справа от знака равенства, выполнить между ним и регистром слева операцию, стоящую перед знаком равенства и записать результат в регистр или переменную слева.
- § > Сброс битов в регистрах.
- Ещё одна логическая операция языка Си – побитовое "И", записывается в виде символа "&". Как известно, операция логического "И", применительно к двум битам даёт единицу тогда и только тогда, когда оба исходных бита имеют единичное значение, это позволяет применять её для сброса разрядов в регистрах. При этом используется битовая маска, в которой все разряды единичные, кроме нулевого на позиции сбрасываемого. Её легко получить из маски с установленным N-ным битом, применив к ней операцию побитного инвертирования:
- ~(1<<N) в этом выражении символ "~" означает смену логического состояния всех битов маски на противоположные. Так, например, если (1<<3) в двоичном представлении – 00001000b, то ~(1<<3) уже 11110111b. Сброс седьмого бита в регистре SREG будет выглядеть так:
- SREG = SREG & (~ (1<<7)); или кратко: SREG &= ~ (1<<7);
- В упомянутом ранее заголовочном файле для конкретного микроконтроллера приведены стандартные имена разрядов регистров специального назначения, например:
- #define OCIE0 1
- здесь #define – указание компилятору заменять в тексте программы сочетание символов "OCIE0" на число 1, то есть стандартное имя бита OCIE0, который входит в состав регистра TIMSK микроконтроллера Atmega64 на его порядковый номер в этом регистре. Благодаря этому установку бита OCIE0 в регистре TIMSK можно нагляднее записывать так:
- TIMSK|=(1<<OCIE0);
- Устанавливать или сбрасывать несколько разрядов регистра одновременно можно, объединяя битовые маски в выражениях оператором логического "ИЛИ":
- PORTA |= (1<<1)|(1<<4); // Установить выводы 1 и 4 порта A в единицу;
- PORTA&=~((1<<2)|(1<<3)); // Выводы 2 и 3 порта A сбросить в ноль.
-
- § > Проверка разрядов регистра на ноль и единицу.
- Регистры специального назначения микроконтроллеров содержат в своём
- составе множество битов-признаков, так называемых "флагов”, уведомляющих
- программу о текущем состоянии микроконтроллера и его отдельных модулей.
- Проверка логического уровня флага сводится к подбору выражения, которое
- становится истинным или ложным в зависимости от того установлен или
- сброшен данный разряд в регистре. Таким выражением может служить
- логическое "И” между регистром и маской с установленным разрядом N на
- позиции проверяемого бита :
- (REGISTR & (1<<N)) в этом выражении операция "И” во всех разрядах кроме
- N-ного даст нулевые значения, а проверяемый разряд оставит без изменения.
- Таким образом возможное значения выражения будут или 0 или 2^N, например
- для второго бита регистра SREG:
- Приведённое выражение можно использовать в условном операторе if
- (выражение) или операторе цикла while (выражение), которые относятся к
- группе логических, то есть воспринимают в качестве аргументов значения
- типа истина и ложь. Поскольку язык Си, приводя числовые значения к
- логическим, любые числа не равные нулю воспринимает как логическую истину,
- значение (REGISTR & (1<<N)) равное 2^N в случае установленного бита, будет
- воспринято как "истина".
- Если появляется необходимость при установленном бите N получить для нашего
- выражения логическое значение «ложь», достаточно дополнить его оператором
- логической инверсии в виде восклицательного знака - !(REGISTR & (1<<N)).
- Не следует путать его с похожим оператором побитовой инверсии (~) меняющим
- состояние битов разряда на противоположное. Логическая инверсия работает
- не с числовыми значениями, а с логическими, то есть преобразует истинное в
- ложное и наоборот. Такая конструкция приводится в DataSheet на Atmega как
- пример для ожидания установки бита UDRE в регистре UCSRA, после которого
- можно отправлять данные в UART:
- while ( !( UCSRA & (1<<UDRE)) ) { } // Ждать установки UDRE.
- здесь при сброшенном бите UDRE выражение ( UCSRA & (1<<UDRE)) даст
- значение ”ложь”, инвертированное
- !( UCSRA & (1<<UDRE)) — ”истину”, и пока это так, программа будет
- выполнять действия внутри фигурных скобок, то есть не делать ничего
- (стоять на месте). Как только бит UDRE установится в единицу, программа
- перейдёт к выполнению действий следующих за конструкцией while(), например
- займётся отправкой данных в UART.
- § > Изменение состояния бита регистра на противоположное.
- Эту, с позволения сказать, проблему с успехом решает логическая операция
- побитного "ИСКЛЮЧАЮЩЕГО ИЛИ” и соответствующий ей оператор Си,
- записываемый в виде символа " ^ ”. Правило "исключающего или" с двумя
- битами даёт "истину” тогда и только тогда, когда один из битов установлен,
- а другой сброшен. Не трудно убедиться, что этот оператор, применённый
- между битовой маской и регистром, скопирует в результат биты стоящие
- напротив нулевых битов маски без изменения и инвертирует расположенные
- напротив единичных. Например, если: reg=b0001 0110 и mask=b0000 1111, то
- reg^mask=b0001 1001. Таким способом можно менять состояние светодиода,
- подключенного к пятому биту порта A:
- #define LED 5 // Заменять в программе сочетание символов LED на число 5
- (вывод светодиода).
- …
- PORTA ^=(1<< LED); // Погасить светодиод, если он светится и наоборот.
- § > Арифметика и логика языка Си.
- Мы рассмотрели типичный набор операций, используемый при работе с
- регистрами микроконтроллера. Помимо них в арсенале языка имеется ряд
- простейших арифметических и логических операций, описания которых можно
- найти в любом справочнике по Си, например:
- Для более подробного знакомства с операциями над переменными и языком Си в
- целом, рекомендую книгу "Язык программирования Си" Б. Керниган, Д. Ритчи.
|