I've moving over from 8 bit bare-metal registers, and having to learn some new C Kungfu to wrap my head around the CMSIS Core approach.
I have a snippet of code here from a Peripheral Access Layer from a ARM Cortex M vendor. They create this SN_WDT_TYPE structure, which you can use to set watch-dog timer registers using their notation.
Why do they use the unions? I haven't seen this kind of syntax before.
If you use unions to create structures like that, do you they go several layers deep with pointers? Memory management with the unions? Is there some C syntax thing I'm missing here?
This might be CMSIS specific, does anyone know what the ": 1" is doing in those struct declarations...? I know the __IO ties back to some CMSIS definition of read/write.
HELPFUL STUFF I FOUND AFTER COMMENTS & ANSWERS:
ARM'S CMSCIS PERIPHERAL NAMING CONVENTION -- This example code doesn't seem to confirm too gracefully...
ARM'S BITFIELD COMPILER NOTES ON STRUCTS & UNIONS
/**
* #brief Watchdog Timer (SN_WDT)
*/
typedef struct { /*!< SN_WDT Structure */
union {
__IO uint32_t CFG; /*!< Offset:0x00 WDT Configuration Register */
struct {
__IO uint32_t WDTEN : 1; /*!< WDT enable */
__IO uint32_t WDTIE : 1; /*!< WDT interrupt enable */
__IO uint32_t WDTINT : 1; /*!< WDT interrupt flag */
uint32_t : 13;
__O uint32_t WDKEY : 16; /*!< Watchdog register key */
} CFG_b; /*!< BitSize */
};
union {
__IO uint32_t CLKSOURCE; /*!< Offset:0x04 WDT Clock Source Register */
struct {
__IO uint32_t CLKSOURCE : 2; /*!< WDT clock source */
uint32_t : 14;
__O uint32_t WDKEY : 16; /*!< Watchdog register key */
} CLKSOURCE_b; /*!< BitSize */
};
union {
__IO uint32_t TC; /*!< Offset:0x08 WDT Timer Constant Register */
struct {
__IO uint32_t TC : 8; /*!< Watchdog timer constant reload value */
uint32_t : 8;
__O uint32_t WDKEY : 16; /*!< Watchdog register key */
} TC_b; /*!< BitSize */
};
union {
__O uint32_t FEED; /*!< Offset:0x0C WDT Feed Register */
struct {
__O uint32_t FV : 16; /*!< Watchdog feed value */
__O uint32_t WDKEY : 16; /*!< Watchdog register key */
} FEED_b; /*!< BitSize */
};
} SN_WDT_Type;
The union allows you to access the hardware register as either a 32 bit word or as the bit fields contained in the register. Both representations have their uses. Perhaps you are missing the bit field syntax as it is not used in most application level coding. Bit field layout is compiler specific, but CMSIS headers are built to work with the intended compilers.
Related
the following structure is a structure of registers with type timer_t
typedef struct { /*!< TIMER0 Structure */
uint32_t CFG; /*!< GPTM Configuration */
uint32_t TAMR; /*!< GPTM Timer A Mode */
uint32_t TBMR; /*!< GPTM Timer B Mode */
uint32_t CTL; /*!< GPTM Control */
uint32_t SYNC; /*!< GPTM Synchronize */
uint32_t RESERVED;
uint32_t IMR; /*!< GPTM Interrupt Mask */
uint32_t RIS; /*!< GPTM Raw Interrupt Status */
uint32_t MIS; /*!< GPTM Masked Interrupt Status */
uint32_t ICR; /*!< GPTM Interrupt Clear */
uint32_t TAILR; /*!< GPTM Timer A Interval Load */
uint32_t TBILR; /*!< GPTM Timer B Interval Load */
uint32_t TBMATCHR; /*!< GPTM Timer B Match */
uint32_t TAPR; /*!< GPTM Timer A Prescale */
uint32_t TBPR; /*!< GPTM Timer B Prescale */
uint32_t TAPMR; /*!< GPTM TimerA Prescale Match */
uint32_t TBPMR; /*!< GPTM TimerB Prescale Match */
uint32_t TAR; /*!< GPTM Timer A */
uint32_t TBR; /*!< GPTM Timer B */
uint32_t TAV; /*!< GPTM Timer A Value */
uint32_t TBV; /*!< GPTM Timer B Value */
uint32_t RTCPD; /*!< GPTM RTC Predivide */
uint32_t TAPS; /*!< GPTM Timer A Prescale Snapshot */
uint32_t TBPS; /*!< GPTM Timer B Prescale Snapshot */
uint32_t TAPV; /*!< GPTM Timer A Prescale Value */
uint32_t TBPV; /*!< GPTM Timer B Prescale Value */
uint32_t RESERVED1[981];
uint32_t PP; /*!< GPTM Peripheral Properties */
}timer_t;
and this #define TIMER0 ((timer_t *) TIMER0_BASE) is used to dereference them so I can write on them as TIMER0_BASE is the base address of the used module. now I want to declare another bit field with type timerAModeBitField_t;for every register something to be like this
typedef struct timerAModeBitField{
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
}timerAModeBitField_t;
so I can directly write to their specific bits is it possible to implement something like that so I can first dereference the register and then dereference the bit field TIMER0->TAMR->timerAmode = 0x04
In portable C, the layout of bit-fields is unspecified. So you can't use bit-fields to describe a specific layout in portable C.
However, if you're writing code for a specific architecture, your C implementation may have additional guarantees. On Arm platforms, in particular, the Arm Architecture Procedure Call Standard (AAPCS) applies. It has a rule for bit-fields, and specifically for bit-fields in C and C++. If the bit-field description were provided by your vendor and the peripheral was intended to be used on an Arm processor, the AAPCS guarantees that you're both making the same assumptions on the data layout, so your code will use the intended data layout.
However using the same data layout is not sufficient! You also have to ensure that the compiler will do the right thing when a field is written in one way (say, through the uint32_t) and then read back in the other way (say, through the bit-field). C compilers are generally allowed to assume that when memory is accessed through different types, it's different memory. This allows compilers to optimize code better.
There are several exceptions which I won't go into here, but in general you can't assume that writing memory in one way and reading it back in some other way will give consistent results. Accessing the same memory in different ways is called aliasing. What is the strict aliasing rule? has a very good explanation of how this applies to C. Be careful: violating the aliasing rules can often result in code that fails only at certain levels of optimization, and only in certain contexts (e.g. depending on the number of registers that the compiler had available for a particular block of code), so this can be hard to debug. In particular, beware that casts can often get around compiler warnings, but not against aliasing bugs: casts just tell the compiler to stop complaining about broken code, they don't make the code less broken.
One way to let the compiler know that you're accessing the same memory through different types is to put those types in a union. (This is guaranteed in C11, and not officially guaranteed but widely implemented in C99.) That is:
typedef union {
uint32_t value;
timerAModeBitField bits;
} timerAModeUnion;
typedef struct {
…
timerAModeUnion TAMR;
…
} timer_t;
Then you can reliably do things like bulk-write TIMER0->TAMR.value and read a specific bit-field via TIMER0->TAMR.bits.timerAmode.
You can use the (inner) structure as the data type of an element of another (outer) structure, like this:
typedef struct timerAModeBitField {
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
uint32_t padding : 23;
} timerAModeBitField_t;
typedef struct { /*!< TIMER0 Structure */
uint32_t CFG; /*!< GPTM Configuration */
timerAModeBitField_t TAMR; /*!< GPTM Timer A Mode */
uint32_t TBMR; /*!< GPTM Timer B Mode */
uint32_t CTL; /*!< GPTM Control */
uint32_t SYNC; /*!< GPTM Synchronize */
uint32_t RESERVED;
uint32_t IMR; /*!< GPTM Interrupt Mask */
uint32_t RIS; /*!< GPTM Raw Interrupt Status */
uint32_t MIS; /*!< GPTM Masked Interrupt Status */
uint32_t ICR; /*!< GPTM Interrupt Clear */
uint32_t TAILR; /*!< GPTM Timer A Interval Load */
uint32_t TBILR; /*!< GPTM Timer B Interval Load */
uint32_t TBMATCHR; /*!< GPTM Timer B Match */
uint32_t TAPR; /*!< GPTM Timer A Prescale */
uint32_t TBPR; /*!< GPTM Timer B Prescale */
uint32_t TAPMR; /*!< GPTM TimerA Prescale Match */
uint32_t TBPMR; /*!< GPTM TimerB Prescale Match */
uint32_t TAR; /*!< GPTM Timer A */
uint32_t TBR; /*!< GPTM Timer B */
uint32_t TAV; /*!< GPTM Timer A Value */
uint32_t TBV; /*!< GPTM Timer B Value */
uint32_t RTCPD; /*!< GPTM RTC Predivide */
uint32_t TAPS; /*!< GPTM Timer A Prescale Snapshot */
uint32_t TBPS; /*!< GPTM Timer B Prescale Snapshot */
uint32_t TAPV; /*!< GPTM Timer A Prescale Value */
uint32_t TBPV; /*!< GPTM Timer B Prescale Value */
uint32_t RESERVED1[981];
uint32_t PP; /*!< GPTM Peripheral Properties */
} timer_t;
Then you can access mode bits in this way:
TIMER0->TAMR.timerAmode = 2;
Note: You should not assign 0x04 to a bitfield of width 2, the width of the value is too big. Possible values range from 0 to 3.
EDIT:
Let's minize the example:
typedef union {
struct {
unsigned int mode: 2;
unsigned int padding: 30;
};
uint32_t value;
} control_t;
typedef struct {
control_t control;
uint32_t stuff;
} module_t;
You can always obtain the address of an element of a structure:
module_t module;
control_t* mode_p = &module.control;
mode_p->mode = 3; // write only to 2 bits
uint32_t v = mode_p->value; // read all 32 bits
Via the union you can use either the complete control register or conveniently access just some bits:
module.control.value = 0x12345678;
module.control.mode = 2;
Don't fall into the trap to think too much from a machine code perspective. Use the abstraction of the higher level language to gain better source control by the compiler. Avoid casts as much as possible, since they tell the compiler to think, "The programmer is always right, who am I to doubt her?" Even in cases where your code has errors.
Caution: In fact it is not defined by the C standard how exactly bitfields are allocated. However, compilers do this in a reproducable way, it might well be documented. Make sure you guard your code with a check on the compiler you validate for your usage.
You could try this approach using unnamed members:
#include <stdint.h>
typedef struct {
...
union {
struct {
uint32_t timerAMode : 2;
uint32_t timerACaptMode : 1;
uint32_t timeraPWMMode : 1;
uint32_t timerACountDir : 1;
uint32_t timerAMatchIntEn : 1;
uint32_t timerAWaitOnTrig : 1;
uint32_t timerASnapShotMode : 1;
uint32_t timerAIntervalLoad : 1;
};
uint32_t TAMR;
};
...
} timer_t;
#define TIMER0_BASE (0x40001000)
volatile timer_t *TIMER0 = (timer_t*) TIMER0_BASE;
int main(void)
{
TIMER0->TAMR = 0x1234;
TIMER0->timerAMode = 2;
}
By using unnamend members you can access members of the union/struct directly without adding a field name for that union.
This allows accessing TAMR at once as well as the separate fields of it.
Why in the CMSIS device file "TM4C123GH6PM.h" there are reserved structure members declared in some of the peripheral's structures for example in TIMER0_Type structure there are two reserved members.
does it have something to do with the microcontroller itself or is it concerned with word alignment and how the structure is going to be allocated in the memory?
typedef struct { /*!< TIMER0 Structure */
__IO uint32_t CFG; /*!< GPTM Configuration */
__IO uint32_t TAMR; /*!< GPTM Timer A Mode */
__IO uint32_t TBMR; /*!< GPTM Timer B Mode */
__IO uint32_t CTL; /*!< GPTM Control */
__IO uint32_t SYNC; /*!< GPTM Synchronize */
__I uint32_t RESERVED;
__IO uint32_t IMR; /*!< GPTM Interrupt Mask */
__IO uint32_t RIS; /*!< GPTM Raw Interrupt Status */
__IO uint32_t MIS; /*!< GPTM Masked Interrupt Status */
__O uint32_t ICR; /*!< GPTM Interrupt Clear */
__IO uint32_t TAILR; /*!< GPTM Timer A Interval Load */
__IO uint32_t TBILR; /*!< GPTM Timer B Interval Load */
__IO uint32_t TAMATCHR; /*!< GPTM Timer A Match */
__IO uint32_t TBMATCHR; /*!< GPTM Timer B Match */
__IO uint32_t TAPR; /*!< GPTM Timer A Prescale */
__IO uint32_t TBPR; /*!< GPTM Timer B Prescale */
__IO uint32_t TAPMR; /*!< GPTM TimerA Prescale Match */
__IO uint32_t TBPMR; /*!< GPTM TimerB Prescale Match */
__IO uint32_t TAR; /*!< GPTM Timer A */
__IO uint32_t TBR; /*!< GPTM Timer B */
__IO uint32_t TAV; /*!< GPTM Timer A Value */
__IO uint32_t TBV; /*!< GPTM Timer B Value */
__IO uint32_t RTCPD; /*!< GPTM RTC Predivide */
__IO uint32_t TAPS; /*!< GPTM Timer A Prescale Snapshot */
__IO uint32_t TBPS; /*!< GPTM Timer B Prescale Snapshot */
__IO uint32_t TAPV; /*!< GPTM Timer A Prescale Value */
__IO uint32_t TBPV; /*!< GPTM Timer B Prescale Value */
__I uint32_t RESERVED1[981];
__IO uint32_t PP; /*!< GPTM Peripheral Properties */
} TIMER0_Type;
On that particular part there is a 981 word gap between TIMER0_TBPV and TIMER0_PP, and a 1 word (32bit) gap between TIMER0_SYNC and TIMER0_IMR. From the datasheet:
The "reserved" fields simply force alignment with the hardware register map for which the type is used as an overlay. You certainly should not instantiate an object of this type in memory!
These structs are defined for easier access to memory mapped peripheral registers.
Each register has a fixed absolute address. The struct layout needs to be carefully designed such that the base address of the struct plus the offset of the member within the struct results in the register address.
As the register addresses are only partially contiguous, gaps without registers must be filled with dummy struct members, named RESERVEDxx.
They are a 1:1 map with the hardware. Say that you have only two registers:
A # 0x1000
B # 0x1004
You could define this struct:
struct {
u32* Ar;
u32* Br;
}regs;
And then assign manually the addresses:
struct regs myRegs;
myRegs.Ar = (u32*)0x1000;
myRegs.Br = (u32*)0x1004;
But doing this is error prone and also very long to do. What you can do, and what CMSIS does, is to define a struct with datatypes instead of pointers, knowing that the compiler will treat such structure as contiguous elements in the memory and will do pointer access for you.
struct {
u32 Ar;
u32 Br;
}regs;
struct regs* myRegsPtr = (struct regs*) 0x1000;
Now myRegsPtr holds the base address of the hardware register structure. Since now the C structure mimics the HW layout, accessing a member of the structure does exactly the pointer arithmetic you need for accessing the register you requested.
myRegs->Br; //This does 0x1000 + sizeof(all members before Br), reaching the correct offset.
Now back to your question. When we look at the datasheet we see this particular HW structure:
Can you spot the jump of 981*4 bytes? That is where you RESERVED area in your struct come from. In order to access the last register, the compiler must know that there are this amount of bytes to "jump".
I have the following structs (from a library I'm using) with some fields and I would like to assign with OR operation a new value. But I'm debugging and I can't see how anything is writing there in stm32l4xx_hal_tim.c file:
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array This array is accessed by a #ref DMA_Handle_index */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
}TIM_HandleTypeDef;
typedef struct
{
__IO uint32_t CR1; /*!< TIM control register 1, Address offset: 0x00 */
__IO uint32_t CR2; /*!< TIM control register 2, Address offset: 0x04 */
__IO uint32_t SMCR; /*!< TIM slave mode control register, Address offset: 0x08 */
__IO uint32_t DIER; /*!< TIM DMA/interrupt enable register, Address offset: 0x0C */
__IO uint32_t SR; /*!< TIM status register, Address offset: 0x10 */
__IO uint32_t EGR; /*!< TIM event generation register, Address offset: 0x14 */
__IO uint32_t CCMR1; /*!< TIM capture/compare mode register 1, Address offset: 0x18 */
__IO uint32_t CCMR2; /*!< TIM capture/compare mode register 2, Address offset: 0x1C */
__IO uint32_t CCER; /*!< TIM capture/compare enable register, Address offset: 0x20 */
__IO uint32_t CNT; /*!< TIM counter register,
} TIM_TypeDef;
I have a part of code where I defined: TIM_HandleTypeDef TIMER_Struct;
And I would like to access the field "CR1" of TIM_TypeDef struct that is "*Instance" field of TIM_HandleTypeDef. So I have done it by this way in a function DRV_TIMER_init():
#include "main.h"
#include "stm32l4xx_hal_tim.h"
uint32_t uwPrescalerValue = 0;
TIM_HandleTypeDef TIMER_Struct;
void DRV_TIMER_init(void);
int main(void)
{
DRV_TIMER_init();
while(1)
{
}
}
//where uint32_t SystemCoreClock = 4000000; in other system source file.
void DRV_TIMER_init(void)
{
uwPrescalerValue = (uint32_t)(SystemCoreClock / 1000000) - 1;
TIMER_Struct.Init.Period = 100 - 1;
TIMER_Struct.Init.Prescaler = uwPrescalerValue;
TIMER_Struct.Init.ClockDivision = 0; // these accesses work
TIMER_Struct.Instance -> CR1 |= 0x01 << 3; // this no works
}
Even if I write directly:
TIMER_Struct.Instance -> CR1 = 0xFFFFFFFF;
It stills without having effect.
I think It could be a fact that I'm not controlling appropiately the pointer access or similar. But I don't see how can I access and modify the content of the commented field. Since I can see (debug mode) how the rest of struct fields updates are writen correctly.
Any correction suggested here?
TIMER_Struct.Instance -> CR1 = 0xFFFFFFFF;
I try different ways to get it with no success. I need new ideas.
TIM_TypeDef *Instance;
is just a pointer which is not pointing to anywhere so you can't dereference it
you have defined somewhere in the lib a macro like this:
#define TIM (TIM_TypeDef*)0xDEADBEEF;
this is how you map the registers to the memory
try modifying your code like this:
TIMER_Struct.Instance = (TIM_Typedef*)0xDEADBEEF;
or just
TIMER_Struct.Instance = TIM;
than
TIMER_Struct.Instance -> CR1 |= 0x01 << 3;
should work
I think you forgot to define TIMER_Struct.Instance
TIM_HandleTypeDef TIMER_Struct;
TIMER_Struct.Instance = TIM1;
//Now you can access TIMER_Struct.Instance
TIMER_Struct.Instance->CR1 = (uint32_t)0xFFFFFFFF;
But I prefer to use CMSIS for writing to registers. No need for the HAL. With CMSIS writing to a register could look like:
TIM1->CR1=(uint32_t)0xFFFFFFFF
I have found the solution. First you must enable the peripheral clock. If not, there won't be any effect over timer registers, because the pointer structs fields were pointing directly to the hardware registers allocation. This was the cause why I can't see any update when watching my struct var at debugging.
I was enabling clk (with hal_xxx_init()) after writing to the allocation. That was my mistake.
Here in this part of code is the correction:
//where uint32_t SystemCoreClock = 4000000; in other system source file.
void DRV_TIMER_init(void)
{
uwPrescalerValue = (uint32_t)(SystemCoreClock / 1000000) - 1;
TIMER_Struct.Init.Period = 100 - 1;
TIMER_Struct.Init.Prescaler = uwPrescalerValue;
TIMER_Struct.Init.ClockDivision = 0; // these accesses work
if (HAL_TIM_Base_Init(&TIMER_Struct) != HAL_OK)
{
/* Initialization Error */
TIM_Error_Handler();
}
TIMER_Struct.Instance -> CR1 |= 0x01 << 3; // this works now
}
If you try to put the last sentence before the:
HAL_TIM_Base_Init(&TIMER_Struct);
no
TIMER_Struct.Instance -> CR1 = xxx; //write mode register acces
will have effect.
Could someone please explain the following construction to a beginner:
typedef struct
{
__IO uint32_t CTRL; /**< Control Register */
__IO uint32_t CNT; /**< Counter Value Register */
__IO uint32_t COMP0; /**< Compare Value Register 0 */
__IO uint32_t COMP1; /**< Compare Value Register 1 */
__I uint32_t IF; /**< Interrupt Flag Register */
__IO uint32_t IFS; /**< Interrupt Flag Set Register */
__IO uint32_t IFC; /**< Interrupt Flag Clear Register */
__IO uint32_t IEN; /**< Interrupt Enable Register */
__IO uint32_t FREEZE; /**< Freeze Register */
__I uint32_t SYNCBUSY; /**< Synchronization Busy Register */
} RTC_TypeDef;
#define RTC_BASE (0x40080000UL)
#define RTC ((RTC_TypeDef *) RTC_BASE)
especially the last line
Why are the brackets so unusual? What does the * mean? Pointer or multiply operator ?
Thanks
Why are the brackets so unusual? What does the * mean? Pointer or
multiply operator ?
* here is a pointer not multiplication operator.
There is nothing unusual here about the macro. It is just type casting the address to be of type struct RTC_TypeDef
Whenever you will encounter a single or multiple * after a datatype (even a user defined datatype), its a pointer.
What you are calling a constructions:
typedef struct
{
__IO uint32_t CTRL; /**< Control Register */
__IO uint32_t CNT; /**< Counter Value Register */
__IO uint32_t COMP0; /**< Compare Value Register 0 */
__IO uint32_t COMP1; /**< Compare Value Register 1 */
__I uint32_t IF; /**< Interrupt Flag Register */
__IO uint32_t IFS; /**< Interrupt Flag Set Register */
__IO uint32_t IFC; /**< Interrupt Flag Clear Register */
__IO uint32_t IEN; /**< Interrupt Enable Register */
__IO uint32_t FREEZE; /**< Freeze Register */
__I uint32_t SYNCBUSY; /**< Synchronization Busy Register */
} RTC_TypeDef;
is a structure and programmatically called a struct, a user-defined datatype. struct is keyword in C. And so is typedef. To keep it simple as of now, I'll say, typedef here is specifically used to have the convenience to write RTC_TypeDef; instead of having the need to write struct stRTC for example.
RTC_BASE is a macro. So, before compilation pre-processor will replace RTC_BASE with the value (0x40080000UL), again, it is for convenience and readability.
This last line:
#define RTC ((RTC_TypeDef *) RTC_BASE)
also is a macro defined. Which is nothing but a pointer of the RTC_Typedef type, pointing to whatever is located at the address (0x40080000UL)
So, before the compilation the pre-processor will replace all instances of RTC with ((RTC_TypeDef *)0x40080000UL).
If you increment RTC pointer, it will increment by sizeof(RTC_Typedef).
The structure definition is pretty straight forward, variables are encapsulated in a structure. That structure can then be referenced as RTC_TypeDef because you use typedef.
typedef struct
{
__IO uint32_t CTRL; /**< Control Register */
__IO uint32_t CNT; /**< Counter Value Register */
__IO uint32_t COMP0; /**< Compare Value Register 0 */
__IO uint32_t COMP1; /**< Compare Value Register 1 */
__I uint32_t IF; /**< Interrupt Flag Register */
__IO uint32_t IFS; /**< Interrupt Flag Set Register */
__IO uint32_t IFC; /**< Interrupt Flag Clear Register */
__IO uint32_t IEN; /**< Interrupt Enable Register */
__IO uint32_t FREEZE; /**< Freeze Register */
__I uint32_t SYNCBUSY; /**< Synchronization Busy Register */
} RTC_TypeDef; /** #} */
The following line means every time you write RTC_BASE, the compiler will replace that line with (0x40080000UL)
#define RTC_BASE (0x40080000UL)
And finally, the following line can be broken into two parts:
(RTB_TypeDef *)RTC_BASE
Which means (RTB_TypeDef *)(0x40080000UL) - basically tells the compiler to treat memory address 0x40080000UL as a pointer to RTB_TypeDef
and the extra parenthesis are there to denote a single variable as a compund expression RTC.
From that point on, whenever the compiler sees RTC, it will replace it with (RTB_TypeDef *)(0x40080000UL), basically a convenient way to mark the beginning of this structure in memory
I have just started to explore the CMSIS for ARM controllers. It seems rather convenient to use it, however I was wondering where are the actual register values defined. Let's just take for example the GPIOs.
There is a structure GPIOA_AHB_Type defined with various members. Then, for GPIOB, there is a memory (or register?) address defined, let's say GPIOB_AHB_BASE. Afterwards, a pointer is set to GPIOB_AHB_BASE, like this:
#define GPIOB_AHB ((GPIOA_AHB_Type*) GPIOB_AHB_BASE)
GPIOB_AHB's member variables as GPIOB_AHB->DIR for example, to set it input or output. My question is, where precisely are these member variables initialized? I guess the actual address of the registers is device specific, so I tried to find them in the device specific header, but all I found was the GPIOB_AHB_BASE define and the declaration of the member variables. How does the compiler know that when I type GPIOB_AHB->DIR, I want to write into the register that sets that port's I/O direction?
If you look in your CMSIS header, you'll see all the structure definitions. Here's an example from my current project:
typedef struct
{
__IO uint32_t DATA; /*!< Port A Data Register */
__IO uint32_t CR; /*!< Port A Output Control Register */
__IO uint32_t FR1; /*!< Port A Function Register 1 */
__IO uint32_t FR2; /*!< Port A Function Register 2 */
uint32_t RESERVED0[6];
__IO uint32_t OD; /*!< Port A Open Drain Control Register */
__IO uint32_t PUP; /*!< Port A Pull-up Control Register */
uint32_t RESERVED1[2];
__IO uint32_t IE; /*!< Port A Input Control Register */
} TSB_PA_TypeDef;
Later on, a pointer to a structure of that type is defined:
#define PERI_BASE (0x40000000UL)
#define TSB_PA_BASE (PERI_BASE + 0x00C0000UL)
#define TSB_PA (( TSB_PA_TypeDef *) TSB_PA_BASE)
So that you can use it like:
TSB_PA->CR |= (1U << 2); // make Port A, bit 2 an output
value = TSB_PA->DATA & (1U << 5); // read Port A, bit 5.