Thread: Macros, Base addresses and API functions

  1. #1
    Registered User
    Join Date
    Sep 2016
    Posts
    41

    Macros, Base addresses and API functions

    So I’m a little confused with this code I am wading through. It’s the CMSIS level for the 32-bit zero gecko from Silabs.

    Basically they have a series of defines, for the base address of each peripheral. Followed by a macro, which contains a pointer to a struct of ***_TypeDef as the first parameter (these typedef structs are declared in repsective peripheral header files), and then the defined base addresses as a second paramater. Defines as follows:
    Code:
    #defineAES_BASE (0x400E0000UL) /**<AES base address */ #defineDMA_BASE (0x400C2000UL) /**<DMA base address */ #defineMSC_BASE (0x400C0000UL) /**<MSC base address */ #defineEMU_BASE (0x400C6000UL) /**<EMU base address */ #defineRMU_BASE (0x400CA000UL) /**<RMU base address */ #defineCMU_BASE (0x400C8000UL) /**<CMU base address */ #defineTIMER0_BASE (0x40010000UL) /**<TIMER0 base address */ #defineTIMER1_BASE (0x40010400UL) /**<TIMER1 base address */ #defineACMP0_BASE (0x40001000UL) /**<ACMP0 base address */ #defineUSART1_BASE (0x4000C400UL) /**<USART1 base address */ #definePRS_BASE (0x400CC000UL) /**<PRS base address */ #defineIDAC0_BASE (0x40004000UL) /**<IDAC0 base address */ #defineGPIO_BASE (0x40006000UL) /**<GPIO base address */ #defineVCMP_BASE (0x40000000UL) /**<VCMP base address */ #defineADC0_BASE (0x40002000UL) /**<ADC0 base address */ #defineLEUART0_BASE (0x40084000UL) /**<LEUART0 base address */ #definePCNT0_BASE (0x40086000UL) /**<PCNT0 base address */ #defineI2C0_BASE (0x4000A000UL) /**<I2C0 base address */ #defineRTC_BASE (0x40080000UL) /**<RTC base address */ #defineWDOG_BASE (0x40088000UL) /**<WDOG base address */ #defineCALIBRATE_BASE (0x0FE08000UL) /**<CALIBRATE base address */ #defineDEVINFO_BASE (0x0FE081B0UL) /**<DEVINFO base address */ #defineROMTABLE_BASE (0xF00FFFD0UL) /**<ROMTABLE base address */ #defineLOCKBITS_BASE (0x0FE04000UL) /**<Lock-bits page base address */ #defineUSERDATA_BASE (0x0FE00000UL) /**<User data page base address */ /**@} End of group EFM32ZG222F32_Peripheral_Base */ /**************************************************************************//***@defgroup EFM32ZG222F32_Peripheral_Declaration EFM32ZG222F32Peripheral Declarations* @{*****************************************************************************/ #defineAES ((AES_TypeDef *) AES_BASE) /**<AES base pointer */ #defineDMA ((DMA_TypeDef *) DMA_BASE) /**<DMA base pointer */ #defineMSC ((MSC_TypeDef *) MSC_BASE) /**<MSC base pointer */ #defineEMU ((EMU_TypeDef *) EMU_BASE) /**<EMU base pointer */ #defineRMU ((RMU_TypeDef *) RMU_BASE) /**<RMU base pointer */ #defineCMU ((CMU_TypeDef *) CMU_BASE) /**<CMU base pointer */ #defineTIMER0 ((TIMER_TypeDef *) TIMER0_BASE) /**<TIMER0 base pointer */ #defineTIMER1 ((TIMER_TypeDef *) TIMER1_BASE) /**<TIMER1 base pointer */ #defineACMP0 ((ACMP_TypeDef *) ACMP0_BASE) /**<ACMP0 base pointer */ #defineUSART1 ((USART_TypeDef *) USART1_BASE) /**<USART1 base pointer */ #definePRS ((PRS_TypeDef *) PRS_BASE) /**<PRS base pointer */ #defineIDAC0 ((IDAC_TypeDef *) IDAC0_BASE) /**<IDAC0 base pointer */ #defineGPIO ((GPIO_TypeDef *) GPIO_BASE) /**<GPIO base pointer */ #defineVCMP ((VCMP_TypeDef *) VCMP_BASE) /**<VCMP base pointer */ #defineADC0 ((ADC_TypeDef *) ADC0_BASE) /**<ADC0 base pointer */ #defineLEUART0 ((LEUART_TypeDef *) LEUART0_BASE) /**<LEUART0 base pointer */ #definePCNT0 ((PCNT_TypeDef *) PCNT0_BASE) /**<PCNT0 base pointer */ #defineI2C0 ((I2C_TypeDef *) I2C0_BASE) /**<I2C0 base pointer */ #defineRTC ((RTC_TypeDef *) RTC_BASE) /**<RTC base pointer */ #defineWDOG ((WDOG_TypeDef *) WDOG_BASE) /**<WDOG base pointer */ #defineCALIBRATE ((CALIBRATE_TypeDef *) CALIBRATE_BASE) /**<CALIBRATE base pointer */ #defineDEVINFO ((DEVINFO_TypeDef *) DEVINFO_BASE) /**<DEVINFO base pointer */ #defineROMTABLE ((ROMTABLE_TypeDef *) ROMTABLE_BASE) /**<ROMTABLE base pointer */

    The respective structs all look something like this:

    Code:
    typedefstruct
    
    { __IOMuint32_tCTRL;/**<Control Register */ __IOMuint32_tFRAME;/**<USART Frame Format Register */ __IOMuint32_tTRIGCTRL;/**<USART Trigger Control register */ __IOMuint32_tCMD;/**<Command Register */ __IMuint32_tSTATUS;/**<USART Status Register */ __IOMuint32_tCLKDIV;/**<Clock Control Register */ __IMuint32_tRXDATAX;/**<RX Buffer Data Extended Register */ __IMuint32_tRXDATA;/**<RX Buffer Data Register */ __IMuint32_tRXDOUBLEX;/**<RX Buffer Double Data Extended Register */ __IMuint32_tRXDOUBLE;/**<RX FIFO Double Data Register */ __IMuint32_tRXDATAXP;/**<RX Buffer Data Extended Peek Register */ __IMuint32_tRXDOUBLEXP;/**<RX Buffer Double Data Extended Peek Register */ __IOMuint32_tTXDATAX;/**<TX Buffer Data Extended Register */ __IOMuint32_tTXDATA;/**<TX Buffer Data Register */ __IOMuint32_tTXDOUBLEX;/**<TX Buffer Double Data Extended Register */ __IOMuint32_tTXDOUBLE;/**<TX Buffer Double Data Register */ __IMuint32_tIF;/**<Interrupt Flag Register */ __IOMuint32_tIFS;/**<Interrupt Flag Set Register */ __IOMuint32_tIFC;/**<Interrupt Flag Clear Register */ __IOMuint32_tIEN;/**<Interrupt Enable Register */ __IOMuint32_tIRCTRL;/**<IrDA Control Register */ __IOMuint32_tROUTE;/**<I/O Routing Register */ __IOMuint32_tINPUT;/**<USART Input Register */ __IOMuint32_tI2SCTRL;/**<I2S Control Register */ }USART_TypeDef;/**@} */
    First a little bit of context:

    At the start of my learning, this was naturally all a bit confusing. (I made a post earlier in the year about these structs. It was made clear to me that the size of each register and thus the calculated offset, was defined by size of the data type. Very cool!)

    Then after having stumbled upon the syntax for writing to a register i.e.

    Code:
    USART1→ROUTE (= / |= / &=) PREDEFINED_MASK_EXAMPLE;
    I then learned about the arrow operator (something I personally feel should be called the deference [not de-reference] operator) and why we use it as opposed to the dot operator: because I am accessing a member of a struct via a pointer. Again, very cool. Woot.


    So all of that makes sense. I call a macro, access a member via a pointer and the base address is passed to the macro. Then magic.

    What doesn’t make sense is two things:

    1. In the macros, how does the second parameter i.e. base address, actually become associated with the struct. Is this something that is inherent to macros??

    2. The same question plays out, in a slightly different form, in the following function which is defined in the silabs api for this chip.


    Code:
    uint8_t USART_SpiTransfer(USART_TypeDef *usart, uint8_t data)
     <<<<<<<<----------
    {
      while (!(usart->STATUS & USART_STATUS_TXBL))
        ;
      usart->TXDATA = (uint32_t)data;
        <<<<<<<<<<<<--------------------------------
      while (!(usart->STATUS & USART_STATUS_TXC))
        ;
      return (uint8_t)usart->RXDATA;<<<<<<<<<<------------
    }
    I understand that the first parameter is a pointer to a struct of USART_TypeDef and that we are writing to an instance of that struct.

    What I am struggling to see is where the appropriate peripheral base addresses are being passed to this instance of said struct. For all I can see it’s merely a random instance of a struct, just like any other struct, without association to any real address space register.

    Again, even here:

    Code:
    #defineUSART1 ((USART_TypeDef *) USART1_BASE) /**<USART1 base pointer */

    The passing of the struct as the first parameter (and base address as second parameter), to the macro is only relevant when I call that macro directly.. yes?

    Again,how does this base address become associated with the struct inside the macro and how then does the same thing happen to the instance of the struct, that is created inside the above mentioned function?


    Thanks Salem and everyone in advance! haha


  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    First of all, when you paste code to the forum, make sure you use the "paste as text" option (or copy as text option in your IDE). Otherwise, you end up with the horrible mess above.

    First,
    Code:
    USART_TypeDef myVar;
    #define USART1       ((USART_TypeDef *) &myVar)        /**<USART1 base pointer */
    ...
    USART1->TXDATA = 0;
    Do you understand this bit of convolution as being the same as
    Code:
    myVar.TXDATA = 0;
    All the macros are doing is substituting the memory address the compiler knows about with &myVar, with a physical address on your board such as 0x4000C400UL.


    > I understand that the first parameter is a pointer to a struct of USART_TypeDef and that we are writing to an instance of that struct.
    Given your macros, I expect that the first parameter will always be USART1 when programming for this particular device.
    As in
    Code:
    uint8_t rxByte = USART_SpiTransfer(USART1, '\n');
    Presumably on other supported chipsets, there may be a
    Code:
    #define USART2       ((USART_TypeDef *) USART2_BASE)        /**<USART2 base pointer */
    Along with matching calls like
    Code:
    uint8_t rxByte = USART_SpiTransfer(USART2, '\n');
    The whole point of the macros, base register pointers and wrapper functions like USART_SpiTransfer() is to create a much nicer programming environment where 99% of the code simply doesn't care that a USART exists at address 0x4000C400UL. If that address were to change, it would be a simple 1-line code change and recompilation, and the rest of the code carries on in happy ignorance of the underlying hardware.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    First of all, when you paste code to the forum, make sure you use the "paste as text" option (or copy as text option in your IDE). Otherwise, you end up with the horrible mess above.
    Oops!! Didn't see that. Will do.

    First,
    Code:
    [COLOR=white !important]?

    1
    2
    3
    4
    USART_TypeDef myVar;
    #define USART1 ((USART_TypeDef *) &myVar) /**<USART1 base pointer */
    ...
    USART1->TXDATA = 0;


    [/COLOR]
    Do you understand this bit of convolution as being the same as
    Code:
    [COLOR=white !important]?

    1
    myVar.TXDATA = 0;


    [/COLOR]
    All the macros are doing is substituting the memory address the compiler knows about with &myVar, with a physical address on your board such as 0x4000C400UL.



    Yes I understand that macros are simple text replacement. And I understand that the dot operator and arrow operator are essentially the same. As mentioned the only reason we use the arrow operator, is because I am accessing a member of a struct via a pointer.

    Otherwise, without the pointer, I could simply access the member like this:

    Code:
    
    
    Code:
    myVar.TXDATA = 0;
    


    I understand all of this, the peripherals like USART, CMU etc. are all memory mapped to registers in address space. Totally solid on that concept.

    When I use that macro i.e.

    Code:
    USART1->ROUTE = BITMASK_EXAMPLE;
    I am simply writing to that address space.


    My question was more 'what are the exact mechanics for associating that base address with the struct pointer' (below). Having done a little more digging around, I have found that the following macro contains a simple cast:

    Code:
    
    
    Code:
    #define USART1       ((USART_TypeDef *) USART1_BASE)        /**<USART1 base pointer */
    


    The nested brackets confused me, I thought it was some sort of nested bracket magic (or something). But apparently it's just like any other casting operation.

    So that's solved; it's casting the USART1_BASE macro as a pointer of type USART_TypeDef, to that address. Super cool trick!!! Please correct me if I'm not 100% in my understanding on that one.



    > I understand that the first parameter is a pointer to a struct of USART_TypeDef and that we are writing to an instance of that struct.
    Given your macros, I expect that the first parameter will always be USART1 when programming for this particular device.
    As in
    Code:
    [COLOR=white !important]?

    1
    uint8_t rxByte = USART_SpiTransfer(USART1, '\n');


    [/COLOR]
    Presumably on other supported chipsets, there may be a
    Code:
    [COLOR=white !important]?

    1
    #define USART2 ((USART_TypeDef *) USART2_BASE) /**<USART2 base pointer */


    [/COLOR]
    Along with matching calls like
    Code:
    [COLOR=white !important]?

    1
    uint8_t rxByte = USART_SpiTransfer(USART2, '\n');


    [/COLOR]
    The whole point of the macros, base register pointers and wrapper functions like USART_SpiTransfer() is to create a much nicer programming environment where 99% of the code simply doesn't care that a USART exists at address 0x4000C400UL. If that address were to change, it would be a simple 1-line code change and recompilation, and the rest of the code carries on in happy ignorance of the underlying hardware.



    AAHHHH!!!! Of course!! There are matching function calls! derrrr.... In my intense study and staring at the code, I completely forgot about the part where I actually call the function! haha.. I need to pass the macro USART1 to this function as the first parameter (which would be just like writing the base address, cast as USART1_TypeDef*) and then the arrow operator, inside the function, takes care of the rest. Oh I feel so silly.

    That is all just so cool btw. I really do love C.



    Thank you Salem. Once again you are most helpful!!

    MedicineMan25

  4. #4
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    oh ........ I did it again.

    "Paste as text"

    Got it.

  5. #5
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    So I thought I would write a little simulation, to better understand the syntax and process that's happening here.



    Code:
    typedef struct{
        
        char data[25];
        
    }DWM_tx_Typedef;
    
    
    DWM_tx_Typedef* usart1; // <<--- simulating hard register
    
    
    #define USART_BASE (&usart1) // <<--- (simulating) address of hard register
    #define USART ((DWM_tx_Typedef*) USART_BASE)
    
    
    void tx_write(DWM_tx_Typedef* usart, char* data){
    
        int length = strlen(data);
        
        for(uint i=0; i<length; i++){
        usart->data[i] = data[i];
        }
                
        printf("%p \n", usart);
        printf("%s", usart->data);
    
    }
    
    
    int main(int argc, char** argv) {
        
        char array[10] = "testing12";
        tx_write(USART, &array[0]);
        
        return (EXIT_SUCCESS);
    }
    Last edited by MedicineMan25; 01-02-2017 at 06:49 AM.

  6. #6
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    It's OK, apart from this needs to be an actual instance
    DWM_tx_Typedef usart1; // <<--- simulating hard register

    You make a pointer out of it with the first #define


    and be wary of making sure any strings you print have a \0 in place. Your copy loop does not copy a \0 explicitly.
    > printf("%s", usart->data);
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  7. #7
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    It's OK, apart from this needs to be an actual instance
    DWM_tx_Typedef usart1; // <<--- simulating hard register

    You make a pointer out of it with the first #define
    Oooo yes of course.

    and be wary of making sure any strings you print have a \0 in place. Your copy loop does not copy a \0 explicitly.
    > printf("%s", usart->data);
    Good point, strlen doesn't count the null.


    Thanks!!
    MM25

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Use inline functions instead of macros?
    By RichSelian in forum C Programming
    Replies: 1
    Last Post: 07-03-2011, 08:17 AM
  2. parameterized macros vs. functions
    By Tbrez in forum C Programming
    Replies: 3
    Last Post: 04-02-2009, 12:33 PM
  3. Macros vs Inline Functions
    By vb.bajpai in forum C Programming
    Replies: 4
    Last Post: 08-02-2007, 11:51 AM
  4. Macros as functions
    By Darklighter in forum Windows Programming
    Replies: 4
    Last Post: 11-25-2006, 08:56 AM
  5. Macros Vs Inline functions
    By passionate_guy in forum C++ Programming
    Replies: 4
    Last Post: 04-29-2006, 02:52 AM

Tags for this Thread