I've recently taken an interest in embedded development and am wondering how most people document their projects. For example:

  • Each pin on PORTE is wired to an LED and is aliased as LEDPORT.
  • Methods related to the LED functionality is located in led.h.
  • Timer/Counter 0 on PORTC controls the blink rate of the LEDS.


I can certainly use a bullet list like this while the project is small, but I have a feeling that it will become unwieldy as the project grows into something more complex. How is this typically done? Is there a preferred format for this kind of documentation in the professional embedded development world?

After receiving some feedback, I'm thinking of something like this, does it seem reasonable?

|E   |0  |Led0      |
|E   |1  |Led1      |
|... |...|...       |
|C   |0  |Timer     |Controls the Led blink rate
  • \$\begingroup\$ Can you create a class for LED functionality and encapsulate the LED port (PORTE) inside that class? The port may be hard-coded or assigned through the constructor. \$\endgroup\$ Aug 2 '15 at 19:05
  • \$\begingroup\$ It's C @NickAlexeev. No classes, but I did encapsulate it inside its own file & header. \$\endgroup\$
    – RubberDuck
    Aug 2 '15 at 19:26
  • \$\begingroup\$ What device are you using? It really depends on that ( kind of opinion oriented I know), because you want to adopt the same structure as your device to make the doc consistent. \$\endgroup\$
    – MathieuL
    Aug 10 '15 at 17:17
  • \$\begingroup\$ I wouldn't have thought it would matter @MathieuL, but I'm currently using an ATXMega128A. \$\endgroup\$
    – RubberDuck
    Aug 10 '15 at 17:23
  • 1
    \$\begingroup\$ I work on a project that has hundreds of I/O points. We use Excel spreadsheets to document all the I/O points. Since all the I/O is distributed across several microcontroller boards, I think multiple excel sheets are used, organized by where in the machine they physically are (the sensors and actuators, that is) \$\endgroup\$
    – lyndon
    Aug 16 '15 at 2:01

The short answer is "whatever makes sense to you and the rest of your team".

Since that isn't very informative as far as how to implement something, ultimately communicating the intended behavior is the point.

Comments can't be tested. They are a risk for becoming outdated and unmaintained in general. Comments can help though with understanding the why. There are other smart engineers like yourself that will figure out the how by reading the actual source.

I would recommend doing some research on writing a Hardware Abstraction Layer (HAL). This does a couple of things.

  1. A HAL will show in one file what pins are being used for what purpose. It might also be advisable to indicate which pins are not used (they come in handy for debug/bringup of new designs, so take the extra two minutes to document unused ports/pins now and save yourself 15 minutes in searching for them when you need them.)

  2. By writing a HAL, the code that uses your HAL (the embedded application) won't care what the actual pinout is. This will ease in portability when (not if) you need to change micros in that the brunt of the work that would have to take place is in the HAL itself and not in your application.

In summary, there's nothing wrong with comments, but the executable code is the only thing that matters in terms of what is actually happening.

  • \$\begingroup\$ One reason I write unit tests is because code is the only documentation that's never out of date. I see this as an application of that principal. ++ \$\endgroup\$
    – RubberDuck
    Aug 12 '15 at 10:32
  • 1
    \$\begingroup\$ Roughly, the port mappings will just be a project-specific h file which contains all port mappings, such as #define LED0_PORT PORTE and #define LED0 (1<<0). And then in the hardware-independent part, you will write things like LED0_PORT |= LED0;. Should you at a later point need to move the LED0 to another pin in another PCB revision, just edit the h file. And make sure to use exactly the same name for PCB signals as the C macros/variables, to minimize confusion between hw and sw (especially if hw and sw are made by different guys). \$\endgroup\$
    – Lundin
    Aug 17 '15 at 14:46

I found the table-like approach quite uesefull, but for myself and for users of my hardware (mostly students). This is from a simple LPC1114 board, where most uC pins are provided on connectors:

enter image description here

This table (actually only a part) is much more complicated, because most pins are multiplexed:

enter image description here

Note that in both cases the documentation is for a board that is made (and hence documented) by me, but programmed by someone else, and used for more than one purpose. Hence the hardware design has a 'life' of its own, separate from any software. When you have a project for which you design both the hardware and the software your approach might be different.

But in the end you (and your readers) will have to decide what is most usefull.


For the most part, the "usual" documentation would apply, as with any other project.

For the ports and bit fields, I find a table based approach to be the best. The table has a column for each bit and a description column. In each row, the bit combinations are laid out, typically using 1 or 0 and the don't care with a x or blank, the description describes the meaning of the combination. More lengthy discussions are left as notes or further paragraphs with more detail.

  • 2
    \$\begingroup\$ I like that. Gives it a visual element that I find appropriate. \$\endgroup\$
    – RubberDuck
    Aug 2 '15 at 20:39
  • \$\begingroup\$ Technically "don't care" should be a "X". \$\endgroup\$
    – Matt Young
    Aug 11 '15 at 22:09

I'm an old-school PIC assembly-type person, so my suggestions may not be applicable to what you are doing. But in general, I document what the port pin is doing right at the point in the code where the pin is defined.

Here is an example:

    ADC_INIT        EQU b'00000100' ; ra0,1,3 = a/d   ra2,4,5 = digital
;NOTE: RA3 is a/d so that RA0,1 can be A i/p:  watch out for R-M-W accesses!

; PORT A Device Bits    note: ra 0,1 a/d inputs so no labels for them
    #define _MUXCLK  RA,2        ; RA2, a/d ext mux clk out (active LO)
    #define _MUXRST  RA,3        ; RA3, a/d ext mux reset out (active HI)
    #define _PBWR    RA,4        ; RA4, "WRITE" button (active LO)
    #define _PBRD    RA,5        ; RA5, "READ" button (active LO)

RA_INIT         EQU b'00010000' ; turn off open drain o/p ra4 (early '71s)
DDR_A           EQU b'00110011' ; ddr: ra 2,3==out,  ra 0,1,4,5==in
DIPWRMSK        EQU b'00010000' ;
DIPRDMSK        EQU b'00100000' ;

;WARNING: Output pin MUXRST is configured as a/d input in ADCON1 (the smallest
;possible a/d input configuration) which means that it ALWAYS reads 0 for
;digital i/p.  Any port A R-M-W instructions such as bsf, bcf, tstf, xor, ior
;will force MUXRST LO.  MUXCLK is also o/p: anytime MUXCLK changes, MUXRST will
;go LO.  Reccomend NEVER to use any other pins on port RA as outputs.

; PORT B Device Bits
    #define _IPCRXD RB,0        ; RB0, IPC data input (active LO)
    #define _IPCTXD RB,1        ; RB1, IPC data out (active HI)
    #define _DHTRLD RB,2        ; RB2, display heater LEDs data output
    #define _DSTAT  RB,3        ; RB3, display status & inputs (bidirectional)
    #define _STROBE RB,4        ; RB4, ctrl, DipSw, display strobe, eeprom !CS
    #define _SERCLK RB,5        ; RB5, ctrl, DipSw, display CLOCK
    #define _EECLK  RB,6        ; RB6, eeprom clock
    #define _SERDAT RB,7        ; RB7, ctrl, DipSw, eeprom DATA (bidirectional)

RB_INIT         EQU b'00000001' ;initial port B data status
DDR_B           EQU b'10001001' ;ddr: rb 1,2,4,5,6==out,  rb 0,3,7==in
DDR_BLO         EQU b'10001001' ;AND  0s force bits LO
DDR_BHI         EQU b'00000001' ;OR  1s force bits HI

; PORT C Device Bits
   #define _LCD0    RC,0        ; RC0, LCD bit 4    Note: 4 bit mode: present
   #define _LCD1    RC,1        ; RC1, LCD bit 5    upper nybble first, strobe,
   #define _LCD2    RC,2        ; RC2, LCD bit 6    present lower nybble, strobe
   #define _LCD3    RC,3        ; RC3, LCD bit 7
   #define _LCDS    RC,4        ; RC4, LCD reg select: 0==command  1==data
   #define _LCDE    RC,5        ; RC5, E clock: normally ==0, pulse 1 to strobe
   #define _SRTX    RC,6        ; RC6, RS-232 Tx bit
   #define _SRRX    RC,7        ; RC7, RS-232 Rx Bit

RC_INIT         EQU b'00000000' ; initial port C status
DDR_C           EQU b'11000000' ; port C ddr: rc0..6==out, rc7==in
;b6==i/p till board layout error fixed or patched

; main board control s/r bits           all outputs active HI
;b0=K8  b1=K7   b2=K6   b3=K5   b4=K4   b5=K3   b6=K2   b7=K1
    #define _MGAS_M MAINRLY,0   ;24 Vac main gas valve
    #define _BLOWR6 MAINRLY,1   ;exhaust blower 6
    #define _BLOWR7 MAINRLY,2   ;exhaust blower 7
    #define _PRHT2  MAINRLY,3   ;1== preheat ON
    #define _PRHT1  MAINRLY,4   ;1== preheat ON
    #define _Z1GAS  MAINRLY,5   ;1== zone gas ON
    #define _Z1SBY  MAINRLY,6   ;1== standby mode (0==full heat)
    #define _SW24V  MAINRLY,7   ;24 Vac Power relay

; expansion board control s/r bits       all outputs active HI
;b0=K8  b1=K7   b2=K6   b3=K5   b4=K4   b5=K3   b6=K2   b7=K1
    #define _PRHT4  EXPRLY,0    ;1== preheat ON
    #define _PRHT3  EXPRLY,1    ;1== preheat ON
    #define _Z4SBY  EXPRLY,2    ;1== standby mode (0==full heat)
    #define _Z4GAS  EXPRLY,3    ;1== zone gas ON
    #define _Z2GAS  EXPRLY,4    ;1== zone gas ON
    #define _Z2SBY  EXPRLY,5    ;1== standby mode (0==full heat)
    #define _Z3GAS  EXPRLY,6    ;1== zone gas ON
    #define _Z3SBY  EXPRLY,7    ;1== standby mode (0==full heat)

;off board relay control bits
;b0=K1  b1=K2   b2=K3   b3=K4   b4,b5=dip sw mux    b6=K5   b7=K6
    #define _MGAS_O OFBRLY,0    ;110 Vac main gas valve
    #define _BLOWR1 OFBRLY,1    ;blower 1
    #define _BLOWR2 OFBRLY,2    ;blower 2
    #define _BLOWR3 OFBRLY,3    ;blower 3
    #define _DSMUXB OFBRLY,4    ;dip sw selector mux ctrl bit B
    #define _DSMUXA OFBRLY,5    ;dip sw selector mux ctrl bit A
    #define _BLOWR4 OFBRLY,6    ;blower 5
    #define _BLOWR5 OFBRLY,7    ;blower 4
;NOTE: background routines write OFBRLY bits 0-3 and ignore OFBRLY bits 4-7.
;Dip sw mux bits are mapped to bit positions 4&5.  OFBRLY bits 4&5 are mapped
;into bit positions 6&7.  OFBRLY bits 6&7 are ignored and are intended to be
;used as flags for blowers 6&7 (MAINRLY bits 1&2).

;local trinary switch inputs (SW1EVEN, SW1ODD)
Z1MASK          EQU b'10000000' ;zone 1 mask
Z2MASK          EQU b'01000000' ;zone 2 mask
;Z3MASK          EQU b'00100000' ;zone 3 mask
BTHOLD          EQU b'00010000' ;batch timer hold: 00=once, 11=always
STRTCAN         EQU b'00001000' ;batch timer start - stop switch
UPDN            EQU b'00000100' ;batch timer up - down switch

What I use:

On the schematic: the name would be "PA1_INDICATOR_LED" In the code somewhere:

define INDICATOR_LED_ON() ()

Where the parenthesis are filled in with whatever code flips the bit on your HW.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.