====== Атомарный доступ к структурам ======


===== Проблема атомарного доступа =====

Под атомарным доступом будем понимать такое обращение (операции чтения, модификации, записи, или их последовательность) к переменной или периферийному регистру, которое не приведет к конфликту при возникновении прерывания или приоритетного вытеснения задачи в RTOS системах во время выполнения операции. Конфликт может возникнуть, когда в прерывании, или другой задаче выполняется доступ к этому же ресурсу.

Простой пример: необходимо инвертировать вывод RA0 контроллера: 
<code cpp>
_LATA0 = ~_LATA0;
</code>
С точки зрения синтаксиса и логики все правильно, но давайте посмотрим на код, который получится в результате компиляции
<code>
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
</code>

<note warning>**Никогда так не делайте!** C30 предоставляет встроенную функцию ''%%__builtin_btg()%%'', которая атомарно инвертирует бит в адресном пространстве. Код выше - пример некорректного обращения к периферии. </note>

В приведенном примере текущее значение регистра ''LATA'' загружается в регистр ядра, модицифируется и затем сохраняется обратно в регистр ''LATA''. Теперь представьте, что после выполнения инструкции
<code>
  0288  202C41     mov.w #0x2c4,0x0002
</code>
возникло прерывание, которое так же обращается к регистру ''LATA'', модифицирует его, например, устанавливает какой-нибудь бит. После выхода из обработчика прерывания начнет выполняться инструкция по адресу ''0x028A''. Но ведь значение значение ''LATA'', которое было до прерывания ##уже сохранено##! И модифицироваться будет именно оно, а не актуальное состояние защелки порта. И после выполнения 
<code>
  0296  B7E2C4     mov.b 0x0000,0x02c4
</code>
в порт запишется значение, которое не учитывает установку бита в прерывании. Графитовый стержень не опустился. Бум!

В результате мы получим конфликт доступа к ресурсу и, как следствие, неработоспособную программу. Если вы считаете, что такая ситуация надуманная, то пройдите по [[tnkernel:faq#Разделяемые ресурсы|ссылке]]. Это реальный вопрос от реального человека, который наткнулся на проблему атомарного доступа к периферии сразу же после начала освоения вытесняющей RTOS [[tnkernel:ref:intro|TNKernel]].

Проблема становится еще более актуальной при работе с архитектурами типа Read-Modify-Write (ARM, MIPS) которые не имеют инструкций прямой модификации памяти в адресном пространстве. Для изменения значения периферийного регистра или переменной требуется загрузка данных в регистр АЛУ, модификации и выгрузка обратно. А ведь есть еще многоядерные процессоры...

~~UP~~

===== Методы решения =====

Вариантов решения проблемы существует несколько. Каждый из них имеет свои плюсы и минусы.

==== Запрещение прерываний ====

<code cpp>
DI();
_LATA0 = ~_LATA0;
EI();
</code>

Самый простой и очевидный способ: чтобы последовательность выполнения инструкций не нарушилась, нужно просто исключить саму возможность запуска конкурирующего кода. В этом случае операция инвертирования бита будет выполняться атомарно.
Однако, этот метод имеет множество недостатков:
  * Увеличение объема кода за счет инструкций запрещающих и разрешающих прерывание.
  * Уменьшение скорости выполнения кода по тем же причинам.
  * При таком способе реализации атомарного доступа невозможно обеспечить детерминированную задержку входа в прерывание - появится джиттер. В некоторых системах это недопустимо.
  * Такое решение нельзя использовать в системах с вытесняющей RTOS (там есть свои методы).

Если целевая платформа включает в себя гибкий контроллер прерывания, то побочные эффекты такого метода могут быть довольно сильными, так как необходимо выполнить больше действий для запрещения прерываний. Например, для PIC24/dsPIC нужно сохранить во временной переменной (в стеке) текущий приоритет ядра, установить приоритет ядра на максимум, а после выполнения атомарной операции доступа восстановить приоритет из временной переменной. Это является единственно корректным способом запрещения прерываний при разработке ПО на языке Си (использование инструкции ''disi'' не рекомендуется).

==== Критическая секция ====

<code cpp>
tn_sys_enter_critical();
_LATA0 = ~_LATA0;
tn_sys_exit_critical();
</code>

Такой метод можно применять при использовании вытесняющей RTOS. [[tnkernel:ref:sys:intro#Запрещение переключения контекста|Критическая секция]] - это часть кода, в которой запрещено переключение контекста. Чаще всего это означает запрещение прерываний и (возможно) выполнение дополнительных действий над внутренними переменными планировщика.

Критическая секция имеет те же недостатки, что и предыдущий способ. Кроме того, использование критических секций не поощряется, так как в этом случае вы вмешиваетесь в работу планировщика и нарушаете принципы функционирования RTOS. Если доступ к ресурсу можно выделить в относительно большой кусок кода, тогда разумнее использовать мютексы.

==== Мютексы ====

<code cpp>
tn_mutex_lock(&mutex, TIMEOUT);
_LATA0 = ~_LATA0;
tn_mutex_unlock(&mutex);
</code>

[[tnkernel:ref:mutex:intro|Мютекс]] - это объект RTOS, предназначенный для реализации конкурентного доступа к общему для задач ресурсу.

Если в используемой вами RTOS механизм мютексов не реализован, можно использовать двоичные семафоры, но в этом случае может возникнуть [[tnkernel:ref:mutex:intro#Инверсия приоритетов|инверсия приоритетов]] - неприятная ситуация при которой более приоритетная задача не может получить доступ к ресурсу.

Использование мютексов - наиболее правильный метод обеспечения доступа к разделяемым ресурсам. Однако, при обслуживании периферии такой метод может быть очень накладным по объему и, что самое главное, по скорости выполнения кода.

<note important>Ограничивать мютексом рекомендуется относительно большую часть кода, но никак не доступ к полю структуры или биту порта.</note>

==== Аппаратные методы ====

Некоторые архитектуры имеют удобные способы обеспечения атомарного доступа. Из известных мне это 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~~



===== Доступ к битовым полям структуры =====

Мы рассмотрели общую проблему конкурентного доступа к ресурсам программы. Однако, существует и частный случай этой проблемы - доступ к битовым полям структуры.

Как правило, для работы с периферией микроконтроллера используются служебные регистры, находящиеся в адресном пространстве процессора, которые будем называть **периферийными**. Эти регистры чаще всего представляются в виде структур с битовыми полями:

<code cpp>
__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__));
</code>

При доступе к этим битовым полям компилятор может сгенерировать атомарную инструкцию:
<code>
75:                        AD1CON1bits.ADON = 1;
 00298  A8E321     bset.b 0x0321,#7
76:                        AD1CON1bits.ADON = 0;
 0029A  A9E321     bclr.b 0x0321,#7
</code>

Но все станет гораздо хуже, когда вы попытаетесь записать в поле структуры значение переменной:
<code>
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
</code>

Проблемы так же могут появиться при доступе к битовому полю, размер которого больше 1 бита:
<code>
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
</code>

Способ решения проблемы тут один - использовать инструкцию ''xor'', которая обеспечивает атомарный доступ **в любом случае**, даже если требуется изменить большое битовое поле.

<note>
Рассмотрим пример: запись в младшие четыре бита регистра TRISB значения 0x000A.
\\ 
\\ 


<code>
 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
</code>

  * ''TRISB = 0xFFFF''
  * первая инструкция загружает значение ''TRISB'' в регистр ''W0'';
  * вторая инструкция атомарно и безопасно модифицирует четыре младших бита регистра ''W0'';
  * третья инструкция накладывает на регистр ''W0'' маску, выделяя 4 младших бита
  * последняя инструкция атомарно и безопасно (**не трогая остальных битов!**) модифицирует ''TRISB''; **TRISB = 0xFFFA**;

Даже если после выполнения первой инструкции возникнет прерывание, которое изменит значение TRISB (естественно, не младших 4-х битов), операция выполниться корректно. Приведенный выше код является **атомарным**.
</note>

О "волшебном" свойстве 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~~

===== Макросы атомарного доступа к полям структуры =====

<note warning>Согласно лицензионному соглашению, код, который содержится в файле ''AVIXAtomicSFR.h'' является неотъемлемой частью **RTOS AVIX-RT** и не может быть приведен полностью или частично без согласования с держателем права. Поэтому пришлось сделать небольшой рефакторинг. Оригинальный код входит в состав AVIX-RT, демонстрационная версия которой может быть скачана с сайта [[http://www.avix-rt.com/]].
\\ 
\\ 

**Я не являюсь автором данных макросов.**
</note>






==== Скачать ====

  * **1.0.70** (14 сентября 2009) {{:articles:mchp:bfa_rev_1_0_70.rar|скачать}} @ 6 кБ
    * Исправлен макрос ''BFAR()''
      * <color grey>Инструкции обслуживания единичных битов заменены на ассемблерные, так при некоторых уровня оптимизации получался совсем уж неоптимальный код</color>
    * Добавлены макросы ''BFAM()'', ''BFAMI()'', ''BFAMD()''
      * <color grey>Предназначены для того же, для чего и ''BFAR()'', но биты, на которые влияет операция увазываются не диапазоном, а маской. Полное описание будет чуть позже</color>
\\ 


----
\\ 


  * **1.0.31** (8 мая 2009) {{articles:mchp:bfa_rev_1_0_31.rar|скачать}} @ 6 кБ
    * Исправлена ошибка в макросе ''BFAR()''
      * <color grey>При установке опции компиляции ''-mlarge-scalar'' (разрешение располагать скалярные переменные в far области ОЗУ) выдавалась ошибка при использовании макроса ''BFAR()'' с произвольной переменной (без атрибута SFR).</color>
    * Исправлен макрос ''BFA()''
      * <color grey>При использовании макроса ''BFA()'' выдавалось предупреждение о безусловном преобразовании большой константы в ''unsigned int''</color>
    * **Добавлен макрос BFARD()**, предназначенный для прямого доступа к любой скалярной переменной в ОЗУ.

\\ 

----
\\ 

  * **1.0.24** (4 марта 2009) {{articles:mchp:bfa_rev_1_0_24.rar|скачать}} @ 6 кБ
    * Изменен порядок передачи параметров
      * <color grey>Параметр, указывающий тип операции в макрос передается первым</color>
    * Добавлен макрос ''BFARI()''
      * <color grey>Макрос ''BFARI()'' обеспечивает доступ к структуре по указателю</color>
    * Для всех макросов добавлены новые операции: ''BFA_SET'' и ''BFA_CLR''
      * <color grey>Эти операции могут использоваться для установки или сброса битов по маске в битовом поле. Маска может передаваться как в виде константы, так и в виде переменной.</color>
    * ''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
    * <color grey>Атомарный доступ к именованым полям периферийных регистров PIC24/dsPIC</color>
  * ''**BFAR**(comm, reg_name, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange
    * <color grey>Атомарный доступ к битовым полям периферийных регистров и переменных. Битовое поле указывается как числовой диапазон. Переменная должна быть расположена в NEAR DATA SPACE (первые 8 r< ОЗУ)</color>
  * ''**BFARI**(comm, pt, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange **I**ndirect
    * <color grey>Атомарный доступ к битовым полям периферийных регистров и переменных по указателю. Битовое поле указывается как числовой диапазон</color>
  * ''**BFARD**(comm, val, lower, upper, ...)'' - **B**it **F**ield **A**ccess using **R**ange **D**irect
    * <color grey>Атомарный доступ к битовым полям периферийных регистров и переменных. Битовое поле указывается как числовой диапазон. Переменная может быть расположена в любом месте ОЗУ (NEAR или FAR DATA SPACE)</color>

\\ 


=== BFA() ===

---- 
\\ 

Макрос обеспечивает атомарный доступ к именованым полям структур периферийных регистров микроконтроллеров PIC24/dsPIC.

**Вызов:**
<code cpp>
BFA(comm, reg_name, field_name, ...)
</code>

**Параметры:**

  ; ''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'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\ 

**Пример вызова:**  
<code cpp>
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 */
</code>

~~UP~~

\\ 

=== BFAR() ===

----
\\ 

Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной, которая находится в области **NEAR DATA SPACE** (первые 8 кБ ОЗУ). Битовое поле указывается в виде диапазона (младщий бит / старший бит).


**Вызов:**
<code cpp>
BFAR(comm, reg_name, lower, upper, ...) 
</code>

**Параметры:**

  ; ''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'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\ 

**Пример вызова:**  
<code cpp>
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 */
</code>

~~UP~~

\\ 

=== BFARI() ===

----
\\ 

Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной <color green>по указателю</color>. Битовое поле указывается в виде диапазона (младщий бит / старший бит). Переменная может находиться как в NEAR, так и в FAR DATA SPACE.

**Вызов:**
<code cpp>
BFARI(comm, pt, lower, upper, ...) 
</code>

**Параметры:**

  ; ''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'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\ 

**Пример вызова:**  
<code cpp>
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 */
</code>

~~UP~~

\\ 

=== BFARD() ===

----
\\ 

Макрос обеспечивает атомарный доступ к битовому полю любой структуры или переменной, которая находится в любой области ОЗУ (NEAR или FAR). Битовое поле указывается в виде диапазона (младщий бит / старший бит).


**Вызов:**
<code cpp>
BFARD(comm, val, lower, upper, ...) 
</code>

**Параметры:**

  ; ''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'', параметр указывает значение, которое записывается в битовое поле. В остальных случаях параметр должен быть равен битовой маске. ##Параметр может быть переменной##.
\\ 

**Пример вызова:**  
<code cpp>
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 */
</code>

~~UP~~

\\ 





==== Пояснения ====

=== Проверка параметров ===

Приведенные выше макросы проверяют передаваемый параметр ''comm'', указывающий на метод доступа к полю структуры. Остальные параметры не проверяются. Для ''comm'' определены следующие разрешенные значения:

<code cpp>
#define BFA_WR    0xAAAA        /* Запись в битовое поле                  */ 
#define BFA_RD    0x5555        /* Чтение битового поля                   */
#define BFA_IV    0x9999        /* Побитовое инвертирование битового поля */
</code>

Контроль передачи параметров в макрос сделан интересным способом: в заголовочном файле объявлены уникальные для каждого параметра типы:

<code cpp>
#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);
</code>

которые используются в макросах для объявления локальной переменной:

<code cpp>
__BFA_COMM_GET(comm) v = __VA_ARGS__+0; 
</code>

Если передаваемый в макрос параметр отличается от определенных ''BFA_WR'', ''BFA_RD'' и ''BFA_IV'', то компилятор выдаст ошибку:

<code>
source\appl\appl.c: In function 'main':
source\appl\appl.c:73: error: '__BFA_COMMAND_ERROR_0' undeclared (first use in this function)
</code>

~~UP~~

=== Условный оператор ? : ===

Использование условного оператора ''?:'' позволило реализовать с помощью одного макроса как выполнение операции, так и возврат значения. Если не вдаваться в детали реализации, все макросы выглядят следующим образом:

<code cpp>
#define BFAXX(comm, ...)
({
    ((comm) == BFA_WR)
    ? ({
      })
      /* ........ */
    : ({ /* BFA_RD */
         __BFA_STRUCT_VAL(reg_name).field_name;
      })
})
</code>

Если параметр ''comm'' (тип доступа к структуре) равен ''BFA_RD'', то макрос генерирует следующее выражение:

<code>
__BFA_STRUCT_VAL(reg_name).field_name;
</code>

где

<code>
#define __BFA_STRUCT_VAL(a)   a##bits
</code>

Таким образом, написав

<code>
b = BFA(BFA_RD, IPC0, INT0IP);
</code>

после работы препроцессора получим простое присваивание:

<code>
b = IPC0bits.INT0IP;
</code>

~~UP~~



==== Примеры использования ====

В оригинальной документации, которая поставляется с демонстрационной версией AVIX-RT приведено много примеров использования этих макросов. К сожалению, лицензионное соглашение запрещает публикацию документа третьим лицам. Однако никто не запрещает зарегистрироваться на сайте [[http://www.avix-rt.com/]] и скачать AVIX-RT.

Конечно, прямое использование макросов ''BFA()'' и ''BFAR()'' не слишком наглядно. Тем не менее это один из методов:

<code cpp>
BFA(BFA_WR, IPC0, INT0IP, 0);    /* Запись в поле INT0IP регистра IPC0 константы 0 */
BFA(BFA_WR, IPC0, INT0IP, 4);    /* Запись в поле INT0IP регистра IPC0 константы 4 */
</code>

Если регистр используется в вашем приложении только на запись, можно определить следующий макрос:

<code cpp>
#define LED(v)   BFA(BFA_WR, LATB, LATB0, (v))
</code>

и затем использовать уже его:

<code cpp>
LED(1);
LED(0);
</code>

Если поле структуры используется как на запись, так и на чтение, можно определить следующий макрос с переменным количеством параметров:

<code>
#define TMR1_PS(a, ...)   BFA((a), T1CON, TCKPS, __VA_ARGS__)
</code>

Использовать это макрос нужно так:

<code>
TMR1_PS(BFA_WR, 0);
a = TMR1_PS(BFA_RD);
</code>

Очень часто необходимо получить атомарный доступ к необъявленному полю структуры - например, установить определенное значение на нескольких выводах контроллера. Для этого можно использовать макрос ''BFAR()'':

<code>
#define LCD_PORT(v)  BFAR(BFA_WR, LATE,  0, 3, (v))
</code>

А дальше очевидно:

<code>
LCD_PORT(0x02);
LCD_PORT(0x00);
</code>

~~UP~~




==== Выводы ====

<note>Использование подобных макросов ##обязательно## для всех, кто хочет писать быстрый и безопасный код для микроконтроллеров PIC24/dsPIC с использованием компилятора C30.
</note>

<note important>
Рассмотренный подход может быть реализован и для любой другой архитектуры и другого компилятора. Для этого необходимо чтобы:
  * компилятор имел гибкий 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]].
</note>

К основным достоинствам макросов ''BFA()'' и ''BFAR()'' относится реализация ##атомарного доступа## к разделяемым ресурсам. Конечно, это не означает, что в прерывании и в основном коде можно безболезненно шевелить одной и той же ногой контроллера. Но доступ к разным битам порта осуществляется безопасно. 

При использовании макросов ''BFA()'' и ''BFAR()'' нет необходимости запрещать прерывания или реализовывать критическую секцию. Это позволяет детерминировать время входа в прерывание, уменьшить объем занимаемого кода и скорость выполнения операций.

Более того при использовании макросов доступа к полям структуры уменьшается объем кода и время выполнение по сравнению с непосредственным доступом:

<code>
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
</code>

~~UP~~