any further guidance on where I can read about function pointers and structs in embedded design
Do you understand any assembly languages, or microcontroller architectures at the hardware level?
I've found it very useful to understanding C (which some people have called "a high level assembler"), to be able to imagine exactly how it compiles onto the (or "a", anyway) target device.
For example, a function pointer uses the indirect call mechanism; put the address of a function in a register and then do "icall" or the equivalent via that register. The advantage is that you can load that register from all sorts of useful places, like tables (character dispatch table for an editor, say) or a structure (byte write and read functions associated with a higher level 'device' structure.)
Structures mapped onto io registers are particularly useful (IMO) when you have duplicated functions in the chip. For instance, on an AVR you might have up to four uarts, all of which look like:
typedef struct uart_t_ {
volatile uint8_t ucsra; //status A
volatile uint8_t ucsrb; //status B
volatile uint8_t ucsrc; //status C
uint8_t u_res1; // reserved (not used)
uint8_t ubrrl; //bitrate low
uint8_t ubrrh; //bitrate high
volatile uint8_t data; // rx/tx data.
} uart_t;
So instead of writing code for each uart, you can stick a pointer into a variable (at some risk of being ugly and bug-attracting):
uart_senddata('a', (uart_t *) (&UCSR0A));
At one point I had re-written the Arduino serial code along these lines. It got a lot smaller, and prettier.
Alas, this does not seem to be the way that chip vendors like to describe their peripherals. There are some dangers; the C language definition does not tightly control how a compiler should lay out a structure. Some compilers require (non-standard) additions to prevent them from adding padding, or reordering "things" (endianness, for one.) Also, at the bit level, bitfields are not strongly defined. So code written with structures may not be very portable...