## TFT ILI9341實作 ## Demo {%youtube _0Ot_U6uRhs %} ## STM32CubeIDE設定 ### PIN 設定 ![PinoutClear](https://imgur.com/VJNPOvV.png) --- ### GPIO設定 ![image](https://hackmd.io/_uploads/S1v2Erwyll.png) --- ### RCC設定 ![RccSet](https://imgur.com/7kPTJ9A.png) --- ### SYS設定 ![SysSet](https://imgur.com/rNjjVDv.png) --- ### TIM設定 ![image](https://hackmd.io/_uploads/SJGgSSvylg.png) --- ### SPI設定 選 ![image](https://hackmd.io/_uploads/BJKrBBvkxl.png) ![image](https://hackmd.io/_uploads/rJNQrSP1gx.png) ![image](https://hackmd.io/_uploads/S1_NrrvJgl.png) --- ### NVIC 設定 ![image](https://hackmd.io/_uploads/HJLtSrvJgl.png) --- ### CRC設定 ![image](https://hackmd.io/_uploads/SkSP0Bw1gx.png) ___ ### touchGFX設定 ![image](https://hackmd.io/_uploads/rkcM0SP1lg.png) ![image](https://hackmd.io/_uploads/Bkc40SPkeg.png) ___ ### Clock設定 ![ClockSet](https://imgur.com/SlT0y70.png) --- ### 外設檔案分開+生成 ![GeneratorSet](https://imgur.com/Lo1x0Fc.png) #### 生成程式碼 ![CodeGenerator](https://imgur.com/LDLzPZb.png) --- ### Nucleo_Pinou ![nucleo_pinout](https://imgur.com/pW4VU4f.png) --- ### 電路圖 ![ILI9341接腳](https://hackmd.io/_uploads/r1qqjrDyxx.png) --- ## ILI9341實作 [下載touchGFX](https://www.st.com/en/development-tools/touchgfxdesigner.html) --- ### 程式碼 #### main.c :::spoiler ```c= /* USER CODE BEGIN Includes */ #include "z_touch_XPT2046.h" #include "z_displ_ILI9XXX.h" #include "stdio.h" #include "string.h" /* USER CODE END Includes */ /* USER CODE BEGIN 2 */ HAL_Delay(200); Displ_Init(Displ_Orientat_0); HAL_Delay(200); touchgfxSignalVSync(); /* USER CODE END 2 */ /* USER CODE BEGIN WHILE */ while (1) { if (Touch_GotATouch(2)){ touchgfxSignalVSync(); } if ("other events needing a display update") touchgfxSignalVSync(); /* USER CODE END WHILE */ MX_TouchGFX_Process(); /* USER CODE BEGIN 3 */ } ``` ::: #### z_displ_ILI9XXX.h :::spoiler ```c= /* * z_displ_ILI9488.h * rel. TouchGFX.1.30 * * Created on: 05 giu 2023 * Author: mauro * * licensing: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32/blob/c097f0e7d569845c1cf98e8d930f2224e427fd54/LICENSE * * Installing and using this library follow instruction on: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32 * * These are the init instruction to put in you main() USER CODE BEGIN 2 * * (if Direct Handling) * Displ_Init(Displ_Orientat_0); // initialize display controller - set orientation parameter as per your needs * Displ_CLS(BLACK); // clear the screen - BLACK or any other color you prefer * Displ_BackLight('I'); // initialize backlight * * (if using TouchGFX - button mode) * Displ_Init(Displ_Orientat_0); // initialize display controller - set orientation parameter as per TouchGFX setup * touchgfxSignalVSync(); // ask display syncronization * Displ_BackLight('I'); // initialize backlight * * (if using TouchGFX - full mode) * Displ_Init(Displ_Orientat_0); // initialize display controller - set orientation parameter as per TouchGFX setup * Displ_BackLight('I'); // initialize backlight * HAL_TIM_Base_Start_IT(&TGFX_T); // start TouchGFX tick timer * * see also z_touch_XPT2046.h * */ #ifndef __Z_DISPL_ILI9XXX_H #define __Z_DISPL_ILI9XXX_H /*||||||||||| USER/PROJECT PARAMETERS |||||||||||*/ /****************** STEP 0 ****************** *** if mapping flash on the uC addresses space *** ********** uncomment the below #define *********** ******** end assign it the correct value ********* ***** If external flash handled by TOUCHGFX,****** ************* let #define commented ************** **************************************************/ /***************** STEP 1 ***************** ************ Enable TouchGFX interface ************ * uncommenting the below #define to enable * functions interfacing TouchGFX ***************************************************/ #define DISPLAY_USING_TOUCHGFX /****************** STEP 2 ***************** * which display are you using? *************************************************/ #define ILI9341 //#define ILI9488_V1 //#define ILI9488_V2 #include "fonts.h" /****************** STEP 3 ****************** **************** PORT PARAMETERS ***************** ** properly set the below th 2 defines to address ******** the SPI port defined on CubeMX ********* **************************************************/ #define DISPL_SPI_PORT hspi2 #define DISPL_SPI SPI2 /****************** STEP 4 ****************** ***************** SPI PORT SPEED ***************** * define HERE the prescaler value to assign SPI port * when transferring data to/from DISPLAY or TOUCH * Keep in mind that Touch SPI Baudrate should be no more than 1 Mbps ***************************************************/ #define DISPL_PRESCALER SPI_BAUDRATEPRESCALER_4 //prescaler assigned to display SPI port #define TOUCH_PRESCALER SPI_BAUDRATEPRESCALER_256 //prescaler assigned to touch device SPI port /***************** STEP 5 ***************** ************* SPI COMMUNICATION MODE ************** *** enable SPI mode want, uncommenting ONE row **** **** (Setup the same configuration on CubeMX) ***** ***************************************************/ //#define DISPLAY_SPI_POLLING_MODE //#define DISPLAY_SPI_INTERRUPT_MODE #define DISPLAY_SPI_DMA_MODE // (mixed: polling/DMA, see below) /***************** STEP 6 ***************** ***************** Backlight timer ***************** * if you want dimming backlight UNCOMMENT the * DISPLAY_DIMMING_MODE below define and properly * set other defines. * Using backlight as a switch (only on/off) leave * DISPLAY_DIMMING_MODE commented * if DIMMING: * On CubeMX set DISPL_LED pin as a timer PWM pin. * Timer COUNTER PERIOD (ARR) defines dimming light steps: * keep it low value - e.g. 10 - if dimming with buttons, * use higher value - e.g. 100 - if dimming with encoder, ... * Avoiding display flickering timer PRESCALER should * let timer clock to be higher than COUNTER PERIOD * 100 Hz. * Set all other defines below ***************************************************/ #define DISPLAY_DIMMING_MODE // uncomment this define to enable dimming function otherwise there is an on/off switching function #define BKLIT_TIMER TIM3 //timer used (PWMming DISPL_LED pin) #define BKLIT_T htim3 //timer used #define BKLIT_CHANNEL TIM_CHANNEL_2 //channel used #define BKLIT_CCR CCR2 //Capture-compare register used (same number as channel) #define BKLIT_STBY_LEVEL 1 //Display backlight level when in stand-by (levels are CNT values) #define BKLIT_INIT_LEVEL 100 //Display backlight level on startup /***************** STEP 7 ***************** ***************** TouchGFX Time base timer ***************** * If using library in TouchGFX-full-mode * (see GitHub page indicated on top for details) * you have to set #define DELAY_TO_KEY_REPEAT -1 * in "z_touch_XPT2046.h" and setup a timer as a * time base for TouchGFX. * It has to be set to generate a * HAL_TIM_PeriodElapsedCallback 60 times per second * That timer has to be assigned to the below macros. * if not in TouchGFX-full-mode: assign macros to * an unused timer ***************************************************/ #define TGFX_TIMER TIM3 #define TGFX_T htim3 /***************** STEP 8 ***************** ************* frame buffer DEFINITION ************* * IF NO TOUCHGFX: * BUFLEVEL defines size of each one of the 2 SPI * buffers: buffer size is 2^BUFLEVEL so 2 means * 4 bytes buffer and 10 means 1 kbyte (each). * It must be not below 10! * If TOUCHGFX: * buffers are not used if display handles RGB565. * If display uses RGB666 one buffer will be used for * color format translation. In this case set * BUFLEVEL following this table: * TouchGFX buffers>10KB need BUFLEVEL 15 * TouchGFX buffers>5KB need BUFLEVEL 14 * TouchGFX buffers>2700bytes need BUFLEVEL 13 * TouchGFX buffers>1300bytes need BUFLEVEL 12 ***************************************************/ #define BUFLEVEL 11 /*|||||||| END OF USER/PROJECT PARAMETERS ||||||||*/ /*|||||||||||||| DEVICE PARAMETERS |||||||||||||||||*/ /* you shouldn't need to change anything here after */ #ifdef ILI9488_V1 #define ILI9488 #endif #ifdef ILI9488_V2 #define ILI9488 #endif /*************** color depth **************** *** choose one of the two color depth available *** ***** to use on the display RGB565 and RGB666 ***** ***************************************************/ #ifdef ILI9341 #define Z_RGB565 #endif #ifdef ILI9488_V1 #define Z_RGB666 #endif #ifdef ILI9488_V2 #define Z_RGB565 #endif /*************** display size *************** ***************************************************/ #ifdef ILI9341 #define DISPL_WIDTH 240 // 0 orientation #define DISPL_HEIGHT 320 // 0 orientation #endif #ifdef ILI9488 #define DISPL_WIDTH 320 // 0 orientation #define DISPL_HEIGHT 480 // 0 orientation #endif /************* from POLLING to DMA ***************** *** below DISPL_DMA_CUTOFF data size, transfer **** ****** will be polling, even if DMA enabled ******* ***************************************************/ #define DISPL_DMA_CUTOFF 20 // (bytes) used only in DMA_MODE /*||||||||||| END OF DEVICE PARAMETERS ||||||||||||*/ #include <string.h> typedef enum { Displ_Orientat_0, Displ_Orientat_90, Displ_Orientat_180, Displ_Orientat_270 } Displ_Orientat_e; #define SPI_COMMAND GPIO_PIN_RESET //DISPL_DC_Pin level sending commands #define SPI_DATA GPIO_PIN_SET //DISPL_DC_Pin level sending data // set the buffers size as per BUFLEVEL and DISPLAY_USING_TOUCHGFX // (if using TouchGFX, don't buffers from this library) #define SIZEBUF (1<<BUFLEVEL) /******************************* * Color names *******************************/ #define RED 0xF800 #define GREEN 0x07E0 #define BLUE 0x001F #define YELLOW 0xFFE0 #define MAGENTA 0xF81F #define ORANGE 0xFD00 #define CYAN 0x07FF #define D_RED 0xC000 #define D_GREEN 0x0600 #define D_BLUE 0x0018 #define D_YELLOW 0xC600 #define D_MAGENTA 0xC018 #define D_ORANGE 0xC300 #define D_CYAN 0x0618 #define DD_RED 0x8000 #define DD_GREEN 0x0400 #define DD_BLUE 0x0010 #define DD_YELLOW 0x8400 #define DD_MAGENTA 0x8020 #define DD_ORANGE 0x8200 #define DD_CYAN 0x0410 #define WHITE 0xFFFF #define D_WHITE 0xC618 #define DD_WHITE 0x8410 #define DDD_WHITE 0x4208 #define DDDD_WHITE 0x2104 #define BLACK 0x0000 #define color565(r, g, b) ((((r) & 0xF8) << 8) | (((g) & 0xFC) << 3) | (((b) & 0xF8) >> 3)) /********************************** / ILI9XXX LCD family commands **********************************/ #define ILI9XXX_SLEEP_OUT 0x11 //wake up display #define ILI9XXX_DISPLAY_ON 0x29 // enable display #define ILI9XXX_PIXEL_FORMAT 0x3A // RGB565/RGB666/... #define ILI9XXX_RGB_INTERFACE 0xB0 // type of communication (full duplex, half, etc.) #define ILI9XXX_MEMWR 0x2C // writes into memory #define ILI9XXX_COLUMN_ADDR 0x2A // set area display to write into #define ILI9XXX_PAGE_ADDR 0x2B // set area display to write into #define ILI9XXX_MADCTL 0x36 // order followed writing into memory (-> screen orientation) #define ILI9XXX_MADCTL_0DEG 0X00 // parameter of MADCTL command #define ILI9XXX_MADCTL_90DEG 0x20 // parameter of MADCTL command #define ILI9XXX_MADCTL_180DEG 0x40 // parameter of MADCTL command #define ILI9XXX_MADCTL_270DEG 0x60 // parameter of MADCTL command #define ILI9XXX_INIT_SHORT_DELAY 5 // Hal_Delay parameter #define ILI9XXX_INIT_LONG_DELAY 150 // Hal_Delay parameter #define ILI9XXX_POWER0 0xC0 #define ILI9XXX_POWER1 0xC1 #define ILI9488_POWER2 0xC2 #define ILI9341_POWERA 0xCB #define ILI9341_POWERB 0xCF /********************************************************** * macro setting SPI baudrate prescaler **********************************************************/ #define SET_DISPL_SPI_BAUDRATE DISPL_SPI->CR1 &= (uint16_t) ~SPI_CR1_BR_Msk; \ DISPL_SPI->CR1 |= DISPL_PRESCALER #define SET_TOUCH_SPI_BAUDRATE TOUCH_SPI->CR1 &= (uint16_t) ~SPI_CR1_BR_Msk; \ TOUCH_SPI->CR1 |= TOUCH_PRESCALER /**********************************************************/ #define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } #ifndef DISPLAY_USING_TOUCHGFX void Displ_drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color); void Displ_fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color); void Displ_fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color); void Displ_drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color); void Displ_Border(int16_t x, int16_t y, int16_t w, int16_t h, int16_t t, uint16_t color); void Displ_CLS(uint16_t bgcolor); void Displ_CString(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, const char* str, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor); void Displ_fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color); void Displ_drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color); void Displ_Init(Displ_Orientat_e orientation); void Displ_Line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color); void Displ_Orientation(Displ_Orientat_e orientation); void Displ_Pixel(uint16_t x, uint16_t y, uint16_t color); void Displ_WChar(uint16_t x, uint16_t y, char ch, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor); void Displ_WString(uint16_t x, uint16_t y, const char* str, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor); void Displ_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data); #endif /* ! DISPLAY_USING_TOUCHGFX */ void Displ_FillArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); void Displ_Orientation(Displ_Orientat_e orientation); void Displ_Init(Displ_Orientat_e orientation); void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi); void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi); uint32_t Displ_BackLight(uint8_t cmd); #ifdef DISPLAY_USING_TOUCHGFX int touchgfxDisplayDriverTransmitActive(); void touchgfxDisplayDriverTransmitBlock(const uint8_t* pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h); extern void DisplayDriver_TransferCompleteCallback(); extern void touchgfxSignalVSync(void); #endif /* DISPLAY_USING_TOUCHGFX */ #endif /* __Z_DISPL_ILI9XXX_H */ ``` ::: #### z_displ_ILI9XXX.c :::spoiler ```c= /* * z_displ_ILI94XX.c * rel. TouchGFX.1.30 * * Created on: 5 giu 2023 * Author: mauro * * licensing: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32/blob/c097f0e7d569845c1cf98e8d930f2224e427fd54/LICENSE * * To install and use this library follow instruction on: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32 * */ #include "main.h" extern SPI_HandleTypeDef DISPL_SPI_PORT; #ifdef DISPLAY_DIMMING_MODE extern TIM_HandleTypeDef BKLIT_T; #endif extern TIM_HandleTypeDef TGFX_T; extern volatile uint8_t Touch_PenDown; // set to 1 by pendown interrupt callback, reset to 0 by sw Displ_Orientat_e current_orientation; // it records the active display orientation. Set by Displ_Orientation volatile uint8_t Displ_SpiAvailable=1; // 0 if SPI is busy or 1 if it is free (transm cplt) int16_t _width; ///< (oriented) display width int16_t _height; ///< (oriented) display height // if using TouchGFX buffer are not used (size set to 2 bytes for software convenience) // unless using ILI9488V1.0 (RGB666) which needs dispBuffer1 for color format conversion // if not using TouchGFX double buffering is needed #ifdef DISPLAY_USING_TOUCHGFX static uint8_t dispBuffer2[2]; #ifdef Z_RGB666 static uint8_t dispBuffer1[SIZEBUF]; #else static uint8_t dispBuffer1[2]; #endif // Z_RGB666 #else static uint8_t dispBuffer1[SIZEBUF]; static uint8_t dispBuffer2[SIZEBUF]; #endif //DISPLAY_USING_TOUCHGFX static uint8_t *dispBuffer=dispBuffer1; /****************************************** * @brief enable display, disabling touch * device selected if CS low ******************************************/ void Displ_Select(void) { if (TOUCH_SPI==DISPL_SPI){ // if SPI port shared (display <-> touch) if (HAL_GPIO_ReadPin(DISPL_CS_GPIO_Port, DISPL_CS_Pin)) { // if display not yet selected HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_SET); // unselect touch SET_DISPL_SPI_BAUDRATE; //change SPI port speed as per display needs HAL_GPIO_WritePin(DISPL_CS_GPIO_Port, DISPL_CS_Pin, GPIO_PIN_RESET); // select display } } } /************************** * @BRIEF engages SPI port communicating with displayDC_Status * depending on the macro definition makes transmission in Polling/Interrupt/DMA mode * @PARAM DC_Status indicates if sending command or data * data buffer data to send * dataSize number of bytes in "data" to be sent * isTouchGFXBuffer 1 only when called by touchgfxDisplayDriverTransmitBlock (for byte endian conversion). All other cases 0 **************************/ void Displ_Transmit(GPIO_PinState DC_Status, uint8_t* data, uint16_t dataSize, uint8_t isTouchGFXBuffer ){ while (!Displ_SpiAvailable) {}; // waiting for a free SPI port. Flag is set to 1 by transmission-complete interrupt callback Displ_Select(); HAL_GPIO_WritePin(DISPL_DC_GPIO_Port, DISPL_DC_Pin, DC_Status); if (isTouchGFXBuffer){ #ifdef Z_RGB565 //if color format is RGB565 just swap even and odd bytes correcting endianess for ILI driver uint32_t *limit=(uint32_t*)(data+dataSize); for (uint32_t *data32=(uint32_t*)data; data32<limit; data32++) { *data32=__REV16(*data32); } #else //if display color format is RGB666: convert RGB565 received by TouchGFX and swap bytes uint8_t *buf8Pos=dispBuffer1; //using a local pointer uint16_t *limit=(uint16_t*)(data+dataSize); for (uint16_t *data16=(uint16_t*)data; (data16<limit) & ((buf8Pos-dispBuffer1)<(SIZEBUF-3)); data16++) { *(buf8Pos++)=((*data16 & 0xF800)>>8); // R color *(buf8Pos++)=((*data16 & 0x07E0)>>3); // G color *(buf8Pos++)=((*data16 & 0x001F)<<3); // B color } data=dispBuffer1; //data (pointer to data to transfer via SPI) has to point to converted buffer dataSize=(buf8Pos-dispBuffer1); //and dataSize has to contain the converted buffer size #endif //Z_RGB565 } #ifdef DISPLAY_SPI_INTERRUPT_MODE Displ_SpiAvailable=0; HAL_SPI_Transmit_IT(&DISPL_SPI_PORT , data, dataSize); #else #ifdef DISPLAY_SPI_DMA_MODE if (dataSize<DISPL_DMA_CUTOFF) { #endif //DISPLAY_SPI_DMA_MODE Displ_SpiAvailable=0; HAL_SPI_Transmit(&DISPL_SPI_PORT , data, dataSize, HAL_MAX_DELAY); Displ_SpiAvailable=1; #ifdef DISPLAY_USING_TOUCHGFX if (isTouchGFXBuffer){ DisplayDriver_TransferCompleteCallback(); } #endif //DISPLAY_USING_TOUCHGFX #ifdef DISPLAY_SPI_DMA_MODE } else { Displ_SpiAvailable=0; HAL_SPI_Transmit_DMA(&DISPL_SPI_PORT , data, dataSize); } #endif //DISPLAY_SPI_DMA_MODE #endif //DISPLAY_SPI_INTERRUPT_MODE } /********************************** * @BRIEF transmit a byte in a SPI_COMMAND format **********************************/ void Displ_WriteCommand(uint8_t cmd){ Displ_Transmit(SPI_COMMAND, &cmd, sizeof(cmd),0); } /********************************** * @BRIEF transmit a set of data in a SPI_DATA format * @PARAM data buffer data to send * dataSize number of bytes in "data" to be sent * isTouchGFXBuffer 1 only when called by touchgfxDisplayDriverTransmitBlock (for byte endian conversion). All other cases 0 **********************************/ void Displ_WriteData(uint8_t* buff, size_t buff_size, uint8_t isTouchGFXBuffer){ if (buff_size==0) return; Displ_Transmit(SPI_DATA, buff, buff_size, isTouchGFXBuffer); } /********************************** * @brief ILIXXX initialization sequence **********************************/ void ILI9XXX_Init(){ Displ_Select(); HAL_GPIO_WritePin(DISPL_RST_GPIO_Port, DISPL_RST_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(DISPL_RST_GPIO_Port, DISPL_RST_Pin, GPIO_PIN_SET); HAL_Delay(150); /****************************************** * below lines shlould empower brightness * and quality with a higher power * consumption. I haven't seen meaningful * differences on both displays: REMOVED * **************************************** uint8_t data[10]; Displ_WriteCommand(ILI9XXX_POWER0); #ifdef ILI9341 data[0]=0x3F; //default 0x21, 3.3V=0x09, 5V=2B Displ_WriteData(data,1); #endif #ifdef ILI9488 data[0]=0x1F; //default 0E, 3.3V=0x09, 5V=17 data[1]=0x1F; //default 0E, 5V=17 Displ_WriteData(data,2); #endif Displ_WriteCommand(ILI9XXX_POWER1); #ifdef ILI9341 data[0]=0x00; //default 0x00 #endif #ifdef ILI9488 data[0]=0x40; //default 0x44 #endif Displ_WriteData(data,1); #ifdef ILI9488 Displ_WriteCommand(ILI9488_POWER2); data[0]=0x44; //default 0x44 Displ_WriteData(data,1); #endif #ifdef ILI9341 Displ_WriteCommand(ILI9341_POWERA); data[0]=0x39; //fixed data[1]=0x2C; //fixed data[2]=0x00; //fixed data[3]=0x35; //default 0x34 data[4]=0x00; //default 0x02 Displ_WriteData(data,5); Displ_WriteCommand(ILI9341_POWERB); data[0]=0x00; //fixed data[1]=0x99; //default 0x81 data[2]=0x30; //default 0x30 Displ_WriteData(data,3); #endif */ Displ_WriteCommand(ILI9XXX_PIXEL_FORMAT); #ifdef Z_RGB666 Displ_WriteData((uint8_t *)"\x66",1,0); // RGB666 #endif #ifdef Z_RGB565 Displ_WriteData((uint8_t *)"\x55",1,0); // RGB565 #endif Displ_WriteCommand(ILI9XXX_RGB_INTERFACE); Displ_WriteData((uint8_t *)"\x80",1,0); // disable MISO pin Displ_WriteCommand(ILI9XXX_RGB_INTERFACE); Displ_WriteData((uint8_t *)"\x80",1,0); // disable MISO pin Displ_WriteCommand(ILI9XXX_SLEEP_OUT); HAL_Delay(120); Displ_WriteCommand(ILI9XXX_DISPLAY_ON); HAL_Delay(5); } /********************************************** * @brief defines the display area involved * in a writing operation and set * display ready to receive pixel * information * @param x1,y1,x2,y2 top left and bottom * right corner of the area * to write **********************************************/ void Displ_SetAddressWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { static uint8_t data[4]; ((uint32_t *)data)[0]=(((x2 & 0xFF)<<24) | ((x2 & 0xFF00)<<8) | ((x1 & 0xFF)<<8) | ((x1 & 0xFF00)>>8) ); Displ_WriteCommand(ILI9XXX_COLUMN_ADDR); Displ_WriteData(data, 4,0); ((uint32_t *)data)[0]=(((y2 & 0xFF)<<24) | ((y2 & 0xFF00)<<8) | ((y1 & 0xFF)<<8) | ((y1 & 0xFF00)>>8) ); Displ_WriteCommand(ILI9XXX_PAGE_ADDR); Displ_WriteData(data, 4,0); Displ_WriteCommand(ILI9XXX_MEMWR); } /***************************************************** * @brief first display initialization. * @param orientation display orientation *****************************************************/ void Displ_Init(Displ_Orientat_e orientation){ if (TOUCH_SPI==DISPL_SPI){ // if touch and display share the same SPI port HAL_GPIO_WritePin(DISPL_CS_GPIO_Port, DISPL_CS_Pin, GPIO_PIN_SET); // unselect display (will be selected at writing time) HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_SET); // unselect touch (will be selected at writing time) } else { // otherwise leave both port permanently selected HAL_GPIO_WritePin(DISPL_CS_GPIO_Port, DISPL_CS_Pin, GPIO_PIN_RESET); // select display SET_DISPL_SPI_BAUDRATE; HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_RESET); // select touch SET_TOUCH_SPI_BAUDRATE; } ILI9XXX_Init(); Displ_Orientation(orientation); } /********************************************** * @brief set orientation of the display * @param m orientation **********************************************/ void Displ_Orientation(Displ_Orientat_e orientation){ static uint8_t data[1]; switch(orientation) { case Displ_Orientat_0: data[0]=ILI9XXX_MADCTL_0DEG; _height = DISPL_HEIGHT; _width = DISPL_WIDTH; break; case Displ_Orientat_90: data[0]=ILI9XXX_MADCTL_90DEG; _height = DISPL_WIDTH; _width = DISPL_HEIGHT; break; case Displ_Orientat_180: data[0]=ILI9XXX_MADCTL_180DEG; _height = DISPL_HEIGHT; _width = DISPL_WIDTH; break; case Displ_Orientat_270: data[0]=ILI9XXX_MADCTL_270DEG; _height = DISPL_WIDTH; _width = DISPL_HEIGHT; break; } Displ_WriteCommand(ILI9XXX_MADCTL); Displ_WriteData(data,1,0); current_orientation = orientation; //stores active orientation into a global variable for touch routines } void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi){ if (hspi->Instance==DISPL_SPI) { Displ_SpiAvailable=1; } } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance==DISPL_SPI) { Displ_SpiAvailable=1; #ifdef DISPLAY_USING_TOUCHGFX DisplayDriver_TransferCompleteCallback(); #endif } } /***************************** * @brief fill a rectangle with a color * @param x, y top left corner of the rectangle * w, h width and height of the rectangle ******************************/ void Displ_FillArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { /* four steps: * - define area size to file and data size to transfer * - setup data buffer to transfer * - transfer data to display * - swap buffers */ uint32_t k,x1,y1,area,times; if((x >= _width) || (y >= _height) || (w == 0) || (h == 0)) return;// x1=x + w - 1; if (x1 > _width) { x1=_width; } y1=y + h - 1; if (y1 > _height) { y1=_height; } // SETUP DISPLAY DATA BUFFER TO TRANSFER #ifdef Z_RGB565 // setting up dispBuffer in RGB565 format uint32_t data32; data32=(color>>8) | (color<<8) | (color<<24); // supposing color is 0xABCD, data32 becomes 0xCDABCDAB - set a 32 bit variable with swapped endians area=((y1-y+1)*(x1-x+1)); // area to fill in 16bit pixels uint32_t *buf32Pos=(uint32_t *)dispBuffer; //dispBuffer defined in bytes, buf32Pos access it as 32 bit words if (area<(SIZEBUF>>1)) // if area is smaller than dispBuffer times=(area>>1)+1; // number of times data32 has to be loaded into buffer else times=(SIZEBUF>>2); // dispBuffer size as 32bit-words for (k = 0; k < times; k++) *(buf32Pos++)=data32; // loads buffer moving 32bit-words #endif #ifdef Z_RGB666 // setting up dispBuffer in RGB666 format uint32_t datasize; uint8_t Rbyte=(color & 0xF800)>>8; uint8_t Gbyte=(color & 0x07E0)>>3; uint8_t Bbyte=(color & 0x001F)<<3; area=(((y1-y+1)*(x1-x+1))*3); // area to fill in bytes (3 bytes per pixel) uint8_t *buf8Pos=dispBuffer; //using a local pointer: changing values next datasize = (area<(SIZEBUF-3) ? area : (SIZEBUF-3)); //as buf8Pos receives 3 bytes each cycle we must be sure that SIZEBUF will be not overridden in the next loop k=0; while ((buf8Pos-dispBuffer)<=datasize){ *(buf8Pos++)=Rbyte; *(buf8Pos++)=Gbyte; *(buf8Pos++)=Bbyte; } datasize=(buf8Pos-dispBuffer); #endif //START WRITING TO DISPLAY Displ_SetAddressWindow(x, y, x1, y1); #ifdef Z_RGB565 // transferring RGB666 format dispBuffer times=(area>>(BUFLEVEL-1)); //how many times buffer must be sent via SPI. It is (BUFFLEVEL-1) because area is 16-bit while dispBuffer is 8-bit for (k=0;k<times;k++) { Displ_WriteData(dispBuffer,SIZEBUF,0); } Displ_WriteData(dispBuffer,(area<<1)-(times<<BUFLEVEL),0); #endif #ifdef Z_RGB666 // transferring RGB666 format dispBuffer times=(area/datasize); //how many times buffer must be sent via SPI. for (k=0;k<times;k++) { Displ_WriteData(dispBuffer,datasize,0); } Displ_WriteData(dispBuffer,(area-times*datasize),0); //transfer last data frame #endif //BUFFER SWAP dispBuffer = (dispBuffer==dispBuffer1 ? dispBuffer2 : dispBuffer1); // swapping buffer } #ifndef DISPLAY_USING_TOUCHGFX /***************************************** * WARNING: non tested, never used *****************************************/ void ILI9488_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data, uint32_t size){ Displ_SetAddressWindow(x, y, w+x-1, h+y-1); Displ_WriteData(data,size,0); } void Displ_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data){ #ifdef Z_RGB565 uint32_t size=((uint32_t)w*(uint32_t)h)<<1; #endif #ifdef Z_RGB666 uint32_t size=((uint32_t)w*(uint32_t)h)*3; #endif Displ_SetAddressWindow(x, y, w+x-1, h+y-1); #ifdef EXT_FLASH_BASEADDRESS if (((uint32_t)data>=EXT_FLASH_BASEADDRESS) && ((uint32_t)data<EXT_FLASH_BASEADDRESS+EXT_FLASH_SIZE)) { data-=EXT_FLASH_BASEADDRESS; while (size>SIZEBUF){ Flash_Read((uint32_t)data, dispBuffer , SIZEBUF); Displ_WriteData(dispBuffer,SIZEBUF,0); data+=SIZEBUF; size-=SIZEBUF; dispBuffer = (dispBuffer==dispBuffer1 ? dispBuffer2 : dispBuffer1); // swapping buffer } Flash_Read((uint32_t)data, dispBuffer , size); data=dispBuffer; } #endif Displ_WriteData(data,size,0); dispBuffer = (dispBuffer==dispBuffer1 ? dispBuffer2 : dispBuffer1); // swapping buffer } /*********************** * @brief print a single pixel * @params x, y pixel position on display * color ... to be printed ***********************/ void Displ_Pixel(uint16_t x, uint16_t y, uint16_t color) { if((x >= _width) || (y >= _height)) return; Displ_FillArea(x, y, 1, 1, color); } void Displ_drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; // writePixel(x0 , y0+r, color); Displ_Pixel(x0 , y0+r, color); Displ_Pixel(x0 , y0-r, color); Displ_Pixel(x0+r, y0 , color); Displ_Pixel(x0-r, y0 , color); while (x<y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; Displ_Pixel(x0 + x, y0 + y, color); Displ_Pixel(x0 - x, y0 + y, color); Displ_Pixel(x0 + x, y0 - y, color); Displ_Pixel(x0 - x, y0 - y, color); Displ_Pixel(x0 + y, y0 + x, color); Displ_Pixel(x0 - y, y0 + x, color); Displ_Pixel(x0 + y, y0 - x, color); Displ_Pixel(x0 - y, y0 - x, color); } } /***************** * @brief clear display with a color. * @param bgcolor *****************/ void Displ_CLS(uint16_t bgcolor){ Displ_FillArea(0, 0, _width, _height, bgcolor); } void drawCircleHelper( int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint16_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; while (x<y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x4) { Displ_Pixel(x0 + x, y0 + y, color); Displ_Pixel(x0 + y, y0 + x, color); } if (cornername & 0x2) { Displ_Pixel(x0 + x, y0 - y, color); Displ_Pixel(x0 + y, y0 - x, color); } if (cornername & 0x8) { Displ_Pixel(x0 - y, y0 + x, color); Displ_Pixel(x0 - x, y0 + y, color); } if (cornername & 0x1) { Displ_Pixel(x0 - y, y0 - x, color); Displ_Pixel(x0 - x, y0 - y, color); } } } void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint16_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; while (x<y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x1) { Displ_Line(x0+x, y0-y, x0+x, y0+y+1+delta, color); Displ_Line(x0+y, y0-x,x0+y, y0+x+1+delta, color); } if (cornername & 0x2) { Displ_Line(x0-x, y0-y, x0-x, y0+y+1+delta, color); Displ_Line(x0-y, y0-x, x0-y, y0+x+1+delta, color); } } } void Displ_fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color) { Displ_Line(x0, y0-r, x0, y0+r, color); fillCircleHelper(x0, y0, r, 3, 0, color); } /************************************************************************ * @brief draws a line from "x0","y0" to "x1","y1" of the given "color" ************************************************************************/ void Displ_Line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) { int16_t l,x,steep,ystep,err,dx, dy; if (x0==x1){ // fast solve vertical lines if (y1>y0){ Displ_FillArea(x0, y0, 1, y1-y0+1, color); } else { Displ_FillArea(x0, y1, 1, y0-y1+1, color); } return; } if (y0==y1){ // fast solve horizontal lines if (x1>x0) Displ_FillArea(x0, y0, x1-x0+1, 1, color); else Displ_FillArea(x1, y1, x0-x1+1, 1, color); return; } steep = (y1>y0 ? y1-y0 : y0-y1) > (x1>x0 ? x1-x0 : x0-x1); if (steep) { _swap_int16_t(x0, y0); _swap_int16_t(x1, y1); } if (x0 > x1) { _swap_int16_t(x0, x1); _swap_int16_t(y0, y1); } dx = x1 - x0; err = dx >> 1; if (y0 < y1) { dy = y1-y0; ystep = 1 ; } else { dy = y0-y1; ystep = -1 ; } l=00; for (x=x0; x<=x1; x++) { l++; err -= dy; if (err < 0) { if (steep) { Displ_FillArea(y0, x0, 1, l, color); } else { Displ_FillArea(x0, y0, l, 1, color); } y0 += ystep; l=0; x0=x+1; err += dx; } } if (l!=0){ if (steep) { Displ_FillArea(y0, x0, 1, l-1, color); } else { Displ_FillArea(x0, y0, l-1,1, color); } } } /*********************** * @brief print an empty rectangle of a given thickness * @params x, y top left corner * w, h width and height * t border thickness * color border color, inner part unchanged ***********************/ void Displ_Border(int16_t x, int16_t y, int16_t w, int16_t h, int16_t t, uint16_t color){ Displ_FillArea(x, y, w, t, color); Displ_FillArea(x, y+h-t, w, t, color); Displ_FillArea(x, y, t, h, color); Displ_FillArea(x+w-t, y, t, h, color); } void Displ_drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) { Displ_Line(x0, y0, x1, y1, color); Displ_Line(x1, y1, x2, y2, color); Displ_Line(x2, y2, x0, y0, color); } void Displ_fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) { int16_t a, b, y, last; // Sort coordinates by Y order (y2 >= y1 >= y0) if (y0 > y1) { _swap_int16_t(y0, y1); _swap_int16_t(x0, x1); } if (y1 > y2) { _swap_int16_t(y2, y1); _swap_int16_t(x2, x1); } if (y0 > y1) { _swap_int16_t(y0, y1); _swap_int16_t(x0, x1); } if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing a = b = x0; if(x1 < a) a = x1; else if(x1 > b) b = x1; if(x2 < a) a = x2; else if(x2 > b) b = x2; // drawFastHLine(a, y0, b-a+1, color); Displ_Line(a, y0, b, y0, color); return; } int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1, dy12 = y2 - y1; int32_t sa = 0, sb = 0; // For upper part of triangle, find scanline crossings for segments // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 // is included here (and second loop will be skipped, avoiding a /0 // error there), otherwise scanline y1 is skipped here and handled // in the second loop...which also avoids a /0 error here if y0=y1 // (flat-topped triangle). if(y1 == y2) last = y1; // Include y1 scanline else last = y1-1; // Skip it for(y=y0; y<=last; y++) { a = x0 + sa / dy01; b = x0 + sb / dy02; sa += dx01; sb += dx02; /* longhand: a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if(a > b) _swap_int16_t(a,b); // drawFastHLine(a, y, b-a+1, color); Displ_Line(a, y, b, y, color); } // For lower part of triangle, find scanline crossings for segments // 0-2 and 1-2. This loop is skipped if y1=y2. sa = (int32_t)dx12 * (y - y1); sb = (int32_t)dx02 * (y - y0); for(; y<=y2; y++) { a = x1 + sa / dy12; b = x0 + sb / dy02; sa += dx12; sb += dx02; /* longhand: a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if(a > b) _swap_int16_t(a,b); // drawFastHLine(a, y, b-a+1, color); Displ_Line(a, y, b, y, color); } } /*********************** * @brief display one character on the display * @param x,y: top left corner of the character to be printed * ch, font, color, bgcolor: as per parameter name * size: (1 or 2) single or double wided printing **********************/ void Displ_WChar(uint16_t x, uint16_t y, char ch, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor) { uint32_t i, b, bytes, j, bufSize, mask; const uint8_t *pos; uint8_t wsize=font.Width; //printing char width if (size==2) wsize<<= 1; bufSize=0; bytes=font.Height * font.Size ; pos=font.table+(ch - 32) * bytes ;//that's char position in table switch (font.Size) { case 3: mask=0x800000; break; case 2: mask=0x8000; break; default: mask=0x80; } #ifdef Z_RGB565 uint16_t color1, bgcolor1; uint16_t *dispBuffer16=(uint16_t *)dispBuffer; color1 = ((color & 0xFF)<<8 | (color >> 8)); //swapping byte endian: STM32 is little endian, ST7735 is big endian bgcolor1 = ((bgcolor & 0xFF)<<8 | (bgcolor >> 8)); //swapping byte endian: STM32 is little endian, ST7735 is big endian for(i = 0; i < (bytes); i+=font.Size){ b=0; switch (font.Size) { case 3: b=pos[i]<<16 | pos[i+1]<<8 | pos[i+2]; break; case 2: b=pos[i]<<8 | pos[i+1]; break; default: b=pos[i]; } for(j = 0; j < font.Width; j++) { if((b << j) & mask) { dispBuffer16[bufSize++] = color1; if (size==2){ dispBuffer16[bufSize++] = color1; } } else { dispBuffer16[bufSize++] = bgcolor1; if (size==2) { dispBuffer16[bufSize++] = bgcolor1; } } } } bufSize<<=1; #endif #ifdef Z_RGB666 // setting up char image in RGB666 format uint8_t Rcol=(color & 0xF800)>>8; uint8_t Gcol=(color & 0x07E0)>>3; uint8_t Bcol=(color & 0x001F)<<3; uint8_t Rbak=(bgcolor & 0xF800)>>8; uint8_t Gbak=(bgcolor & 0x07E0)>>3; uint8_t Bbak=(bgcolor & 0x001F)<<3; for(i = 0; i < (bytes); i+=font.Size){ b=0; switch (font.Size) { case 3: b=pos[i]<<16 | pos[i+1]<<8 | pos[i+2]; break; case 2: b=pos[i]<<8 | pos[i+1]; break; default: b=pos[i]; } for(j = 0; j < font.Width; j++) { if((b << j) & mask) { dispBuffer[bufSize++] = Rcol; dispBuffer[bufSize++] = Gcol; dispBuffer[bufSize++] = Bcol; if (size==2){ dispBuffer[bufSize++] = Rcol; dispBuffer[bufSize++] = Gcol; dispBuffer[bufSize++] = Bcol; } } else { dispBuffer[bufSize++] = Rbak; dispBuffer[bufSize++] = Gbak; dispBuffer[bufSize++] = Bbak; if (size==2) { dispBuffer[bufSize++] = Rbak; dispBuffer[bufSize++] = Gbak; dispBuffer[bufSize++] = Bbak; } } } } #endif Displ_SetAddressWindow(x, y, x+wsize-1, y+font.Height-1); Displ_WriteData(dispBuffer,bufSize,0); dispBuffer = (dispBuffer==dispBuffer1 ? dispBuffer2 : dispBuffer1); // swapping buffer } void Displ_drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) { int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis if(r > max_radius) r = max_radius; Displ_Line(x+r, y, x+w-r-1, y, color); Displ_Line(x+r, y+h-1, x-1+w-r, y+h-1, color); Displ_Line(x, y+r, x, y-1+h-r, color); // Left Displ_Line(x+w-1, y+r, x+w-1, y-1+h-r, color); // Right drawCircleHelper(x+r , y+r , r, 1, color); drawCircleHelper(x+w-r-1, y+r , r, 2, color); drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color); drawCircleHelper(x+r , y+h-r-1, r, 8, color); } void Displ_fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) { int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis if(r > max_radius) r = max_radius; Displ_FillArea(x+r, y, w-2*r, h, color); fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color); fillCircleHelper(x+r , y+r, r, 2, h-2*r-1, color); } /************************ * @brief print a string on display starting from a defined position * @params x, y top left area-to-print corner * str string to print * font to bu used * size 1 (normal), 2 (double width) * color font color * bgcolor background color ************************/ void Displ_WString(uint16_t x, uint16_t y, const char* str, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor) { uint16_t delta=font.Width; if (size>1) delta<<=1; while(*str) { /* * these rows split oversize string in more screen lines if(x + font.Width >= _width) { x = 0; y += font.Height; if(y + font.Height >= _height) { break; } if(*str == ' ') { // skip spaces in the beginning of the new line str++; continue; } } */ Displ_WChar(x, y, *str, font, size, color, bgcolor); x += delta; str++; } } /************************ * @brief print a string on display centering into a defined area * @params x0, y0 top left area corner * x1, y1 bottom right corner * str string to print * font to bu used * size 1 (normal), 2 (double width) * color font color * bgcolor background color ************************/ void Displ_CString(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, const char* str, sFONT font, uint8_t size, uint16_t color, uint16_t bgcolor) { uint16_t x,y; uint16_t wsize=font.Width; static uint8_t cambia=0; if (size>1) wsize<<=1; if ((strlen(str)*wsize)>(x1-x0+1)) x=x0; else x=(x1+x0+1-strlen(str)*wsize) >> 1; if (font.Height>(y1-y0+1)) y=y0; else y=(y1+y0+1-font.Height) >> 1; if (x>x0){ Displ_FillArea(x0,y0,x-x0,y1-y0+1,bgcolor); } else x=x0; // fixing here mistake could be due to roundings: x lower than x0. if (x1>(strlen(str)*wsize+x0)) Displ_FillArea(x1-x+x0-1,y0,x-x0+1,y1-y0+1,bgcolor); if (y>y0){ Displ_FillArea(x0,y0,x1-x0+1,y-y0,bgcolor); } else y=y0; //same comment as above if (y1>=(font.Height+y0)) Displ_FillArea(x0,y1-y+y0,x1-x0+1,y-y0+1,bgcolor); cambia = !cambia; Displ_WString(x, y, str, font, size, color, bgcolor); } #endif /************************************** * @brief set backlight level * PLEASE NOTE: if not in "DIMMING MODE" only 'F', '1', '0' and 'Q' available * @param cmd 'S' put display in stby (light level=BKLIT_STBY_LEVEL) * 'W' wake-up from stdby restoring previous level * '+' add 1 step to the current light level * '-' reduce 1 step to the current light level * 'F','1' set the display level to max * '0' set the display level to 0 (off) * 'I' 'Initialize' IT MUST BE in dimming mode * 'Q' do nothing, just return current level * @return current backlight level **************************************/ uint32_t Displ_BackLight(uint8_t cmd) { #ifdef DISPLAY_DIMMING_MODE static uint16_t memCCR1=0; //it stores CCR1 value while in stand-by #endif switch (cmd) { case 'Q': __NOP(); break; #ifndef DISPLAY_DIMMING_MODE case 'F': case '1': HAL_GPIO_WritePin(DISPL_LED_GPIO_Port, DISPL_LED_Pin, GPIO_PIN_SET); break; case '0': HAL_GPIO_WritePin(DISPL_LED_GPIO_Port, DISPL_LED_Pin, GPIO_PIN_RESET); break; #else case 'F': case '1': BKLIT_TIMER->BKLIT_CCR=BKLIT_TIMER->ARR; break; case '0': BKLIT_TIMER->BKLIT_CCR=0; break; case 'W': BKLIT_TIMER->BKLIT_CCR=memCCR1; //restore previous level break; case 'S': memCCR1=BKLIT_TIMER->BKLIT_CCR; if (BKLIT_TIMER->BKLIT_CCR>=(BKLIT_STBY_LEVEL)) //set stby level only if current level is higher BKLIT_TIMER->BKLIT_CCR=(BKLIT_STBY_LEVEL); break; case '+': if (BKLIT_TIMER->ARR>BKLIT_TIMER->BKLIT_CCR) // if CCR1 has not yet the highest value (ARR) ++BKLIT_TIMER->BKLIT_CCR; else BKLIT_TIMER->BKLIT_CCR=BKLIT_TIMER->ARR; break; case '-': if (BKLIT_TIMER->BKLIT_CCR>0) // if CCR1 has not yet the lowest value (0) --BKLIT_TIMER->BKLIT_CCR; else BKLIT_TIMER->BKLIT_CCR=0; break; case 'I': HAL_TIM_PWM_Start(&BKLIT_T, BKLIT_CHANNEL); BKLIT_TIMER->BKLIT_CCR=BKLIT_INIT_LEVEL; break; #endif default: break; } #ifndef DISPLAY_DIMMING_MODE return HAL_GPIO_ReadPin(DISPL_LED_GPIO_Port, DISPL_LED_Pin); #else return (BKLIT_TIMER->BKLIT_CCR); #endif } /********************************************************* * @brief TouchGFX integration: returns status of * communication to the display * @return 1 = there is a transmission running * 0 = no transmission *********************************************************/ int touchgfxDisplayDriverTransmitActive(){ // using the flag indicating SPI port availability // already used to drive communication via DMA return (!Displ_SpiAvailable); } /********************************************************* * @brief TouchGFX integration: write to display the * block indicated by parameters *********************************************************/ void touchgfxDisplayDriverTransmitBlock(const uint8_t* pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h){ //START WRITING TO DISPLAY Displ_SetAddressWindow(x, y, x+w-1, y+h-1); Displ_WriteData((uint8_t* )pixels,((w*h)<<1),1); } /********************************************************* * @brief TouchGFX integration: this is the callback * function run by timer interrupt implementing * the tick timer for TouchGFX *********************************************************/ #ifdef DISPLAY_USING_TOUCHGFX void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef * htim){ if (htim==&TGFX_T){ touchgfxSignalVSync(); } } #endif //DISPLAY_USING_TOUCHGFX ``` ::: #### z_touch_XPT2046.h :::spoiler ```c= /* * z_touch_XPT2046.h * rel. TouchGFX.1.30 * * Created on: 05 giu 2023 * Author: mauro * * licensing: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32/blob/c097f0e7d569845c1cf98e8d930f2224e427fd54/LICENSE * * Installing and using this library follow instruction on: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32 * * WARNING: * in main.h put the #insert of this file BELOW the #insert of z_displ_ILIxxxx.h * * If using TouchGFX, * you have also to add the below include: #include "main.h" * into STM32TouchController.cpp file * changing also sampleTouch() * as shown here: bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y) { return ((bool) Touch_TouchGFXSampleTouch(&x, &y)); } * * see also z_displ_ili9XXX.h * */ #ifndef __XPT2046_H #define __XPT2046_H /*||||||||||| USER/PROJECT PARAMETERS |||||||||||*/ /***************** STEP 1 ***************** **************** PORT PARAMETERS ***************** ** properly set the below the 2 defines to address ******** the SPI port defined on CubeMX *********/ #define TOUCH_SPI_PORT hspi2 #define TOUCH_SPI SPI2 /***************** STEP 2 ***************** ********** KEY REPEAT FOR TOUCHGFX *********** * used only in TouchGFX integration * - set a value above 0 defining the timeout (ms) * before starting key repeat, reading touch sensor * - set 0 disabling key repeat (single pulse) * - set -1 for a continuous touch needed by * "dragging" widgets * (see GitHub page indicated on top for details) **************************************************/ #define DELAY_TO_KEY_REPEAT -1 /*|||||||| END OF USER/PROJECT PARAMETERS ||||||||*/ /*|||||||||||||| DEVICE PARAMETERS |||||||||||||||||*/ /* you should need to change nothing from here on */ /************************************************** * this is the command to send to XPT2046 asking to * poll axis and return corresponging value. **************************************** **********/ #define X_AXIS 0xD0 #define Y_AXIS 0x90 #define Z_AXIS 0xB0 /********************************************************************************** * polling XPT2046 axis, the returning value exceeding the below limit * indicates there is no touch. WARNING: a "random within limit" value is returned * sometimes (often) even if there is no touch, so at least two consecutive r * eadings must be performed to confirm a touch **********************************************************************************/ #ifdef ILI9341 #define X_THRESHOLD 0x0200 //below threeshold there is no touch #define Z_THRESHOLD 0x0200 //below threeshold there is no touch #endif #ifdef ILI9488 #define X_THRESHOLD 0x0500 //below threeshold there is no touch #define Z_THRESHOLD 0x0500 //below threeshold there is no touch #endif /********************************************************************************** ***************************** CALIBRATION PARAMETERS ***************************** ********************************************************************************** * parameters for the linear conversion from a touch sensor reading, to * the XY display position * using the formula: * Xdispl = AX * Xtouch + BX * Ydispl = AY * Ytouch + BY * **********************************************************************************/ #ifdef ILI9341 #define T_ROTATION_0 #define AX 0.00801f #define BX -11.998f #define AY 0.01119f #define BY -39.057f /* #define AX -0.00801f #define BX 320.0f #define AY -0.01119f #define BY 240.0f */ #endif #ifdef ILI9488_V1 #define T_ROTATION_270 #define AX 0.016f #define BX -20.0f #define AY 0.011f #define BY -15.0f #endif #ifdef ILI9488_V2 #define T_ROTATION_0 #define AX -0.0112f #define BX 336.0f #define AY 0.0166f #define BY -41.38f #endif /********************************************************************************** * parameters screen/touch orientation: set the touch orientation to the corresponding * screen orientation: * on ILI9341 0° on touch correspond to 0° of the screen * on ILI9341 0° on touch correspond to 270° of the screen * set also the size of a 0° row and a 90° row (a 0° height) **********************************************************************************/ #ifdef T_ROTATION_0 #define TOUCH0 Displ_Orientat_0 #define TOUCH90 Displ_Orientat_90 #define TOUCH180 Displ_Orientat_180 #define TOUCH270 Displ_Orientat_270 #define TOUCH_0_WIDTH DISPL_WIDTH #define TOUCH_0_HEIGHT DISPL_HEIGHT #endif #ifdef T_ROTATION_90 #define TOUCH0 Displ_Orientat_90 #define TOUCH90 Displ_Orientat_180 #define TOUCH180 Displ_Orientat_270 #define TOUCH270 Displ_Orientat_0 #define TOUCH_0_WIDTH DISPL_HEIGHT #define TOUCH_0_HEIGHT DISPL_WIDTH #endif #ifdef T_ROTATION_180 #define TOUCH0 Displ_Orientat_180 #define TOUCH90 Displ_Orientat_270 #define TOUCH180 Displ_Orientat_0 #define TOUCH270 Displ_Orientat_90 #define TOUCH_0_WIDTH DISPL_WIDTH #define TOUCH_0_HEIGHT DISPL_HEIGHT #endif #ifdef T_ROTATION_270 #define TOUCH0 Displ_Orientat_270 #define TOUCH90 Displ_Orientat_0 #define TOUCH180 Displ_Orientat_90 #define TOUCH270 Displ_Orientat_180 #define TOUCH_0_WIDTH DISPL_HEIGHT #define TOUCH_0_HEIGHT DISPL_WIDTH #endif /*|||||||||||||| INTERFACE PARAMETERS |||||||||||||||||*/ /********************************************************************************** * next parameters are used only in TouchGFX: in Touch_TouchGFXSampleTouch() and * Touch_GotATouch(), helping in using dragging widgets like scrolling lists * You can try to change the below parameters only if your display looses quality or over-used **********************************************************************************/ #define TOUCHGFX_TIMING 60 //delay between 2 consecutive Touch_GotATouch(2)readings (0=disabled) #define TOUCHGFX_SENSITIVITY 1 //square of X pixels size having the same value (1 disabled) #define TOUCHGFX_MOVAVG 1 //makes position based on average of the last X readings (1 disabled) #define TOUCHGFX_REPEAT_IT 0 // after a long touch (dragging) repeat X times last position (0=disabled) #if DELAY_TO_KEY_REPEAT==-1 #define TOUCHGFX_REPEAT_NO 0 // after a REPEAT_IT repeat X times a no touch (0=disabled) #else #define TOUCHGFX_REPEAT_NO 5 // after a REPEAT_IT repeat X times a no touch (0=disabled) #endif /*||||||||||| END OF INTERFACE PARAMETERS ||||||||||||*/ /*|||||||||||||| FUNCTION DECLARATIONS |||||||||||||||||*/ void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin); void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); uint8_t Touch_In_XY_area(uint16_t xpos,uint16_t ypos,uint16_t width,uint16_t height); uint8_t Touch_GotATouch(uint8_t reset); uint8_t Touch_WaitForUntouch(uint16_t delay); uint8_t Touch_WaitForTouch(uint16_t delay); uint8_t Touch_PollTouch(); void Touch_GetXYtouch(uint16_t *x, uint16_t *y, uint8_t *isTouch); #ifdef DISPLAY_USING_TOUCHGFX uint8_t Touch_TouchGFXSampleTouch(int32_t *x, int32_t *y); #endif /* DISPLAY_USING_TOUCHGFX */ #endif /* __XPT2046_H */ ``` ::: #### z_touch_XPT2046.c :::spoiler ```c= /* * z_touch_XPT2046.c * rel. TouchGFX.1.30 * * Created on: 5 giu 2023 * Author: mauro * * licensing: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32/blob/c097f0e7d569845c1cf98e8d930f2224e427fd54/LICENSE * * Install and use this library following instruction on: https://github.com/maudeve-it/ILI9XXX-XPT2046-STM32 * */ #include "main.h" extern SPI_HandleTypeDef TOUCH_SPI_PORT; volatile extern uint8_t Displ_SpiAvailable; // 0 if SPI is busy or 1 if it is free (transm cplt) extern Displ_Orientat_e current_orientation; // indicates the active display orientation. Set by Displ_Orientation volatile uint8_t Touch_PenDown=0; // set to 1 by pendown interrupt callback, reset to 0 by sw volatile uint8_t Touch_Int_Enabled=1; // while reading touch sensor touch interrupt handling is disabled through this flag void Touch_HandlePenDownInterrupt (){ if (Touch_Int_Enabled) { Touch_PenDown=1; } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if (GPIO_Pin==TOUCH_INT_Pin){ Touch_HandlePenDownInterrupt(); } } void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin){ HAL_GPIO_EXTI_Callback(GPIO_Pin); } void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin){ HAL_GPIO_EXTI_Callback(GPIO_Pin); } /****************************************** * @brief enable touch, disabling display * set SPI baudrate as needed ******************************************/ void Touch_Select(void) { if (TOUCH_SPI==DISPL_SPI){ // if touch and display share the same SPI port if (!HAL_GPIO_ReadPin(DISPL_CS_GPIO_Port, DISPL_CS_Pin)) { // if display selected while (!Displ_SpiAvailable) {}; // waiting for completing display communication. Flag is set to 1 by transmission-complete interrupt callback HAL_GPIO_WritePin(DISPL_CS_GPIO_Port, DISPL_CS_Pin, GPIO_PIN_SET); // unselect display } SET_TOUCH_SPI_BAUDRATE; //change SPI port speed as per display needs HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_RESET); } } /****************************************** * @brief disable touch ******************************************/ void Touch_UnSelect(void) { if (TOUCH_SPI==DISPL_SPI){ // if touch and display share the same SPI port HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_SET); // unselect touch } } /******************************************************************************* * @brief Poll display for the current level of X, Y, or Z * @params axis use only one of the three options X_AXIS, Y_AXIS or Z_AXIS * @return the level measured on the "axis" axis * PLEASE NOTE this function should be only for internal usage * Use Touch_GetXYTouch() instead *******************************************************************************/ uint16_t Touch_PollAxis(uint8_t axis) { uint8_t poll[2] = {0,0}; uint32_t poll16; if (TOUCH_SPI==DISPL_SPI){ // if touch and display share the same SPI port Touch_Select(); // enable CS on touch device } Touch_Int_Enabled=0; //disable interrupt handling: sensor reading triggers interrupt // disable interrupt while enquiring the touch sensor because it triggers the interrupt pin HAL_NVIC_DisableIRQ(TOUCH_INT_EXTI_IRQn); HAL_SPI_Transmit(&TOUCH_SPI_PORT, &axis, 1, 10); if (HAL_SPI_Receive(&TOUCH_SPI_PORT, poll, 2, 10) == HAL_OK) { poll16 = (poll[0]<<8) + poll[1]; } else { poll16 = 0; } //enable back interrupt after reading the sensor HAL_NVIC_ClearPendingIRQ(TOUCH_INT_EXTI_IRQn); HAL_NVIC_EnableIRQ(TOUCH_INT_EXTI_IRQn); Touch_Int_Enabled=1; if (TOUCH_SPI==DISPL_SPI){ // if touch and display share the same SPI port Touch_UnSelect(); } return poll16; } /********************************************************************************* * @brief polls touch screen just on Z axis returning if * it is currently touched. * That's regardless touch recording flag (interrupt received) * @return 1/0 1 if detected a touch, otherwise 0; *********************************************************************************/ uint8_t Touch_PollTouch(){ const uint8_t pollingLevel=4; uint8_t k; uint32_t touch; // reading Z making an average over (1<<pollingLevel) readings touch=0; for (k=0;k<(1<<pollingLevel);k++) touch += Touch_PollAxis(Z_AXIS); touch >>= pollingLevel; //get the average value // *isTouch= ((touch<=Z_THRESHOLD) ? 0 : 1); return (touch>Z_THRESHOLD); } /********************************************************************************* * @brief polls touch screen and returning its XY screen position * that's regardless touch recording flag (interrupt received) * @return x,y in case isTouch=1 contain touch coordinates * isTouch is 1 if detected a touch, otherwise 0; *********************************************************************************/ void Touch_GetXYtouch(uint16_t *x, uint16_t *y, uint8_t *isTouch){ const uint8_t pollingLevel=4; //sTouchData XYposition; uint8_t k; uint32_t touchx,touchy,touch; // get the average value (over "1<<pollingLevel" attempts of X, Y and Z axes readings) // reading Z touch=0; for (k=0;k<(1<<pollingLevel);k++) touch += Touch_PollAxis(Z_AXIS); touch >>= pollingLevel; //get the average value if (touch<=Z_THRESHOLD) { *isTouch=0; HAL_NVIC_ClearPendingIRQ(TOUCH_INT_EXTI_IRQn); return; // no touch: return 0 } // reading X touch=0; for (k=0;k<(1<<pollingLevel);k++) touch += Touch_PollAxis(X_AXIS); touch >>= pollingLevel; //get the average value if (touch<=X_THRESHOLD) { *isTouch=0; HAL_NVIC_ClearPendingIRQ(TOUCH_INT_EXTI_IRQn); return; // no touch: return 0 } touchx=(AX*touch+BX); // reading Y - there is no a threshold for Y touch=0; for (k=0;k<(1<<pollingLevel);k++) touch += Touch_PollAxis(Y_AXIS); touch >>= pollingLevel; //get the average value touchy=(AY*touch+BY); //having X and Y axis average values // calculating coordinates as per screen orientation switch (current_orientation) { case TOUCH0: *x=touchx; *y=touchy; break; case TOUCH90: *x=touchy; *y=(TOUCH_0_WIDTH-touchx); break; case TOUCH180: *x=(TOUCH_0_WIDTH-touchx); *y=(TOUCH_0_HEIGHT - touchy); break; case TOUCH270: *x=(TOUCH_0_HEIGHT- touchy); *y=touchx; break; } // set flag indicating there was a touch *isTouch=1; return; } /*********************************************************** * @brief wait for a touch within an assigned time * @params delay max time (ms) waiting for a touch, 0=infinite * #return 1 if touched within "delay" period * 0 if elapsed time with no touch * PLEASE NOTE: doesn't reset Touch recording flag ***********************************************************/ uint8_t Touch_WaitForTouch(uint16_t delay) { uint16_t starttime; starttime = HAL_GetTick(); while (!Touch_PenDown) { if ((delay!=0) && ((HAL_GetTick()-starttime)>delay)) return 0; }; return 1; } /************************************************************* * @brief wait for the pen left within an assigned time * @params delay max time (ms) waiting for leaving touch, 0=infinite * #return 1 if no touch on display * 0 if elapsed time still touching display * PLEASE NOTE if pen up, it resets the touch recording flag *************************************************************/ uint8_t Touch_WaitForUntouch(uint16_t delay) { uint16_t starttime; uint8_t pen_up=0; starttime = HAL_GetTick(); while (1) { if ((delay!=0) && ((HAL_GetTick()-starttime)>delay)) return 0; if (Touch_PollAxis(Z_AXIS)<=Z_THRESHOLD) pen_up=1; // if (Touch_PollAxis(Y_AXIS)>=Y_THRESHOLD) // check on Y_AXIS no more used since introducing ILI9488 // pen_up=1; if (Touch_PollAxis(X_AXIS)<=X_THRESHOLD) pen_up=1; if (pen_up) { // Pen is now up: reset Touch_PenDown anyway. HAL_Delay(10); // pen is Up just now: wait just a few Touch_PenDown=0; return 1; } } } /*********************************************************** * @brief check if there is a touch inside the * display area defined by parameters * @params xpos, * ypos, * width, * height display area to be polled for a touch * @return 1 if there is a touch inside area * 0 if no touch or touch outside area defined ***********************************************************/ uint8_t Touch_In_XY_area(uint16_t xpos,uint16_t ypos,uint16_t width,uint16_t height) { //sTouchData posXY; uint16_t x,y; uint8_t isTouch; Touch_GetXYtouch(&x, &y, &isTouch); if (!isTouch) return 0; if (x>=xpos) if (x<xpos+width) if (y>=ypos) if (y<ypos+height) return 1; return 0; } /*********************************************************** * @brief check if interrupt registered a touch * resetting touch flag if asked by parameter * @params reset 0 returns touch flag value without resetting it * 1 returns touch flag value resetting it * 2 returns touch flag only every TOUCHGFX_TIMING timeinterval. It doesn't reset flag. * (use "2" in main loop activating touchgfxSignalVSync() * @returns 1 if recorded a touch * 0 if no touch recorded ***********************************************************/ uint8_t Touch_GotATouch(uint8_t reset) { static uint32_t touchTime=0; uint8_t result = Touch_PenDown; // if (result) // result=Touch_PollTouch(); if (reset==2){ if ((HAL_GetTick()-touchTime) >= TOUCHGFX_TIMING) touchTime=HAL_GetTick(); else result=0; } if (reset==1) Touch_PenDown=0; return result; } #ifdef DISPLAY_USING_TOUCHGFX /*********************************************************** * @brief Linking function to TouchGFX * Handles key repeat (controlled by PAUSE_TO_KEY_REPEAT) * @return 1 if detected a touch, otherwise 0 * @usage in STM32TouchController.cpp add this line * #include "main.h", * then, into STM32TouchController::sampleTouch(int32_t& x, int32_t& y) * change "return false;" * into: "return ((bool) Touch_TouchGFXSampleTouch(&x, &y));" * that's enough for touch integration in TouchGFX ***********************************************************/ uint8_t Touch_TouchGFXSampleTouch(int32_t *x, int32_t *y){ // sTouchData result; uint8_t isTouch=0; // preset to no touch uint16_t xx=0,yy=0; // need to convert library coordinates type (uint16_t) to TouchGFX ones (int32_t) static uint8_t flipTouch=0; // switches 0/1, on every function call, until sensor is touched allowing to return key repeat static uint32_t touchTime=1; // tick value get on the first touch. 0 means display untouched. static uint16_t avgXX=0, avgYY=0; // need to convert library coordinates type (uint16_t) to TouchGFX one (int32_t) static uint8_t repetition=TOUCHGFX_REPEAT_IT+TOUCHGFX_REPEAT_NO; if (Touch_GotATouch(0)){ // polls interrupt flag not resetting it Touch_GetXYtouch(&xx,&yy,&isTouch); // get touch sensor position if (!isTouch){ // received a "no touch" if (touchTime != 0){ // if previously touched if ((repetition--)>TOUCHGFX_REPEAT_NO+1){ // n-repetition of last touch sending *x = avgXX; *y = avgYY; isTouch=1; } else if ((repetition==255)) { // that's -1 touchTime=0; // set display as untouched Touch_GotATouch(1); // reset interrupt touch flag repetition=TOUCHGFX_REPEAT_IT+TOUCHGFX_REPEAT_NO; //reset repetition counter } } } else { // display touched if (touchTime==0) { // if previously untouched avgXX =(xx/TOUCHGFX_SENSITIVITY)*TOUCHGFX_SENSITIVITY; avgYY =(yy/TOUCHGFX_SENSITIVITY)*TOUCHGFX_SENSITIVITY; touchTime=HAL_GetTick(); // store tick value at touch time flipTouch=1; // set switch to send touch now } else { // not a new touch if (((HAL_GetTick()-touchTime)>DELAY_TO_KEY_REPEAT) && (DELAY_TO_KEY_REPEAT > 0)){ // if timeout to key repeat is over (0 means no key repeat) flipTouch=!flipTouch; // alternate every time function is called } else if (DELAY_TO_KEY_REPEAT == 0) flipTouch=0; // (DELAY_TO_KEY_REPEAT == 0) means a single pulse, "-1" keep pulse as long as touch } if (flipTouch) { // return position only if the switching flag is on *x=(((avgXX*(TOUCHGFX_MOVAVG-1)+((xx/TOUCHGFX_SENSITIVITY)*TOUCHGFX_SENSITIVITY)))/TOUCHGFX_MOVAVG); *y=(((avgYY*(TOUCHGFX_MOVAVG-1)+((yy/TOUCHGFX_SENSITIVITY)*TOUCHGFX_SENSITIVITY)))/TOUCHGFX_MOVAVG); avgXX = *x; avgYY = *y; } else { // otherwise return "no touch" from display isTouch = 0; } } } return isTouch; } #endif ``` ::: #### STM32TouchController.cpp :::spoiler ```c= #include "main.h" #include "z_touch_XPT2046.h" bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y) { /** * By default sampleTouch returns false, * return true if a touch has been detected, otherwise false. * * Coordinates are passed to the caller by reference by x and y. * * This function is called by the TouchGFX framework. * By default sampleTouch is called every tick, this can be adjusted by HAL::setTouchSampleRate(int8_t); * */ return ((bool) Touch_TouchGFXSampleTouch(&x, &y)); } ``` ::: ___ ### touchGFX設定 點擊 ![image](https://hackmd.io/_uploads/H1i6ABvJll.png) ![image](https://hackmd.io/_uploads/rk4H1UvJex.png) ![image](https://hackmd.io/_uploads/SJRSyLD1gg.png) 調整UI介面 ![image](https://hackmd.io/_uploads/H1IuJIDkgg.png) 添加背景 ![image](https://hackmd.io/_uploads/SJPqkLw1eg.png) 把原本的背景刪除 ![image](https://hackmd.io/_uploads/HkFTy8Pklx.png) 可以模擬看看成效 ![image](https://hackmd.io/_uploads/rkiWeLPyxl.png) 完成後就可以生成程式碼了 ![image](https://hackmd.io/_uploads/HJGEeUDyle.png) ## [📖參考資料](https://www.youtube.com/watch?v=g1siKaPox88)