Saturday, September 4, 2010

Transforming files with wildcards in Make. Makefile tip #3. Manipulating .hex files, and auto CRC generation.

Last tip, for a while, in our series of tips on using Make and Makefiles.

Make has several function that can manipulate strings. Pattern Substitution, 'patsubst', is one such function. It replaces a from, with a to in a text target string. '%' acts as a wildcard, matching any number of any characters within a word, when used in a from or to pattern of 'patsubst'.

A keyword that can be used in functions is 'wildcard'. This keyword, used anywhere in a Makefile, is replaced by a space-separated list of names of existing files that match one of the given file name patterns. By combining 'patsubst' and 'wildcard' a Makefile can be created that will transform all files in a directory to an other file type. In the following example simple text files (*.txt) are transformed into Intel Hex files (*.hex), to change human editable configuration files, to files that can be burned in to Flash.

objects := $(patsubst %.txt,%.hex,$(wildcard *.txt))

.PHONY : all
all : $(objects)

# Create hex files from txt source files:
%.hex : %.txt
 srec_cat $< -COsmac -Output $@ -Intel

The program 'srec_cat' is part of the SRecord package by Peter Miller. SRecord is a suite of programs for transforming files to and from Motorola S-Record (S9), and Intel Hex, Records, to/from many other formats.

I use a combination of Make and srec_cat to automatically calculate and append a CRC of the binary image being created, so that my Embedded System can validate that the program is good on each power up. See our past entries on IEC 60730.

In the examples below I show how I create a CRC in the Makefile, then verify it in my C application. Real Soon Now I plan on switching to CCITT-CRC with Magic Number checking. The XMODEM CRC was the only one available in both AVR-LibC and SRecord when I started down this road...

# Create final output files (.hex, .eep) from ELF output file.
%.hex: %.elf
# Do normal stuff here.
# Append CRC to the HEX file that was created in the 'normal stuff' section:
# Rename the standard compiler output from *.hex to *.org.hex:
        mv $(OBJDIR)/$@ $(OBJDIR)/$(TARGET).org.hex
# Calculate then append the CRC, to a newly created *.hex output (Watch for line wraps, the following is one long line):
 srec_cat $(OBJDIR)/$(TARGET).org.hex -Intel -Little_Endian_CRC16 -MAximum-Address $(OBJDIR)/$(TARGET).org.hex -Intel -Cyclic_Redundancy_Check_16_XMODEM -Fill 0xFF -OVER $(OBJDIR)/$(TARGET).org.hex -Intel -Output $(OBJDIR)/$(TARGET).hex -Intel

How to utilize the above CRC in an application:

/* *************************************************************************************** */
extern uint16_t __data_load_end[1]; /* Defined by the linker script.  Set to address of last byte of .text+.data section */
#include <util/crc16.h>
static __inline__ uint16_t crc_flash_check_has_error( void );
static __inline__ uint16_t crc_flash_check_has_error( void )
{
  uint8_t  byte_u8;
  uint16_t crc_u16, i;
  uint16_t const flash_end_u16 = (uint16_t) &(__data_load_end[0]);

  for( crc_u16 = i = 0; i < flash_end_u16; i++)
    {
       byte_u8 = pgm_read_byte( i );
       crc_u16 = _crc_xmodem_update( crc_u16, byte_u8 );
    }

  if( pgm_read_word( flash_end_u16 ) != crc_u16 )
    {
      return( 1 );
    }
  else
    {
      return( 0 );
    }
}

__attribute__ ((OS_main)) int main( void )
{
  hardware_setup(); /* Put system in known state.  Bootloader should have already done this, it is here in case we have old bootloader */

  if( crc_flash_check_has_error() )
    {/* Flash is corrupted: */
      for(;;)/*ever*/
        {/* Blink the LEDs to indicate corrupted memory, we are dead */
          _delay_ms(200.0);
          LED_TOGGLE();
        }
    }

}/* main() */

The great unanswerable question in some cases is what do you do when you know the Flash is corrupted? The LED_TOGGLE() section, or even the test itself, could be what is damaged.