[C++, Программирование микроконтроллеров] Попытка использовать современный C++ и паттерны проектирования для программирования микроконтроллеров
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет!
Проблема использования С++ в микроконтроллерах терзала меня довольно долгое время. Дело было в том, что я искренне не понимал, как этот объектно ориентированный язык может быть применим к встраиваем системам. Я имею ввиду, как выделять классы и на базе чего составлять объекты, то есть как именно применять этот язык правильно. Спустя некоторое время и прочтения n-ого количества литературы, я пришёл к кое каким результатам, о чем и хочу поведать в этой статье. Имеют ли какую либо ценность эти результаты или нет — остается на суд читателя. Мне будет очень интересно почитать критику к моему подходу, чтобы наконец ответить себе на вопрос: «Как же правильно использовать C++ при программировании микроконтроллеров?».
Предупреждаю, в статье будет много исходного кода.
В этой статье, я, на примере использования USART в МК stm32 для связи с esp8266 постараюсь изложить свой подход и его основные преимущества. Начнем с того, что главное преимущество использование C++ для меня — это возможность сделать аппаратную развязку, т.е. сделать использование модулей верхнего уровня независимым от аппаратной платформы. Это будет вытекать в то, что система станет легко модифицирована при каких либо изменениях. Для этого я выделил три уровня абстракции системы:
- HW_USART — аппаратный уровень, зависит от платформы
- MW_USART — средний уровень, служит для развязки первого и третьего уровней
- APP_ESP8266 — уровень приложения, ничего не знает о МК
HW_USART
Самый примитивный уровень. Я использовал камень stm32f411, USART №2, также выполнил поддержку DMA. Интерфейс реализован в виде всего трех функций: инициализировать, отправить, получить.
Функция инициализации выглядит следующим образом:
bool usart2_init(uint32_t baud_rate)
{
bool res = false;
/*-------------GPIOA Enable, PA2-TX/PA3-RX ------------*/
BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN) = true;
/*----------GPIOA set-------------*/
GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1);
GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3);
constexpr uint32_t USART_AF_TX = (7 << 8);
constexpr uint32_t USART_AF_RX = (7 << 12);
GPIOA->AFR[0] |= (USART_AF_TX | USART_AF_RX);
/*!---------------USART2 Enable------------>!*/
BIT_BAND_PER(RCC->APB1ENR, RCC_APB1ENR_USART2EN) = true;
/*-------------USART CONFIG------------*/
USART2->CR3 |= (USART_CR3_DMAT | USART_CR3_DMAR);
USART2->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);
USART2->BRR = (24000000UL + (baud_rate >> 1))/baud_rate; //Current clocking for APB1
/*-------------DMA for USART Enable------------*/
BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN) = true;
/*-----------------Transmit DMA--------------------*/
DMA1_Stream6->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));
DMA1_Stream6->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.tx));
DMA1_Stream6->CR = (DMA_SxCR_CHSEL_2| DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC | DMA_SxCR_DIR_0);
/*-----------------Receive DMA--------------------*/
DMA1_Stream5->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));
DMA1_Stream5->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.rx));
DMA1_Stream5->CR = (DMA_SxCR_CHSEL_2 | DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC);
DMA1_Stream5->NDTR = MAX_UINT16_T;
BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;
return res;
}
Особенного в функции ничего нету, кроме разве что того, что я использую битовые маски для уменьшения результирующего кода.
Тогда функция отправки выглядит следующим образом:
bool usart2_write(const uint8_t* buf, uint16_t len)
{
bool res = false;
static bool first_attempt = true;
/*!<-----Copy data to DMA USART TX buffer----->!*/
memcpy(usart2_buf.tx, buf, len);
if(!first_attempt)
{
/*!<-----Checking copmletion of previous transfer------->!*/
while(!(DMA1->HISR & DMA_HISR_TCIF6)) continue;
BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF6) = true;
}
first_attempt = false;
/*!<------Sending data to DMA------->!*/
BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = false;
DMA1_Stream6->NDTR = len;
BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = true;
return res;
}
В функции есть костыль, в виде переменной first_attempt, которая помогает определить самая ли первая это отправка по DMA или нет. Зачем это нужно? Дело в том, что проверку о том, успешна ли предыдущая отправка в DMA или нет я сделал ДО отправки, а не ПОСЛЕ. Сделал я так, чтобы после отправки данных не тупо ждать её завершения, а выполнять полезный код в это время.
Тогда функция приема выглядит следующим образом:
uint16_t usart2_read(uint8_t* buf)
{
uint16_t len = 0;
constexpr uint16_t BYTES_MAX = MAX_UINT16_T; //MAX Bytes in DMA buffer
/*!<---------Waiting until line become IDLE----------->!*/
if(!(USART2->SR & USART_SR_IDLE)) return len;
/*!<--------Clean the IDLE status bit------->!*/
USART2->DR;
/*!<------Refresh the receive DMA buffer------->!*/
BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = false;
len = BYTES_MAX - (DMA1_Stream5->NDTR);
memcpy(buf, usart2_buf.rx, len);
DMA1_Stream5->NDTR = BYTES_MAX;
BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF5) = true;
BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;
return len;
}
Особенностью этой функции является то, что мне заранее не известно сколько байт я должен получить. Для индикации полученных данных я проверяю флаг IDLE, затем, если состояние IDLE зафиксировано, чищу флаг и читаю данные из буфера. Если же состояние IDLE не зафиксировано, то функция просто возвращает нуль, то есть отсутствие данных.
На этом предлагаю закончить с низким уровнем и перейти непосредственно к C++ и паттернам.
MW_USART
Здесь я реализовал базовый абстрактный класс USART и применил паттерн «прототип» для создания наследников (конкретных классов USART1 и USART2). Я не буду описывать реализацию паттерна прототип, так как его можно найти по первой ссылке в гугле, а сразу приведу исходный код, и пояснения приведу ниже.
#pragma once
#include <stdint.h>
#include <vector>
#include <map>
/*!<========Enumeration of USART=======>!*/
enum class USART_NUMBER : uint8_t
{
_1,
_2
};
class USART; //declaration of basic USART class
using usart_registry = std::map<USART_NUMBER, USART*>;
/*!<=========Registry of prototypes=========>!*/
extern usart_registry _instance; //Global variable - IAR Crutch
#pragma inline=forced
static usart_registry& get_registry(void) { return _instance; }
/*!<=======Should be rewritten as========>!*/
/*
static usart_registry& get_registry(void)
{
usart_registry _instance;
return _instance;
}
*/
/*!<=========Basic USART classes==========>!*/
class USART
{
private:
protected:
static void add_prototype(USART_NUMBER num, USART* prot)
{
usart_registry& r = get_registry();
r[num] = prot;
}
static void remove_prototype(USART_NUMBER num)
{
usart_registry& r = get_registry();
r.erase(r.find(num));
}
public:
static USART* create_USART(USART_NUMBER num)
{
usart_registry& r = get_registry();
if(r.find(num) != r.end())
{
return r[num]->clone();
}
return nullptr;
}
virtual USART* clone(void) const = 0;
virtual ~USART(){}
virtual bool init(uint32_t baudrate) const = 0;
virtual bool send(const uint8_t* buf, uint16_t len) const = 0;
virtual uint16_t receive(uint8_t* buf) const = 0;
};
/*!<=======Specific class USART 1==========>!*/
class USART_1 : public USART
{
private:
static USART_1 _prototype;
USART_1()
{
add_prototype( USART_NUMBER::_1, this);
}
public:
virtual USART* clone(void) const override final
{
return new USART_1;
}
virtual bool init(uint32_t baudrate) const override final;
virtual bool send(const uint8_t* buf, uint16_t len) const override final;
virtual uint16_t receive(uint8_t* buf) const override final;
};
/*!<=======Specific class USART 2==========>!*/
class USART_2 : public USART
{
private:
static USART_2 _prototype;
USART_2()
{
add_prototype( USART_NUMBER::_2, this);
}
public:
virtual USART* clone(void) const override final
{
return new USART_2;
}
virtual bool init(uint32_t baudrate) const override final;
virtual bool send(const uint8_t* buf, uint16_t len) const override final;
virtual uint16_t receive(uint8_t* buf) const override final;
};
Сначала файла идёт перечисление enum class USART_NUMBER со всеми доступными USART, для моего камня их всего два. Затем идёт опережающее объявление базового класса class USART. Далее идёт объявление контейнер а всех прототипов std::map<USART_NUMBER, USART*> и его реестра, который реализован в виде синглтона Мэйерса.
Тут я напоролся на особенность IAR ARM, а именно то, что он инициализирует статические переменные два раза, в начале программы и непосредственно при входе в main. Поэтому я несколько переписал синглтон, заменив статическую переменную _instance на глобальную. То, как это выглядит в идеале, описано в комментарии.
Далее объявлен базовый класс USART, где определены методы добавления прототипа, удаления прототипа, а также создания объекта(так как конструктор классов наследников объявлен как приватный, для ограничения доступа).
Также объявлен чисто виртуальный метод clone, и чисто виртуальные методы инициализации, отправки и получения.
После всего лишь, мы наследуем конкретные классы, где определяем чисто виртуальные методы, описанные выше.
Код определения методов привожу ниже:
#include "MW_USART.h"
#include "HW_USART.h"
usart_registry _instance; //Crutch for IAR
/*!<========Initialization of global static USART value==========>!*/
USART_1 USART_1::_prototype = USART_1();
USART_2 USART_2::_prototype = USART_2();
/*!<======================UART1 functions========================>!*/
bool USART_1::init(uint32_t baudrate) const
{
bool res = false;
//res = usart_init(USART1, baudrate); //Platform depending function
return res;
}
bool USART_1::send(const uint8_t* buf, uint16_t len) const
{
bool res = false;
return res;
}
uint16_t USART_1::receive(uint8_t* buf) const
{
uint16_t len = 0;
return len;
}
/*!<======================UART2 functions========================>!*/
bool USART_2::init(uint32_t baudrate) const
{
bool res = false;
res = usart2_init(baudrate); //Platform depending function
return res;
}
bool USART_2::send(const uint8_t* buf, const uint16_t len) const
{
bool res = false;
res = usart2_write(buf, len); //Platform depending function
return res;
}
uint16_t USART_2::receive(uint8_t* buf) const
{
uint16_t len = 0;
len = usart2_read(buf); //Platform depending function
return len;
}
Здесь реализованы методы НЕ пустышки только для USART2, так как его я и использую для общения с esp8266. Соответственно, наполнение может быть любое, также оно может быть реализовано с помощью указателей на функции, которые принимают свое значение исходя из текущего чипа.
Теперь же я предлагаю перейти к APP уровню и посмотреть, зачем же все это было нужно.
APP_ESP8266
Определяю базовый класс для ESP8266 по паттерну «одиночка». В нем определяю указатель на базовый класс USART*.
class ESP8266
{
private:
ESP8266(){}
ESP8266(const ESP8266& root) = delete;
ESP8266& operator=(const ESP8266&) = delete;
/*!<---------USART settings for ESP8266------->!*/
static constexpr auto USART_BAUDRATE = ESP8266_USART_BAUDRATE;
static constexpr USART_NUMBER ESP8266_USART_NUMBER = USART_NUMBER::_2;
USART* usart;
static constexpr uint8_t LAST_COMMAND_SIZE = 32;
char last_command[LAST_COMMAND_SIZE] = {0};
bool send(uint8_t const *buf, const uint16_t len = 0);
static constexpr uint8_t ANSWER_BUF_SIZE = 32;
uint8_t answer_buf[ANSWER_BUF_SIZE] = {0};
bool receive(uint8_t* buf);
bool waiting_answer(bool (ESP8266::*scan_line)(uint8_t *));
bool scan_ok(uint8_t * buf);
bool if_str_start_with(const char* str, uint8_t *buf);
public:
bool init(void);
static ESP8266& Instance()
{
static ESP8266 esp8266;
return esp8266;
}
};
Здесь же есть constexpr переменная, в которой и хранится номер используемого USART. Теперь для изменения номера USART нам достаточно только лишь поменять её значение! Связывание же происходит в функции инициализации:
bool ESP8266::init(void)
{
bool res = false;
usart = USART::create_USART(ESP8266_USART_NUMBER);
usart->init(USART_BAUDRATE);
const uint8_t* init_commands[] =
{
"AT",
"ATE0",
"AT+CWMODE=2",
"AT+CIPMUX=0",
"AT+CWSAP="Tortoise_assistant","00000000",5,0",
"AT+CIPMUX=1",
"AT+CIPSERVER=1,8888"
};
for(const auto &command: init_commands)
{
this->send(command);
while(this->waiting_answer(&ESP8266::scan_ok)) continue;
}
return res;
}
Строка usart = USART::create_USART(ESP8266_USART_NUMBER); связывает наш уровень приложения с конкретным USART модулем.
Вместо выводов, просто выражу надежду, что материал окажется кому-нибудь полезен. Спасибо за прочтение!
===========
Источник:
habr.com
===========
Похожие новости:
- [PostgreSQL, C++, Visual Studio, Разработка под Windows] Использование libpq в VisualStudio (Windows)
- [Тестирование IT-систем, Тестирование веб-сервисов, Тестирование мобильных приложений, Тестирование игр] Mind Map в помощь тестировщику
- [Habr, Программирование, Анализ и проектирование систем, Управление разработкой] Хабр — ума палата
- [Программирование микроконтроллеров, Производство и разработка электроники, DIY или Сделай сам, Игры и игровые приставки, Электроника для начинающих] STM32F429 + IL9341 = LVGL, DOOM1
- [C++, Системное программирование, Программирование микроконтроллеров] Запуск сложных C++ приложений на микроконтроллерах
- [Программирование микроконтроллеров, Компьютерное железо, DIY или Сделай сам] Raspberry Pi Pico на МК RP2040: начало и первые шаги. Что есть поесть за $4
- [Программирование, C++, Учебный процесс в IT, Карьера в IT-индустрии] C++ в Практикуме. Как обучить студентов плюсам, не отпугивая
- [Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Window surface (перевод)
- [Настройка Linux, *nix, Оболочки] Как «приручить» консоль, или 5 шагов к жизни с командной строкой
- [C++, C, Программирование микроконтроллеров] Реализация многозадачности на функциональных очередях (без RTOS)
Теги для поиска: #_c++, #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_s++ (с++), #_c++14, #_stm32, #_usart, #_dma, #_patterny_proektirovanija (паттерны проектирования), #_prototip (прототип), #_odinochka (одиночка), #_c++, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Проблема использования С++ в микроконтроллерах терзала меня довольно долгое время. Дело было в том, что я искренне не понимал, как этот объектно ориентированный язык может быть применим к встраиваем системам. Я имею ввиду, как выделять классы и на базе чего составлять объекты, то есть как именно применять этот язык правильно. Спустя некоторое время и прочтения n-ого количества литературы, я пришёл к кое каким результатам, о чем и хочу поведать в этой статье. Имеют ли какую либо ценность эти результаты или нет — остается на суд читателя. Мне будет очень интересно почитать критику к моему подходу, чтобы наконец ответить себе на вопрос: «Как же правильно использовать C++ при программировании микроконтроллеров?». Предупреждаю, в статье будет много исходного кода. В этой статье, я, на примере использования USART в МК stm32 для связи с esp8266 постараюсь изложить свой подход и его основные преимущества. Начнем с того, что главное преимущество использование C++ для меня — это возможность сделать аппаратную развязку, т.е. сделать использование модулей верхнего уровня независимым от аппаратной платформы. Это будет вытекать в то, что система станет легко модифицирована при каких либо изменениях. Для этого я выделил три уровня абстракции системы:
HW_USART Самый примитивный уровень. Я использовал камень stm32f411, USART №2, также выполнил поддержку DMA. Интерфейс реализован в виде всего трех функций: инициализировать, отправить, получить. Функция инициализации выглядит следующим образом: bool usart2_init(uint32_t baud_rate)
{ bool res = false; /*-------------GPIOA Enable, PA2-TX/PA3-RX ------------*/ BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN) = true; /*----------GPIOA set-------------*/ GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1); GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3); constexpr uint32_t USART_AF_TX = (7 << 8); constexpr uint32_t USART_AF_RX = (7 << 12); GPIOA->AFR[0] |= (USART_AF_TX | USART_AF_RX); /*!---------------USART2 Enable------------>!*/ BIT_BAND_PER(RCC->APB1ENR, RCC_APB1ENR_USART2EN) = true; /*-------------USART CONFIG------------*/ USART2->CR3 |= (USART_CR3_DMAT | USART_CR3_DMAR); USART2->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE); USART2->BRR = (24000000UL + (baud_rate >> 1))/baud_rate; //Current clocking for APB1 /*-------------DMA for USART Enable------------*/ BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN) = true; /*-----------------Transmit DMA--------------------*/ DMA1_Stream6->PAR = reinterpret_cast<uint32_t>(&(USART2->DR)); DMA1_Stream6->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.tx)); DMA1_Stream6->CR = (DMA_SxCR_CHSEL_2| DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC | DMA_SxCR_DIR_0); /*-----------------Receive DMA--------------------*/ DMA1_Stream5->PAR = reinterpret_cast<uint32_t>(&(USART2->DR)); DMA1_Stream5->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.rx)); DMA1_Stream5->CR = (DMA_SxCR_CHSEL_2 | DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC); DMA1_Stream5->NDTR = MAX_UINT16_T; BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true; return res; } Особенного в функции ничего нету, кроме разве что того, что я использую битовые маски для уменьшения результирующего кода. Тогда функция отправки выглядит следующим образом: bool usart2_write(const uint8_t* buf, uint16_t len)
{ bool res = false; static bool first_attempt = true; /*!<-----Copy data to DMA USART TX buffer----->!*/ memcpy(usart2_buf.tx, buf, len); if(!first_attempt) { /*!<-----Checking copmletion of previous transfer------->!*/ while(!(DMA1->HISR & DMA_HISR_TCIF6)) continue; BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF6) = true; } first_attempt = false; /*!<------Sending data to DMA------->!*/ BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = false; DMA1_Stream6->NDTR = len; BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = true; return res; } В функции есть костыль, в виде переменной first_attempt, которая помогает определить самая ли первая это отправка по DMA или нет. Зачем это нужно? Дело в том, что проверку о том, успешна ли предыдущая отправка в DMA или нет я сделал ДО отправки, а не ПОСЛЕ. Сделал я так, чтобы после отправки данных не тупо ждать её завершения, а выполнять полезный код в это время. Тогда функция приема выглядит следующим образом: uint16_t usart2_read(uint8_t* buf)
{ uint16_t len = 0; constexpr uint16_t BYTES_MAX = MAX_UINT16_T; //MAX Bytes in DMA buffer /*!<---------Waiting until line become IDLE----------->!*/ if(!(USART2->SR & USART_SR_IDLE)) return len; /*!<--------Clean the IDLE status bit------->!*/ USART2->DR; /*!<------Refresh the receive DMA buffer------->!*/ BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = false; len = BYTES_MAX - (DMA1_Stream5->NDTR); memcpy(buf, usart2_buf.rx, len); DMA1_Stream5->NDTR = BYTES_MAX; BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF5) = true; BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true; return len; } Особенностью этой функции является то, что мне заранее не известно сколько байт я должен получить. Для индикации полученных данных я проверяю флаг IDLE, затем, если состояние IDLE зафиксировано, чищу флаг и читаю данные из буфера. Если же состояние IDLE не зафиксировано, то функция просто возвращает нуль, то есть отсутствие данных. На этом предлагаю закончить с низким уровнем и перейти непосредственно к C++ и паттернам. MW_USART Здесь я реализовал базовый абстрактный класс USART и применил паттерн «прототип» для создания наследников (конкретных классов USART1 и USART2). Я не буду описывать реализацию паттерна прототип, так как его можно найти по первой ссылке в гугле, а сразу приведу исходный код, и пояснения приведу ниже. #pragma once
#include <stdint.h> #include <vector> #include <map> /*!<========Enumeration of USART=======>!*/ enum class USART_NUMBER : uint8_t { _1, _2 }; class USART; //declaration of basic USART class using usart_registry = std::map<USART_NUMBER, USART*>; /*!<=========Registry of prototypes=========>!*/ extern usart_registry _instance; //Global variable - IAR Crutch #pragma inline=forced static usart_registry& get_registry(void) { return _instance; } /*!<=======Should be rewritten as========>!*/ /* static usart_registry& get_registry(void) { usart_registry _instance; return _instance; } */ /*!<=========Basic USART classes==========>!*/ class USART { private: protected: static void add_prototype(USART_NUMBER num, USART* prot) { usart_registry& r = get_registry(); r[num] = prot; } static void remove_prototype(USART_NUMBER num) { usart_registry& r = get_registry(); r.erase(r.find(num)); } public: static USART* create_USART(USART_NUMBER num) { usart_registry& r = get_registry(); if(r.find(num) != r.end()) { return r[num]->clone(); } return nullptr; } virtual USART* clone(void) const = 0; virtual ~USART(){} virtual bool init(uint32_t baudrate) const = 0; virtual bool send(const uint8_t* buf, uint16_t len) const = 0; virtual uint16_t receive(uint8_t* buf) const = 0; }; /*!<=======Specific class USART 1==========>!*/ class USART_1 : public USART { private: static USART_1 _prototype; USART_1() { add_prototype( USART_NUMBER::_1, this); } public: virtual USART* clone(void) const override final { return new USART_1; } virtual bool init(uint32_t baudrate) const override final; virtual bool send(const uint8_t* buf, uint16_t len) const override final; virtual uint16_t receive(uint8_t* buf) const override final; }; /*!<=======Specific class USART 2==========>!*/ class USART_2 : public USART { private: static USART_2 _prototype; USART_2() { add_prototype( USART_NUMBER::_2, this); } public: virtual USART* clone(void) const override final { return new USART_2; } virtual bool init(uint32_t baudrate) const override final; virtual bool send(const uint8_t* buf, uint16_t len) const override final; virtual uint16_t receive(uint8_t* buf) const override final; }; Сначала файла идёт перечисление enum class USART_NUMBER со всеми доступными USART, для моего камня их всего два. Затем идёт опережающее объявление базового класса class USART. Далее идёт объявление контейнер а всех прототипов std::map<USART_NUMBER, USART*> и его реестра, который реализован в виде синглтона Мэйерса. Тут я напоролся на особенность IAR ARM, а именно то, что он инициализирует статические переменные два раза, в начале программы и непосредственно при входе в main. Поэтому я несколько переписал синглтон, заменив статическую переменную _instance на глобальную. То, как это выглядит в идеале, описано в комментарии. Далее объявлен базовый класс USART, где определены методы добавления прототипа, удаления прототипа, а также создания объекта(так как конструктор классов наследников объявлен как приватный, для ограничения доступа). Также объявлен чисто виртуальный метод clone, и чисто виртуальные методы инициализации, отправки и получения. После всего лишь, мы наследуем конкретные классы, где определяем чисто виртуальные методы, описанные выше. Код определения методов привожу ниже: #include "MW_USART.h"
#include "HW_USART.h" usart_registry _instance; //Crutch for IAR /*!<========Initialization of global static USART value==========>!*/ USART_1 USART_1::_prototype = USART_1(); USART_2 USART_2::_prototype = USART_2(); /*!<======================UART1 functions========================>!*/ bool USART_1::init(uint32_t baudrate) const { bool res = false; //res = usart_init(USART1, baudrate); //Platform depending function return res; } bool USART_1::send(const uint8_t* buf, uint16_t len) const { bool res = false; return res; } uint16_t USART_1::receive(uint8_t* buf) const { uint16_t len = 0; return len; } /*!<======================UART2 functions========================>!*/ bool USART_2::init(uint32_t baudrate) const { bool res = false; res = usart2_init(baudrate); //Platform depending function return res; } bool USART_2::send(const uint8_t* buf, const uint16_t len) const { bool res = false; res = usart2_write(buf, len); //Platform depending function return res; } uint16_t USART_2::receive(uint8_t* buf) const { uint16_t len = 0; len = usart2_read(buf); //Platform depending function return len; } Здесь реализованы методы НЕ пустышки только для USART2, так как его я и использую для общения с esp8266. Соответственно, наполнение может быть любое, также оно может быть реализовано с помощью указателей на функции, которые принимают свое значение исходя из текущего чипа. Теперь же я предлагаю перейти к APP уровню и посмотреть, зачем же все это было нужно. APP_ESP8266 Определяю базовый класс для ESP8266 по паттерну «одиночка». В нем определяю указатель на базовый класс USART*. class ESP8266
{ private: ESP8266(){} ESP8266(const ESP8266& root) = delete; ESP8266& operator=(const ESP8266&) = delete; /*!<---------USART settings for ESP8266------->!*/ static constexpr auto USART_BAUDRATE = ESP8266_USART_BAUDRATE; static constexpr USART_NUMBER ESP8266_USART_NUMBER = USART_NUMBER::_2; USART* usart; static constexpr uint8_t LAST_COMMAND_SIZE = 32; char last_command[LAST_COMMAND_SIZE] = {0}; bool send(uint8_t const *buf, const uint16_t len = 0); static constexpr uint8_t ANSWER_BUF_SIZE = 32; uint8_t answer_buf[ANSWER_BUF_SIZE] = {0}; bool receive(uint8_t* buf); bool waiting_answer(bool (ESP8266::*scan_line)(uint8_t *)); bool scan_ok(uint8_t * buf); bool if_str_start_with(const char* str, uint8_t *buf); public: bool init(void); static ESP8266& Instance() { static ESP8266 esp8266; return esp8266; } }; Здесь же есть constexpr переменная, в которой и хранится номер используемого USART. Теперь для изменения номера USART нам достаточно только лишь поменять её значение! Связывание же происходит в функции инициализации: bool ESP8266::init(void)
{ bool res = false; usart = USART::create_USART(ESP8266_USART_NUMBER); usart->init(USART_BAUDRATE); const uint8_t* init_commands[] = { "AT", "ATE0", "AT+CWMODE=2", "AT+CIPMUX=0", "AT+CWSAP="Tortoise_assistant","00000000",5,0", "AT+CIPMUX=1", "AT+CIPSERVER=1,8888" }; for(const auto &command: init_commands) { this->send(command); while(this->waiting_answer(&ESP8266::scan_ok)) continue; } return res; } Строка usart = USART::create_USART(ESP8266_USART_NUMBER); связывает наш уровень приложения с конкретным USART модулем. Вместо выводов, просто выражу надежду, что материал окажется кому-нибудь полезен. Спасибо за прочтение! =========== Источник: habr.com =========== Похожие новости:
Программирование микроконтроллеров ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:21
Часовой пояс: UTC + 5