cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H745BI SPI port does not start

ews
Associate II

I am trying to get a SPI port up and running on an STM32H745BI chip but after configuring the port and starting the transmission I never see any signals on the pins and the port never gets an End Of Transmission (EOT) high signal. Startup code is listed below, it hangs while waiting for EOT. Relevant register values are in the attached image. Any ideas on why this code doesn't work? Thanks!

 

 

// define GPIO pin interface to external ADC, see DS12923 Table 9 for alternate function mapping																																// Schematic signal
static pinDef adc_rdl	= { .port = GPIOI, .pin = PIN_0,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_NSS
static pinDef adc_sck	= { .port = GPIOI, .pin = PIN_1,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_SCK
static pinDef adc_sdo	= { .port = GPIOI, .pin = PIN_2,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MISO
static pinDef adc_sdi	= { .port = GPIOI, .pin = PIN_3,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MOSI

void spi2::init(void)
{
	// init hardware pins
	configurePin(adc_rdl);
	configurePin(adc_sck);
	configurePin(adc_sdo);
	configurePin(adc_sdi);
	
	// init SPI2 port 
	SET_BIT(RCC->APB1LENR, RCC_APB1LENR_SPI2EN);		// turn on SPI2 clock (read change in RCC_C1_APB1LENR)
	SET_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);		// reset SPI2 peripheral
	CLEAR_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);	// release reset
	SET_BIT(SPI2->CFG2, SPI_CFG2_SSOE);					// enable SS pin output (this must be done before setting master)
	SET_BIT(SPI2->CFG2, SPI_CFG2_MASTER);				// act as SPI bus master
	MODIFY_REG(SPI2->CR2, SPI_CR2_TSIZE_Msk, 4);		// transfer four bytes
	SET_BIT(SPI2->CR1, SPI_CR1_SPE);					// enable the SPI port
	
	// put dummy data into Tx FIFO
	SPI2->TXDR = 0x01;									// SPI2->SR TXTF goes high here, so we have data in the FIFO
	SPI2->TXDR = 0x02;
	SPI2->TXDR = 0x03;
	SPI2->TXDR = 0x04;
	
	// start transmission
	SET_BIT(SPI2->CR1, SPI_CR1_CSTART);
	while (!READ_BIT(SPI2->SR, SPI_SR_EOT)) {}			// <-- program hangs here, EOT never goes high
}

 

 

 

Edit:

Just checking my clock inputs:

The SPI2 spi_ker_ck input source gets selected by RCC_D2CCIP1R_SPI123SEL which is set to 1 so that it uses pll2_p_ck as an input. I can route pll2_p_ck out of the MCO2 pin where it is 200MHz as expected.

Then spi_pclk comes from the APB1 clock that is running at 120MHz. I don't have a way to measure that directly but I can write to the SPI2 registers just fine so some kind of clock must be there.

There is a mention in the errata of a possible stall caused when spi_pclk is much faster than spi_ker_ck but that does not seem to be the case here.

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
ews
Associate II

OK, I think I figured this one out finally. It looks like you need to enable the SPI port, set CSTART=1, and then put enough data in the Tx buffer to make SR.TXTF go high, meaning there is a full packet ready to be sent. The data goes out SPI port and SR.EOT goes high and CSTART goes low when the packet is sent. 

 

The thing that was missing for me was that SR.TXTF has to be manually set to zero by writing a 1 to the IFCR.TXTFC bit between transfers. If you don't set it to zero it doesn't transition upward the next time the Tx buffer has a full packet worth of data and the SPI data transmission never starts.

 

Anyway, here's a minimalist code snippet that sends dummy data out of SPI2. The configurePin() routines are just my own GPIO set-up utility and can be replaced with the equivalent HAL or whatever calls.

 

 

#include "../inc/ext_adc.h"

using namespace gpio;

// define GPIO pin interface to external ADC, see DS12923 Table 9 for alternate function mapping									
static pinDef adc_rdl	= { .port = GPIOI, .pin = PIN_0,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_NSS
static pinDef adc_sck	= { .port = GPIOI, .pin = PIN_1,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_SCK
static pinDef adc_sdo	= { .port = GPIOI, .pin = PIN_2,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MISO
static pinDef adc_sdi	= { .port = GPIOI, .pin = PIN_3,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MOSI

void ext_adc::init(void)
{
	// init hardware pins
	configurePin(adc_rdl);	// SPI2_NSS
	configurePin(adc_sck);	// SPI2_SCK
	configurePin(adc_sdo);	// SPI2_MISO
	configurePin(adc_sdi);	// SPI2_MOSI

	// init SPI2 port 
	SET_BIT(RCC->APB1LENR, RCC_APB1LENR_SPI2EN);		// turn on SPI2 clock (read change in RCC_C1_APB1LENR)
	SET_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);		// reset SPI2 peripheral
	CLEAR_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);	// release reset
	
	// configure SPI2 port	
	//SET_BIT(SPI2->CFG2, SPI_CFG2_LSBFRST);
	SET_BIT(SPI2->CFG2, SPI_CFG2_SSOE);				// enable SS pin output (this must be done before setting master)
	SET_BIT(SPI2->CFG2, SPI_CFG2_MASTER);				// act as SPI bus master
	MODIFY_REG(SPI2->CR2, SPI_CR2_TSIZE_Msk, 4);			// transfer four bytes
	MODIFY_REG(SPI2->CFG1, SPI_CFG1_FTHLV_Msk, 4 << SPI_CFG1_FTHLV_Pos);	// expect to receive four bytes 
	SET_BIT(SPI2->CFG2, SPI_CFG2_AFCNTR);				// keep control of SPI port GPIO pins at all times
}


void ext_adc::testSend(void)
{
	/* Notes: 
	 * 1) Transmission seems to be started when CSTART=1 and TXTF transistions from 0 to 1. Once TSIZE bytes are transmitted, CSTART
	 *    is set to zero by hardware but TXTF appears to require a manual reset. 
	 * 2) STM32 has different byte endian-ness than some devices used on the SPI line, __builtin_bswap32(32bitValue) converts between big/little
	 */
	
	SET_BIT(SPI2->CR1, SPI_CR1_SPE);				// enable the SPI port	
	SET_BIT(SPI2->CR1, SPI_CR1_CSTART);				// enable transmission
	SPI2->TXDR = 0x01020304;					// write data to TX FIFO (this makes SR.TXTF go high and starts transmission)
	while (!READ_BIT(SPI2->SR, SPI_SR_EOT)) {}			// wait for transmission to complete 
	SET_BIT(SPI2->IFCR, SPI_IFCR_TXTFC);				// clear txtf flag
	CLEAR_BIT(SPI2->CR1, SPI_CR1_SPE);				// disable the SPI port	
}

 

 

P.S.

Don't forget to turn on the clocks to the SPI ports that you're using:

// configure SPI clocks
MODIFY_REG(RCC->D2CCIP1R, RCC_D2CCIP1R_SPI123SEL_Msk, 0b001 << RCC_D2CCIP1R_SPI123SEL_Pos); // SPI1,2,3 clock
MODIFY_REG(RCC->D2CCIP1R, RCC_D2CCIP1R_SPI45SEL_Msk, 0b001 << RCC_D2CCIP1R_SPI45SEL_Pos); // SPI4,5 clock

View solution in original post

2 REPLIES 2
ews
Associate II

I attempted to re-implement my SPI2 driver using HAL code, I do see the NSS line go high but it never goes low. There is no clock or data on any of the pins.

 

#include <stm32h7xx_hal.h>
#include <stm32_hal_legacy.h>

#ifdef __cplusplus
extern "C"
#endif
void SysTick_Handler(void)
{
	HAL_IncTick();
	HAL_SYSTICK_IRQHandler();
}

uint32_t ticks;
uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 };

int main(void)
{
	HAL_Init();

	// init LED pin
	__GPIOD_CLK_ENABLE();
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.Pin = GPIO_PIN_12;
	GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);

	// init SPI2 GPIO pins, PI0=NSS, PI1=SCK, PI2=MISO, PI3=MOSI for AF5
	__GPIOI_CLK_ENABLE();
	GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; 
	GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStructure.Alternate = 5;
	HAL_GPIO_Init(GPIOI, &GPIO_InitStructure);
	
	// turn on SPI2 clock and reset it
	SET_BIT(RCC->APB1LENR, RCC_APB1LENR_SPI2EN);		// turn on SPI2 clock (read change in RCC_C1_APB1LENR)
	SET_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);		// reset SPI2 peripheral
	CLEAR_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);	// release reset
	
	// init SPI2 
	SPI_HandleTypeDef hspi2;
	hspi2.Instance = SPI2;
	hspi2.Init.Mode = SPI_MODE_MASTER;
	hspi2.Init.Direction = SPI_DIRECTION_2LINES;
	hspi2.Init.DataSize = SPI_DATASIZE_4BIT;
	hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
	hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
	hspi2.Init.NSS = SPI_NSS_HARD_OUTPUT;
	hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
	hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
	hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
	hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
	hspi2.Init.CRCPolynomial = 0x0;
	hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
	hspi2.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
	hspi2.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
	hspi2.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
	hspi2.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
	hspi2.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
	hspi2.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
	hspi2.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
	hspi2.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
	hspi2.Init.IOSwap = SPI_IO_SWAP_DISABLE;
	if (HAL_SPI_Init(&hspi2) != HAL_OK) { while (1){} }
	hspi2.Lock = HAL_UNLOCKED;
	
	ticks = HAL_GetTick();
	
	for (;;)
	{
		if (ticks - HAL_GetTick() > 500)
		{
			ticks = HAL_GetTick();
			HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
			HAL_SPI_Transmit(&hspi2, data, 4, 1000);
		}
	}
}
ews
Associate II

OK, I think I figured this one out finally. It looks like you need to enable the SPI port, set CSTART=1, and then put enough data in the Tx buffer to make SR.TXTF go high, meaning there is a full packet ready to be sent. The data goes out SPI port and SR.EOT goes high and CSTART goes low when the packet is sent. 

 

The thing that was missing for me was that SR.TXTF has to be manually set to zero by writing a 1 to the IFCR.TXTFC bit between transfers. If you don't set it to zero it doesn't transition upward the next time the Tx buffer has a full packet worth of data and the SPI data transmission never starts.

 

Anyway, here's a minimalist code snippet that sends dummy data out of SPI2. The configurePin() routines are just my own GPIO set-up utility and can be replaced with the equivalent HAL or whatever calls.

 

 

#include "../inc/ext_adc.h"

using namespace gpio;

// define GPIO pin interface to external ADC, see DS12923 Table 9 for alternate function mapping									
static pinDef adc_rdl	= { .port = GPIOI, .pin = PIN_0,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_NSS
static pinDef adc_sck	= { .port = GPIOI, .pin = PIN_1,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_SCK
static pinDef adc_sdo	= { .port = GPIOI, .pin = PIN_2,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MISO
static pinDef adc_sdi	= { .port = GPIOI, .pin = PIN_3,  .mode = Alternate, .type = PushPull, .speed = VeryHigh, .pull = None, .alternate = AF5 };	// SPI2_MOSI

void ext_adc::init(void)
{
	// init hardware pins
	configurePin(adc_rdl);	// SPI2_NSS
	configurePin(adc_sck);	// SPI2_SCK
	configurePin(adc_sdo);	// SPI2_MISO
	configurePin(adc_sdi);	// SPI2_MOSI

	// init SPI2 port 
	SET_BIT(RCC->APB1LENR, RCC_APB1LENR_SPI2EN);		// turn on SPI2 clock (read change in RCC_C1_APB1LENR)
	SET_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);		// reset SPI2 peripheral
	CLEAR_BIT(RCC->APB1LRSTR, RCC_APB1LRSTR_SPI2RST);	// release reset
	
	// configure SPI2 port	
	//SET_BIT(SPI2->CFG2, SPI_CFG2_LSBFRST);
	SET_BIT(SPI2->CFG2, SPI_CFG2_SSOE);				// enable SS pin output (this must be done before setting master)
	SET_BIT(SPI2->CFG2, SPI_CFG2_MASTER);				// act as SPI bus master
	MODIFY_REG(SPI2->CR2, SPI_CR2_TSIZE_Msk, 4);			// transfer four bytes
	MODIFY_REG(SPI2->CFG1, SPI_CFG1_FTHLV_Msk, 4 << SPI_CFG1_FTHLV_Pos);	// expect to receive four bytes 
	SET_BIT(SPI2->CFG2, SPI_CFG2_AFCNTR);				// keep control of SPI port GPIO pins at all times
}


void ext_adc::testSend(void)
{
	/* Notes: 
	 * 1) Transmission seems to be started when CSTART=1 and TXTF transistions from 0 to 1. Once TSIZE bytes are transmitted, CSTART
	 *    is set to zero by hardware but TXTF appears to require a manual reset. 
	 * 2) STM32 has different byte endian-ness than some devices used on the SPI line, __builtin_bswap32(32bitValue) converts between big/little
	 */
	
	SET_BIT(SPI2->CR1, SPI_CR1_SPE);				// enable the SPI port	
	SET_BIT(SPI2->CR1, SPI_CR1_CSTART);				// enable transmission
	SPI2->TXDR = 0x01020304;					// write data to TX FIFO (this makes SR.TXTF go high and starts transmission)
	while (!READ_BIT(SPI2->SR, SPI_SR_EOT)) {}			// wait for transmission to complete 
	SET_BIT(SPI2->IFCR, SPI_IFCR_TXTFC);				// clear txtf flag
	CLEAR_BIT(SPI2->CR1, SPI_CR1_SPE);				// disable the SPI port	
}

 

 

P.S.

Don't forget to turn on the clocks to the SPI ports that you're using:

// configure SPI clocks
MODIFY_REG(RCC->D2CCIP1R, RCC_D2CCIP1R_SPI123SEL_Msk, 0b001 << RCC_D2CCIP1R_SPI123SEL_Pos); // SPI1,2,3 clock
MODIFY_REG(RCC->D2CCIP1R, RCC_D2CCIP1R_SPI45SEL_Msk, 0b001 << RCC_D2CCIP1R_SPI45SEL_Pos); // SPI4,5 clock