I did not yet read the dip but here are some of my thoughts:

At the old days peripherals were simple. An uart might have a control register, a status register and a data register, 8 bit each. It just did not matter how they were accessed. Now a peripheral like usb or ethernet may have tens of 32 bit registers.

The Basic language did not have pointers or any way to address a certain location of memory. Several extensions were made to get access to system locations. One common extension was poke and peek functions. They had 16 bit address and 8 bit data. Basic did not have any data types or stuctures. D has pointers that can be used to access memory. It also has several data types. A library function does not know if it should do 8/16/32/64 bit access without templates. That would be too complicated for such a low level operation like register access.

The registers of a peripheral may be defined as a struct and a pointer of this struct type is used to access the registers. There are individual registers but there may also be some sub register sets inside the register set. A peripheral may have common registers and then per channel registers. The register struct may then have substructs or an array of register sets that may be accessed as structs or arrays.

Yes, there are different kind of registers.
- Normal registers can be read and written. These are used as normal control and status registers. Some bits may be changed by hardware at any time. This may be a problem because it is impossible to have a fully atomic access. The time between read and write should be as short as possible. - Read only registers may be used to represent the capabilities of the peripheral or calibration values. They always return the same data. Status registers represent the current state of the hardware and may change any time when the conditions change. Write to these registers has no effect. - Write only registers are used to send data. The data packet is written byte by byte to this same address. These type of registers are also used to clear status. Reading the register may return the last data or zero or anything else and the value should be ignored. - Bidirectional registers are used as data registers. A read will return the last received byte and a write will transmit the byte written.

Usually it does not matter if these registers are accessed wrong way (write a read only or read a write only) so there is no need to mark them different. They can all be volatile.


It is also common that one register has mixed read/write, read only and write only bits. Many registers have also undefined/reserved bits, which sometimes should be written with zeros and sometimes left as they are.

One of the most common operations is to wait some status:
while ((regs.status&0x80)==0)  { /* check timeout here */ }
The way to clear the status may be one of:
- write directly to the status bit
  regs.status &= 0xffffff7f;
- write a 1 to the bit
  regs.status |= 0x80;
- sometimes writing 0 to other bits has no effect and there is no need to read-modify-write
  regs.status = 0x80;
- sometimes status is cleared by writing to another bit
  regs.status |= 0x200;
- sometimes there is a separate clear register
  regs.statusclear = 0x80;
- sometimes accessing the data register clears status automatically - sometimes reading the status register clears the status. In this case all status bits have to be checked at once.

Many of these have the result that reading the register does not give back the data that was written.

And no, I did not read this on Wikipedia. All these forms of access exist in the processor I use (STM32F407) It seems that several teams have made the peripherals on the chip and every peripheral has its own way to access it.


Another thing is: do I need to mark every member in a struct volatile or is it enough to mark the struct definition or the struct pointer. Will it go transitively to every member of an array of substructs or do I need mark the substructs volatile?

One thing is the struct pointer. The peripherals have a fixed address. If there is only one peripheral, the address can be a compile time constant. If there are several similar peripherals, the address may be known at compile time or it could be immutable that is initialized at start. Now I have to make the pointer shared to have the struct members shared. This means the pointer is also mutable and volatile. The pointer can not be cached and has to be fetched again every time the struct variables are accessed. This decreases performance.


D has been marketed as a system language. Accessing registers is an essential part of system programming. Whatever the method is, it has to be in the language, not an external library function.

Reply via email to