Skip to main content
Associate II
Feb 16, 2026 1:15 PM UTC
Solved

How to translate ILI9341 driver in touchgfx project to HAL version?

TFT-LCD driver:

void touchgfxDisplayDriverTransmitBlock(const uint8_t* pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
 static uint16_t old_x0=0xFFFF, old_x1=0xFFFF, old_y0=0xFFFF, old_y1=0xFFFF;
 IsTransmittingBlock_ = 1;
 
 uint16_t x1 = x + w - 1;
 uint16_t y1 = y + h - 1;

 // Set columns, if changed
 if (x != old_x0 || x1 != old_x1)
 {
 parameter[0] = (x >> 8) & 0xFF;
 parameter[1] = x & 0xFF;
 parameter[2] = (x1 >> 8) & 0xFF;
 parameter[3] = x1 & 0xFF;
 send_cmd(DCS_SET_COLUMN_ADDRESS, parameter, 4);

 old_x0 = x;
 old_x1 = x1;
 }

 // Set rows, if changed
 if (y != old_y0 || y1 != old_y1)
 {
 parameter[0] = (y >> 8) & 0xFF;
 parameter[1] = y & 0xFF;
 parameter[2] = (y1 >> 8) & 0xFF;
 parameter[3] = y1 & 0xFF;
 send_cmd(DCS_SET_PAGE_ADDRESS, parameter, 4);

 old_y0 = y;
 old_y1 = y1;
 }

 send_cmd(DCS_WRITE_MEMORY_START, NULL, 0);
 send_data((uint16_t*)pixels, w * h);
}

int touchgfxDisplayDriverTransmitActive(void)
{
 return IsTransmittingBlock_;
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
 if (GPIO_Pin == DISP_TE_Pin)
 {
 tearingEffectCount++;
 touchgfxSignalVSync();
 }
}

static void send_cmd(uint8_t cmd, uint8_t * parameter, uint16_t size)
{
 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_RESET);
 HAL_GPIO_WritePin(DISP_DCX_GPIO_Port, DISP_DCX_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);

 if (parameter != NULL && size > 0)
 {
 HAL_GPIO_WritePin(DISP_DCX_GPIO_Port, DISP_DCX_Pin, GPIO_PIN_SET);
 HAL_SPI_Transmit(&hspi1, parameter, size, HAL_MAX_DELAY);
 }

 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_SET);
}

static void send_data(uint16_t * data, uint16_t size)
{
 hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
 Error_Handler();
 }

 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_RESET);
 HAL_GPIO_WritePin(DISP_DCX_GPIO_Port, DISP_DCX_Pin, GPIO_PIN_SET);
 HAL_SPI_Transmit_DMA(&hspi1, (const uint8_t *)data, size); 
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi1)
 {
 IsTransmittingBlock_ = 0;
 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_SET);

 hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
 Error_Handler();
 }

 DisplayDriver_TransferCompleteCallback();
 }
}

SPI-FLASH driver: 

extern SPI_HandleTypeDef hspi2;

volatile uint8_t isReceivingData = 0;

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
}

void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 isReceivingData = 0;
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 }
}

I code as above,but the screen displays black line:

Zhuangwei_1-1771246929445.jpeg

I'm not sure whether my 

 

static void send_data(uint16_t * data, uint16_t size)
{
 hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
 Error_Handler();
 }

 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_RESET);
 HAL_GPIO_WritePin(DISP_DCX_GPIO_Port, DISP_DCX_Pin, GPIO_PIN_SET);
 HAL_SPI_Transmit_DMA(&hspi1, (const uint8_t *)data, size); 
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi1)
 {
 IsTransmittingBlock_ = 0;
 HAL_GPIO_WritePin(DISP_CS_GPIO_Port, DISP_CS_Pin, GPIO_PIN_SET);

 hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
 if (HAL_SPI_Init(&hspi1) != HAL_OK)
 {
 Error_Handler();
 }

 DisplayDriver_TransferCompleteCallback();
 }
}

 

and

 

void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 isReceivingData = 0;
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 }
}

 

are  correct or not.
My board: NUCLEO-U083RC + X-NUCLEO-GFX01M2


/**********************************************************************************************************************************/

 

Official TFT-LCD driver:

#include "stm32u0xx_hal.h"
#include "DCS.h"
#include "main.h"
#include "MB1642DisplayDriver.h"
#include <assert.h>

extern SPI_HandleTypeDef hspi1;
extern DMA_HandleTypeDef hdma_spi1_tx;
extern TIM_HandleTypeDef htim6;

volatile uint16_t tearingEffectCount = 0;

//Signal TE interrupt to TouchGFX
void touchgfxSignalVSync(void);
void setVSYNC(void);
void clearVSYNC(void);

static void Display_DCS_Send(uint8_t command)
{
 // Reset the nCS pin
 DISPLAY_CSX_GPIO_Port->BRR = DISPLAY_CSX_Pin;
 // Set the DCX pin
 DISPLAY_DCX_GPIO_Port->BRR = DISPLAY_DCX_Pin;

 // Send the command
 *((__IO uint8_t*)&hspi1.Instance->DR) = command;

 // Wait until the bus is not busy before changing configuration
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}

 // Reset the DCX pin
 DISPLAY_DCX_GPIO_Port->BSRR = DISPLAY_DCX_Pin;

 // Set the nCS
 DISPLAY_CSX_GPIO_Port->BSRR = DISPLAY_CSX_Pin;
}

static uint8_t Display_DCS_Read(uint8_t address)
{
 // Set the clock prescaler to a high number to avoid reading to fast from the display
 const uint32_t originalPrescaler = *((__IO uint32_t*)&hspi1.Instance->CR1) & SPI_CR1_BR_Msk;
 *((__IO uint32_t*)&hspi1.Instance->CR1) &= ~SPI_CR1_BR_Msk;
 *((__IO uint32_t*)&hspi1.Instance->CR1) |= SPI_CR1_BR_Msk & SPI_BAUDRATEPRESCALER_256;

 // Reset the nCS pin
 DISPLAY_CSX_GPIO_Port->BRR = DISPLAY_CSX_Pin;

 // Reset the DCX pin
 DISPLAY_DCX_GPIO_Port->BRR = DISPLAY_DCX_Pin;

 // Send the command
 *((__IO uint8_t*)&hspi1.Instance->DR) = address;
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY))
 {}

 // Set the DCX pin
 DISPLAY_DCX_GPIO_Port->BSRR = DISPLAY_DCX_Pin;

 // Empty the RX buffer
 while (hspi1.Instance->SR & SPI_FLAG_RXNE)
 {
	 *((__IO uint8_t*)&hspi1.Instance->DR);
 }

 // Receive the data
 *((__IO uint8_t*)&hspi1.Instance->DR) = 0;
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY))
 {}
 uint8_t data = *((__IO uint8_t*)&hspi1.Instance->DR);

 // Set the nCS
 DISPLAY_CSX_GPIO_Port->BSRR = DISPLAY_CSX_Pin;

 // Set the clock prescaler to the original value
 *((__IO uint32_t*)&hspi1.Instance->CR1) &= ~SPI_CR1_BR_Msk;
 *((__IO uint32_t*)&hspi1.Instance->CR1) |= originalPrescaler;

 return data;
}

static void Display_DCS_Send_With_Data(uint8_t command, uint8_t* data, uint8_t size)
{
 // Reset the nCS pin
 DISPLAY_CSX_GPIO_Port->BRR = DISPLAY_CSX_Pin;
 // Set the DCX pin
 DISPLAY_DCX_GPIO_Port->BRR = DISPLAY_DCX_Pin;

 *((__IO uint8_t*)&hspi1.Instance->DR) = command;

 // Wait until the bus is not busy before changing configuration
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}
 DISPLAY_DCX_GPIO_Port->BSRR = DISPLAY_DCX_Pin;

 while (size > 0U)
 {
 *((__IO uint8_t*)&hspi1.Instance->DR) = *data;
 data++;
 size--;
 /* Wait until TXE flag is set to send data */
 while(((hspi1.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 }

 // Wait until the bus is not busy before changing configuration
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}

 // Set the nCS
 DISPLAY_CSX_GPIO_Port->BSRR = DISPLAY_CSX_Pin;
}

void DisplayDriver_DisplayOn(void)
{
 // Display ON
 Display_DCS_Send(DCS_SET_DISPLAY_ON);
 HAL_Delay(100);
}

void Display_OFF(void)
{
 // Display OFF
 Display_DCS_Send(DCS_SET_DISPLAY_OFF);
 HAL_Delay(100);
}

static uint16_t old_x0=0xFFFF, old_x1=0xFFFF, old_y0=0xFFFF, old_y1=0xFFFF;

void Display_Set_Area(uint16_t x0, uint16_t y0,
 uint16_t x1, uint16_t y1)
{
 uint8_t arguments[4];

 // Set columns, if changed
 if (x0 != old_x0 || x1 != old_x1)
 {
 arguments[0] = x0 >> 8;
 arguments[1] = x0 & 0xFF;
 arguments[2] = x1 >> 8;
 arguments[3] = x1 & 0xFF;
 Display_DCS_Send_With_Data(0x2A, arguments, 4);

 old_x0 = x0;
 old_x1 = x1;
 }

 // Set rows, if changed
 if (y0 != old_y0 || y1 != old_y1)
 {
 arguments[0] = y0 >> 8;
 arguments[1] = y0 & 0xFF;
 arguments[2] = y1 >> 8;
 arguments[3] = y1 & 0xFF;
 Display_DCS_Send_With_Data(0x2B, arguments, 4);

 old_y0 = y0;
 old_y1 = y1;
 }
}

volatile uint8_t IsTransmittingBlock_;
void Display_Bitmap(const uint16_t *bitmap, uint16_t posx, uint16_t posy, uint16_t sizex, uint16_t sizey)
{
 IsTransmittingBlock_ = 1;
 __HAL_SPI_ENABLE(&hspi1); // Enables SPI peripheral
 uint8_t command = DCS_WRITE_MEMORY_START;

 // Define the display area
 Display_Set_Area(posx, posy, posx+sizex-1, posy+sizey-1);

 // Reset the nCS pin
 DISPLAY_CSX_GPIO_Port->BRR = DISPLAY_CSX_Pin;
 // Set the DCX pin
 DISPLAY_DCX_GPIO_Port->BRR = DISPLAY_DCX_Pin;

 *((__IO uint8_t*)&hspi1.Instance->DR) = command;

 // Wait until the bus is not busy before changing configuration
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}
 DISPLAY_DCX_GPIO_Port->BSRR = DISPLAY_DCX_Pin;

 // Set the SPI in 16-bit mode to match endianess
 hspi1.Instance->CR2 = SPI_DATASIZE_16BIT;

 // Disable spi peripherals
 __HAL_SPI_DISABLE(&hspi1);
 __HAL_DMA_DISABLE(&hdma_spi1_tx);

 CLEAR_BIT(hspi1.Instance->CR2, SPI_CR2_LDMATX);

 /* Clear all flags */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi1_tx, (DMA_FLAG_GI1 << (hdma_spi1_tx.ChannelIndex & 0x1cU)));

 /* Configure DMA Channel data length */
 hdma_spi1_tx.Instance->CNDTR = sizex*sizey;
 /* Configure DMA Channel destination address */
 hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi1.Instance->DR;

 /* Configure DMA Channel source address */
 hdma_spi1_tx.Instance->CMAR = (uint32_t)bitmap;

 /* Disable the transfer half complete interrupt */
 __HAL_DMA_DISABLE_IT(&hdma_spi1_tx, DMA_IT_HT);
 /* Enable the transfer complete interrupt */
 __HAL_DMA_ENABLE_IT(&hdma_spi1_tx, (DMA_IT_TC | DMA_IT_TE));

 /* Enable the Peripherals */
 __HAL_DMA_ENABLE(&hdma_spi1_tx);
 __HAL_SPI_ENABLE(&hspi1);

 /* Enable Tx DMA Request */
 SET_BIT(hspi1.Instance->CR2, SPI_CR2_TXDMAEN);
}

void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
 UNUSED(GPIO_Pin);

 if (GPIO_Pin == DISPLAY_TEARING_EFFECT_Pin)
 { 
 // The draw speed of displays varies based on factors such as design, production variance, temperature and so on
 // to achieve good performance on all displays the timer tick rate needs to be adjusted based on the actual draw time of the latest frame
 // The following code dynamically adjusts the prescaler on every new frame to match the draw rate
 const uint32_t TARGET = DCS_DISPLAY_HEIGHT; // Number of lines in display drawing direction
 volatile uint32_t count = __HAL_TIM_GET_COUNTER(&htim6);
 if(count < TARGET - 1 || count > TARGET + 1)
 {
 const uint16_t oldPrescaler = htim6.Instance->PSC;
 const uint16_t newPrescaler = (count * oldPrescaler + TARGET / 2) / TARGET;
 
 // Check if the proposed new prescaler is in a reasonable range
 if (newPrescaler > 1000 && newPrescaler < 5000)
 {
 htim6.Instance->PSC = newPrescaler;
 htim6.Instance->EGR = TIM_EGR_UG; // Reinitialize the counter and generates an update of the registers
 }
 }
 (&htim6)->Instance->CR1 &= ~(TIM_CR1_CEN);
 (&htim6)->Instance->CNT = 0;
 
 tearingEffectCount++;

 // VSync has occurred, increment TouchGFX engine vsync counter
 touchgfxSignalVSync();
 setVSYNC();
 }
}

void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
 UNUSED(GPIO_Pin);
 (&htim6)->Instance->CR1 = (TIM_CR1_CEN);

 clearVSYNC();
}

void DisplayDriver_DisplayInit(void)
{
 uint8_t arguments[4];
 __HAL_SPI_ENABLE(&hspi1);
 // Sleep out
 Display_DCS_Send(DCS_EXIT_SLEEP_MODE);
 HAL_Delay(100);

 // Display Normal mode
 Display_DCS_Send(DCS_ENTER_NORMAL_MODE);
 HAL_Delay(100);

 // The GFX01M2-AZ2 display (Driver IC ILI9341) needs to have the colors and X-axis inverted
 const uint8_t id = Display_DCS_Read(0xDC); // Read RDID3
 if (id == 0x00) // RDID3 for the GFX01M2-AZ2 is 0x00
 {
 // MADCTL: Exchange RGB / BGR + Mirror X
 arguments[0] = 0x48;
 Display_DCS_Send_With_Data(DCS_SET_ADDRESS_MODE, arguments, 1);
 HAL_Delay(100);
 
 // Set frame rate of AZ2 to 60 fps
 arguments[0] = 0x00;
 arguments[1] = 0x1F;
 Display_DCS_Send_With_Data(DCS_FRMCTR1, arguments, 2);
 HAL_Delay(100);
 }

 // Pixel Format
 arguments[0] = 0x05; // RGB565
 Display_DCS_Send_With_Data(DCS_SET_PIXEL_FORMAT, arguments, 1);
 HAL_Delay(100);

 // Tearing effect line on
 arguments[0] = 0; //0x00;
 Display_DCS_Send_With_Data(DCS_SET_TEAR_ON, arguments, 1);
 HAL_Delay(100);

 // Tearing effect scan line
 arguments[0] = 0;
 arguments[1] = 0;
 Display_DCS_Send_With_Data(DCS_SET_TEAR_SCANLINE, arguments, 2);
 HAL_Delay(100);
}

void DisplayDriver_DisplayReset(void)
{
 HAL_GPIO_WritePin(DISPLAY_RESET_GPIO_Port, DISPLAY_RESET_Pin, GPIO_PIN_RESET);
 HAL_Delay(100);
 HAL_GPIO_WritePin(DISPLAY_RESET_GPIO_Port, DISPLAY_RESET_Pin, GPIO_PIN_SET);
 HAL_Delay(100);
}

void DisplayDriver_Init(void)
{
 /* DMA interrupt init */
 /* DMA1_Channel1_IRQn interrupt configuration */
 HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

int touchgfxDisplayDriverTransmitActive(void)
{
 return IsTransmittingBlock_;
}

void touchgfxDisplayDriverTransmitBlock(const uint8_t* pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
 Display_Bitmap((uint16_t*)pixels, x, y, w, h);
}

void DisplayDriver_DMACallback(void)
{
 /* Transfer Complete Interrupt management ***********************************/
 if ((0U != (DMA1->ISR & (DMA_FLAG_TC1))) && (0U != (hdma_spi1_tx.Instance->CCR & DMA_IT_TC)))
 {
 /* Disable the transfer complete and error interrupt */
 __HAL_DMA_DISABLE_IT(&hdma_spi1_tx, DMA_IT_TE | DMA_IT_TC);

 /* Clear the transfer complete flag */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi1_tx, DMA_FLAG_TC1);

 IsTransmittingBlock_ = 0;

 // Wait until the bus is not busy before changing configuration
 // SPI is busy in communication or Tx buffer is not empty
 while(((hspi1.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}

 // Set the nCS
 DISPLAY_CSX_GPIO_Port->BSRR = DISPLAY_CSX_Pin;

 // Go back to 8-bit mode
 hspi1.Instance->CR2 = SPI_DATASIZE_8BIT;

 // Signal Transfer Complete to TouchGFX
 DisplayDriver_TransferCompleteCallback();
 }
 /* Transfer Error Interrupt management **************************************/
 else if ((0U != (DMA1->ISR & (DMA_FLAG_TC1))) && (0U != (hdma_spi1_tx.Instance->CCR & DMA_IT_TE)))
 {
 /* When a DMA transfer error occurs */
 /* A hardware clear of its EN bits is performed */
 /* Disable ALL DMA IT */
 __HAL_DMA_DISABLE_IT(&hdma_spi1_tx, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE));

 /* Clear all flags */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi1_tx, DMA_FLAG_GI1 );

 assert(0); // Halting program - Transfer Error Interrupt received.
 }
}

int touchgfxDisplayDriverShouldTransferBlock(uint16_t bottom)
{
 // Only allow block transfer if the display has drawn past the bottom of the requested block (plus a margin of two lines)
 // A timer is used to estimate how many lines have been drawn by setting the prescaler so the tick rate matches the line draw rate
 uint16_t lastLineDrawn = htim6.Instance->CNT;
 return bottom + 2 < lastLineDrawn || tearingEffectCount > 0;
}

Offcial SPI-FLASH driver:

#include "MB1642DataReader.h"
#include "stm32u0xx_hal.h"
#include "main.h"

#define CMD_RDID 0x9F
#define CMD_READ 0x03
#define CMD_WREN 0x06
#define CMD_PP 0x02
#define CMD_RDSR 0x05
#define CMD_SE 0xD8
#define STATUS_WIP 0x01

extern SPI_HandleTypeDef hspi2;

//private variables
//DMA handles for reading pixels from SPI peripheral
extern DMA_HandleTypeDef hdma_spi2_rx;
extern DMA_HandleTypeDef hdma_spi2_tx;

//Status flag. Non-zero when receiving data
static volatile uint8_t isReceivingData = 0;

void readData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 __HAL_SPI_ENABLE(&hspi2);
 FLASH_CS_GPIO_Port->BRR = FLASH_CS_Pin;
 *((__IO uint8_t*)&SPI2->DR) = CMD_READ;

 //clock out address
 *((__IO uint8_t*)&hspi2.Instance->DR) = (address24 >> 16) & 0xFF;

 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR) = (address24 >> 8) & 0xFF;

 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR) = address24 & 0xFF;

 switch (length)
 {
 default:
 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR) = 0 ;
 case 3:
 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR) = 0;
 case 2:
 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR) = 0;
 case 1:
 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR) = 0;
 case 0:
 break;
 }
 switch (length)
 {
 case 1:
 *((__IO uint8_t*)&hspi2.Instance->DR);
 case 2:
 *((__IO uint8_t*)&hspi2.Instance->DR);
 case 3:
 *((__IO uint8_t*)&hspi2.Instance->DR);
 default:
 break;
 }

 uint8_t* const buf_end = buffer + length - 4;

 while ((buf_end - buffer) > 3)
 {
 while(((SPI2->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 *((__IO uint8_t*)&SPI2->DR) = 0;
 while(((SPI2->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 *((__IO uint8_t*)&SPI2->DR) = 0;
 while(((SPI2->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 *((__IO uint8_t*)&SPI2->DR) = 0;
 while(((SPI2->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 *((__IO uint8_t*)&SPI2->DR) = 0;
 }

 while (buffer < buf_end)
 {
 while(((SPI2->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 *((__IO uint8_t*)&SPI2->DR) = 0;
 }

 /* Wait until the bus is ready before releasing Chip select */
 while(((hspi2.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}

 FLASH_CS_GPIO_Port->BSRR = FLASH_CS_Pin;
 const int rest = length < 4 ? length : 4;
 for (int i = 0; i<rest; i++)
 {
 *buffer++ = *((__IO uint8_t*)&SPI2->DR);
 }
}

void readDataDMA(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 __HAL_SPI_ENABLE(&hspi2);

 // Pull Flash CS pin low
 isReceivingData = 1;
 FLASH_CS_GPIO_Port->BRR = FLASH_CS_Pin;

 *((__IO uint8_t*)&hspi2.Instance->DR) = CMD_READ;

 //clock out address
 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR) = (address24 >> 16) & 0xFF;

 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR) = (address24 >> 8) & 0xFF;

 while(((hspi2.Instance->SR) & SPI_FLAG_TXE) != SPI_FLAG_TXE)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR) = address24 & 0xFF;

 /* Wait until the bus is ready before reading 4 dummy bytes */
 while(((hspi2.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR);
 *((__IO uint8_t*)&hspi2.Instance->DR);

 /* Reset the threshold bit */
 CLEAR_BIT(hspi2.Instance->CR2, SPI_CR2_LDMATX | SPI_CR2_LDMARX);

 /* Set RX Fifo threshold according the reception data length: 8bit */
 SET_BIT(hspi2.Instance->CR2, SPI_RXFIFO_THRESHOLD);

 /******** RX ****************/
 /* Disable the peripheral */
 __HAL_DMA_DISABLE(&hdma_spi2_rx);

 /* Clear all flags */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi2_rx, (DMA_FLAG_GI1 << (hdma_spi2_rx.ChannelIndex & 0x1cU)));

 /* Configure DMA Channel data length */
 hdma_spi2_rx.Instance->CNDTR = length;

 /* Configure DMA Channel destination address */
 hdma_spi2_rx.Instance->CPAR = (uint32_t)&hspi2.Instance->DR;

 /* Configure DMA Channel source address */
 hdma_spi2_rx.Instance->CMAR = (uint32_t)buffer;

 __HAL_DMA_DISABLE_IT(&hdma_spi2_rx, DMA_IT_HT | DMA_IT_TE);
 __HAL_DMA_ENABLE_IT(&hdma_spi2_rx, (DMA_IT_TC));

 /* Enable the Peripheral */
 __HAL_DMA_ENABLE(&hdma_spi2_rx);

 /* Enable Rx DMA Request */
 SET_BIT(hspi2.Instance->CR2, SPI_CR2_RXDMAEN);

 /******** TX ****************/
 /* Disable the peripheral */
 __HAL_DMA_DISABLE(&hdma_spi2_tx);

 /* Clear all flags */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi2_tx, (DMA_FLAG_GI1 << (hdma_spi2_tx.ChannelIndex & 0x1cU)));

 /* Configure DMA Channel data length */
 hdma_spi2_tx.Instance->CNDTR = length;

 /* Configure DMA Channel destination address */
 hdma_spi2_tx.Instance->CPAR = (uint32_t)&hspi2.Instance->DR;

 /* Configure DMA Channel source address */
 hdma_spi2_tx.Instance->CMAR = (uint32_t)buffer;

 /* Enable the Peripheral */
 __HAL_DMA_ENABLE(&hdma_spi2_tx);

 /* Enable Tx DMA Request */
 SET_BIT(hspi2.Instance->CR2, SPI_CR2_TXDMAEN);
}

void DataReader_DMACallback()
{
 /* Transfer Complete Interrupt management ***********************************/
 if ((0U != (DMA1->ISR & (DMA_FLAG_TC1 << (hdma_spi2_rx.ChannelIndex & 0x1cU)))) && (0U != (hdma_spi2_rx.Instance->CCR & DMA_IT_TC)))
 {
 /* Disable the transfer complete and error interrupt */
 __HAL_DMA_DISABLE_IT(&hdma_spi2_rx, DMA_IT_TE | DMA_IT_TC);
 /* Clear the transfer complete flag */
 __HAL_DMA_CLEAR_FLAG(&hdma_spi2_rx, (DMA_FLAG_TC1 << (hdma_spi2_rx.ChannelIndex & 0x1cU)));


 // Wait until the bus is not busy before changing configuration
 // SPI is busy in communication or Tx buffer is not empty
 while(((hspi2.Instance->SR) & SPI_FLAG_BSY) != RESET)
 {}

 FLASH_CS_GPIO_Port->BSRR = FLASH_CS_Pin;

 isReceivingData = 0;
 }
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 readData(address24, buffer, length);
}

void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 readDataDMA(address24, buffer, length);
}

uint32_t DataReader_IsReceivingData(void)
{
 return isReceivingData;
}

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

 

ZhuangweiBest answer
Associate II
Feb 17, 2026 9:11 AM UTC
extern SPI_HandleTypeDef hspi2;

volatile uint8_t isReceivingData = 0;

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
}
void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 isReceivingData = 1;
 //Blocked receiving version
 //isReceivingData = 1;

 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
 
 //Blocked receiving version
 //HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 //HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 //isReceivingData = 0;
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 isReceivingData = 0;
 }
}

I found the reason. I was too careless. My SPI FLASH driver DMA receiving code does not set the receiving flag isReceivingData. In this way, every call to DataReader_WaitForReceiveDone after receiving flash data once through DataReader_StartDMAReadData will return immediately. The following is the correct code:

extern SPI_HandleTypeDef hspi2;

volatile uint8_t isReceivingData = 0;

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
}
void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
 isReceivingData = 1;
 //Blocked receiving version
 //isReceivingData = 1;
 //HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 //HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 //isReceivingData = 0;
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 isReceivingData = 0;
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 }
}

 

4 replies

Andrew Neil
Super User
Feb 16, 2026 1:39 PM UTC

Welcome to the forum.

Please see How to write your question to maximize your chances to find a solution for best results.

 

have you used a logic analyser to verify what's actually happening on the wires?

 

PS:

Your title says, "How to translate ILI9341 driver in touchgfx project to HAL version", but the code you've presented is already HAL.

Does this mean that you have a previous version of the code which didn't use HAL?

If so, did that version work?

If it did, then compare & contrast with your HAL-based version

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
ZhuangweiAuthor
Associate II
Feb 16, 2026 2:11 PM UTC

Thank you for your reply.
The first two codes are my hal version code.
When I adjusted the Block Size from 2048 to 640, the black stripes disappeared. 

Zhuangwei_0-1771249897655.png

And the waveform is as follows:

Zhuangwei_3-1771250425414.png

 

When I adjusted the Block Size from 640 to 2048, the black stripes appeared.

Zhuangwei_2-1771250306297.png

And the waveform is as follows:

Zhuangwei_1-1771250157323.png

This is the transmission waveform after this function call.

 HAL_SPI_Transmit_DMA

MOSI data disappears in the second half of CLK line, but the clock is still running. I suspect there is something wrong with my dma transmission code.
Is there a sample project related to HAL version? I think I can learn from it. 

 

ZhuangweiAuthorBest answer
Associate II
Feb 17, 2026 9:11 AM UTC
extern SPI_HandleTypeDef hspi2;

volatile uint8_t isReceivingData = 0;

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
}
void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 isReceivingData = 1;
 //Blocked receiving version
 //isReceivingData = 1;

 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
 
 //Blocked receiving version
 //HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 //HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 //isReceivingData = 0;
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 isReceivingData = 0;
 }
}

I found the reason. I was too careless. My SPI FLASH driver DMA receiving code does not set the receiving flag isReceivingData. In this way, every call to DataReader_WaitForReceiveDone after receiving flash data once through DataReader_StartDMAReadData will return immediately. The following is the correct code:

extern SPI_HandleTypeDef hspi2;

volatile uint8_t isReceivingData = 0;

void DataReader_WaitForReceiveDone(void)
{
 while(isReceivingData)
 {}
}

void DataReader_ReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
}
void DataReader_StartDMAReadData(uint32_t address24, uint8_t* buffer, uint32_t length)
{
 uint8_t cmd[4];
 cmd[0] = CMD_READ;
 cmd[1] = (address24 >> 16) & 0xFF;
 cmd[2] = (address24 >> 8) & 0xFF;
 cmd[3] = address24 & 0xFF;

 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
 HAL_SPI_Transmit(&hspi2, cmd, 4, HAL_MAX_DELAY);
 HAL_SPI_Receive_DMA(&hspi2, buffer, length);
 isReceivingData = 1;
 //Blocked receiving version
 //isReceivingData = 1;
 //HAL_SPI_Receive(&hspi2, buffer, length, HAL_MAX_DELAY);
 //HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 //isReceivingData = 0;
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
 if (hspi == &hspi2)
 {
 isReceivingData = 0;
 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET);
 }
}