-1

I'm pretty new to the STM32 world, and I'm trying to get my SPI communication to work. I have it running in Master mode and configured to Software Slave management mode. And I'm using a simplex comm. format with transmission only from the Master side.

When I try to load data into may SPI Data Register, there's no change to the DR as far as I can tell from the debugger.

Any help with this would be greatly appreciated:

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define RCC_BASE_ADDR           0x40023800U
#define SPI_1_BASE_ADDR         0x40013000U
#define GPIO_A_BASE_ADDR        0x40020000U

// Register Address Offsets

    // RCC
#define RCC_CR_OFFSET           0x00U
#define RCC_AHB_1_EN_OFFSET     0x30U
#define RCC_APB_1_EN_OFFSET     0x40U
#define RCC_APB_2_EN_OFFSET     0x44U

    // SPI
#define SPIx_CR_1               0x00U
#define SPIx_CR_2               0x04U
#define SPIx_SR                 0x08U
#define SPIx_DR                 0x0CU
//#define SPIx_RX                   0x14U
//#define SPIx_TX                   0x18U
#define SPIx_CFGR               0x1CU

    // GPIO
#define MODE_R                  0x00U       // Mode of GPIO
#define OTYPE_R                 0x04U       // Output type of GPIO
#define OSPEED_R                0x08U       // Output speed
#define PUPD_R                  0x0CU       // input Config (Pull Up/Down)
#define IDR                     0x10U       // input Data reg
#define ODR                     0x14U       // Output Data reg
#define AFLR                    0x20U       // Alternate function GPIOx[0:7]
#define AFHR                    0x24U       // Alternate function GPIOx[8:15]


// Bit position offsets

// RCC
    //AHB1_EN
#define GPIO_A                  0

    // APB1_EN
#define SPI_1                   12

// SPI
    // CR1
#define BIDIMODE                15
#define BIDIOE                  14
#define DFF                     11
#define RXONLY                  10
#define SSM                     9
#define SSI                     8
#define LSBFIRST                7
#define SPE                     6
#define BAUD                    5       // [Width : 3]
#define MSTR                    2
#define CPOL                    1
#define CPHA                    0
    // CR2
#define TXE                     1       // Transfer buffer status
    // SR

// GPIOx


// Global vars

// ** RCC
// Enable register for APB2 buss
uint32_t* rcc_apb2_en = (uint32_t*) (RCC_BASE_ADDR + RCC_APB_2_EN_OFFSET);
uint32_t* rcc_ahb1_en = (uint32_t*) (RCC_BASE_ADDR + RCC_AHB_1_EN_OFFSET);

// ** GPIO
// GPIO Mode register
uint32_t* gpio_a_mode   = (uint32_t*) (GPIO_A_BASE_ADDR + MODE_R);
// GPIO Alternate function Selection register (LOW)
uint32_t* gpio_aflr     = (uint32_t*) (GPIO_A_BASE_ADDR + AFLR);

// ** SPI
// SPI Control Register 1
uint32_t* spi_cr1 = (uint32_t*) (SPI_1_BASE_ADDR + SPIx_CR_1);
// SPI Status register
uint32_t* spi_sr = (uint32_t*)  (SPI_1_BASE_ADDR + SPIx_SR);
// SPI data register
uint32_t* spi_dr = (uint32_t*)  (SPI_1_BASE_ADDR + SPIx_DR);



// Configure GPIO pins A5 (SCLK) and A7 (MOSI)
void GpioInit(){
    // Enable GPIO A on ahb1 bus
    *rcc_ahb1_en |= (0x1 << GPIO_A);

    // Set the mode for pins 5 and 6 on port A to their alternate functionality
    *gpio_a_mode |= (0x2 << 10) | (0x2 << 14);

    // Set Pin5 as Serial clock and pin7 as MOSI
    *gpio_aflr |= (0x5 << 20) | (0x5 << 28);
}

void SPI_init(){
    // Enable SPI on RCC clock
    *rcc_apb2_en |= (0x1 << SPI_1);

    // Reference to CR1 register in SPI 1 address region

    // Configure SPI as for 2 wire unidirectional mode aka full duplex transmission

    *spi_cr1 &= ~(0x1 << BIDIMODE);

    // Set Data frame as 16 bits wide
    *spi_cr1 |= (0x1 << DFF);

    // Not implemented

        // Internal slave select

        // Transfer is LSB first

        // Enable SPI

    // Enable software slave management
    *spi_cr1 |= (0x1 << SSM);

    // Set SSI to high to avoid mode fault
    *spi_cr1 |= (0x1 << SSI);
    // Set baud rate as default DIV2
    *spi_cr1 &= ~(0x7 << BAUD);

    // Configure as master
    *spi_cr1 |= (0x1 << MSTR);
    //  Set to mode 0
    *spi_cr1 &= ~(0x3 << CPHA);

}

void SPIx_write(uint8_t* value, uint8_t len){
    // Check the value of the DFF register
    uint8_t dff_val = ( (uint32_t)(*spi_cr1) >> DFF) & 0x1;
    // 16 Bit transmission
    if(dff_val){
        while(len > 0){
            // Check the status of the TX register
            // If empty, then transfer 2 bytes
            if(1 /*( (*spi_sr)  >> TXE ) & 0x1 */){
                // transfer first 2 bytes
//              *spi_dr = *((uint16_t*) value);
                *spi_dr = *((uint16_t*) value);
                // decrement by 2
                len-=2;
                // Move to next 2 bytes
                (uint16_t*)value++;
                continue;
            }
            // If not empty then delay
            for(uint8_t i = 0; i < 2; i++);
        }
    }
    // 8 Bit transmission
    else{
        while(len){
            // Check the status of the TX register
            // If empty, then transfer 1 byte
            if( (*spi_sr) & (0x1 << TXE) ){
                // transfer first byte
                *spi_dr = *value;
                // decrement by 1
                len--;
                // Move to next byte
                value++;
                continue;
            }
            // If not empty then delay
            for(uint8_t i = 0; i < 2; i++);
        }
    }

}


int main(void)
{
    char data[] = "hello";
    GpioInit();     // Initialize GPIO pins 5 & 7
    SPI_init();         // SPI instantiate
    // Enable the SPI for communication
    *spi_cr1 |= (0x1 << SPE);
    // Write to SPI data reg
    SPIx_write((uint8_t*)data, strlen(data));
    /* Loop forever */
    for(;;);
}
glts
  • 21,808
  • 12
  • 73
  • 94
  • Most SPI devices have a chip-select signal that you have to drive (usually low) during each transaction. I don't see any sign of that in your code. – pmacfarlane Mar 20 '23 at 20:41
  • I configured it for software management mode, so I drove my Chip select, in this case (NSS) to high with this line: *spi_cr1 |= (0x1 << SSI); it has to be high in when using SSM mode. – Grimmace_23 Mar 20 '23 at 20:53
  • I haven't used this _exact_ STM32 part, and I don't know how you've wired it up. But normally I disable hardware NSS, and use a regular GPIO pin (in output mode) to control the chip-select. Normally this must be driven low before the start of each transaction, then driven high after it. – pmacfarlane Mar 20 '23 at 21:21
  • You also clearly need the `volatile` as suggested in an answer. – pmacfarlane Mar 20 '23 at 21:28
  • Yes, it would be necessary if I need to increase the optimization level of my code later on. This is a trial with -0O optimization, so it doesn't make any difference. I have tried that already. – Grimmace_23 Mar 20 '23 at 22:09
  • What is the "other" device you are trying to talk to over SPI? – pmacfarlane Mar 20 '23 at 22:21
  • If you think you can read back (e.g. via the debugger) a value you wrote into the data register, you are mistaken. From the reference manual: "The data register is split into 2 buffers - one for writing (Transmit Buffer) and another one for reading (Receive buffer). A write to the data register will write into the Tx buffer and a read from the data register will return the value held in the Rx buffer." – pmacfarlane Mar 20 '23 at 22:24

2 Answers2

1
  1. Avoid reinventing the wheel and use STM-provided CMSIS definitions. Your attempt is quite naive. For example:
uint32_t* rcc_ahb1_en = (uint32_t*) (RCC_BASE_ADDR + RCC_AHB_1_EN_OFFSET);

will consume RAM for no reason and adds another level of indirection. Additionally, you need to use some volatile keywords to let compiler know that some memory locations may change without any visible to compiler operations changing it (they are called "side effects prone").

If you want to stick to your own definitions (good luck defining 10000 registers) it would be much better to:

#define rcc_ahb1_en ((volatile uint32_t*) (RCC_BASE_ADDR + RCC_AHB_1_EN_OFFSET))

and the same with all others

0___________
  • 60,014
  • 4
  • 34
  • 74
0

The only symptom of your problem that you have given is the following:

When I try to load data into may SPI Data Register, there's no change to the DR as far as I can tell from the debugger.

You are misunderstanding how the data register works. From the reference manual at section 28.5.4:

DR[15:0]: Data register Data received or to be transmitted. The data register is split into 2 buffers - one for writing (Transmit Buffer) and another one for reading (Receive buffer). A write to the data register will write into the Tx buffer and a read from the data register will return the value held in the Rx buffer.

Since you are running in simplex mode (only transmitting), there will never be anything in the receive buffer, so it will always read as zero. This includes the debugger reading the data register.

You may have other problems, but you have not described them.

pmacfarlane
  • 3,057
  • 1
  • 7
  • 24
  • I get what you're saying. I guess I was expecting to see something loaded onto the DR register after writing to it, at least just for one cycle before anything else. Thanks for the clarification! – Grimmace_23 Mar 21 '23 at 00:23