8
\$\begingroup\$

I want to configure a simple interrupt-based SPI slave transmitter/receiver on a STM32H7 MCU. Even though I have quite a bit experience with the older series of STM32 ARM MCUs, it seems that a lot of things are different for the H7 series and it takes quite an effort to relearn and to remaster even some of the more common features.

I want to execute a simple example where I send 8 bytes form the PC (master-side) and receive 8 bytes from the ARM MCU (slave-side). I am using a C232HM MPSSE cable to send/receive data from the PC.

The MCU SPI Tx/Rx code is presented below:

#include "stm32h7xx.h"

static void InitializeMCO(void);
static void ConfigureHSI(void);
static void InitializeMasterTxSPI(void);

volatile uint8_t aTxBuffer[8] = {'S','T','M','3','2','O','u','t'};
volatile uint8_t aRxBuffer[8] = {'E','m','p','t','y','A','r','r'};

uint32_t aRxBuffPos;
uint32_t aTxBuffPos;

uint8_t rxCounter;
uint8_t txCounter;

void SPI1_IRQHandler(void);

int main()
{
        aRxBuffPos = 0;
        aTxBuffPos = 0;

        rxCounter = 0;
        txCounter = 0;

    ConfigureHSI();
    InitializeMCO();
        InitializeMasterTxSPI();

    while (1)
    {

    };
}

/* Initializes the MCU clock */
static void ConfigureHSI(void)
{
    PWR->CR3 |= PWR_CR3_SCUEN;
    PWR->D3CR |= (PWR_D3CR_VOS_1 | PWR_D3CR_VOS_0);
    while ((PWR->D3CR & PWR_D3CR_VOSRDY) != PWR_D3CR_VOSRDY)
    {
    };

    FLASH->ACR = FLASH_ACR_LATENCY_2WS;

    RCC->CR |= RCC_CR_HSION;
    while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY)
    {
    };

    RCC->PLLCKSELR = (4u << RCC_PLLCKSELR_DIVM1_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM2_Pos) | 
                         (32u << RCC_PLLCKSELR_DIVM3_Pos) | 
                         RCC_PLLCKSELR_PLLSRC_HSI;

    RCC->PLLCFGR = RCC_PLLCFGR_DIVR1EN | 
                       RCC_PLLCFGR_DIVQ1EN | 
                       RCC_PLLCFGR_DIVP1EN | 
                       (2u << RCC_PLLCFGR_PLL1RGE_Pos) | 
                       (1u << RCC_PLLCFGR_PLL1VCOSEL_Pos);

    RCC->PLL1DIVR = ((2u - 1u) << RCC_PLL1DIVR_R1_Pos) | 
                        ((2u - 1u) << RCC_PLL1DIVR_Q1_Pos) | 
                        ((2u - 1u) << RCC_PLL1DIVR_P1_Pos) | 
                        ((50u - 1u) << RCC_PLL1DIVR_N1_Pos)
            ;

    RCC->D1CFGR = RCC_D1CFGR_D1CPRE_DIV1;
    RCC->D1CFGR = RCC_D1CFGR_HPRE_DIV2 | RCC_D1CFGR_D1PPRE_DIV2;
    RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 | RCC_D2CFGR_D2PPRE2_DIV2;
    RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;

    RCC->CR |= RCC_CR_PLL1ON;
    while (!(RCC->CR & RCC_CR_PLLRDY))
    {
    };

    RCC->CFGR |= (1u << 25);
    RCC->CFGR |= RCC_CFGR_SW_PLL1;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL1))
    {
    };
}

/* Displays MCO on PC9 */
static void InitializeMCO(void)
{
    RCC->CFGR |= RCC_CFGR_MCO2;
    RCC->AHB4ENR &= ~RCC_AHB4ENR_GPIOCEN;
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN;
    GPIOC->MODER &= ~GPIO_MODER_MODER9;
    GPIOC->MODER |= GPIO_MODER_MODER9_1;
    GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9;
    GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9;
    GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR9;
    GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;  
        GPIOC->AFR[0] &= ~GPIO_AFRL_AFRL0;
}

static void InitializeMasterTxSPI(void)
{
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN;   // Enable usage of GPIOA

    GPIOA->MODER &= ~GPIO_MODER_MODER5;
    GPIOA->MODER |= GPIO_MODER_MODER5_1;   // Alternate function for SPI1 SCK on PA5
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;   // High Speed on PA5
    GPIOA->AFR[0] |= (0x05 << 5 * 4);   // AFRL selected AF5 (SPI1 SCK) for PA5

    GPIOA->MODER &= ~GPIO_MODER_MODER6;
    GPIOA->MODER |= GPIO_MODER_MODER6_1;   // Alternate function for SPI1 MISO on PA6
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;   // High Speed on PA6
    GPIOA->AFR[0] |= (0x05 << 6 * 4);   // AFRL selected AF5 (SPI1 MISO) for PA6

    GPIOA->MODER &= ~GPIO_MODER_MODER7;
    GPIOA->MODER |= GPIO_MODER_MODER7_1;   // Alternate function for SPI1 MOSI on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 7 * 4);   // AFRL selected AF5 (SPI1 MOSI) for PA7

    GPIOA->MODER &= ~GPIO_MODER_MODER15;
    GPIOA->MODER |= GPIO_MODER_MODER15_1;   // Alternate function for SPI1 NSS on PA4
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15;   // High Speed on PA4
    GPIOA->AFR[1] |= (0x05 << (15 - 8) * 4);   // AFRL selected AF5 (SPI1 NSS) for PA4

    GPIOA->PUPDR |=  GPIO_PUPDR_PUPDR15;   // Ensure all pull up pull down resistors are enabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;   // Ensure all pull up pull down resistors are disabled

    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

    SPI1->CFG1 = (0u << SPI_CFG1_FTHLV_Pos) | 
                     (7u << SPI_CFG1_DSIZE_Pos);        
        SPI1->CFG2 = 0;

        //SPI1->CFG2 |= SPI_CFG2_LSBFRST;
        //SPI1->CFG2 |= SPI_CFG2_CPHA;
        //SPI1->CFG2 |= SPI_CFG2_CPOL;
        //SPI1->CR2 = 8;

    NVIC_SetPriority(SPI1_IRQn, 1);
    NVIC_EnableIRQ(SPI1_IRQn);

        //SPI1->IER |= SPI_IER_DXPIE;
        SPI1->IER |= SPI_IER_RXPIE;
        SPI1->IER |= SPI_IER_TXPIE;

        SPI1->CR1 |= SPI_CR1_SPE;
}

void SPI1_IRQHandler(void)
{  
  if(SPI1->SR & SPI_SR_RXP)
  {
   //while(SPI1->SR & SPI_SR_RXP)
   {
      aRxBuffer[aRxBuffPos++] = *((__IO uint8_t *)&SPI1->RXDR);
     //aRxBuffer[aRxBuffPos++] = *(volatile uint8_t *) SPI1->RXDR; 
     //aRxBuffer[aRxBuffPos++] = SPI1->RXDR;
   }
  }

  if(SPI1->SR & SPI_SR_TXP)
  {
   //while(SPI1->SR & SPI_SR_TXP)
   {
     *(volatile uint8_t *) &(SPI1)->TXDR = aTxBuffer[aTxBuffPos++];
     //*(volatile uint8_t *) &(SPI1)->TXDR = RxBuff[SPI_ByteCount++]; 
   }
  }

  if (aTxBuffPos >= 8)
  {
    aTxBuffPos = 0;
    txCounter++;
  }

  if (aRxBuffPos >= 8)
  {
    aRxBuffPos = 0;
    rxCounter++;
  }
}

The code was compiled using IAR Embedded Workbench.

The PC SPI Tx/Rx code is presented below:

#include <stdio.h>
#include <Windows.h>
#include "libMPSSE_spi.h"

void print_and_quit(char cstring[]) {
    printf("%s\n", cstring);
    system("pause");
    exit(1);
}

int main(int argc, char **argv) {

    Init_libMPSSE();

    FT_STATUS status;
    FT_DEVICE_LIST_INFO_NODE channelInfo;
    FT_HANDLE handle;

    // check how many MPSSE channels are available
    uint32 channelCount = 0;
    status = SPI_GetNumChannels(&channelCount);
    if (status != FT_OK)
        print_and_quit("Error while checking the number of available MPSSE channels.");
    else if (channelCount < 1)
        print_and_quit("Error: no MPSSE channels are available.");

    printf("There are %d channels available.\n\n", channelCount);

    for (int i = 0; i < channelCount; i++) {
        status = SPI_GetChannelInfo(i, &channelInfo);
        if (status != FT_OK)
            print_and_quit("Error while getting details for an MPSSE channel.");

        printf("Channel number: %d\n", i);
        printf("Description: %s\n", channelInfo.Description);
        printf("Serial Number: %d\n", channelInfo.SerialNumber);
    }

    // ask the user to select a channel
    uint32 channel = 0;
    //printf("\nEnter a channel number to use: ");
    //scanf_s("%d", &channel);

    // open the MPSSE channel (get the handle for it)
    status = SPI_OpenChannel(channel, &handle);
    if (status != FT_OK)
        print_and_quit("Error while opening the MPSSE channel.");
    else
        printf("Channel opened\n");

    ChannelConfig channelConfig;
    channelConfig.ClockRate = 4000000;
    channelConfig.configOptions = SPI_CONFIG_OPTION_MODE0 | SPI_CONFIG_OPTION_CS_DBUS3 | SPI_CONFIG_OPTION_CS_ACTIVELOW;
    channelConfig.LatencyTimer = 1;
    status = SPI_InitChannel(handle, &channelConfig);
    if (status != FT_OK)
        print_and_quit("Error while initializing the MPSSE channel.");
    else
        printf("Channel initialized\n");

    uint8 tx_buffer[8] = { 'P' ,  'C',  'S',  'P',  'I',  'O',  'u', 't', },
          rx_buffer[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

    uint32 transferCount = 0;
    uint32 options = SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES | SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE | SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE;

    //while (1)
    {
        status = SPI_ReadWrite(handle, rx_buffer, tx_buffer, 8, &transferCount, options);
        printf("tx = %c %c %c %c %c %c %c %c, rx = %c %c %c %c %c %c %c %c\n", tx_buffer[0], tx_buffer[1], tx_buffer[2], tx_buffer[3], tx_buffer[4], tx_buffer[5], tx_buffer[6], tx_buffer[7],
                                                                               rx_buffer[0], rx_buffer[1], rx_buffer[2], rx_buffer[3], rx_buffer[4], rx_buffer[5], rx_buffer[6], rx_buffer[7]);
        Sleep(500);
    }
    system("pause");

    Cleanup_libMPSSE();
    return 0;
}

The code was compiled using Microsoft Visual Studio.

At first, I was under the impression that that everything works properly. At least from the PC side I was seemingly able to send and receive data to/from the MCU. Upon closer debugging, I've noticed that only the PC-to-MCU line is working properly. The MCU always receives all-zero data.

To illustrate, this is the debugger output before initiating the transfer: enter image description here

Ideally, the contents of aRxBuffer should be overwritten after the transfer has commenced. In reality, the MCU transmits all of its data properly, albeit it receives all-zero data instead of what was actually sent: enter image description here

I've done various troubleshooting attempts like:

  • I probed the SCK/MISO/MOSI signals with an oscilloscope and they all appear to be correct, i.e., there are no physical issues with the wires or the PCB tracks. In other words, there is a proper digital MOSI signal going from the C232HM to the MCU.
  • I played around with all the clock phase/polarity combinations on both the MCU and the PC sides and none of adjustments seem to generate any data on the receiver side (I can corrupt the data received on the PC side though, albeit that is to be expected).
  • Interestingly, if I disconnect the MOSI cable from C232HM and connect the MOSI pin on the MCU side to a +3.3V rail, my aRxBuffer buffer gets filled with 0xFF. So it seems that there is some receptive response on the MCU side, albeit it is not executed on the CLK edge.

Does anyone have a hunch where the problem might be?

\$\endgroup\$
5
  • \$\begingroup\$ Have you considered if the debugger could itself be causing issues? What happens if you get the debugger out of the picture and test by making the MCU loop back the data sent by the PC (or better yet, its complement). \$\endgroup\$ Sep 4, 2018 at 15:21
  • \$\begingroup\$ It doesn't seem to be the case. I've added these addition to the code: if (aRxBuffPos >= 8) { aRxBuffPos = 0; for (uint8_t i=0; i<8; i++) aTxBuffer[i] = aRxBuffer[i]; rxCounter++; } During first four promts I get the same message back (STM32OUT). Consequently, the Tx FIFO is buffered out and I get blank data (0x00) back from the MCU. Thus, the debugger does not seem to be the culprit in this case. \$\endgroup\$
    – K.R.
    Sep 4, 2018 at 16:00
  • \$\begingroup\$ Maybe you should try single byte transfer without the buffer, to bypass any potential implementation issues there \$\endgroup\$ Sep 4, 2018 at 16:34
  • \$\begingroup\$ Have you tried different configs for pull-up pull-down or using it as open-drain? \$\endgroup\$ Aug 18, 2020 at 17:13
  • \$\begingroup\$ Have you found the answer? These might be the points to look at: 1) Driver library/API change 2)DMA and queue handling 3) Event signal from SPI and acknowledging sequence through the ST library functions. \$\endgroup\$
    – jay
    Jul 29, 2021 at 19:39

2 Answers 2

1
\$\begingroup\$

First, I wished the source code had enough comments, so a reader may not have to go through all the symbols in the datasheet and library header.

I do not have enough understanding/information, so what I say here is very much hypothetical.

"interrupt-based SPI slave transmitter/receiver on a STM32H7 MCU":
InitializeMasterTxSPI() shouldn't really configure the SPI as a master as the name implies. I checked the datasheet for the master/slave cfg (nothing else I checked), "SPI1->CFG2 = 0;" seems to be the line setting it to slave. That is supposed to be the story for you to get any receive signal in slave mode (no voluntary transmission).

This is the most possible scenario looking at your explanations:
SPI1_IRQHandler() must be coming from some of the receive buffer status flag, if you get any IRQ. It is not the strobe signal from the master (Host) that generates IRQ to STM32.

That means the STM32 (slave) strobe signal is in "active" state always, because of either hardware problem, or host not strobe-ing properly.
Whenever STM32 gets set number of SPI clocks, IRQ is generated. And, the ISR arms the SPI back for the next operation.

There are, if I remember correctly, designated strobe pins when SPI is configured to slave mode. Check if the strobe pin is wired correct and configured correct. Check if the host strobe signal is coming through. You may test IRQ at strobe change as well.

This can explain how you get all '0' or all 'f' by hard-wiring MOSI on STM32 side. Meantime, "how the STM32 gets all '0' with MOSI connected to the host" needs closer look.

\$\endgroup\$
0
\$\begingroup\$

This answer is not a solution, but a suggestion of a method to isolate the problem.

I assume you have one or more STM32H7 output pins that you can drive as GPIOs, and that you can sense with an oscilloscope or logic analyzer.

First, determine what the STM32H7 recognizes on the SPI input pins. In your background (non-interrupt) code, run a tight loop that reads the SPI data and/or clock pins (from the associated GPIO input data register), and write the bit(s) onto GPIO output pins that you can sense with an oscilloscope or logic analyzer.

You should see that the GPIO output pin(s) follow the SPI data and clock inputs, with a delay due to the GPIO sampling. If you do not see this, you know that something is wrong with the input pin sampling.

For this, you'll want the SPI clock rate to be much slower than the microcontroller clock, and you'll want the GPIO output set to a high speed.

Second, assuming the above test works, check that the SPI is receiving input words properly. To do this, update your SPI interrupt routine to toggle a GPIO output each time a value is received. You could use the low-order bit of the aRxBuffPos index as the GPIO output value, you should see it change each time a word is received.

Third, assuming the above tests all work, check the data. Your code overwrites the receive data buffer on each 8 values, but you want to see if you ever receive a non-zero value. So, modify the interrupt code to drive the GPIO pin high when it sees a non-zero value. If you ever see the GPIO line go high, you are able to receive a non-zero value. Then I would update the test to write the low-order bit of the received data value to the GPIO line, and see what you have to write on the host (PC) side in order to get non-zero data received by the STM32H7.

I think that sequence is pretty much guaranteed to isolate the problem.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.