What does a STM32F4 do with its SPI clock output between transactions? - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 03:41:40 pm
content: Does it go tri-state?Is this even configurable?My Master mode only SPI3 seems to have a pulldown on CLK, and is otherwise tri-state.This is my SPI3 init codeCode:#define STLED316S_SPI_SCK_PIN GPIO_PIN_10 /* PC.10 */#define STLED316S_SPI_SCK_GPIO_PORT GPIOC /* GPIOC */#define STLED316S_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOC#define STLED316S_SPI_SCK_SOURCE GPIO_PinSource10#define STLED316S_SPI_SCK_AF GPIO_AF6_SPI3#define STLED316S_SPI_MISO_PIN GPIO_PIN_11 /* PC.11 */#define STLED316S_SPI_MISO_GPIO_PORT GPIOC /* GPIOC */#define STLED316S_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOC#define STLED316S_SPI_MISO_SOURCE GPIO_PinSource11#define STLED316S_SPI_MISO_AF GPIO_AF6_SPI3#define STLED316S_SPI_MOSI_PIN GPIO_PIN_12 /* PC.12 */#define STLED316S_SPI_MOSI_GPIO_PORT GPIOC /* GPIOC */#define STLED316S_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOC#define STLED316S_SPI_MOSI_SOURCE GPIO_PinSource12#define STLED316S_SPI_MOSI_AF GPIO_AF6_SPI3void Init_SPI3_Pins(void){__HAL_RCC_SPI3_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOE_CLK_ENABLE();GPIO_InitTypeDef GPIO_InitStruct;// Configure SPI pins. The same SPI is now used for the STLED316S, both ADS1118 and the MCP3550 etcGPIO_InitStruct.Pin = STLED316S_SPI_SCK_PIN | STLED316S_SPI_MOSI_PIN;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;HAL_GPIO_Init(STLED316S_SPI_SCK_GPIO_PORT, &GPIO_InitStruct);GPIO_InitStruct.Pin = STLED316S_SPI_MISO_PIN;GPIO_InitStruct.Pull = GPIO_PULLDOWN;HAL_GPIO_Init(STLED316S_SPI_SCK_GPIO_PORT, &GPIO_InitStruct);This also refershttps://www.eevblog.com/forum/projects/sc18is604-sclk-input-does-it-have-a-resistor-network-on-it/

author: nctnico
author_rank: Super Contributor
timestamp: October 10, 2025, 03:46:58 pm
content: SPI clock stays in the state where it was to make the final clock transition. If it didn't stay at the same level, it could produce extra clock cycles on the SPI bus. You'd have to check the manual but typically GPIO related I/O settings are ignored once a pin is assigned to a digital peripheral.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 10, 2025, 03:56:44 pm
content: No, the SPI clock out doesn't go tri-state unless you configure a multimaster mode.After a completed transmission, the clock output will be at its idle state (0 if CPOL = 0, 1 if CPOL = 1).

author: Pseudobyte
author_rank: Frequent Contributor
timestamp: October 10, 2025, 03:58:16 pm
content: Quote from: nctnico on October 10, 2025, 03:46:58 pmI'm pretty sure the clock is driven to whatever CPOL is configured as. 0-> LOW, 1 -> HIGHI suppose though that is the same as what you said nico, just in a different way.I am not sure if the pullup/pulldowns settings are ignored when the alternate function is selected.I can say for sure the pullups/pulldown settings are not ignored.

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 04:22:36 pm
content: What I see is this2V/div, 5us/div.Yes this could cause problems, depending on when /CS goes down and up.Connecting a 50k pulldown to GND lowers the "quiescent" level by about 10%, suggesting that that SC18IS604 chip has an internal pullup of about 5k, which is completely ridiculous.https://www.nxp.com/docs/en/data-sheet/SC18IS604.pdf

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 10, 2025, 04:27:08 pm
content: Never seen that. May be indicative of multimaster mode being enabled.

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 04:55:29 pm
content: All my SPI peripherals are initialised withCode:case SPI_MODE_ILI9488:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;// 4: 10.5MHz - max 15MHzm_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = ILI9488_SPI;break;HAL_SPI_Init(&m_spi);Stepping through the code, SPI CR1 is 0000001100001100 which looks rightHowever, I disable SPI after each transferCode:// SPE=0 disable SPISPI3->CR1 &= ~SPI_CR1_SPE;so maybe the output goes tri-state then? I found I had to do this to enable the SPI to be shared among various devices with different clock rates. I also find that MISO behaves similarly, which tends to confirm this.The best way will be some pulldown on the clock...Google agrees:Yes, the STM32F4 SPI clock output (SCK) goes to a high-impedance (tri-state) state when SPI is disabled by clearing the SPE bit in the SPIx_CR1 register. This is the intended behavior, which prevents the SCK line from being held in an active state and potentially interfering with other bus operations while the peripheral is not in use.Fortunately I always set /CS=1 before disabling the SPI.More tests show there is a 50k pullup inside that weird chip. First time I've seen that. It also looks like somewhere on the PCB I have a 50k pulldown to GND.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 10, 2025, 05:56:16 pm
content: Quote from: peter-h on October 10, 2025, 04:55:29 pmAh. That was the key information. May we ask why you do this?

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 06:00:52 pm
content: I found very difficult problems when reconfiguring the SPI clock speeds. I am sharing the SPI between slaves running at 650kHz to 21MHz.A 4k7 pulldown does the job nicely.

author: voltsandjolts
author_rank: Unknown
timestamp: October 10, 2025, 06:10:43 pm
content: Set the gpio pin low?In your scope photo above, why the 70% voltage at the start?

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 10, 2025, 06:36:24 pm
content: Quote from: peter-h on October 10, 2025, 06:00:52 pmSo I guess you had to disable it and reenable it to reconfigure it? Which isn't too surprising.One solution if you didn't want to make any hardware change is to configure the corresponding pin as GPIO with a low level before disabling SPI, and reconfigure the pin as alternate after re-enabling SPI.

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 06:52:18 pm
content: QuoteI did wonder. Can the CLK output be configured with SPI disabled?QuoteBecause that chip has a permanent ~50k pullup inside it. And every SPI op I do (note that the SPI is also mutexed) disables SPI at the end.QuoteExactly.

author: uer166
author_rank: Super Contributor
timestamp: October 10, 2025, 06:56:36 pm
content: I had the exact same issue; just reconfigure the I/O as an output before disabling SPI, and reverse the order when re-enabling SPI.

author: peter-h
author_rank: Super Contributor
timestamp: October 10, 2025, 07:03:11 pm
content: But that implies that you could set the GPIO with a pulldown, for example, when the SPI is originally initialised. That should not work but I can easily test it.Funny thing, I see this very old code for SPI2 (which drives just one device)Code:GPIO_InitTypeDef GPIO_InitStruct = {0};/* Peripheral clock enable */__HAL_RCC_SPI2_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**SPI2 GPIO ConfigurationPC2 ------> SPI2_MISOPC3 ------> SPI2_MOSIPB10 ------> SPI2_SCK*/GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;B_HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);which suggests that you can choose the output pin mode for CLK.Well, you can't. This code leaves SPI3 CLK floatingCode:GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLDOWN;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);I guess AF6 disablesGPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLDOWN;

author: cv007
author_rank: Frequent Contributor
timestamp: October 10, 2025, 10:25:43 pm
content: The gpio alternate function diagram kind of glosses over the fact that there is an output enable 'switch' in the output control (shown as a box inside the output driver, and presumably is inside this box). The peripheral must control this output enable 'switch' when in alternate mode, as there are certainly alternate pins that are used as inputs. So by turning off a peripheral, that peripheral 'could' choose to turn off the output enable for one of its pins that is an output.But, the pullup/pulldown are not controlled outside of gpio, and the only pin mode that disables them is the analog mode. This setting is 'independent' of anything else, and unless in analog mode you should be able to control pullup/down/nopull regardless of whether a peripheral is in control or not. The pin in alternate mode simply has the peripheral controlling the output driver enable and the output value, where the pin input/output modes have the gpio controlling these.sck pin to gpio output mode,highlow (idle value)spi setup as needed, onset sck pin to alternate modespi transactionsck pin to gpio output modespi off

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 10, 2025, 11:17:08 pm
content: Peter, that's not what uer166 and I suggested.Do not use the internal pull-down, it's useless in this case. Reconfigure the pin as a digital output using GPIO_MODE_OUTPUT_PP.Set the digital output to 0 beforehand. You only need to do it once.Then, to reconfigure the pin for SPI, just switch back the mode to GPIO_MODE_AF_PP.

author: cv007
author_rank: Frequent Contributor
timestamp: October 11, 2025, 01:14:16 am
content: Also note that the mode register is a shared register, so if for example you have isr code that is shutting off spi (and changing pin mode to output instead of alternate) you will either have to know there is not other interrupt code at a different irq priority doing something similar on the same port (changing a pin's mode) or you simply have to protect the rmw of the mode register which the hal gpio code from st does not do.QuoteYou could simply do nothing if the floating (+device pullup) is harmless. Just check if sck is idle after enabling spi but before enabling /CS. Maybe that is not even necessary.I gather there is no real problem at hand, except to understand why the spi peripheral is changing the sck pin from output enable to output disable when spi is disabled.

author: peter-h
author_rank: Super Contributor
timestamp: October 11, 2025, 08:06:54 am
content: The problem is that I haveloadsof places where I am doing SPI transfers and thus loads of places where the SPI is disabled after a transfer.In most cases I use DMA. In other cases I use programmed I/O. I am interfacing to e.g. several different ADCs, with the ADS1118 being polled as it was an absolute bugger to get it going, MCP3550, ~80ms conversion time waits on some of these, LCDs (they use DMA), 7-seg LEDs (ST316), etc. The SPI is switched to run all these under the RTOS, with the correct mutexing etc. There is no way I am going to unravel that lotMonths and months were spent on making it all rock solid.I will just put in a 4k7 from CLK to GNDIt is an issue only with that SPI to I2C converter chip which has ~90uA pullups on clock and data in.The whole product works anyway, presumably because no clock transitions (or something which might look like a +ve edge) occur before /CS goes back to a 1. But I would prefer to deal with this, in the (rare) version using that chip, which is used only with a touch screen LCDhttps://www.eevblog.com/forum/microcontrollers/looking-for-some-simple-tips-on-resistive-touch-panel-on-an-lcd/msg6042471/#msg6042471If the pulldown could be always enabled, that would be better than nothing, but the 32F4 pulldowns are about 40k which is not enough to deal with the 90uA "pullup".Another approach would be a little SOT23 buffer on the clock going into that chip.A pullup on clock is a stupid idea. If anything, there should be a pulldown.QuoteSure

author: uer166
author_rank: Super Contributor
timestamp: October 11, 2025, 08:52:36 am
content: Does the SPI peripheral self-disable? Surely you have some kind of SPI_DMA_Rx thing or what-have you where you actually disable the peripheral.You only need to put the reconfiguring to output set to low in there.. Unless you have absolute spaghetti code that calls the SpiDisable/Enable loads of times. Even then, just a search and replace with a wrapper is easy enough.

author: peter-h
author_rank: Super Contributor
timestamp: October 11, 2025, 01:59:48 pm
content: Sure; anything is possible, but this would be bad risk management, given the near impossibility of retesting everything.As an example, the DMA code isCode:/**** For use where fast transfers are needed, on the limit of SPI3 speed so with zero gaps. This is impossible* to do by polling at 10.5mbps or 21mbps and is probably marginal at 5.25mbps. The 16 bit SPI mode just* manages gap-free with polling but works only with even block sizes, and has the "first byte problem" which* DMA gets around.** ** DMA ONLY SO NO CCM ACCESS SO THE TWO BUFFERS HAVE TO BE "STATIC" **** This function is blocking so the caller can set CS=1 right away (check device data sheet!). A non-blocking* version would make sense only if transmitting only (txonly=true) but the caller would have to tidy up* the DMA and SPI3->CR2.*** Two modes, obviously mutually exclusive, for tx-only and rx-only:* If txonly, dumps rx data so you don't need to allocate a buffer for it* If rxonly, transmits all-0x00 so you don't need to feed SPI with some "known garbage"** The rx-only mode is superfluous in most cases but it does avoid shifting non-0x00 data to the device* while we are reading data out of it. With some devices this can matter. The ADS1118 ADC is one such.** Because this function needs to work with XXX_spi3_set_mode(), care is taken to not modify the SPI config.** The yield parameter just releases control to RTOS, so equal-priority tasks don't have to wait until DMA* finished. This might be useful for long/slow DMA transfers. Usually not useful.* 31/8/22 if true, worsens corruption of incoming USB VCP data, a lot more than it is corrupted* anyway!** Returns (false) if memory is in CCM, size=0, null pointers...** NULL pointers are allowed if using the txonly or rxonly modes; then the unused one can be NULL.**/bool SPI3_DMA_TransmitReceive(uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, bool txonly, bool rxonly, bool yield){// Check for invalid inputsif ( Size==0 ) return (false);if ( (pTxData==NULL) && !rxonly ) return (false);if ( (pRxData==NULL) && !txonly ) return (false);uint32_t txadd = (uint32_t) pTxData;uint32_t rxadd = (uint32_t) pRxData;static uint8_t txonly_target=0;// must not be in CCMstatic uint8_t rxonly_source=0;// must not be in CCM// Test for CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K (linkerscript.ld)if ( ((txadd>=0x10000000) && (txadd<0x10010000)) || ((rxadd>=0x10000000) && (rxadd<0x10010000)) ){return(false);}// DMA ch 1 clock enable already done in b_main.c//RCC->AHB1ENR |= (1u << 21);// DMA1EN=1 - DMA1 clock enable//hang_around_us(1);// give it a chance to wake upSPI3->CR1 &= ~SPI_CR1_SPE;// disable SPI// DMA1 Ch 0 Stream 0 is SPI3 RXDMA1_Stream0->CR = 0;// disable DMA so all regs can be written// Wait until DMA clears the EN bit by itself.// Should be pointless since the previous transfer waited for end of transfer (hardware bug)//while ( (DMA1_Stream0->CR & DMA_SxCR_EN) != 0) {};DMA1->LIFCR = (0x03d << 0);// clear int flags & transfer complete - 111101 stream 0DMA1_Stream0->NDTR = Size;if (txonly){DMA1_Stream0->M0AR = (uint32_t) &txonly_target;// memory address to dump rx data to}else{DMA1_Stream0->M0AR = rxadd;// memory address in normal mode}DMA1_Stream0->PAR = (uint32_t) &(SPI3->DR);// peripheral addressDMA1_Stream0->FCR = 0;// direct modeif (txonly){DMA1_Stream0->CR = 0 << 25// CHSEL: ch 0| 0 << 23// MBURST: memory burst - single transfer| 0 << 21// PBURST: peripheral burst - single transfer| 3 << 16// PL: highest priority| 0 << 15// PINCOS: no peripheral address increment offset| 0 << 13// MSIZE: memory data size: byte| 0 << 11// PSIZE: peripheral data size: byte| 0 << 10// MINC: memory address increment: 0| 0 << 9// PINC: peripheral address increment: 0| 0 << 8// CIRC: no circular mode| 0 << 6// DIR: peripheral to memory| 0 << 5// PFCTRL: DMA is flow controller| 1 << 0;// EN: enable stream}else{DMA1_Stream0->CR = 0 << 25// CHSEL: ch 0| 0 << 23// MBURST: memory burst - single transfer| 0 << 21// PBURST: peripheral burst - single transfer| 3 << 16// PL: highest priority| 0 << 15// PINCOS: no peripheral address increment offset| 0 << 13// MSIZE: memory data size: byte| 0 << 11// PSIZE: peripheral data size: byte| 1 << 10// MINC: memory address increment: 1| 0 << 9// PINC: peripheral address increment: 0| 0 << 8// CIRC: no circular mode| 0 << 6// DIR: peripheral to memory| 0 << 5// PFCTRL: DMA is flow controller| 1 << 0;// EN: enable stream}// DMA1 Ch 0 Stream 7 is SPI3 TXDMA1_Stream7->CR = 0;// disable DMA so all regs can be written// Wait until DMA clears the EN bit by itself.// Should be pointless since the previous transfer waited for end of transfer (hardware bug)//while ( (DMA1_Stream7->CR & DMA_SxCR_EN) != 0) {};DMA1->HIFCR = (0x03d << 22);// clear int flags & transfer complete - 111101 stream 7DMA1_Stream7->NDTR = Size;if (rxonly){DMA1_Stream7->M0AR = (uint32_t) &rxonly_source;// memory address to fetch dummy tx data from}else{DMA1_Stream7->M0AR = txadd;// memory address in normal mode}DMA1_Stream7->PAR = (uint32_t) &(SPI3->DR);// peripheral addressDMA1_Stream7->FCR = 0;// direct modeif (rxonly){DMA1_Stream7->CR = 0 << 25// CHSEL: ch 0| 0 << 23// MBURST: memory burst - single transfer| 0 << 21// PBURST: peripheral burst - single transfer| 0 << 16// PL: priority low| 0 << 15// PINCOS: no peripheral address increment offset| 0 << 13// MSIZE: memory data size: byte| 0 << 11// PSIZE: peripheral data size: byte| 0 << 10// MINC: memory address increment: 0| 0 << 9// PINC: peripheral address increment: 0| 0 << 8// CIRC: no circular mode| 1 << 6// DIR: memory to peripheral| 0 << 5// PFCTRL: DMA is flow controller| 1 << 0;// EN: enable stream}else{DMA1_Stream7->CR = 0 << 25// CHSEL: ch 0| 0 << 23// MBURST: memory burst - single transfer| 0 << 21// PBURST: peripheral burst - single transfer| 0 << 16// PL: priority low| 0 << 15// PINCOS: no peripheral address increment offset| 0 << 13// MSIZE: memory data size: byte| 0 << 11// PSIZE: peripheral data size: byte| 1 << 10// MINC: memory address increment: 1| 0 << 9// PINC: peripheral address increment: 0| 0 << 8// CIRC: no circular mode| 1 << 6// DIR: memory to peripheral| 0 << 5// PFCTRL: DMA is flow controller| 1 << 0;// EN: enable stream}// Config SPI3 to let DMA handle the data. These need to be cleared when transfer complete!// This starts the transferSPI3->CR2 |= 3;// TXDMAEN, RXDMAEN: 11 - both set in one goSPI3->CR1 |= (1<<6);// SPE=1 enable SPI// Wait for DMA to finish. Blocking is necessary to prevent device CS=1 too early.// There could be a timeout here but a failure is impossible short of duff silicon, because// we are a Master and generating the SPI clock.// 20/11/22: A primitive timeout has been implemented anyway.volatile uint32_t limit = 0;while(true){limit--;if (limit==0) break;// Either end-transfer detection method below works but the NDTR may be less reliable// http://efton.sk/STM32/gotcha/g20.html// https://www.eevblog.com/forum/microcontrollers/32f4-3-ways-to-detect-end-of-dma-transfer/#if 0uint16_t temp1;temp1 = DMA1_Stream0->NDTR;if ( temp1 == 0 ) break;// transfer count = 0#elseuint32_t temp2;temp2 = DMA1->LISR;if ( (temp2 & (1<<5)) !=0 ) break;// TCIF0#endifif (yield) osDelay(1);// release to RTOS (see notes in comments)// Third method of detecting end of transfer: wait for DMA to set EN to 0.// All 3 methods produce the same time from last SPI clock to CS=1 (1.8us).//if ( (DMA1_Stream0->CR & DMA_SxCR_EN) == 0) break;}SPI3->CR2 &= ~3;// TXDMAEN, RXDMAEN: 00 - both cleared in one go// Clear int pending flags. They get cleared at the top of this function anyway, but...DMA1->LIFCR = (0x03d << 0);// clear int flags & transfer complete - 111101 stream 0DMA1->HIFCR = (0x03d << 22);// clear int flags & transfer complete - 111101 stream 7// Clear any rx data and the overrun flag in case not all received data was read// A small delay is needed because the very last transfer may not propagate to DR.// Not needed because RX DMA has read out everythinghang_around_us(1);SPI3->DR;SPI3->DR;SPI3->SR;SPI3->CR1 &= ~(1<<6);// SPE=0 disable SPIreturn (true);}and the /CS=1 is done right after calling it. Obviously the /CS=1 could get delayed by e.g. RTOS switching, but the SPI itself is mutexed so no other device will be getting its /CS=0. The system does rely on no spurious clock edges before /CS=1, inevitably, and you could get an RTOS switch during that time, so an extension of /C=0 of a few ms maybe. If one had a seriously floating clock emitting garbage edges, and was setting the CLK pin to 0 as suggested, right after SPI is disabled, one would need to disable interrupts around that bit of code. So I think it is better to not have crap on the clock in the first place.

author: DavidAlfa
author_rank: Super Contributor
timestamp: October 11, 2025, 02:45:44 pm
content: Quote from: peter-h on October 10, 2025, 06:00:52 pmWas it that hard? Just disable the spi peripheral before changing the speed, re-enable afterwards.Enable the pull-up/down resistor in PUPDR reg to avoid unintended noise.Code:/* Choose: SPI_BAUDRATEPRESCALER_2SPI_BAUDRATEPRESCALER_4SPI_BAUDRATEPRESCALER_8SPI_BAUDRATEPRESCALER_16SPI_BAUDRATEPRESCALER_32SPI_BAUDRATEPRESCALER_64SPI_BAUDRATEPRESCALER_128SPI_BAUDRATEPRESCALER_256*/void set_SPI_prescaler(SPI_TypeDef * spi, uint8_t speed);CLEAR_BIT(spi->CR1 , SPI_CR1_SPE);MODIFY_REG(spi->CR1, SPI_CR1_BR_Msk, speed);SET_BIT(spi->CR1 , SPI_CR1_SPE);}Or:Code:/* Choose: 1...256 in pow(2) steps */void set_SPI_prescaler(SPI_TypeDef * spi, uint8_t speed);CLEAR_BIT(spi->CR1 , SPI_CR1_SPE);MODIFY_REG(spi->CR1, SPI_CR1_BR_Msk, (speed<<SPI_CR1_BR_Pos));SET_BIT(spi->CR1 , SPI_CR1_SPE);}

author: peter-h
author_rank: Super Contributor
timestamp: October 11, 2025, 03:18:48 pm
content: QuoteYes that is what happens.The problem is that when you disable it, the clock and MISO start floating.

author: DavidAlfa
author_rank: Super Contributor
timestamp: October 11, 2025, 04:15:44 pm
content: Disabling the peripheral will make the pin float, but the GPIO is still fully functional, so just set PUPDR reg (Pull resistor).Disabling the SPI after each function only wastes time, instead, call setSPI(speed) at the beginning of every function.Use a local variable to store the current speed, so it only sets the speed when required, avoiding unnecessary overhead.Code:static uint16_t curr_spi_speed = 0xFFFF; // Preload non-existing prescaler to force update on 1st callvoid set_spi_speed(uint16_t speed){if(curr_spi_speed!=speed){curr_spi_speed=speed;...}}

author: peter-h
author_rank: Super Contributor
timestamp: October 11, 2025, 04:42:40 pm
content: QuoteI tried that but it doesn't appear to work.Code:// Select a pulldown on the clock pin. See// https://www.eevblog.com/forum/microcontrollers/what-does-a-stm32f4-do-with-its-spi-clock-output-between-transactions/// Does not work; you have to use a 4k7 to GND if driving the SC18IS604.GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLDOWN;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);I need the pulldown on PC10 and ideally also PC11 and PC12.PUPDR:So I should do a RMW on bit pairs 20,21 22,23 24,25 and set each bit pair to 10?This is so funny... I found thisI enable a pullup on SPI3-DI to detect if a chip is present.Code:// Enable pullup on PC11 if temp=1 to detect non-installed analog optiontmp = GPIOC->PUPDR;tmp &= ~(3 << 22);// zero both PUPDR11 bitstmp |= (1 << 22);// set them to 01 (pullup)GPIOC->PUPDR = tmp;}This WORKS:Code:// Select a pulldown on the clock pin PC10 and the DO pin PC12. See// https://www.eevblog.com/forum/microcontrollers/what-does-a-stm32f4-do-with-its-spi-clock-output-between-transactions/// Does not work if driving the SC18IS604 SPI to I2C converter which has a 90uA pullup; then you need a 4k7// on CLK to GNDuint32_t tmp = GPIOC->PUPDR;tmp &= ~(3 << 20);// zero both PUPDR10 bitstmp |= (2 << 20);// set them to 10 (pulldown)tmp &= ~(3 << 24);// zero both PUPDR12 bitstmp |= (2 << 24);// set them to 10 (pulldown)GPIOC->PUPDR = tmp;As a slight aside, what would an SPI slave do if it saw an extra clock edge and then /CS went to 1? It is hardly going to start a new transaction, but you never know...

author: Siwastaja
author_rank: Super Contributor
timestamp: October 11, 2025, 06:32:59 pm
content: Quote from: DavidAlfa on October 11, 2025, 04:15:44 pmAre you sure this is a sensible optimization? I mean, it may (or may not) improve the average call overhead a little bit, but those calls that do not change the speed still need to check against the variable and return, which is probably only a few clock cycles faster than always writing to the peripheral register; but worst-case performance gets worse, since those writes thatdoneed to happen now also have to check, and also update the static variable, in addition to writing to the peripheral register. Plus you add uncertainty / jitter.99% like this doesn't matter either way, but just asking because you are suggesting a premature micro-optimization. Instead, KISS.

author: peter-h
author_rank: Super Contributor
timestamp: October 11, 2025, 07:36:19 pm
content: At the risk of taking this more off topic, this is an example my SPI3 sharing codeCode:// As above but using DMA, and only for DATA so sets D/C=1.// Max size is 64k.void LCD_Transmit_buf_DAT(uint8_t *outbuf, uint32_t size){if (size==0) return;#ifdef LCD_CONTROLLER_ST7789{SPI3_Lock();// If current SPI3 init is not this device, initialise itif ( g_spi3_current_config != XXX_SPI_MODE_ST7789 ){XXX_spi3_set_mode(XXX_SPI_MODE_ST7789);g_spi3_current_config = XXX_SPI_MODE_ST7789;}}#endif#ifdef LCD_CONTROLLER_ILI9488{SPI3_Lock();// If current SPI3 init is not this device, initialise itif ( g_spi3_current_config != XXX_SPI_MODE_ILI9488 ){XXX_spi3_set_mode(XXX_SPI_MODE_ILI9488);g_spi3_current_config = XXX_SPI_MODE_ILI9488;}}#endif// Set a fixed DAT value on D/CHAL_GPIO_WritePin(LCD_DC_GPIO_PORT, LCD_DC_PIN, GPIO_PIN_SET);// Send out the blockxxx_lcd_cs(0);SPI3_DMA_TransmitReceive(outbuf, NULL, (uint16_t)size, true, false, false);xxx_lcd_cs(1);SPI3_Unlock();}I compare current SPI3 config with the desired one and if different, I re-initialise the SPI.The SPI init function is this. One has to change more than just the speedCode:/*** @brief Set the mode of the SPI for the required peripheral*This must be called before using each peripheral in turn* @param mode: XXX_SPI_MODE_STLED316, XXX_SPI_MODE_ADS1118 or XXX_SPI_MODE_MCP3550 or XXX_SPI_MODE_HI3593 etc**** SPI3 runs off APB1 which is 42MHz.** This function works regardless of whether the SPI transfer is done with HAL_SPI_TransmitReceive() or with* SPI3_DMA_TransmitReceive().**/void XXX_spi3_set_mode(uint8_t mode){// SPI configurationswitch (mode) {case XXX_SPI_MODE_STLED316:// options *** below work but should notm_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;//m_spi.Init.CLKPolarity = SPI_POLARITY_HIGH;// ****m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;// ***//m_spi.Init.CLKPhase = SPI_PHASE_2EDGE;// ****m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;// ***m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;// 650kHz (max 1MHz)m_spi.Init.FirstBit = SPI_FIRSTBIT_LSB;// LSB first - unusualm_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = STLED316S_SPI;break;case XXX_SPI_MODE_ADS1118:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_16BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;// correct per data sheetm_spi.Init.CLKPhase = SPI_PHASE_2EDGE;// likewise - sample on -ve ck edgem_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;// 2.6MHz (max 4MHz for ADS1118)m_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = ADS1118_SPI;break;case XXX_SPI_MODE_MCP3550:m_spi.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;//m_spi.Init.CLKPolarity = SPI_POLARITY_HIGH;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;// SPI mode 0,0, uses 4 bytes//m_spi.Init.CLKPhase = SPI_PHASE_2EDGE;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;// 2.6MHz (max 5MHz for MCP3550)m_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = MCP3550_SPI;break;case XXX_SPI_MODE_HI3593:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;// This is actually correct per data sheetm_spi.Init.CLKPhase = SPI_PHASE_1EDGE;// and so is thism_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;// 5.2MHz (max 10MHz for HI3593)//m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;// 10.5MHz (max 10MHz for HI3593)m_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = HI3593_SPI;break;case XXX_SPI_MODE_NEO_M9N:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;// 64: 650kHz (max 1MHz for 125kbyte/sec max data rate)m_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = NEO_M9N_SPI;break;case XXX_SPI_MODE_LY68L6400:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;// 2: 21MHz - max possible on XXXm_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = LY68L6400_SPI;break;case XXX_SPI_MODE_ST7789:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;// 2: 21MHz - max possible on XXXm_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = ST7789_SPI;break;case XXX_SPI_MODE_ILI9488:m_spi.Init.Direction = SPI_DIRECTION_2LINES;m_spi.Init.DataSize = SPI_DATASIZE_8BIT;m_spi.Init.CLKPolarity = SPI_POLARITY_LOW;m_spi.Init.CLKPhase = SPI_PHASE_1EDGE;m_spi.Init.NSS = SPI_NSS_SOFT;m_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;// 4: 10.5MHz - max 15MHzm_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;m_spi.Init.CRCPolynomial = 7;m_spi.Init.Mode = SPI_MODE_MASTER;m_spi.Instance = ILI9488_SPI;break;default:break;}HAL_SPI_Init(&m_spi);// Select a pulldown on the clock pin PC10 and the DO pin PC12. See// https://www.eevblog.com/forum/microcontrollers/what-does-a-stm32f4-do-with-its-spi-clock-output-between-transactions/// Does not work if driving the SC18IS604 SPI to I2C converter which has a 90uA pullup; then you need a 4k7// on CLK to GNDuint32_t tmp = GPIOC->PUPDR;tmp &= ~(3 << 20);// zero both PUPDR10 bitstmp |= (2 << 20);// set them to 10 (pulldown)tmp &= ~(3 << 24);// zero both PUPDR12 bitstmp |= (2 << 24);// set them to 10 (pulldown)GPIOC->PUPDR = tmp;}

author: DavidAlfa
author_rank: Super Contributor
timestamp: October 11, 2025, 10:25:41 pm
content: Quote from: Siwastaja on October 11, 2025, 06:32:59 pmYeah, maybe the gain is marginal.I think jitter is out of context here, it's just a SPI transfer, few us sooner or later won't change anything?

author: peter-h
author_rank: Super Contributor
timestamp: October 12, 2025, 08:11:47 am
content: Well this has been a great learning experienceNot only on the SPI going floating when disabled, but also on how to enable a pulldown on PC10 and PC12, which cannot be done with ST's functions once the pins have been set to SPI mode. This needs a direct write to PUPDR.However, the SC18IS604 90uA pullup is still too strong and a fix is needed on its PCB, and I will probably just put in a 74HC buffer.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: October 12, 2025, 09:21:50 am
content: Quote from: peter-h on October 12, 2025, 08:11:47 amNo. Not really (probably). If you disable the SPI, then the pin just goes back to whatever way it was configured before. I.e: If you first set it to an active low output, then enable SPI to send data and then disable SPI, it's probably an active low output again. I think that floating inputs is the default state of (most?) STM32 chips. STM cube has some functions to set "all unused" pins to some defined state. (enables the pullup resistors? But have a look at the generated code to be sure what it does.Quote from: peter-h on October 12, 2025, 08:11:47 amOne of the things I've learned it to double and triple check assumptions. Don't just assume that the pin will always go to High-Z just because you saw this happen, but ask yourself why it goes to High-Z, and what factors could influence it. But it's not always easy. Even recognizing that you made assumptions can be the hardest part.

author: peter-h
author_rank: Super Contributor
timestamp: October 12, 2025, 09:29:25 am
content: QuoteI will test that and report. That would be amazing. Config it for PP and 0, then config it for SPI. Then when you disable SPI, it should go straight back to 0. I'd be damned if that works!!

author: peter-h
author_rank: Super Contributor
timestamp: October 12, 2025, 05:19:50 pm
content: It doesn't seem to work. The pullup/pulldown does work though, as mentioned above.The AF mode selection (needed for SPI to come out on the pins) overrides the normal GPIO mode selection.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 12, 2025, 05:35:07 pm
content: Yes you need to switch the mode to normal OUTPUT PP, otherwise it remains connected to the SPI peripheral and they decided to put the SCK (and likely MOSI as well) pin in Hi-Z when you disable the peripheral, which is likely on purpose to enable multimaster communication.Come to think of it, it may well be a general behavior of most peripherals. You can try with one UART for instance. What happens to the TX pin if you disable the UART? I seem to remember that it would also put in in Hi-Z.

author: DavidAlfa
author_rank: Super Contributor
timestamp: October 12, 2025, 05:45:11 pm
content: Toggle the output latch low with BSRR, then set the MODER reg to output PP, now the pin is permanently low.You might find this useful:https://www.eevblog.com/forum/microcontrollers/macro-for-efficient-stm32-gpio-handling/Code:// Only at the init. The latch wont change afterwards, so you only need to toogle between AF and OUPUT modes.HAL_setPinLow(SCK);HAL_setPinOtype(SCK, OUTPUT_PP);// When reconfiguring the spi periph.HAL_setPinMode(SCK, MODE_OUTPUT); // SCK pin as gpio output... (reconfig spi clock)HAL_setPinMode(SCK, MODE_AF); // restore SCK function

author: ejeffrey
author_rank: Super Contributor
timestamp: October 12, 2025, 06:48:17 pm
content: Quote from: peter-h on October 10, 2025, 04:22:36 pmI had this problem with the kinetis "L" chips, their cortex M0+ chips. The problem was that the NXP provided SDK driver was turning the peripheral off entirely after a transaction. Once I ditched the provided driver and wrote my own the problem went away.

author: peter-h
author_rank: Super Contributor
timestamp: October 12, 2025, 07:56:45 pm
content: I have to disable SPI3 before the reconfigs (the reasons will never be proved) but my SPI2, which is used for an Adesto 4MB FLASH chip only, remains enabled.Maybe some HAL SPI functions do funny stuff but I don't use HAL code for SPI except for most of the config. Data transfers are done with register access.

author: cv007
author_rank: Frequent Contributor
timestamp: October 12, 2025, 08:27:29 pm
content: QuoteIn the case of the uart, TE controls the tx pin output enable- TE=0 gets you a floating tx pin as output enable is disabled, unless you set the pullup (which is a good idea for both tx and rx pins). The UE bit has no effect. The uart also has the ability to swap tx/rx pins.Each peripheral has 2 controls to the gpio pin- output enable and output value. When the gpio mode is set to alternate for a pin, the peripheral has control of these two lines, and all the other gpio pin functionality (pull, output drive mode, speed) is still only in the gpio peripheral. Reading of the pin is always also available in the gpio peripheral, unless set to analog mode. Each peripheral handles enabling the output driver via one of its register bits, and I guess it would take a little poking around the register bits to see which one (could be peripheral enable, or tx enable for uart, etc.).

author: nctnico
author_rank: Super Contributor
timestamp: October 13, 2025, 09:12:08 am
content: Quote from: SiliconWizard on October 12, 2025, 05:35:07 pmIt is probably worth checking what the HAL code does to de-initialiase a pin. It might set the pin to GPIO input despite being configured as a GPIO output earlier on.

author: peter-h
author_rank: Super Contributor
timestamp: October 13, 2025, 10:20:34 am
content: You can set a pin to PP mode, say 0 output, then set it to AF for SPI, which removes the aforementioned setting, and when later the SPI is disabled, the SPI hardware makes it go hi-Z, by turning off both the MOSFETs.The problem is that all the time SPI isselected in AF, the output data register is removed from the picture. The only thing that remains from the original config of an output pin is a pullup or a pulldown, and the pin "speed" (actually this just changes the RDSon of the two MOSFETs shown).The RM states this too, though interestingly you could select not just PP but also open drain for the AF (might be handy for something)It makes it even more clear that once AF is selected, the output pin register value is out of the picture.

author: cv007
author_rank: Frequent Contributor
timestamp: October 13, 2025, 06:50:11 pm
content: QuoteThe analog and input diagrams show the output enable 'switch' (not shown in the other diagrams), which is controlled by the gpio mode unless in alternate mode where the peripheral has control of this 'switch' to allow it to choose whether a pin is an input or an output. The push-pull or open drain setting in gpio still applies in alternate mode, and although the gpio does not control the output state anymore the peripheral does, but if the peripheral disables the output enable you will not see that output state.The problem in figuring this out in the spi peripheral is that the sck pin will only enable the pin output when spe is set AND the spi is in master mode (whichever nss is being used). Since it is easy to get the spi into slave mode, which makes the sck pin an input (and float if no pull on the line) it can be a little confusing.For I2c, you put the scl/sda pins in open drain mode, with pullups (if no external pullups, or do anyway), in alternate mode, and the i2c peripheral takes control of the output enable and output state. With open drain mode selected in gpio, the high side driver is disconnected.So, as stated in reply #14, and elsewhere-QuoteYou setup gpio to have it in output mode, push-pull, with the output value of 0 (or whatever idle state is), after enabling spi (and in master mode) put the pin in alternate mode, do your transaction, in the process of turning off spi first set the pin back to output mode where gpio now has control of output enable and output state (output state and push-pull need no changes).

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 13, 2025, 07:44:27 pm
content: Quote from: cv007 on October 13, 2025, 06:50:11 pmYes, this is what I've been suggesting as well.

author: peter-h
author_rank: Super Contributor
timestamp: October 13, 2025, 09:41:59 pm
content: That's an interesting method - thank you both!One hopes that changing the AF config away from the SPI mode doesn't break the SPI controller somehow.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 13, 2025, 09:49:12 pm
content: Quote from: peter-h on October 13, 2025, 09:41:59 pmNope, peripherals and pin multiplexing are completely separated AFAIK and as far as I've tested.Which also allows you to only use part of the peripheral pins, for instance you can of course use only TX or only RX for an UART, or only MISO or only MOSI for SPI.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: October 14, 2025, 10:08:41 am
content: Oops, my bad. Just disabling the SPI block is not enough, you have to reset / change that AF part too. There are some applications where a bunch of serial ports are used, and the AF switching is used to create "more" serial ports. This does of course not create more hardware serial ports but it can be useful as long as you don't need all the uarts at the same time.

author: cv007
author_rank: Frequent Contributor
timestamp: October 14, 2025, 10:08:35 pm
content: QuoteIts not just the mode register of course, and the only atomic (rm)writes in gpio are to bsrr/brr. Sharing all port pins with each config register is not a great idea as pins are treated individually in most cases. I saw some 'new' hal2 gpio code from st where they protect the writes to these registers, but that hal code was for a cortex-m with atomic instructions (U5 I think) and they are using those instructions to do that job (I think they are using macros. such as ATOMIC_MODIFY_REG). Most hal users are most likely unaware that there is a potential problem lurking in setting these registers, but most hal users are also probably not changing anything in these registers after init and/or not in multiple run levels. The probabilities are also on your side if this kind if thing is ignored, but its something that may show up and so infrequently that is is almost impossible to figure out. I interrupt protect these registers (just disable irq's) not because its a big problem, but so it can never be a problem.QuoteThere is actually no need for open drain mode, and push-pull works the same. The peripheral in this case uses the output enable line to choose 1/0, and the output line value stays at 0. So open drain and push-pull mode act the same. I would suspect the only reason the gpio was given an open drain mode is because it would be the only way in that configuration to use the pin atomically via bsrr/brr. Some other mcu's skip this mode since they can change dir atomically.