Quiero configurar un transmisor / receptor esclavo SPI basado en interrupciones simple en una MCU STM32H7. Aunque tengo bastante experiencia con las series más antiguas de MCU ARM STM32, parece que muchas cosas son different para la serie H7 y requiere un gran esfuerzo para volver a aprender y remasterizar incluso algunas de las características más comunes.
Quiero ejecutar un ejemplo simple donde envío 8 bytes desde la PC (lado maestro) y recibo 8 bytes de la MCU ARM (lado esclavo). Estoy usando un cable MPSSE C232HM para enviar / recibir datos desde la PC.
El código MCU SPI Tx / Rx se presenta a continuación:
#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++;
}
}
El código se compiló utilizando IAR Embedded Workbench.
El código PC SPI Tx / Rx se presenta a continuación:
#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;
}
El código se compiló con Microsoft Visual Studio.
Al principio, tenía la impresión de que todo funciona correctamente. Al menos desde el lado de la PC aparentemente pude enviar y recibir datos a / desde la MCU. Al realizar una depuración más cercana, noté que solo la línea de PC a MCU funciona correctamente. La MCU siempre recibe datos de todo cero.
Para ilustrar, esta es la salida del depurador antes de iniciar la transferencia:
Idealmente,elcontenidodeaRxBufferdebesobrescribirsedespuésdequelatransferenciahayacomenzado.Enrealidad,laMCUtransmitetodossusdatoscorrectamente,aunquerecibedatosdetodoceroenlugardeloquerealmenteseenvió:
He realizado varios intentos de solución de problemas como:
- Probé las señales SCK / MISO / MOSI con un osciloscopio y todas parecen ser correctas, es decir, no hay problemas físicos con los cables o las pistas de PCB. En otras palabras, hay una señal digital MOSI adecuada que va del C232HM al MCU.
- He jugado un poco con todas las combinaciones de fase / polaridad de reloj en ambos lados, MCU y PC, y ninguno de los ajustes parece generar datos en el lado del receptor (puedo corromper los datos recibidos en el lado de la PC, aunque es de esperar).
- Curiosamente, si desconecto el cable MOSI de C232HM y conecto el pin MOSI en el lado de la MCU a un riel de + 3.3V, mi búfer aRxBuffer se llena con 0xFF. Así que parece que hay alguna respuesta receptiva en el lado de la MCU, aunque no se ejecuta en el borde CLK.
¿Alguien tiene una corazonada de dónde podría estar el problema?