Hardware register access
The current state of this file represents a complete change of heart from the proposal made earlier.
The idea is to refer to hardware registers in the style TIMER0.MODE
, making the timer look like a C structure with the registers as members. Code that manages multiple timers can use the notation TIMER[i]->MODE
. This is achieved in the following way:
hardware.h
declares a structure with tag_timer
that has members namedMODE
, etc., at the appropriate offsets.- Structure members at a fixed offset can be described either with calculated padding between each member and the next (with the calculation done by a preprocessing step), or by using the trick of anonymous structures nested inside anonymous unions.
hardware.h
also declares instancesTIMER0
,TIMER1
, etc., with external linkage, and an array of pointersTIMER
.- In some convenient place, say
startup.c
, there's a definition of theTIMER
array as a constant table containing&TIMER0
,&TIMER1
, etc. - The linker script provides symbols
TIMER0
,TIMER1
, etc., defining each with the appropriate absolute address.
This scheme avoids the use of macros and also avoids the trick of casting a hex constant to a pointer type. Because the linker script contains one address per device, the list of commands in the script stays reasonable in length.
The provision of pointer arrays should be done selectively, including all devices that may have more than one instance on some devices. I2C seems like a case in point, with one bus on V1 and two on V2 – though actually even the V1 chip has two combined I2C/SPI devices. In any case, it's wise to have an array of device addresses on both chips, so the same driver software can be compiled for either chip. The UART is a special case, with a single legacy UART (which we call UART
) on both chips, and two extended UARTS, called UARTE0
and UARTE1
, on V2 – with UARTE0
sharing hardware with the legacy UART. An array UARTE[]
is provided on V2 but not on V1.
A minor disadvantage of this scheme is that the compiler, not knowing the absolute address of the timer, cannot reduce the reference TIMER0.MODE
to a load from a single, fixed address, and instead needs a sequence such as
ldr r0, =TIMER0 ldr r1, =0x504 ldr r2, [r0, r1]
That costs at most one extra instruction per reference to a hardware register, and may be significant in timing loops, but perhaps nowhere else. Try writing
unsigned * const GPIO_OUT = &GPIO.OUT;
and then referring to *GPIO_OUT
in the body of the loop: the address ought to be kept in a register, giving the bext possible code. It would be good to verify this with a loop that produces the fastest possible square wave by bit-banging.