123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- Порты у ARM Cortex-M шестнадцатибитные.
- И что??? Это не повод для извращений...
- Код:
- #define LCD_DATA (*(__IO uint8_t *)((uint32_t)&(GPIOС->ODR)))
- И юзаем как обычно...
- Можно и обе половинки юзать... как восьмибитные...
- Код:
- #define LCD_DATA_L (*(__IO uint8_t *)((uint32_t)&(GPIOС->ODR)))
- #define LCD_DATA_H (*(__IO uint8_t *)((uint32_t)&(GPIOС->ODR) + 1))
- -------------------
- char Version_str[] = "void118's superproject " __DATE__ ", " __TIME__;
- -------------------
- [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;
- ===============================================================================
- int a; // Целое
- int *a; // Указатель на целое
- int **a; // Указатель на указатель на целое
- int a[10]; // Массив из десяти целых
- int *a[10]; // Массив из десяти указателей на целые
- int (*a)[10]; // Указатель на массив из десяти целых
- int (*a)(int); // Указатель на функцию, которая берет целый аргумент и возвращает целое
- int (*a[10])(int); // Массив из десяти указателей на функции, которые берут целый аргумент и возвращают целое
- ===============================================================================
- В каких случаях используется ключевое слово static?
- Полностью отвечают на этот вопрос довольно редко. Спецификатор static в
- языке Си используется в трёх случаях:
- (а) Переменная, описанная внутри тела функции как статическая, сохраняет
- свое значение между вызовами функции.
- (b) Переменная, описанная как статическая внутри модуля, но снаружи тела
- функции, доступна для всех функций в пределах этого модуля и не доступна
- функциям любых других модулей. То есть, это локализованная глобальная
- переменная.
- (с) Функции, описанные внутри модуля как статические, могут быть вызваны
- только другими функциями из этого модуля. То есть, область видимости
- функции локализована модулем, внутри которого она описана.
- Большинство кандидатов отвечают правильно на первую часть. Умеренное
- число кандидатов справляется со второй частью, ну и небольшое количество
- понимают ответ (с). Это серьёзный недостаток кандидата, если он не
- понимает важность и преимущества ограничения области видимости данных и
- кода.
- ===============================================================================
- Что означает ключевое слово const?
- Как только интервьюируемый говорит: «Const - значит константа», я понимаю, что имею дело с непрофессионалом. Дэн Сакс в прошлом году дал исчерпывающее объяснение спецификатору const, так что каждый читатель ESP должен быть досконально ознакомлен с тем, что const может сделать для вас и чего он не может. Если вы не читали эту рубрику, достаточно будет сказать, что const означает «только для чтения». Хотя этот ответ не совсем справедливо отражает предмет разговора, я бы принял его в качестве правильного.
- Если кандидат даст правильный ответ, то я задам ему следующие дополнительные вопросы:
- Что означают следующие объявления?
- const int a; // целочисленная константа (только для чтения)
- int const a; // целочисленная константа (только для чтения)
- const int *a; // указатель на целочисленную константу
- int * const a; // константный указатель на целое
- const int * const a; // константный указатель на целочисленную константу
- ===============================================================================
- Используя директиву #define, как бы вы описали именованную константу, которая возвращает число секунд в году? Високосными годами следует пренебречь.
- #define SECONDS_PER_YEAR (60UL * 60UL * 24UL * 365UL)
- ===============================================================================
- Напишите «стандартный» макрос MIN. То есть, макрос, который берет два аргумента и возвращает меньший из них.
- #define MIN(A,B) ((A) <= (B) ? (A) : (B))
- ===============================================================================
- Что означает ключевое слово volatile? Приведите три различных примера его использования.
- Ключевое слово volatile информирует компилятор о том, что переменная может быть изменена не только из текущего выполняемого кода, но и из других мест. Тогда компилятор будет избегать определенных оптимизаций этой переменной.
- Примеры volatile переменных:
- (а) Регистры в периферийных устройствах (например, регистры состояния)
- (b) Глобальные переменные, используемые в обработчиках прерываний.
- (с) Глобальные переменные, используемые совместно несколькими задачами в многопотоковом приложении.
- ===============================================================================
- Дана целая переменная «а», напишите два фрагмента кода. Первый должен установить 3-ий бит этой переменной. Второй должен очищать его. В обоих случаях, другие биты должны остаться без изменений.
- (с) Используйте #define и битовые маски. Это хорошо переносимый метод и его стоит использовать. Оптимальное решение этой проблемы, на мой взгляд, было бы таким:
- #define BIT3 (0?1 << 3)
- static int a;
- void set_bit3(void)
- {
- a |= BIT3;
- }
- void clear_bit3(void)
- {
- a &= ~BIT3;
- }
- ===============================================================================
- В некотором проекте требуется установить целую переменную по абсолютному адресу 0?67a9 к значению 0xaa55.
- ptr = (int *)0?67a9;
- *ptr = 0xaa55;
- Более запутывающий вариант выглядит так:
- *(int * const)(0?67a9) = 0xaa55;
- ===============================================================================
- использование указателя на массив функций
- typedef enum {GEAR_DOWN = 0, WTG_FOR_TKOFF, RAISING_GEAR, GEAR_UP, LOWERING_GEAR} State_Type;
-
- /*Этот массив содержит указатели на функции, вызываемые в определенных состояниях*/
- void (*state_table[])() = {GearDown, WtgForTakeoff, RaisingGear, GearUp, LoweringGear};
-
- State_Type curr_state;
- mainn () {
- ...
- state_table[curr_state]();
- ...
- }
- void InitializeLdgGearSM(void)
- {
- curr_state = GEAR_DOWN;
- timer = 0.0;
- /*Остановка аппаратуры, выключение лампочек и т.д.*/
- }
-
- void GearDown(void)
- {
- /* Переходим в состояние ожидания, если самолет
- не на земле и поступила команда поднять шасси*/
- if ((gear_lever == UP) && (prev_gear_lever == DOWN) && (squat_switch == UP)) {
- timer = 2.0;
- curr_state = WTG_FOR_TKOFF;
- };
- prev_gear_lever = gear_lever;
- }
- ...
- ===============================================================================
- Си для микроконтроллеров и чайников.
- Часть 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); // Погасить светодиод, если он светится и наоборот.
- § > Арифметика и логика языка Си.
- Мы рассмотрели типичный набор операций, используемый при работе с
- регистрами микроконтроллера. Помимо них в арсенале языка имеется ряд
- простейших арифметических и логических операций, описания которых можно
- найти в любом справочнике по Си, например:
- Для более подробного знакомства с операциями над переменными и языком Си в
- целом, рекомендую книгу "Язык программирования Си" Б. Керниган, Д. Ритчи.
|