From 3a3c55f4474c7538fab2e1046da09e81d3305d3a Mon Sep 17 00:00:00 2001 From: Jay <jdev.coffee@gmail.com> Date: Mon, 21 Aug 2023 20:02:10 +0200 Subject: [PATCH] Add st25dvdiscovery driver (#1) * Add sendCmd(), doTurnOn(), doTurnOff() * Override all virtual DisplayImpl methods * Include ST25DVDISCOVERY Display driver to Makefile * Successful GRAM Access * Write() functionality --------- Co-authored-by: Jay <jdev.coffee@gmail.com> Signed-off-by: Federico Terraneo <fede.tft@miosix.org> --- Makefile | 5 +- drivers/display_st25dvdiscovery.cpp | 325 ++++++++++++++++++++++ drivers/display_st25dvdiscovery.h | 411 ++++++++++++++++++++++++++++ 3 files changed, 739 insertions(+), 2 deletions(-) create mode 100644 drivers/display_st25dvdiscovery.cpp create mode 100644 drivers/display_st25dvdiscovery.h diff --git a/Makefile b/Makefile index e1f8674..26801ab 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ## Makefile for mxgui ## This makefile builds libmxgui.a ## -MAKEFILE_VERSION := 1.09 +MAKEFILE_VERSION := 1.10 GCCMAJOR := $(shell arm-miosix-eabi-gcc --version | \ perl -e '$$_=<>;/\(GCC\) (\d+)/;print "$$1"') ## KPATH and CONFPATH are forwarded by the parent Makefile @@ -40,7 +40,8 @@ drivers/display_stm32f4discovery.cpp \ drivers/event_stm32f4discovery.cpp \ drivers/display_generic_1bpp.cpp \ drivers/display_generic_4bpp.cpp \ -drivers/display_st7735.cpp +drivers/display_st7735.cpp \ +drivers/display_st25dvdiscovery.cpp ifeq ("$(VERBOSE)","1") Q := diff --git a/drivers/display_st25dvdiscovery.cpp b/drivers/display_st25dvdiscovery.cpp new file mode 100644 index 0000000..c96cfda --- /dev/null +++ b/drivers/display_st25dvdiscovery.cpp @@ -0,0 +1,325 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#ifdef _BOARD_STM32F415VG_ST25DVDISCOVERY +#include "display_st25dvdiscovery.h" +#include "miosix.h" +#include "misc_inst.h" +#include "line.h" +#include <cstdarg> + +using namespace std; +using namespace miosix; + +namespace mxgui { + +//Control interface +typedef Gpio<GPIOB_BASE,13> scl; //SPI SCK +typedef Gpio<GPIOC_BASE, 3> sda; //SPI MOSI +typedef Gpio<GPIOC_BASE, 0> dcx; //Data/command +typedef Gpio<GPIOB_BASE,12> csx; //SPI CS + +/** + * Send and receive a byte through SPI2 + * \param c byte to send + * \return byte received + */ +static unsigned char spi2sendRev(unsigned char c=0) +{ + SPI2->DR=c; + while((SPI2->SR & SPI_SR_RXNE)==0) ; + return SPI2->DR; +} + +void sendCmd(unsigned char cmd, int len, ...) +{ + + // Send Command + dcx::low(); + csx::low(); + spi2sendRev(cmd); + delayUs(1); + dcx::high(); + + // Send Arguments + va_list arg; + va_start(arg,len); + for(int i=0;i<len;i++) + { + spi2sendRev(va_arg(arg,int)); + delayUs(1); + } + va_end(arg); + csx::high(); +} + +Transaction::Transaction(unsigned char cmd) +{ + dcx::low(); + csx::low(); + spi2sendRev(cmd); + delayUs(1); + dcx::high(); +} + +void Transaction::write(unsigned char c) +{ + spi2sendRev(c); + delayUs(1); +} + +Transaction::~Transaction() +{ + csx::high(); +} + + +void registerDisplayHook(DisplayManager& dm) +{ + dm.registerDisplay(&DisplayImpl::instance()); +} + +DisplayImpl& DisplayImpl::instance() +{ + static DisplayImpl instance; + return instance; +} + +void DisplayImpl::doTurnOn() +{ + sendCmd(0x29,0); //LCD_DISPLAY_ON +}; + +void DisplayImpl::doTurnOff() +{ + sendCmd(0x28,0); //LCD_DISPLAY_ON +}; + +void DisplayImpl::doSetBrightness(int brt) +{ + //TODO - Can be set by 0x51h +} + +pair<short int, short int> DisplayImpl::doGetSize() const +{ + return make_pair(height,width); +} + +void DisplayImpl::write(Point p, const char *text) { + font.draw(*this, textColor, p, text); +} + +void DisplayImpl::clippedWrite(Point p, Point a, Point b, const char *text) { + font.clippedDraw(*this, textColor, p, a, b, text); +} + +void DisplayImpl::clear(Color color) { + clear(Point(0,0), Point(width-1,height-1), color); +} + +void DisplayImpl::clear(Point p1, Point p2, Color color) { + unsigned char lsb = color & 0xFF; + unsigned char msb = (color >> 8) & 0xFF; + + imageWindow(p1, p2); + int numPixels = (p2.x() - p1.x() + 1) * (p2.y() - p1.y() + 1); + + Transaction t(0x2c); + //Send data to write on GRAM + for(int i=0; i < numPixels; i++) { + t.write(msb); + t.write(lsb); + } +} + +void DisplayImpl::beginPixel() {} + +void DisplayImpl::setPixel(Point p, Color color) +{ + unsigned char lsb = color & 0xFF; + unsigned char msb = (color >> 8) & 0xFF; + sendCmd(0x2c,2,msb,lsb); // RAMWR +} + +void DisplayImpl::line(Point a, Point b, Color color) +{ + // TODO - Horizontal line optimization + Line::draw(*this, a, b, color); +} + +void DisplayImpl::scanLine(Point p, const Color *colors, unsigned short length) +{ + imageWindow(p,Point(width-1,p.y())); + + unsigned char lsb = 0x00; + unsigned char msb = 0x00; + + Transaction t(0x2c); // RAMWR + for (int i = 0; i < length; i++) + { + lsb = colors[i] & 0xFF; + msb = (colors[i] >> 8) & 0xFF; + t.write(msb); + t.write(lsb); + } +} + +Color* DisplayImpl::getScanLineBuffer() { + if(buffer == 0) buffer = new Color[getWidth()]; + return buffer; +} + +void DisplayImpl::scanLineBuffer(Point p, unsigned short length) +{ + scanLine(p, buffer, length); +} + +void DisplayImpl::drawImage(Point p, const ImageBase& img) +{ + short int xEnd = p.x() + img.getWidth() - 1; + short int yEnd = p.y() + img.getHeight() - 1; + if(xEnd >= width || yEnd >= height) { return; } + + const unsigned short *imgData = img.getData(); + if(imgData != 0) + { + unsigned char lsb = 0x00; + unsigned char msb = 0x00; + + //Optimized version for memory-loaded images + imageWindow(p, Point(xEnd, yEnd)); + int numPixels = img.getHeight() * img.getWidth(); + + Transaction t(0x2c); // RAMWR + for(int i=0; i <= numPixels; i++) + { + lsb = imgData[i] & 0xFF; + msb = (imgData[i] >> 8) & 0xFF; + t.write(msb); + t.write(lsb); + } + } + else { img.draw(*this,p); } +} + +void DisplayImpl::clippedDrawImage(Point p, Point a, Point b, const ImageBase& img) { + img.clippedDraw(*this,p,a,b); +} + +void DisplayImpl::drawRectangle(Point a, Point b, Color c) { + line(a,Point(b.x(), a.y()), c); + line(Point(b.x(), a.y()), b, c); + line(b,Point(a.x(), b.y()), c); + line(Point(a.x(), b.y()), a, c); +} + +DisplayImpl::pixel_iterator DisplayImpl::begin(Point p1, Point p2, IteratorDirection d) { + if(p1.x()<0 || p1.y()<0 || p2.x()<0 || p2.y()<0) { + return pixel_iterator(); + } + if(p1.x() >= width || p1.y() >= height || p2.x() >= width || p2.y() >= height) { + return pixel_iterator(); + } + if(p2.x() < p1.x() || p2.y() < p1.y()) { + return pixel_iterator(); + } + + if(d==DR) textWindow(p1,p2); + else imageWindow(p1,p2); + + unsigned int numPixels=(p2.x()-p1.x()+1)*(p2.y()-p1.y()+1); + return pixel_iterator(numPixels); +} + +DisplayImpl::DisplayImpl() : buffer(0) +{ + // TODO - RCC Sequence needed for PLLSAI? There is no PLLSAI for this MCU + + // SPI2 Configuration + { + FastInterruptDisableLock dLock; + + scl::mode(Mode::ALTERNATE); scl::alternateFunction(5); //SPI5 + sda::mode(Mode::ALTERNATE); sda::alternateFunction(5); + csx::mode(Mode::OUTPUT); csx::high(); + dcx::mode(Mode::OUTPUT); + + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; + RCC_SYNC(); + } + + SPI2->CR1=SPI_CR1_SSM //Sowtware CS + | SPI_CR1_SSI //Software CS high + | SPI_CR1_SPE //SPI enabled + | (3<<3) //Divide input clock by 16: 84/16=5.25MHz + | SPI_CR1_MSTR; //Master mode + Thread::sleep(1); + + // ILI9341 Configuration + sendCmd(0xca,3,0xc3,0x08,0x50); //undocumented command + sendCmd(0xcf,3,0x00,0xc1,0x30); //LCD_POWERB + sendCmd(0xed,4,0x64,0x03,0x12,0x81); //LCD_POWER_SEQ + sendCmd(0xe8,3,0x85,0x00,0x78); //LCD_DTCA + sendCmd(0xcb,5,0x39,0x2c,0x00,0x34,0x02); //LCD_POWERA + sendCmd(0xf7,1,0x20); //LCD_PRC + sendCmd(0xea,2,0x00,0x00); //LCD_DTCB + sendCmd(0xb1,2,0x00,0x1b); //LCD_FRMCTR1 + sendCmd(0xb6,2,0x0a,0xa2); //LCD_DFC + sendCmd(0xc0,1,0x10); //LCD_POWER1 + sendCmd(0xc1,1,0x10); //LCD_POWER2 + sendCmd(0xc5,2,0x45,0x15); //LCD_VCOM1 + sendCmd(0xc7,1,0x90); //LCD_VCOM2 + sendCmd(0x36,1,0x08); //LCD_MAC + sendCmd(0xf2,1,0x00); //LCD_3GAMMA_EN + sendCmd(0xb6,4,0x0a,0xa7,0x27,0x04); //LCD_DFC + sendCmd(0x2a,4,0x00,0x00,0x00,0xef); //LCD_COLUMN_ADDR + sendCmd(0x2b,4,0x00,0x00,0x01,0x3f); //LCD_PAGE_ADDR + sendCmd(0xf6,3,0x01,0x00,0x00); //LCD_INTERFACE + sendCmd(0x3a,1,0x05); + sendCmd(0x2c,0); //LCD_GRAM + Thread::sleep(200); + sendCmd(0x26,1,0x01); //LCD_GAMMA + sendCmd(0xe0,15,0x0f,0x29,0x24,0x0c,0x0e,0x09,0x4e,0x78,0x3c,0x09,0x13, + 0x05,0x17,0x11,0x00); //LCD_PGAMMA + sendCmd(0xe1,15,0x00,0x16,0x1b,0x04,0x11,0x07,0x31,0x33,0x42,0x05,0x0c, + 0x0a,0x28,0x2f,0x0f); //LCD_NGAMMA + sendCmd(0x11,0); //LCD_SLEEP_OUT + Thread::sleep(500); + sendCmd(0x29,0); //LCD_DISPLAY_ON + sendCmd(0x2c,0); //LCD_GRAM + + imageWindow(Point(0,0), Point(width-1,height-1)); +}; + +DisplayImpl::~DisplayImpl() +{ + if (buffer) delete[] buffer; +}; + +} + +#endif //_BOARD_STM32F415VG_ST25DVDISCOVERY \ No newline at end of file diff --git a/drivers/display_st25dvdiscovery.h b/drivers/display_st25dvdiscovery.h new file mode 100644 index 0000000..5ee40f0 --- /dev/null +++ b/drivers/display_st25dvdiscovery.h @@ -0,0 +1,411 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#ifndef MXGUI_LIBRARY +#error "This is header is private, it can be used only within mxgui." +#error "If your code depends on a private header, it IS broken." +#endif //MXGUI_LIBRARY + +#ifndef DISPLAY_ST25DVDISCOVERY_H +#define DISPLAY_ST25DVDISCOVERY_H + +#ifdef _BOARD_STM32F415VG_ST25DVDISCOVERY +#include "display.h" +#include "color.h" +#include "point.h" + +namespace mxgui { + +//This display is 16 bit per pixel, check that the color depth is properly +//configured +#ifndef MXGUI_COLOR_DEPTH_16_BIT +#error The ILI9341 driver requires a color depth of 16bit per pixel +#endif + +/** + * Send a command to the ILI9341 display controller + * \param cmd command + * \param len length of the (optional) argument, or 0 for commands without + * arguments. + */ +void sendCmd(unsigned char cmd, int len, ...); + +/** + * Simply another flavour of sending data to Display + * Useful for sending dynamically sized data +*/ +class Transaction +{ +public: + Transaction(unsigned char cmd); + void write(unsigned char c); + ~Transaction(); +}; + +class DisplayImpl : public Display +{ + +public: + /** + * \return an instance to this class (singleton) + */ + static DisplayImpl& instance(); + + /** + * Turn the display On after it has been turned Off. + * Display initial state is On. + */ + void doTurnOn() override; + + /** + * Turn the display Off. It can be later turned back On. + */ + void doTurnOff() override; + + /** + * Set display brightness. Depending on the underlying driver, + * may do nothing. + * \param brt from 0 to 100 + */ + void doSetBrightness(int brt) override; + + /** + * \return a pair with the display height and width + */ + std::pair<short int, short int> doGetSize() const override; + + /** + * Write text to the display. If text is too long it will be truncated + * \param p point where the upper left corner of the text will be printed + * \param text, text to print. + */ + void write(Point p, const char *text) override; + + /** + * Write part of text to the display + * \param p point of the upper left corner where the text will be drawn. + * Negative coordinates are allowed, as long as the clipped view has + * positive or zero coordinates + * \param a Upper left corner of clipping rectangle + * \param b Lower right corner of clipping rectangle + * \param text text to write + */ + void clippedWrite(Point p, Point a, Point b, const char *text) override; + + /** + * Clear the Display. The screen will be filled with the desired color + * \param color fill color + */ + void clear(Color color) override; + + /** + * Clear an area of the screen + * \param p1 upper left corner of area to clear + * \param p2 lower right corner of area to clear + * \param color fill color + */ + void clear(Point p1, Point p2, Color color) override; + + /** + * This member function is used on some target displays to reset the + * drawing window to its default value. You have to call beginPixel() once + * before calling setPixel(). You can then make any number of calls to + * setPixel() without calling beginPixel() again, as long as you don't + * call any other member function in this class. If you call another + * member function, for example line(), you have to call beginPixel() again + * before calling setPixel(). + */ + void beginPixel() override; + + /** + * Draw a pixel with desired color. You have to call beginPixel() once + * before calling setPixel() + * \param p point where to draw pixel + * \param color pixel color + */ + void setPixel(Point p, Color color) override; + + /** + * Draw a line between point a and point b, with color c + * \param a first point + * \param b second point + * \param c line color + */ + void line(Point a, Point b, Color color) override; + + /** + * Draw an horizontal line on screen. + * Instead of line(), this member function takes an array of colors to be + * able to individually set pixel colors of a line. + * \param p starting point of the line + * \param colors an array of pixel colors whoase size must be b.x()-a.x()+1 + * \param length length of colors array. + * p.x()+length must be <= display.width() + */ + void scanLine(Point p, const Color *colors, unsigned short length) override; + + /** + * \return a buffer of length equal to this->getWidth() that can be used to + * render a scanline. + */ + Color *getScanLineBuffer() override; + + /** + * Draw the content of the last getScanLineBuffer() on an horizontal line + * on the screen. + * \param p starting point of the line + * \param length length of colors array. + * p.x()+length must be <= display.width() + */ + void scanLineBuffer(Point p, unsigned short length) override; + + /** + * Draw an image on the screen + * \param p point of the upper left corner where the image will be drawn + * \param i image to draw + */ + void drawImage(Point p, const ImageBase& img) override; + + /** + * Draw part of an image on the screen + * \param p point of the upper left corner where the image will be drawn. + * Negative coordinates are allowed, as long as the clipped view has + * positive or zero coordinates + * \param a Upper left corner of clipping rectangle + * \param b Lower right corner of clipping rectangle + * \param i Image to draw + */ + void clippedDrawImage(Point p, Point a, Point b, const ImageBase& img) override; + + /** + * Draw a rectangle (not filled) with the desired color + * \param a upper left corner of the rectangle + * \param b lower right corner of the rectangle + * \param c color of the line + */ + void drawRectangle(Point a, Point b, Color c) override; + + class pixel_iterator + { + public: + + /** + * Default constructor, results in an invalid iterator. + */ + pixel_iterator(): pixelLeft(0) {} + + /** + * Set a pixel and move the pointer to the next one + * \param color color to set the current pixel + * \return a reference to this + */ + pixel_iterator& operator= (Color color) + { + pixelLeft--; + + unsigned char lsb = color & 0xFF; + unsigned char msb = (color >> 8) & 0xFF; + + wr->write(msb); + wr->write(lsb); + + return *this; + } + + /** + * Compare two pixel_iterators for equality. + * They are equal if they point to the same location. + */ + bool operator== (const pixel_iterator& itr) + { + return this->pixelLeft==itr.pixelLeft; + } + + /** + * Compare two pixel_iterators for inequality. + * They different if they point to different locations. + */ + bool operator!= (const pixel_iterator& itr) + { + return this->pixelLeft!=itr.pixelLeft; + } + + /** + * \return a reference to this. + */ + pixel_iterator& operator* () { return *this; } + + /** + * \return a reference to this. Does not increment pixel pointer. + */ + pixel_iterator& operator++ () { return *this; } + + /** + * \return a reference to this. Does not increment pixel pointer. + */ + pixel_iterator& operator++ (int) { return *this; } + + /** + * Must be called if not all pixels of the required window are going + * to be written. + */ + void invalidate() {} + + private: + + /** + * Constructor + * \param pixelLeft number of remaining pixels + */ + pixel_iterator(unsigned int pixelLeft): pixelLeft(pixelLeft) + { + wr = new Transaction(0x2c); + } + + unsigned int pixelLeft; ///< How many pixels are left to draw + + Transaction* wr; + + friend class DisplayImpl; + }; + + /** + * Specify a window on screen and return an object that allows to write + * its pixels. + * Note: a call to begin() will invalidate any previous iterator. + * \param p1 upper left corner of window + * \param p2 lower right corner (included) + * \param d increment direction + * \return a pixel iterator + */ + pixel_iterator begin(Point p1, Point p2, IteratorDirection d); + + /** + * \return an iterator which is one past the last pixel in the pixel + * specified by begin. Behaviour is undefined if called before calling + * begin() + */ + pixel_iterator end() const + { + // Default ctor: pixelLeft is zero + return pixel_iterator(); + } + + /** + * Destructor + */ + ~DisplayImpl() override; + +private: + + /** + * Constructor. + * Do not instantiate objects of this type directly from application code. + */ + DisplayImpl(); + + #if defined MXGUI_ORIENTATION_VERTICAL + static const short int width=240; + static const short int height=320; + #elif defined MXGUI_ORIENTATION_HORIZONTAL || \ + defined MXGUI_ORIENTATION_VERTICAL_MIRRORED || \ + defined MXGUI_ORIENTATION_HORIZONTAL_MIRRORED + #error unsupported orientation + #else + #error No orientation defined + #endif + + Color* buffer; ///< For scanLineBuffer + + /** + * This member function is used to set the cursor stored in memory, as well as + * the SC,SP,EC,EP counters of display driver. + * \param p1 top-left point of the rectangle + * \param p2 bottom-right point of the rectangle + */ + static inline void window(Point p1, Point p2) + { + int SC[] = {p1.x() & 0xff, (p1.x() >> 8) & 0xff}; + int EC[] = {p2.x() & 0xff, (p2.x() >> 8) & 0xff}; + int SP[] = {p1.y() & 0xff, (p1.y() >> 8) & 0xff}; + int EP[] = {p2.y() & 0xff, (p2.y() >> 8) & 0xff}; + + sendCmd(0x2a,4,SC[1],SC[0],EC[1],EC[0]); //LCD_COLUMN_ADDR + sendCmd(0x2b,4,SP[1],SP[0],EP[1],EP[0]); //LCD_PAGE_ADDR + sendCmd(0x2c,0); //LCD_RAMWR + } + + /** + * Set a hardware window on the screen, optimized for writing text. + * The GRAM increment will be set to up-to-down first, then left-to-right which + * is the correct increment to draw fonts + * \param p1 upper left corner of the window + * \param p2 lower right corner of the window + */ + static inline void textWindow(Point p1, Point p2) + { + #ifdef MXGUI_ORIENTATION_VERTICAL + // p3 is p2 transposed relative to p1. So that the column and page addresses exchanges + Point p3 = Point(p1.x()+p2.y()-p1.y(),p1.y()+p2.x()-p1.x()); + window(p1,p3); + sendCmd(0x36,1,0x28); //LCD_MAC + #elif defined MXGUI_ORIENTATION_HORIZONTAL + #error Not implemented + #elif defined MXGUI_ORIENTATION_VERTICAL_MIRRORED + #error Not implemented + #else //MXGUI_ORIENTATION_HORIZONTAL_MIRRORED + #error Not implemented + #endif + } + + /** + * Set a hardware window on the screen, optimized for drawing images. + * The GRAM increment will be set to left-to-right first, then up-to-down which + * is the correct increment to draw images + * \param p1 upper left corner of the window + * \param p2 lower right corner of the window + */ + static inline void imageWindow(Point p1, Point p2) + { + #ifdef MXGUI_ORIENTATION_VERTICAL + window(p1,p2); + sendCmd(0x36,1,0x08); //LCD_MAC + #elif defined MXGUI_ORIENTATION_HORIZONTAL + #error Not implemented + #elif defined MXGUI_ORIENTATION_VERTICAL_MIRRORED + #error Not implemented + #else //MXGUI_ORIENTATION_HORIZONTAL_MIRRORED + #error Not implemented + #endif + } +}; + + +} //namespace mxgui +#endif //_BOARD_STM32F415VG_ST25DVDISCOVERY + +#endif //DISPLAY_ST25DVDISCOVERY_H \ No newline at end of file -- GitLab