Всех рад снова приветствовать 🤝 Думаю многие читали статьи Эдуарда в Сообществе, посвященные созданию набора библиотек для работы с микроконтроллерами 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.