STM32F4 AmpLib: SPI Example, Shift Register

This page will detail one usage of the stm32f4-amperture-periphlib SPI Library in order to drive a 74HC595 Shift Register using the STM32-NUCLEO-F401 board.

Full Library Page

Please click here to find the link and description of the Library package.

To see a working example project using these configurations and code, please click here.

Init Config

First, import spi.c and spi.h into your project accordingly.

A 74HC595 shift register follows these rules:

  • 8 bit register
  • Rising Edge Clocking
  • First Edge is data
  • Receive Only
  • Needs Data “Latch” outside of SPI.

So, according to those rules, we must configure our init function properly.

For this example, we will use the SPI1 peripheral connected to GPIO port B. We will also configure the SPI with the MSTR (Master) and SSM+SSI (Internal Slave Select: 1) options enabled.

Since we do not need MISO functionality, we can disable those settings in the init function using comments, we can use that GPIO pin for a data latch later.

void spiInit(SPI_TypeDef *SPIx, GPIO_TypeDef *GPIOx, uint8_t mosiPin,
        uint8_t misoPin, uint8_t sckPin, uint8_t afMode){

    // Enable RCC APB Register for SPI Peripheral.
    // Uncomment the desired SPI register enable.

    //RCC -> APB1ENR |= RCC_APB1ENR_SPI2EN;
    //RCC -> APB1ENR |= RCC_APB1ENR_SPI3EN;
    //RCC -> APB2ENR |= RCC_APB2ENR_SPI0EN;
    RCC -> APB2ENR |= RCC_APB2ENR_SPI1EN;
    //RCC -> APB2ENR |= RCC_APB2ENR_SPI4EN;

    // Enable RCC AHB Register for GPIO
    // Please replace RCC_AHB1ENR_GPIOxEN, x = (A, B, C, ... ,I)
    RCC -> AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

    // Disable Internal SPI Peripheral Clock.
    SPIx -> CR1     &=  ~(SPI_CR1_SPE);

    SPIx -> CR1     =   (0
                        | SPI_CR1_CPHA  // Clock Phase, 0 = 1st Edge, 1 = 2nd
                        // | SPI_CR1_CPOL  // Clock Polariy, 0=Rising, 1=Falling
                        | SPI_CR1_MSTR  // Master Enable
                        // | SPI_CR1_LSBFIRST  // Lowest Significant Bit First
                        | SPI_CR1_SSM   // Software Slave Management
                        | SPI_CR1_SSI   // Internal Slave Select Bit
                        // | SPI_CR1_RXONLY    // Receive Only
                        // | SPI_CR1_DFF   // Data Frame Format, 0=8-bit, 1=16-bit
                        // | SPI_CR1_CRCNEXT   // CRC Next (TODO:Learn about this)
                        // | SPI_CR1_CRCEN // Enable CRC Calculation
                        // | SPI_CR1_BIDIMODE  // 2-direc data mode, 0=2-line
                        // | SPI_CR1_DIDIOE    // 2-direc Output Enable
                        | (SPI_CR1_BR & (AMP_SPI_BAUD_RATE << 3)) // Baud Rate
                        );

    SPIx -> CR2     =   (0
                        // | SPI_CR2_RXDMAEN   // Receive DMA Buffer Enable
                        // | SPI_CR2_TXDMAEN   // Transmit DMA Buffer Enable
                        // | SPI_CR2_SSOE      // SS Output Enable (Multimaster=0)
                        // | SPI_CR2_ERRIE     // Error Interrupt Enable
                        // | SPI_CR2_RXNEIE    // Receive Buffer Not Empty IntEnab
                        // | SPI_CR2_TXEIE     // Transmit Buffer Empty IntEnable
                        );

    // Re-Enable SPI Peripheral
    SPIx -> CR1 |= SPI_CR1_SPE; 

    // Set GPIO pins to AF, High Speed, NoPull
    GPIOx -> MODER      |=  (0x02 << (2 * mosiPin)) | /*(0x02 << (2 * misoPin))*/
        (0x02 << (2 * sckPin)); 
    GPIOx -> OSPEEDR    |=  (0x03 << (2 * mosiPin)) | (0x03 << (2 * misoPin))
        | (0x03 << (2 * sckPin)); 
    GPIOx -> PUPDR      &=  ~((0x03 << (2 * mosiPin)) | (0x03 << (2 * misoPin))
        | (0x03 << (2 * sckPin)));

    // Set GPIO Pins to Alternate Function Setting
    // Please refer to the datasheet for which numbers to use.
    // http://www.st.com/st-web-ui/static/active/en/resource/technical/document/datasheet/DM00102166.pdf
    // Page 45
    if(mosiPin > 7) GPIOx -> AFR[1] |= (afMode << (4 * (mosiPin - 7)));
    else GPIOx -> AFR[0] |= (afMode << (4 * mosiPin));
    if(misoPin > 7) GPIOx -> AFR[1] |= (afMode << (4 * (misoPin - 7)));
    else GPIOx -> AFR[0] |= (afMode << (4 * misoPin));
    if(sckPin > 7) GPIOx -> AFR[1] |= (afMode << (4 * (sckPin - 7)));
    else GPIOx -> AFR[0] |= (afMode << (4 * sckPin));
}

Now, with our SPI peripheral configured, we need to set up our data latch for the shift register, which we will connect to Port B, pin 4. Thankfully the RCC has already been enabled for that port, so we only need to set the GPIOB.MODER register to set PB4 to output.

// Initialize PB_5 to be the Shift Register Latch
GPIOB -> MODER &= ~(3 << (2*4));
GPIOB -> MODER |= (1 << (2*4));
void shiftRegisterLatch(){
    GPIOB -> ODR           |=   (1 << 4);
    Delay(10);
    GPIOB -> ODR           &=   ~(1 << 4);
    Delay(10);
}
/*
 * ===========================================================================
 *
 *       Filename:  main.c
 *
 *    Description:  Example of SPI peripheral use to drive a 74HC595 Shift
 *                  Register to achieve a crawling light bar.
 *
 *      Libs Used:  SPI
 *
 *        Version:  1.0
 *        Created:  01/12/2016 
 *       Revision:  none
 *       Compiler:  arm-none-eabi-gcc
 *
 * ===========================================================================
 */

#include <stm32f4xx.h>
#include <stdint.h>
#include <spi.h>

void shiftRegisterSendData(uint8_t data){
    spiByteSend(SPI1, data);
}

void Delay(uint32_t nCount){
    for(; nCount != 0; nCount--);
}

void shiftRegisterLatch(){
    GPIOB -> ODR           |=   (1 << 4);
    Delay(10);
    GPIOB -> ODR           &=   ~(1 << 4);
    Delay(10);
}

void initHeartbeat(){
    RCC -> AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    GPIOA -> MODER = (1 << 10); 
    GPIOA -> ODR &= ~(1 << 5); 
}

int main(void){
    // Initialize SPI1 Clock to be connected to GPIO:
    // SPI1_MOSI      -> PB_5
    // SPI1_SCK       -> PB_3
    // GPIO AF MODE   -> 5
    spiInit(SPI1, GPIOB, 5, 4, 3, 5);


    // Initialize PB_5 to be the Shift Register Latch
    GPIOB -> MODER &= ~(3 << (2*4));
    GPIOB -> MODER |= (1 << (2*4));

    initHeartbeat();

    int j = 1;
    for(;;){
        int i = 0;

        // Turn Heartbeat Light ON
        GPIOB -> ODR           |=   (1 << 4);

        for(i = 0; i < 3; i++){
            shiftRegisterSendData(j);
        }
        
        shiftRegisterLatch();

        // Heartbeat
        GPIOA -> ODR ^= (1 << 5);

        // Delay Function, sets speed of crawling light.
        // NOTE: Watch what happens when you set delay length to 0.
        Delay(300000);

        j = j << 1;
        if (j >= 0xFF) j = 1;
    }
}
This entry was posted in stm32f4-amperture-periphlib and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *