cancel
Showing results for 
Search instead for 
Did you mean: 

Tutorial code for FMC interface with LCD TFT fails on my device, I got it to run by editing the code but the graphics updates are notably slow. Am I misunderstanding how FMC on STM operates entirely?

NNagy.1
Senior

I was following a video tutorial for F(S)MC setup for an 8080 LCD interface. I have an Elegoo ILI9341 shield. When I ran the code, my display was just a flashing white background no matter what functions I tried to call.

All of the tutorial functions were based off of these two commands:

//***** Functions prototypes *****//
//1. Write Command to LCD
void ILI9341_SendCommand(uint8_t com)
{
	*(__IO uint8_t *)(0x60000000) = com;
}
 
//2. Write data to LCD
void ILI9341_SendData(uint8_t data)
{
	*(__IO uint8_t *)(0x60040000) = data;
}

With the addresses corresponding to the NE1 bank, and 0x60040000 in particular being selected for A18 register select configuration.

Maybe I'm misunderstanding how the FMC works, but what confused me about these functions is that the register select and write signals are never explicitly set?

I made the following changes:

/** FMC GPIO Configuration
PE7   ------> FMC_D4
PE8   ------> FMC_D5
PE9   ------> FMC_D6
PE10   ------> FMC_D7
PD13   ------> FMC_A18
PD14   ------> FMC_D0
PD15   ------> FMC_D1
PC7   ------> FMC_NE1
PD0   ------> FMC_D2
PD1   ------> FMC_D3
PD4   ------> FMC_NOE
PD5   ------> FMC_NWE
*/
#define FMC_RS_Pin GPIO_PIN_13
#define FMC_RS_GPIO_Port GPIOD
#define FMC_CS_Pin GPIO_PIN_7
#define FMC_CS_GPIO_Port GPIOC
#define FMC_NWE_Pin GPIO_PIN_5
#define FMC_NWE_GPIO_Port GPIOD
 
/* some custom commands */
// these are non-parallel writes to GPIO, be warned about delays
#define CS_ACTIVE  HAL_GPIO_WritePin(FMC_CS_GPIO_Port, FMC_CS_Pin, 1);
#define CD_COMMAND HAL_GPIO_WritePin(FMC_RS_GPIO_Port, FMC_RS_Pin, 0);
#define CD_DATA    HAL_GPIO_WritePin(FMC_RS_GPIO_Port, FMC_RS_Pin, 1);
#define WR_ACTIVE  HAL_GPIO_WritePin(FMC_NWE_GPIO_Port, FMC_NWE_Pin, 0);
#define WR_IDLE    HAL_GPIO_WritePin(FMC_NWE_GPIO_Port, FMC_NWE_Pin, 1);
#define WR_STROBE  { WR_ACTIVE; WR_IDLE; }
/* end of custom commands */
 
//***** Functions prototypes *****//
//1. Write Command to LCD
void ILI9341_SendCommand(uint8_t com)
{
	CD_COMMAND;
	*(__IO uint8_t *)(0x60000000) = com;
	WR_STROBE;
}
 
//2. Write data to LCD
void ILI9341_SendData(uint8_t data)
{
	CD_DATA;
	*(__IO uint8_t *)(0x60040000) = data;
	WR_STROBE;
}

Now the TFT display functions work properly and I get the correct images. However, it runs very slowly compared to what I've seen is capable for the display (I can see the shapes drawn out across the screen instead of them looking like they pop into existence).

I tried two other ideas to try and speed up the functions. One was bit-masking GPIOD directly:

//***** Functions prototypes *****//
//1. Write Command to LCD
void ILI9341_SendCommand(uint8_t com)
{
	*(__IO uint8_t *)(0x60000000) = com;
	GPIOD->ODR &= ~(FMC_RS_Pin | FMC_NWE_Pin); // set reg select low (command) and not-write low
	GPIOD->ODR |= FMC_NWE_Pin; // set not-write high
}
 
//2. Write data to LCD
void ILI9341_SendData(uint8_t data)
{
	*(__IO uint8_t *)(0x60040000) = data;
	GPIOD->ODR |= (FMC_RS_Pin & ~FMC_NWE_Pin); 
	GPIOD->ODR |= FMC_NWE_Pin; // set not-write high
}

But this caused the screen to be stuck flashing again, I think maybe because I can't have register select and write signals change simultaneously?

The last thing I tried was removing the specified register select lines, since the data and commands are loaded into different addresses on the host I thought maybe only the write signal has to be explicitly set low:

//***** Functions prototypes *****//
//1. Write Command to LCD
void ILI9341_SendCommand(uint8_t com)
{
	//CD_COMMAND;
	*(__IO uint8_t *)(0x60000000) = com;
	WR_STROBE;
}
 
//2. Write data to LCD
void ILI9341_SendData(uint8_t data)
{
	//CD_DATA;
	*(__IO uint8_t *)(0x60040000) = data;
	WR_STROBE;
}

This did display on the screen, and it definitely loads much faster than with the CD lines un-commented, but a lot of the display is dis-colored.

The original tutorial I followed the creator demonstrated his TFT display working properly with his original single-line sendCommand() and sendData() functions, so maybe I'm not understanding correctly how FMC works? How does the STM device know to set its own register select and write signals? Does it listen for when the two bank addresses have been updated and trigger writes accordingly?

If it helps, I'm working with a Nucleo F767ZI and have the following RCC and FMC configurations (set in Cube):

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}
 
/* FMC initialization function */
static void MX_FMC_Init(void)
{
 
  /* USER CODE BEGIN FMC_Init 0 */
 
  /* USER CODE END FMC_Init 0 */
 
  FMC_NORSRAM_TimingTypeDef Timing = {0};
 
  /* USER CODE BEGIN FMC_Init 1 */
 
  /* USER CODE END FMC_Init 1 */
 
  /** Perform the SRAM1 memory initialization sequence
  */
  hsram1.Instance = FMC_NORSRAM_DEVICE;
  hsram1.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
  /* hsram1.Init */
  hsram1.Init.NSBank = FMC_NORSRAM_BANK1;
  hsram1.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
  hsram1.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
  hsram1.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
  hsram1.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
  hsram1.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
  hsram1.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
  hsram1.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
  hsram1.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
  hsram1.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
  hsram1.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY;
  hsram1.Init.WriteFifo = FMC_WRITE_FIFO_DISABLE;
  hsram1.Init.PageSize = FMC_PAGE_SIZE_NONE;
  /* Timing */
  Timing.AddressSetupTime = 6;
  Timing.AddressHoldTime = 0;
  Timing.DataSetupTime = 6;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 0;
  Timing.DataLatency = 0;
  Timing.AccessMode = FMC_ACCESS_MODE_A;
  /* ExtTiming */
 
  if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
  {
    Error_Handler( );
  }
 
  /* USER CODE BEGIN FMC_Init 2 */
 
  /* USER CODE END FMC_Init 2 */
}

0 REPLIES 0