cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103RB DMA issue.

Phill Harvey-Smith
Associate II

Hi all,

I'm using SPI to communicate with an Adafruit 2.8" TFT LCD, (ILI9341 based). This is mostly working fine, I can plot pixels, draw lines, circles fonts etc. So the communication with the display is working. The STM end of things is a Nucleo 64 board with a F103RB.

However as part of my text plotting routines I want to be able to scroll the display when it becomes full, this obviously requires reading a line of pixel data and then writing it again several lines up the screen. I have this working completely reliably using a pair of for next loops along with a function to read or write a block of data. However this is slow.

So I have re-implemented this using DMA to do the reading and writing, as this will be faster (and it is about 2x the speed). After much fiddling I got this working.

However I then added code un-related to my DMA code and the DMA seemed to randomly stop working, I have no idea why? One thing to note is that I only use the DMA for transfering the large blocks of data during scrolling (320 pixels * 3 bytes / pixel), not for the normal commands to the display. I don't however change anything about the SPI channel (apart from switching on and off the DMA flags), once initialised.

My SPI Init code :

	LL_SPI_InitTypeDef SPI_InitStruct;
	LL_GPIO_InitTypeDef GPIO_InitStruct;
 
	LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_5);
 
	/* Peripheral clock enable */
	LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2);
 
	/**SPI2 GPIO Configuration
	 PB13   ------> SPI2_SCK
	 PB14   ------> SPI2_MISO
	 PB15   ------> SPI2_MOSI
	*/
	GPIO_InitStruct.Pin = LL_GPIO_PIN_13|LL_GPIO_PIN_15;
	GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
	GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
	GPIO_InitStruct.Pin = LL_GPIO_PIN_14;
	GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
	LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
	SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
	SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
	SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
	SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_HIGH;
	SPI_InitStruct.ClockPhase = LL_SPI_PHASE_2EDGE;
	SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
	SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2;
	SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
	SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
	SPI_InitStruct.CRCPoly = 10;
	LL_SPI_Init(SPI2, &SPI_InitStruct);
 
	/* Setup flag pins, CS, C/D, reset */
	GPIO_InitStruct.Pin = nCS_IL9341_Pin | CD_IL9341_Pin | RES_IL9341_Pin;
	GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
	GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	LL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
	ILIDeSelect();
	ILIClearReset();
 
	LL_SPI_Enable(SPI2);

My DMA code :

void ILI9341_ConfigDMA(void)
{
	LL_DMA_InitTypeDef	DMA_InitStructure;
 
	/* DMA1 used for SPI2 Transmission
	 * DMA1 used for SPI2 Reception
	*/
	/* (1) Enable the clock of DMA1 and DMA1 */
	LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
 
	LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_5);
 
	DMA_InitStructure.PeriphOrM2MSrcAddress = LL_SPI_DMA_GetRegAddr(SPI2);
	DMA_InitStructure.MemoryOrM2MDstAddress = 0;
	DMA_InitStructure.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
	DMA_InitStructure.NbData = (LCD_WIDTH*3);
	DMA_InitStructure.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
	DMA_InitStructure.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
	DMA_InitStructure.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
	DMA_InitStructure.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
	DMA_InitStructure.Mode = LL_DMA_MODE_NORMAL;
	DMA_InitStructure.Priority = LL_DMA_PRIORITY_LOW;
	LL_DMA_Init(DMA1, LL_DMA_CHANNEL_4, &DMA_InitStructure);
 
	LL_DMA_DeInit(DMA1, LL_DMA_CHANNEL_5);
 
	DMA_InitStructure.PeriphOrM2MSrcAddress = LL_SPI_DMA_GetRegAddr(SPI2);;
	DMA_InitStructure.MemoryOrM2MDstAddress = 0;
	DMA_InitStructure.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
	DMA_InitStructure.NbData = (LCD_WIDTH*3);
	DMA_InitStructure.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
	DMA_InitStructure.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
	DMA_InitStructure.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
	DMA_InitStructure.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
	DMA_InitStructure.Mode = LL_DMA_MODE_NORMAL;
	DMA_InitStructure.Priority = LL_DMA_PRIORITY_LOW;
	LL_DMA_Init(DMA1, LL_DMA_CHANNEL_5, &DMA_InitStructure);
}
 
void ILI9341_DMA_SendReceive(uint8_t	*Buffer,
							 uint32_t	Count,
							 uint8_t	DummySend)
{
	uint8_t		Dummy = LCD_NOP;
 
	_delay_us(1);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_4,Count);
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_4,(uint32_t)Buffer);
 
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5,Count);
 
	if(DummySend)
	{
		LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5,(uint32_t) &Dummy);
		LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MEMORY_NOINCREMENT);
	}
	else
	{
		LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5,(uint32_t) Buffer);
		LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_5, LL_DMA_MEMORY_INCREMENT);
	}
 
	LL_SPI_EnableDMAReq_RX(SPI2);
	LL_SPI_EnableDMAReq_TX(SPI2);
 
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
 
	while (!LL_DMA_IsActiveFlag_TC5(DMA1) && !LL_DMA_IsActiveFlag_TC4(DMA1))
	{}
 
	while (LL_SPI_IsActiveFlag_BSY(SPI2) && !LL_SPI_IsActiveFlag_RXNE(SPI2))
	{}
 
	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_4);
	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);
 
	LL_SPI_DisableDMAReq_RX(SPI2);
	LL_SPI_DisableDMAReq_TX(SPI2);
}
 
#define DMA_CALL_READ 	1
#define DMA_CALL_WRITE 	1
 
 
void ILI9341_Scroll_ScreenDMA(uint16_t	Lines)
{
	uint16_t	LineNo;
//	uint8_t		Dummy = LCD_NOP;
	uint16_t	NoBytes;
	static uint8_t		__attribute__((aligned(4))) LineBuff[(320*3)];	// write buffer
 
	ILI9341_ConfigDMA();
 
	ILISelect();
	ILI9341_Write_Command(LCD_PIXEL_FORMAT);
	ILI9341_Write_Data(PX_18_IF);
 
	NoBytes=LCD_WIDTH*3;
	for(LineNo=Lines;LineNo < LCD_HEIGHT ;LineNo++)
	{
		ILI9341_Set_AddressRW(0,LineNo,LCD_WIDTH,LineNo+1,0);
		ILI9341_Read_Data();	// skip dummy byte
		ILI9341_DMA_SendReceive(LineBuff,NoBytes+1,1);
 
		ILIDeSelect();
		ILISelect();
		ILI9341_Write_Command(LCD_NOP);
 
		ILI9341_Set_AddressRW(0,(LineNo-Lines),LCD_WIDTH,(LineNo-Lines)+1,1);
		ILISetData();
		ILI9341_DMA_SendReceive(LineBuff,NoBytes,0);
 
		ILIDeSelect();
		ILISelect();
		ILI9341_Write_Command(LCD_NOP);
	}
 
	ILI9341_Write_Command(LCD_PIXEL_FORMAT);
	ILI9341_Write_Data(PX_DEFAULT);
	ILIDeSelect();
}

Anyone have any idea why this is the case? That adding un-related code can cause this to work / not work seemingly at random?

Cheers.

Phill.

5 REPLIES 5

> stop working

Qualify.

Use debugger and logic analyzer on the SPI.

Does the code stuck somewhere? If so, where? Or code works just SPI stopped working? Or SPI works just display does not respond? Or display still responds to "normal" commands, just the DMA does not work?

JW

Phill Harvey-Smith
Associate II

Been using debugger on the nucleo board, it all seems to step through and the program continues to run as normal, the display still responds to further commands so it looks like to me that the SPI itself is still OK.

The odd thing is that sometimes, the scroll seems only to scroll the first pixel of each line as if the DMA was giving up early....though one pixel = 3 RGB bytes.

I did wonder if there was a timing / reflection issue on the SPI lines so put some 22R resistors inline to try and stop any reflections but this didn't seem to make a difference.

I've just done some tests with my scope hooked up to the SCK line, when it's working I'm getting a solid block of clocks when the DMA is running, and small bursts of data with gaps between them at other times when just accssing the SPI by programmed I/O. When it doesn't work it looks like the clock line is only going through a few cycles before giving up.

Doing some tests commenting out calls to various bits of code in my main loop I found that the DMA seemed to work as long as the size of the output elf file was less than about 30500 bytes.

Cheers.

Phill.

You don't observe the SPI registers using the debugger, while running, do you? (if you do, don't)

JW

Phill Harvey-Smith
Associate II

Ahhhh I think I've found (part of the) the problem, I wasn't clearing the transfer complete flag before re-enabling the DMA I'd sort of assumed that disabling then re-enabling the DMA channel would do this, it doesn't seem to. As my code isn't using interrupts but was just checking the flag in the DMA1->ISR, it of course dropped straight past the check as the bit was still set. The next thing it did was disabled the DMA, needless to say before it had transferred the data to the display :( Which is why it seemed to only scroll 1 pixel at the edge of the screen.

Well I have at least learned something about the DMA flags 🙂

Cheers.

Phill.

That's strange - in the code you posted, in ILI9341_Scroll_ScreenDMA() you call ILI9341_ConfigDMA() as the first thing, which in turn calls LL_DMA_DeInit() for both channels, before doing anything else. A glance into [STM32Cube_FW_F1_V1.6.0]\Drivers\STM32F1xx_HAL_Driver\Inc\stm32f1xx_ll_dma.c tells me that LL_DMA_DeInit() uses one of the channel-specific LL_DMA_ClearFlag_GI[channel] macro to clear the pending status bits... So how comes they were not cleared?

JW