====== Атомарный доступ к структурам ======
===== Проблема атомарного доступа =====
Под атомарным доступом будем понимать такое обращение (операции чтения, модификации, записи, или их последовательность) к переменной или периферийному регистру, которое не приведет к конфликту при возникновении прерывания или приоритетного вытеснения задачи в RTOS системах во время выполнения операции. Конфликт может возникнуть, когда в прерывании, или другой задаче выполняется доступ к этому же ресурсу.
Простой пример: необходимо инвертировать вывод RA0 контроллера:
_LATA0 = ~_LATA0;
С точки зрения синтаксиса и логики все правильно, но давайте посмотрим на код, который получится в результате компиляции
10: _LATA0 = ~_LATA0;
0288 202C41 mov.w #0x2c4,0x0002
028A 784091 mov.b [0x0002],0x0002
028C 60C0E1 and.b 0x0002,#1,0x0002
028E A20401 btg 0x0002,#0
0290 BFC2C4 mov.b 0x02c4,0x0000
0292 A10400 bclr 0x0000,#0
0294 704001 ior.b 0x0000,0x0002,0x0000
0296 B7E2C4 mov.b 0x0000,0x02c4
**Никогда так не делайте!** C30 предоставляет встроенную функцию ''%%__builtin_btg()%%'', которая атомарно инвертирует бит в адресном пространстве. Код выше - пример некорректного обращения к периферии.
В приведенном примере текущее значение регистра ''LATA'' загружается в регистр ядра, модицифируется и затем сохраняется обратно в регистр ''LATA''. Теперь представьте, что после выполнения инструкции
0288 202C41 mov.w #0x2c4,0x0002
возникло прерывание, которое так же обращается к регистру ''LATA'', модифицирует его, например, устанавливает какой-нибудь бит. После выхода из обработчика прерывания начнет выполняться инструкция по адресу ''0x028A''. Но ведь значение значение ''LATA'', которое было до прерывания ##уже сохранено##! И модифицироваться будет именно оно, а не актуальное состояние защелки порта. И после выполнения
0296 B7E2C4 mov.b 0x0000,0x02c4
в порт запишется значение, которое не учитывает установку бита в прерывании. Графитовый стержень не опустился. Бум!
В результате мы получим конфликт доступа к ресурсу и, как следствие, неработоспособную программу. Если вы считаете, что такая ситуация надуманная, то пройдите по [[tnkernel:faq#Разделяемые ресурсы|ссылке]]. Это реальный вопрос от реального человека, который наткнулся на проблему атомарного доступа к периферии сразу же после начала освоения вытесняющей RTOS [[tnkernel:ref:intro|TNKernel]].
Проблема становится еще более актуальной при работе с архитектурами типа Read-Modify-Write (ARM, MIPS) которые не имеют инструкций прямой модификации памяти в адресном пространстве. Для изменения значения периферийного регистра или переменной требуется загрузка данных в регистр АЛУ, модификации и выгрузка обратно. А ведь есть еще многоядерные процессоры...
~~UP~~
===== Методы решения =====
Вариантов решения проблемы существует несколько. Каждый из них имеет свои плюсы и минусы.
==== Запрещение прерываний ====
DI();
_LATA0 = ~_LATA0;
EI();
Самый простой и очевидный способ: чтобы последовательность выполнения инструкций не нарушилась, нужно просто исключить саму возможность запуска конкурирующего кода. В этом случае операция инвертирования бита будет выполняться атомарно.
Однако, этот метод имеет множество недостатков:
* Увеличение объема кода за счет инструкций запрещающих и разрешающих прерывание.
* Уменьшение скорости выполнения кода по тем же причинам.
* При таком способе реализации атомарного доступа невозможно обеспечить детерминированную задержку входа в прерывание - появится джиттер. В некоторых системах это недопустимо.
* Такое решение нельзя использовать в системах с вытесняющей RTOS (там есть свои методы).
Если целевая платформа включает в себя гибкий контроллер прерывания, то побочные эффекты такого метода могут быть довольно сильными, так как необходимо выполнить больше действий для запрещения прерываний. Например, для PIC24/dsPIC нужно сохранить во временной переменной (в стеке) текущий приоритет ядра, установить приоритет ядра на максимум, а после выполнения атомарной операции доступа восстановить приоритет из временной переменной. Это является единственно корректным способом запрещения прерываний при разработке ПО на языке Си (использование инструкции ''disi'' не рекомендуется).
==== Критическая секция ====
tn_sys_enter_critical();
_LATA0 = ~_LATA0;
tn_sys_exit_critical();
Такой метод можно применять при использовании вытесняющей RTOS. [[tnkernel:ref:sys:intro#Запрещение переключения контекста|Критическая секция]] - это часть кода, в которой запрещено переключение контекста. Чаще всего это означает запрещение прерываний и (возможно) выполнение дополнительных действий над внутренними переменными планировщика.
Критическая секция имеет те же недостатки, что и предыдущий способ. Кроме того, использование критических секций не поощряется, так как в этом случае вы вмешиваетесь в работу планировщика и нарушаете принципы функционирования RTOS. Если доступ к ресурсу можно выделить в относительно большой кусок кода, тогда разумнее использовать мютексы.
==== Мютексы ====
tn_mutex_lock(&mutex, TIMEOUT);
_LATA0 = ~_LATA0;
tn_mutex_unlock(&mutex);
[[tnkernel:ref:mutex:intro|Мютекс]] - это объект RTOS, предназначенный для реализации конкурентного доступа к общему для задач ресурсу.
Если в используемой вами RTOS механизм мютексов не реализован, можно использовать двоичные семафоры, но в этом случае может возникнуть [[tnkernel:ref:mutex:intro#Инверсия приоритетов|инверсия приоритетов]] - неприятная ситуация при которой более приоритетная задача не может получить доступ к ресурсу.
Использование мютексов - наиболее правильный метод обеспечения доступа к разделяемым ресурсам. Однако, при обслуживании периферии такой метод может быть очень накладным по объему и, что самое главное, по скорости выполнения кода.
Ограничивать мютексом рекомендуется относительно большую часть кода, но никак не доступ к полю структуры или биту порта.
==== Аппаратные методы ====
Некоторые архитектуры имеют удобные способы обеспечения атомарного доступа. Из известных мне это Cortex-M3 с его [[http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/15921.html|"bit-band"]] областью в адресном пространстве и MIPS32 с инструкциями ''LL'' и ''SC''. Последние, впрочем, предназначены для использования в многоядерных процессорах, однако так же успешно могут применяться и при работе с [[articles:mchp:32_bit_first_step_ppt|PIC32]].
К аппаратным методам обеспечения атомарного доступа можно так же отнести инструкцию ''disi'' 16-битных контроллеров Microchip. Эта инструкция запрещает прерывания на определенное количество командных тактов.
~~UP~~
===== Доступ к битовым полям структуры =====
Мы рассмотрели общую проблему конкурентного доступа к ресурсам программы. Однако, существует и частный случай этой проблемы - доступ к битовым полям структуры.
Как правило, для работы с периферией микроконтроллера используются служебные регистры, находящиеся в адресном пространстве процессора, которые будем называть **периферийными**. Эти регистры чаще всего представляются в виде структур с битовыми полями:
__extension__ typedef struct tagOC4CONBITS {
union {
struct {
unsigned OCM :3;
unsigned OCTSEL :1;
unsigned OCFLT :1;
unsigned :8;
unsigned OCSIDL :1;
};
struct {
unsigned OCM0 :1;
unsigned OCM1 :1;
unsigned OCM2 :1;
};
};
} OC4CONBITS;
extern volatile OC4CONBITS OC4CONbits __attribute__((__sfr__));
При доступе к этим битовым полям компилятор может сгенерировать атомарную инструкцию:
75: AD1CON1bits.ADON = 1;
00298 A8E321 bset.b 0x0321,#7
76: AD1CON1bits.ADON = 0;
0029A A9E321 bclr.b 0x0321,#7
Но все станет гораздо хуже, когда вы попытаетесь записать в поле структуры значение переменной:
74: a = 1;
00298 200010 mov.w #0x1,0x0000
0029A 884010 mov.w 0x0000,0x0802
75: b = 0;
0029C EF2800 clr.w 0x0800
76:
77: AD1CON1bits.ADON = a;
0029E 804011 mov.w 0x0802,0x0002
002A0 DD08C7 sl 0x0002,#7,0x0002
002A2 BFC321 mov.b 0x0321,0x0000
002A4 A17400 bclr 0x0000,#7
002A6 704001 ior.b 0x0000,0x0002,0x0000
002A8 B7E321 mov.b 0x0000,0x0321
78: AD1CON1bits.ADON = b;
002AA 804001 mov.w 0x0800,0x0002
002AC DD08C7 sl 0x0002,#7,0x0002
002AE BFC321 mov.b 0x0321,0x0000
002B0 A17400 bclr 0x0000,#7
002B2 704001 ior.b 0x0000,0x0002,0x0000
002B4 B7E321 mov.b 0x0000,0x0321
Проблемы так же могут появиться при доступе к битовому полю, размер которого больше 1 бита:
82: IPC0bits.INT0IP = 2;
002BE BFC0A4 mov.b 0x00a4,0x0000
002C0 B3CF81 mov.b #0xf8,0x0002
002C2 604001 and.b 0x0000,0x0002,0x0000
002C4 A01400 bset 0x0000,#1
002C6 B7E0A4 mov.b 0x0000,0x00a4
Способ решения проблемы тут один - использовать инструкцию ''xor'', которая обеспечивает атомарный доступ **в любом случае**, даже если требуется изменить большое битовое поле.
Рассмотрим пример: запись в младшие четыре бита регистра TRISB значения 0x000A.
\\
\\
002FA 801630 mov.w TRISB, W0
002FC 68006A xor.w W0, #0x000A, W0
002FE 60006F and.w W0, #0x000F, W0
00300 B6A2C6 xor.w TRISB
* ''TRISB = 0xFFFF''
* первая инструкция загружает значение ''TRISB'' в регистр ''W0'';
* вторая инструкция атомарно и безопасно модифицирует четыре младших бита регистра ''W0'';
* третья инструкция накладывает на регистр ''W0'' маску, выделяя 4 младших бита
* последняя инструкция атомарно и безопасно (**не трогая остальных битов!**) модифицирует ''TRISB''; **TRISB = 0xFFFA**;
Даже если после выполнения первой инструкции возникнет прерывание, которое изменит значение TRISB (естественно, не младших 4-х битов), операция выполниться корректно. Приведенный выше код является **атомарным**.
О "волшебном" свойстве XOR я знал давно, но формализованный подход увидел первый раз на [[http://www.microchip.com/forums/tm.aspx?m=375664|форуме microchip.com]]. Автор замечательной коммерческой [[http://www.avix-rt.com/|RTOS AVIX-RT]] выложил макрос, который использует inline ассемблер для реализации xor доступа. Затем в состав AVIX вошел заголовочный файл, в котором реализованы расширенные варианты подобного макроса для архитектур PIC24/dsPIC (компилятор C30) и PIC32 (MIPS32 M4K, компилятор C32).
По моему скромному мнению эти макросы являются высшим пилотажем. В них используется много интересных решений которые я рассмотрю ниже.
~~UP~~
===== Макросы атомарного доступа к полям структуры =====
Согласно лицензионному соглашению, код, который содержится в файле ''AVIXAtomicSFR.h'' является неотъемлемой частью **RTOS AVIX-RT** и не может быть приведен полностью или частично без согласования с держателем права. Поэтому пришлось сделать небольшой рефакторинг. Оригинальный код входит в состав AVIX-RT, демонстрационная версия которой может быть скачана с сайта [[http://www.avix-rt.com/]].
\\
\\
**Я не являюсь автором данных макросов.**
==== Скачать ====
* **1.0.70** (14 сентября 2009) {{:articles:mchp:bfa_rev_1_0_70.rar|скачать}} @ 6 кБ
* Исправлен макрос ''BFAR()''
* Инструкции обслуживания единичных битов заменены на ассемблерные, так при некоторых уровня оптимизации получался совсем уж неоптимальный код
* Добавлены макросы ''BFAM()'', ''BFAMI()'', ''BFAMD()''
* Предназначены для того же, для чего и ''BFAR()'', но биты, на которые влияет операция увазываются не диапазоном, а маской. Полное описание будет чуть позже
\\
----
\\
* **1.0.31** (8 мая 2009) {{articles:mchp:bfa_rev_1_0_31.rar|скачать}} @ 6 кБ
* Исправлена ошибка в макросе ''BFAR()''
* При установке опции компиляции ''-mlarge-scalar'' (разрешение располагать скалярные переменные в far области ОЗУ) выдавалась ошибка при использовании макроса ''BFAR()'' с произвольной переменной (без атрибута SFR).
* Исправлен макрос ''BFA()''
* При использовании макроса ''BFA()'' выдавалось предупреждение о безусловном преобразовании большой константы в ''unsigned int''
* **Добавлен макрос BFARD()**, предназначенный для прямого доступа к любой скалярной переменной в ОЗУ.
\\
----
\\
* **1.0.24** (4 марта 2009) {{articles:mchp:bfa_rev_1_0_24.rar|скачать}} @ 6 кБ
* Изменен порядок передачи параметров
* Параметр, указывающий тип операции в макрос передается первым
* Добавлен макрос ''BFARI()''
* Макрос ''BFARI()'' обеспечивает доступ к структуре по указателю
* Для всех макросов добавлены новые операции: ''BFA_SET'' и ''BFA_CLR''
* Эти операции могут использоваться для установки или сброса битов по маске в битовом поле. Маска может передаваться как в виде константы, так и в виде переменной.
* ''BFA_IV'' заменено на ''BFA_INV''. Операция инвертирования теперь инвертирует биты по передаваемой маске.
\\
----
\\
* **1.0.21** (3 марта 2009) {{articles:mchp:bfa_rev_1_0_21.rar|скачать}} @ 4 кБ
* Первая версия. Только для компилятора Microchip C30
~~UP~~
==== Описание ====
Архив содержит заголовочный файл ''bfa.h'', который включает в себя четыре макроса, реализующих атомарный доступ к полю структуры или к любой скалярной переменной. Один из макросов предназначен для работы со структурами, именование которых соответствует правилам Microchip C30 для периферийных регистров. Остальный макросы могут использоваться с любой структурой или переменной размером не больше машинного слова (''int'').
Для использования макросов необходимо и достаточно подключить к модулю файл ''bfa.h'' и ##включить оптимизацию не ниже ''-01''##.
\\
\\
**Список макросов**
* ''**BFA**(comm, reg_name, field_name, ...)'' - **B**it **F**ield **A**ccess
* Атомарный доступ к именованым полям периферийных регистров PIC24/dsPIC
* ''**BFAR**(comm, reg_name, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange
* Атомарный доступ к битовым полям периферийных регистров и переменных. Битовое поле указывается как числовой диапазон. Переменная должна быть расположена в NEAR DATA SPACE (первые 8 r< ОЗУ)
* ''**BFARI**(comm, pt, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange **I**ndirect
* Атомарный доступ к битовым полям периферийных регистров и переменных по указателю. Битовое поле указывается как числовой диапазон
* ''**BFARD**(comm, val, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange **D**irect
* Атомарный доступ к битовым полям периферийных регистров и переменных. Битовое поле указывается как числовой диапазон. Переменная может быть расположена в любом месте ОЗУ (NEAR или FAR DATA SPACE)
\\
=== BFA() ===
----
\\
Макрос обеспечивает атомарный доступ к именованым полям структур периферийных регистров микроконтроллеров PIC24/dsPIC.
**Вызов:**
BFA(comm, reg_name, field_name, ...)
**Параметры:**
; ''comm'' : тип доступа:
{| class = "fpl"
|-
| ''BFA_WR''
| запись в битовое поле
|-
| ''BFA_RD''
| чтение битового поля
|-
| ''BFA_SET''
| установка битов в битовом поле по маске
|-
| ''BFA_CLR''
| сброс битов в битовом поле по маске
|-
| ''BFA_INV''
| инвертирование битов в битовом поле по маске
|}
; ''reg_name'' : имя периферийного регистра, к которому осуществляется доступ, например, ''PORTA'', ''TRISB'', ''CMCON'' и т.п.
; ''field_name'' : имя битового поля в структуре регистра (см. документацию на микроконтроллер и заголовочный файл C30 для этого микроконтроллера). Например, для регистра ''IPC0'' это может быть ''T1IP''.
; ''. . .'' : необязательный параметр, используется только если ''comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]''. Если ''comm = BFA_RD'' - параметр не указывается. Если ''comm = BFA_WR'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\
**Пример вызова:**
BFA(BFA_WR, IPC0, INT0IP, 0); /* Запись в поле INT0IP регистра IPC0
константы 0 */
BFA(BFA_INV, IPC0, INT0IP, (1 << 0)); /* Инвертирование младшего бита поля
INT0IP регистра IPC0 */
BFA(BFA_WR, IPC0, INT0IP, 4); /* Запись в поле INT0IP регистра IPC0
константы 4 */
BFA(BFA_CLR, IPC0, INT0IP, (1 << 0)); /* Сброс младшего бита поля INT0IP
регистра IPC0 */
a = 2;
BFA(BFA_WR, IPC0, INT0IP, a); /* Запись в поле INT0IP регистра IPC0 значения
переменной a */
b = BFA(BFA_RD, IPC0, INT0IP); /* Чтение значения поля INT0IP регистра IPC0
в переменную b */
~~UP~~
\\
=== BFAR() ===
----
\\
Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной, которая находится в области **NEAR DATA SPACE** (первые 8 кБ ОЗУ). Битовое поле указывается в виде диапазона (младщий бит / старший бит).
**Вызов:**
BFAR(comm, reg_name, lower, upper, ...)
**Параметры:**
; ''comm'' : тип доступа:
{| class = "fpl"
|-
| ''BFA_WR''
| запись в битовое поле
|-
| ''BFA_RD''
| чтение битового поля
|-
| ''BFA_SET''
| установка битов в битовом поле по маске
|-
| ''BFA_CLR''
| сброс битов в битовом поле по маске
|-
| ''BFA_INV''
| инвертирование битов в битовом поле по маске
|}
; ''reg_name'' : имя регистра, к которому осуществляется доступ, например, ''PORTA'', ''TRISB'', ''CMCON'' и т.п. Может быть любой переменной программы, которая находится в области ''near'' памяти данных (первые 8 кБ)
; ''lower'' : младший бит поля структуры, от 0 до 15
; ''upper'' : старший бит поля структуры, от 0 до 15, ''upper >= lower''
; ''. . .'' : необязательный параметр, используется только если ''comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]''. Если ''comm = BFA_RD'' - параметр не указывается. Если ''comm = BFA_WR'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\
**Пример вызова:**
BFAR(BFA_WR, TRISB, 0, 7, 0xAA); /* Запись 0xAA в младший байт регистра
TRISB */
BFAR(BFA_INV, TRISB, 8, 15, 0xFFFF); /* Инвертирование старшего байта
регистра TRISB */
a = 2;
BFAR(BFA_SET, TRISB, 0, 7, a); /* Установка битов в младшем байте
TRISB по маске в переменной a */
b = BFAR(BFA_RD, TRISB, 0, 7); /* Чтение значения младшего байта
регистра TRISB в переменную b */
~~UP~~
\\
=== BFARI() ===
----
\\
Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной по указателю. Битовое поле указывается в виде диапазона (младщий бит / старший бит). Переменная может находиться как в NEAR, так и в FAR DATA SPACE.
**Вызов:**
BFARI(comm, pt, lower, upper, ...)
**Параметры:**
; ''comm'' : тип доступа:
{| class = "fpl"
|-
| ''BFA_WR''
| запись в битовое поле
|-
| ''BFA_RD''
| чтение битового поля
|-
| ''BFA_SET''
| установка битов в битовом поле по маске
|-
| ''BFA_CLR''
| сброс битов в битовом поле по маске
|-
| ''BFA_INV''
| инвертирование битов в битовом поле по маске
|}
; ''pt'' : указатель на периферийный регистр или любую переменную. Макрос автоматически приводит указатель к типу ''int*''.
; ''lower'' : младший бит поля структуры, от 0 до 15
; ''upper'' : старший бит поля структуры, от 0 до 15, ''upper >= lower''
; ''. . .'' : необязательный параметр, используется только если ''comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]''. Если ''comm = BFA_RD'' - параметр не указывается. Если ''comm = BFA_WR'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\
**Пример вызова:**
BFAR(BFA_WR, &TRISB, 0, 7, 0xAA); /* Запись 0xAA в младший байт регистра
TRISB */
BFAR(BFA_INV, &TRISB, 8, 15, 0xFFFF); /* Инвертирование старшего байта
регистра TRISB */
a = 2;
BFAR(BFA_SET, &TRISB, 0, 7, a); /* Установка битов в младшем байте
TRISB по маске в переменной a */
c = &TRISD;
b = BFAR(BFA_RD, c, 0, 7); /* Чтение значения младшего байта
регистра TRISD в переменную b */
~~UP~~
\\
=== BFARD() ===
----
\\
Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной, которая находится в любой области ОЗУ (NEAR или FAR). Битовое поле указывается в виде диапазона (младщий бит / старший бит).
**Вызов:**
BFARD(comm, val, lower, upper, ...)
**Параметры:**
; ''comm'' : тип доступа:
{| class = "fpl"
|-
| ''BFA_WR''
| запись в битовое поле
|-
| ''BFA_RD''
| чтение битового поля
|-
| ''BFA_SET''
| установка битов в битовом поле по маске
|-
| ''BFA_CLR''
| сброс битов в битовом поле по маске
|-
| ''BFA_INV''
| инвертирование битов в битовом поле по маске
|}
; ''val'' : имя регистра, к которому осуществляется доступ, например, ''PORTA'', ''TRISB'', ''CMCON'' и т.п. Может быть любой скалярной переменной программы.
; ''lower'' : младший бит поля структуры, от 0 до 15
; ''upper'' : старший бит поля структуры, от 0 до 15, ''upper >= lower''
; ''. . .'' : необязательный параметр, используется только если ''comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]''. Если ''comm = BFA_RD'' - параметр не указывается. Если ''comm = BFA_WR'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\
**Пример вызова:**
BFARD(BFA_WR, qwer, 0, 7, 0xAA); /* Запись 0xAA в младший байт
переменной qwer */
BFARD(BFA_INV, TRISB, 8, 15, 0xFFFF); /* Инвертирование старшего байта
регистра TRISB */
a = 2;
BFARD(BFA_SET, TRISB, 0, 7, a); /* Установка битов в младшем байте
TRISB по маске в переменной a */
b = BFARD(BFA_RD, TRISB, 0, 7); /* Чтение значения младшего байта
регистра TRISB в переменную b */
~~UP~~
\\
==== Пояснения ====
=== Проверка параметров ===
Приведенные выше макросы проверяют передаваемый параметр ''comm'', указывающий на метод доступа к полю структуры. Остальные параметры не проверяются. Для ''comm'' определены следующие разрешенные значения:
#define BFA_WR 0xAAAA /* Запись в битовое поле */
#define BFA_RD 0x5555 /* Чтение битового поля */
#define BFA_IV 0x9999 /* Побитовое инвертирование битового поля */
Контроль передачи параметров в макрос сделан интересным способом: в заголовочном файле объявлены уникальные для каждого параметра типы:
#define __BFA_COMM_ERR(a) __BFA_COMMAND_ERROR_##a
#define __BFA_COMM_GET(a) __BFA_COMM_ERR(a)
typedef int __BFA_COMM_GET(BFA_WR);
typedef int __BFA_COMM_GET(BFA_RD);
typedef int __BFA_COMM_GET(BFA_IV);
которые используются в макросах для объявления локальной переменной:
__BFA_COMM_GET(comm) v = __VA_ARGS__+0;
Если передаваемый в макрос параметр отличается от определенных ''BFA_WR'', ''BFA_RD'' и ''BFA_IV'', то компилятор выдаст ошибку:
source\appl\appl.c: In function 'main':
source\appl\appl.c:73: error: '__BFA_COMMAND_ERROR_0' undeclared (first use in this function)
~~UP~~
=== Условный оператор ? : ===
Использование условного оператора ''?:'' позволило реализовать с помощью одного макроса как выполнение операции, так и возврат значения. Если не вдаваться в детали реализации, все макросы выглядят следующим образом:
#define BFAXX(comm, ...)
({
((comm) == BFA_WR)
? ({
})
/* ........ */
: ({ /* BFA_RD */
__BFA_STRUCT_VAL(reg_name).field_name;
})
})
Если параметр ''comm'' (тип доступа к структуре) равен ''BFA_RD'', то макрос генерирует следующее выражение:
__BFA_STRUCT_VAL(reg_name).field_name;
где
#define __BFA_STRUCT_VAL(a) a##bits
Таким образом, написав
b = BFA(BFA_RD, IPC0, INT0IP);
после работы препроцессора получим простое присваивание:
b = IPC0bits.INT0IP;
~~UP~~
==== Примеры использования ====
В оригинальной документации, которая поставляется с демонстрационной версией AVIX-RT приведено много примеров использования этих макросов. К сожалению, лицензионное соглашение запрещает публикацию документа третьим лицам. Однако никто не запрещает зарегистрироваться на сайте [[http://www.avix-rt.com/]] и скачать AVIX-RT.
Конечно, прямое использование макросов ''BFA()'' и ''BFAR()'' не слишком наглядно. Тем не менее это один из методов:
BFA(BFA_WR, IPC0, INT0IP, 0); /* Запись в поле INT0IP регистра IPC0 константы 0 */
BFA(BFA_WR, IPC0, INT0IP, 4); /* Запись в поле INT0IP регистра IPC0 константы 4 */
Если регистр используется в вашем приложении только на запись, можно определить следующий макрос:
#define LED(v) BFA(BFA_WR, LATB, LATB0, (v))
и затем использовать уже его:
LED(1);
LED(0);
Если поле структуры используется как на запись, так и на чтение, можно определить следующий макрос с переменным количеством параметров:
#define TMR1_PS(a, ...) BFA((a), T1CON, TCKPS, __VA_ARGS__)
Использовать это макрос нужно так:
TMR1_PS(BFA_WR, 0);
a = TMR1_PS(BFA_RD);
Очень часто необходимо получить атомарный доступ к необъявленному полю структуры - например, установить определенное значение на нескольких выводах контроллера. Для этого можно использовать макрос ''BFAR()'':
#define LCD_PORT(v) BFAR(BFA_WR, LATE, 0, 3, (v))
А дальше очевидно:
LCD_PORT(0x02);
LCD_PORT(0x00);
~~UP~~
==== Выводы ====
Использование подобных макросов ##обязательно## для всех, кто хочет писать быстрый и безопасный код для микроконтроллеров PIC24/dsPIC с использованием компилятора C30.
Рассмотренный подход может быть реализован и для любой другой архитектуры и другого компилятора. Для этого необходимо чтобы:
* компилятор имел гибкий inline-ассемблера (например, как у [[http://gcc.gnu.org/|gcc]])
* архитектура имела механизмы прямого доступа к памяти
Последнее означает, что должно выполняться одно из двух условий:
* Набор инструкций должен включать в себя инструкцию ''xor'' с прямым доступом к интересующей области памяти
* Архитектура должна иметь специальные механизмы прямого доступа. Примером может служить [[http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/Behcjiic.html|bit-band область]] у Cortex-M3 или регистры SET/CLR/INV для всей периферии у [[http://www.microchip.com/pic32/|PIC32]].
К основным достоинствам макросов ''BFA()'' и ''BFAR()'' относится реализация ##атомарного доступа## к разделяемым ресурсам. Конечно, это не означает, что в прерывании и в основном коде можно безболезненно шевелить одной и той же ногой контроллера. Но доступ к разным битам порта осуществляется безопасно.
При использовании макросов ''BFA()'' и ''BFAR()'' нет необходимости запрещать прерывания или реализовывать критическую секцию. Это позволяет детерминировать время входа в прерывание, уменьшить объем занимаемого кода и скорость выполнения операций.
Более того при использовании макросов доступа к полям структуры уменьшается объем кода и время выполнение по сравнению с непосредственным доступом:
75: IPC0bits.INT0IP = 4;
00294 BFC0A4 mov.b 0x00a4,0x0000
00296 B3CF81 mov.b #0xf8,0x0002
00298 604001 and.b 0x0000,0x0002,0x0000
0029A A02400 bset 0x0000,#2
0029C B7E0A4 mov.b 0x0000,0x00a4
76: BFA(BFA_WR, IPC0, INT0IP, 4);
0029E 800520 mov.w 0x00a4,0x0000
002A0 A22000 btg 0x0000,#2
002A2 600002 and.w 0x0000,0x0004,0x0000
002A4 B6A0A4 xor.w 0x00a4
~~UP~~