C.txt 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. [FAQ] Изменение и чтение отдельных битов
  2. имеем порт J, в codevision работать с ним как PORTJ.n нельзя,
  3. поэтому для установки бита N в единицу не трогая другие биты делаем так:
  4. PORTJ = PORTJ | (1<<N); или короче PORTJ |= (1<<N);
  5. установка бита N в ноль тогда будет:
  6. PORTJ = PORTJ & ~(1 << N);
  7. -------------------
  8. #ifndef _BV // If !WinAVR
  9. #define _BV(n) (1 << (n))
  10. #endif
  11. #define set_bit(p, n) p |= _BV(n)
  12. #define clear_bit(p, n) p &= ~_BV(n)
  13. #define invert_bit(p, n) p ^= _BV(n)
  14. -------------------
  15. ПРОВЕРЕНО в компиляторах IAR, CVAVR, ImageCraft ICC
  16. 1) вариант для любого компилятора:
  17. // объявление:
  18. #define SET_B(x) |= (1<<x)
  19. #define CLR_B(x) &=~(1<<x)
  20. #define INV_B(x) ^=(1<<x)
  21. // x - номер бита в регистре
  22. // использование:
  23. PORTB SET_B(5); // "установить" бит5
  24. PORTB CLR_B(2); // "очистить" бит2
  25. PORTB INV_B(6); // инвертировать бит6
  26. "установить" значит сделать "1"
  27. "очистить" значит сделать "0"
  28. "инвертировать" - сделать "0" если был "1" и наоборот.
  29. ===============================================================================
  30. Вот классический алгоритм реверса битов в байте:
  31. unsigned char b = 0x72; // b = 01110010b
  32. // Меняем местами соседние биты в парах
  33. b = (b & 0x55) << 1 | (b & 0xAA) >> 1;
  34. // Меняем местами пары битов в тетрадах
  35. b = (b & 0x33) << 2 | (b & 0xCC) >> 2;
  36. // Меняем местами тетрады битов в байтах числа
  37. b = (b & 0x0F) << 4 | (b & 0xF0) >> 4;
  38. ===============================================================================
  39. // Тип, описывающий глобальные флаги программы
  40. typedef struct _SFLAGS {
  41. uint8_t KeyModePressed: 1; // Состояние клавиши MODE
  42. uint8_t KeyColorPressed: 1; // Состояние клавиши COLOR
  43. uint8_t GotoSleep: 1; // Команда "Заснуть" для основного потока (main)
  44. uint8_t RxComplete: 1; // Принята посылка в USART0
  45. uint8_t: 4; // дополнение до 8 бит (резерв)
  46. } SFLAGS;
  47. volatile struct //flags
  48. {
  49. unsigned char Direction : 1;
  50. unsigned char SensorFree : 1;
  51. unsigned char Tick : 1;
  52. unsigned char DirChd : 1;
  53. } Flag;
  54. использование:
  55. Flag.Tick = TRUE;
  56. ===============================================================================
  57. Си для микроконтроллеров и чайников.
  58. Часть 2. Операции с переменными и регистрами микроконтроллера.
  59. § > Обзор стандартных операций с регистрами.
  60. Настало время перейти к более серьёзным операциям над регистрами и программными переменными. Управление работой микроконтроллера в большинстве случаев сводится к следующему простому набору действий с его регистрами:
  61. 1. Запись в регистр необходимого значения.
  62. 2. Чтение значения регистра.
  63. 3. Установка в единицу нужных разрядов регистра.
  64. 4. Сброс разрядов регистра в ноль.
  65. 5. Проверка разряда на логическую единицу или логический ноль.
  66. 6. Изменение логического состояния разряда регистра на противоположное.
  67. Во всех указанных действиях принимает участие оператор присваивания языка Си, записываемый в виде знака равенства. Принцип действия оператора примитивно прост - он записывает в регистр или переменную расположенную слева от него, значение того, что записано справа. Справа может находится константа, другой регистр, переменная либо состоящее из них выражение, например:
  68. A = 16; // Присвоить переменной A значение 16;
  69. A = B; // Считать значение переменной B и присвоить это значение переменной A;
  70. A = B+10; // Считать значение переменной B, прибавить к считанному значению 10, результат присвоить переменной A (значение переменной B при этом не изменяется).
  71. § > Запись и чтение регистров.
  72. Из рассмотренных примеров видно, что оператор присваивания сам по себе решает две первые задачи — запись и чтение значений регистров. Например для отправки микроконтроллером AVR байта по шине UART достаточно записать его в передающий регистр с именем UDR:
  73. UDR = 8; // Отправить по UART число 8;
  74. Чтобы получить принятый по UART байт достаточно считать его из регистра UDR:
  75. A = UDR; // Считать принятый байт из UART и переписать в переменную A.
  76. § > Установка битов регистров.
  77. Язык Си не имеет в своём составе команд непосредственного сброса или установки разрядов переменной, однако присутствуют побитовые логические операции "И" и "ИЛИ", которые успешно используются для этих целей.
  78. Оператор побитовой логической операции "ИЛИ" записывается в виде вертикальной черты - "|" и может выполнятся между двумя переменными, а так же между переменной и константой. Напомню, что операция "ИЛИ" над двумя битами даёт в результате единичный бит, если хотя бы один из исходных битов находится с состоянии единицы. Таким образом для любого бита логическое "ИЛИ" с "1" даст в результате "1", независимо от состояния этого бита, а "ИЛИ" с логическим "0" оставит в результате состояние исходного бита без изменения. Это свойство позволяет использовать операцию "ИЛИ" для установки N-ого разряда в регистре. Для этого необходимо вычислить константу с единичным N-ным битом по формуле 2^N, которая называется битовой маской и выполнить логическое "ИЛИ" между ней и регистром, например для установки бита №7 в регистре SREG:
  79. (SREG | 128) — это выражение считывает регистр SREG и устанавливает в считанном значении седьмой бит, далее достаточно изменённое значение снова поместить в регистр SREG:
  80. SREG = SREG | 128; // Установить бит №7 регистра SREG.
  81. Такую работу с регистром принято называть "чтение - модификация - запись", в отличие от простого присваивания она сохраняет состояние остальных битов без именения.
  82. Приведённый программный код, устанавливая седьмой бит в регистре SREG, выполняет вполне осмысленную работу - разрешает микроконтроллеру обработку программных прерываний. Единственный недостаток такой записи — в константе 128 не легко угадать установленный седьмой бит, поэтому чаще маску для N-ного бита записывают в следующем виде:
  83. (1<<N) - это выражение на языке Си означает, число один, сдвинутое на N разрядов влево, это и есть маска с установленным N-ным битом. Тогда предыдущий код в более читабельном виде:
  84. REG = SREG | (1<<7);
  85. или ещё проще с использование краткой формы записи языка Си:
  86. REG |= (1<<7);
  87. которая означает - взять содержимое справа от знака равенства, выполнить между ним и регистром слева операцию, стоящую перед знаком равенства и записать результат в регистр или переменную слева.
  88. § > Сброс битов в регистрах.
  89. Ещё одна логическая операция языка Си – побитовое "И", записывается в виде символа "&". Как известно, операция логического "И", применительно к двум битам даёт единицу тогда и только тогда, когда оба исходных бита имеют единичное значение, это позволяет применять её для сброса разрядов в регистрах. При этом используется битовая маска, в которой все разряды единичные, кроме нулевого на позиции сбрасываемого. Её легко получить из маски с установленным N-ным битом, применив к ней операцию побитного инвертирования:
  90. ~(1<<N) в этом выражении символ "~" означает смену логического состояния всех битов маски на противоположные. Так, например, если (1<<3) в двоичном представлении – 00001000b, то ~(1<<3) уже 11110111b. Сброс седьмого бита в регистре SREG будет выглядеть так:
  91. SREG = SREG & (~ (1<<7)); или кратко: SREG &= ~ (1<<7);
  92. В упомянутом ранее заголовочном файле для конкретного микроконтроллера приведены стандартные имена разрядов регистров специального назначения, например:
  93. #define OCIE0 1
  94. здесь #define – указание компилятору заменять в тексте программы сочетание символов "OCIE0" на число 1, то есть стандартное имя бита OCIE0, который входит в состав регистра TIMSK микроконтроллера Atmega64 на его порядковый номер в этом регистре. Благодаря этому установку бита OCIE0 в регистре TIMSK можно нагляднее записывать так:
  95. TIMSK|=(1<<OCIE0);
  96. Устанавливать или сбрасывать несколько разрядов регистра одновременно можно, объединяя битовые маски в выражениях оператором логического "ИЛИ":
  97. PORTA |= (1<<1)|(1<<4); // Установить выводы 1 и 4 порта A в единицу;
  98. PORTA&=~((1<<2)|(1<<3)); // Выводы 2 и 3 порта A сбросить в ноль.
  99. § > Проверка разрядов регистра на ноль и единицу.
  100. Регистры специального назначения микроконтроллеров содержат в своём
  101. составе множество битов-признаков, так называемых "флагов”, уведомляющих
  102. программу о текущем состоянии микроконтроллера и его отдельных модулей.
  103. Проверка логического уровня флага сводится к подбору выражения, которое
  104. становится истинным или ложным в зависимости от того установлен или
  105. сброшен данный разряд в регистре. Таким выражением может служить
  106. логическое "И” между регистром и маской с установленным разрядом N на
  107. позиции проверяемого бита :
  108. (REGISTR & (1<<N)) в этом выражении операция "И” во всех разрядах кроме
  109. N-ного даст нулевые значения, а проверяемый разряд оставит без изменения.
  110. Таким образом возможное значения выражения будут или 0 или 2^N, например
  111. для второго бита регистра SREG:
  112. Приведённое выражение можно использовать в условном операторе if
  113. (выражение) или операторе цикла while (выражение), которые относятся к
  114. группе логических, то есть воспринимают в качестве аргументов значения
  115. типа истина и ложь. Поскольку язык Си, приводя числовые значения к
  116. логическим, любые числа не равные нулю воспринимает как логическую истину,
  117. значение (REGISTR & (1<<N)) равное 2^N в случае установленного бита, будет
  118. воспринято как "истина".
  119. Если появляется необходимость при установленном бите N получить для нашего
  120. выражения логическое значение «ложь», достаточно дополнить его оператором
  121. логической инверсии в виде восклицательного знака - !(REGISTR & (1<<N)).
  122. Не следует путать его с похожим оператором побитовой инверсии (~) меняющим
  123. состояние битов разряда на противоположное. Логическая инверсия работает
  124. не с числовыми значениями, а с логическими, то есть преобразует истинное в
  125. ложное и наоборот. Такая конструкция приводится в DataSheet на Atmega как
  126. пример для ожидания установки бита UDRE в регистре UCSRA, после которого
  127. можно отправлять данные в UART:
  128. while ( !( UCSRA & (1<<UDRE)) ) { } // Ждать установки UDRE.
  129. здесь при сброшенном бите UDRE выражение ( UCSRA & (1<<UDRE)) даст
  130. значение ”ложь”, инвертированное
  131. !( UCSRA & (1<<UDRE)) — ”истину”, и пока это так, программа будет
  132. выполнять действия внутри фигурных скобок, то есть не делать ничего
  133. (стоять на месте). Как только бит UDRE установится в единицу, программа
  134. перейдёт к выполнению действий следующих за конструкцией while(), например
  135. займётся отправкой данных в UART.
  136. § > Изменение состояния бита регистра на противоположное.
  137. Эту, с позволения сказать, проблему с успехом решает логическая операция
  138. побитного "ИСКЛЮЧАЮЩЕГО ИЛИ” и соответствующий ей оператор Си,
  139. записываемый в виде символа " ^ ”. Правило "исключающего или" с двумя
  140. битами даёт "истину” тогда и только тогда, когда один из битов установлен,
  141. а другой сброшен. Не трудно убедиться, что этот оператор, применённый
  142. между битовой маской и регистром, скопирует в результат биты стоящие
  143. напротив нулевых битов маски без изменения и инвертирует расположенные
  144. напротив единичных. Например, если: reg=b0001 0110 и mask=b0000 1111, то
  145. reg^mask=b0001 1001. Таким способом можно менять состояние светодиода,
  146. подключенного к пятому биту порта A:
  147. #define LED 5 // Заменять в программе сочетание символов LED на число 5
  148. (вывод светодиода).
  149. PORTA ^=(1<< LED); // Погасить светодиод, если он светится и наоборот.
  150. § > Арифметика и логика языка Си.
  151. Мы рассмотрели типичный набор операций, используемый при работе с
  152. регистрами микроконтроллера. Помимо них в арсенале языка имеется ряд
  153. простейших арифметических и логических операций, описания которых можно
  154. найти в любом справочнике по Си, например:
  155. Для более подробного знакомства с операциями над переменными и языком Си в
  156. целом, рекомендую книгу "Язык программирования Си" Б. Керниган, Д. Ритчи.