Top.Mail.Ru

STM32 и C++. Мой вариант архитектуры, обработка прерываний.

Всех рад снова приветствовать 🤝 Думаю многие читали статьи Эдуарда в Сообществе, посвященные созданию набора библиотек для работы с микроконтроллерами STM32 на C++. Собственно, на этой почве у Эдуарда  и возник вопрос, как максимально гармонично вписать обработку прерываний, поэтому я сегодня опишу на демо-примере мою концепцию реализации.

И поскольку речь пойдет именно об общей идее, то я не буду убирать HAL и т. д., не вижу особого смысла в данном случае. При желании соответствующие вызовы библиотеки без проблем заменяются на аналогичные манипуляции напрямую с регистрами контроллера.

Я набросаю базовый функционал для пары периферийных модулей, пусть будет Timer и GPIO, чего вполне хватит для наглядной демонстрации. Глобально я не просчитывал всевозможные варианты, буду реализовывать ту идею, которая у меня сформировалась сразу после прочтения темы на форуме, так что не исключено, что в будущем архитектуру нужно будет подправить (в случае желания развить проект дальше). А теперь переходим к сути, которую я буду разбивать на отдельно взятые этапы.

Перенос таблицы векторов прерываний в RAM.

Рассмотрим в двух словах, как организована обработка прерываний по умолчанию. Я далее по тексту буду использовать контроллер семейства STM32F10x, для других названия файлов будут незначительно отличаться. Итак, в файле startup_stm32f103c8tx.s в g_pfnVectors определены точки входа в конкретные функции, вызывающиеся при возникновении того или иного прерывания.

Таким образом, для добавления своего кода в тот или иной обработчик прерываний, присутствующий в startup_stm32f103c8tx.s, функции переопределяются в пользовательском коде, именно так и происходит при генерации проекта через STM32CubeMx. Я же хочу дефолтные обработчики заменить своими, из чего вытекает необходимость создать свою таблицу векторов прерываний в RAM и поместить ее адрес в регистр SCB->VTOR. Так, разбираем пошагово идею:

  1. Создаем свою кастомную таблицу векторов прерываний, физически это означает определение массива. Поместим в 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:

Структура проекта для STM32.

При этом все эти классы наследуют от базового 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. Естественно, необходимо, чтобы каждый из объектов имел свой обработчик прерывания, поскольку физически именно так и есть:

Timer interrupt.

Для статической же функции получим:

Использование static-функции.

По существу, это ничем не лучше использования 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" непосредственно после выбора целевого контроллера:

С++ проект в STM32CubeIDE.

В STM32CubeMx активируем необходимую периферию, у меня будут два порта ввода-вывода – один на вход (PA3), другой на выход (PC13):

GPIO.

На PA3 повесим кнопку, на PC13 светодиод, как же без него. И парочка таймеров, TIM2:

Timer configuration.

TIM3:

TIM3 settings.

Тактирование:

Настройки тактирования.

Из данной конфигурации вытекает тот факт, что таймер 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.

Подписаться
Уведомить о
guest

39 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Эдуард
4 месяцев назад

Вот это да. Не успею я достичь такого уровня программирования.
Сдохну раньше.

По нормальному посмотреть не успел.
Но вопросы появились.
Вот эта часть:

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;
    }
  }
}

Насколько я понимаю, это обработчик прерывания.
А кроме стандартных операций я могу здесь указать какую либо функцию, которая находится в main.c, что бы она выполнилась или нет?
Если нет, а есть возможность изменить только флаг, придётся делать автомат, что то же неплохо. Нужно же когда то это изучить.

Эдуард
Ответ на комментарий  Aveal
4 месяцев назад

Ну всё.
Вот теперь я развернусь.

Эдуард
Ответ на комментарий  Aveal
4 месяцев назад

Теперь всё, что я делал до этого, переписывать придётся.
И всё будет сильно зависимо от платформы.
Хотя не факт.

KIR
KIR
Ответ на комментарий  Эдуард
4 месяцев назад

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

Эдуард
Ответ на комментарий  KIR
4 месяцев назад

Я о таком методе читал.
Но знаний понять не хватило.
С рабочим примером проще разобраться будет.
Не теоретик я.

tonyk
tonyk
4 месяцев назад

А как обстоят дела с наследованием? Ещё интересно, как при таком подходе обрабатывать прерывания от двух устройств, работающих в связке, например, 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();
   }
}

antonmai
2 месяцев назад

Собрал таймер на этой основе. При срабатывании прерывания вываливается в usage_fault

antonmai
Ответ на комментарий  Aveal
2 месяцев назад

посмотрел в таблицу векторов там вписан адрес generatedStaticFunction(). Получается она не правильный адрес отдает.

antonmai
Ответ на комментарий  Aveal
2 месяцев назад

Ну, концепция, как раз, и понравилась.

antonmai
Ответ на комментарий  Aveal
2 месяцев назад

ок. Если сделать один таймер, то usag fault. Если два, то hard fault.

Эдуард
Ответ на комментарий  antonmai
2 месяцев назад

С базовыми таймерам есть пример на этой библиотеке.
Он рабочий.

antonmai
Ответ на комментарий  Эдуард
2 месяцев назад

У меня F205. Видимо напортачил при переносе.

Эдуард
Ответ на комментарий  antonmai
2 месяцев назад

У меня нет такого.
А тактирование заработало?
И какой чип конкретно?

antonmai
Ответ на комментарий  Эдуард
2 месяцев назад

Да работает. Стартую таймер и через положенное время usagefault. Насколько выставлю, так и вылетает.

antonmai
Ответ на комментарий  Эдуард
2 месяцев назад

stm32f205rct

antonmai
antonmai
Ответ на комментарий  Aveal
2 месяцев назад

Проблемы была как обычно в копипасте. Всё заработало. Только библиотека vector сразу накинула полтос киллограмм. Сейчас протокол связи с усб допилю и продолжу эксперементы молодого ученого.

Эдуард
Ответ на комментарий  antonmai
2 месяцев назад

Странно. У меня килобайт 12 накидывалось.

tonyk
tonyk
Ответ на комментарий  Эдуард
2 месяцев назад

12К- жуть!
При моём подходе требуется (4*количество_прерываний) байт в ОЗУ и даже не знаю сколько байт в ПЗУ для функции

void CPP_caller( void )
{
    IRQn_Type
        irqn = IRQ::getActiveIRQn();

    if( CPP_handler[ irqn ] )
    {
        (( IRQ* )CPP_handler[ irqn ]) -> IRQ_Handler();
    }
    else
    {
        abort();
    }
}

Ещё заметьте, что использование класса <vector> тянет у вас вызовы new().

Кстати, попробуйте внести изменения в расположение выводов периферии у МК и запустить перестроение проекта. Вы узнаете кое-что интересное об отношении Куба к файлам с расширением "cpp", содержащих код Куба, а заодно позанимаетесь извращением. Решение есть, но тогда придётся попрощаться с кодогенератором Куба.

Вобщем, подтверждается мысль, что скрещивание бульдога с носорогом порождает монстра. Посему или угомонитесь и играйте на Си по правилам Куба, или выбрасывайте КАЛ и пишите на С++, используя регистры периферии.

tonyk
tonyk
Ответ на комментарий  Aveal
2 месяцев назад

Ещё нюанс, который остался вами неосознан. Я сразу создаю массив с указателями на экземпляры классов, имеющих виртуальный метод-обработчик прерывания. Статически. Для чего у вас нужен <vector>? Для хранения связанного списка, скорей всего, двухсвязного, с адресами классов, имеющих обработчики прерываний. То есть в итоге вы получаете, как минимум, дополнительное удвоение потребления ОЗУ в сравнении с моим подходом. И это только то, что лежит на поверхности. Сколько и как использует <vector> ОЗУ вообще и хип в частности ваще то ещё приключение. А ведь есть ещё многозадачность, и как ваша концепция согласуется с ней, а? Получается, так себе концепция?

Эдуард
Ответ на комментарий  tonyk
2 месяцев назад

А можно Ваш вариант полностью?
Для какой нибудь периферии.
Что бы откомпилить и посмотреть что к чему.

antonmai
antonmai
Ответ на комментарий  Эдуард
2 месяцев назад

А тема то не отпускает!

Эдуард
Ответ на комментарий  antonmai
2 месяцев назад

Так библиотеки доделывать нужно. А проблема до конца не решена.

antonmai
antonmai
Ответ на комментарий  tonyk
2 месяцев назад

Подход был показан как КОНЦЕПЦИЯ! А не конечное решение. С кубом вполне можно взаимодействовать, если принять его правила. Никакой тюнинг не сделает из городского такси болид формулы 1. Концепции разные. Не нравиться куба, не используйте. Вот вы предложили, а мы подумаем. Может это ещё лучшее. Для того тут и собираемся.

Эдуард
Ответ на комментарий  Aveal
2 месяцев назад

Я не против HAL. Я им даже пользуюсь иногда.
Просто я хочу уйти от него.
Мне так удобнее.
Сейчас я комбинирую библиотеки, что уже написаны и CMSIS, где на библиотеке не выедешь.

antonmai
antonmai
Ответ на комментарий  Эдуард
2 месяцев назад

Так это показатель роста, когда перерастаешь стартовый набор. Ну и "Слабо без HAL?" никто не отменял. Ещё бы времени на это всё...

antonmai
antonmai
Ответ на комментарий  Aveal
2 месяцев назад

Так это ещё и БЕСПЛАТНОЕ ТАКСИ!

39
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x