Skip to main content
Associate III
January 26, 2026
Solved

How can I update SPI ILI9341 screen on every timer interrupt?

  • January 26, 2026
  • 2 replies
  • 219 views

I have an ILI9341 SPI screen and a small self-made library that can display different numbers on the screen. I want to be able to update the screen (display a new number) every time there is an interrupt. I have tested the screen functions independently, and they work properly. But when I call them from the interrupt handler, they fail. The board is STM32F407G-DISC1.

Timer configuration

static void MX_TIM2_Init(void)
{

 /* USER CODE BEGIN TIM2_Init 0 */

 /* USER CODE END TIM2_Init 0 */

 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};

 /* USER CODE BEGIN TIM2_Init 1 */

 /* USER CODE END TIM2_Init 1 */
 htim2.Instance = TIM2;
 htim2.Init.Prescaler = 999;
 htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim2.Init.Period = 335900;
 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
 if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
 {
 Error_Handler();
 }
 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
 if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN TIM2_Init 2 */

 /* USER CODE END TIM2_Init 2 */

}

Timer interrupt

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{
	//HAL_SPI_TransmitReceive_DMA(&hspi1, xyz_addr, xyz_data, 6);
	if ((new_step == 1) && (draw_ready == 1)) {
		draw_number(steps_count, 12, &hspi2);
	}
	new_step = 0;
}

Display functions (they use polling to interact with the screen, no interrupts or dma) 

#include "display.h"
#define DC_GPIO_PORT GPIOB
#define DC_GPIO_PIN GPIO_PIN_14
#define ILI9341_WIDTH 240
#define ILI9341_HEIGHT 320

//uint8_t DIGITS_CODES[10] = {126, 48, 109, 121, 55, 91, 95, 112, 127, 125};
uint8_t DIGITS_CODES[10] = {
 63, // 0 = A B C D E F
 6, // 1 = B C
 91, // 2 = A B D E G
 79, // 3 = A B C D G
 102, // 4 = B C F G
 109, // 5 = A C D F G
 125, // 6 = A C D E F G
 7, // 7 = A B C
 127, // 8 = A B C D E F G
 111 // 9 = A B C D F G
};

void Send_Command(uint8_t command, uint8_t *args, int num_args, SPI_HandleTypeDef *hspi) {
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(DC_GPIO_PORT, DC_GPIO_PIN, GPIO_PIN_RESET);
	uint8_t cmd[1] = {command};
	HAL_SPI_Transmit(hspi, cmd, 1, 100);
	HAL_GPIO_WritePin(DC_GPIO_PORT, DC_GPIO_PIN, GPIO_PIN_SET);
	HAL_SPI_Transmit(hspi, args, num_args, 100);
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
}

void Send_Command_NoArgs(uint8_t command, SPI_HandleTypeDef *hspi) {
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(DC_GPIO_PORT, DC_GPIO_PIN, GPIO_PIN_RESET);
	uint8_t cmd[1] = {command};
	HAL_SPI_Transmit(hspi, cmd, 1, 100);
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
}

void Send_Command_NoArgs_No_NSS_Set(uint8_t command, SPI_HandleTypeDef *hspi) {
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(DC_GPIO_PORT, DC_GPIO_PIN, GPIO_PIN_RESET);
	uint8_t cmd[1] = {command};
	HAL_SPI_Transmit(hspi, cmd, 1, 100);
	//HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
}

uint32_t min(uint32_t a, uint32_t b) {
	if (a < b) {
		return a;
	}
	return b;
}

void Write_Rectangle(uint16_t row_lower, uint16_t row_higher, uint16_t col_lower, uint16_t col_higher, uint16_t color, SPI_HandleTypeDef *hspi2) {
	//16 bit/pixel color order (R:5-bit, G:6-bit, B:5-bit)

	//1.Set column
	uint8_t col_params[4] = {(uint8_t)(col_lower >> 8), (uint8_t)(col_lower & 0x00FF),
								(uint8_t)(col_higher >> 8), (uint8_t)(col_higher & 0x00FF)};
	Send_Command(SET_COL_CMD, col_params, 4, hspi2);

	//2.Set row
	uint8_t row_params[4] = {(uint8_t)(row_lower >> 8), (uint8_t)(row_lower & 0x00FF),
							(uint8_t)(row_higher >> 8), (uint8_t)(row_higher & 0x00FF)};
	Send_Command(SET_ROW_CMD, row_params, 4, hspi2);

	//3.Set color
	Send_Command_NoArgs_No_NSS_Set(SET_COLOR_CMD, hspi2);
	// Compute rectangle size
	uint32_t width = col_higher - col_lower + 1;
	uint32_t height = row_higher - row_lower + 1;
	uint32_t pixels = width * height;
	// Send color repeatedly
	HAL_GPIO_WritePin(DC_GPIO_PORT, DC_GPIO_PIN, GPIO_PIN_SET);
	int array_size = min(64, pixels * 2);
	uint8_t pix_array[64];
	for (int i = 0; i < array_size; i++) {
		if (i % 2 == 0) {
			pix_array[i] = (uint8_t)(color >> 8);
		}
		else {
			pix_array[i] = (uint8_t)(color & 0xFF);
		}
	}
	if (array_size < 64) {
		HAL_SPI_Transmit(hspi2, pix_array, array_size, 100);
	}
	else {
		uint32_t pixels_written = 0;
		while (pixels_written + array_size < pixels * 2) {
			HAL_SPI_Transmit(hspi2, pix_array, array_size, 100);
			pixels_written += array_size;
		}
		HAL_SPI_Transmit(hspi2, pix_array, pixels * 2 - pixels_written, 100);
	}
	HAL_GPIO_WritePin(NSS_GPIO_PORT, NSS_GPIO_PIN, GPIO_PIN_SET);
}



void draw_digit(uint16_t x, uint16_t y, uint32_t size, uint8_t digit, uint16_t color, SPI_HandleTypeDef *hspi2) {
	if (digit > 9) {
		return;
	}
	uint8_t digit_code = DIGITS_CODES[digit];
	uint8_t width = size / 6;
	for (int i = 0; i < 7; i++) {
		if (digit_code & 1) {
		 switch (i) {

		 case 0: // A (top)
		 Write_Rectangle(
		 y,
		 y + width,
		 x,
		 x + size,
		 color, hspi2
		 );
		 break;

		 case 1: // B (top right)
		 Write_Rectangle(
		 y,
		 y + size,
		 x + size,
		 x + size + width,
		 color, hspi2
		 );
		 break;

		 case 2: // C (bottom right)
		 Write_Rectangle(
		 y + size,
		 y + size * 2,
		 x + size,
		 x + size + width,
		 color, hspi2
		 );
		 break;

		 case 3: // D (bottom)
		 Write_Rectangle(
		 y + size * 2 - width,
		 y + size * 2,
		 x,
		 x + size,
		 color, hspi2
		 );
		 break;

		 case 4: // E (bottom left)
		 Write_Rectangle(
		 y + size,
		 y + size * 2,
		 x,
		 x + width,
		 color, hspi2
		 );
		 break;

		 case 5: // F (top left)
		 Write_Rectangle(
		 y,
		 y + size,
		 x,
		 x + width,
		 color, hspi2
		 );
		 break;

		 case 6: // G (middle)
		 Write_Rectangle(
		 y + size - width / 2,
		 y + size + width - width / 2,
		 x,
		 x + size,
		 color, hspi2
		 );
		 break;
		 }
		}
		digit_code = digit_code >> 1;
	}
}

void draw_number(uint32_t number, uint32_t size, SPI_HandleTypeDef *hspi2) {
	 // Find highest power of 10
	 uint32_t value = number;
	 uint32_t div = 1;
	 uint16_t x = 5;
	 uint16_t y = 5;
	 uint16_t color = 0;

	 while (value / div >= 10) {
		 div *= 10;
	 }
	 while (div != 0) {
		 uint8_t digit = (uint8_t)(value / div);
		 draw_digit(x, y, size, digit, color, hspi2);
		 value %= div;
		 div /= 10;
		 x += size + 5;
	 }
}

//actual

///https://github.com/martnak/STM32-ILI9341/blob/master/Src/ILI9341/ILI9341_STM32_Driver.c
void ILI9341_Init(SPI_HandleTypeDef *hspi2)
{
 // Hardware reset
 HAL_GPIO_WritePin(RST_GPIO_PORT, RST_GPIO_PIN, GPIO_PIN_RESET);
 HAL_Delay(10);
 HAL_GPIO_WritePin(RST_GPIO_PORT, RST_GPIO_PIN, GPIO_PIN_SET);
 HAL_Delay(120);

 // Software reset
 Send_Command_NoArgs(0x01, hspi2);
 HAL_Delay(1000);

 // POWER CONTROL A
 uint8_t pwrA[5] = {0x39, 0x2C, 0x00, 0x34, 0x02};
 Send_Command(0xCB, pwrA, 5, hspi2);

 // POWER CONTROL B
 uint8_t pwrB[3] = {0x00, 0xC1, 0x30};
 Send_Command(0xCF, pwrB, 3, hspi2);

 // DRIVER TIMING CONTROL A
 uint8_t dtca[3] = {0x85, 0x00, 0x78};
 Send_Command(0xE8, dtca, 3, hspi2);

 // DRIVER TIMING CONTROL B
 uint8_t dtcb[2] = {0x00, 0x00};
 Send_Command(0xEA, dtcb, 2, hspi2);

 // POWER ON SEQUENCE CONTROL
 uint8_t pwrSeq[4] = {0x64, 0x03, 0x12, 0x81};
 Send_Command(0xED, pwrSeq, 4, hspi2);

 // PUMP RATIO CONTROL
 uint8_t pump[1] = {0x20};
 Send_Command(0xF7, pump, 1, hspi2);

 // POWER CONTROL 1 (VRH)
 uint8_t pwr1[1] = {0x23};
 Send_Command(0xC0, pwr1, 1, hspi2);

 // POWER CONTROL 2 (SAP, BT)
 uint8_t pwr2[1] = {0x10};
 Send_Command(0xC1, pwr2, 1, hspi2);

 // VCOM CONTROL 1
 uint8_t vcom1[2] = {0x3E, 0x28};
 Send_Command(0xC5, vcom1, 2, hspi2);

 // VCOM CONTROL 2
 uint8_t vcom2[1] = {0x86};
 Send_Command(0xC7, vcom2, 1, hspi2);

 // MEMORY ACCESS CONTROL (rotation)
 uint8_t mac[1] = {0x48};
 Send_Command(0x36, mac, 1, hspi2);

 // PIXEL FORMAT (RGB565)
 uint8_t pixel_fmt[1] = {0x55};
 Send_Command(0x3A, pixel_fmt, 1, hspi2);

 // FRAME RATE CONTROL
 uint8_t frmctr[2] = {0x00, 0x18};
 Send_Command(0xB1, frmctr, 2, hspi2);

 // DISPLAY FUNCTION CONTROL
 uint8_t dfc[3] = {0x08, 0x82, 0x27};
 Send_Command(0xB6, dfc, 3, hspi2);

 // 3GAMMA FUNCTION DISABLE
 uint8_t gamma_disable[1] = {0x00};
 Send_Command(0xF2, gamma_disable, 1, hspi2);

 // GAMMA CURVE SELECT
 uint8_t gamma_curve[1] = {0x01};
 Send_Command(0x26, gamma_curve, 1, hspi2);

 // POSITIVE GAMMA CORRECTION
 uint8_t pgamma[15] = {
 0x0F, 0x31, 0x2B, 0x0C, 0x0E,
 0x08, 0x4E, 0xF1, 0x37, 0x07,
 0x10, 0x03, 0x0E, 0x09, 0x00
 };
 Send_Command(0xE0, pgamma, 15, hspi2);

 // NEGATIVE GAMMA CORRECTION
 uint8_t ngamma[15] = {
 0x00, 0x0E, 0x14, 0x03, 0x11,
 0x07, 0x31, 0xC1, 0x48, 0x08,
 0x0F, 0x0C, 0x31, 0x36, 0x0F
 };
 Send_Command(0xE1, ngamma, 15, hspi2);

 // EXIT SLEEP
 Send_Command_NoArgs(0x11, hspi2);
 HAL_Delay(120);

 // DISPLAY ON
 Send_Command_NoArgs(0x29, hspi2);
}

void clean_screen(uint16_t color, SPI_HandleTypeDef *hspi2) {
	Write_Rectangle(
	 0, // row_lower
	 ILI9341_HEIGHT - 1, // row_higher
	 0, // col_lower
	 ILI9341_WIDTH - 1, // col_higher
	 color,
	 hspi2
	 );
}

 The main

int main(void)
{

 /* USER CODE BEGIN 1 */


 /* USER CODE END 1 */

 /* MCU Configuration--------------------------------------------------------*/

 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
 HAL_Init();

 /* USER CODE BEGIN Init */

 /* USER CODE END Init */

 /* Configure the system clock */
 SystemClock_Config();

 /* USER CODE BEGIN SysInit */

 /* USER CODE END SysInit */

 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_DMA_Init();
 MX_I2S3_Init();
 MX_SPI1_Init();
 MX_USB_HOST_Init();
 MX_SPI2_Init();
 MX_TIM2_Init();
 /* USER CODE BEGIN 2 */
 //Reset the screen
 ILI9341_Init(&hspi2);
 //screen experiment, delete later (black rectangle)
 HAL_Delay(1000);

 //Write_Rectangle(0, 50, 0, 50, 0, &hspi2); //good
 //draw_digit(10, 10, 12, 8, 0, &hspi2); //good
 //draw_number(UINT32_MAX, 12, &hspi2); //good
 draw_ready = 1;

 //Set everything for the accelometer
 uint8_t OUT_X_L = 0x28;
 uint8_t OUT_X_H = 0x29;
 uint8_t OUT_Y_L = 0x2A;
 uint8_t OUT_Y_H = 0x2B;
 uint8_t OUT_Z_L = 0x2C;
 uint8_t OUT_Z_H = 0x2D;
 uint8_t CTRL_REG5 = 0x24; //for sensitivity (change from default 2 to 4/8) (bits 5-4)
 //receivers and transmitters
 xyz_addr[0] = OUT_X_L; xyz_addr[1] = OUT_X_H; xyz_addr[2] = OUT_Y_L; xyz_addr[3] = OUT_Y_H; xyz_addr[4] = OUT_Z_L; xyz_addr[5] = OUT_Z_H;
 uint8_t ctrl_val;

 //fill buffer with 0s
 for (int i = 0; i < ACC_BUF_SIZE; i++) {
	 acc_values_buf[i] = 0;
 }

 //receive and set accelerator sensitivity
 //HAL_SPI_TransmitReceive(&hspi1, &CTRL_REG5, &ctrl_val, 1, 100);
 //ctrl_val = ctrl_val | 0x08; //set FSCALE0
 //uint8_t tx_buf[2];
 //tx_buf[0] = CTRL_REG5 & 0x7F; //register address
 //tx_buf[1] = ctrl_val; //data to write into it
 //HAL_SPI_Transmit(&hspi1, tx_buf, 1, 100);

 //initial dma call (deprecated)
 //HAL_SPI_TransmitReceive_DMA(&hspi1, xyz_addr, xyz_data, 6);
 //default value
 //clean_screen(0xFF, &hspi2);
 //draw_number(steps_count, 12, &hspi2);

 /* USER CODE END 2 */

 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1)
 {

 /* USER CODE END WHILE */
 MX_USB_HOST_Process();

 /* USER CODE BEGIN 3 */
 //During each SPI clock cycle, full-duplex transmission of a single bit occurs.
 //1 clock cycle is 168/32MHz = 5.25MHz
 //meaning there are 5.25 * 10^6 bit, or 5.25 * 10^6/8 bytes = 656250 bytes
 //or, all coordinates are received 109375 times.

 }
 /* USER CODE END 3 */
}

 

Best answer by cockatoo

I have found a solution. I have moved the screen logic into main function while loop, and created volatile tag variable, which is set by the interrupt and reset by the loop in the main function every time.

2 replies

mƎALLEm
Technical Moderator
January 26, 2026

Hello,

What do you mean by they fail here: "But when I call them from the interrupt handler, they fail". 

You need to describe the symptoms: it doesn't display anything? it diplays rubbish characters? 

What did you do to debug the issue apart from calling the display APIs outside the Timer interrupt callback?

Meanwhile, draw_number(steps_count, 12, &hspi2); should complete its execution in less time than the timer period.

 

 

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question."
cockatooAuthor
Associate III
January 28, 2026

It doesn't display anything

cockatooAuthorBest answer
Associate III
January 28, 2026

I have found a solution. I have moved the screen logic into main function while loop, and created volatile tag variable, which is set by the interrupt and reset by the loop in the main function every time.