Shop OBEX P1 Docs P2 Docs Learn Events
How to boot from the 2nd half of a 24C512? — Parallax Forums

How to boot from the 2nd half of a 24C512?

As mentioned here, an FM24C512 or BL24C512 can be very useful for storing a unique serial number or MAC address in the "hidden" 128 byte ID page. But it can also be very useful for storing a second boot image because it has twice as much memory as the P1 needs. This could be used to make field software updates fail safe. If the download process gets interrupted by a power failure or cable disconnect the boot loader in the first half of the memory stays operable and can be used to retry the update later.

If the bootloader detects a valid boot image in the second half it can load it into RAM and execute it. But how can this be done? A REBOOT command is not what I want because it would look for serial boot and the boot image in the first half of the EEPROM, again. I should start cog0 with the Spin interpreter as it's done after loading the EEPROM contents to RAM by the ROM bootloader. Is there a defined entry point where I can jump to?

Comments

  • VonSzarvasVonSzarvas Posts: 3,488
    edited 2022-07-11 11:34

    This ASM snippet will boot from upper. ($8000- or change the start address with the mem_start variable)

    ' Execute the program stored in upper part of 64kB eeprom.
    '
    ' NOTE: mem_start variable (at end of code) determines memory location
    
    ASM
    
            ' reset pins
            mov     dira,#0
            mov     outa,#0
    
    ':delay  djnz    time_xtal,#:delay       'allow 20ms @20MHz for pins to settle
    
            ' led high
            'or            dira,LED0                    '  HIGH LED0
            'or            outa,LED0
    
            ' set clock to 12MHz
            clkset  clkmode12
            wrlong  clkfreq12, #0
            wrbyte  clkmode12, #4
    
            ' configure pins
            shl     mask_scl,pin_num
            shl     mask_sda,pin_num
    
            ' configure chip address
            shl     chip_address,#1
            or      cs_write,chip_address
            or      cs_read,chip_address
    
            ' set temp variable to zero
            mov addr,#0
    
    
    ' Load ram from eeprom and launch
    
    boot                    mov     smode,#0                'clear mode in case error
    
                            call    #ee_read                'send read command
    :loop                   call    #ee_receive             'get eeprom byte
                            wrbyte  eedata,addr             'write to ram
                            add     addr,#1
    
                            cmp count,first_byte  wz        'calculate the file length
                    if_z   mov low_byte,eedata              'from the word value in
                            cmp  count,second_byte  wz      'bytes at address $0008 and $0009
                    if_z   call #getcount
    
                            djnz    count,#:loop            'loop until done
                            call    #ee_stop                'end read (followed by launch)
    
    
    ' Launch program in ram
    
    launch                  rdword  addr,#$0004+2        'if pbase address invalid, shutdown
                            cmp     addr,#$0010  wz
            if_nz           jmp     #shutdown
    
                            rdbyte  addr,#$0004          'if xtal/pll enabled, start up now
                            and     addr,#$F8            '..while remaining in rcfast mode
                            clkset  addr
    
    :delay                  djnz    time_xtal,#:delay    'allow 20ms @20MHz for xtal/pll to settle
    
                            rdbyte  addr,#$0004          'switch to selected clock
                            clkset  addr
    
                            coginit interpreter          'reboot cog with interpreter
    
    ' Shutdown
    
    shutdown                mov        dira,#0                 'cancel any outputs
    ':delay                  djnz       time_xtal,#:delay       'allow 20ms @20MHz for pins to settle
                            mov        smode,#$02              'stop clock
    ':delay                  djnz       time_xtal,#:delay       'allow 20ms @20MHz for pins to settle
                            clkset  128                        'reboot now!
    
    
    
    
    '**************************************
    '* I2C routines for 24x256/512 EEPROM *
    '* assumes fastest RC timing - 20MHz  *
    '*   SCL low time  =  8 inst, >1.6us  *
    '*   SCL high time =  4 inst, >0.8us  *
    '*   SCL period    = 12 inst, >2.4us  *
    '**************************************
    
    
    ' Begin eeprom read
    
    ee_read
    
                            call    #ee_write               'begin write (sets address)
    
                            mov     eedata,cs_read             'send read command
                            call    #ee_start
            if_c            jmp     #shutdown               'if no ack, shutdown
    
                            mov     count,byte_count            'initialize count at $8000
    
    ee_read_ret             ret
    
    
    ' Begin eeprom write
    
    ee_write                call    #ee_wait                'wait for ack and begin write
    
                            mov     addr,mem_start       'load program start address
                            mov     eedata,addr          'send high address
                            shr     eedata,#8
                            call    #ee_transmit
            if_c            jmp     #shutdown               'if no ack, shutdown
    
                            mov     eedata,addr          'send low address
                            call    #ee_transmit
                            mov     addr,#$0              'reset address
            if_c            jmp     #shutdown               'if no ack, shutdown
    
    ee_write_ret            ret
    
    
    ' Wait for eeprom ack
    
    ee_wait                 mov     count,#400              '       400 attempts > 10ms @20MHz
    
    :loop                   mov     eedata,cs_write             '1      send write command
    
                            call    #ee_start               '132+
    
            if_c            djnz    count,#:loop            '1      if no ack, loop until done
    
            if_c            jmp     #shutdown               '       if no ack, shutdown
    
    ee_wait_ret             ret
    
    
    ' Start + transmit
    
    ee_start                mov     bits,#9                 '1      ready 9 start attempts
    :loop                   andn    outa,mask_scl           '1(!)   ready scl low
    
                            or      dira,mask_scl           '1!     scl low
                            nop                             '1
                            andn    dira,mask_sda           '1!     sda float
                            call    #delay3                 '5
                            or      outa,mask_scl           '1!     scl high
                            nop                             '1
                            test    mask_sda,ina    wc      'h?h    sample sda
    
            if_nc           djnz    bits,#:loop             '1,2    if sda not high, loop until done
    
            if_nc           jmp     #shutdown               '1      if sda still not high, shutdown
    
                            or      dira,mask_sda           '1!     sda low
    
    
    ' Transmit/receive
    
    ee_transmit             shl     eedata,#1               '1      ready to transmit byte and receive ack
                            or      eedata,#%00000000_1     '1
                            jmp     #ee_tr                  '1
    
    ee_receive              mov     eedata,#%11111111_0     '1      ready to receive byte and transmit ack
    
    ee_tr                   mov     bits,#9                 '1      transmit/receive byte and ack
    :loop                   test    eedata,#$100    wz      '1      get next sda output state
                            andn    outa,mask_scl           '1!     scl low
                            rcl     eedata,#1               '1      shift in prior sda input state
                            muxz    dira,mask_sda           '1!     sda low/float
                            call    #delay2                 '4
                            test    mask_sda,ina    wc      'h?h    sample sda
                            or      outa,mask_scl           '1!     scl high
                            nop                             '1
                            djnz    bits,#:loop             '1,2    if another bit, loop
    
                            and     eedata,#$FF             '1      isolate byte received
    ee_receive_ret
    ee_transmit_ret
    ee_start_ret            ret                             '1      nc=ack
    
    
    ' Stop
    
    ee_stop                 mov     bits,#9                 '1      ready 9 stop attempts
    :loop                   andn    outa,mask_scl           '1!     scl low
                            nop                             '1
                            or      dira,mask_sda           '1!     sda low
                            call    #delay3                 '5
                            or      outa,mask_scl           '1!     scl high
                            call    #delay1                 '3
                            andn    dira,mask_sda           '1!     sda float
                            call    #delay2                 '4
                            test    mask_sda,ina    wc      'h?h    sample sda
            if_nc           djnz    bits,#:loop             '1,2    if sda not high, loop until done
    
    ee_jmp  if_nc           jmp     #shutdown               '1      if sda still not high, shutdown
    
    ee_stop_ret             ret                             '1
    
    getcount                                                'calculate the binary files length
                            mov high_byte,eedata
                            shl high_byte,#8
                            or high_byte,low_byte
                            mov count,high_byte
                            sub count,#9
    
    getcount_ret            ret
    
    '-----------[ delays ]--------------------------------------------------------
    
    delay100ms    mov       bit_time,_100ms
                  jmp       #delay
    
    delay10ms     mov       bit_time,_10ms
                  jmp       #delay
    
    delay1300     mov       bit_time,_1300ns
                  jmp       #delay
    
    delay650      mov       bit_time,_650ns
                  jmp       #delay
    
    delay         add       bit_time,cnt
                  waitcnt   bit_time,#0
    delay_ret
    delay100ms_ret
    delay10ms_ret
    delay650_ret
    delay1300_ret ret 
    
    
    ' Cycle delays
    
    delay3                  nop     '1
    delay2                  nop     '1
    delay1                  nop     '1
    delay1_ret
    delay2_ret
    delay3_ret              ret     '1
    
    mask_sda                long    $2
    mask_scl                long    $1
    pin_num                 long    $1C
    mem_start               long    $8000 ' IMPORTANT - Where we load the new program from
    chip_address            long    0
    cs_write                long    $A0
    cs_read                 long    $A1
    
    time_xtal               long    20 * 20000 / 4 / 1      '20ms (@20MHz, 1 inst/loop)
    smode                   long    0
    interpreter             long    $0001 << 18 + $3C01 << 4 + %0000
    first_byte              long    $7ff8
    second_byte             long    $7ff7
    byte_count              long    $8000
    h8000                   long    $8000
    
    clkmode12               long    %0000_0000
    clkfreq12               long    12_000_000
    
    _100ms                  long    0
    _10ms                   long    0
    _650ns                  long    0
    _1300ns                 long    0
    'LED0                    long    1 << 20
    
    command                 res     1
    addr                    res     1
    count                   res     1
    bits                    res     1
    eedata                  res     1
    low_byte                res     1
    high_byte               res     1
    bit_time                res     1
    
    
    ENDASM
    
  • Code that boots upper half of 512 EEprom was in OBEX.
    I can't remember the exact name of the code.

    For this to work, you would always store new firmware to upper half of EEprom.
    And lower half of EEprom always hold the code that loads from upper half

  • ManAtWorkManAtWork Posts: 2,178
    edited 2022-07-12 08:28

    Thanks @VonSzarvas
    So the whole boot process can be summarized like this:
    1. load the first 10 bytes from EEPROM address $8000 to RAM address $0000
    2. take the length of the boot image from byte 8 and 9
    3. load the rest of the image to RAM
    4. check if code is valid (checksum or whatever)
    5. take XTAL/PLL mode from byte 4
    6. switch clock mode, wait 20ms for PLL
    7. execute a COGINT interpreter command

    .. where <interpreter> points to the somehow magic address $7C010. Ah, I just looked up the P1 COGINT instruction. $7C010 = 1<<18 + $3C01<<4 + 0 means PAR=$0004 and code adr=$F004 for cog #0. This is the entry point for the spin interpreter.

  • The code above assumes that it is launched in cog 0, right? So coginit interpreter restarts its own cog and does not fall into the shutdown procedure. I have to do it differently. If no valid code is found in the upper half of the EEPROM I have to go back and startup the network interface to accept firmware updates from the PC. No problem... I just have to start the ASM code in a different cog (say 1) and wait with cog 0 if it reports an error. If there's no error it will never come back but restarts cog 0 with the interpreter and the main program in RAM and shuts down cog 1.

  • VonSzarvasVonSzarvas Posts: 3,488
    edited 2022-07-13 10:34

    Correct.

    In a product it worked like this...

    The lower EEPROM held a bootloader that ran each time the P1 reset.
    The bootloader pinged a server (over GPRS) for firmware updates, and if existed then downloaded and copied the update to upper EEPROM, verified the checksum and repeated if failed. The repeat delay got longer each attempt, etc..
    Finally the ASM code snippet was executed with COGINIT BootUpper, 0

  • Jippee! It actually seems to work. Although it's a lot more complicated than I first thought.

    I'd like to protect the firmware image in the second half of the EEPROM with a checksum to be able to detect incomplete or corrupted downloads. But I can tell whether the checksum is correct or not only after I have read the complete firmware image and I don't want to do that twice. So if I detect an error I have to re-load the bootloader from the first half of the EEPROM and launch it in network mode so it can wait for downloads from the PC.

    To detect if the bootloader comes fresh from a cold start or if the firmware boot already failed I patch a global variable after loading the bootloader from EEPROM to RAM. The address of the variable is passed to the bootstrap ASM code in the PAR register.

  • Cunning :)

    Btw... One way I handled the checksum in the past was by downloading an update in 128 long blocks, saving to ram and verifying a checksum before writing to EE. Then repeat until the full image stored.

    Trouble was it didn't help if a powerfail occured on the last write, so the bootloader being able to re-try the update is still important.

    I also zero'd the first few longs of EE when starting the update, and then downloaded the image backwards. It means the checksum bytes close to $8000 are the last written, so quick to verify if a valid image exists.

Sign In or Register to comment.