Designing bootloader for Microchip dsPIC33E/PIC24E micro-controller(3)

If you have read my “Designing bootloader for Microchip dsPIC33E/PIC24E micro-controller(2)” of this series, you should be able to extract all the necessary information from the hex file compiled by Microchip’s MPLAB. The necessary information includes nothing but address and data. The whole hex file is actually a map telling you what data is supposed to be placed at which part of the MCU’s flash. Today, I am going to focus on dsPIC33E/PIC24E’s user memory space structure, tips for hex file extraction and GOTO/RESET replacement.

Before you can put any data to its target location in a MCU’s flash, the first thing you must know is how the flash is structured. Although the user memory space is implemented continuously on the MCU’s flash from 0x000000 to 0x02ABFE (or 0x0557FE for bigger memory version), it is actually sectioned into different pieces for manage purpose: Erase block or “page” contains 1024 instructions, and each program block or “row” contains 128 instructions, or you can say each page contains 8 rows. From the view of “page/erase block” or “row/program block”, there are 86 pages or 684 rows in a device with address limit 0x02ABFE, or 171 pages or 1368 rows for 0x0557FE. For smaller memory device, you might wonder, since 0x02ABFE means there are 175104 available address, if you divide 175104 by 1024 or 128, you should get 171 and 1368  respectively, why you end up only having half of the result? If you couldn’t understand, you should check out my previous post of this series where I explained why each instruction takes two address. You might also have notice that 175104/2/1024 should give you 85.5 rather than 86. This time you are right, it is 85.5, and the last page is only half as big as all the others — 512 instructions wide. But microchip still counts the half page as a page. That’s how you get 86 on its datasheet. “Page” is the smallest unit to erase, which means if you run a page erase operation on a MCU, you will at least have to erase the whole page of data, or you can also do a whole chip erase….. Accordingly, “row” is the smallest unit to program. When you run a program operation, you will program 128 instructions in a row. This also requires that you should have your 128 instructions ready to go before the execution. Latch media is the temporary warehouse to store those 128 instructions before you burn them into the flash, I will cover it in the following post.

Hex extraction is quite straightforward if you have read my previous post of this series. Here is some python codes I wrote to extract hex data and line them up according to their address:

def _Parse_Hex32(self):
    extended_Lineaer_Address = 0    # Left-shift by 16 bits
    extended_Segment_Address = 0    # Left-shift by 4 bits
    for i in range(0, len(self._hex32_Lines)):
        #print str(self._hex32_Lines[i])

        byte_Count, starting_Address, record_Type, data = self._Parse_Line(self._hex32_Lines[i])
        if record_Type == 1:        # End record
            if i != (len(self._hex32_Lines) - 1):
                raise Hex32_Invalid("Data type \"End(0)\" appears (line \"" + str(i+1) + "\") before the end of file.")
        elif record_Type == 2:      # Extended segment address
            if len(data) == 2:
                print ("!!! Warning !!!: Data type \"Extended Segment Address(2)\" appears on line\"" + str(i+1) + "\". ")
                extended_Segment_Address = (data[0] * 256 + data[1]) * 16
            else:
                raise Hex32_Invalid("Data type \"Extended Segment Address(2)\" (line\"" + str(i+1) + "\") contains more than two bytes.")
        elif record_Type == 4:      # Extended linear address
            if len(data) == 2:
                extended_Linear_Address = (data[0] * 256 + data[1]) * 256
            else:
                raise Hex32_Invalid("Data type \"Extended Linear Address(4)\" (line\"" + str(i+1) + "\") contains more than two bytes.")
        elif record_Type == 0:      # Data record
            for i in range(0, len(data) / 4):
                device_Address = (extended_Linear_Address + extended_Segment_Address + starting_Address) / 2 + i
                # Flag the LUT, divide the address by because the real device addres inrements by 2
                self._Flag_LUT(device_Address)

                # Fill in the hex into the array
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 0] = data[self._instruction_Size_In_Hex * i + 0]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 1] = data[self._instruction_Size_In_Hex * i + 1]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 2] = data[self._instruction_Size_In_Hex * i + 2]
                self._flash_Memory[device_Address * self._instruction_Size_In_Hex + 3] = data[self._instruction_Size_In_Hex * i + 3]

        else:
            raise Hex32_Invalid("Unsupport data type: " + str(reccord_Type))

As I explained in my first post of this series, the GOTO-RESET data in the hex data must be replaced by your own instructions that point to the starting address of your bootloader. Rather than executing your customer application, a device with modified GOTO-RESET will first jump to the bootloader to check if there is new firmware coming in. If yes, then burn the firmware into the flash, otherwise, jump to the starting address of your customer application and run it.

So attention should be paid in two places: 1) Store the  GOTO-RESET  data from your compiler, and replace it with your starting address of bootloader; 2) Save the stored GOTO-RESET data at the end of your bootloader, so that the device knows where to find your application once the booloading is finished.

########################################################################################################
# Replace the very first two instrutions (which is the GOTO-RESET) with the ones that points to the
# starting address of bootloader.
# Write the original GOTO-RESET instructions to the ending of bootloader.
# So that when a hardware reset occurs, the program will hardwarely points to address zero. Since we've
# already changed the first two instrutions, the program then will jump to the bootloader first instead
# of user's application. After the booloader is done, the program will jump back to user's application
# by reading in the ending GOTO-RESET.
########################################################################################################
def _Modify_Goto_Reset(self):
    # Check if there is GOTO-RESET instruction in the beginning
    if self._flash_Memory[2] == 4 and self._flash_Memory[6] == 0:
        # Copy user's original GOTO-RESET instruction
        user_Lower_Address_Goto = self._flash_Memory[0:2]     # (04)jump to lower 16-bit address, format LSB, MSB
        user_Higher_Address_Goto = self._flash_Memory[4:6]   # (00)jump to higher 16-bit address, format LSB, MSB

        # Replace with the ones that points to where the bootloader is
        bootloader_Lower_Address_Goto = [ self._bootloader_Starting_Address & 0xff, (self._bootloader_Starting_Address >> 8) & 0xff ]
        bootloader_Higher_Address_Goto = [(self._bootloader_Starting_Address >> 16) & 0xff, (self._bootloader_Starting_Address >> 24) & 0xff]

        self._flash_Memory[0] = bootloader_Lower_Address_Goto[0]
        self._flash_Memory[1] = bootloader_Lower_Address_Goto[1]

        self._flash_Memory[4] = bootloader_Higher_Address_Goto[0]
        self._flash_Memory[5] = bootloader_Higher_Address_Goto[1]

        # Fill back in the user's original GOTO-RESET towards to the ending of bootloader
        # The GOTO-RESET after bootloader is set to be physically placed at two instructions
        # ahead of bootloader. Program will jump onto that address once bootloader is finished
        new_Goto_Reset_Address = self._bootloader_Starting_Address / 2 - 2

        self._Flag_LUT(new_Goto_Reset_Address)

        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 0] = user_Lower_Address_Goto[0]
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 1] = user_Lower_Address_Goto[1]
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 2] = 4
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 3] = 0  # Phantom byte

        new_Goto_Reset_Address = new_Goto_Reset_Address + 1
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 0] = user_Higher_Address_Goto[0]
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 1] = user_Higher_Address_Goto[1]
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 2] = 0
        self._flash_Memory[new_Goto_Reset_Address * self._instruction_Size_In_Hex + 3] = 0  # Phantom byte

    else:
        raise Hex32_Invalid("No user's GOTO-RESET found at the beginning of hex file.")

5 thoughts on “Designing bootloader for Microchip dsPIC33E/PIC24E micro-controller(3)

    1. This is a good questions. I’ve never used pickt to burn the firmware, but I assume it’s pretty much the same as ICD. First of all, you don’t ask pickit to burn from certain location, as neither of them provide this option. Rather, programmer just takes in a piece of compiled firmware, and “dumps” it into the chip’s flash as is.

      If you open the firmware with a binary editor, you will find out that each line of the compiled instruction starts with an address, this address is instructing the programmer where to sit itself inside the flash. So as long as we can change that address correctly, the programmer should be able to burn it accordingly.

      In order to do this, you have two options: 1) manually change the address in the binary firmware file, and make sure you also update its CRC bit. To learn more about this, you can check my blog regarding:

      INTEL HEX32 format

      or 2) set a flag inside your code to instruct the compiler to generate the custom address automatically. Obviously, the 2nd option is easier. Here is the how flag looks like:

      .section *, code, address(your_start_address)

  1. This is good question. For a particular PIC, the user program starting address is always the same. e.g. 0x200 in this case. However, saving the default value will 1) help make this bootloader more general-purposed. 2) avoid problems in a case when the compiler could place some data starting from 0x200 and your code might have to be starting from somewhere else.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s