Всех рад снова приветствовать 🤝 Думаю многие читали статьи Эдуарда в Сообществе, посвященные созданию набора библиотек для работы с микроконтроллерами STM32 на C++. Собственно, на этой почве у Эдуарда и возник вопрос, как максимально гармонично вписать обработку прерываний, поэтому я сегодня опишу на демо-примере мою концепцию реализации.
И поскольку речь пойдет именно об общей идее, то я не буду убирать HAL и т. д., не вижу особого смысла в данном случае. При желании соответствующие вызовы библиотеки без проблем заменяются на аналогичные манипуляции напрямую с регистрами контроллера.
Я набросаю базовый функционал для пары периферийных модулей, пусть будет Timer и GPIO, чего вполне хватит для наглядной демонстрации. Глобально я не просчитывал всевозможные варианты, буду реализовывать ту идею, которая у меня сформировалась сразу после прочтения темы на форуме, так что не исключено, что в будущем архитектуру нужно будет подправить (в случае желания развить проект дальше). А теперь переходим к сути, которую я буду разбивать на отдельно взятые этапы.
Перенос таблицы векторов прерываний в RAM.
Рассмотрим в двух словах, как организована обработка прерываний по умолчанию. Я далее по тексту буду использовать контроллер семейства STM32F10x, для других названия файлов будут незначительно отличаться. Итак, в файле startup_stm32f103c8tx.s в g_pfnVectors
определены точки входа в конкретные функции, вызывающиеся при возникновении того или иного прерывания.
Таким образом, для добавления своего кода в тот или иной обработчик прерываний, присутствующий в startup_stm32f103c8tx.s, функции переопределяются в пользовательском коде, именно так и происходит при генерации проекта через STM32CubeMx. Я же хочу дефолтные обработчики заменить своими, из чего вытекает необходимость создать свою таблицу векторов прерываний в RAM и поместить ее адрес в регистр SCB->VTOR
. Так, разбираем пошагово идею:
- Создаем свою кастомную таблицу векторов прерываний, физически это означает определение массива. Поместим в main.cpp (о создании демо-проекта - во второй части статьи):
uint32_t ramVectorTable[constants::isrVectorTableSize] __attribute__(( aligned (constants::isrVectorTableAlingment) ));
Здесь используются константы, определенные в constants.h (полный код и проект в конце статьи):
inline constexpr uint8_t isrVectorTableSize = 67; inline constexpr int isrVectorTableAlingment = 0x100;
Размер таблицы векторов определяется количеством обработчиков в g_pfnVectors
.
- Переместим все дефолтные обработчики из
g_pfnVectors
вramVectorTable
– это и будет вторым шагом. Впоследствии мы изменим те элементы таблицы, которые потребуется, остальные же спокойно будут пребывать в первозданном виде, что, в свою очередь, избавит нас от множества потенциальных проблем.
for (uint16_t i = 0; i < constants::isrVectorTableSize; i++) { ramVectorTable[i] = g_pfnVectors[i]; }
- Остается один шаг – поместить адрес
ramVectorTable
в регистрSCB->VTOR
:
__disable_irq(); SCB->VTOR = (uint32_t)&ramVectorTable; __DSB(); __enable_irq();
Периферия. База.
Плавно переходим к следующему этапу – непосредственной работе с периферией контроллера. И, опять же, опишу мой вариант реализации. Заключается он в следующем: для каждого типа периферийных модулей создается свой класс, инкапсулирующий в себе все взаимодействие с данным типом. Таким образом, для данного проекта я добавляю классы Gpio
и Timer
:
При этом все эти классы наследуют от базового PeripheralUnit
, который в себе заключает общий функционал:
class PeripheralUnit { public: PeripheralUnit(); virtual ~PeripheralUnit(); virtual void interruptHandler(); bool getInterruptFlag(); void clearInterruptFlag(); static void setIsrVectorTable(uint32_t *table); protected: void init(); void initIsr(IRQn_Type irq); bool isInitialized; bool interruptFlag; private: static uint32_t *isrVectorTable; };
Пробежимся прямо по очереди:
interruptHandler()
– обработчик прерывания, данная функция будет переопределяться в случае необходимости в производных классах.getInterruptFlag() / clearInterruptFlag()
– получение/очистка флага прерываний.setIsrVectorTable(uint32_t *table)
– статический метод для модификации указателя на актуальную таблицу векторов прерываний.init() / initIsr(IRQn_Type irq)
– базовая инициализация, а также конфигурация прерывания, опять же – в случае необходимости.isInitialized / interruptFlag
– флаги, сигнализирующие об успешной инициализации и о срабатывании прерывания.isrVectorTable
– и, наконец, тот самый указатель на таблицу векторов, которая уже создана на первом этапе.
Далее реализация:
#include "PeripheralUnit.h" #include "IrqCallback.h" constexpr int isrVectorTableOffset = 16; uint32_t* PeripheralUnit::isrVectorTable; std::vector<PeripheralUnit*> IrqCallbackBase::irqPeripherals; PeripheralUnit::PeripheralUnit() : isInitialized(false), interruptFlag(false) { } PeripheralUnit::~PeripheralUnit() { } void PeripheralUnit::setIsrVectorTable(uint32_t* table) { PeripheralUnit::isrVectorTable = table; } bool PeripheralUnit::getInterruptFlag() { return interruptFlag; } void PeripheralUnit::clearInterruptFlag() { interruptFlag = false; } void PeripheralUnit::init() { isInitialized = true; } void PeripheralUnit::initIsr(IRQn_Type irq) { IrqCallbackBase::irqPeripherals.push_back(this); isrVectorTable[isrVectorTableOffset + irq] = (uint32_t)((SimpleCallback)(*createCallback(IrqCallbackBase::irqPeripherals.size() - 1))); } void PeripheralUnit::interruptHandler() { interruptFlag = true; }
Здесь все логично и, в целом, понятно уже из названия функций. Инициализация на данном этапе фактически пустая, в частности по той причине, что ее мы отдали CubeMx. Обработчик прерываний по умолчанию просто устанавливает флаг interruptFlag
в true
. Регистрация кастомного обработчика прерывания происходит так:
void PeripheralUnit::initIsr(IRQn_Type irq) { IrqCallbackBase::irqPeripherals.push_back(this); isrVectorTable[isrVectorTableOffset + irq] = (uint32_t)((SimpleCallback)(*createCallback(IrqCallbackBase::irqPeripherals.size() - 1))); }
К описанию этого процесса и переходим.
Обработка прерываний.
Итак, особо углубляться я не буду, если что пишите в комментарии, на форум или в группу, буду рад помочь )
Базовая ситуация такая – для того, чтобы обеспечить вызов своего обработчика прерывания, необходимо всего-то поместить его адрес на нужную позицию в ramVectorTable
. Но! Указатель на функцию и указатель на метод класса это две принципиально разные вещи, поэтому изначально стандарт допускает для данных манипуляций использовать только статические методы, что кардинально противоречит моей концепции.
Допустим, есть класс Timer
и два объекта tim1Instance
, tim2Instance
. Естественно, необходимо, чтобы каждый из объектов имел свой обработчик прерывания, поскольку физически именно так и есть:
Для статической же функции получим:
По существу, это ничем не лучше использования HAL’овских обработчиков, засунутых в один отдельный файл stm32f1xx_it.c. И более того, если попытаться прогуглить решение данной проблемы, то в подавляющем большинстве случаев ответом будет именно выделение функционала в статическую функцию. У нас случай нестандартный, данный вариант априори не подходит, поэтому у меня будет другое решение.
Итоговый код таков, файл IrqCallback.h:
typedef void (*SimpleCallback)(void); class IrqCallbackBase { public: IrqCallbackBase(SimpleCallback function) { callback = function; } static void staticInvoke(uint8_t index) { irqPeripherals[index]->interruptHandler(); } static std::vector<PeripheralUnit*> irqPeripherals; operator SimpleCallback() const { return callback; } private: SimpleCallback callback; }; template <uint8_t I> class IrqDynamicCallback : public IrqCallbackBase { public: IrqDynamicCallback() : IrqCallbackBase(&IrqDynamicCallback<I>::generatedStaticFunction) { } static void generatedStaticFunction() { return staticInvoke(I); } }; template<uint8_t I> struct IrqDynamicCallbackFactory { static inline std::shared_ptr<IrqCallbackBase> create(uint8_t index) { if (index == I) { return std::shared_ptr<IrqCallbackBase>(new IrqDynamicCallback<I>()); } else { return IrqDynamicCallbackFactory<I + 1>::create (index); } } }; struct Overflow { static inline std::shared_ptr<IrqCallbackBase> create(uint8_t index) { return NULL; } }; template<> struct IrqDynamicCallbackFactory<constants::isrVectorTableSize> : Overflow {}; std::shared_ptr<IrqCallbackBase> createCallback(uint8_t index) { return IrqDynamicCallbackFactory<0>::create(index); }
Использование в PeripheralUnit
мы уже видели:
void PeripheralUnit::initIsr(IRQn_Type irq) { IrqCallbackBase::irqPeripherals.push_back(this); isrVectorTable[isrVectorTableOffset + irq] = (uint32_t)((SimpleCallback)(*createCallback(IrqCallbackBase::irqPeripherals.size() - 1))); }
Помещаем текущий периферийный модуль, для которого и реализуем обработку прерываний, в вектор IrqCallbackBase::irqPeripherals
. А в таблицу векторов прерываний заносим адрес функции, что в конечном итоге приведет к вызову метода interruptHandler()
текущего объекта PeripheralUnit
.
Конкретная периферия.
Теперь по-быстрому добавляем классы для работы с таймерами и портами ввода-вывода, начав с первого из перечисленных:
class Timer : public PeripheralUnit { public: enum class Mode { Base, Irq, Dma }; Timer(); virtual ~Timer(); void init(TIM_HandleTypeDef *timerHandle, IRQn_Type irq); void start(Mode); private: void interruptHandler(); TIM_HandleTypeDef *handle; };
Собственно, три основных метода – инициализация, запуск в одном из режимов и обработчик прерывания:
void Timer::init(TIM_HandleTypeDef *timerHandle, IRQn_Type irq) { handle = timerHandle; PeripheralUnit::initIsr(irq); PeripheralUnit::init(); } void Timer::start(Mode operationMode) { switch(operationMode) { case (Mode::Base): HAL_TIM_Base_Start(handle); break; case (Mode::Irq): HAL_TIM_Base_Start_IT(handle); break; case (Mode::Dma): default: break; } } void Timer::interruptHandler() { if (__HAL_TIM_GET_FLAG(handle, TIM_FLAG_UPDATE) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(handle, TIM_IT_UPDATE) != RESET) { __HAL_TIM_CLEAR_IT(handle, TIM_IT_UPDATE); interruptFlag = true; } } }
Для этого демо-проекта я сделал только обработку прерывания по событию переполнения, в результате которого флаг interruptFlag
будет выставлен в единицу.
Пару слов по поводу обработки в целом – как по мне, так удобнее всего сделать полноценную event-driven архитектуру, при которой на событие прерывания одного из модулей может приходиться N-ое количество подписчиков. В примере я сделаю максимально просто, в while(1)
будем проверять соответствующие флаги, по значению которых уже выполнять те или иные действия.
Итак, движемся к GPIO. Здесь все еще более незатейливо, просто оборачиваем соответствующие вызовы HAL:
class Gpio : public PeripheralUnit { public: enum class State { Set, Reset }; Gpio(); virtual ~Gpio(); void init(GPIO_TypeDef *gpioPort, uint16_t gpioPin); void write(State state); State read(); void toggle(); private: GPIO_TypeDef *port; uint16_t pin; };
void Gpio::init(GPIO_TypeDef *gpioPort, uint16_t gpioPin) { port = gpioPort; pin = gpioPin; PeripheralUnit::init(); } void Gpio::write(State state) { switch(state) { case State::Set: HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET); break; case State::Reset: HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET); break; default: break; } } Gpio::State Gpio::read() { State currentState = State::Reset; if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) { currentState = State::Set; } return currentState; } void Gpio::toggle() { HAL_GPIO_TogglePin(port, pin); }
Демо-проект.
Переходим к тестированию и для начала создаем в STM32CubeIDE новый проект. Все точно так же, как и для «обычного» C-проекта, за исключением "Targeted language" непосредственно после выбора целевого контроллера:
В STM32CubeMx активируем необходимую периферию, у меня будут два порта ввода-вывода – один на вход (PA3), другой на выход (PC13):
На PA3 повесим кнопку, на PC13 светодиод, как же без него. И парочка таймеров, TIM2:
TIM3:
Тактирование:
Из данной конфигурации вытекает тот факт, что таймер TIM2 будет переполняться каждые 500 мс, а TIM3 – каждые 100 мс. Также в CubeMx включаем прерывания таймеров, на примере TIM3:
Генерируем код и осуществляем два действия:
- переименовываем main.c в main.cpp
- из файла stm32f1xx_it.c выкидываем с корнем обработчики
TIM2_IRQHandler()
иTIM3_IRQHandler()
, что естественно, так как прерывания обслуживаются внутри соответствующих модулей.
Инициализацию оставляем на откуп CubeMx, при желании выкинуть HAL - просто помещаем все настройки в функцию init()
конкретного класса. И да, если в процессе работы еще раз сгенерировать код через Cube, то будет создан main.c без пользовательского кода, вместо того, чтобы обновить main.cpp =\ Так что с этим разбираться нужно будет вручную, перенося либо код из main.cpp в новый main.c с последующим переименованием, либо в обратном направлении.
Для демо-проекта берем простейшую задачу: при нажатой кнопке (PA3) по переполнению TIM2 (каждые 500 мс в данном случае) изменяем состояние PC13 с целью обеспечить мигание диода. Если кнопка не нажата – бездействуем. По переполнению TIM3 просто инкрементируем счетчик extraTimerCounter
.
Так будет выглядеть реализация с учетом созданного в первой части статьи, создаем соответствующие объекты:
Timer timer; Timer extraTimer; Gpio output; Gpio input;
Инициализируем все и запускаем таймеры, в принципе, инициализацию можно перекинуть и в конструктор:
output.init(GPIOC, GPIO_PIN_13); input.init(GPIOA, GPIO_PIN_3); timer.init(&htim2, TIM2_IRQn); timer.start(Timer::Mode::Irq); extraTimer.init(&htim3, TIM3_IRQn); extraTimer.start(Timer::Mode::Irq);
В while(1)
проверяем прерывания и производим необходимые действия:
// Handle TIM2 interrupt bool interruptOccured = timer.getInterruptFlag(); if (interruptOccured == true) { if (input.read() == Gpio::State::Set) { output.toggle(); } timer.clearInterruptFlag(); }
// Handle TIM3 interrupt interruptOccured = extraTimer.getInterruptFlag(); if (interruptOccured == true) { extraTimerCounter++; extraTimer.clearInterruptFlag(); }
Все, на этом процесс завершен, собираем, прошиваем, проверяем. В результате чего имеем наглядную возможность убедиться в полной работоспособности 👍 Я опустил кусок, связанный с переносом таблицы векторов прерываний, который мы уже обсудили. Он присутствует в самом начале main()
:
for (uint16_t i = 0; i < constants::isrVectorTableSize; i++) { ramVectorTable[i] = g_pfnVectors[i]; } __disable_irq(); SCB->VTOR = (uint32_t)&ramVectorTable; __DSB(); __enable_irq(); PeripheralUnit::setIsrVectorTable(ramVectorTable);
Вот, в общем-то, на этом и все, пожалуй. Я не добавлял никаких проверок на передаваемые аргументы и т. д. и т. п., просто быстрый вариант решения конкретной задачи. И по итогу, полный код файлов и ссылка на полный проект:
#ifndef IRQCALLBACK_H_ #define IRQCALLBACK_H_ #include "stm32f1xx_hal.h" #include "constants.h" #include <vector> #include <memory> typedef void (*SimpleCallback)(void); class IrqCallbackBase { public: IrqCallbackBase(SimpleCallback function) { callback = function; } static void staticInvoke(uint8_t index) { irqPeripherals[index]->interruptHandler(); } static std::vector<PeripheralUnit*> irqPeripherals; operator SimpleCallback() const { return callback; } private: SimpleCallback callback; }; template <uint8_t I> class IrqDynamicCallback : public IrqCallbackBase { public: IrqDynamicCallback() : IrqCallbackBase(&IrqDynamicCallback<I>::generatedStaticFunction) { } static void generatedStaticFunction() { return staticInvoke(I); } }; template<uint8_t I> struct IrqDynamicCallbackFactory { static inline std::shared_ptr<IrqCallbackBase> create(uint8_t index) { if (index == I) { return std::shared_ptr<IrqCallbackBase>(new IrqDynamicCallback<I>()); } else { return IrqDynamicCallbackFactory<I + 1>::create (index); } } }; struct Overflow { static inline std::shared_ptr<IrqCallbackBase> create(uint8_t index) { return NULL; } }; template<> struct IrqDynamicCallbackFactory<constants::isrVectorTableSize> : Overflow {}; std::shared_ptr<IrqCallbackBase> createCallback(uint8_t index) { return IrqDynamicCallbackFactory<0>::create(index); } #endif /* IRQCALLBACK_H_ */
#include "PeripheralUnit.h" #include "IrqCallback.h" constexpr int isrVectorTableOffset = 16; uint32_t* PeripheralUnit::isrVectorTable; std::vector<PeripheralUnit*> IrqCallbackBase::irqPeripherals; PeripheralUnit::PeripheralUnit() : isInitialized(false), interruptFlag(false) { } PeripheralUnit::~PeripheralUnit() { } void PeripheralUnit::setIsrVectorTable(uint32_t* table) { PeripheralUnit::isrVectorTable = table; } bool PeripheralUnit::getInterruptFlag() { return interruptFlag; } void PeripheralUnit::clearInterruptFlag() { interruptFlag = false; } void PeripheralUnit::init() { isInitialized = true; } void PeripheralUnit::initIsr(IRQn_Type irq) { IrqCallbackBase::irqPeripherals.push_back(this); isrVectorTable[isrVectorTableOffset + irq] = (uint32_t)((SimpleCallback)(*createCallback(IrqCallbackBase::irqPeripherals.size() - 1))); } void PeripheralUnit::interruptHandler() { interruptFlag = true; }
#ifndef PERIPHERALUNIT_H_ #define PERIPHERALUNIT_H_ #include "stm32f1xx_hal.h" class PeripheralUnit { public: PeripheralUnit(); virtual ~PeripheralUnit(); virtual void interruptHandler(); bool getInterruptFlag(); void clearInterruptFlag(); static void setIsrVectorTable(uint32_t *table); protected: void init(); void initIsr(IRQn_Type irq); bool isInitialized; bool interruptFlag; private: static uint32_t *isrVectorTable; }; #endif /* PERIPHERALUNIT_H_ */
#include "Timer.h" Timer::Timer() : handle(NULL) { } Timer::~Timer() { } void Timer::init(TIM_HandleTypeDef *timerHandle, IRQn_Type irq) { handle = timerHandle; PeripheralUnit::initIsr(irq); PeripheralUnit::init(); } void Timer::start(Mode operationMode) { switch(operationMode) { case (Mode::Base): HAL_TIM_Base_Start(handle); break; case (Mode::Irq): HAL_TIM_Base_Start_IT(handle); break; case (Mode::Dma): default: break; } } void Timer::interruptHandler() { if (__HAL_TIM_GET_FLAG(handle, TIM_FLAG_UPDATE) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(handle, TIM_IT_UPDATE) != RESET) { __HAL_TIM_CLEAR_IT(handle, TIM_IT_UPDATE); interruptFlag = true; } } }
#ifndef TIMER_TIMER_H_ #define TIMER_TIMER_H_ #include "PeripheralUnit.h" class Timer : public PeripheralUnit { public: enum class Mode { Base, Irq, Dma }; Timer(); virtual ~Timer(); void init(TIM_HandleTypeDef *timerHandle, IRQn_Type irq); void start(Mode); private: void interruptHandler(); TIM_HandleTypeDef *handle; }; #endif /* TIMER_TIMER_H_ */
#include "Gpio.h" Gpio::Gpio() : port(NULL), pin(0) { } Gpio::~Gpio() { } void Gpio::init(GPIO_TypeDef *gpioPort, uint16_t gpioPin) { port = gpioPort; pin = gpioPin; PeripheralUnit::init(); } void Gpio::write(State state) { switch(state) { case State::Set: HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET); break; case State::Reset: HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET); break; default: break; } } Gpio::State Gpio::read() { State currentState = State::Reset; if (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET) { currentState = State::Set; } return currentState; } void Gpio::toggle() { HAL_GPIO_TogglePin(port, pin); }
#ifndef GPIO_GPIO_H_ #define GPIO_GPIO_H_ #include "PeripheralUnit.h" class Gpio : public PeripheralUnit { public: enum class State { Set, Reset }; Gpio(); virtual ~Gpio(); void init(GPIO_TypeDef *gpioPort, uint16_t gpioPin); void write(State state); State read(); void toggle(); private: GPIO_TypeDef *port; uint16_t pin; }; #endif /* GPIO_GPIO_H_ */
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2022 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "constants.h" #include "Timer.h" #include "Gpio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ TIM_HandleTypeDef htim2; TIM_HandleTypeDef htim3; /* USER CODE BEGIN PV */ Timer timer; Gpio output; Gpio input; Timer extraTimer; uint32_t extraTimerCounter = 0; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM2_Init(void); static void MX_TIM3_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ uint32_t ramVectorTable[constants::isrVectorTableSize] __attribute__(( aligned (constants::isrVectorTableAlingment) )); extern uint32_t g_pfnVectors[constants::isrVectorTableSize]; /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ for (uint16_t i = 0; i < constants::isrVectorTableSize; i++) { ramVectorTable[i] = g_pfnVectors[i]; } __disable_irq(); SCB->VTOR = (uint32_t)&ramVectorTable; __DSB(); __enable_irq(); PeripheralUnit::setIsrVectorTable(ramVectorTable); /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM2_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ output.init(GPIOC, GPIO_PIN_13); input.init(GPIOA, GPIO_PIN_3); timer.init(&htim2, TIM2_IRQn); timer.start(Timer::Mode::Irq); extraTimer.init(&htim3, TIM3_IRQn); extraTimer.start(Timer::Mode::Irq); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // Handle TIM2 interrupt bool interruptOccured = timer.getInterruptFlag(); if (interruptOccured == true) { if (input.read() == Gpio::State::Set) { output.toggle(); } timer.clearInterruptFlag(); } // Handle TIM3 interrupt interruptOccured = extraTimer.getInterruptFlag(); if (interruptOccured == true) { extraTimerCounter++; extraTimer.clearInterruptFlag(); } } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 7199; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 5000; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ } /** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 719; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 10000; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); /*Configure GPIO pin : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /*Configure GPIO pin : PA3 */ GPIO_InitStruct.Pin = GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */
Ссылка на проект: MT_STM32_CppDemoProject.
Вот это да. Не успею я достичь такого уровня программирования.
Сдохну раньше.
По нормальному посмотреть не успел.
Но вопросы появились.
Вот эта часть:
Насколько я понимаю, это обработчик прерывания.
А кроме стандартных операций я могу здесь указать какую либо функцию, которая находится в main.c, что бы она выполнилась или нет?
Если нет, а есть возможность изменить только флаг, придётся делать автомат, что то же неплохо. Нужно же когда то это изучить.
Это обычная функция, внутри нее можно любые действия предпринимать, как и в любой другой функции.
Ну всё.
Вот теперь я развернусь.
=)
Теперь всё, что я делал до этого, переписывать придётся.
И всё будет сильно зависимо от платформы.
Хотя не факт.
Да уж. Код двух базовых классов явно просится на построчное комментирование ... А то думаю для многих это будет статья из разряда чоень интересно, но ничего не понятно...
Я о таком методе читал.
Но знаний понять не хватило.
С рабочим примером проще разобраться будет.
Не теоретик я.
А как обстоят дела с наследованием? Ещё интересно, как при таком подходе обрабатывать прерывания от двух устройств, работающих в связке, например, UART+DMA как вот тут:
////////////////////////////////////////////////////////////////////////////////
void MB_RTU_UART::IRQ_Handler( IRQn_Type _irq )
{
if( _irq == IRQn_tx )
{
// Обработка прерывания от потока ПДП передатчика.
.....
}
else if( _irq == IRQn_rx )
{
// Обработка прерывания от потока ПДП приёмника.
......
}
else if( _irq == usart_IRQn )
{
// Обработка прерывания от передатчика.
//----------------------------------------------------------------------
uint32
usart_isr = usart -> ISR;
if( usart_isr & USART_ISR_TC )
{
.....
}
// Обработка прерывания от приёмника.
//----------------------------------------------------------------------
if( usart_isr & USART_ISR_RTOF )
{
....
}
}
else
{
// Чушь какая-то. Поймали прерывание, на которое не подписывались.
// Занавес!
abort();
}
}
Ну чисто навскидку, класс ModbusRtu добавить с объектами Uart/Dma, в нем же обслуживать прерывания и того, и того. А, в целом много вариантов может быть разных )
Собрал таймер на этой основе. При срабатывании прерывания вываливается в usage_fault
Надо отладчиком отследить этот момент. А вообще у нас просто конкретный вопрос возник, как прерывания непосредственно в реализации класса обрабатывать, я и опубликовал решение. А в целом, если ставить задачу библиотеки написать полноценные, то там конечно надо все продумывать, скорее всего я бы другую концепцию реализовывал.
посмотрел в таблицу векторов там вписан адрес generatedStaticFunction(). Получается она не правильный адрес отдает.
Ну, концепция, как раз, и понравилась.
Я в отпуске сейчас, как вернусь смогу глянуть проект.
ок. Если сделать один таймер, то usag fault. Если два, то hard fault.
С базовыми таймерам есть пример на этой библиотеке.
Он рабочий.
У меня F205. Видимо напортачил при переносе.
У меня нет такого.
А тактирование заработало?
И какой чип конкретно?
Да работает. Стартую таймер и через положенное время usagefault. Насколько выставлю, так и вылетает.
stm32f205rct
Комменты за сегодняшний день в результате неравной борьбы с сервером утеряны безвозвратно... Можешь продублировать плз, как проблема решилась и по результатам. Спасибо заранее!
Проблемы была как обычно в копипасте. Всё заработало. Только библиотека vector сразу накинула полтос киллограмм. Сейчас протокол связи с усб допилю и продолжу эксперементы молодого ученого.
Странно. У меня килобайт 12 накидывалось.
12К- жуть!
При моём подходе требуется (4*количество_прерываний) байт в ОЗУ и даже не знаю сколько байт в ПЗУ для функции
Ещё заметьте, что использование класса <vector> тянет у вас вызовы new().
Кстати, попробуйте внести изменения в расположение выводов периферии у МК и запустить перестроение проекта. Вы узнаете кое-что интересное об отношении Куба к файлам с расширением "cpp", содержащих код Куба, а заодно позанимаетесь извращением. Решение есть, но тогда придётся попрощаться с кодогенератором Куба.
Вобщем, подтверждается мысль, что скрещивание бульдога с носорогом порождает монстра. Посему или угомонитесь и играйте на Си по правилам Куба, или выбрасывайте КАЛ и пишите на С++, используя регистры периферии.
Вот Эдуард как раз и занимается, у меня ни мотивации, ни желания нет )
Ну да, с точки зрения затрат памяти тут неслабо
Ещё нюанс, который остался вами неосознан. Я сразу создаю массив с указателями на экземпляры классов, имеющих виртуальный метод-обработчик прерывания. Статически. Для чего у вас нужен <vector>? Для хранения связанного списка, скорей всего, двухсвязного, с адресами классов, имеющих обработчики прерываний. То есть в итоге вы получаете, как минимум, дополнительное удвоение потребления ОЗУ в сравнении с моим подходом. И это только то, что лежит на поверхности. Сколько и как использует <vector> ОЗУ вообще и хип в частности ваще то ещё приключение. А ведь есть ещё многозадачность, и как ваша концепция согласуется с ней, а? Получается, так себе концепция?
А можно Ваш вариант полностью?
Для какой нибудь периферии.
Что бы откомпилить и посмотреть что к чему.
А тема то не отпускает!
Так библиотеки доделывать нужно. А проблема до конца не решена.
Подход был показан как КОНЦЕПЦИЯ! А не конечное решение. С кубом вполне можно взаимодействовать, если принять его правила. Никакой тюнинг не сделает из городского такси болид формулы 1. Концепции разные. Не нравиться куба, не используйте. Вот вы предложили, а мы подумаем. Может это ещё лучшее. Для того тут и собираемся.
Золотые слова! К сожалению, зачастую открываешь среднестатистическую тему на любом форуме в рунете - 95 процентов сообщений о том, как плох HAL, 5% - по сути вопроса) это ещё довольно радужная картина, если 5% по делу
Я не против HAL. Я им даже пользуюсь иногда.
Просто я хочу уйти от него.
Мне так удобнее.
Сейчас я комбинирую библиотеки, что уже написаны и CMSIS, где на библиотеке не выедешь.
Так это показатель роста, когда перерастаешь стартовый набор. Ну и "Слабо без HAL?" никто не отменял. Ещё бы времени на это всё...
Я не против и не за HAL, зависит от задачи и других вводных. Просто смысл строчить бесконечно на форумах критику HAL, тратя на это все свое время, которое невосполнимо. И по итогу новички вместо ответа на свой конкретный вопрос, получают тонну желчи бестолковой. Я глобально против этого, поэтому и сделал свой форум, без этой хе*ни.
HAL медленна, неоптимальна и избыточна, но она на это изначально и не претендовала. Про болид божественное сравнение ) HAL - такси.
Так это ещё и БЕСПЛАТНОЕ ТАКСИ!
😀
С тобой то все как раз понятно) вместо критики пустой, ты делаешь свой продукт, это похвально с любой стороны.
В продвижение статьи. Сделал CAN мост на данном принципе с засыпанием и пробуждением, индикацией работы. На 500к не пробовал, но на сотке работает так же как и халовский, на пользоваться на порядок удобней. Если ещё в c++ углубиться вообще было бы зашибись. Сейчас винегрет получился. Единственная проблема, если подключаю халовский юсб вылетаю в дефаулт хендлер.
Отлично! А вылетать может из-за нехватки памяти.
Кстати, не хочешь в сообществе статью опубликовать со своей реализацией?
Пока не готов. Ещё тестирую. Сначала сделал класс BasicTimer с расчетом под любой таймер на 205 камень. Таймеры работают без проблем. Для пробы включил десяток. Все работают. Но без юсб. Юсб оставил халовский как есть . Без перемещения в раму таблицы векторов юсб работает. С перемещением - вылет в дефолт. Что с таймерами , что без них. Естественно не использую таймеры с двойными прерываниями - 3 из 14.
По стеку вызовов непонятно, откуда в default() вылетает?
Время будет попытаюсь.
Да и не понятно, что с двойными прерываниями делать.
Проверяешь, было ли установлено прерывание до тебя, если нет, сажаешь свой обработчик.
Если да, запоминаешь вектор, ставишь свой, обрабатываешь свою часть, затем передаёшь управление запомненному.
Таким образом можно построить практически бесконечные цепочки. Лишь бы памяти хватило.
Так вся прелесть метода теряется.
class SomeClass(IRQ_n irq);
SomeClass.SetCallback(void (*callback(void* callbackObj));
SomeClass.EnableIrq();
Всё изолированно.
А так придется interrupt выносить в отдельный самостоятельный класс. Не айс.
Я не совсем то имел в виду.
Но с Вашим вариантом то же обдумать нужно.
Я просто мыслю ещё старыми категориями. Со времён 8086 и ассемблера.
;-(
Таки и я. Перерывчик был небольшой. Лет 30. Пытаюсь наверстать.
У меня складывается ощущение, что программированием занимаются люди старой школы.
Молодёжь больше стремится к свистопеределкам на Ардуино.
Как бы эти прерывания добить до конца.
Библиотеки копятся. А прерывания так и не доделаны.
Меня , пока , устраивает как сейчас. Проект нужно добить. Осталось SPI и флешку через него.
Даа. Тяжело быть деревянным. Однако побороть юсб так и не удалось. После инициализации юсб при срабатывании OTG_FS_IRQn камень уходит в дефолт хендлер. Собствено всё содранно со статьи за одним исключением. У меня есть IntrruptUnit который является наследником PeripheralUnit.
Собственно у, скажем, модуля кан , несколько InterruptUnit:
RxFifo0Interrupt* pRx0Interrupt;
RxFifo1Interrupt* pRx1Interrupt;
SceInterrupt* pSceInterrupt;
TxInterrupt* pTxInterrupt;
В них инициализация, контроль, запуск останов, регистрация и вызов калбеков.
В юсб один InterruptUnit UsbOtgFsInterrupt. Кан прекрасно работает, а юсб валиться в дефолт.
Причем, если всё вернуть к халовской реализации и просто поднять обработчики в раму, происходит то же самое.
Добрый день!
А ты CAN и USB попеременно пробуешь? Насколько я помню, у STM одновременная работа CAN и USB невозможна.
на 205 можно. Последний раз голый проект с юсб. Без переноса в рам работает, после переноса default_handler. В SCB висит OTG_FS_IRQn.
Что-то у меня платы все разбрелись куда-то, если есть под f103 или f411 проект с воспроизводимой проблемой, могу попробовать.
Flash Spi сделал без прерываний, так что протестить interrupt spi не удалось.
Всем здравствуйте! Хотел бы найти ответ на свой вопрос. Есть устройство, которое должно быть надежным даже в случае отказа, возникновения ошибок в программе (зависание STM32). Мысль такая, если возникает ошибка, заходим в обработчик исключения, в нем отключаем мотор и все что нужно. На первое время, пока с моими знаниями, хотя бы так. Что дальше будет, для меня пока не важно - главное отключить мотор. Я не могу зайти в обработчики (ни в какие) исключений. Что я для этого сделал:
1) Включил глобальные прерывания: __enable_irq();
2) Разрешил их как обычные прерывания: NVIC_EnableIRQ(NonMaskableInt_IRQn);
NVIC_EnableIRQ(HardFault_IRQn);
NVIC_EnableIRQ(MemoryManagement_IRQn);
NVIC_EnableIRQ(BusFault_IRQn);
NVIC_EnableIRQ(UsageFault_IRQn);
NVIC_EnableIRQ(SVCall_IRQn);
NVIC_EnableIRQ(DebugMonitor_IRQn);
NVIC_EnableIRQ(PendSV_IRQn);
NVIC_EnableIRQ(SysTick_IRQn);
3) Переоределил сами обработчики:
extern "C" void HardFault_Handler(void)
{ GPIOB->BSRR |= GPIO_BSRR_BR_5; // выключаем мотор }
ну и так во всех, что есть:
NonMaskableInt_IRQn
HardFault_IRQn
MemoryManagement_IRQn
BusFault_IRQn
UsageFault_IRQn
SVCall_IRQn
DebugMonitor_IRQn
PendSV_IRQn
SysTick_IRQn
4) искусственно создаю ситуацию появления ошибки. Или обработчик, например Таймера, не переопределю, а он вызывается по переполнению, или такой командой: __asm("svc 0");
5) уже под отладчиком я регистр NVIC_ISER0 устанавливаю в 0xFFFFFFFF, что все прерывания разрешить системные (хотя здесь я не уверен что я правильно настраиваю)
Но при созданной ситуации "STMка зависла", я не попадаю ни в один из определённых обработчиков исключений. А попадаю в цикл всем известный Loop
.section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
b Infinite_Loop
.size Default_Handler, .-Default_Handler
Что я сделал неправильно или что недоделал или в чем не прав и ошибаюсь?
Добрый день! А проект можете выложить?
В целом я проверил - у меня HardFault отрабатывает четко, генерируем исключение:
Попадаем в обработчик:
Спасибо! Для меня с помощью Вас и Эдуарда все разрешилось. Просьба удалить мой вопрос здесь. Я написал свой вопрос на форуме, вдруг там людям понадобятся. Что сам понял, могу ответь потом сам себе уже на форуме.
Да пусть тут тоже остается, а с форума можно ссылку сюда кинуть ) Тему опубликовал.
https://microtechnics.ru/community/stm32/ne-mogu-popast-v-obrabotchik-isklyucheniya/
Это по вопросу Алексея Байдина. Там разбор, что случилось на самом деле.