1

Let say there is a 32 bits register defined as TIMER and its 32 bits address TIMER_ADDR in the memory (DDRAM).

uint32_t TIMER_ADDR; // 32 bits address declared as uint32_t

The layout of TIMER is defined as:

struct timer {
    uint32_t start:1;
    uint32_t mode: 3;
    uint32_t init: 4;
    uint32_t value:24
}

Later I defined a local var loc_timer as:

struct timer loc_timer;

How can I to read this register to a local register in the program so I can modify the content

loc_timer.mode = 4;
loc_timer.init = 10;

and write it back to register TIMER ?

something like

(*(uint32_t *))&loc_timer = (*((uint32_t *)(TIMER_ADDR))); // read
(*((uint32_t *)(TIMER_ADDR))) = (*(uint32_t *))&loc_timer; // write

but it does not work :-(

Asam Padeh
  • 135
  • 2
  • 9

3 Answers3

3

Like this:

struct timer loc_timer = *(struct timer *)TIMER_ADDR; // read register
loc_timer.mode = 4;                                   // modify fields
loc_timer.init = 10;
*(struct timer *)TIMER_ADDR = loc_timer;              // write register back again

Note that since this is a memory-mapped register you should treat it as volatile, e.g.

volatile struct_timer * const timer = (struct timer *)TIMER_ADDR;
struct timer loc_timer = *timer;                      // read register
loc_timer.mode = 4;                                   // modify fields
loc_timer.init = 10;
*timer = loc_timer;                                   // write register back again
Paul R
  • 208,748
  • 37
  • 389
  • 560
  • The fields should probalby be volatile. – Devolus Dec 20 '13 at 09:58
  • @Devolus: your comment overlapped with my edit, but yes, it's safest to use `volatile`, even though in this particular case it's probably not needed. – Paul R Dec 20 '13 at 10:01
  • Hi Paul, compiler warns me cast to pointer from integer of different size. I tried to simulate this in Linux gcc although it is not the right way to do since I do not have the hardware yet. – Asam Padeh Dec 20 '13 at 10:17
  • My guess is that you are compiling for 64 bits, so the cast of a 32 bit integer to a 64 bit pointer will generate a warning. If you compile with `gcc -m32 ...` this should not happen. – Paul R Dec 20 '13 at 10:26
  • @PaulR volatile is _always_ needed when reading a hardware register. – Lundin Dec 20 '13 at 10:30
  • 1
    @Lundin: `volatile` is not always *needed*, but it is always good *practice*, in the interest of defensive programming. – Paul R Dec 20 '13 at 10:32
  • @PaulR It is always needed in case your compiler is half-decent. Any half decent compiler will optimize code such as for example `uint32_t MY_REGISTER = 5; ... if(MY_REGISTER == something)` and then the program will get built incorrectly. The only case where it isn't needed is when the compiler's optimizer is broken or disabled entirely. – Lundin Dec 20 '13 at 10:55
  • 1
    Yes, I'm fully aware of that, and I'm not arguing against using `volatile` in general - I'm just being pedantic because there are cases where it is not actually needed, e.g. registers where the value never changes between reads (i.e. registers that behave in an identical way to memory). – Paul R Dec 20 '13 at 11:07
1

Problem 1: the register isn't declared as volatile so reading from it or writing to it will not work. The compiler may decide to optimize the read to a different value than expected, post-phone it until later, or to skip it entirely.

Problem 2: the register is a 32 bit variable called "TIMER_ADDR". Does it contain an address, if so, why was it not declared as a pointer? Impossible to tell for the reader.

Problem 3: you can't use bit fields for bit mapping of hardware registers, for numerous reasons. See this.

Problem 4: you can't use structs nor bit fields for mapping of hardware registers, without a guarantee that no padding is enabled. You need a compiler-specific pragma or a compile time static check to protect against this.


If we ignore the above and also make a guess that TIMER_ADDR is indeed a variable and not an address to one, then the solution would be:

struct timer loc_timer = *(struct timer*) &TIMER_ADDR;
// and the other way around:
TIMER_ADDR = *(uint32_t*) &loc_timer;

Formally, such casts are undefined behavior and the compile might warn about them. In practice, it will work as long as we are certain that there are no alignment or padding issues.

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Hi @Lundin, I edited original post, TIMER_ADDR indeed is 32bit address of TIMER. It is not a variable. – Asam Padeh Dec 20 '13 at 20:48
  • Hi @Lundin, due to hardware requirement I need to copy the register value to a local variable, do modification, and write the new value again back to register in a complete 32 bits, instead of modifying the field using pointer approach like Dan Saks popular white paper. – Asam Padeh Dec 20 '13 at 20:58
0

After some investigations, here is the fact, PROGRAMMER knows what type of data TIMER_ADDR is pointing at (i.e struct timer) so he/she should be able to dereference it correctly. Otherwise this exercise is pointless and will print garbage !!!

So in our case:

struct timer loc_timer;  
loc_timer = *(struct timer *)TIMER_ADDR;  

// Modify some struct members  

// Copy back loc_timer to register  
*(struct timer *)TIMER_ADDR = local_timer;  

// Print new content of TIMER register  
printf("new value  = %08x\n", *(struct timer *)TIMER_ADDR);   
Asam Padeh
  • 135
  • 2
  • 9