Electronic – PIC On-the-fly software updates over radio

picprogramming

I plan to run one of the 8-bit PIC microcontrollers (like PIC16F1938) connected to the radio-module (NRF24L01+).

  • Suppose, that I use less, than a half of the available program memory, would it be possible to implement on-the-fly software updates?
  • Are there some library or example projects, that already implemented something like this?

My own considerations:

  • Memory organisation

    0 x ~size/2 size
    +-------------+--------------------+----+-----------------------+----+
    | BOOT LOADER | USER CODE | | DOWNLOADED USER CODE | |
    +-------------+--------------------+----+-----------------------+----+

  • Boot loader:

    • Small
    • Can't update itself on-the-fly
    • Aligned to some fixed address in the program memory (at 0?)
    • Works in two modes:
      • NORMAL_MODE
        • Check CRC of the USER CODE
        • Run USER CODE, if CRC OK
        • Switch to UPDATE_MODE, if CRC not ok
      • UPDATE_MODE:
        • Copy the DOWNLOADED USER CODE into USER CODE
        • Set mode back to NORMAL_MODE
        • Restart
  • USER CODE:
    • Aligned to the address x
    • Not larger as (size - bootloader_size) / 2
    • Contains update routine, that can
      • Download the new user code
      • Store it at the address bootloader_size + (size - bootloader_size)/2
      • Set boot loader mode to UPDATE_MODE
      • Restart

Best Answer

It appears that you are suggesting that your user code would transfer a software update into an area in flash designated as DOWNLOADED_USER_CODE area and then your boot-loader would copy that data into the USER_CODE area, where it could be executed.

You can do the boot loading as you suggested, but there are a few downsides to this approach.
1) You waste about half of the available program space because you have two copies of your program in flash, one in USER_CODE, and one in DOWNLOADED_USER_CODE.

2) The suggested approach wastes time because you must burn the flash memory twice, once when storing the image in DOWNLOAED_USER_CODE, and once again when copying the image to USER_CODE. I'm not sure about the exact flash programming speed for your part but typically that approach makes the user wait anywhere from several extra seconds to several extra minutes.

3) If the USER_CODE contains the update algorithm then what happens if you update the device with USER_CODE that has a logical bug (human error when writing code, not CRC error) in it that prevents radio communication . You would then be unable to fix the problem without PIC programming hardware and physical access to the device.

The approach I usually take, that avoids all those problems, is to let the boot-loader both download and burn the new user code. The boot loader itself gets a little bigger because it has to contain some communications software (usually stripped down to the minimum required), but that is balanced out by the fact that you now have twice as much flash for your user-code.

Additionally, now you always have a fallback if the user code has an error in it (since you don't need any part of the user code to execute the download process). This is especially important if doing development and testing on-site somewhere.

To be sure you can always get back to your boot loader to do a download you usually have three options.

1) Place the boot loader at the boot vector for your device so it always runs when power is cycled. At power-up there should be some attempt to wait for a connection from external update software before loading the user-code. This delay need not be long. Even 1 or two seconds is usually adequate. You should always try and connect even if the user code is valid, because your user may want to update even when they have valid code.

2) Enable the watchdog timer in your device, so that if your user code stops working your device will reset and go back to your boot loader. Your boot loader can then make an attempt to download new user code.

3) Place a command in your user code that allows jumping back to the boot loader. This prevents you from having to go and manually reset the device in order to do updates. But if that command stops working you can always fall back on the first two options. Note that whenever user code jumps to the boot loader the cleanest way is to execute a software reset so that you can guarantee that all your registers are reset to default before the boot-loader runs. This will make the boot loader easier because you won't be trying to undo any user register settings.

Using a CRC, checksum, or SHA as a criteria to execute the user code is a good idea. You probably should have additional fields in a header for your code...

  • The length in bytes of your user program, and the address of the first byte in memory where it is stored. This is important so you know over which bytes the checksum should be calculated.

  • A version number or area with text describing which version of the program you are using. This is helpful to the user so they know what they have loaded in the device.

  • The address of the first instruction in your user code which should be executed. You could avoid this field and just use the first byte of your user code, but you may find it more flexible if you can put the first instruction address somewhere else.

    The way I typically conduct the firmware transfers is to make the boot loader a programming slave to some other external program. The boot loader just accepts commands that write bytes or erase pages of flash, and sends status back to the master program (which is usually some user interface on a laptop or PC).

    I find this to be a pretty compact approach because it puts most of the transfer logic on the PC side and not in the microcontroller. It also allows me to just update some small area of memory (such as calibration data or settings) if I want to without having to do a full transfer of the whole user area.

    The PIC16 can't execute code from RAM, but on processors that can (such as PIC32, AVR32, Freescale/NXP S08 and S12) I make the boot loader copy itself to RAM and then jump to and execute the RAM copy so that the boot loader can even upgrade its own flash area (if for some reason I wanted a new boot loader).