Electronic – Receiver issues on STM32H7 interrupt-based SPI

ftdinucleospistm32

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?

Best Answer

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.