Electronic – ILI9341 with STM32 SPI troubles

embeddedspistm32stm32f0tft

I am trying to interface with an SPI ILI9341 screen using an STM32 microcontroller. I know there are a whole lot of things that can go wrong with these interfaces, and I've spent an awful lot of time searching and solving problems. But I'm kind of at a loss as to why my display seems to succeed in its initialization, but refuses to display any colors.

What I'm seeing is that the screens 'turn on' to a flickering grey color, after which they ignore the 'set address window / send data' commands which should draw colors to the screen.

Here are the commands that I'm sending – the 'spi_write_[command|data]' methods just set D/C low or high respectively and then send a byte over the peripheral, and the commands/data are from several other libraries, mostly Adafruit's. I've reviewed them several times and tried tweaking a few commands, including the SPI timing, but nothing seems to help.

ILI9341 Initialization commands:

LDR  r1, =0x000000EF
BL   spi_write_command
LDR  r1, =0x00000003
BL   spi_write_data
LDR  r1, =0x00000080
BL   spi_write_data
LDR  r1, =0x00000002
BL   spi_write_data

LDR  r1, =0x000000CF
BL   spi_write_command
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x000000C1
BL   spi_write_data
LDR  r1, =0x00000030
BL   spi_write_data

LDR  r1, =0x000000ED
BL   spi_write_command
LDR  r1, =0x00000064
BL   spi_write_data
LDR  r1, =0x00000003
BL   spi_write_data
LDR  r1, =0x00000012
BL   spi_write_data
LDR  r1, =0x00000081
BL   spi_write_data

LDR  r1, =0x000000E8
BL   spi_write_command
LDR  r1, =0x00000085
BL   spi_write_data
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x00000078
BL   spi_write_data

LDR  r1, =0x000000CB
BL   spi_write_command
LDR  r1, =0x00000039
BL   spi_write_data
LDR  r1, =0x0000002C
BL   spi_write_data
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x00000034
BL   spi_write_data
LDR  r1, =0x00000002
BL   spi_write_data

LDR  r1, =0x000000F7
BL   spi_write_command
LDR  r1, =0x00000020
BL   spi_write_data

LDR  r1, =0x000000EA
BL   spi_write_command
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x00000000
BL   spi_write_data

LDR  r1, =0x000000C0 // (Power control 1)
BL   spi_write_command
LDR  r1, =0x00000023
BL   spi_write_data

LDR  r1, =0x000000C1 // (Power control 2)
BL   spi_write_command
LDR  r1, =0x00000010
BL   spi_write_data

LDR  r1, =0x000000C5 // (VCOM control 1)
BL   spi_write_command
LDR  r1, =0x0000002B
BL   spi_write_data
LDR  r1, =0x0000002B
BL   spi_write_data

LDR  r1, =0x000000C7 // (VCOM offset)
BL   spi_write_command
LDR  r1, =0x000000C0
BL   spi_write_data

LDR  r1, =0x00000036 // (Memory access type)
BL   spi_write_command
LDR  r1, =0x00000048
BL   spi_write_data

LDR  r1, =0x0000003A // (Pixel format)
BL   spi_write_command
LDR  r1, =0x00000055
BL   spi_write_data

LDR  r1, =0x000000B1 // (Framerate control)
BL   spi_write_command
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x0000001B
BL   spi_write_data

LDR  r1, =0x000000B6 // (Display function)
BL   spi_write_command
LDR  r1, =0x00000008
BL   spi_write_data
LDR  r1, =0x00000082
BL   spi_write_data
LDR  r1, =0x00000027
BL   spi_write_data

LDR  r1, =0x000000F2 // (Gamma select)
BL   spi_write_command
LDR  r1, =0x00000000
BL   spi_write_data

LDR  r1, =0x00000026 // (Gamma set)
BL   spi_write_command
LDR  r1, =0x00000001
BL   spi_write_data

LDR  r1, =0x000000E0 // (Gamma curve)
BL   spi_write_command
LDR  r1, =0x0000000F
BL   spi_write_data
LDR  r1, =0x00000031
BL   spi_write_data
LDR  r1, =0x0000002B
BL   spi_write_data
LDR  r1, =0x0000000C
BL   spi_write_data
LDR  r1, =0x0000000E
BL   spi_write_data
LDR  r1, =0x00000008
BL   spi_write_data
LDR  r1, =0x0000004E
BL   spi_write_data
LDR  r1, =0x000000F1
BL   spi_write_data
LDR  r1, =0x00000037
BL   spi_write_data
LDR  r1, =0x00000007
BL   spi_write_data
LDR  r1, =0x00000010
BL   spi_write_data
LDR  r1, =0x00000003
BL   spi_write_data
LDR  r1, =0x0000000E
BL   spi_write_data
LDR  r1, =0x00000009
BL   spi_write_data
LDR  r1, =0x00000000
BL   spi_write_data

LDR  r1, =0x000000E1 // (Gamma curve)
BL   spi_write_command
LDR  r1, =0x00000000
BL   spi_write_data
LDR  r1, =0x0000000E
BL   spi_write_data
LDR  r1, =0x00000014
BL   spi_write_data
LDR  r1, =0x00000003
BL   spi_write_data
LDR  r1, =0x00000011
BL   spi_write_data
LDR  r1, =0x00000007
BL   spi_write_data
LDR  r1, =0x00000031
BL   spi_write_data
LDR  r1, =0x000000C1
BL   spi_write_data
LDR  r1, =0x00000048
BL   spi_write_data
LDR  r1, =0x00000008
BL   spi_write_data
LDR  r1, =0x0000000F
BL   spi_write_data
LDR  r1, =0x0000000C
BL   spi_write_data
LDR  r1, =0x00000031
BL   spi_write_data
LDR  r1, =0x00000036
BL   spi_write_data
LDR  r1, =0x0000000F
BL   spi_write_data

LDR  r1, =0x00000020 // No display inversion.
BL   spi_write_command
// (Delay >=120ms)
MOVS r3, #120
BL   delay_ms
LDR  r1, =0x00000011 // Exit sleep mode
BL   spi_write_command
// (Delay >=120ms)
MOVS r3, #120
BL   delay_ms
LDR  r1, =0x00000029 // Display on
BL   spi_write_command

Fill screen with color – this simply sets the 'address window' to the whole screen, and then writes a single color 240*320 times:

// Set address window; r4-r7 hold x/y values, for whole screen:
// X = [0:239], Y = [0:319]
// Set the 'display addressing' window to the whole screen.
MOVS r4, #0
MOVS r5, #239
MOVS r6, #0
LDR  r7, =0x0000013F // (319)
LDR  r2, =0x000000FF
// Column-set
LDR  r1, =0x0000002A
BL   spi_write_command
// Min-X
MOVS r1, r4
LSRS r1, r1, #8
BL   spi_write_data
MOVS r1, r4
ANDS r1, r1, r2
BL   spi_write_data
// Max-X
MOVS r1, r5
LSRS r1, r1, #8
BL   spi_write_data
MOVS r1, r5
ANDS r1, r1, r2
BL   spi_write_data
// Row-set
LDR  r1, =0x0000002B
BL   spi_write_command
// Min-Y
MOVS r1, r6
LSRS r1, r1, #8
BL   spi_write_data
MOVS r1, r6
ANDS r1, r1, r2
BL   spi_write_data
// Max-Y
MOVS r1, r7
LSRS r1, r1, #8
BL   spi_write_data
MOVS r1, r7
ANDS r1, r1, r2
BL   spi_write_data
// RAM-Write command.
LDR  r1, =0x0000002C
BL   spi_write_command
// Write the given color (16-bit) 240*320 times, 1 for each pixel.
LDR  r7, =0x00000140
MOVS r4, #240
MULS r4, r4, r7
MOVS r5, r3
LDR  r6, =0x000000FF
ANDS r5, r5, r6
MOVS r6, r3
LSRS r6, r6, #8
ili9341_fill_screen_write_px:
    MOVS r1, r6
    BL   spi_write_data
    MOVS r1, r5
    BL   spi_write_data
    SUBS r4, r4, #1
    BNE  ili9341_fill_screen_write_px

Thanks for any help! I've verified that each pin's signals are making it to the displays with an oscilloscope – even the Data/Command and CS pins – so I'm really out of ideas as to what could be going wrong.

Best Answer

Okay, it didn't take me long to abandon the idea of using assembly language to introduce people to microcontrollers, and I did eventually figure this out.

The flickering gray screen that I observed looks like it is a normal 'startup' state for the display when a hardware reset has been performed by pulling the reset pin low, then high.

But the STM32's SPI peripheral sends the full contents of its 16-bit 'Data Register' unless you use some specific conventions. It's easiest to cast the register to a pointer to an 8-bit integer.

SPI settings (using ST's device header files):

void hspi_init(SPI_TypeDef *SPIx) {
  // Ensure that the peripheral is disabled, and reset it.
  SPIx->CR1 &= ~(SPI_CR1_SPE);
  if (SPIx == SPI1) {
    RCC->APB2RSTR |=  (RCC_APB2RSTR_SPI1RST);
    RCC->APB2RSTR &= ~(RCC_APB2RSTR_SPI1RST);
  }
  // Set clock polarity/phase to 0/0 or 1/1.
  SPIx->CR1 |=  (SPI_CR1_CPOL |
                 SPI_CR1_CPHA);
  // Set the STM32 to act as a host device.
  SPIx->CR1 |=  (SPI_CR1_MSTR);
  // Set software 'Chip Select' signal.
  SPIx->CR1 |=  (SPI_CR1_SSM);
  // Set the internal 'Chip Select' signal.
  SPIx->CR1 |=  (SPI_CR1_SSI);
  // Use MSB-first format.
  SPIx->CR1 &= ~(SPI_CR1_LSBFIRST);
  // Set the Baud rate prescaler.
  SPIx->CR1 &= ~(SPI_CR1_BR);
  // Start slow? SPI_clock = Core_clock / (2 ^ (BR))
  // So, a value of 4 should slow things down by a factor of 16.
  //SPIx->CR1 |=  (0x4 << SPI_CR1_BR_Pos);
  // Enable the peripheral.
  SPIx->CR1 |=  (SPI_CR1_SPE);
}

'Write byte' function:

inline void hspi_w8(SPI_TypeDef *SPIx, uint8_t dat) {
  // Wait for TXE.
  while (!(SPIx->SR & SPI_SR_TXE)) {};
  // Send the byte.
  *(uint8_t*)&(SPIx->DR) = dat;
}

With those settings, it seems to work.

Related Topic