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:
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:
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?
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\$