GPIO conventions on STM32

Thread Starter

Futurist

Joined Apr 8, 2025
765
Ah yes. There are lots of idiosyncrasies that they don't tell you, such as you need to enable the peripheral clock for each module.
I should have known that, I've done that before but when you're hacking and tweaking it's easily done.

The proof-of-concept code was semi-hard-coded for SPI and GPIOA pins and so that was the root.

@nsaspook will frown on this but I added this little helper.


C:
private void enable_clock_from_pin(uint64_t pin)
{
    // Enable the GPIO clock for the pin
   
    uint32_t base = (uint32_t)(DECODE_BASE(pin));
   
    switch (base)
    {
    case GPIOA_BASE:
        __GPIOA_CLK_ENABLE();
        break;
    case GPIOB_BASE:
        __GPIOB_CLK_ENABLE();
        break;  
    case GPIOC_BASE:
        __GPIOC_CLK_ENABLE();
        break;
    case GPIOD_BASE:
        __GPIOD_CLK_ENABLE();
        break;
    case GPIOE_BASE:
        __GPIOE_CLK_ENABLE();
        break;
    case GPIOF_BASE:
        __GPIOF_CLK_ENABLE();
        break;
    case GPIOG_BASE:
        __GPIOG_CLK_ENABLE();
        break;
    case GPIOH_BASE:
        __GPIOH_CLK_ENABLE();
        break;
    }
}
This works when using the helper macros I mentioned yesterday.

e.g.

Code:
    enable_clock_from_pin(int_pin);
    enable_clock_from_pin(ce_pin);
    enable_clock_from_pin(cs_pin);
 

Thread Starter

Futurist

Joined Apr 8, 2025
765
Well when TX uses lowest transmitter power, I get message flow from my workshop to my bedroom and these are about as far away from each other that I can get without leaving the building and there are four/five walls involved. It loses a message from time to time so that's likely the max workable range for the power setting (on that channel - 100).

I took the receiver board into the bedroom and plugged it into a local USB outlet and the green LED flashes once per second, that' the rate the TX code is using to generate test messages.

If I can hook up the board to a battery I can do some range testing in my neighborhood.

Is there such a thing as a battery pack with USB outlet built in?
 

nsaspook

Joined Aug 27, 2009
16,330
I should have known that, I've done that before but when you're hacking and tweaking it's easily done.

The proof-of-concept code was semi-hard-coded for SPI and GPIOA pins and so that was the root.

@nsaspook will frown on this but I added this little helper.


C:
private void enable_clock_from_pin(uint64_t pin)
{
    // Enable the GPIO clock for the pin
 
    uint32_t base = (uint32_t)(DECODE_BASE(pin));
 
    switch (base)
    {
    case GPIOA_BASE:
        __GPIOA_CLK_ENABLE();
        break;
    case GPIOB_BASE:
        __GPIOB_CLK_ENABLE();
        break;
    case GPIOC_BASE:
        __GPIOC_CLK_ENABLE();
        break;
    case GPIOD_BASE:
        __GPIOD_CLK_ENABLE();
        break;
    case GPIOE_BASE:
        __GPIOE_CLK_ENABLE();
        break;
    case GPIOF_BASE:
        __GPIOF_CLK_ENABLE();
        break;
    case GPIOG_BASE:
        __GPIOG_CLK_ENABLE();
        break;
    case GPIOH_BASE:
        __GPIOH_CLK_ENABLE();
        break;
    }
}
This works when using the helper macros I mentioned yesterday.

e.g.

Code:
    enable_clock_from_pin(int_pin);
    enable_clock_from_pin(ce_pin);
    enable_clock_from_pin(cs_pin);
I don't frown on that as I've done much more complicated ways to generalize device setups for on the fly configurations for OS systems software (with MMU and VM hardware protection) where applications programs need abstracted access to resources. It just that here, it's mainly candy that not really needed for single level zero access to resources.

https://github.com/nsaspook/RIOT/blob/PIC32MZEF/boards/pic32-cpicmzef/cpicmzef.c
https://github.com/nsaspook/RIOT/blob/PIC32MZEF/cpu/mips_pic32_common/periph/spi.c
C:
static void release_dma_chan(uint8_t chan)
{
    assert(chan < DMA_NUMOF);

    IEC4CLR = _IEC4_DMA0IE_MASK << chan; /* Disable the DMA interrupt. */
    IFS4CLR = _IFS4_DMA0IF_MASK << chan; /* Clear the DMA interrupt flag. */
    DCHxCON(pic_dma[chan]) = 0;
    DCHxECON(pic_dma[chan]) = 0;
    DCHxINT(pic_dma[chan]) = 0;
}

static void init_dma_chan(uint8_t chan, uint32_t irq_num, volatile unsigned int *SourceDma, volatile unsigned int *DestDma, spi_t bus)
{
    assert(chan < DMA_NUMOF);
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    pic_dma[chan].regs = (volatile uint32_t *)(&DCH0CON + (chan * DMA_REGS_SPACING));
    pic_dma[chan].bus = bus;

    IEC4CLR = _IEC4_DMA0IE_MASK << chan; /* Disable the DMA chan interrupt. */
    IFS4CLR = _IFS4_DMA0IF_MASK << chan; /* Clear the DMA chan interrupt flag. */
    DMACONSET = _DMACON_ON_MASK; /* Enable the DMA module. */
    DCHxCON(pic_dma[chan]) = 0;
    DCHxECON(pic_dma[chan]) = 0;
    DCHxINT(pic_dma[chan]) = 0;
    DCHxSSA(pic_dma[chan]) = KVA_TO_PA(SourceDma); /* Source start address. */
    DCHxDSA(pic_dma[chan]) = KVA_TO_PA(DestDma); /* Destination start address. */
    DCHxSSIZ(pic_dma[chan]) = 1; /* default Source bytes. */
    DCHxDSIZ(pic_dma[chan]) = 1; /* default Destination bytes. */
    DCHxCSIZ(pic_dma[chan]) = 1; /* Bytes to transfer per event. */
    DCHxECON(pic_dma[chan]) = irq_num << _DCH0ECON_CHSIRQ_POSITION; /* cell trigger interrupt */
    DCHxECONSET(pic_dma[chan]) = _DCH0ECON_SIRQEN_MASK; /* Start cell transfer if an interrupt matching CHSIRQ occurs */
    DCHxINTSET(pic_dma[chan]) = _DCH0INT_CHBCIE_MASK; /* enable Channel block transfer complete interrupt. */
    /*
     * set vector priority and receiver DMA trigger enables for the board hardware configuration
     */
    *(dma_config[chan].ipc_regset) = dma_config[chan].ipc_mask_p & (SPIxPRI_SW0 << dma_config[chan].ipc_mask_pos_p);
    *(dma_config[chan].ipc_regset) = dma_config[chan].ipc_mask_s & (SPIxSUBPRI_SW0 << dma_config[chan].ipc_mask_pos_s);
    IEC4SET = dma_config[chan].iec_mask << chan; /* DMA interrupt enable if needed */
}

/* disable receive interrupts and set the UART buffer mode for DMA */
static void spi_reset_dma_irq(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    switch (bus) {
    case 1:
        IEC3CLR = _IEC3_SPI1RXIE_MASK; /* disable SPIxRX interrupt */
        SPI1CONbits.SRXISEL = 1; /* not empty */
        IFS3CLR = _IFS3_SPI1RXIF_MASK; /* clear SPIxRX flag */
        break;
    case 2:
        IEC4CLR = _IEC4_SPI2RXIE_MASK;
        SPI2CONbits.SRXISEL = 1;
        IFS4CLR = _IFS4_SPI2RXIF_MASK;

        break;
    default:
        break;
    }
}

static void trigger_bus_dma_tx(uint8_t chan, size_t len, uint32_t physSourceDma)
{
    assert(chan < DMA_NUMOF);

    DCHxSSA(pic_dma[chan]) = physSourceDma;
    DCHxSSIZ(pic_dma[chan]) = (len & _DCH0SSIZ_CHSSIZ_MASK);
    DCHxCONSET(pic_dma[chan]) = _DCH0CON_CHEN_MASK; /* Channel enable. */
}

static void trigger_bus_dma_rx(uint8_t chan, size_t len, uint32_t physDestDma)
{
    assert(chan < DMA_NUMOF);

    spi_reset_dma_irq(pic_dma[chan].bus);
    DCHxDSA(pic_dma[chan]) = physDestDma;
    DCHxDSIZ(pic_dma[chan]) = (len & _DCH0DSIZ_CHDSIZ_MASK);
    DCHxCONSET(pic_dma[chan]) = _DCH0CON_CHEN_MASK; /* Channel enable. */
}

/* adjust speed on the fly, these extra functions are prototyped in board.h */
void spi_speed_config(spi_t bus, spi_mode_t mode, spi_clk_t clk)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    pic_spi[bus].regs = (volatile uint32_t *)(_SPI1_BASE_ADDRESS + (bus - 1) * SPI_REGS_SPACING);

    SPIxCONCLR(pic_spi[bus]) = (_SPI1CON_ON_MASK);
    if (clk) {
        SPIxBRG(pic_spi[bus]) = (PERIPHERAL_CLOCK / (2 * clk)) - 1;
    }

    switch (mode) {
    case SPI_MODE_0:
        SPIxCONCLR(pic_spi[bus]) = _SPI1CON_CKP_MASK;
        SPIxCONSET(pic_spi[bus]) = _SPI1CON_CKE_MASK;
        break;
    case SPI_MODE_1:
        SPIxCONCLR(pic_spi[bus]) = (_SPI1CON_CKP_MASK | _SPI1CON_CKE_MASK);
        break;
    case SPI_MODE_2:
        SPIxCONCLR(pic_spi[bus]) = _SPI1CON_CKE_MASK;
        SPIxCONSET(pic_spi[bus]) = _SPI1CON_CKP_MASK;
        break;
    case SPI_MODE_3:
        SPIxCONSET(pic_spi[bus]) = (_SPI1CON_CKP_MASK | _SPI1CON_CKE_MASK);
        break;
    default:
        break;
    }
    SPIxCONSET(pic_spi[bus]) = (_SPI1CON_ON_MASK);
}

/* 1,2,3 are the active spi devices on the cpicmzef board configuration
* DMA channels are allocated for 1&2 tx/rx
*/
static void spi_irq_enable(spi_t bus)
{
    uint32_t mask;

    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    /* set enable and flag mask */
    mask = spi_config[bus].int_mask;
    *(spi_config[bus].iec_regclr) = mask; /* disable SPIxRX interrupt */

    switch (bus) {
    case 1:
        init_dma_chan(SPI1_DMA_TX, _SPI1_TX_VECTOR, &SPI1BUF, &SPI1BUF, bus);
        init_dma_chan(SPI1_DMA_RX, _SPI1_RX_VECTOR, &SPI1BUF, &SPI1BUF, bus);
        break;
    case 2:
        init_dma_chan(SPI2_DMA_TX, _SPI2_TX_VECTOR, &SPI2BUF, &SPI2BUF, bus);
        init_dma_chan(SPI2_DMA_RX, _SPI2_RX_VECTOR, &SPI2BUF, &SPI2BUF, bus);
        break;
    default:
        break;
    }

    SPIxCONCLR(pic_spi[bus]) = _SPI1CON_SRXISEL_MASK & (3 << _SPI1CON_SRXISEL_POSITION); /* clear all */
    /* interrupt when not full */
    SPIxCONSET(pic_spi[bus]) = _SPI1CON_SRXISEL_MASK & (1 << _SPI1CON_SRXISEL_POSITION); /* set mode */
    /*  last transfer is shifted out */
    SPIxCONCLR(pic_spi[bus]) = _SPI1CON_STXISEL_MASK & (3 << _SPI1CON_STXISEL_POSITION); /* clear all */
    /*
     * set vector priority and receiver interrupt enables for the board hardware configuration
     */
    *(spi_config[bus].ifs_regclr) = mask; /* clear SPIxRX flag */
    *(spi_config[bus].ipc_regset) = spi_config[bus].ipc_mask_p & (SPIxPRI_SW0 << spi_config[bus].ipc_mask_pos_p);
    *(spi_config[bus].ipc_regset) = spi_config[bus].ipc_mask_s & (SPIxSUBPRI_SW0 << spi_config[bus].ipc_mask_pos_s);
    *(spi_config[bus].iec_regset) = mask; /* enable SPIxRX interrupt */
}
The original code was modified for on-the-fly DMA drive SPI configuration when I added DMA, interrupt, mode and speed options to the original polled SPI driver.
 
Top