Masalah menggunakan C ++ di mikrokontroler telah mengganggu saya selama beberapa waktu. Intinya adalah, sejujurnya saya tidak mengerti bagaimana bahasa berorientasi objek ini dapat diterapkan pada sistem tertanam. Maksud saya, bagaimana memilih kelas dan atas dasar apa membuat objek, yaitu, bagaimana tepatnya menggunakan bahasa ini dengan benar. Setelah beberapa waktu dan membaca literatur yang ke-n, saya sampai pada beberapa hasil, yang ingin saya ceritakan di artikel ini. Apakah hasil ini bernilai atau tidak, terserah pembaca. Akan sangat menarik bagi saya untuk membaca kritik terhadap pendekatan saya untuk akhirnya menjawab sendiri pertanyaan: "Bagaimana cara menggunakan C ++ dengan benar saat memprogram mikrokontroler?"
Berhati-hatilah, artikel ini akan berisi banyak sekali source code.
Pada artikel ini, saya, menggunakan contoh penggunaan USART di MK stm32 untuk berkomunikasi dengan esp8266, akan mencoba menjelaskan pendekatan saya dan keuntungan utamanya. Mari kita mulai dengan fakta bahwa keuntungan utama menggunakan C ++ bagi saya adalah kemampuan untuk melakukan decoupling perangkat keras, yaitu. memanfaatkan modul tingkat atas yang tidak bergantung pada platform perangkat keras. Ini akan menghasilkan fakta bahwa sistem akan menjadi mudah dimodifikasi jika ada perubahan. Untuk ini, saya telah mengidentifikasi tiga tingkat abstraksi sistem:
- HW_USART - tingkat perangkat keras, bergantung pada platform
- MW_USART - tingkat menengah, berfungsi untuk memisahkan tingkat pertama dan ketiga
- APP_ESP8266 - tingkat aplikasi, tidak tahu apa-apa tentang MK
HW_USART
Level paling primitif. Saya menggunakan permata stm32f411, USART # 2, juga menerapkan dukungan DMA. Antarmuka diimplementasikan dalam bentuk hanya tiga fungsi: menginisialisasi, mengirim, menerima.
Fungsi inisialisasi terlihat seperti ini:
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;
}
Tidak ada yang istimewa dalam fungsinya, kecuali mungkin saya menggunakan bit mask untuk mengurangi kode yang dihasilkan.
Kemudian fungsi kirim terlihat seperti ini:
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;
}
Fungsi ini memiliki kruk, dalam bentuk variabel first_attempt, yang membantu menentukan apakah ini adalah pengiriman pertama melalui DMA atau bukan. Mengapa ini dibutuhkan? Faktanya adalah saya memeriksa apakah pengiriman sebelumnya ke DMA berhasil atau tidak SEBELUM pengiriman, bukan SETELAH. Saya membuatnya sehingga setelah mengirim data, tidak bodoh untuk menunggu sampai selesai, tetapi untuk mengeksekusi kode yang berguna saat ini.
Kemudian fungsi terima terlihat seperti ini:
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;
}
Keunikan dari fungsi ini adalah saya tidak tahu sebelumnya berapa byte yang harus saya terima. Untuk menunjukkan data yang diterima, saya memeriksa bendera IDLE, kemudian, jika status IDLE diperbaiki, saya menghapus bendera dan membaca data dari buffer. Jika status IDLE tidak diperbaiki, maka fungsinya hanya mengembalikan nol, yaitu tidak ada data.
Pada titik ini, saya mengusulkan untuk mengakhiri dengan level rendah dan langsung menuju ke C ++ dan pola.
MW_USART
Di sini saya telah menerapkan kelas USART abstrak dasar dan menerapkan pola "prototipe" untuk membuat keturunan (kelas USART1 dan USART2 yang konkret). Saya tidak akan menjelaskan penerapan pola prototipe, karena dapat ditemukan di tautan pertama di Google, tetapi saya akan segera memberikan kode sumber dan menjelaskan di bawah ini.
#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;
};
Pertama, file tersebut adalah enumerasi kelas enumerasi USART_NUMBER dengan semua USART yang tersedia, untuk batu saya hanya ada dua di antaranya. Kemudian muncul deklarasi maju kelas dasar USART . Berikutnya adalah deklarasi container dan semua prototipe std :: map <USART_NUMBER, USART *> dan registry-nya, yang diimplementasikan sebagai singleton oleh Mayers.
Di sini saya menemukan fitur IAR ARM, yaitu fakta bahwa ia menginisialisasi variabel statis dua kali, di awal program dan segera setelah masuk main. Oleh karena itu, saya menulis ulang singleton, mengganti variabel _instance statis dengan variabel global. Idealnya, tampilannya dijelaskan dalam komentar.
Selanjutnya, kelas dasar USART dideklarasikan , di mana metode untuk menambahkan prototipe, menghapus prototipe, dan membuat objek didefinisikan (karena konstruktor dari kelas yang diwariskan dinyatakan sebagai pribadi untuk membatasi akses).
Metode klon virtual murni juga dideklarasikan , dan metode inisialisasi, pengiriman, dan penerimaan virtual murni.
Bagaimanapun, kami mewarisi kelas konkret, di mana kami mendefinisikan metode virtual murni yang dijelaskan di atas.
Saya mengutip kode untuk mendefinisikan metode di bawah ini:
#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;
}
Berikut diimplementasikan BUKAN metode dummy hanya untuk USART2, karena saya menggunakannya untuk berkomunikasi dengan esp8266. Dengan demikian, pengisiannya bisa apa saja, itu juga bisa diimplementasikan menggunakan pointer ke fungsi yang mengambil nilainya berdasarkan chip saat ini.
Sekarang saya mengusulkan untuk pergi ke level APP dan melihat mengapa semua ini dibutuhkan.
APP_ESP8266
Saya mendefinisikan kelas dasar untuk ESP8266 menurut pola "tunggal". Di dalamnya saya mendefinisikan pointer ke kelas dasar 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;
}
};
Ada juga variabel constexpr yang menyimpan nomor USART yang digunakan. Sekarang, untuk mengganti nomor USART, kita hanya perlu mengubah nilainya! Pengikatan terjadi dalam fungsi inisialisasi:
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;
}
Baris usart = USART :: create_USART (ESP8266_USART_NUMBER); mengaitkan lapisan aplikasi kita dengan modul USART tertentu.
Daripada kesimpulan, saya hanya menyampaikan harapan agar materi tersebut dapat bermanfaat bagi seseorang. Terima kasih sudah membaca!