0

Reading some C files related to programming an STM32F4 series Arm controller, I found the following instruction:

/* Reset CFGR register */
RCC->CFGR = 0x00000000;

Since I never found "->" in C language I wonder if it's an instruction strictly related to configuration of STM32F4 family.
Someone could explain me the meaning of it?
Where can I find documentation about?
STM32F4 Reference Manual seems to not explain this.

JimmyB
  • 3,823
  • 2
  • 18
  • 21
LittleSaints
  • 197
  • 1
  • 2
  • 5
  • 7
    I'm voting to close this question as off-topic because this is not an electronic engineering, but a (C language) programming question. – Marcus Müller Jun 25 '19 at 09:16
  • 1
    @MarcusMüller Writing firmware for microcontrollers is considered ontopic here. *it's an instruction strictly related to configuration of STM32F4 family* makes it a firmware related question even though the answer is no. [Compare with this one](https://electronics.stackexchange.com/questions/439670/variable-declaration-with-in-c) – followed Monica to Codidact Jun 25 '19 at 09:43
  • 2
    "Since I never found "->" in C language" Then you never found a beginner-level C programming book either. Learn fundamental C programming before attempting to read C code, maybe... – Lundin Jun 25 '19 at 10:48
  • 2
    @berendi none of the question revolves around microcontroller programming. OP simply doesn't know basic C Struct pointer handling. OP claiming this is specific to STM32F4 has nothing to do with it actually being specific to microcontroller programming. (it's simply not) – Marcus Müller Jun 25 '19 at 11:06
  • @MarcusMüller I assume you are famiar with the definition of `RCC` in the stm32 headers. Using the `->` operator on a pointer acquired by converting a nonzero integer constant to a pointer is certainly *implementation defined*, maybe even *undefined behaviour*. Imagine asking this on stackoverflow, language lawyers would come down on the question with all their wrath. This question makes sense only in an embedded context. – followed Monica to Codidact Jun 25 '19 at 13:47
  • @berendi yeah, the way the unsigned integer literals behind these definitions are converted to pointers is indeed implementation-specific, but `RCC` **is** a pointer by type (through being `#define RCC ((RCC_TypeDef *) RCC_BASE)`), no matter how that pointer got its value. So, albeit the language lawyers having a point about whether the way the source code defines that pointer, it doesn't change the fact that `pointer_type->member` is really standard C and not implementation-specific at all. – Marcus Müller Jun 25 '19 at 14:02
  • @MarcusMüller everything is about embedded programming in the question, except the answer. – followed Monica to Codidact Jun 25 '19 at 14:49

2 Answers2

9

The -> operator is the structure/union pointer operator, which is definitely part of the C language. (Thanks Jeroen, see comment below).

Assume you have a struct:

struct 
{
  int width;
  int length;
} Box;

Box aBox;

Than you can get to the properties as:

int width  = aBox.width;
int length = aBox.length;

Now assume you have a pointer to that box:

Box* pBox = &aBox;

Than you have to use the -> instead of the . symbol:

int width  = pBox->width;
int length = pBox->length;

In both cases, width and length will have the same value. aBox will contain the properties and e.g. if it is located at addres 0x123456, than the content of pBox will be 0x123456.

Michel Keijzers
  • 13,867
  • 18
  • 69
  • 139
4

The -> operator is a convenient way to address a memory mapped register of a certain peripheral.

Registers in the STM32 memory map are grouped in a way that makes it possible to write a struct definition for each peripheral, having the registers of the peripheral in one unit, for example:

typedef struct
{
  __IO uint32_t CR;            /*!< RCC clock control register,       Address offset: 0x00 */
  __IO uint32_t PLLCFGR;       /*!< RCC PLL configuration register,   Address offset: 0x04 */
  __IO uint32_t CFGR;          /*!< RCC clock configuration register, Address offset: 0x08 */
   /* ... */
}  RCC_TypeDef;

It's advantage over using a separate definition for each register becomes more apparent when there are more than one peripherals of the same kind, e.g. UARTs. There is a typedef struct { /* ... */ } USART_TypeDef; similar to the above one, describing the arrangement of the UART registers, then the base address of each USART/UART is defined as pointers to a USART_TypeDef.

#define USART1              ((USART_TypeDef *) USART1_BASE)
#define USART2              ((USART_TypeDef *) USART2_BASE)

Consider the following function:

void usart1_send_byte(uint8_t data) {
  while((USART1->SR & USART_SR_TXE) == 0)
    ;
  USART1->DR = data;
}

If there were separate definitions for each register in the system headers, it would become someting like

void usart1_send_byte(uint8_t data) {
  while((*USART1_SR & USART_SR_TXE) == 0)
    ;
  *USART1_DR = data;
}

Now we would like to generalize this function to work with any UART/USART in the system, not just USART1. Converting the first variant is easy,

void usart1_send_byte(USART_TypeDef *u, uint8_t data) {
  while((u->SR & USART_SR_TXE) == 0)
    ;
  u->DR = data;
}

but the other one would become quite awkward. Should we pass the address of each register it touches as a parameter? Or use more complicated constructs like *(usart_base + USART_DR_OFFSET) every time?

  • It's actually an inconvenient way to access a memory-mapped register. The machine code generated will work with offsets rather than absolute addresses, so it must first load the base register address, then calculate the offset, which gives slower register access time. This is why bloatware libs from ST, Atmel etc results in needlessly slow code. – Lundin Jun 25 '19 at 10:51
  • 3
    @Lundin well, I've yet to see a compiler emit that code in anything but `-O0`, sorry. – Marcus Müller Jun 25 '19 at 11:08
  • @MarcusMüller Since these are `volatile` qualified structs and registers, the compiler can't very well optimize the access or it is non-conforming. – Lundin Jun 25 '19 at 11:09
  • `volatile` doesn't mean the addressing needs to be as indirect as you describe. Even if you declared the pointer, and not the object, volatile, – Marcus Müller Jun 25 '19 at 11:10
  • then: many, many architectures have Load/store instructions that are *meant* to do a base + offset access in one clock cycle. It's a pretty standard feature of ISAs. (ARM's [ldr](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289873425.htm), for example, or AVR's [equivalent](https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_LDD_Z.html)) – Marcus Müller Jun 25 '19 at 11:11
  • @Lundin In my experience the offset is calculated at compile time, so if you ask the compiler to optimize the code size you get assembly code that is pretty much as short as it can be. I must confess however that I have not used ST or Atmel; I only use CMSIS style code. – Elliot Alderson Jun 25 '19 at 11:20
  • @Lundin https://godbolt.org/z/cVQk6z – followed Monica to Codidact Jun 25 '19 at 11:23
  • I'm debugging an Atmel SAM ASF bug as we speak, gcc ARM -O3 gives me this for accessing a register part of a struct: `movs r2, #0xA5` `ldr r3, =0x40002000` `strb r2, [r3, #12]`, where the actual register address is `0x4000200C` and the struct starts at `0x40002000`. – Lundin Jun 25 '19 at 11:25
  • @berendi Yeah that code loads an offset just as I said, my point proven. `1073873152` == `0x40020100`. – Lundin Jun 25 '19 at 11:30
  • @Lundin If the function accesses a couple of registers of a single peripheral (as peripheral driver functions usually do), this approach would actually win, because the base address has to be loaded only once. Loading a full 32-bit word is an expensive operation on ARM (usually being an indexed load itself), compared to the additional cost of doing an indexed load/store instead of a non-indexed access. – followed Monica to Codidact Jun 25 '19 at 11:30
  • @Lundin it loads one address instead of three. – followed Monica to Codidact Jun 25 '19 at 11:32
  • @berendi Loading an absolute address is certainly faster than loading an absolute address and then calculating an offset. Most of the time we are only interested in writing to 1 single register, for which this code is ineffective and the compiler can't do much about it, because of `volatile`. – Lundin Jun 25 '19 at 11:33
  • 1
    @Lundin 1. [Accesses without an offset are NOT faster on ARM](http://infocenter.arm.com/help/topic/com.arm.doc.100166_0001_00_en/ric1417175924567.html) 2. because of (1.), there is no difference when accessing a single register, however there are lots of common cases where at least two registers are accessed, i.e. reading a status register, then writing a control or data register (or both), and in such cases, the `struct` approach generates shorter and faster code, like in the example linked above. – followed Monica to Codidact Jun 25 '19 at 12:09
  • One thing doesn't exclude the other. It would be trivial to have one `#define` based register map with absolute addresses, and one with structs/offsets. But that's not how ST or Atmel have written their libs. – Lundin Jun 25 '19 at 12:51