GD32F303 debugging notes (2) SPI (software SPI, hardware SPI, hardware SPI+DMA)

Preface

Currently, TFT-LCD is used in a project, and its driver chip is ILI9341. In order to better achieve the display effect, in the final code we will use the hardware SPI+DMA module that comes with the microcontroller ( because the waveform output by SPI+DMA during the debugging process failed to drive the screen successfully, in the end only the hardware SPI of GD32 was used. Please give me some suggestions after reading this article. I will explain the phenomenon in detail ) to reduce the load on the CPU as much as possible. However, during the early debugging process, we will use software simulation SPI and separate hardware SPI. Since we only use SPI to communicate with the driver chip of the screen display, we only use SPI transmission. In the following code demonstration, SPI reception is not included. In the future, if there is an opportunity to use SPI communication FLASH memory chips or others, I will publish another debugging article. (I will publish another article to introduce the transplantation of screen drive display and LVGL graphical interface library in detail. Friends who are interested can wait patiently.)

SPI

A serial communications bus that contains a clock line , a chip select line , and one or two data lines .

  • If one data line is used to represent SPI's TX and RX sharing the same line, that is, 3-wire SPI , sending data and receiving data cannot be performed simultaneously.
  • If two data lines are used to represent SPI's TX and RX, each uses a separate line, that is, 4-wire SPI , sending data and receiving data can be performed simultaneously.
  • The chip select line enables SPI communication from the slave device, so that multiple slave devices capable of SPI communication can be mounted on the SPI communication bus controlled by the host (such as a microcontroller) (this also means that the chip select line is connected to multiple slave devices There are also multiple ).
  • The clock line is the rate of SPI communication, just like the crystal oscillator of a microcontroller or the human heartbeat. The transmission and reception on the data line must be carried out with the level transition on the clock line.

The typical timing diagram of SPI communication is not given here, because the chip manual of any slave device that supports SPI communication will have the corresponding communication timing, and in the following chapters, there will be actual waveforms captured by the oscilloscope.

DMA

An assistant that can help the CPU greatly reduce processing pressure. I won’t introduce it here.

Programming of each module

Before configuring, please make sure you already have a GD32F303 keil project that includes its corresponding standard library . The project can be created using official routines or according to the project creation and compilation of GD32F303 Debugging Notes (Zero) . In addition, it is highly recommended to have an oscilloscope or logic analyzer nearby to view the communication waveforms output by our ports.

1. Clock configuration

  • Turn on the GPIO port clock, GPIO pin multiplexing clock, DMA clock and SPI2 module clock.
void SystemClock_Reconfig(void)
{
    
    
		/* Enable all peripherals clocks you need*/
		rcu_periph_clock_enable(RCU_GPIOA);
		rcu_periph_clock_enable(RCU_GPIOB);
		rcu_periph_clock_enable(RCU_GPIOC);
		rcu_periph_clock_enable(RCU_GPIOD);
		
		rcu_periph_clock_enable(RCU_DMA0);
		rcu_periph_clock_enable(RCU_DMA1);
//		rcu_periph_clock_enable(RCU_I2C1);
//		rcu_periph_clock_enable(RCU_ADC0);
//		rcu_periph_clock_enable(RCU_ADC2);
//		rcu_periph_clock_enable(RCU_USART1);
		rcu_periph_clock_enable(RCU_USART2);
		rcu_periph_clock_enable(RCU_SPI2);
		/* Timer1,2,3,4,5,6,11,12,13 are hanged on APB1,
		 * Timer0,7,8,9,10 			 are hanged on APB2
		 */
		rcu_periph_clock_enable(RCU_TIMER1);	

		rcu_periph_clock_enable(RCU_AF);
}

2. GPIO configuration

Please add image descriptionPlease add image description
Please add image description

  • According to the description of the SPI2 pin in the manual above and the wiring in the actual circuit schematic diagram, the relevant IO configuration is as follows:
/* SPIx By Soft Or Hardware */
#define SPI2_SOFT			0		// 1:使用软件SPI				0:使用硬件SPI 这里使用软件SPI初始化屏,硬件SPI刷屏 
#define SPI2_DMA			0		//1:使用硬件SPI+DMA		0:仅使用硬件SPI(使用软件SPI时该宏无意义)

// SPI port and pins definition
#define SPI2_PORT						GPIOB
#define SPI2_SCK_PIN					GPIO_PIN_3
#define SPI2_MISO_PIN					GPIO_PIN_4
#define SPI2_MOSI_PIN					GPIO_PIN_5
#define SPI2_CS_PIN						GPIO_PIN_6 //NSS

// TFT port and pins definition
#define TFT_PORT						GPIOB
#define TFT_RS_PIN						GPIO_PIN_7		//1:发数据 0:发命令
#define TFT_RST_PIN						GPIO_PIN_8		//1:不复位 0:复位	 
#define TFT_BG_PIN						GPIO_PIN_9		//1:亮 		 0:不亮	

void GPIO_Init(void)
{
    
    
	/* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
	
#if SPI2_SOFT
	gpio_init(SPI2_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SPI2_CS_PIN | SPI2_MOSI_PIN | SPI2_SCK_PIN);	
	gpio_init(SPI2_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, SPI2_MISO_PIN);
#else
	gpio_pin_remap_config(GPIO_SPI2_REMAP,DISABLE);
	gpio_init(SPI2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SPI2_MOSI_PIN | SPI2_SCK_PIN); // | SPI2_MISO_PIN
	gpio_init(SPI2_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, SPI2_MISO_PIN);
	gpio_init(SPI2_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SPI2_CS_PIN);	

#endif
	/* demo board TFT_LCD I/O */
	gpio_init(TFT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, TFT_RST_PIN | TFT_BG_PIN);	
	gpio_init(TFT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, TFT_RS_PIN);

}

3. DMA configuration

Please add image description

  • As can be seen from the above figure, the TX of SPI2 corresponds to the CH1 of DMA1, and the RX of SPI2 corresponds to the CH0 of DMA1.
  • Since it is a screen driver, you only need to write send in the code.
#define SPI2_TX_SIZE	8
uint8_t SPI2TX_Buffer[SPI2_TX_SIZE] = {
    
    0};

void DMA_Init(void)
{
    
    
	dma_parameter_struct dma_init_SPI2_TX={
    
    0};

	dma_deinit(DMA1, DMA_CH1);									//SPI2_TX

	/* initialize DMA1 channel1(SPI2_TX) */						
	dma_init_SPI2_TX.direction = DMA_MEMORY_TO_PERIPHERAL;
	dma_init_SPI2_TX.memory_addr = (uint32_t)SPI2TX_Buffer;
	dma_init_SPI2_TX.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_SPI2_TX.memory_width = DMA_MEMORY_WIDTH_32BIT;
	dma_init_SPI2_TX.number = SPI2_TX_SIZE;
	dma_init_SPI2_TX.periph_addr = (uint32_t)(&SPI_DATA(SPI2));
	dma_init_SPI2_TX.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_SPI2_TX.periph_width = DMA_PERIPHERAL_WIDTH_32BIT;
	dma_init_SPI2_TX.priority = DMA_PRIORITY_LOW;
	dma_init(DMA1, DMA_CH1, &dma_init_SPI2_TX);	
	dma_circulation_disable(DMA1, DMA_CH1);
	
	dma_memory_to_memory_disable(DMA1,DMA_CH1);					//SPI2_TX
	/* enable all DMA channels you need */
	dma_channel_disable(DMA1,DMA_CH1);							//SPI2_TX
}

4. SPI configuration

  • Host full-duplex mode, 8-bit data bits, clock line idle high level second edge detection, hardware NSS is not applicable, SPI baud rate is 27MHz, data is sent high bit first.
  • Among them, spi2_init.clock_polarity_phase can be replaced by others, and spi2_init.prescale is set slower such as a few hundred kHz during initial debugging.
void SPIx_Init(void)
{
    
    
	spi_parameter_struct spi2_init = {
    
    0};
  /* deinitilize SPI and the parameters */
  spi_i2s_deinit(SPI2);

	/* SPI2 parameter config */
	spi2_init.device_mode			= SPI_MASTER;
	spi2_init.trans_mode			= SPI_TRANSMODE_FULLDUPLEX;
	spi2_init.frame_size			= SPI_FRAMESIZE_8BIT;
	spi2_init.clock_polarity_phase	= SPI_CK_PL_HIGH_PH_2EDGE;		//SPI_CK_PL_HIGH_PH_1EDGE		SPI_CK_PL_LOW_PH_1EDGE
	spi2_init.nss					= SPI_NSS_SOFT;
	spi2_init.prescale				= SPI_PSC_2;					// Fspi1=54MHz/32=0.84375MHz
	spi2_init.endian				= SPI_ENDIAN_MSB;
	spi_init(SPI2, &spi2_init);
	
	/* enable SPI2 */
	spi_enable(SPI2);
	/* enable SPI2 DMA channel */
	spi_dma_enable(SPI2,SPI_DMA_TRANSMIT);
//	spi_dma_enable(SPI2,SPI_DMA_RECEIVE);
}

5. SPI writing function

  • Different chips used or different implementation methods mean that there will be slight differences in writing the underlying code. In this chapter, it is the difference in writing this function for SPI.
  • As the title of my article indicates, I will list the low-level writing functions in three ways: software SPI, hardware SPI, and hardware SPI+DMA.

1. Software SPI writing function

  • Software SPI, to put it bluntly, allows the CPU to operate IO to simulate SPI waveforms.
  • We write a byte and the SCK clock line toggles 8 times. Data is sent starting from the high bit, then it is ANDed to 0x80. The data line determines whether the current bit is high or low based on the result of ANDING to 0x80 and outputs high and low levels. We consider the operation of such a frame to be the operation of writing one byte.
void SPI_WriteByte(uint8_t ndata)
{
    
    
	uint8_t i;
	for(i=0;i<8;i++)
	{
    
    
		gpio_bit_write(SPI2_PORT, SPI2_SCK_PIN, RESET);
		if(ndata&0x80)
			gpio_bit_write(SPI2_PORT, SPI2_MOSI_PIN, SET);
		else
			gpio_bit_write(SPI2_PORT, SPI2_MOSI_PIN, RESET);
		gpio_bit_write(SPI2_PORT, SPI2_SCK_PIN, SET);
		ndata <<= 1;
	}
}

2. Hardware SPI writing function

  • Hardware SPI allows the CPU to control the hardware module in the microcontroller that can output SPI waveforms.
  • Remember the SPI2 we initialized and configured above, where we have already set the working mode of SPI2. Here we only need to send read and write commands.
  • In order to ensure strict compliance with the communication timing, it is necessary to check whether the relevant cache register is set before reading/writing.
  • In order to prevent unexpected interference from causing the MCU to freeze or run away in actual situations, in addition to the necessary watchdog, any non-essential while loop must have a timeout mechanism.
  • Although I performed a read operation here, I did not process the read content. If you want to have this function, just use the read value as the return value of the function. There are other uses for writing this here.
void SPIx_Master_Write_Byte_Hard(uint8_t ndata,uint32_t TimeOut)
{
    
    
	uint32_t timeout_t=0;

	timeout_t = TimeOut;

	/* loop while spi tx data register in not emplty */
	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE) )
	{
    
    
		if(TimeOut > 0) TimeOut--; 
		else 				break;			
	}
	/* send byte through the SPI2 peripheral */
	spi_i2s_data_transmit(SPI2,ndata);

	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE))
	{
    
    
		if(timeout_t > 0) timeout_t--; 
		else 					break;		
	}		
	spi_i2s_data_receive(SPI2);
}

3. Hardware SPI+DMA writing function

  • Hardware SPI+DMA, the principle is the same as simply operating hardware SPI, but it becomes DMA to operate the hardware SPI module, and the CPU controls the DMA.
  • According to our configuration of DMA1_CH1 above, when we enable this channel, it means the beginning of a data transfer.
void SPIx_Transmit_DMA(uint32_t spi_periph,uint16_t* ndata,uint16_t Size)
{
    
    
	if(SPI2 == spi_periph)
	{
    
    
		/* TX */
		dma_channel_disable(DMA1,DMA_CH1);	
		
		dma_memory_address_config(DMA1,DMA_CH1,(uint32_t)ndata);
		dma_transfer_number_config(DMA1,DMA_CH1,Size);

		dma_channel_enable(DMA1,DMA_CH1);		
	}
}

6. Main function part

1. Display part

  • Here we encapsulate another layer to cut the underlying functions of software SPI and hardware SPI.
void lcd_wr_byte(uint8_t ndata)   
{
    
    		
	gpio_bit_write(TFT_PORT,TFT_RS_PIN,SET);	
	gpio_bit_reset(SPI2_PORT,SPI2_CS_PIN);	//CS=0
#if SPI2_SOFT
	SPIx_Master_Write_Byte(ndata,0xFFFFFFFFU);
#else
	SPIx_Master_Write_Byte_Hard(ndata,0xFFFFFFFFU);
#endif
	gpio_bit_set(SPI2_PORT,SPI2_CS_PIN);	//CS=1
}
  • This is the TFT-LCD color screen part, which you can roughly understand.
void LCD_Fillx(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint16_t* color)
{
    
              
	uint16_t i;
	uint8_t MSB_8=0,LSB_8=0;
	
	if(sx>ex)
	{
    
    
		i	= sx;
		sx = ex;
		ex = i;
	}
	if(sy > ey)
	{
    
    
		i	= sy;
		sy = ey;
		ey = i;			
	}

	uint32_t total = (ex - sx + 1)*(ey - sy + 1);
	uint32_t j = 0;		
	LCD_SetCursor(sx,sy,ex,ey);
	lcd_wr_gram();

	for(j = 0;j < total;j++)
	{
    
    									   
		lcd_wr_byte((*color)>>8);
		lcd_wr_byte((*color)&0x00FF);
	}
} 

void LCD_Fill_DMA(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint16_t* color)
{
    
              
	uint16_t i;
	
	if(sx>ex)
	{
    
    
		i	= sx;
		sx = ex;
		ex = i;
	}
	if(sy > ey)
	{
    
    
		i	= sy;
		sy = ey;
		ey = i;			
	}

	uint32_t total = (ex - sx + 1)*(ey - sy + 1)*2;
	
	LCD_SetCursor(sx,sy,ex,ey);
	lcd_wr_gram();
	gpio_bit_reset(SPI2_PORT,SPI2_CS_PIN);	//CS=0
	gpio_bit_write(TFT_PORT,TFT_RS_PIN,SET);	
	SPIx_Transmit_DMA(SPI2,color,total);		//(uint16_t*)0xF800		color
}  

2. Task function

void TASK_TFT_REFRESH(void)
{
    
    
	static uint32_t refresh_count=0;
	static uint16_t color_temp=0;
	
	if(refresh_count%3==0)
		color_temp = 0xF800;
	else if(refresh_count%3==1)
		color_temp = 0x001F;
	else if(refresh_count%3==2)
		color_temp = 0xFFE0;		
#if (SPI2_SOFT==0) && (SPI2_DMA==1)
	LCD_Fill_DMA(0,0,319,239,&color_temp);
#else
	LCD_Fillx(0,0,319,239,&color_temp);
#endif
	refresh_count++;
}

3. Main function

  • The main function program logic is as follows:
  • TMT is a time slice framework. See GITEE for the source code . Here we let TASK_TFT_REFRESH(); this function be executed every 1.5 seconds. The main function can be understood here.
int main(void)
{
    
    	
	SystemTick_Init();
	
	SystemClock_Reconfig();
	
	GPIO_Init();
	Timer1_Init();
	DMA_Init();
	USARTx_Init();
#if !SPI2_SOFT
	SPIx_Init();
#endif	
	NVIC_Init();
	TMT_Init();
	LCD_Init();
	TMT.Create(TASK_TFT_REFRESH,1500);
	delay_ms(2000);
	while(1)
	{
    
    
		TMT.Run();
	}
}

7. Result Demonstration

1. Actual effect

  • The final effect is as follows. The difference between software and hardware SPI is only in the screen refresh speed, so the video will not be added separately.

2. Drive waveform

  • During the debugging process, this is actually the key point. To determine whether our code is successfully configured and whether the logic is incorrect, we need to view the input/output waveform of the corresponding IO to understand.

- Software SPI waveform
Please add image description

  • In the picture above, the yellow waveform is the SPI clock line (SCK), and the blue waveform is the SPI data output line (MOSI).
  • We can clearly see that the SPI waveform is based on one byte (8 bits) as one frame, the clock line flips 8 times, the data sent is 0x55 (0b01010101), and the rate is about 2.3MHz.

- Hardware SPI waveform

Please add image description

  • In the picture above, under hardware SPI simulation, we can clearly see that the SPI waveform still uses one byte (8 bits) as one frame, the clock line flips 8 times, the data sent is still 0x55 (0b01010101), and the rate is 3.5MHz about. However, the hardware IO response is faster than the CPU software simulation, and the flips of SCK and MOSI during communication are almost simultaneous.
    Please add image description

  • At this time, we can adjust the hardware SPI configuration and set spi2_init.prescale = SPI_PSC_2; Since my microcontroller works at a frequency of 108M, the SPI2 module frequency under this configuration is 27MHz.

  • The picture above shows the waveform captured by the oscilloscope. You can see that the frequency is 25.64MHz, which is similar to the configured one. However, the waveform of the clock line (SCK) has been significantly distorted. Personally, I think it is caused by the length of the PCB traces , the reference ground captured by the oscilloscope , and the internal circuit of the module of the chip itself . However, the screen can still be refreshed successfully, so I won't worry about it.
    Please add image description

  • The multi-byte SPI transmit clock line waveform is as follows:

- Hardware SPI+DMA waveform
Please add image description

  • The picture above is the clock line waveform diagram using hardware SPI+DMA.
  • We found that the waveform in this case was continuous, and to a certain extent, there was nothing wrong with our code configuration. However, this continuous waveform cannot drive my color screen . After repeated verification, I found that no matter whether DMA is used or not, as long as the output SPI waveform is continuous, it cannot communicate with the TFT color screen.
  • Remember how we wrote the byte write function of pure hardware SPI above?
void SPIx_Master_Write_Byte_Hard(uint8_t ndata,uint32_t TimeOut)
{
    
    
	uint32_t timeout_t=0;

	timeout_t = TimeOut;

	/* loop while spi tx data register in not emplty */
	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE) )
	{
    
    
		if(TimeOut > 0) TimeOut--; 
		else 				break;			
	}
	/* send byte through the SPI2 peripheral */
	spi_i2s_data_transmit(SPI2,ndata);

	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE))
	{
    
    
		if(timeout_t > 0) timeout_t--; 
		else 					break;		
	}		
	spi_i2s_data_receive(SPI2);
}
  • Here we modify it slightly and it becomes like this:
  • In 4-wire full-duplex mode, if I only use sending and don't care about receiving, there is actually nothing to receive or process.
void SPIx_Master_Write_Byte_Hard(uint8_t ndata,uint32_t TimeOut)
{
    
    
	uint32_t timeout_t=0;

	timeout_t = TimeOut;

	/* loop while spi tx data register in not emplty */
	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_TBE) )
	{
    
    
		if(TimeOut > 0) TimeOut--; 
		else 				break;			
	}
	/* send byte through the SPI2 peripheral */
	spi_i2s_data_transmit(SPI2,ndata);

//	while( RESET == spi_i2s_flag_get(SPI2,SPI_FLAG_RBNE))
//	{
    
    
//		if(timeout_t > 0) timeout_t--; 
//		else 					break;		
//	}		
//	spi_i2s_data_receive(SPI2);
}
  • Here we find that when pure hardware SPI sends multiple bytes, the clock line also becomes a continuous waveform. As long as the hardware outputs a continuous waveform, it will cause abnormal communication with the screen.

8. Summary and questions

1. Summary

  • At this point, we finally successfully output the SPI waveform using the above three methods. Two of the methods can successfully make the screen run, which can be considered a success in terms of results.

2. Questions

  • After collecting information on the Internet, someone's article said that it is normal for the SPI waveform to be continuous, and that the continuous waveform is better than the discontinuous one, indicating that the SPI module responds faster.
  • So I would like to ask you:
    1. Are there any cases where SPI continuous waveform communication is normal? If so, which type of modules are these cases more common in?
    2. The existing SPI communication screen can successfully drive the screen after a slight pause after the clock flips 8 times. So how to insert a short pause operation in GD32 when using hardware SPI+DMA?
    3. Are there any successful cases of continuous waveform SPI four-wire communication using ILI9341? If so, can you give relevant suggestions?
    4. Regarding this phenomenon, is it a hardware compatibility issue? Are there some SPI communication modules that only support frame (8bit or 16bit) transmission but not this multi-bit transmission?
  • If you have relevant suggestions, please leave a message in the comment area to learn from each other and grow together.

!!!This article was originally published by Huanxi 6666 on CSDN. Please indicate the source when copying or reprinting:)!!!

Guess you like

Origin blog.csdn.net/qq_37554315/article/details/120591975
SPI