Jack Ganssle, Editor of The Embedded Muse Jack Ganssle's Blog
RSS Feed This is Jack's outlet for thoughts about designing and programming embedded systems. It's a complement to my bi-weekly newsletter The Embedded Muse. Contact me at jack@ganssle.com. I'm an old-timer engineer who still finds the field endlessly fascinating (bio).

For novel ideas about building embedded systems (both hardware and firmware), join the 40,000+ engineers who subscribe to The Embedded Muse, a free biweekly newsletter. The Muse has no hype and no vendor PR. Click here to subscribe.

How I write Code

December 16, 2019

"If you think good design is expensive, what about the cost of bad design?"

I believe strongly in solid design of all engineering artifacts. Unless it's something trivial, I'll start with at least a rough block diagram before creating a schematic. For a technical document the table of contents comes first. In both cases it's obvious that these are usually rough and tend to change as the schematic or document is created. But they give a sense of direction that guides the formation of the artifact.

Ditto for code.

And ditto for the details of writing individual functions.

First, I write the header block with a description of the function's function, and passed parameters. For a trivial procedure, that might be the only commenting needed. For instance:

/**************************************************
Function to toggle the debugging pins.

Inputs:
  - Which trigger (0 or 1) to pulse
  - Time (in usec) to assert the pin

Coded 01/14/2017 by JGG
**************************************************/

Then it's easy to fill in the C:

void trigger(int pin, int time_usec)
  {
  switch(pin)
    {
    case 0:
      trigger0 = 1;
      wait_us(time_usec);
      trigger0 = 0;
      break;
    case 1:
      trigger1 = 1;
      wait_us(time_usec);
     trigger1 = 0;
    }
  }

A bit more complex function starts the same way, with a complete header block:

/**********************************************************************
  Function to read the analog input selected by the muxes. It also
corrects the reading by applying the scaling factor determined by
a prior read of the reference voltage. The correction is needed as
experience with this board shows the A/D is a bit sensitive to 
temperature. Vref, from an AD780, is very stable and runs through the 
same muxes and ADC.
  If the battery is VREF, it reads the reference and recomputes
the scale factor.

  The scale factor is an average of AVG_REF reads of the reference
voltage normalized to the known reference voltage (VREF_NOMINAL). 

Inputs:
  - Battery desired (from battery enum)
  - Which voltage of that battery to read (from which_voltage enum)
 
Returns:
  - Analog voltage 

Coded 01/16/2013 by JGG
**********************************************************************/

Then, I write the function declaration, and the most important comments that will go in that function:

float analog_read(battery batt, which_voltage volt)
{
 
  // Set analog mux to desired batt and voltage

  // If VREF, read the AD780 and recompute scale factor
  // Note: Normalize the scale to ADC_REF, the ADC
  //    reference voltage

I'll present my Better Firmware Faster seminar in Melbourne, Australia February 20. All are invited. More info here.

// If not VREF, read & average the ADC and scale result }

Now it's a trivial matter to fill in the code:

float analog_read(battery batt, which_voltage volt)
{
  int i;
  float result = 0;
   
  analog_select(batt, volt);                 // Set analog mux to desired batt and voltage
  if(batt == VREF)                           // If VREF, read the AD780 and recompute scale factor
    {
    scale = 0.0;
    for(i = 0; i < AVG_REF; i++)scale = scale + adc.read();
    scale = scale * ADC_REF / float(AVG_REF);// Note: Normalize the scale to ADC_REF, the ADC
                                             //   reference voltage
    scale = VREF_NOMINAL / scale;            
    }
  else                                       // If not VREF, read & average the ADC and scale result
    {
    for(i = 0; i < AVG_OTHER; i++)result = result + adc.read();
    result = result * scale * ADC_REF / float(AVG_OTHER);
    }
  return result;
}

By writing the comments first I've designed the structure of the function. Design is the hard part, and these details, if wrong, will doom the project.

Code is merely an implementation detail.

(This is a pretty simple example; more complex code benefits more from designing first).

Some developers think it's possible to write self-documenting code. As one who reverse-engineers an awful lot of the stuff, I have never seen a non-trivial project that is truly self-documenting. A future maintainer may have no idea what the code is trying to accomplish. Good comments greatly streamline gleaning information from what might be complex code.

Feel free to email me with comments.

Back to Jack's blog index page.

If you'd like to post a comment without logging in, click in the "Name" box under "Or sign up with Disqus" and click on "I'd rather post as a guest."

Recent blog postings: