STM32 microcontroller beginner 6-SPI communication driver IPS color screen

SPI is the abbreviation of Serial Peripheral Interface (Serial Peripheral Interface), which is a high-speed, full-duplex, synchronous communication bus, and only occupies four wires on the pins of the chip .

Like IIC, it is one of the most commonly used communication methods in microcontroller development. Compared with IIC, its characteristics are as follows:

1. The communication rate is fast. Taking STM32F103C8T6 as an example, its SPI communication rate can reach 18Mbps, that is, 2.25MB/s, and the IIC high-speed mode is only 3.4Mbps;

2. Full-duplex communication. SPI has two data lines, one master-out slave-in, one master-in slave-out. At the same time, data can be sent and data can be received, while IIC is half-duplex, sending, Receiving data cannot be done simultaneously;

The main application scenarios include SD card data reading and writing, TFT color screen control, etc.

This article will explain in detail the use of STM32 hardware SPI interface to drive IPS color screen display pictures (the display text will not be introduced in detail, it is similar to OLED display text).

Let's take a look at the SPI physical interface definition:  

MISO – Master Input Slave Output , master device data input, slave device data output;

MOSI – Master Output Slave Input, master device data output, slave device data input;

SCLK – Serial Clock, clock signal, generated by the master device;

CS – Chip Select, slave device enable signal, controlled by the master device, each slave device corresponds to a CS interface of the master device, if you want to communicate with a slave device, enable the corresponding slave device through CS. In STM32 it is SPI_NSS.

In my article on IIC software simulation, I analyzed the software simulation implementation method of IIC in detail. Similarly, SPI can also be implemented by software simulation, as long as the GPIO level is controlled according to its timing. Here we do not use software simulation implementation for the time being, directly configure the hardware SPI inside STM32, and then call the library function. Taking STM32F103C8T6 as an example, this model has two SPIs.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

The screen I use here is a 1.3-inch IPS color screen with a resolution of 240*240, a maximum support of 16-bit true color (RGB565), and a driver chip ST7789.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 16-bit true color means that each pixel has 65535 (2^16) colors.

The principle of three primary colors says: most colors can be produced by synthesizing red, green and blue in different proportions. Similarly, most monochromatic light can also be decomposed into three colors of red, green and blue.

Each pixel on the IPS screen is composed of a set of red, green and blue shading sheets, and the white backlight presents three colors of red, green and blue through the shading sheets. Changing the angle of the corresponding shading film can change the intensity of the color, and the red, green, and blue colors of different intensities are mixed together to form 65,535 colors displayed on the screen.

Among these 16 bits, the first 5 bits indicate the intensity of red, the middle 6 bits indicate the intensity of green, and the latter 5 bits indicate the intensity of blue. That is, red, green and blue occupy 5, 6, and 5 bits respectively, which is why RGB565 got its name. Therefore, the intensities of red, green and blue are 32, 64, and 32 respectively.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

A pixel needs to occupy 16 bits (ie 2Byte) of data, and the entire screen needs 240x240x2=115200Byte (ie 115.2KB) of data.

-------Pin definition:

The screen has 7 pins, the interface definition and the connection method with the microcontroller are as follows:

GND: power supply ground

VCC:  positive power supply (3-5V)

SCL: SPI clock line. Connect to MCU SPI_SCK

SDA: SPI data line. for that screen. There is only data output and no output is required, that is, master out and slave in, so it is connected to the single chip microcomputer MOSI. The MISO of the MCU is not used.

RES: screen reset. Before the MCU communicates with the screen, it is necessary to reset the screen once, that is, set the pin low for a short time and then set it high (used to 100ms).

DC: data/command selection port. Different from the OLED screen I introduced before, OLED distinguishes whether it is a command or displays SRAM data by writing data to different registers, and this screen distinguishes whether the currently received data is a command or a display through the state of the pin level data. When the DC port is low, commands are written; when it is high, data is written.

BLK: Screen backlight. Unlike OLED screens with self-illuminating pixels, IPS screens require a backlight to display images. The backlight of the screen is turned on by default. When the BLK port is set low, the backlight is turned off. Here you can use a GPIO to control it, or leave it floating directly, or connect it to a high level.

The specific connection method with STM32 is as follows:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 Because there is only one slave station of the TFT screen, so SPI_NSS is not connected.

------------------------------------------

Next, while watching the program, explain

1. Header file and GPIO reset definition

Include the peripheral header files used in the .c file: GPIO, RCC, SPI

Define the high and low levels of PA1, PA2, and PA3. This is different from the 51 single-chip microcomputer. The 51 single-chip microcomputer can use sbit to map variables to the GPIO port, but STM32 does not have sbit.

#include<stm32f10x.h>
#include<stm32f10x_gpio.h>
#include<stm32f10x_rcc.h>
#include<stm32f10x_spi.h>

#define SPI_DC_H() GPIO_SetBits(GPIOA,GPIO_Pin_2)			//数据/命令选择
#define SPI_DC_L() GPIO_ResetBits(GPIOA,GPIO_Pin_2)			

#define LCD_RES_H() GPIO_SetBits(GPIOA,GPIO_Pin_3)				//LCD复位(PA3)
#define LCD_RES_L() GPIO_ResetBits(GPIOA,GPIO_Pin_3)

#define LCD_BLK_H()	GPIO_SetBits(GPIOA,GPIO_Pin_1)				//LCD背光(PA1)
#define LCD_BLK_L() GPIO_ResetBits(GPIOA,GPIO_Pin_1)

2. Convert the picture to hexadecimal data

To display characters on the OLED, it is realized by making the screen pixels light up or go out according to the font. The same is true for displaying pictures. For color screens, each pixel has more than 0 or 1 states. It has 65535 states. Just let each pixel be in its own way according to the way I want to get a picture . We can put it in an array, where the elements are the state values ​​of each pixel. Then transmit the elements of the array to the display buffer register of the TFT screen in order through SPI, and then the desired picture can be displayed on the screen.

The gadget that needs to be used here is Img2Lcd. This is a tool that converts images into arrays. The tool can be downloaded online.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 Now to demonstrate how to convert the following picture into a C program array. Since I am using a 240x240 square screen here, in order to fill the screen, I deliberately cut the picture into a square.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_18,color_FFFFFF,t_70,g_se,x_16

 It should be noted that Img2Lcd can only convert pictures in jpg format. If it is a picture in other formats, it needs to be converted to jpg format first.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_14,color_FFFFFF,t_70,g_se,x_16

 then open the file in the gadget

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 After opening, you need to set related parameters. The output grayscale selects 16-bit true color.

Attention should be paid to the maximum width and height settings. The STM32F103C8T6 I use here has only 64KB of Flash, so the size of the array must not exceed 64KB, otherwise it will fail to compile due to insufficient storage space. Here I leave a certain amount of Flash margin for other variables and programs to occupy, and allocate 60KB of space to the array. And one pixel of the screen requires 2B of space, that is, a total of 60K÷2=30000 pixel states can be stored. Then take the square root of 30000, which is about 173. So set the maximum width and height to 173x173 here. (If the single-chip microcomputer used is 128K or above flash, it can be displayed in full screen and set to 240x240)

Uncheck "Include image header data", check "high bit first" (this is because the screen sends high bit first and then low bit, if you don't check it, there will be an inverted color effect). Finally, you can also increase the contrast a little bit (because the color of the IPS screen is not bright enough, the final picture will be a bit dim).

After the setting is complete, click the small button on the right of "Maximum Width and Height" to generate a preview as shown below.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 Then click "Save", select the path and confirm to get a .c file, here we directly open it with Notepad. It can be found that there is an array inside, the number of elements is 59859, which is exactly equal to 173x173x2.

Each element here is a 2-digit hexadecimal number, and every two elements represent a pixel.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 We then copy-paste that array into the program (below). The array is long, it can be folded.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 In this way, this step is completed.

3. SPI initialization

Because the hardware SPI1 is used, SPI1 needs to be initialized.

The first is the GPIO setting. SPI1 occupies a total of four IOs from PA4 to PA7, and these four IOs are set as multiplexed push-pull outputs. In addition, three IO ports PA1, PA2, and PA3 are used as control ports for backlight, data/command selection, and reset respectively. So it is also necessary to initialize these three IOs here, and set them as push-pull outputs. Then turn on the corresponding clock.
Then there is the configuration of SPI. Like GPIO, there is also a structure in the SPI peripheral library, and the members in it are the parameters of SPI. The specific content of the structure can be viewed in the peripheral library.8f7c84e0e5104c33a422f4e6ccc2fa00.png

The code below is for reference

void SPI_UserInit(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;    //PA4为SPI_NSS,PA5为SPI_SCK,PA6为SPI_MISO,PA7为SPI_MOSI,对SPI这4个端口进行初始化
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;		//PA1为BLK背光,PA2位DC选择,PA3为屏幕复位
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;			//推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  SPI_InitTypeDef SPI_InitStructure;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);		//使能时钟

  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//设置为双向双线全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//设置为SPI主站。控制屏幕,单片机需做主站
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//设置数据大小为8位,即每次发送8位数据
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//设置串行时钟的稳态为时钟高
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;		//设置位捕捉的时钟活动沿为第一个上升沿
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//设置NSS为软件控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//设置波特率分频(该值越大,波特率越慢)
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//设置先发送高位
  SPI_InitStructure.SPI_CRCPolynomial = 7;			//设置CRC校验												
  SPI_Init(SPI1, &SPI_InitStructure);				//初始化
 
  SPI_Cmd(SPI1, ENABLE);		//使能SPI1
}

4. SPI write data and write command

Like the OLED screen, the sub-functions for writing data and writing commands need to be declared separately.

As mentioned earlier, the screen uses the DC port to distinguish commands and data, so the difference between writing data and writing commands is that the DC ports are high and low respectively. Since writing data is also divided into writing 8-bit data and writing 16-bit data, there are also two functions for writing data.

It should be noted that SPI itself supports 16 data frames (set by  SPI_InitStructure.SPI_DataSize  ), but in order to facilitate sending 8-bit data frames, the DataSize is set to 8 in SPI initialization. Sending 16-bit data is achieved by first sending the lower 8 bits, then shifting the data to the right by 8 bits, and then sending 8-bit data again.

void SPI_WriteCmd(u8 Data)	//写命令
{
	SPI_DC_L();
	SPI_I2S_SendData(SPI1,Data);
}

void LCD_WriteData8(u8 Data)	//写8位数据
{
	SPI_DC_H();
	SPI_I2S_SendData(SPI1,Data);
}

void LCD_WriteData16(u16 Data)	//写16位数据
{
	SPI_DC_H();
	SPI_I2S_SendData(SPI1,(Data>>8) ) ;	//Date右移8位
	SPI_I2S_SendData(SPI1,Data);		
}

5. Screen initialization

Like the OLED screen, after the IPS is powered on, parameters need to be set for initialization.

The specific parameters that need to be set can be found in the ST7789 manual. You can refer to the following parameters.

void LCD_Init(void)
{
	u16 x;
	LCD_RES_L();
	Delay_ms(50);
	LCD_RES_H();
	Delay_ms(50);                //复位屏幕
	
	SPI_WriteCmd(0x3A);						//设置颜色格式为16位真彩
	LCD_WriteData8(0x05);				//03 4K;05 65K;06 262K
	
	SPI_WriteCmd(0x36);			//设置屏幕方向为从上到下,从左到右
	LCD_WriteData8(0x00);

//----------- ST7789S Frame rate setting ---------//
	SPI_WriteCmd(0xB2);				
	LCD_WriteData8(0x0C);
	LCD_WriteData8(0x0C);
	LCD_WriteData8(0x00);
	LCD_WriteData8(0x33);
	LCD_WriteData8(0x33);
	
	SPI_WriteCmd(0xB7); 
	LCD_WriteData8(0x35);
	  
//----------- ST7789S Power setting ---------//

	SPI_WriteCmd(0xBB);
	LCD_WriteData8(0x19);
	
	SPI_WriteCmd(0xC0);
	LCD_WriteData8(0x2C);
	
	SPI_WriteCmd(0xC2);
	LCD_WriteData8(0x01);
	
	SPI_WriteCmd(0xC3);
	LCD_WriteData8(0x12);   
	
	SPI_WriteCmd(0xC4);
	LCD_WriteData8(0x20);  
	
	SPI_WriteCmd(0xC6); 
	LCD_WriteData8(0x0F);    
	
	SPI_WriteCmd(0xD0); 
	LCD_WriteData8(0xA4);
	LCD_WriteData8(0xA1);

//----------- Posistive Voltage Gamma Control ---------//
	SPI_WriteCmd(0xE0);
	LCD_WriteData8(0xD0);
	LCD_WriteData8(0x04);
	LCD_WriteData8(0x0D);
	LCD_WriteData8(0x11);
	LCD_WriteData8(0x13);
	LCD_WriteData8(0x2B);
	LCD_WriteData8(0x3F);
	LCD_WriteData8(0x54);
	LCD_WriteData8(0x4C);
	LCD_WriteData8(0x18);
	LCD_WriteData8(0x0D);
	LCD_WriteData8(0x0B);
	LCD_WriteData8(0x1F);
	LCD_WriteData8(0x23);

	SPI_WriteCmd(0xE1);
	LCD_WriteData8(0xD0);
	LCD_WriteData8(0x04);
	LCD_WriteData8(0x0C);
	LCD_WriteData8(0x11);
	LCD_WriteData8(0x13);
	LCD_WriteData8(0x2C);
	LCD_WriteData8(0x3F);
	LCD_WriteData8(0x44);
	LCD_WriteData8(0x51);
	LCD_WriteData8(0x2F);
	LCD_WriteData8(0x1F);
	LCD_WriteData8(0x1F);
	LCD_WriteData8(0x20);
	LCD_WriteData8(0x23);

//----------- Setting ---------//
	SPI_WriteCmd(0x21); 
	SPI_WriteCmd(0x11)
	SPI_WriteCmd(0x29);

	LCD_SetRegion(0,0,239,239);
	SPI_DC_H();
	for(x=0;x<57600;x++)            //清屏
		{
			LCD_WriteData16(0xffff);
		}
	LCD_BLK_H();
}

After the function, I added a loop here, which is used to clear the screen. Because the data in the SRAM displayed inside the screen will be lost when the power is turned off. After each power-on, the image before the last power-off will not be displayed, but a snowflake screen like the one shown below will be displayed.


The principle of clearing the screen is to write data to the display SRAM again, and fill all the pixels with a certain state (which can be understood as a color). Here I fill it with 0xffff, 0xffff is pure white in RGB565, so the final effect is a white screen. If it is filled with 0x0000, it is filled with black, and the final effect is a black screen. Of course, you can also fill in any color you want as needed.

The cycle fills one pixel at a time, and the full screen has a total of 57600 pixels, so the cycle is 57600 times.

6. Set the display area

The display area is a rectangular area, so you need to set the start point and end point.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

0x2a: set(x0,x1)

0x2b: set(y0, y1)

void LCD_SetRegion(u16 x0, u16 y0, u16 x1, u16 y1)//设置显示区域(x0,y0,x1,y1)
{
	SPI_WriteCmd(0x2a);
	LCD_WriteData16(x0);
	LCD_WriteData16(x1);
	SPI_WriteCmd(0x2b);
	LCD_WriteData16(y0);
	LCD_WriteData16(y1);
	SPI_WriteCmd(0x2c);	        //下面发送的是数据
}

7. SPI transfer array

The library function for SPI transmission data in STM32 is         SPI_I2S_SendData(SPIx,Deta);

There are 59859 elements in the image array, so 59859 data need to be sent. Before sending data, the DC port needs to be set high.

void image(void)
	{
		u16 x;
		SPI_DC_H();        //DC置高,发送数据
		for(x=0;x<59860;x++)    //循环填充像素
		{
			SPI_I2S_SendData(SPI1,gImage_girl[x]);    //将图片数组的元素通过SPI发送给IPS屏
		}
	}

8. Main function

Initialize GPIO, SPI, and ISP screens, set display area coordinates, and send data.

In order to allow the picture to be displayed in the middle of the screen, we need to set the display area in the middle.
Here is a simple calculation, 240-173=67, 67/2≈33, so the coordinates (x0, y0) can be set to (33, 33), and the
image resolution is 173 x173, so the coordinates of x1, y1 should be (33+173-1, 33+173-1), or 205,205. The reason why we need to subtract 1 here is similar to the "tree planting problem" in elementary school mathematics. So the function to limit the area should be (33,33,205,205)

32756d0db6574345821b7c93c083e5b8.png

int main(void)
{	
	SPI_UserInit();
	LCD_Init();
	LCD_SetRegion(33,33,205,205);
	image();
}

The final display effect is as follows:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

If it is a single-chip microcomputer with Flash greater than 115KB, it can be displayed in full screen (because of the camera shooting, the picture looks chromatic):

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

If the Flash is small, the full screen can also be displayed by dividing it multiple times.

Through the above steps, various pictures and videos can be displayed on the screen.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

 "bad apple" demo

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAI2xpdWZlbmdlcyM=,size_20,color_FFFFFF,t_70,g_se,x_16

display text

         This article just explains and demonstrates how to use SPI communication to display pictures on the IPS screen. No matter what kind of single-chip microcomputer, its Flash is very limited, and the picture will not be directly stored in the internal Flash in actual use. The most commonly used method is to store the picture in the SD card, and then the microcontroller reads the data from the SD card. Regarding the use of the SD card, we will explain it later in the article.

For other advanced usages such as making animation effects and playing videos, we will introduce them later.

Guess you like

Origin blog.csdn.net/qq_55203246/article/details/124281077