Порты у 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); // Погасить светодиод, если он светится и наоборот.


§ 	> Арифметика и логика языка Си.  

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

Для более подробного знакомства с операциями над переменными и языком Си в 
целом, рекомендую книгу "Язык программирования Си" Б. Керниган, Д. Ритчи.
