2025-01-14 07:49 AM
Hi,
I am developing and exploring a register programming based SPI driver. I took my time to read the datasheet and analyze the SPI startup sequence. For testing, I am using pre-programmed SST25VF080B memory IC.
I wrote a simple and low speed (less than 1 Mbps) SPI driver using registers. It did not look too complicated for me, I verified my setup function with a debugger (not 100% sure, if the setup code itself is correct, but I know for sure, that every line in the SPI_Setup function gets executed correct.) Problem arises when I enter the SPI_Send function - attempt to write data into SPI4->DR register for transmission fails, the register isnt acquiring the data... Logic analyzer even shows that the clock is not generated either. It may be a simple mistake or mistype somewhere, but I can not get it any further on my own, its my first time dealing with STM32 registers in SPI... Any help is much appreciated.
Some notes: I double checked the wiring and hardware, if I use the SPI pins as regular GPIOs, they work just fine, so hardware problems are very unlikely. I use the CS pin with hardware slave management - the exact SPI4_NSS pin is used and it is automatically pulled down as soon as SPI peripheral is enabled. Logic analyzer shows the CS pin toggling, so I have at least one step into it... If I missed some important project details, please let me know.
#include "main.h"
void Core_Clock_Setup (void){
RCC->CR |= RCC_CR_HSEON; //Set the clock source to external crystal/resonator (HSE)
while (!(RCC->CR & RCC_CR_HSEON)); //Wait until clock gets stable
RCC->APB1ENR |= RCC_APB1ENR_PWREN; //Enable power interface clock
PWR->CR1 &= ~(1U << 14);
PWR->CR1 &= ~(1U << 15); //Set internal voltage regulator to is reset value (scale 1)
FLASH->ACR &= ~FLASH_ACR_ARTEN; //Disable ART accelerator
FLASH->ACR &= ~FLASH_ACR_ARTRST; //Reset ART accelerator
FLASH->ACR |= FLASH_ACR_PRFTEN; //Enable prefetch
FLASH->ACR |= FLASH_ACR_LATENCY_6WS; //Set 7 CPU clock cycle flash memory access time (in order to get 200 MHz core clock)
//@ 25 MHz crystal, 200 MHz core clock configuration down below
RCC->CFGR &= ~(((1 << (7 - 4 + 1)) - 1) << 4);
RCC->CFGR |= (0 << 4); //Core clock division by 1 (core clock is not devided)
RCC->CFGR |= (0b101 << 10); //APB1 Low speed prescaler of 4 (50 MHz, max is 54 Mhz)
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //APB2 High speed prescaler of 2 (100 MHz, max is 108 Mhz)
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE;//HSE is set to be PLL entry
RCC->PLLCFGR |= RCC_PLLCFGR_PLLP_0; //PLLP Setting corresponding PLL prescalers (division by 2)
RCC->PLLCFGR &= ~((1 << 6) - 1);
RCC->PLLCFGR |= (16 & ((1 << 6) - 1)); //PLLM Setting corresponding PLL prescalers (division by 16)
RCC->PLLCFGR &= ~(((1 << (14 - 6 + 1)) - 1) << 6);
RCC->PLLCFGR |= (256 << 6); //PLLN Setting corresponding PLL prescalers ( multiplication by 256)
RCC->CR |= RCC_CR_PLLON; //Enable PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); //Wait until PLL gets stable
RCC->CFGR |= RCC_CFGR_SW_PLL; //PLL is set to be core clock
//RCC->CFGR |= RCC_CFGR_SW_HSE; //HSE is set to be core clock
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait until PLL indeed becomes core clock source
}
void Timer_Setup(void){
//RCC->DCKCFGR1 &= ~(1 << 24); //TIMxCLK = 2xPCLKx
RCC->DCKCFGR1 |= (1 << 24); //TIMxCLK = 4xPCLKx
RCC->APB1ENR |= (1 << 4); //Enable Timer 6 clock
TIM6->PSC = 99; //APB1 is 50 Mhz and 100 MHZ for timer (The number is set: Clock in MHz - 1)
TIM6->ARR = 0xFFFF; //Auto reload max value of 16 bits (0xFFFF)
TIM6->CR1 |= (1 << 0); //Enable Timer 6 counter
while(!(TIM6->SR & (1<<0))); //Wait until timer update bit is set
}
void delay_ms (uint16_t ms){
for(uint16_t i = 0; i<ms; i++)
{
TIM6->CNT = 0; //Reset counter
while (TIM6->CNT < 1000); //Wait until counter reaches desired value
}
}
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void GPIO_Setup(void){
RCC->AHB1ENR |= (1 << 2); //Enable clock for GPIO bank C
RCC->AHB1ENR |= (1 << 4); //Enable clock for GPIO bank E
delay_ms(1);
//PC14 OUTPUT
GPIOC->MODER |= (0b01 << 28); //PC14 General purpose output mode
GPIOC->OTYPER &= ~ (1 << 14); //PC14 Output push-pull (reset state)
GPIOC->OSPEEDR |= (0b11 << 28); //PC14 very high GPIO speed
GPIOC->PUPDR |= (0b10 << 28); //PC14 pull down resistors
//PC15 OUTPUT
GPIOC->MODER |= (0b01 << 30); //PC15 General purpose output mode
GPIOC->OTYPER &= ~ (1 << 15); //PC15 Output push-pull (reset state)
GPIOC->OSPEEDR |= (0b11 << 30); //PC15 very high GPIO speed
GPIOC->PUPDR |= (0b10 << 30); //PC15 pull down resistors
/*
//PE4 OUTPUT
GPIOE->MODER |= (0b01 << 8); //PE4 General purpose output mode
GPIOE->OTYPER &= ~ (1 << 4); //PE4 Output push-pull (reset state)
GPIOE->OSPEEDR |= (0b11 << 8); //PE4 very high GPIO speed
GPIOE->PUPDR |= (0b10 << 8); //PE4 pull down resistors
//PE0 OUTPUT
GPIOE->MODER |= (0b01 << 0); //PE0 General purpose output mode
GPIOE->OTYPER &= ~ (1 << 0); //PE0 Output push-pull (reset state)
GPIOE->OSPEEDR |= (0b11 << 0); //PE0 very high GPIO speed
GPIOE->PUPDR |= (0b10 << 0); //PE0 pull down resistors
*/
//-----------------------------------------------------------------------------------
//PE5 INPUT ALL BUTTONS EXTERNALLY PULLED UP
GPIOE->MODER |= (0b00 << 0); //PE0 General purpose input mode
//PE6 INPUT
//GPIOE->MODER |= (0b00 << 12); //PE6 General purpose input mode
}
void SPI_Setup(void){
/*
SCK -> PE2
CS -> PE4
MISO -> PE5
MOSI -> PE6
*/
RCC->APB2ENR |= (1 << 13); //Enable SPI4 Clock
//GPIOE clock has been enabled earlier
//RCC->AHB1ENR |= (1 << 4); //Enable clock for GPIO bank E
GPIOE->AFR[0] |= (0b0101 << 8); //Select alternate function as SPI SCK on PE2
GPIOE->AFR[0] |= (0b0101 << 16); //Select alternate function as SPI CS on PE4
GPIOE->AFR[0] |= (0b0101 << 20); //Select alternate function as SPI MISO on PE5
GPIOE->AFR[0] |= (0b0101 << 24); //Select alternate function as SPI MOSI on PE6
GPIOE->MODER |= (0b10 << 2); //PE2 as alternate function
GPIOE->MODER |= (0b10 << 6); //PE4 as alternate function
GPIOE->MODER |= (0b10 << 8); //PE5 as alternate function
GPIOE->MODER |= (0b10 << 10); //PE6 as alternate function
GPIOE->OSPEEDR |= (0b11 << 2); //PE2 as very high speed
GPIOE->OSPEEDR |= (0b11 << 6); //PE4 as very high speed
GPIOE->OSPEEDR |= (0b11 << 8); //PE5 as very high speed
GPIOE->OSPEEDR |= (0b11 << 10); //PE6 as very high speed
//---------------------------------------------------------------------------------------------------
SPI4->CR1 &= ~ (1 << 0); //CPOL bit, clock polarity, according to SPI Slave
SPI4->CR1 &= ~ (1 << 1); //CPHA bit, clock phase, according to SPI Slave
SPI4->CR1 |= (1 << 2); //Master mode selected
SPI4->CR1 |= (0b111 << 3); //Baud rate: 100 MHZ / 256
SPI4->CR1 &= ~ (1 << 7); //MSB sent first
SPI4->CR1 &= ~ (1 << 9); //Software slave management disabled
SPI4->CR1 &= ~ (1 << 10); //Full duplex mode
SPI4->CR2 |= (1 << 2); //Single master mode selected
SPI4->CR2 &= ~ (1 << 4); //Motorola mode selected
SPI4->CR2 |= (0b0111 << 8); //8 bit data frame format selected
}
void SPI_Enable (void){
SPI4->CR1 |= (1 << 6); //Enable SPI
}
void SPI_Disable (void){
SPI4->CR1 &= ~ (1 << 6); //Disable SPI
}
void SPI_Send (uint8_t data){
while(!(SPI4->SR &(1 << 1))); //Wait until TX buffer is empty
SPI4->DR = data;
while(!(SPI4->SR &(1 << 1))); //Wait until TX buffer is empty
while(SPI4->SR &(1 << 7)); //Wait until bus is not busy
//Perform dummy read to flush registers
uint8_t test = SPI4->DR;
test = SPI4->SR;
}
uint8_t SPI_Receive (void){
uint8_t data;
while(SPI4->SR &(1 << 7)); //Wait until bus is not busy
SPI4->DR = 0; //Send dummy data
while(!(SPI4->SR &(1 << 0))); //Wait until RX buffer is not empty
data = SPI4->DR;
return data;
}
int main (void){
Core_Clock_Setup();
Timer_Setup();
GPIO_Setup();
SPI_Setup();
uint8_t data = 0;
while(1){
GPIOC->BSRR |= ((1 << 14) << 16);
delay_ms(100);
GPIOC->BSRR |= (1 << 14);
delay_ms(100);
if(!(GPIOE->IDR&(1 << 0)))
{
SPI_Enable();
SPI_Send(0x03);
SPI_Send(0x00);
SPI_Send(0x00);
SPI_Send(0b00010000);
data = SPI_Receive();
SPI_Disable();
}
}
}
2025-01-14 07:56 AM - edited 2025-01-14 07:58 AM
Without even having a look at your code: read out and check/post the SPI and relevant GPIO registers content.
JW
2025-01-14 09:11 AM
If I understood your message correctly, my SPI CR1 and CR2 registers look exactly how I treat them in my setup function, like I said, I have verified that every line in the setup function gets executed correctly.
2025-01-14 09:48 AM - edited 2025-01-14 09:48 AM
> I have verified that every line in the setup function gets executed correctly
And have you verified also that there's nothing which would change those registers afterwards?
The same for the relevant GPIO registers.
Reading out and checking content of registers is 101. Post for others to look at them.
I also presume that you've already tested the SPI_CK pin's continuity to the point where you are measuring it, by setting given pin to GPIO Output and toggling it.
JW