STM32 Удобные внешние прерывания

STM32 Удобные внешние прерывания

При изучении STM32 меня приятно удивило большое количество внешних прерываний (всего 16), да и еще возможность настроить их на любой пин микроконтроллера. Такой расклад после того, как долгое время сидел на AVR — кажется фантастичным. Однако при детальном изучении радость моя несколько поуменьшилась. Оказывается, есть и ограничения:
1) Настроить прерывания можно только на один из входов, где совпадает нумерация порта.
Например, настроить два прерывания на GPIOA.0 и GPIOB.0 — не получится. Только одно из них. Это необходимо учитывать при разводке схемы.
2) Отсутствует способ вызова прерываний по нижнему уровню (иногда требуется именно этот способ, например при сочленении с WIZnet). Хотя этот недочет, в принципе, — программно решаемый.
3) Всего 7 векторов в прерываниях. А событий — 16. А это значит, что обработка нескольких событий производится в одном и том же месте.

Но даже такое положение вещей все равно гораздо более привлекательное, чем в старом-добром AVR-e.

Цель написания этой статьи — разбавить 3-е по списку ограничение — недостаток векторов, упрощение инициализации внешних прерываний. И напоследок, разберемся, что такое «слабосвязанные функции» и с чем их едят (С) Умка :). Итак, начнем:

У нас есть 16 внешних событий и 7 обработчиков. Так каким же образом все организовано? А очень просто:
Первые пять событий имеют свои собственные вектора (от 0 по 4 включительно), а вот остальные события делят оставшиеся два вектора приблизительно поровну:
Внешние события с 5 по 9 — группируются в один вектор, и с 10 по 15 — в другой.
Чтобы узнать, какое именно событие возникло, необходимо опросить регистр внешних прерываний EXTI->PR.
Пример:

void EXTI15_10_IRQHandler(void)
{
  if(EXTI->PR & 1<<Number_of_event)
  {
    EXTI->PR =  1<<Number_of_event; //сбрасываем событие записью "1" в нужный бит
    // обрабатываем событие
  }
}

где Number_of_event — это номер события

Все бы хорошо, да вот одна незадача: Внешние прерывания, обрабатывающиеся в одном векторе, используются в разных сочлененных устройствах, которые в свою очередь описаны в разных модулях. И как быть?
Одно из решений я предлагаю рассмотреть здесь.
Создадим отдельный модуль, в котором поместим шаблоны («рыбы») этих векторов и разместим в них вызов слабосвязанных функций. Преимущество таких функций в следующем: если тело вызываемой функции будет отсутствовать — не страшно. Она попросту не будет вызываться. Причем тело функции может располагаться в любой части программы, и обеспечивать видимость для нашего модуля совсем необязательно.
Итак, названия этих функций сделаем фиксированными по типу EXTIx_IRQHandler, где x — это номер прерывания. А уже в модулях периферии можно будет разместить тела тех самых вызываемых функций.

Итак, сам код:

external.h
#ifndef EXTERNAL_H
#define EXTERNAL_H
#include "bitbanding.h"

#define GPIO_TO_INT(GPIO)				(((uint32_t)(&(GPIO->CRL)) - GPIOA_BASE)>>10)

#define __PIN_TO_EXTI_IRQn(PIN)				(PIN == 0)? EXTI0_IRQn :\
							(PIN == 1)? EXTI1_IRQn :\
							(PIN == 2)? EXTI2_IRQn :\
							(PIN == 3)? EXTI3_IRQn :\
							(PIN == 4)? EXTI4_IRQn :\
							(PIN <= 9)? EXTI9_5_IRQn : EXTI15_10_IRQn
#define _PIN_TO_EXTI_IRQn(PIN)				__PIN_TO_EXTI_IRQn(PIN)
#define PIN_TO_EXTI_IRQn(PIN)				_PIN_TO_EXTI_IRQn(PIN)

#define __PIN_TO_EXTI_HANDLER(PIN)			EXTI##PIN##_IRQHandler
#define _PIN_TO_EXTI_HANDLER(PIN)			__PIN_TO_EXTI_HANDLER(PIN)
#define PIN_TO_EXTI_HANDLER(PIN)			_PIN_TO_EXTI_HANDLER(PIN)

#define EXTI_MODE_DISABLE				0x00
#define EXTI_MODE_RISE					0x01
#define EXTI_MODE_FALL					0x02
#define EXTI_MODE_BOTH					0x03

#define _EXTI_INIT(GPIO, PIN, EXTI_MODE, NVIC_PRIORITY)	do{\
			AFIO->EXTICR[PIN/4] = (AFIO->EXTICR[PIN/4] & ~((uint16_t)0x0F<<((PIN % 4)<<2)))|(GPIO_TO_INT(GPIO)<<((PIN % 4)<<2));\
			BIT_BAND_PER(EXTI->FTSR,1UL<<PIN)=!!(EXTI_MODE & 0x02);\
			BIT_BAND_PER(EXTI->RTSR,1UL<<PIN)=!!(EXTI_MODE & 0x01);\
			NVIC_SetPriority(PIN_TO_EXTI_IRQn(PIN),NVIC_PRIORITY);\
			(EXTI_MODE>0)? NVIC_EnableIRQ(PIN_TO_EXTI_IRQn(PIN)): NVIC_DisableIRQ(PIN_TO_EXTI_IRQn(PIN));\
			EXTI->PR = 1UL<<PIN;\
	                BIT_BAND_PER(EXTI->IMR,1UL<<PIN)=!!(EXTI_MODE);\
			}while(0)
	
#define EXTI_INIT(GPIO, PIN, EXTI_MODE, NVIC_PRIORITY)	_EXTI_INIT(GPIO, PIN, EXTI_MODE, NVIC_PRIORITY)

//example: EXTI_INIT(GPIOA, 9, EXTI_MODE_BOTH, 15);
#endif


Данный модуль использует другие модули, такие как bitbanding.h
Рассмотрим подробнее, что и для чего здесь написано:
Макрофункция GPIO_TO_INT преобразует имя структуры порта GPIO в ее порядковый номер.
Например, GPIO_TO_INT(GPIOA) — вернет 0, GPIO_TO_INT(GPIOB)1, и т.д. Напрямую использовать это нет нужды, однако эта макрофункция вызывается из основной макрофункции EXTI_INIT, описанной ниже. Она нам пригодится для удобной настройки прерывания.

Макрофункция PIN_TO_EXTI_IRQn(PIN) возвращает имя прерывания, для последующей инициализации в NVIC.
Пример: PIN_TO_EXTI_IRQn(9) преобразится к виду EXTI9_5_IRQn
Этот макрос так же будет использован основным макросом инициализации внешнего прерывания.

Макрос PIN_TO_EXTI_HANDLER(PIN) преобразуется к виду вызываемой функции нашими «рыбами».
Таким образом описав следующее:

#define ENCODER_PIN 10
#define ENCODER_IRQ_HANDLER PIN_TO_EXTI_HANDLER(ENCODER_PIN)
void ENCODER_IRQ_HANDLER(void); // вызываемая функция при возникновении события
 

получим, что ENCODER_IRQ_HANDLER преобразуется к виду EXTI10_IRQHandler, а ведь именно функцию с таким именем будет вызывать наш модуль.

Ну, и напоследок, о главном:
Макрофункция EXTI_INIT(GPIO, PIN, EXTI_MODE, NVIC_PRIORITY), где
GPIO — порт пина,
PIN — номер пина,
EXTI_MODE — режим прерывания. их 4:
EXTI_MODE_DISABLE — обработка события выключена
EXTI_MODE_RISE — событие настроено на подъем фронта
EXTI_MODE_FALL — по спаду
EXTI_MODE_BOTH — по подъему и спаду.

NVIC_PRIORITY — приоритет прерывания.

Следующий листинг — исполняемый файл external.c


#include "stm32f10x.h"
#include "external.h"

void EXTI5_IRQHandler(void) __attribute__((weak));
void EXTI6_IRQHandler(void) __attribute__((weak));
void EXTI7_IRQHandler(void) __attribute__((weak));
void EXTI8_IRQHandler(void) __attribute__((weak));
void EXTI9_IRQHandler(void) __attribute__((weak));
void EXTI10_IRQHandler(void) __attribute__((weak));
void EXTI11_IRQHandler(void) __attribute__((weak));
void EXTI12_IRQHandler(void) __attribute__((weak));
void EXTI13_IRQHandler(void) __attribute__((weak));
void EXTI14_IRQHandler(void) __attribute__((weak));
void EXTI15_IRQHandler(void) __attribute__((weak));

void EXTI9_5_IRQHandler(void)
{
  if (EXTI->PR & (1<<5))
    EXTI5_IRQHandler();
  if (EXTI->PR & (1<<6))
    EXTI6_IRQHandler();
  if (EXTI->PR & (1<<7))
    EXTI7_IRQHandler();
  if (EXTI->PR & (1<<8))
    EXTI8_IRQHandler();
  if (EXTI->PR & (1<<9))
    EXTI9_IRQHandler();
}

void EXTI15_10_IRQHandler(void)
{
  if (EXTI->PR & (1<<10))
    EXTI10_IRQHandler();
  if (EXTI->PR & (1<<11))
    EXTI11_IRQHandler();
  if (EXTI->PR & (1<<12))
    EXTI12_IRQHandler();
  if (EXTI->PR & (1<<13))
    EXTI13_IRQHandler();
  if (EXTI->PR & (1<<14))
    EXTI14_IRQHandler();
  if (EXTI->PR & (1<<15))
    EXTI15_IRQHandler();
}


Разберем все по порядку: вначале идут инклюды: Здесь ничего особенного.
Ниже у нас описания вызываемых функций. Они описаны как слабосвязанные. Подробную информацию по этой теме можно почерпнуть с официального сайта KEIL здесь

далее — пошли наши «рыбы». Здесь тоже ничего необычного. Видно, что «рыба» представляет собой простые вызовы слабосвязанных функций по условию. Поэтому заботиться о сбросе флагов нужно в самих функциях, впрочем, это обычная практика.

Здесь за ненадобностью отсутствуют описания векторов с 0 по 4. Понятное дело, они работают и без того красиво.

Вот и получается, что появился дополнительный ряд функций, по виду похожих на стандартные
EXTI5_IRQHandler, EXTI6_IRQHandler и т.д.

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

#define ENCODER_OP01_PORT GPIOC
#define ENCODER_OP01_PIN  9

#define ENCODER_OP02_PORT GPIOA
#define ENCODER_OP02_PIN  8



Здесь указаны настройки пинов для OP01 и OP02 (так названы связи в принципиальной схеме).

В самом модуле устройства можно написать следующее:

#include "gpio_init.h"

#define	Enc_OP01_IRQHandler		PIN_TO_EXTI_HANDLER(ENCODER_OP01_PIN)
#define Enc_OP02_IRQHandler		PIN_TO_EXTI_HANDLER(ENCODER_OP02_PIN)

void Init_Enc(void)
{
  //Настраиваем порт OP01
  GPIO_INIT_PIN(ENCODER_OP01_PORT, ENCODER_OP01_PIN, GPIO_MODE_INPUT_FLOATING);
  //Настраиваем прерывание OP01
  EXTI_INIT(ENCODER_OP01_PORT, ENCODER_OP01_PIN, EXTI_MODE_BOTH, 0);
  //Настраиваем порт OP02
  GPIO_INIT_PIN(ENCODER_OP02_PORT, ENCODER_OP02_PIN, GPIO_MODE_INPUT_FLOATING);
  //Настраиваем прерывание OP02
  EXTI_INIT(ENCODER_OP02_PORT, ENCODER_OP02_PIN, EXTI_MODE_BOTH, 0);
}

void Enc_OP01_IRQHandler(void)
{
  EXTI->PR = 1<<ENCODER_OP01_PIN;
  //обработка события OP01
}

void Enc_OP02_IRQHandler(void)
{
  EXTI->PR = 1<<ENCODER_OP02_PIN;
  //обработка события OP02
}


Здесь задефайнены имена вызываемых функций, которые будут преобразованы к стандарту EXTIx_IRQHandler нашего модуля. Этот этап необязателен, если вы будете использовать жестко назначенные имена функций и номера прерываний.

Здесь используются макросы из модуля gpio_init.h.
Функции Enc_OP01_IRQHandler и Enc_OP02_IRQHandler будут преобразованы к виду EXTI9_IRQHandler и EXTI8_IRQHandler препроцессором соответственно, что нам и требуется.

Итак, в модулях теперь у нас становится все красиво и лаконично. Теперь можно не думать, какие еще события в тех же векторах обрабатываются в других модулях. Каждому модулю принадлежит лишь та часть кода, которая относится непосредственно к этому модулю
  • +2
  • 20 августа 2013, 01:07
  • Mihail

Комментарии (69)

RSS свернуть / развернуть
жду отзывов)
0
Мсьё знает толк в препроцессорных извращениях. ;-)
0
надеюсь, это комплимент))
0
Увы, нет. Не знает. Иначе были бы операции ##.
Если посмотреть на описание ключа -ffunction-sections компилятора GCC, то становится видно, что убирать объявление неиспользуемых функций, забирая оные в #ifdef ненужно. Она всё равно не попадёт в код.
0
что именно не попадет в код? какая функция?
0
Я про то, что конструкцию
#if (defined EXTI5_ENABLED) || (defined EXTI6_ENABLED)||(defined EXTI7_ENABLED)||\
(defined EXTI8_ENABLED)||(defined EXTI9_ENABLED)
void EXTI9_5_IRQHandler(void)
{

}
#endif
в дефайны можно не заворачивать.
0
код еще раз внимательно посмотрите. Неужели в нем не видно ##?
0
увидел, но у препроцессорофилов его больше :)

важно: Я не призываю переписать все #ifdef EXTI5_ENABLED на ##, ибо это будет ещё менее читаемо.
0
так это и невозможно в принципе. но да ладно… я нашел другое решение, о котором скоро здесь напишу. Вами нелюбимые #ifdef исчезнут
0
говорят, что все «филы», как и «фобы» — одинаково больные люди…
+1
Британские учёные?
0
не знаю… просто крайности — не для меня. во всем должна быть золотая середина.
+1
подумайте о том, что будет если в «воздухе» будет висеть следующий код:
void EXTI0_IRQHandler(void)
{
  EXTI->PR = (1<<4);
  EXTI4_HANDLER();
}

Разве не видно, что компилятор будет показывать ошибку на отсутствующую функцию EXTI4_HANDLER();?
-1
можно условной компиляцией сделать #define EXTI4_HANDLER();
Вообще, как по мне, писать функцию для того, чтобы там вызвать функцию — очень плохая идея.
Объясню почему:
вот вызывается прерывание. И мы, сохраняя в стеке адрес возврата, прыгаем в EXTI0_IRQHandler. Там мы пишем(используя регистры процессора) в EXTI->PR, (1<<4) после чего вновь кладём в стек адрес возврата (а может ещё и регистры), кладём в регистры адрес прыжка в функцию, прыгаем туда, там проделываем полезную работу, после чего прыгаем обратно, потом прыгаем обратно, выходя из прерываний. «плохо быть слепоглухонемым сироткой без ручек. по возможности, избегайте этого» (с)
-1
а писать разные обработчики разной периферии в одной функции — идея еще хуже. Подумаешь, на один стек больше. В программе выделяется стековое пространство несколько килобайт. Когда у вас программа пишется, там может и до 10 вложений происходить, и больше…
+2
а писать разные обработчики разной периферии в одной функции — идея еще хуже.
Чем?
0
тем, что код получается разбросан частями. Да и в каком модуле будет находиться вектор EXTI15_10_IRQHandler? в каком из модулей? по какому принципу?
0
тем, что код получается разбросан частями
Код в любом случае будет разбросан частями.
а и в каком модуле будет находиться вектор EXTI15_10_IRQHandler
Уж явно не как в стм-овских библиотеках, в отдельном файле! В том модуле, который отвечает за тот логический блок, который отрабатывает данное прерывание. Или (если несколько логических блоков сошлись в данном прерывании) — выделить в отдельный модуль.

Если уж такой вопрос встал: а где будет находиться функция инициализации прерывания? В каком модуле? По какому принципу? А функция инициализации NVIC? А RCC?
0
ну так вот, вот вам и отдельный модуль!
+1
Тогда зачем из него вызывается другой?
0
чтобы все, относящееся к одному устройству — находилось в одном месте. Там общие переменные, общие дефайны и прочее… А иначе нужно весь интерфейс выворачивать наизнанку, делать видимыми те переменные, которые видны не должны и прочее…
0
чтобы все, относящееся к одному устройству — находилось в одном месте.
А если у тебя логика устройства такова, что 3 «устройства» — периферии задействованы в одном блоке? Тогда лепим ещё один слой абстракции?
Поверьте, отлаживать код, в котором этих слоёв 5-6 (например usb стек, да с РТОС, да с диспетчером ввода-вывода) всё равно сложно.
Ещё, если начать скрывать часть переменных, можно попасть в очень некрасивое положение: если в какой-то части работы устройства требуется подождать, пока не отработает периферия. Тогда либо ждёшь специального флага, либо разбиваешь функцию на 2 и пишешь каллбэки.
«Предыдущее итожа, я скажу примерно так:»

Далеко не всегда можно и нужно делать функции в обработчиках переменных.

Далеко не всегда надо отделять код работы периферий от логики приложения. Зачастую они слишьком тесно связаны, и это приводит к сложностям.

Далеко не всегда вообще надо делить код на много маленьких функций. Это вредно как для производительности, так и для логики. Иногда, неверно разделив логику на блоки, мы с большим трудом собираем её обратно.
0
Не понимаю сути ваших претензий. В любом случае в общем блоке прерываний все события отделены друг от друга. Это первое. Второе: чем плохо разделение? Производительность? Тогда вам на ассемблере писать надо если переживаете за лишних 3 такта. И третье: достаточно взглянуть на мой пример и можно понять насколько удобно имеется возможность переназначить пин в другое место. Просто в хедере правим пару параметров, а в тексте программы никаких изменений.
0
И если уж совсем невмоготу нужно что-то быстро обработать — используйте первые 5 прерываний. Они работают как обычно
0
Кстати под сочлененными устройствами я подразумевал не вложенную периферию, УВВ, а конкретный внешний ЧИП или энкодер, или еще какая вещь, которая может сразу задействовать несколько УВВ. Логично в этом случае все помещать в один модуль
0
Далеко не всегда надо отделять код работы периферий от логики приложения.
А я считаю ровно наоборот — далеко не всегда надо не отделять логику приложения от периферии.

Я уже здесь не раз говорил, что большое число устройств (хотя зависит от направления разработки) не требуют каких-то фантастических скоростей реакции, а, значит, лучше их обрабатывать как удобнее, а не как быстрее. Для этого лучше иметь универсальную библиотеку, которую потрошить и встраивать хоть на асме уже при конкретных исключениях и требованиях.
+1
статья уже обновилась и дефайны лишние исчезли.
0
теперь EXTI0_IRQHandler вообще не используется в модуле и работает в программе как обычно напрямую
0
Эх, вот послушали всяких брюзжащих меня и сделали хуже :)
Препроцессор — дело не красивое, но нужное. Без него будет всё медленно работать, как в данной версии. Зачем спрашивать про те прерывания, которых нет?
0
подумаю. надо подождать, может и верну кое-что…
0
просто в данный момент модуль external получается полностью отвязанным. Никакие настройки проекта на него не влияют. А в этом есть плюс. Можно просто добавить его в проект и сразу использовать новые вектора. В этом есть неоспоримый плюс. Если внедрить условную компиляцию — уже так не получится… Но там свои плюсы.
0
можно условной компиляцией сделать #define EXTI4_HANDLER();
Хорошее решение, да =DD

вот вызывается прерывание. И мы, сохраняя в стеке...
И что тут плохого? Если нужна суперскорость — делаем прерывание отдельно. И даже на асме, если хочется.
А так флаг прерывания сбрасывается как бы автоматически, вне зависимости от того, на каком выводе сидит детектор и что слушает пользовательский код. Хотя бы ради этой изоляции можно пожертвовать парой тактов на вызов внешней функции.
0
Что-то не нравится запись BIT_BAND_PER(EXTI->IMR,1<<PIN)=!!(EXTI_MODE). Не проверял, но думаю будут проблемы для значений PIN больше чем 15. Скорее, даже для больше чем 14. Регистр IMR 32-битный, значит, надо писать 1UL<<PIN
0
значение PIN не должно быть больше 15, поэтому здесь ошибки возникать не должно, но замечено верно. Подправлю
0
обновил статью.
0
#define Enc_OP01_IRQHandler             PIN_TO_EXTI_HANDLER(ENCODER_OP01_PIN)

void Enc_OP01_IRQHandler(void)
{

}


Не люблю такую маскировку имени функции — вроде, на первый взгляд кажется, что у неё символьное имя Enc_OP01_IRQHandler, а потом, глядишь, а оно на самом деле EXTI9_IRQHandler (или какое-нибудь ещё). Ну упс.

Да и связывание неявное, через дефайны, хм, не очень очевидно.
+1
Ну как я уже писал в статье — сие действо вовсе не обязательно. У меня это стоит для полной поддержки верхних дефайнов. Изменил номер пина — изменилось и название обработчика. Метод возможно и не явный. Однако имеет место быть.
0
Просто я делал те же прерывания по такому интерфейсу:
static const TPin Button = {PA, 0};

static void test_onButtonClick(void)
{
    // Код, выполняющийся при отпускнии кнопки
}

void test_Init(void)
{
	exti_Listen(&Button, EDGE_RISING, &test_onButtonClick);
}


Явное связывание обработчика с заданным выводом. Но да, вариантов реализации одного и того же может быть море
0
ну у меня в принципе, код получается шустрее, и у вас насколько понимаю уже все прерывания описаны… а у меня практически напрямую связывание. Вообще препроцессор для меня — это такое же средство программирования, и им не пренебрегаю. С помощью него можно многое сделать «задаром», то есть без нагрузки на процессор во время исполнения. Тем более если речь идет о каких-то константных операциях. Смысл константных выражений пихать в переменные не вижу. Потому что с препроцессором дружу на равных
0
Пока мне не нужно это сокращение нагрузки — всё ок =) Зато очень кратко, ясно и независимо.

Не совсем задаром — выигрываем только в скорости, в остальном проигрываем.
С препроцессором и я дружу, использовал ранее очень широко. Но взгляды с тех пор изменились, и в приоритетах теперь иное.
+1
… препроцессор для меня — это такое же средство программирования...
 Несомненно, это верно. Средство, и очень полезное. Иногда. В малых дозах.
 Перед применением больших доз следует осознать, что препроцессор затрудняет понимание именно сишного кода.
 К тому же, директивы препроцессора не поддаются отладке, в отладчик они не попадают.
 У знатоков препроцессора есть неплохие шансы выиграть The International Obfuscated C Code Contest.
+1
Мсьё знает толк в препроцессорных извращениях. ;-)
надеюсь, это комплимент))
Так штааа… можно понимать двояко.
0
отсюда и слово «надеюсь»
0
Например, вот боян 1999 года, не позднее. Я пробовал, оно действительно играет в шахматы в консоли. :-)
0
И не только играет, но и выигрывает. Впрочем, там большая часть кода на С так выглядит, эта еще читабельная даже.
0
Боян — более боянистый, чем я считал.
www.ioccc.org/winners.html#Vern_Paxson
www.ioccc.org/years.html#1992_vern
0
таки у меня и есть в малых. Вон, люди не возносят меня в ранг препроцессорофилов, впрочем как и фобов тоже. А там, где присменимы константные решения в проекте (например, обозначение ножек и интерфейсов) препроцессор — милое дело. Сделает всю «грязную работу» по правке сишного кода за меня. И мне останется только лишь править два три параметра, вполне себе понятных.
0
Я так же делал раньше. Но один минус, а именно невозможность передачи вывода как целого куда-то (в функцию или структуру) весьма отрицательно сказался на моих взаимоотношениях с дефайновым определением распиновки прибора.
0
Мысли. Их всего 2:
1) Операция AFIO->EXTICR[PIN/4] = (AFIO->EXTICR[PIN/4] & ~… неатомарна и чревата глюками — это же не AVR где может и пронесёт нелёгкая)), здесь задач может крутиться ого-го сколько. И каждая может использовать для своих целей регистр EXTICR[PIN/4].
2) Заглядывая в будущее. Раз уж так получается, что макросы «замыливаются», т.е. EXTI_INIT тянет за собой _EXTI_INIT, потом еще __EXTI_INIT… то предлагаю на верхнем уровне оставить EXTI_INIT как есть, но глубже — использовать имена с упоминанием семейства. То есть назвать макрос _EXTI_INIT_F1XX(). Вот почему: инициализация блока EXTI нужна и в семействе F4XX, но там она немножно по-другому, другие регистры. И когда Вы сделаете такую же библиотеку для F4XX, то не будет путаницы в именах. Тогда нужную реализацию инициализации к проекту подключать будет просто: #ifdef STM32F1XX #define EXTI_INIT() _EXTI_INIT_F1XX() #else #define EXTI_INIT() _EXTI_INIT_F4XX() #endif. Главный макрос останется единым, что позволит легко переводить проекты с платформы на платформу.
+2
1. а как вы сделаете ее атомарной? запретите все прерывания? по биту выставлять битбандом? какое решение у вас на виду?
EXICR, насколько я понимаю, используется только при настройке. Там только распределение, с какого порта нужно всосать событие.
за второе спасибо. Ценный совет. по существу. НО пока что у меня только одно семейство. На стм всего несколько недель. Дальше будет позже
0
Только при настройке? Да это ж ARM, здесь задачи появляются и удаляются в процессе работы. Где гарантия что инициализовать AFIO->EXTICR не понадобится какой-то задаче в самый неприятный момент?

Есть несколько способов сделать атомарность. Это может быть критические секции с помощью жесткой триады: save_interrupt(); disable_interrupt(); <работа> restore_interrupt(); с помощью средств, которые есть в компиляторах.

Атомарность можно организовать и с помощью хорошо описанной на этом ресурсе сладкой парочки LDREX/STREX we.easyelectronics.ru/STM32/atomarnye-operacii-v-cortex-m3.html

Атомарность можно сделать и битбандом (достаточно геморройно для групповых полей).

Главное — безопасность. Я, например, потратил не одну неделю чтобы много небезопасных библиотечных CMSIS-функций для таких глобальных систем систем как RCC, AFIO, и т.п. перевести на битбанд и на критические секции. И для F1xxx и для F4xx. И — забыл про них)) Теперь спокойно работаю.
0
ну я слабо представляю себе ситуацию, что какому-то процессу вдруг понадобится переключить прерывание, скажем с GPIOA.8 на GPIOC.8 Если по схеме ножки жестко разведены, и прерывания четко сформированы системой (если она есть). И в процессе менять это — ну не вижу вообще такой необходимости. Более того. такие шалости всем нужно запрещать, чтоб неповадно было… Но это уже вопрос к ОС. У меня же осью здесь и не пахнет… Так что считаю сие избыточным…
0
Говорю же — не одному процессу! Нескольким процессам. Это же ARM! Там запросто может быть RTOS. Одна задача работает с EXTI0, а другая c EXTI1.
Ну, например: В проекте пара внешних ADC (не заставляйте меня придумывать чтО именно они меряют — ну пусть напряжение и ускорение)). Каждая задача, запускаясь, проводит калибровку, измерение, накопление, линеаризацию, архивирование. Каждое ADC выдаёт свою готовность на EXTI0/1. Задачи совершенно независимые. Они могут создаваться или уничтожаться так, как Главной Задаче будет удобно. А вместе с задачами, сами понимаете, и прерывания EXTI0/1 то разрешаются, то запрещаются.
EXTICR[i] может быть прочитано и изменено в любой момент.
Когда система рухнет — дело Вашего везения))
0
Говорю же — не одному процессу! Нескольким процессам. Это же ARM! Там запросто может быть RTOS.
АРМ тут ни причём, в АВР тоже может быть RTOS и несколько процессов могут обращаться к одним и тем же регистрам. То есть в любом случае нужно использовать атомарный доступ, если он нужен.
0
Чуть выше писал: «это же не AVR где [b]может[/b] и пронесёт нелёгкая))».
Кто спорит?)) Наколоться можно и на AVR.
Атомарность не связана с RTOS. Достаточно прерывания.
Акцент на ARM сделан чтобы подчеркнуть его производительность.
0
Да это ж ARM
Ну, подумаешь… И не таких видали… Одноядерный процессор, ничего особенного с точки зрения доступа к данным…
0
Да особенностей доступа нет. И к данным, и к таким общим ресурсам как AFIO->EXTICR[i], или к RCC->чего-то там. То есть надо всё это защищать от совместного использования.
0
вот как соберусь на RTOS перелезать — непременно задумаюсь об атомарности. а пока — это лишнее.
0
Не совсем =) В данном случае, возможно, и лишнее, но в общем прерывания тоже требуют атомарности доступа к переменным, чтоб всё было ок.
0
Ну нафига в прерываниях AFIO->EXTICR менять? Я сам сторонник атомарности, но во всем же должен быть разумный предел.
А если это ось, то левым процессам раз и навсегда нужно запрещать ковыряться в таких регистрах как AFIO. Потому не ровен час что что нибудь слетит, независимо, атомарный доступ или не атомарный этот процесс имел…
2 чем плох метод передавать константные дефайны в функцию какую-то там?
0
В чем разница между вашим const pin={PA, 6} и моими define pin PA, 6? По способу передачи? В том что не один, а два входных параметра функции? Ну это я переживу, учитывая какие преимущества я имею на этапе компиляции
0
Пока мы используем это напрямую (подставляя дефайн в вызов функции или макрос препроцессора), это и быстро, и ок. Пусть и доступ к номеру/названию порта такого определения вывода надо получать через доп. макросы.

Но уже второй уровень вложенности функций доставит некоторые неудобства, а хранение в массивах и структурах определения вывода практически неизбежно приведёт к константам в каком-нибудь виде (const XXX = {}). Почему бы тогда это не сделать сразу?=)

Об эстетический стороне не говорю, это уже чересчур субъективно
0
А, самое главное только что вспомнил: я стремился избавиться от контроллерозависимости во всём коде, кроме драйверов. То есть никаких GPIOC, никаких #include <stm32f4xx.h> и типа того в основном коде. В общем-то ради чего всё и затевалось, и о чём этом успел благополучно позабыть <.<
Это ограничение и изменило уровень использования препроцессора до минимума.
0
а вот контроллеронезависимости можно добиться только с помощью препроцессора. #ifdef. Иначе никак.
а почему я не могу передать константу #define в подфункцию второго и последующих уровней? По-моему, тут нет проблем с этим.
0
а вот если вы в цикле решили настроить сразу все пины, то здесь наверное есть преимущество вашего метода… Организовать своеобразный массив… Однако этот метод не по мне, поскольку при инициализации разной переферии нужны обычно только несколько пинов. И городить глобальный цикл где-то не вижу смысла. Да и красоты в этом нет никакой
0
ередать можно, но если там штук пять параметров через запятую — ух:) Благо, для портов их всего два…
0
глядя на ваш код системного таймера — не скажу, что там кроссплатформенность… Все же есть функции, которые относятся к определенному семейству только. так что вы немного лукавите…
0
Отнюдь — для каждого типа контроллера код таймера свой. Но интерфейс один и тот же:)
0
вооот, а если иапользовать #ifdef — то код таймера отличался бы лишь на малую толику. Не пришлось бы переписывать заново модуль
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.