Top.Mail.Ru
Уведомления
Очистить все

Перенос таблицы векторов прерываний и проблемы под C++ связанные с этим.

Эдуард
(@eduard)
Level 2 Moderator

Прерывания в STM32.

Или опус от том, что я хочу получить.

Прерывания генерируемые под HAL в CubeMX сделаны просто, до безобразия. В каталог с проектом кидается файл stm32f4xx_it.c, в котором описывается прерывание, если мы его выбрали в Кубе.

Примечание: В примере кода из этого файла и в дальнейшем я буду убирать строки не имеющие отношения к теме.

Вот код из этого файла:

void USART1_IRQHandler(void)

{

  HAL_UART_IRQHandler(&huart1);

}

 

void UART4_IRQHandler(void)

{

  HAL_UART_IRQHandler(&huart4);

}

 

void UART5_IRQHandler(void)

{

  HAL_UART_IRQHandler(&huart5);

}

Как видим, выбрав 3 периферийных устройства UART и USART, поставив галочки в закладке отведённой под прерывания, мы получаем три обработчика прерываний. В них идут строки отвечающие за стандартную обработку прерываний HALом. Так же в этом файле можно дописать свои строки для своих нужд. И расширить функциональность обработчика прерываний.

Перед тем, как рассказать что хочу я, небольшое вступление.

Так исторически сложилось, что я сначала попробовал писать на С++ и только позже мне в руки попал STM32. Концепция классов мне понравилась. Мне понравилось то, что на классах можно писать библиотеки, в код которых не нужно лазить ни мне, ни стороннему программисту, если класс написан корректно. И я начал писать свои библиотеки используя классы и готовые функции HAL. Столкнулся с тем, что HAL медлителен и некоторые его функции не совсем корректно работают. (Примеры показать не могу, так как после того, как у меня стало получаться, я не заархивировал то, что я делал до этого, а просто потёр). Кода я начал этим заниматься у меня не было опыта ни в HAL, ни С++. Поэтому у меня был выбор каким путём мне идти. Что самое интересное, огромное большинство программистов, которые привыкли к HAL, почему то воспринимают С++ и классы в штыки и не могут меня понять. Но это их путь, а это мой. Так как я писал раньше только на Ассембелере, мне ближе стало писать с помощью прямых обращений к периферии МК, пользуясь только дефайнами и функциями, которые предоставляет CMSIS.

Теперь по классам могу сказать только несколько слов о том, как я это себе представляю.

Примечание: Основные года, на которые пришлись мои программерские изыски - это 1984-1991 и 2017 и по сей день. А на С++ я начал программировать с 2020 года. Поэтому я сейчас могу путаться в современной терминологии и пользоваться старой, времён СССР. Кроме того я практик, а не теоретик. Я запоминаю и понимаю только то, что сам "ручками пощупал" Поэтому прошу относиться к этому снисходительно.

Класс для меня - это чёрный ящик. Обратившись к которому я могу получить результат. И в котором есть тоже небольшие "коробочки", к которым я могу так или иначе обратиться.

Теперь то же самое, но с прерыванием.

Таким образом у меня сделано в следующей реализации библиотек UART.

extern "C" void UART4_IRQHandler(void)

{

// Прерывание от приёмника.

  if((UART4->SR & USART_SR_RXNE) && (UART4->CR1 & USART_CR1_RXNEIE)) // Если прерывание приема разрешено и что-то получили

  {

    // Обрабатываем

  }

  // Прерывание от передатчика.

  if((UART4->SR & USART_SR_TXE) && (UART4->CR1 & USART_CR1_TXEIE)) // Если прерывание разрешено и произошло,

  {

    // Обрабатываем

  }

}

 

// UART5

extern "C" void UART5_IRQHandler(void)

{

  //Если прерывание приема разрешено и что-то получили

  if((UART5->SR & USART_SR_RXNE) && (UART5->CR1 & USART_CR1_RXNEIE))

  {

    // Обрабатываем

  }

  if((UART5->SR & USART_SR_TXE) && (UART5->CR1 & USART_CR1_TXEIE)) // Если передачтик пуст

  {

    // Обрабатываем

  }

}

Этот код находится внутри библиотеки и его нельзя поменять.

Достоинство:

  1. Обработчик находится внутри класса UART и нам в него не нужно лазить, что бы изменить его, так как свою функцию он обрабатывает.

Недостаток:

  1. Исходит из достоинства. Мы не можем поменять обработчик (Ну нельзя в библиотеку лазить.);

  2. Все прерывания должны быть описаны заранее, что бы компилятор мог расставить их по своим местам. А это лишний код, который нам не нужен. И невозможность написать другую библиотеку, которая будет тоже пользоваться UART, например 1-Wire через UART.

Правда у второго пункта есть обходной манёвр. Использовать класс UART уже в самой библиотеке 1-Wire. Но этот способ уже не катит, если использовать DMA. А в будущем я рассчитываю это сделать.

Второй способ, но он годен только для периферии, которая представлена в единичном экземпляре, например RTC.

Это кусок кода из main():

uart myUART(USART1, false);

RTC_main myRTC(RTC_LSE);

 

void setIRQ(void);

 

int main(void)

{

  myUART.init(256000);

  myUART.println(myRTC.Reinit());

  myRTC.setIT_WUCK(setIRQ, 1); // Инициализация внешнего обработчика прерываний

 

 

  while (1)

  {

    delay(5000);

  }

}

 

void setIRQ(void) // Это у нас внешний обработчик прерывания

{

  Led_01.digitalTogglePin();

  sprintf(SendBuff, "Date : %02d/%02d/%04d - %d", myRTC.getDate(), myRTC.getMonth(), myRTC.getYear(), myRTC.getWeek());

  myUART.print(SendBuff);

  sprintf(SendBuff, " Time : %02d%02d", myRTC.getHour(), myRTC.getMinute(), myRTC.getSeconds());

  myUART.println(SendBuff);

}

Часть содержимого класса RTC:

void RTC_main::setIT_WUCK(callback_IRQ ExternIT, uint16_t Timer)

{

  // Устанавливаем прерывание

  EXTI->IMR |= EXTI_IMR_MR22;

  EXTI->RTSR |= EXTI_RTSR_TR22;

 

  callback_RTC = ExternIT; // Здесь мы навешиваем внешний обработчик прерывания

  NVIC_EnableIRQ(RTC_WKUP_IRQn);

}

И внутренний обработчик прерывания:

extern RTC_main myRTC;

 

// Прерывание

extern "C" void RTC_WKUP_IRQHandler(void)

{

  PWR->CR |= PWR_CR_DBP; // Разрешить доступ к Backup области

  RTC->ISR &= ~RTC_ISR_WUTF;

  EXTI->PR |= EXTI_PR_PR22;

  PWR->CR &= ~PWR_CR_DBP; // запретить доступ к Backup области

 

  if (myRTC.callback_RTC != NULL) myRTC.callback_RTC();

}

Как видно, он ничего не делает, только сбрасывает флаги и обращается к внешней функции обработки.

Достоинство:

  1. Мы добились внешней обработки прерывания, которую мы можем описать в функции main().

Недостаток:

  1. Если RTC было бы два, то как и предыдущем примере, нам бы пришлось описывать каждый RTC в обязательном порядке;

  2. Строка extern RTC_main myRTC; Она однозначно заставляет свой объект обзывать myRTC и никак иначе и, если эта библиотека нам не нужна и мы её не используем, компилятор начинает ругаться, что myRTC не определён.

Из пункта 2 есть два выхода:

  1. На время убрать его из каталога, что неприемлемо по "религиозным мотивам";

  2. В CubeIDE исключить его из проекта. Что нормально, но некрасиво.

Есть третий выход - сделать как в HAL. Просто вынести таблицу за пределы любых библиотек и писать все прерывания там. Но тогда теряется смысл делать всё на классах. Ведь некоторые библиотеки могут сами предоставить обработку прерывания и нам не нужно туда вмешиваться. Но иногда нам нужно вмешаться и как быть?

Есть четвёртый выход - Это перенести таблицу векторов из ПЗУ в ОЗУ. И менять адреса векторов по мере необходимости. Фирма ST предусмотрела в МК такой ход. Я уже научился это делать. Но тут возникают другие проблемы. Прерывание в классе можно сделать только статической функцией, а это, как я понимаю и проверил на практике, будет выглядеть так (на примере TIM6, TIM7.)

Получается, что инициализируя TIM6 мы получаем то что нужно. Но нам хочется использовать и TIM7 тоже. Мы создаём объект myTIM7. И его прерывание перекрывает прерывание которое ранее на себя зарезервировал класс myTIM6. Таким образом прерывание от TIM6 перестаёт выполняться.

#define VECTORTABLE_SIZE (97)

#define VECTORTABLE_ALIGNMENT (0x100ul)

 

volatile uint32_t vectorTable_RAM[VECTORTABLE_SIZE] __attribute__(( aligned (VECTORTABLE_ALIGNMENT) ));

 

extern uint32_t g_pfnVectors[]; // Указатель на вектора, находится в ASM скрипте

 

bool Flag_TIM6 = false;

bool Flag_TIM7 = false;

 

uart myUART(USART1, false);

TIM_Base myTIM6(Tim_Base_6);

TIM_Base myTIM7(Tim_Base_7);

 

int main(void)

{

  SystemClock_Config(Quartz_8);

 

  /*--------------------------------------------------------------------------------------

  * Перекидывание векторов прерывания в ОЗУ

  */

  __disable_irq(); // Отключаем прерывания

  for (uint8_t i = 0; i < VECTORTABLE_SIZE; i++) vectorTable_RAM[i] = g_pfnVectors[i]; // Копируем таблицу векторов на новое место в ОЗУ

  __DMB(); // Data Memory Barrier to ensure write to memory is completed

  SCB->VTOR = (uint32_t)&vectorTable_RAM; // Set VTOR to the new vector table location

  __DSB(); // Data Synchronization Barrier to ensure all

  __enable_irq(); // Разрешаем прерывания

  //--------------------------------------------------------------------------------------

 

  myUART.init(256000);

  myTIM6.Init(41999, 1000, myTIM_ISR6);

  myTIM7.Init(41999, 2000, myTIM_ISR7);

 

  while(1)

  {

    if(Flag_TIM6){ myUART.println("Hello TIM6"); Flag_TIM6 = false;}

    if(Flag_TIM7){ myUART.println("Hello TIM7"); Flag_TIM7 = false;}

  }

 

}

 

/*

* Функции

*/

void myTIM_ISR6(void)

{

  __NOP();

  Flag_TIM6 = true;

}

 

void myTIM_ISR7(void)

{

  __NOP();

  Flag_TIM7 = true;

}

И установка этого прерывания. Это не моя идея, я её подсмотрел. Человек дал не полный код, может быть у него это и решено. Но на связь он не выходит.

Это код из класса:

extern uint32_t vectorTable_RAM[];

 

class TIM_Base *TIM_Base::self = NULL;

 

void TIM_Base::Init(uint16_t Prescaler, uint16_t Counter, callback_IRQ ExternIT)

{

  if(ExternIT != NULL)

  {

    __disable_irq();

    self = this;

    vectorTable_RAM[_TimIrq + 16] = (uint32_t) myISR_TIM; // Подменяем на свой вектор прерывания на класс, но не работает при создании

    // второго подобного объекта, объект созданный позже перехватывает обработку

    __DMB();

    __enable_irq();

    pfUserISR = myISR_TIM; // Ссылка на наш, внешний обработчик прерывания

    _TimBaseN->DIER |= TIM_DIER_UIE; // Разрешаем прерывания от переполнения

    NVIC_EnableIRQ(_TimIrq);

  }

}

 

void TIM_Base::myISR_TIM(void)

{

  self->_TimBaseN->SR &= ~TIM_SR_UIF;

  self->pfUserISR();

}

 

Этот способ достаточен при создании одного объекта. Создание ещё одного объекта всё крашит. Прерывание продолжает вызываться, но из за того, что строка self->_TimBaseN->SR &= ~TIM_SR_UIF; сбрасывает только флаг TIM7, МК зависает в глухой обработке прерывания от TIM6.

Но работает следующий код:

в main()

void myTIM_ISR6(void)

{

  if((TIM6->SR & TIM_SR_UIF) && (TIM6->CR1 & TIM_CR1_CEN))

  {

    TIM6->SR &= ~TIM_SR_UIF;

  }

  __NOP();

  Flag_TIM6 = true;

}

 

void myTIM_ISR7(void)

{

  if((TIM7->SR & TIM_SR_UIF) && (TIM7->CR1 & TIM_CR1_CEN))

  {

    TIM7->SR &= ~TIM_SR_UIF;

  }

  __NOP();

  Flag_TIM7 = true;

}

В классе.

void TIM_Base::Init(uint16_t Prescaler, uint16_t Counter, callback_IRQ ExternIT)

{

  if(ExternIT != NULL)

  {

    __disable_irq();

    self = this;

    vectorTable_RAM[_TimIrq + 16] = (uint32_t) ExternIT; // Подменяем на свой вектор прерывания, так работает, но обслуживание флагов

    // приходится переносить во внешнюю функцию

    __DMB();

    __enable_irq();

    pfUserISR = ExternIT; // Ссылка на наш, внешний обработчик прерывания

    _TimBaseN->DIER |= TIM_DIER_UIE; // Разрешаем прерывания от переполнения

    NVIC_EnableIRQ(_TimIrq);

  }

}

 

void TIM_Base::myISR_TIM(void)

{

  if((TIM6->SR & TIM_SR_UIF) && (TIM6->CR1 & TIM_CR1_CEN))

  {

    TIM6->SR &= ~TIM_SR_UIF;

    self->pfUserISR();

  }

 

  if((TIM7->SR & TIM_SR_UIF) && (TIM7->CR1 & TIM_CR1_CEN))

  {

    TIM7->SR &= ~TIM_SR_UIF;

    self->pfUserISR();

  }

}

Почему так работает, не совсем понятно. Но опять же это не то, что нужно. Представьте, как будет выглядеть ранее описанная библиотека UART.

В общем то мне необходимо решение этой проблемы, я пока не нашёл ответа, может кто нибудь сможет помочь.

Цитата
Topic starter Размещено : 03.05.2022 11:32
Aveal
(@aveal)
Top level Admin

Плоховато читается, когда код не в своем блоке "код", а текстом.

ОтветитьЦитата
Размещено : 16.05.2022 09:40
Эдуард
(@eduard)
Level 2 Moderator

@aveal Я блоком ставил. Скорее не тем.

ОтветитьЦитата
Topic starter Размещено : 16.05.2022 18:59
Поделиться: