Electronic – SPI on STM32 won’t work without pullup resistors and even then performs poorly

armembeddedspistm32stm32f10x

I've been trying to get the SPI1 on the STM32F103C8 (Blue Pill board) working correctly for some time now. As I'm just starting to learn ARM, I am simply trying to shift data to a 74HC595 shift register and latch it to light up a byte of LEDs. I am not reading back any data, so I only have MOSI, SCK and SS lines.

At first I wasn't getting anything out, but reading some online examples I could fix these first problems to get the communication going (I needed to correctly set GPIOA pins and set software SS).

The main problem right now is that if I don't include pull-up resistors on all lines (MOSI, SCK, and SS) the microcontroller doesn't output anything on any line (checked with a scope). On top of this, after adding pull-up resistors the rise time on the pulses is very slow so I can't use too high a frequency (with 10 kΩ pull-up resistors I'm limited to about 250 kHz SCK, and switching to 330 Ω about 4 MHz). I am working on a breadboard, but even then with AVR and messier wiring I could get a 4 MHz SPI working no problem without any added resistors and the waveforms were cleaner.

Here are two pictures (sorry for the abysmal state of my scope screen) transmitting the byte 0b01110010 at a 250 kHz clock. The top trace is SCK and the bottom is MOSI. The first picture is with 10 kΩ pull-up resistors and the second with 330 Ω pull-up resistors that make the waveforms much nicer (but they shouldn't be needed).

I'd appreciate some help to figure out what's going on.

10 kΩ pull-up resistors and 250 kHz clock

330 Ω pull-up resistors and 250 kHz make waveform a lot nicer

The relevant parts of my code are:

#define SS_LOW        GPIOA->BSRR |= 1 << 4 + 16;
#define SS_HIGH        GPIOA->BSRR |= 1 << 4;

// SPI GPIO configuration
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL |= 0b0011 << 4 * 4;    // Set pin A4 as PP out 50mHz for SS
GPIOA->CRL |= 0b1011 << 5 * 4;    // Set pin A5 AltFunc PP out 50mHz for SCK
GPIOA->CRL |= 0b1011 << 7 * 4;    // Set pin A7 AltFunc PP out 50mHz for MOSI
SS_HIGH;

// SPI1 configuration
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;        // Enable SPI1 clock
SPI1->CR1 |= SPI_CR1_SSM;        // Software SS
SPI1->CR1 |= SPI_CR1_SSI;
SPI1->CR1 |= SPI_CR1_BR_0;        // Set prescaler
SPI1->CR1 |= SPI_CR1_BR_1;
SPI1->CR1 |= SPI_CR1_BR_2;
SPI1->CR1 |= SPI_CR1_MSTR;        // Master mode
SPI1->CR1 |= SPI_CR1_SPE;        // Enable SPI

// Transmit byte
SS_LOW;
SPI1->DR = 0b01110010;
while(!(SPI1->SR & SPI_SR_TXE));
while(SPI1->SR & SPI_SR_BSY);
SS_HIGH;

Best Answer

You should reset the value of the pins you are changing before setting the bits.

The reset value of GPIOA_CRL is 0x4444 4444. So each pin is initialized with 0b0100, if you do a |= 0b0011 you end up with 0b0111 which is an open drain output. Same with 0b1011 becomes 0b1111 and that is an alternate function open drain.

So you need to do something like this:

// Reset pin configuration
GPIOA->CRL &= ~(0b1111 << 4 * 4);  // Reset Pin A4
GPIOA->CRL &= ~(0b1111 << 5 * 4);  // Reset Pin A5
GPIOA->CRL &= ~(0b1111 << 7 * 4);  // Reset Pin A7
GPIOA->CRL |= 0b0011 << 4 * 4;  // Set pin A4 as PP out 50mHz for SS
GPIOA->CRL |= 0b1011 << 5 * 4;  // Set pin A5 AltFunc PP out 50mHz for SCK
GPIOA->CRL |= 0b1011 << 7 * 4;  // Set pin A7 AltFunc PP out 50mHz for MOSI