====== Атомарный доступ к структурам ====== ===== Проблема атомарного доступа ===== Под атомарным доступом будем понимать такое обращение (операции чтения, модификации, записи, или их последовательность) к переменной или периферийному регистру, которое не приведет к конфликту при возникновении прерывания или приоритетного вытеснения задачи в 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~~