cancel
Showing results for 
Search instead for 
Did you mean: 

SPI with Registers: Issue in Data transferring

ahmadserhal
Associate II

Hello,

I want to transfer data between NVIDIA Jetson Nano (Master) and STM32 (Slave) using SPI. On the master side, I am using spidev python library. On the slave side, I am coding using registers (without using HAL).

I want to send and receive data continuously between the two systems. When I run the codes, the data are correct for the first few cycles. However, after some cycles, the data are right-shifted by one bit. It happens randomly after some cycles in a circular mode (after some shifting, the data will be correct for some cycles and then the shifting continue again and so on). What could be the issue that is causing this shifting? I want to make sure SPI is always transferring correct data continuously. Is it possible that there are some buffers that need to be cleared after each cycle?

I am using SPI with interrupts. Here is my SPI configuration (spi_enable and disable just change the SPE bit):

 

void spi_config()
{
	// 1. Enable SPI clock
	RCC->APB2ENR &= ~(0b1 << 13);
	RCC->APB2ENR |= (0b1 << 13); // Enable SPI4 clock
	// 2. Configure the control register 1
	spi_disable();
	SPI4->CR1 &= ~(0b111 << 0);
	SPI4->CR1 |= (0b0 << 0); // CPHA to 0: leading edge
	SPI4->CR1 |= (0b0 << 1); // CPOL to 0: CLK is 0 when idle
	SPI4->CR1 |= (0b0 << 2); // SPI as slave
	SPI4->CR1 &= ~(0b1 << 7);
	SPI4->CR1 |= (0b0 << 7); // MSB first
	SPI4->CR1 &= ~(0b111 << 8);
	SPI4->CR1 |= (0b01 << 8); // NSS stuff (SSM and SSI)
	SPI4->CR1 |= (0b0 << 10); // Full-duplex (transmit and receive)
	// 3. Configure the control register 2
	SPI4->CR2 &= ~(0b1 << 2); // NSS stuff
	SPI4->CR2 |= (0b0 << 2);
	SPI4->CR2 &= ~(0b111 << 3);
	SPI4->CR2 |= (0b101 << 3); // baud rate division by 64
	SPI4->CR2 &= ~(0b1111 << 8);
	SPI4->CR2 |= (0b0111 << 8); // data size is 8 bits

	SPI4->CR2 &= ~(0b1 << 6);
	SPI4->CR2 |= (0b1 << 6); // Enable RXNEIE
	SPI4->CR2 &= ~(0b1 << 12);
	SPI4->CR2 |= (0b1 << 12); // Set FRXTH to 8 bits
 
	NVIC_SetPriority(SPI4_IRQn, 0);
	NVIC_EnableIRQ(SPI4_IRQn); // enable SPI4 interrupt in NVIC

	spi_enable();
}

 

 My interrupt callback function is as follows:

 

void SPI4_IRQHandler()
{
	if (SPI4->SR & (0b1 << 0)) { // if Rx is not empty
		SPI4->DR = 0;
		data_rx[rx_buffer_head] = SPI4->DR;
		data[rx_buffer_head] = data_rx[rx_buffer_head];
		data_rx[rx_buffer_head] = 0;
		if(rx_buffer_head == (BUFFER_SIZE - 1)) {
			counter++;
		}
		rx_buffer_head = (rx_buffer_head + 1) % BUFFER_SIZE;
		SPI4->SR &= ~(0b1 << 0);
	}
	// Check for errors
	if (SPI4->SR & (SPI_SR_OVR | SPI_SR_MODF | SPI_SR_CRCERR)) {
		// Handle errors
		SPI4->SR &= ~(SPI_SR_OVR | SPI_SR_MODF | SPI_SR_CRCERR); // Clear error flags
	}
}

 

Note that for testing purposes, I am sending 4 bytes at a time from Jetson Nano.

Also, I monitored the 4 lines of SPI using an oscilloscope. Data are correct, so I am assuming the problem is not on the master side.

If needed, here is my GPIO configuration:

void gpio_init()
{
	// Enable the GPIO clock
	RCC->AHB1ENR &= ~(0b1 << 4);
	RCC->AHB1ENR |= (0b1 << 4); // GPIOE
	// Pins Mode
	GPIOE->MODER &= ~(0b11 << 4); // Reset the SPI4 SCK pin
	GPIOE->MODER |= (0b10 << 4); // SPI4 SCK Alternate function mode
	GPIOE->MODER &= ~(0b11 << 10); // Reset the SPI4 MISO pin
	GPIOE->MODER |= (0b10 << 10); // SPI4 MISO Alternate function mode
	GPIOE->MODER &= ~(0b11 << 12); // Reset the SPI4 MOSI pin
	GPIOE->MODER |= (0b10 << 12); // SPI4 MOSI Alternate function mode
	GPIOE->MODER &= ~(0b11 << 8); // Reset the SPI4 CS pin
	GPIOE->MODER |= (0b00 << 8); // SPI4 CS input mode
	// Set the Pins Speed to very high speed
	GPIOE->OSPEEDR &= ~(0b11 << 4);
	GPIOE->OSPEEDR |= (0b11 << 4);
	GPIOE->OSPEEDR &= ~(0b11 << 10);
	GPIOE->OSPEEDR |= (0b11 << 10);
	GPIOE->OSPEEDR &= ~(0b11 << 12);
	GPIOE->OSPEEDR |= (0b11 << 12);
	GPIOE->OSPEEDR &= ~(0b11 << 8);
	GPIOE->OSPEEDR |= (0b11 << 8);
	// Set the alternate functions of the pins to SPI
	GPIOE->AFR[0] &= ~(0b0101 << 8);
	GPIOE->AFR[0] |= (0b0101 << 8);
	GPIOE->AFR[0] &= ~(0b0101 << 20);
	GPIOE->AFR[0] |= (0b0101 << 20);
	GPIOE->AFR[0] &= ~(0b0101 << 24);
	GPIOE->AFR[0] |= (0b0101 << 24);
}
6 REPLIES 6
gbm
Lead III

There are many errors in your code to point out only a few of them (there are many more):

1. use bit names from standard headers instead of magic values.

2. use assignments, not bitwise operations like &= and |=, to set SPI control registers.

3. write SPI CR2 register first, then CR1. Enable the SPI operation in CR1 (it is disabled in your code).

4. Use byte access to SPI->DR!!!

5. Do not clear RXNE flag - it is cleared automatically by reading DR.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
ahmadserhal
Associate II

Thank you for your response! 

1. Sure I am fixing the code to use standard headers from CMSIS.

2. Sometimes I want to change specific bits and keep the others untouched. If I were to use assignments, the other bits might be changed. Am I correct?

3. I tried but still didn't work. Also, in the provided code, I have a function (spi_enable()) that is enabling the SPE bit in the last line of the function.

4. What do you mean? Can you please provide an example?

5. I know that it is cleared after reading DR, but I just put it to make sure it is low. I will remove it.

Also, I think the problem might be in the clock configuration. Here is mine:

void SystemClock_Config()
{
	#define PLL_M 4
	#define PLL_N 216
	// 1. Enable HSE and wait for the HSE to become ready
	RCC->CR |= RCC_CR_HSEON;
	while (!(RCC->CR & RCC_CR_HSERDY));
	// 2. Set the POWER ENABLE CLOCK and VOLTAGE REGULATOR
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	PWR->CR1 |= PWR_CR1_VOS;
	// 3. Configure the FLASH PREFETCH and the LATENCY related settings
	FLASH->ACR |= FLASH_ACR_PRFTEN; // Prefetch enable
	FLASH->ACR &= ~(0xfUL << (0U));
	FLASH->ACR |= FLASH_ACR_LATENCY_7WS; // set the latency to 7WS
	// 4. Configure the PRESCALARS HCLK, PCLK1, PCLK2
	RCC->CFGR &= ~RCC_CFGR_HPRE; // AHB Prescaler 2
	RCC->CFGR &= ~(0x7UL << (10U));
	RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1 Prescaler 4
	RCC->CFGR &= ~(0x7UL << (13U));
	RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2 Prescaler 2
	// 5. Configure the MAIN PLL
	RCC->PLLCFGR = (PLL_M << (0U)) | (PLL_N << (6U));
	RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLP;
	RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC;
	// 6. Enable the PLL and wait for it to become ready
	RCC->CR |= RCC_CR_PLLON;
	while (!(RCC->CR & RCC_CR_PLLRDY));
	// 7. Select the Clock Source and wait for it to be set
	RCC->CFGR &= ~RCC_CFGR_SW_0;
	RCC->CFGR |= RCC_CFGR_SW_1;
	RCC_CFGR_SW_PLL;
	while (!(RCC->CFGR & RCC_CFGR_SWS_PLL));
}

My updated gpio and spi configuration functions are:

void gpio_init()
{
	// Enable the GPIO clock
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; // GPIOE
	// Pins Mode
	GPIOE->MODER &= ~(0x3UL << (4U)); // Reset the SPI4 SCK pin
	GPIOE->MODER |= GPIO_MODER_MODER2_1; // SPI4 SCK Alternate function mode
	GPIOE->MODER &= ~(0x3UL << (10U)); // Reset the SPI4 MISO pin
	GPIOE->MODER |= GPIO_MODER_MODER5_1; // SPI4 MISO Alternate function mode
	GPIOE->MODER &= ~(0x3UL << (12U)); // Reset the SPI4 MOSI pin
	GPIOE->MODER |= GPIO_MODER_MODER6_1; // SPI4 MOSI Alternate function mode
	GPIOE->MODER &= ~GPIO_MODER_MODER4; // SPI4 CS input mode
	// Set the Pins Speed to very high speed
	GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR2;
	GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;
	GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;
	GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;
	// Set the alternate functions of the pins to SPI
	GPIOE->AFR[0] &= ~(0xfUL << (8U));
	GPIOE->AFR[0] |= (0x5UL << (8U));
	GPIOE->AFR[0] &= ~(0xfUL << (20U));
	GPIOE->AFR[0] |= (0x5UL << (20U));
	GPIOE->AFR[0] &= ~(0xfUL << (24U));
	GPIOE->AFR[0] |= (0x5UL << (24U));
}
void spi_config()
{
	// 1. Enable SPI clock
	RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; // Enable SPI4 clock
	// 2. Configure the control register 2
	SPI4->CR1 &= ~SPI_CR1_SPE;
	SPI4->CR2 &= ~SPI_CR2_SSOE;
	SPI4->CR2 &= ~(0xFUL << (8U));
	SPI4->CR2 |= (0x7UL << (8U)); // data size is 8 bits
	SPI4->CR2 |= SPI_CR2_RXNEIE; // Enable RXNEIE which is the interrupt when RXNE flag is set
	SPI4->CR2 |= SPI_CR2_FRXTH; // FRXTH is set so that the interrupt is triggered at 8 bits
	NVIC_SetPriority(SPI4_IRQn, 0);
	NVIC_EnableIRQ(SPI4_IRQn); // enable SPI4 interrupt in NVIC
	// 3. Configure the control register 1
	SPI4->CR1 &= ~SPI_CR1_CPHA; // CPHA to 0: leading edge
	SPI4->CR1 &= ~SPI_CR1_CPOL; // CPOL to 0: CLK is 0 when idle
	SPI4->CR1 &= ~SPI_CR1_MSTR; // SPI as slave
	SPI4->CR1 &= ~SPI_CR1_LSBFIRST; // MSB first
	SPI4->CR1 |= SPI_CR1_SSI;
	SPI4->CR1 &= ~SPI_CR1_SSM;
	SPI4->CR1 &= ~SPI_CR1_RXONLY; // Full-duplex (transmit and receive)
	SPI4->CR1 &= ~(0x7UL << (3U));
	SPI4->CR1 |= (0x5UL << (3U)); // baud rate division by 64
	SPI4->CR1 |= SPI_CR1_SPE;
}

Note that in the gpio_init() function, I am setting the pins to very high speed.


2. Sometimes I want to change specific bits and keep the others untouched. If I were to use assignments, the other bits might be changed. Am I correct?

 

No, you are not. While this "sometimes" may make some sense for setting individual GPIO pins, for other peripherals it sounds like "I only want to control some features of that interface, the other features should be random". NO, you want to set the interface module to the mode you want to use. So simply write:

 

 

 

SPI4->CR1 = SPI_CR1_SSI | (0x5UL << (3U)) | SPI_CR1_SPE;

 

 

 

BTW, use SPI_CR1_xxx_Pos symbol for bit position.

 

 

4. What do you mean? Can you please provide an example?

5. I know that it is cleared after reading DR, but I just put it to make sure it is low. I will remove it.

#define SPI4_DRB (*(volatile uint8_t *)&SPI4->DR)

Then use SPI4_DRB instead of SPI4->DR.

You cannot reset the RXNE flag by writing to SR. It's read-only.

Oh, and implementing SPI slave is not quite easy. The transfer is controlled by the master, the slave must write data in advance before the transfer starts. In practice, you may reasonable set the value of the third byte being transmitted, because you must write the second byte transmitted before reading the first byte received.

 

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

Code issues aside, a "shifted data" problem can be the result of a signal integrity issue with the SPI clock (edge problem, reflection, etc.).  

I didn't quite understand the last point you mentioned. Actually, in my interrupt callback function, I am writing data to DR before reading it like bellow (I am not sure if I am writing to DR correctly):

 

void SPI4_IRQHandler()
{
	if (SPI4->SR & SPI_SR_RXNE) { // if Rx is not empty
		while ((SPI4->SR & SPI_SR_BSY));
		data_rx[rx_buffer_head] = SPI4_DRB;
		data[rx_buffer_head] = data_rx[rx_buffer_head];
		SPI4_DR = (uint32_t)0;
		data_rx[rx_buffer_head] = 0;
		if(rx_buffer_head == (BUFFER_SIZE - 1)) {
			counter++;
		}
		rx_buffer_head = (rx_buffer_head + 1) % BUFFER_SIZE;
	}
	// Check for errors
	if (SPI4->SR & (SPI_SR_OVR | SPI_SR_MODF | SPI_SR_CRCERR)) {
		// Handle errors
		SPI4->SR &= ~(SPI_SR_OVR | SPI_SR_MODF | SPI_SR_CRCERR); // Clear error flags
	}
}

 

Also, I was checking SR and DR in the memory while transmitting data. The thing is when the data in DR is shifted, the bits BSY and OVR in SR are set. I thought I am resetting these flags when they are set in the function above, but you are right, SR is read-only so I need to clear these flags in another way. I am considering using an error interrupt with higher priority, and when the OVR flag is set, I will read DR until FRLVL is 00. Also, I will read SR to reset it. Does it make sense? (Note that here I am assuming that clearing OVR and BSY might solve the problem but not sure)

 

Also, can you elaborate more on the last point you mentioned regarding setting the third byte being transmitted...

Thank you,


 

Thank you David,

Yes, such problem can be due to signal integrity. But I also think I might have not configured SPI correctly because I am new to working with registers.