- Arm GNU Toolchain 13.3.Rel1 + Vscode + Embedded IDE plugin.
- Форматирование кода .clang-format Google.
- Стиль кода в основном опирается на "Руководство Google по стилю в C++" (Google C++ Style Guide: https://google.github.io/styleguide/cppguide.html).
- Не используются: макросы, выделение памяти, RTTI, исключения.
- Конфигурация периферии МК - полность из CubeMX.
- main.cpp - основной, main.c исключён из проекта (генерируется автоматически из CubeMX).
- Для упрощения RTOS не используется.
- Для уменьшения связности кода и абстракции от конкретного МК используются интерфейсы.
- Вместо наследования предпочитается композиция (за исключением интерфейсов).
- Основная логика работы устройства реализуется на конечных автоматах.
- Логические блоки строятся на неблокирующих операциях, без активного ожидания (по возможности). Это позволяет обрабатывать их как совместно в бесконечном цикле или прерывании, так и отдельно с применением RTOS.
- Для всех операций, которые используют активное ожидание необходимо добавлять остановку по таймауту исполнения.
- Вместо выделения памяти в куче используется
std::array<uint8_t>в качестве буфера на стеке с дальнейшей передачей черезstd::span<uint8_t>в соответствующий класс. В редких случаях для стандартных контейнеров используется аллокатор на стекеshort_alloc.hот Howard Hinnant.
- Все задержки, таймауты и т.д. базируются на основе классов
TimeUs,TimeMs, которые в свою очередь являются реализациями интерфейсовm::ifc::ITime<Us<uint32_t>>иm::ifc::ITime<Ms<uint32_t>>и предоставлют доступ к отсчёту времени с момента запуска МК в микросекундах и миллисекундах. Основаны на таймере TIM5 и SysTick. - Для работы с GPIO используется класс
PinWrapper, наследуемый отm::ifc::mcu::IPin. Предоставляет базовый функцииread(),wirte(),toggle(). - Для измерения температуры используется класс
Adc_1наследуемый отm::ifc::mcu::IAdcDmaCircularReader<uint16_t>и классNtc.Adc_1реализует запуск АЦП в цикличном DMA режиме с прерываниями при заполнении половины и полного буфера. По прерыванию в пользовательских коллбэках идёт фильтрация данных. КлассNtcнаследуется отm::ifc::ITempSense<std::optional<Celsius<float>>>иm::ifc::ITempSenseErrorи реализует пересчёт измеренного напряжения в сопротивление ntc термистора, а далее в температуру в градусах Цельсия. - Библиотечный класс
m::ic::PY25Q128HA, наследуемый от интерфейсаm::ifc::IMemory, реализует функции чтения\записи в память на основе микросхемы PY25Q128HA. Сам класс оперирует интерфейсом синхронного ввода выводаm::ifc::IIO_Sync<Ms<type>>, в данной реализации это классSpi_1, реализующий блокирующую передачу данных по SPI с применением DMA. - Приём и обработка команд по Modbus RTU реализуется в библиотечном классе
m::ModbusRtuProtocol<Us<uint32_t>>, при приёме соответствующих команды вызываются пользовательские коллбэки для их обработки. Приёмопередача пакетов осуществляется в классахUsart_5&Usart_4, наследуемых отm::ifc::IIO_Asyncи реализующих асинхронный приём и передачу байт c использованием DMA. Для разделение байт в шине на пакеты используется классm::DataLinkAsync<Us<uint32_t>>, наследуемый от интерфейсаm::ifc::IDataLinkи реализующий отделение пакетов на основе временных задержек. - Вместо RTOS используется прерывание с частотой 1кГц, в котором вызывается обработка команд Modbus RTU. Для этого используется класс
Tim_7_1kHzнаследуемый отm::ifc::mcu::IItи реализующий вызов пользовательского коллбэка при срабатывании прерывания. - Конечный автомат и некоторая другая логика работают в основном бесконечном цикле.
- Для логирования по USART используется библиотечный класс
m::IIO_AsyncLog, работающий с классомUsart_1. - Для определения мест и последовательности возникновения ошибок используется библиотечный класс
m::SimpleErrorTracer<uint16_t, 100>.
- Для управления драйвером шагового двигателем необходимо сгенерировать STEP сигнал, который представляет из себя меандр с изменяющейся частотой и строго определённым количеством периодов.
- Задача написать интерфейс и реализацию класса для выполнения этой задачи.
struct Packet{
uint8_t n;
uint16_t freq; // 100Hz - 20'000 Hz
};
У интерфейса должны быть методы для установки сигнала в виде std::span<Packet>, запуска генерации, остановки генерации, определения текущего состояния (идёт генерация или нет).
Также должен быть метод возвращающий максимальный поддерживаемый размер генерируемого сигнала.
Для упрощения структура сигнала представляет массив с количеством импульсов и их частотой.
- Максимальный размер массива сигнала можно ограничить 10 элементами.
- Между пачками меандров разной частоты не должно быть дополнительных задержек.
- Сгенерировать весь сигнал целиком необходимо полностью аппаратно, без участия CPU на выходе PA8 МК.
- При запуске генератора с такими параметрами:
std::array<Packet,3> signal{{{2, 1000},{3, 500},{2, 2000}}};
generator.add(signal);
generator.start();
На выходе должен получиться меандр с общим количеством импульсов 7 штук, где сперва идут 2 с частотой 1000 Гц, затем 3 с частотой 500 Гц и 2 с частотой 2000 Гц.
- Если невозможно сгенерировать определённую частоту, то берётся ближайшая поддерживаемая.