STM32 SPI not operating - unable to determine error in code

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
Hello,

I’m hoping someone could help me with my code. I am trying to run SPI on a STM32F407VG discovery board without using cubemx and shifting registers mostly. When I look on an oscilloscope, the Slave/Chip select goes low and high correctly but I am writing data to SPI1 DR and I don't see anything on MOSI or SCLK. What have I done wrong? Using HAL libraries and auto generated code makes the SPI work fine. C code below:

C:
/* ENABLING AND CONFIGURING CLOCKS */
RCC->AHB1ENR |= (1<<0); //GPIO config
RCC->AHB1ENR |= (1<<4);
RCC->APB2ENR |= (1<<12); // SPI_1 Enabled
RCC->CR |= (1<<0); // Enable the 16 MHz HSI clock
RCC->PLLCFGR |= (0<<22); // Set HSI as source
RCC->PLLCFGR |= (1<<1) | (0<<0); // Adjust to use PLL

// Set the divider PLL_M to 16 to get 1 MHz since HSI is 16 MHz
RCC->PLLCFGR |= (0<<0) | (0<<1) | (0<<2) | (0<<3) | (1<<4) | (0<<5);

// SET PLL_N as 288
RCC->PLLCFGR |= (0<<6) | (0<<7) | (0<<8) | (0<<9) | (0<<10) | (1<<11) | (0<<12) | (0<<13) | (1<<14);

// Set PLL_P as 4 to get 72 MHz
RCC->PLLCFGR |= (1<<16) | (0<<17);

// Set APB2 divider to 2 to get 36 MHz
RCC->PLLCFGR |= (1<<15) | (0<<14) | (0<<13);

/*GPIO PINS*/
//Set PE5 as output for CS, push pull, no pull up/down
GPIOE->MODER |= (1<<10);
GPIOE->OTYPER |= (0<<5);
GPIOE->OSPEEDR |= (1<<10);
GPIOE->PUPDR |= (00<<10);
GPIOE->BSRR |= (1<<5);

// Set PA5 as SCLK using AF5
GPIOA->MODER |= (1<<11);
GPIOA->OTYPER |= (0<<5);
GPIOA->OSPEEDR |= (1<<10);
GPIOA->PUPDR |= (1<<10);
GPIOA->AFR[0] |= (1<<22) | (1<<20);

// Set PA6 as MISO using AF5
GPIOA->MODER |= (1<<13);
GPIOA->OTYPER |= (0<<6);
GPIOA->OSPEEDR |= (1<<13);
GPIOA->PUPDR |= (1<<12);
GPIOA->AFR[0] |= (1<<24) | (1<<26);

// Set PA7 as MOSI using AF5
GPIOA->MODER |= (1<<15);
GPIOA->OTYPER |= (0<<7);
GPIOA->OSPEEDR |= (1<<15);
GPIOA->PUPDR |= (1<<14);
GPIOA->AFR[0] |= (1<<28) | (1<<30);

/* Configure SPI */
/* SPI is set as
Master Mode
Direction 2 Lines
8-Bit data size
Clock polarity low
Clock phase 1 Edge
Baudrate prescalre of 32
Send the MSB first
CRC disabled
Software set NSS/CS
*/
SPI1->CR1 |= (0<<15) | (1<<14) | (0<<13) | (0<<11) | ...
(0<<10) | (1<<9) | (0<<7) | (1<<6) | (1<<5) | (0<<4) | ...
(0<<3) | (1<<2) | (0<<1) | (0<<0);
When running the code I set the CS pin to low and then write random data to the SPI1 DR like this:

GPIOE->BSRR |= (0<<5);
Delay by 10000
SPI1->DR |= (1<<7) | (1<<2);

Delay by 50
SPI1->DR |= (0<<7) | (0<<2);
Delay by 10000
GPIOE->BSRR |= (1<<5);


The delay is just a simple counter function. So in summary, I am configuring the HSI to be a source for PLL and then setting the division and multiplication factors such that the output frequency to APB/AHB is 72MHz. SPI1 is on APB2 so after dividing 72MHz by a prescaler of 2, I get 36 MHz as the clock frequency for SPI1 since I have read that SPI1 should be around 36 MHz. I have been through the code and registers again but I cannot find my error. Thank you for your help.
 

MrChips

Joined Oct 2, 2009
30,707
I can help you with this when I have some time to examine your code.

What toolset (IDE) are you running?
Why are you writing directly to HW registers? That is a sure way to miss something.
 

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
I can help you with this when I have some time to examine your code.

What toolset (IDE) are you running?
Why are you writing directly to HW registers? That is a sure way to miss something.
Thank you for your help, I am using Keil and using HW registers because I want to learn and the HAL hides away the basics. There is a lot of tedious code to read through so I tried to write more detailed reasons for what I have written in the code with page numbers on ref manual (link below) and I have found some mistakes. I will correct the code and post it back so no need to go through it right now. Thanks again.

https://www.st.com/content/ccc/reso...df/jcr:content/translations/en.DM00031020.pdf
 

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
It may be better to get it working first using CubeMX and HAL and then learn from what they do.
I did do that first, I actually have a auto generate code using CubeMX and HAL that work correctly but the problem is the HAL libraries hide everything under multiple layers of code. I tried to follow how to set the APB2 clock for the SPI and I kept going around in a loop in the code (meaning I started at the declaration of a clock variable and ended up with just a definition but couldn't find where it was used). e.g. the variable named something like VClockOutput for frequency shows the exact calculations shown in the reference manual but I couldn't find where it was further referenced and used or what was leading to the APB2 frequency using registers.
 

MrChips

Joined Oct 2, 2009
30,707
STM32 is unlike any simple MCU. There are just two many things you have to learn in order to write your own code.
Start off by using CubeMX and HAL or look at prewritten sample code. After you learn to navigate around the libraries while reading the STM32 User Manuals then you will have a chance at writing your own code. It is much harder starting out from scratch. I can lend a hand if you get stuck.
 

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
STM32 is unlike any simple MCU. There are just two many things you have to learn in order to write your own code.
Start off by using CubeMX and HAL or look at prewritten sample code. After you learn to navigate around the libraries while reading the STM32 User Manuals then you will have a chance at writing your own code. It is much harder starting out from scratch. I can lend a hand if you get stuck.
I will go through the working CubeMX/HAL code I already have again because that was based on a pre-written sample code. I appreciate your help.
 

MrChips

Joined Oct 2, 2009
30,707
While using library functions is the preferred technique for getting started I wish to comment on the efficacy of your code.
Take, for example, the following line:

// SET PLL_N as 288
RCC->PLLCFGR |= (0<<6) | (0<<7) | (0<<8) | (0<<9) | (0<<10) | (1<<11) | (0<<12) | (0<<13) | (1<<14);

1) Since you are performing a bitwise OR operation on the current value of the register, (0<<6) and all similar constructs have no effect. This will not set the targeted bit to 0.

2) (1<<14) is terrible code for maintenance. What is the significance of 14?
All configuration bits are assigned names. Look in stm32f4xx.h for the names.
Using constant literals (such as 14) is bound to break your code in the event that these are changed in future or across MCU devices.
 

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
While using library functions is the preferred technique for getting started I wish to comment on the efficacy of your code.
Take, for example, the following line:

// SET PLL_N as 288
RCC->PLLCFGR |= (0<<6) | (0<<7) | (0<<8) | (0<<9) | (0<<10) | (1<<11) | (0<<12) | (0<<13) | (1<<14);

1) Since you are performing a bitwise OR operation on the current value of the register, (0<<6) and all similar constructs have no effect. This will not set the targeted bit to 0.

2) (1<<14) is terrible code for maintenance. What is the significance of 14?
All configuration bits are assigned names. Look in stm32f4xx.h for the names.
Using constant literals (such as 14) is bound to break your code in the event that these are changed in future or across MCU devices.
For 1, I usually leave those bits as not set but here I wanted to show or remember exactly what I did. In the reference manual the multiplier examples are given numerically and binary (9 digits), so this was meant to say 000001001. I guess I could just leave it as (1<<11) | (1<<14) ?

For 2, I don't understand what you mean, the number 14 is to shift the '1' by 15 so that 14th bit is set to 1. By assigned names do you mean e.g. GPIO_MODER_MODER2_0 to set the GPIO mode?

Code:
/* ENABLING AND CONFIGURING CLOCKS */
//GPIO config -- Page 180/1749
//bit 0 is for enabling GPIO-A and bit 4 is for
//enabling GPIO - E
RCC->AHB1ENR |= (1<<0);
RCC->AHB1ENR |= (1<<4);
// SPI enable - Page 187/1749
// bit 12 is for enabling SPI1
RCC->APB2ENR |= (1<<12); // SPI_1 Enabled
//Page 161/1749
//Bit 0 on RCC_CR register is to enable HSI which has a default
//clock of 16 MHz
RCC->CR |= (1<<0); // Enable the 16 MHz HSI clock
// Page 163/1749
// Setting bit 22 in RCC_PLLCFGR sets HSI as the clock source
RCC->PLLCFGR |= (0<<22); // Set HSI as source
//Page 165/1749
//bit 0,1 are SW0 and SW1. To use PLL, you have to set SW bits to 10
//This switch is illustrated on page 152/1749 fig 16. clock tree and here at the bottom
RCC->CFGR    |= (1<<1) | (0<<0);; // Adjust to use PLL
//Page 163/1749
// Set the divider PLL_M to 16 to get 1 MHz since HSI is 16 MHz
RCC->PLLCFGR |= (0<<0) | (0<<1) | (0<<2) | (0<<3) | (1<<4) | (0<<5);
// SET PLL_N as 288
// 288 should equal to 000001001 in binary
RCC->PLLCFGR |= (0<<6) | (0<<7) | (0<<8) | (0<<9) | (0<<10) | (1<<11) | (0<<12) | (0<<13) | (1<<14);
// Set PLL_P as 4 to get 72 MHz
RCC->PLLCFGR |= (1<<16) | (0<<17);
//Page 165/1749
// APB2 prescaler is referred to as PPRE2
// Setting this to 100 sets APB2 divider to 2
// Set APB2 divider to 2 to get 36 MHz
RCC->CFGR |= (1<<15) | (0<<14) | (0<<13);

/* Configure SPI */
/* SPI is set as
Master Mode
Direction 2 Lines
8-Bit data size
Clock polarity low
Clock phase 1 Edge
Baudrate prescalre of 32
Send the MSB first
CRC disabled
Software set NSS/CS
*/
SPI1->CR1 |= (0<<15) | (1<<14) | (0<<13) | (0<<11) | ...
(0<<10) | (1<<9) | (0<<7) | (1<<6) | (1<<5) | (0<<4) | ...
(0<<3) | (1<<2) | (0<<1) | (0<<0);
The rest of the code is just for setting the pins so I have not really added notes to them:
Code:
/*GPIO PINS*/
//Set PE5 as output for CS, push pull, no pull up/down
GPIOE->MODER |= (1<<10);
GPIOE->OTYPER |= (0<<5);
GPIOE->OSPEEDR |= (1<<10);
GPIOE->PUPDR |= (00<<10);
GPIOE->BSRR |= (1<<5);

// Set PA5 as SCLK using AF5
GPIOA->MODER |= (1<<11);
GPIOA->OTYPER |= (0<<5);
GPIOA->OSPEEDR |= (1<<10);
GPIOA->PUPDR |= (1<<10);
GPIOA->AFR[0] |= (1<<22) | (1<<20);

// Set PA6 as MISO using AF5
GPIOA->MODER |= (1<<13);
GPIOA->OTYPER |= (0<<6);
GPIOA->OSPEEDR |= (1<<13);
GPIOA->PUPDR |= (1<<12);
GPIOA->AFR[0] |= (1<<24) | (1<<26);

// Set PA7 as MOSI using AF5
GPIOA->MODER |= (1<<15);
GPIOA->OTYPER |= (0<<7);
GPIOA->OSPEEDR |= (1<<15);
GPIOA->PUPDR |= (1<<14);
GPIOA->AFR[0] |= (1<<28) | (1<<30);
I am also looking through the example I have working already and later I will post the code again with the 'assigned names' that you have mentioned if I have understood that bit correctly. Until then if you find any issues with the code that is stopping SPI from functioning please let me know. Thank you!
 

Attachments

Thread Starter

BeginningToShift

Joined Mar 3, 2020
7
Code:
/* ENABLING AND CONFIGURING CLOCKS */
//GPIO config -- Page 180/1749
//bit 0 is for enabling GPIO-A and bit 4 is for
//enabling GPIO - E
RCC->AHB1ENR = RCC_AHB1ENR_GPIOAEN;
RCC->AHB1ENR = RCC_AHB1ENR_GPIOEEN;
// SPI enable - Page 187/1749
// bit 12 is for enabling SPI1
RCC->APB2ENR = RCC_APB2ENR_SPI1EN;
//Page 161/1749
//Bit 0 on RCC_CR register is to enable HSI which has a default
//clock of 16 MHz
RCC->CR = RCC_CR_HSION;
// Page 163/1749
// Setting bit 22 in RCC_PLLCFGR sets HSI as the clock source
RCC->PLLCFGR = RCC_PLLCFGR_PLLSRC_HSI;
//Page 165/1749
//bit 0,1 are SW0 and SW1. To use PLL, you have to set SW bits to 10
//This switch is illustrated on page 152/1749 fig 16. clock tree and here at the bottom
RCC->CFGR    = RCC_CFGR_SW_PLL;
//Page 163/1749
// Set the divider PLL_M to 16 to get 1 MHz since HSI is 16 MHz
RCC->PLLCFGR = 000010;
// SET PLL_N as 288
// 288 should equal to 000001001 in binary
RCC->PLLCFGR = 000001001;
// Set PLL_P as 4 to get 72 MHz
RCC->PLLCFGR |= (1<<16) | (0<<17);
//Page 165/1749
// APB2 prescaler is referred to as PPRE2
// Setting this to 100 sets APB2 divider to 2
// Set APB2 divider to 2 to get 36 MHz
RCC->CFGR = RCC_CFGR_PPRE2_2;
I have not changed the settings of SPI CR1 to the assigned names yet as I have to find what they are but the above changes do not make a difference to whether SPI works or not. I have also left the multipliers as they were because there are no specific names for 288 and 16 for PLL_N and PLL_M respectively. Apologies if this is getting confusing, let me know if you want me to do anything to make it clear please.
 

MrChips

Joined Oct 2, 2009
30,707
There are too many errors in your code.

As I pointed out before,

GPIOE->OTYPER |= (0<<5);

does absolutely nothing.

//enabling GPIO - E
RCC->AHB1ENR = RCC_AHB1ENR_GPIOAEN;
RCC->AHB1ENR = RCC_AHB1ENR_GPIOEEN;

The second line wipes out the effect of the previous line.

To initialize RCC this is the code that is generated by STM32CubeIDE:

C:
/** Initializes the CPU, AHB and APB buses clocks */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 400;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

Here is the code to initilize PB6 and PB7 as GPIO outputs:
C:
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOB_CLK_ENABLE();

  GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
Why are you making life difficult for yourself?
This is my final post on this thread if you continue to do what you are doing.

Use STM32CubeIDE. It is free.
 
Top