STM32 registers and predefined memory mapped IO addresses

Thread Starter

ApacheKid

Joined Jan 12, 2015
157
I'm just beginning to understand the way the various STM headers files define the types and so on that are associated with the various memory mapped registers.

Something occurred to me as I was perusing this and that is that the entire set of memory mapped registers could be represented with one large, comprehensive typedef, with a pointer to that typedef initialized to the relevant base address. In fact we could define three "parent" typedefs one for AHB1, APB1 and APB2.

Each of these would themselves be decomposed into a hierarchy.

There's no runtime cost to this approach and the code we write might be more understandable, especially to a beginner or novice.

rather than simply:

GPIOA->MODER = 0x00000300;

we'd see

AHB1->GPIO[A].MODER[4] = ANALOG_MODE; // 'A' here being a constant, index into array of GPIO's

or

APB1->DAC |= 1;

we'd see

APB1->DAC[1].CONTROL.ENABLE = 1;

You get the idea, you can see that by making the structure more granular we eliminate the need for using OR ( | ) because the assignments are confined to the specific flags intended and do not touch flags either side of it.

None of the suggested code above carries any overhead in comparison to the conventional code.

Of course I guess that in some "real world" settings we may want to write a composite value to the entire DAC control register and that would still be possible but aside from that the code would be more readable, the structure of the peripheral would reflect its physical structure in memory.

The current stratgey defines APB1 as just a set of #defines:

APB1.jpg

completely masking the fact that the constant TIM2_BASE is in any way part of APB1.

The entire memory mapped IO looks like a tree and so representing it that way might lead to improved readability.

Thoughts? what do experienced STM32 developers think of this?
 
Last edited:

nsaspook

Joined Aug 27, 2009
7,192
That's not how hardware oriented people normally think of computer hardware. Deep Hierarchy seems easier until you run into hardware complexity. You can easily build more localized device typedefs for specific tasks.

PIC32 example for SPI device memory mapped registers. RIOT OS port to pic32mzef
https://forum.allaboutcircuits.com/threads/source-code-for-a-basic-preemptive-rtos.157781/post-1370506
C:
static const spi_conf_t spi_config[] = {
    {
        .mosi_pin = 0,
    },      /* No SPI0 on PIC32, dummy to compile */

    {       /*
             * SPI 1 (MikBUS 1)
             *      MOSI -> RD3
             *      MISO -> RD14
             *      SCK  -> RD1
             */
        .mosi_pin = GPIO_PIN(PORT_D, 3),
        .mosi_reg = (volatile uint32_t *) &RPD3R,
        .mosi_af = OUTPUT_FUNC_SDO1,
        .miso_pin = GPIO_PIN(PORT_D, 14),
        .miso_reg = (volatile uint32_t *) &SDI1R,
        .miso_af = INPUT_PIN_RPD14,
        .int_mask = _IEC3_SPI1RXIE_MASK,     /* enable & flag mask */
        .iec_regclr = (volatile uint32_t *) &IEC3CLR,
        .iec_regset = (volatile uint32_t *) &IEC3SET,
        .ifs_regclr = (volatile uint32_t *) &IFS3CLR,
        .ipc_regset = (volatile uint32_t *) &IPC27SET,  /* IPC SFR */
        .ipc_mask_p = _IPC27_SPI1RXIP_MASK,             /* priority data mask */
        .ipc_mask_pos_p = _IPC27_SPI1RXIP_POSITION,     /* priority in SFR */
        .ipc_mask_s = _IPC27_SPI1RXIS_MASK,             /* sub-priority */
        .ipc_mask_pos_s = _IPC27_SPI1RXIS_POSITION,     /* sub-priority */
    },

    {     /*
           * SPI 2 (MikBUS 2)
           *      MOSI -> RG7
           *      MISO -> RG0
           *      SCK  -> RG6
           */
        .mosi_pin = GPIO_PIN(PORT_G, 7),
        .mosi_reg = (volatile uint32_t *) &RPG7R,
        .mosi_af = OUTPUT_FUNC_SDO2,
        .miso_pin = GPIO_PIN(PORT_G, 0),
        .miso_reg = (volatile uint32_t *) &SDI2R,
        .miso_af = INPUT_PIN_RPG0,
        .int_mask = _IEC4_SPI2RXIE_MASK,     /* enable & flag mask */
        .iec_regclr = (volatile uint32_t *) &IEC4CLR,
        .iec_regset = (volatile uint32_t *) &IEC4SET,
        .ifs_regclr = (volatile uint32_t *) &IFS4CLR,
        .ipc_regset = (volatile uint32_t *) &IPC35SET,  /* IPC SFR */
        .ipc_mask_p = _IPC35_SPI2RXIP_MASK,             /* priority data mask */
        .ipc_mask_pos_p = _IPC35_SPI2RXIP_POSITION,     /* priority in SFR */
        .ipc_mask_s = _IPC35_SPI2RXIS_MASK,             /* sub-priority */
        .ipc_mask_pos_s = _IPC35_SPI2RXIS_POSITION,     /* sub-priority */
    },

    {     /*
           * SPI 3 (MRF24WN0MA-1/RM100 - wifi module)
           *   MOSI -> RB9
           *   MISO -> RB10
           *   SCK  -> RB14
           */
        .mosi_pin = GPIO_PIN(PORT_B, 9),
        .mosi_reg = (volatile uint32_t *) &RPB9R,
        .mosi_af = OUTPUT_FUNC_SDO3,
        .miso_pin = GPIO_PIN(PORT_B, 10),
        .miso_reg = (volatile uint32_t *) &SDI3R,
        .miso_af = INPUT_PIN_RPB10,
        .int_mask = _IEC4_SPI3RXIE_MASK,     /* enable & flag mask */
        .iec_regclr = (volatile uint32_t *) &IEC4CLR,
        .iec_regset = (volatile uint32_t *) &IEC4SET,
        .ifs_regclr = (volatile uint32_t *) &IFS4CLR,
        .ipc_regset = (volatile uint32_t *) &IPC38SET,  /* IPC SFR */
        .ipc_mask_p = _IPC38_SPI3RXIP_MASK,             /* priority data mask */
        .ipc_mask_pos_p = _IPC38_SPI3RXIP_POSITION,     /* priority in SFR */
        .ipc_mask_s = _IPC38_SPI3RXIS_MASK,             /* sub-priority */
        .ipc_mask_pos_s = _IPC38_SPI3RXIS_POSITION,     /* sub-priority */
    },
    {
        .mosi_pin = 0,
    },
    {
        .mosi_pin = 0,
    },
    {
        .mosi_pin = 0,
    },
};
/** @} */

/**
* @name    DMA device configuration
*
* @{
*/

#define DMA_NUMOF   (8)
/* DMA [0..3] used for SPI ports 1 and 2 */
#define SPI1_DMA_RX 0
#define SPI1_DMA_TX 1
#define SPI2_DMA_RX 2
#define SPI2_DMA_TX 3

static const dma_conf_t dma_config[] = {
    {
        .iec_mask = _IEC4_DMA0IE_MASK,                  /* enable */
        .ipc_regset = (volatile uint32_t *) &IPC33SET,  /* IPC SFR */
        .ipc_mask_p = _IPC33_DMA0IP_MASK,               /* priority data mask */
        .ipc_mask_pos_p = _IPC33_DMA0IP_POSITION,       /* priority in SFR */
        .ipc_mask_s = _IPC33_DMA0IS_MASK,               /* sub-priority */
        .ipc_mask_pos_s = _IPC33_DMA0IS_POSITION,       /* sub-priority */
    },
    {
        .iec_mask = 0,     /* DON'T enable */
        .ipc_regset = (volatile uint32_t *) &IPC33SET,
        .ipc_mask_p = _IPC33_DMA1IP_MASK,
        .ipc_mask_pos_p = _IPC33_DMA1IP_POSITION,
        .ipc_mask_s = _IPC33_DMA1IS_MASK,
        .ipc_mask_pos_s = _IPC33_DMA1IS_POSITION,
    },
    {
        .iec_mask = _IEC4_DMA0IE_MASK,
        .ipc_regset = (volatile uint32_t *) &IPC34SET,
        .ipc_mask_p = _IPC34_DMA2IP_MASK,
        .ipc_mask_pos_p = _IPC34_DMA2IP_POSITION,
        .ipc_mask_s = _IPC34_DMA2IS_MASK,
        .ipc_mask_pos_s = _IPC34_DMA2IS_POSITION,
    },
    {
        .iec_mask = 0,
        .ipc_regset = (volatile uint32_t *) &IPC34SET,
        .ipc_mask_p = _IPC34_DMA3IP_MASK,
        .ipc_mask_pos_p = _IPC34_DMA3IP_POSITION,
        .ipc_mask_s = _IPC34_DMA3IS_MASK,
        .ipc_mask_pos_s = _IPC34_DMA3IS_POSITION,
    },
    {
        .iec_mask = 0,
    },
    {
        .iec_mask = 0,
    },
    {
        .iec_mask = 0,
    },
    {
        .iec_mask = 0,
    },
};
C:
#include <stdio.h>
#include <string.h>
#include "assert.h"
#include "board.h"
#include "mutex.h"
#include "periph/gpio.h"
#include "periph/spi.h"
#include "periph/uart.h"

#define SPIxCON(U)          ((U).regs[0x00 / 4])
#define SPIxCONCLR(U)       ((U).regs[0x04 / 4])
#define SPIxCONSET(U)       ((U).regs[0x08 / 4])
#define SPIxSTAT(U)         ((U).regs[0x10 / 4])
#define SPIxSTATCLR(U)      ((U).regs[0x14 / 4])
#define SPIxBUF(U)          ((U).regs[0x20 / 4])
#define SPIxBRG(U)          ((U).regs[0x30 / 4])
#define SPIxCON2(U)         ((U).regs[0x40 / 4])
#define SPI_REGS_SPACING    (_SPI2_BASE_ADDRESS - _SPI1_BASE_ADDRESS)

#define DCHxCON(U)          ((U).regs[0x00 / 4])
#define DCHxCONCLR(U)       ((U).regs[0x04 / 4])
#define DCHxCONSET(U)       ((U).regs[0x08 / 4])
#define DCHxECON(U)         ((U).regs[0x10 / 4])
#define DCHxECONCLR(U)      ((U).regs[0x14 / 4])
#define DCHxECONSET(U)      ((U).regs[0x18 / 4])
#define DCHxINT(U)          ((U).regs[0x20 / 4])
#define DCHxINTCLR(U)       ((U).regs[0x24 / 4])
#define DCHxINTSET(U)       ((U).regs[0x28 / 4])
#define DCHxSSA(U)          ((U).regs[0x30 / 4])
#define DCHxDSA(U)          ((U).regs[0x40 / 4])
#define DCHxSSIZ(U)         ((U).regs[0x50 / 4])
#define DCHxDSIZ(U)         ((U).regs[0x60 / 4])
#define DCHxSPTR(U)         ((U).regs[0x70 / 4])
#define DCHxDPTR(U)         ((U).regs[0x80 / 4])
#define DCHxCSIZ(U)         ((U).regs[0x90 / 4])
#define DCHxCPTR(U)         ((U).regs[0xA0 / 4])
#define DCHxDAT(U)          ((U).regs[0xB0 / 4])
#define DMA_REGS_SPACING    (&DCH1CON - &DCH0CON)

#define SPIxPRI_SW0 2
#define SPIxSUBPRI_SW0  1

/* PERIPHERAL_CLOCK must be defined in board file */

typedef struct pic32_spi_tag {
    volatile uint32_t *regs;
    uint8_t *in;
    size_t len;
    volatile int32_t complete;
} pic32_spi_t;

typedef struct pic_32_dma_tag {
    volatile uint32_t *regs;
    spi_t bus;
} pic32_dma_t;

static pic32_spi_t pic_spi[SPI_NUMOF + 1];
static mutex_t locks[SPI_NUMOF + 1];
static pic32_dma_t pic_dma[DMA_NUMOF];

int32_t spi_complete(spi_t);
static void init_dma_chan(uint8_t, uint32_t, volatile unsigned int *, volatile unsigned int *, spi_t);

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 */
}

static void spi_irq_disable(spi_t bus)
{

    switch (bus) {
    case 1:
        release_dma_chan(SPI1_DMA_RX);
        release_dma_chan(SPI1_DMA_TX);
        break;
    case 2:
        release_dma_chan(SPI2_DMA_RX);
        release_dma_chan(SPI2_DMA_TX);
        break;
    default:
        break;
    }

    *(spi_config[bus].iec_regclr) = spi_config[bus].int_mask; /* disable SPIxRX interrupt */
}

void spi_init(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    mutex_init(&locks[bus]);

    PMD5SET = _PMD5_SPI1MD_MASK << (bus - 1);
    spi_init_pins(bus);
}

void spi_init_pins(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    gpio_init(spi_config[bus].mosi_pin, GPIO_OUT);
    gpio_init(spi_config[bus].miso_pin, GPIO_IN);
    *(spi_config[bus].mosi_reg) = spi_config[bus].mosi_af;
    *(spi_config[bus].miso_reg) = spi_config[bus].miso_af;
}

int spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
{
    volatile int rdata __attribute__((unused));

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

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

    mutex_lock(&locks[bus]);

    PMD5CLR = _PMD5_SPI1MD_MASK << (bus - 1);

    SPIxCON(pic_spi[bus]) = 0;
    SPIxCON2(pic_spi[bus]) = 0;

    /* Clear receive FIFO */
    rdata = SPIxBUF(pic_spi[bus]);

    switch (mode) {
    case SPI_MODE_0:
        SPIxCONCLR(pic_spi[bus]) = (_SPI1CON_CKP_MASK | _SPI1CON_CKE_MASK);
        break;
    case SPI_MODE_1:
        SPIxCONCLR(pic_spi[bus]) = _SPI1CON_CKP_MASK;
        SPIxCONSET(pic_spi[bus]) = _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:
        return SPI_NOMODE;
    }

    /* try to make the FIFO work in some modes */
    SPIxCONSET(pic_spi[bus]) = _SPI1CON_ENHBUF_MASK; /* enable FIFO */
    SPIxBRG(pic_spi[bus]) = (PERIPHERAL_CLOCK / (2 * clk)) - 1;
    SPIxSTATCLR(pic_spi[bus]) = _SPI1STAT_SPIROV_MASK;
    spi_irq_enable(bus);
    SPIxCONSET(pic_spi[bus]) = (_SPI1CON_ON_MASK | _SPI1CON_MSTEN_MASK);

    return SPI_OK;
}

void spi_release(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    spi_irq_disable(bus);
    /* SPI module must be turned off before powering it down */
    SPIxCON(pic_spi[bus]) = 0;
    PMD5SET = _PMD5_SPI1MD_MASK << (bus - 1);
    mutex_unlock(&locks[bus]);
}

static inline void _spi_transfer_bytes_async(spi_t bus, spi_cs_t cs, bool cont,
    const void *out, void *in, size_t len)
{
    const uint8_t *out_buffer = (const uint8_t *) out;
    uint8_t dma_able = 8; /* default to NO DMA to trigger default method */

    (void) cs;
    (void) cont;

    /* set input buffer params for the non-dma isr mode */
    pic_spi[bus].in = in;
    pic_spi[bus].len = len;
    pic_spi[bus].complete = false;

    /* check if we have both buffers
     * need to also check for DMA or interrupt driven channels
     * as spi ports 1@2 don't have the default setup for SPI port
     * interrupts configured so this needs to fail on 1@2 until fixed
     */
    if (out && in) {
        dma_able = 0;
    } else {
        /* don't try with dma bus channels */
        assert(bus == SPI_NO_DMA_BUS);
    }

    /* Translate a kernel (KSEG) virtual address to a physical address. */
    switch (bus + dma_able) {
    case 1:
        trigger_bus_dma_rx(SPI1_DMA_RX, len, KVA_TO_PA(in));
        trigger_bus_dma_tx(SPI1_DMA_TX, len, KVA_TO_PA(out_buffer));
        break;
    case 2:
        trigger_bus_dma_rx(SPI2_DMA_RX, len, KVA_TO_PA(in));
        trigger_bus_dma_tx(SPI2_DMA_TX, len, KVA_TO_PA(out_buffer));
        break;
    default: /* non-dma mode */
        while (len--) {
            if (out_buffer) {
                SPIxBUF(pic_spi[bus]) = *out_buffer++;
                /* Wait until TX FIFO is empty */
                while ((SPIxSTAT(pic_spi[bus]) & _SPI1STAT_SPITBF_MASK)) {
                }
            } else {
                SPIxBUF(pic_spi[bus]) = 0;
                /* Wait until TX FIFO is empty */
                while ((SPIxSTAT(pic_spi[bus]) & _SPI1STAT_SPITBF_MASK)) {
                }
            }
        }
    }
}

void spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
    const void *out, void *in, size_t len)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);
    /* make sure at least one input or one output buffer is given */
    assert(out || in);

    if (cs != SPI_CS_UNDEF) {
        gpio_clear((gpio_t) cs);
    }

    _spi_transfer_bytes_async(bus, cs, cont, out, in, len);

    while (!spi_complete(bus)) {
    }

    if (!cont && cs != SPI_CS_UNDEF) {

        gpio_set((gpio_t) cs);
    }
}

void spi_transfer_bytes_async(spi_t bus, spi_cs_t cs, bool cont,
    const void *out, void *in, size_t len)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);
    /* make sure at least one input or one output buffer is given */
    assert(out || in);

    if (cs != SPI_CS_UNDEF) {

        gpio_clear((gpio_t) cs);
    }

    _spi_transfer_bytes_async(bus, cs, cont, out, in, len);
    /* don't mess with cs on exit */
}

/* spi interrupt in single vector sw0 */
static void spi_rx_irq(spi_t bus)
{
    uint8_t rdata __attribute__((unused));

    while (!((SPIxSTAT(pic_spi[bus]) & _SPI1STAT_SPIRBE_MASK))) {
        if (pic_spi[bus].in) {
            *pic_spi[bus].in++ = SPIxBUF(pic_spi[bus]);
        } else {
            /* dump the received data with no callback */
            rdata = SPIxBUF(pic_spi[bus]);
        }
        if (!--pic_spi[bus].len) {

            pic_spi[bus].complete = true;
        }
    }
}

void spi_1_isr_rx(void)
{
    spi_rx_irq(1);
}

void spi_2_isr_rx(void)
{
    spi_rx_irq(2);
}

void spi_3_isr_rx(void)
{
    spi_rx_irq(3);
}

/* set transfer complete flag */
void dma_spi_1_isr_rx(void)
{
    pic_spi[1].complete = true;
}

void dma_spi_2_isr_rx(void)
{
    pic_spi[2].complete = true;
}

/* not currently used */
void dma_spi_3_isr_rx(void)
{
    pic_spi[3].complete = true;
}

int32_t spi_complete(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF);
    return pic_spi[bus].complete;
}
Using SPI with a ADS1220 in the RIOT OS port
C:
#include <stdint.h>
#include <stdbool.h>
#include "adc.h"
#include "spi.h"
#include "app.h"
#include "config.h"
#include "timers.h"
#include "ads1220.h"
#include "periph/dac.h"
#include "periph/adc.h"

static uint8_t *tx_buff;
static uint8_t *rx_buff;
static bool upd = false;

extern rn4020_appdata_t rn4020_appdata;

static void ads_spi_transfer_bytes(spi_t bus, spi_cs_t cs, bool cont,
                                   const void *out, void *in, size_t len)
{
    spi_speed_config(bus, 1, SPI_CLK_2MHZ); /* mode , no speed change */
    SPI_CS1_0;
    timer_shortdelay(75);
    spi_transfer_bytes(bus, cs, cont, out, in, len);
    tx_buff[0] = ADS1220_CMD_SYNC;
    spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 1);
    timer_shortdelay(25);
    SPI_CS1_1;
}

static void ads_int_setup(void)
{
    IEC0CLR = _IEC0_INT2IE_MASK;    /* disable interrupt */
    INTCONbits.INT2EP = 0;
    IFS0CLR = _IFS0_INT2IF_MASK;    /* clear flag */
    IPC3bits.INT2IP = 1;
    IPC3bits.INT2IS = 1;
    IEC0SET = _IEC0_INT2IE_MASK; /* enable interrupt */
}

int ads1220_init(void)
{
    tx_buff = __pic32_alloc_coherent(32); /* uncached memory for spi transfers */
    rx_buff = __pic32_alloc_coherent(32);

    if (!(tx_buff && rx_buff)) {
        return false;
    }
    /*
     * setup ads1220 registers
     */
    spi_speed_config(SPI_DEV(2), 1, SPI_CLK_2MHZ); /* mode , no speed change */
    SPI_CS1_0;
    timer_shortdelay(50);
    tx_buff[0] = ADS1220_CMD_RESET;
    spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 1);
    timer_shortdelay(250 * US_TO_CT_TICKS);
    tx_buff[0] = ADS1220_CMD_WREG + 3;
    tx_buff[1] = ads1220_r0;
    tx_buff[2] = ads1220_r1 | ADS1220_TEMP_SENSOR;
    tx_buff[3] = ads1220_r2;
    tx_buff[4] = ads1220_r3;
    spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 5);
    tx_buff[0] = ADS1220_CMD_RREG + 3;
    tx_buff[1] = 0;
    tx_buff[2] = 0;
    tx_buff[3] = 0;
    tx_buff[4] = 0;
    spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 5);
    timer_shortdelay(10 * US_TO_CT_TICKS);
    /*
     * Check to be sure we have a device
     */
    ads_int_setup();
    if ((rx_buff[1] != ads1220_r0) ||
        (rx_buff[2] != (ads1220_r1 | ADS1220_TEMP_SENSOR))) { /* checking for sensor flag too */
        printf(
            "\r\nADS1220 configuration error: %x,%x %x,%x %x %x\r\n",
            ads1220_r0, rx_buff[1], ads1220_r1, rx_buff[2],
            rx_buff[3], rx_buff[4]);
        SPI_CS1_1;
        return(-1);
    }
    tx_buff[0] = ADS1220_CMD_SYNC;
    spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 1);
    SPI_CS1_1;
    return true;
}

static void ads1220_writeregister(int32_t StartAddress, int32_t NumRegs, uint32_t *pData)
{
    int32_t i;

    tx_buff[0] = ADS1220_CMD_WREG | (((StartAddress << 2) & 0x0c) | ((NumRegs - 1) & 0x03));

    for (i = 0; i < NumRegs; i++) {
        tx_buff[i + 1] = *pData++;
    }

    ads_spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, NumRegs + 2);
    return;
}

static void ai_set_chan_range_ads1220(uint32_t chan, uint32_t range)
{
    static uint32_t range_last = 99;
    static uint32_t chan_last = 99;
    uint32_t cMux;

    if ((chan != chan_last) || (range != range_last)) {
        chan_last = chan;
        range_last = range;
        /*
         * convert chan/range to input MUX switches/gains if needed
         * we could just feed the raw bits to the Mux if needed
         */

        switch (chan) {
            case 0:
                cMux = ADS1220_MUX_0_1;
                break;
            case 1:
                cMux = ADS1220_MUX_0_2;
                break;
            case 2:
                cMux = ADS1220_MUX_0_3;
                break;
            case 3:
                cMux = ADS1220_MUX_1_2;
                break;
            case 8:
                cMux = ADS1220_MUX_0_G;
                break;
            case 9:
                cMux = ADS1220_MUX_1_G;
                break;
            case 10:
                cMux = ADS1220_MUX_2_G;
                break;
            case 11:
                cMux = ADS1220_MUX_3_G;
                break;
            case 15:
                cMux = ADS1220_MUX_DIV2;
                break;
            default:
                cMux = ADS1220_MUX_0_1;
        }
        cMux |= ((range & 0x03) << 1); /* setup the gain bits for range with NO pga */
        cMux |= ads1220_r0_for_mux_gain;
        ads1220_writeregister(ADS1220_0_REGISTER, 0x01, &cMux);
    }
}

int ads1220_testing(void)
{
    static int i = 0;

    if (upd || (i++ > 90000)) {
        static int a1 = 2048, b1 = 16;
        PDEBUG3_OFF;
        ai_set_chan_range_ads1220(8, 0);

        /* read the ads1220 3 byte data result */
        tx_buff[0] = ADS1220_CMD_RDATA;
        tx_buff[1] = 0;
        tx_buff[2] = 0;
        tx_buff[3] = 0;
        ads_spi_transfer_bytes(SPI_DEV(2), 0, true, tx_buff, rx_buff, 4);
        rn4020_appdata.ads1220value = rx_buff[1];
        rn4020_appdata.ads1220value = (rn4020_appdata.ads1220value << 8) | rx_buff[2];
        rn4020_appdata.ads1220value = (rn4020_appdata.ads1220value << 8) | rx_buff[3];

        /* mangle the data as necessary */
        /* Bipolar Offset Binary */
        /*
            rn4020_appdata.ads1220value &= 0x0ffffff;
                rn4020_appdata.ads1220value ^= 0x0800000;
         */

        if (SWITCH1 == 0) {
            printf(" ADS1220 value: %x, an15 value %i,%i,%i,%i\r\n",
                   (int) (rn4020_appdata.ads1220value >> 10),
                   adc_sample(0, ADC_RES_12BIT), adc_sample(1, ADC_RES_12BIT),
                   adc_sample(2, ADC_RES_12BIT), adc_sample(3, ADC_RES_12BIT));
        }
        upd = false;
        i = 0;
        dac_set(0, a1 += 100);
        dac_set(1, b1 += 100);
    }
    return rn4020_appdata.ads1220value;
}

void rn4020_int_2_isr(void)
{
    PDEBUG3_ON;
    rn4020_appdata.heat_value = (int16_t) ((rn4020_appdata.ads1220value >> 10) - 0x0370);
    upd = true;
}
 
Last edited:

MrChips

Joined Oct 2, 2009
20,918
I have been designing with STM32 for over eight years now.
I use the manufacturer's (or toolset's) #define header files and see no need to reinvent the wheel.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
157
Well aI wasn't trying to reinvent anything, but see if my understanding was correct, to see if we could express things that way.

Also, I'm a very experienced software desinger and have designed many sophisticated API's over the years just not on MCUs.

It's natural for me to create or root out patterns and abstractions.

Part of what made me consider this is the huge benefit we get from intellisense, just typing "AHB1" then a "." would immeditelly reveal every peripheral attached to that bus, then typing (say) "GPIO" would reveal it is an array and so on. One could very quickly see what characteristics and settings each peripheral has.

Rather than trying to remember a huge number of names for things we could rapidly and reliably drill down with little effort and little need to remember lots of names.
 

MrChips

Joined Oct 2, 2009
20,918
With the toolset I am using, in the editor as I am typing a word that is a structure, a popup appears showing the elements of the structure so that I do not have to look it up.
 

Analog Ground

Joined Apr 24, 2019
365
A few things. 1) It is unlikely the microcontroller manufacturers will maintain your proposed level of hierarchy in their chip and board support packages. Microcontrollers are built from existing circuit modules which are pulled together to make a new device. This is reflected in the structure you see in the header files. They pretty much stop at definitions which support multiple instances of the same module (for example, multiple GPIO or timers). 2) You can write a lot of microcontroller code without knowing the bus structure because the memory map is usually completely flat and the only thing needed is the base address of the hardware module. 3) While the bus structure is likely to change from one device to another, the internals of a module do not. Adding a level of hierarchy would seem to have portability issues. Although I have not thought about this one.

Conclusion: Interesting idea but if it gave a practical advantage, someone would be doing it.
 

Thread Starter

ApacheKid

Joined Jan 12, 2015
157
A few things. 1) It is unlikely the microcontroller manufacturers will maintain your proposed level of hierarchy in their chip and board support packages. Microcontrollers are built from existing circuit modules which are pulled together to make a new device. This is reflected in the structure you see in the header files. They pretty much stop at definitions which support multiple instances of the same module (for example, multiple GPIO or timers). 2) You can write a lot of microcontroller code without knowing the bus structure because the memory map is usually completely flat and the only thing needed is the base address of the hardware module. 3) While the bus structure is likely to change from one device to another, the internals of a module do not. Adding a level of hierarchy would seem to have portability issues. Although I have not thought about this one.

Conclusion: Interesting idea but if it gave a practical advantage, someone would be doing it.
Yes these are valid points, reflecting the physical structure does amount to an inflexible abstraction, and portability is reduced.

I suppose the peripherals themselves are more consistent across MCU models than the internal bus structures.
 

MrChips

Joined Oct 2, 2009
20,918
Code and project portability is a top priority. This is why CubeMX was invented.
Now it is STM32CubeIDE. This and Atollic TrueSTUDIO are free.
 

nsaspook

Joined Aug 27, 2009
7,192
Yes these are valid points, reflecting the physical structure does amount to an inflexible abstraction, and portability is reduced.

I suppose the peripherals themselves are more consistent across MCU models than the internal bus structures.
Most of us have played with the idea many moons ago and discovered it's not worth the effort to build monolithic chip to bit abstraction structures. Sorry OO programmers. The Real World is not a hierarchy.
 
Top