Help with SPI on STM32 please - Page 1
Forum section: electronics / general

author: shakedown
author_rank: Contributor
timestamp: August 03, 2025, 03:42:04 pm
content: Hi I am trying to write my own library for using SPI and SPI/DMA on an STM32F411 MCU. I want this F411 to work with a TFT screen and the HAL library is extremely slow. I am using STM32F103 as an SPI slave to test the library is working. I can see using a cheap logic analyzer that the F411 is correctly doing what I am asking i.e. send three dummy bytes {0xFF, 0xFF, 0xFF} to initialise the clock and get three bytes back from the slave F103. The response back from slave should be {0xAA, 0xCC, 0x0F} but instead I am getting {0xC3, 0xC3, 0xC3} continuously. However if I change the dummy bytes to something random like {0xDE, 0xAB, 0xAA} then I start receiving {0xAA, 0xCC, 0x0F} but after sending the same three dummy bytes four times as you can see from second image (response_from_f103.JPG).The slave F103 is using a HAL SPI library (No DMA or IRQ) to make the test easier.The F411 has SPI2 on its APB1 and it is running at 12.5MHz. The F103 has SPI1 on APB2 and it is running at 8MHz. At first I was using 1MHz for F103 APB2 clock and getting it to 8MHz has made things a little better. That is to say more consistent results as seen in this post but they are still sort of random and do not make sense to me. I tried to disable interrupts thinking that is somehow causing a problem but that makes no difference. I do not want to use DMA on the slave F103 yet as I don't know what mode the devices I am going to control using the F411 will use so I need it to work for every scenario. E.g. I don't know if MCP2515 CAN module or the TFT screen uses DMA or polling or IRQ SPI, same with a SPI based LoRa module.Could you help give me any ideas what to do in order to find a fix? Thank you.Image ReferencePURPLE = MASTER SPI (MOSI) - F411GREEN = SLAVE SPI (MISO) - F103

author: Dan N
author_rank: Contributor
timestamp: August 03, 2025, 05:30:15 pm
content: You don't show or describe the slave select signal. The slave won't work without one. If it's always enabled (tied to a constant value) then you have to worry about spurious clock pulses during master power up/down clocking in a few garbage bits and then your slave will forever be out of sync with the master.Your logic analyzer needs to be set to a faster sample rate (8-16x of spi clock) so the clock pulses all look the same and you can then tell where the real rising/falling edge is.Finally it's pretty much impossible to help without seeing your code.

author: shakedown
author_rank: Contributor
timestamp: August 03, 2025, 06:30:34 pm
content: The slave select signal is manual using pin PA0 on F411 as output and pin PA3 on F103 (slave) as input.I have increased the logic analyzer acquisition frequency to 24MHz (double F411 APB clock) and 1T samples. The result is attached below. The results are even more confusing for me now as they are all 0xFF. 48MHz does not capture anything. I did remove the HAL_Delay(2000) as this was just for experimentation, there isnt actual need for it. Should I lower both the SPI clocks for each MCU and then run the logic analyzer so that it is 8-16 times SPI clock frequency?Apologies, I did not include the code because I thought this was more of a timing problem somehow and the code was ok especially for F103 since its just using HAL library.Here is the code for library SPI2 initialise and transmitReceive:Code:void SPI2_DMA_init({// Enable clock for GPIOBRCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;// Set PB13, PB14 and PB15 to alternate function modeGPIOB->MODER |= GPIO_MODER_MODE13_1|GPIO_MODER_MODE14_1|GPIO_MODER_MODE15_1;// Set which type of alternate function isGPIOB->AFR |= (0x05<<20)|(0x05<<24)|(0x05<<28);// Enable clock access to SPI2RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;// Set software slave managementSPI2->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;// Set SPI in master modeSPI2->CR1 |= SPI_CR1_MSTR;// Enable SPI peripheralSPI2->CR1 |= SPI_CR1_SPE;/* Init DMA 1 stream 4 for SPI 2 DMA TX */DMA1_STREAM4_init();/* Init DMA 1 stream 3 for SPI 2 DMA RX */DMA1_STREAM3_init();}void SPI2_transmitReceive_DMA(uint8_t *dummy_data, uint8_t *data, uint8_t len){clear_DMA1_Stream3_LowInterrupt_FCR_Flags();clear_DMA1_Stream4_HiInterrupt_FCR_Flags();DMA1_Stream3->PAR = (uint32_t)&SPI2->DR;DMA1_Stream3->M0AR = (uint32_t)data;DMA1_Stream3->NDTR = len;DMA1_Stream3->CR |= DMA_SxCR_EN;DMA1_Stream4->PAR = (uint32_t)&SPI2->DR;DMA1_Stream4->M0AR = (uint32_t)dummy_data;DMA1_Stream4->NDTR = len;/* Enable stream 4 now to send data */DMA1_Stream4->CR |= DMA_SxCR_EN;/* Wait for DMA to finish receiving */while (u8_getRxFinished(nSPI2) == 0) {;}rstRxFinished(nSPI2);}The master is using the above code like this in main.cCode:SPI2_DMA_init();GPIOA->MODER |= GPIO_MODER_MODER0_0;/* Set CS pin (PA0) to high */GPIOA->BSRR = GPIO_BSRR_BS0;while (1){/* I am using a black pill board and PC13 is onboard LED */HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);HAL_Delay(1000);/* Set CS pin (PA0) to low */GPIOA->BSRR = GPIO_BSRR_BR0;uint8_t u8_dummyData = {0xDE, 0xAB, 0xAA};SPI2_transmitReceive_DMA(u8_dummyData, u8_rcvBuff, 3);/* Set CS pin (PA0) to high again to end transmission this time */GPIOA->BSRR = GPIO_BSRR_BS0;/* Check to see if we are receiving at least one of the expected bytes? */if (u8_rcvBuff == 0xAA){HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);HAL_Delay(2000);HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);HAL_Delay(2000);}}The slave F103 has the following code:Code:while (1){/* Is the PA0 pin on F411 set to low? If it is then transmit data back */if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) == GPIO_PIN_RESET){HAL_SPI_Transmit(&hspi1, dataBuff, 3, HAL_MAX_DELAY);}}Below is also a picture of when debugging F411 and monitoring u8_rcvBuff. The values of u8_rcvBuff here are usually identical to what is seen on the logic analyzer.

author: gpr
author_rank: Contributor
timestamp: August 04, 2025, 01:43:50 pm
content: A few things come to mind.If you have an oscilloscope, check signal integrity first. If you don't, use slower spi speed so logic analyzer can oversample.Pay attention to spi polarity and phase (spi mode) on both ends, you might want to explicitly set it to desired values to not rely on defaults, verify it with scope/LA.Use GPIO pull settings for spi clock/data; if nothing works and remote pin is in high-z, you will get zeros/FFs consistently instead of random garbage.

author: Dan N
author_rank: Contributor
timestamp: August 04, 2025, 03:11:55 pm
content: I would leave all the slave clocks at default settings. You want the slave spi controller internal clock running as fast as possible or it won't be able to sample edges properly. The data transfer rate is controlled by the master clock which you should set to 1MHz until you get everything working.

author: peter-h
author_rank: Super Contributor
timestamp: August 05, 2025, 07:14:13 am
content: You need a scope. Check the tx clock, data and /CS signals. Also check the spec for the display controller max clock frequency. Google for code samples for initialising your actual controller (Claude is quite good at digging up code samples).

author: shakedown
author_rank: Contributor
timestamp: August 05, 2025, 06:47:01 pm
content: I think raising the clock of slave higher than master might have solved the issue. I have not had a chance to go through it multiple times but on first run it was behaving like it should. Thank you all.