
7" TFT touchscreen control with STM32F407 device and FSMC peripheral
To develop a simple graphical interface, I purchased a while ago a 7" TFT touchscreen made by Displaytech. This large screen is controlled via an integrated SSD1963 graphic controller and the touch events and coordinates are gathered via a MAX11802 resistive touchscreen controller from Maxim.
Originally, I started to interface this screen with a PIC32 running @ 80MHz and the final result was not satisfactory in terms of refresh rate. Recently I got one of those cheap (yet so powerful) STM32Discovery board and tried to interface it with the touchscreen...
Required hardware :
- STM32DISCOVERY board
- INT070ATFT-TS screen or a similar one equipped with SSD1963 controller
- Power supply, jumper wires, etc.
You can download the wiring I used here.
Required software :
- IDE to debug and program STM32F407VGT6 (I used Keil µVision 5)
Initializing the SSD1963 screen controller
The SSD1963 is interfaced with the MCU via a parallel port configured in Intel 8080 or Motorola 6800 modes. Both 6800 and 8080 support 8-bit, 9-bit, 16-bit, 18-bit and 24-bit data bus. Depending on the width of the data bus, the display data are packed into the data bus in different ways. Take a look at the datasheet to find out which data bus length is suitable for your application. I chose to use the 16-bit format which is very common.
Instead of controlling a 16-bit GPIO port of the board, I set up the Flexible Static Memory Controller (FSMC) feature. Originally used to communicate with flash memories, it can be employed to send data to a graphical controller.
The following code is an extract of the tft.c source file. It is used to initialize the TFT screen :
#ifndef TFT_H
#define TFT_H
#include <tft.h>
#endif
//Set all control lines as input to allow auto-init
void TFT_CtrlLinesReset(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//Enable clocks
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
//Set control lines for FSMC
void TFT_CtrlLinesConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable GPIOD, GPIOE, GPIOF, GPIOG and AFIO clocks */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF, ENABLE);
/*-- GPIO Configuration ------------------------------------------------------*/
/* SRAM Data lines, NOE (/RD) and NWE (/WR) configuration */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource4, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FSMC);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource11 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource12 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource13 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource14 , GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource15 , GPIO_AF_FSMC);
/* SRAM Address lines configuration (/RS)*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_FSMC);
/* NE3 configuration (/CS)*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource7, GPIO_AF_FSMC);
/*/RESET */
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD, GPIO_Pin_12);
}
//FSMC configuration
void TFT_FSMCConfig(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef p;
/* Enable FMC clock */
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE);
/*-- FSMC Configuration ------------------------------------------------------*/
/*----------------------- SRAM Bank 1 ----------------------------------------*/
/* FSMC_Bank1_NORSRAM1 configuration */
p.FSMC_AddressSetupTime = 5;
p.FSMC_AddressHoldTime = 1;
p.FSMC_DataSetupTime = 9;
p.FSMC_BusTurnAroundDuration = 0;
p.FSMC_CLKDivision = 1;
p.FSMC_DataLatency = 0;
p.FSMC_AccessMode = FSMC_AccessMode_A;
/* Color LCD configuration ------------------------------------
LCD configured as follow:
- Data/Address MUX = Disable
- Memory Type = SRAM
- Data Width = 16bit
- Write Operation = Enable
- Extended Mode = Enable
- Asynchronous Wait = Disable */
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
/* Enable FSMC NOR/SRAM Bank1 */
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
}
void TFT_Initialization(void)
{
//Disable power supply
TFT_5V_Disable();
TFT_33V_Disable();
//Control lines as input
TFT_CtrlLinesReset();
//Enable power supply
TFT_33V_Enable();
delay_1ms(1000);
TFT_CtrlLinesConfig();
delay_1ms(100);
TFT_FSMCConfig();
delay_1ms(100);
//Display on + backlight power on
SSD1963__set_display_on();
TFT_5V_Enable();
//Set pixel data interface to 16-bit (565)
SSD1963__set_pixel_data_interface(0x03);
//Set address mode
SSD1963__set_address_mode(0x02);
//Brightness @ 100%
SSD1963__set_pwm_conf(0x06,0xD9,0x01,0xF0,0x00,0x00);
}
Initializing the MAX11802 touchscreen controller
The MAX11802 is interfaced with the MCU via a SPI interface. Every touch event set an interruption of the main program and the MCU ask for event's coordinates. X and Y coordinates are returned to the MCU by the controller on a range from 0 to 65535.
Here is the max1802.c source file containing the related function to operate the controller. Take a look at the whole repository for a better understanding (interrupts).
#ifndef MAX11802_H
#define MAX11802_H
#include <max11802.h>
#endif
void MAX11802_Initialization(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitDef;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitDef.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitDef.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitDef.GPIO_OType = GPIO_OType_PP;
GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitDef);
GPIO_InitDef.GPIO_Pin = GPIO_Pin_12;
GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitDef.GPIO_OType = GPIO_OType_PP;
GPIO_Init(GPIOB, &GPIO_InitDef);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI1);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
//Start SPI2 with interrupts
SPI_Init(SPI2,&SPI_InitStructure);
NVIC_InitStruct.NVIC_IRQChannel = SPI2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
SPI_I2S_ITConfig(SPI2, SPI_I2S_IT_RXNE, ENABLE);
//Start SPI bus
SPI_Cmd(SPI2, ENABLE);
delay_1ms(100);
//MAX11802 registers configuration
TP_Write_Register(0x01,0x00);
TP_Write_Register(0x02,0x00);
TP_Write_Register(0x03,0x00);
TP_Write_Register(0x04,0x00);
TP_Write_Register(0x05,0x80);
TP_Write_Register(0x06,0x55);
TP_Write_Register(0x07,0x10);
TP_Write_Register(0x08,0x00);
TP_Write_Register(0x09,0x00);
TP_Write_Register(0x0A,0x00);
TP_Write_Register(0x0B,0x00);
//--START--INTERRUPT CONFIG (PB0/TIRQ)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
//External Interrupt as input
GPIO_InitDef.GPIO_Pin = GPIO_Pin_0;
GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;
//Initialize pins
GPIO_Init(GPIOB, &GPIO_InitDef);
/* Tell system that you will use PB0 for EXTI_Line0*/
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource0);
/* PB0 is connected to EXTI_Line0 */
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStruct);
/* Add IRQ vector to NVIC */
/* PD0 is connected to EXTI_Line0, which has EXTI0_IRQn vector */
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
//--END--INTERRUPT CONFIG (PB0/TIRQ)
tp_int=0;
}
void TP_Write_Register(unsigned char reg, unsigned char val)
{
unsigned char byte0;
byte0=reg<<1;
SET_TP_CS();
delay_1ms(1);
SPI_I2S_SendData(SPI2, byte0);
delay_1ms(1);
SPI_I2S_SendData(SPI2, val);
delay_1ms(1);
RESET_TP_CS();
}
void SET_TP_CS(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void RESET_TP_CS(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
//Ask the controller for X coordinate
void TP_Get_X(void)
{
unsigned int tp_x_msb=0;
unsigned int tp_x_lsb=0;
SET_TP_CS();
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xF0);
delay_1ms(5);
RESET_TP_CS();
delay_1ms(5);
SET_TP_CS();
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xA5);
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xFF);
delay_1ms(5);
tp_x_msb=tp_buffer;
SPI_I2S_SendData(SPI2, 0xFF);
delay_1ms(5);
tp_x_lsb=tp_buffer;
delay_1ms(5);
RESET_TP_CS();
tp_x=(tp_x_msb<<8)|(tp_x_lsb);
}
//Ask the controller for Y coordinate
void TP_Get_Y(void)
{
unsigned int tp_y_msb=0;
unsigned int tp_y_lsb=0;
SET_TP_CS();
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xF4);
delay_1ms(5);
RESET_TP_CS();
delay_1ms(5);
SET_TP_CS();
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xA9);
delay_1ms(5);
SPI_I2S_SendData(SPI2, 0xFF);
delay_1ms(5);
tp_y_msb=tp_buffer;
SPI_I2S_SendData(SPI2, 0xFF);
delay_1ms(5);
tp_y_lsb=tp_buffer;
delay_1ms(5);
RESET_TP_CS();
tp_y=(tp_y_msb<<8)|(tp_y_lsb);
}
void TP_Check(void)
{
TP_Get_X();
TP_Get_Y();
TFT_DrawFullRect(0,440,100,40,0x1107);
TFT_Put_Figure(0,448,tp_x,0xFFFF);
TFT_Put_Figure(0,464,tp_y,0xFFFF);
}