[SOLVED] stm32C031xx system header bug ?

This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

  • [SOLVED] stm32C031xx system header bug ?

    Not sure if this is the right place, because responsibilities seem somewhat mixed.

    Anyway, I a m having trouble creating proper output of the SPI peripheral on a stm32C031C6 - the one on the ST Nucleo C031 (MB1717B).
    I am configuring the SPI for 8-bit transfers, and debugging shows the corresponding config register (CR2) is set correctly.
    However, watching the SPI signals, I can see two two 8-bit transfers generated by each write to SPI->DR.
    The first value is a correct byte I intend to transfer, the second one is always zero.


    After some searching and experiments, I realized that the actual write instruction to the register was a 32-bit access : str r0, [<SPI->DR>].
    And checking the MCU-specific include (stm32c031xx.h), the DR register is defined as 32-bit word:
    typedef struct
    {
    __IO uint32_t CR1; /*!< SPI Control register 1 (not used in I2S mode), Address offset: 0x00 */
    ...
    __IO uint32_t DR; /*!< SPI data register, Address offset: 0x0C */

    ...
    } SPI_TypeDef;


    This is not correct, I think.
    According to the ST MCU reference manual (RM0490 Rev3, ch. 25.9.4, page 798), the DR register is only 16-bit wide.

    After changing the DR definition to "uint16_t" (and add another 16-bit padding value afterwards), a 16-bit access is generated by the compiler (strh r0, [>SPI->DR>]).
    The access seems to be fine now, though I still want to check it with a scope.


    While it is part of the Segger STM32C0 support package, this file seems to come from ST, according the the file header :
    * @author MCD Application Team

    * Copyright (c) 2022 STMicroelectronics.


    I am not sure what is happening in this case either.
    Perhaps the hardware serializes the peripheral (32-bit) word write into two consecutive 16-bit accesses ?


    For reference, the my functions to setup the SPI peripheral, and the send function.
    /* this function sets up SPI 1 (SPI & GPIO);
    * - PB.3 = SPI SCLK --> O
    * - PB.4 = SPI MISO <-- i
    * - PB.5 = SPI MOSI --> O
    * - PB.6 = GDO0 <-- i
    * - PB.7 = SPI CS --> O
    * PB6 & PB7 (GDO0 + CS) remain GPIO, i.e. manual SW control
    */
    void cc1101_setupPins (void)
    {
    uint32_t temp, mask;

    // enable power to GPIOB and the SPI peripheral
    RCC->IOPENR |= (1 << RCC_IOPENR_GPIOBEN_Pos);
    RCC->APBENR2 |= (1 << RCC_APBENR2_SPI1EN_Pos);

    // set MODE register bits;
    // the SPI pins are set to 0b10 (alt. function), CS and GDO0 remain GPIOs
    // PB6 (GDO0) is set to DIN
    temp = GPIOB->MODER;
    mask = ~(GPIO_MODER_MODE3_Msk + GPIO_MODER_MODE4_Msk + GPIO_MODER_MODE5_Msk + GPIO_MODER_MODE6_Msk + GPIO_MODER_MODE7_Msk);
    temp &= mask;
    temp |= (GPIO_MODER_MODE3_1 + GPIO_MODER_MODE4_1 + GPIO_MODER_MODE5_1 + GPIO_MODER_MODE7_0); // set 3, 4 and 5 to AF, 7 t output
    GPIOB->MODER = temp;

    // AF mode for SPI pins; all pins (PB3, PB4, PB5) are AF0,
    // i.e. the register default value; this means no need to change anything ...

    // setup the SPI unit; CPOL & CPHA = 0, manual SS, MSB first
    // BR is set to PCLK / 32 (with PCLK = CoreClk / 1 as default value !) = 0b100
    // this equals 48MHz / 32 = 1.5 MHz (max would be 10MHz at most, 6.5MHz for
    mask = (SPI_CR1_BIDIOE_Msk + SPI_CR1_MSTR_Msk + SPI_CR1_SSM_Msk + SPI_CR1_SSI_Msk);

    mask |= (SPI_CR1_BR_2);
    SPI1->CR1 = mask;

    // set 8-bit transfers; 8 bits = 0b0111
    mask = (SPI_CR2_DS_0 + SPI_CR2_DS_1 + SPI_CR2_DS_2);
    SPI1->CR2 = mask;

    // enable the unit
    SPI1->CR1 |= SPI_CR1_SPE_Msk;
    }

    /* send uint8_t via HW - SPI;
    * does only the SPI byte transfer, slave select handling
    * must be done in the context of the calling routine !
    * 'value' value to be sent
    * Return:
    * response received from SPI slave
    */
    uint8_t spi_send (uint8_t tx)
    {
    uint8_t rx = 0;

    SPI1->DR = (uint8_t) tx; // write Tx byte
    while (!(SPI1->SR & SPI_SR_RXNE)); // wait till finished (sync'ed. Rx in)
    rx = (uint8_t) SPI1->DR; // read MISO value
    return (rx);
    }
  • Investigating a bit further, it becomes a bit more complicated.
    As I found out, the second zero-byte transfer is caused by the high-byte of the 16-bit access to SPI->DR.
    As somewhat veiled described in the reference manual, writes to the DR register go into a FIFO.
    Albeit a 4-byte FIFO makes limited sense for a 32-bit MCU.

    Checking example code for a similiar MCU (stm32F05x), 8-bit transfers are done via byte-write to the DR register.
    Implementing such a byte-wise access through a byte-pointer to SPI->DR, the SPI interface works as expected.


    I suppose this is works regardless of the definition in the stm32c031xx.h header (16-bit or 32-bit).
    But still, the 32-bit definition does not reflect reality.
  • Hi Frank,

    Thank you for the analysis.

    As you have correctly seen these files are maintained by ST, and only included in the CPU package.
    We will notify ST of this bug, so it may be patched in a future release.

    Best regards,
    Sebastian
    Please read the forum rules before posting.

    Keep in mind, this is *not* a support forum.
    Our engineers will try to answer your questions between their projects if possible but this can be delayed by longer periods of time.
    Should you be entitled to support you can contact us via our support system: segger.com/ticket/

    Or you can contact us via e-mail.