This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Writing an SPI bootloader for nRF51822 - no softDevice in bl

Hi,

As I’ve mentioned in a previous post (answered ! Thank you Aryan !), I'm writing a bootloader for nrf51822 to be able to update the nRF51's firmware through SPI (data transferred via an STM32 retrieving the file through USB).

My idea is to place a very basic bootloader at @0x0 through 0x37FF, and the firmware starting at 0x3800 to 0x3FFFF.

I want to keep memory usage as low as possible and to have the possibility to upgrade softDevice later. That's why I would rather not having softdevice in the bootloader. In the user code, I don’t use Bluetooth and intend, for now, to use only esb protocol. So, I just include esb_sd_resources_arm.lib

Without the bootloader it’s working fine. But when I try to relocate the user code to 0x3800, problems happen.

With a simple program blinking an LED (no interrupt) the whole bootloader + update + boot is working.

But with the same blinking program modified to use timer interrupt instead of a simple loop, it doesn’t work (and it’s obviously the same with my main program, using interrupts and esb). I’m pretty sure something is wrong with the interrupt vector table. I’ve read that due to Cortex M0 limitations, IVT cannot be relocated. But my idea was to branch, in my bootloader, each interrupt I use in the user code to a specific address at the beginning of user code, which would again branch to the interrupt handler function. This way I should be able to re-root my interrupt and still be not limited with a pre-defined size for each interrupt handler.

To do that, do I have to make any modification to arm_startup_nrf51.s?

Is there something wrong with my idea? Should it work or am I misdirecting?

No doubt a little more information about how esb_sd_resources_arm.lib manages the interrupt would help… Can you please describe how it works, where the handler are placed in memory, etc? I’ve been looking for an application note of some kind about this but without success.

Thank you for your help and concern

Regards

Landry

Parents
  • I don't know if I'm going to be able to explain this .. but I'll try. Apologies if you know all this, it's hard to know what you're exactly asking.

    So your real code builds at 0x3800, which means the first thing at 0x00003800 is the vector table which contains the addresses of all the handlers. Your bootloader which I assume was also built as a separate executable has another vector table at 0x00000000. That's the one the Cortex M0 is going to consult when an exception happens.

    So leaving aside for a moment the 'special' ones like reset_handler, take for instance TIMER2, that's the 10th IRQ which means it's the 27th entry in the vector table (1 for the stack, 16 for the exceptions, then the IRQs) so it's at offset 0x68 from the start of each vector table.

    This is fine for your real code, because you will have supplied an IRQ handler and that will have been linked in and the address of it put at 0x3868, however your bootloader, assuming it was built with the same arm_startup_nrf51.s just has a placeholder which jumps to a piece of code which sits in a loop forever, because your bootloader doesn't define a TIMER2_IRQHandler of its own.

    So when TIMER2 IRQ is raised, the CPU finds the address at 0x0068 and jumps to it and it never gets out of there because its in the bootloader dummy loop code. You want it to find the code at address 0x3868 and jump there. If it was me, to do that I'd modify the code in arm_startup_nrf51.s which is used to build the bootloader (not the app, that one's fine already). Instead of the default loop forever handler, you want one which forwards to the real code's handler. You can do that very explicitly by writing code for each one, or you can have one common routine which takes the value of the IPSR register which has the number of the exception being processed. You take that value, you multiply by 4, add to your code's vector table offset of 0x3800. That is where the address of your real handler lies, you branch to that. You have now trampolined to the correct handler. That's 4 lines of assembler or something like that and you can use that routine for every exception you want to forward.

    That just leaves anything your bootloader is using for its own work. Are you using interrupts? If you are it's a little harder, first your interrupt handler gets called and then you need to decide if you want to handle it, or you want to forward to the real app one, at which point you have to do the same thing, look up the address and forward. If your bootloader doesn't use any interrupts, that's easier.

    And then there's the reset_handler. The reset handler on the bootloader is called when the chip resets and ends up calling your bootloader's main(). At the end of that you really then want to branch to the reset_handler on your real app, again looking it up in the vector table, so it can do all the setup work it needs to do and then call the main in your real app. Like this

    reset_handler (at 0x04) --> main( bootloader ) -> reset_handler (at 0x3804 ) -> main( real app)

    I'd do that myself by adding some code into the assembler which calls the main() routine, right at the very end that also usually ends in a busy loop. Instead there you work out where your real code's reset_handler is and jump to it - thus starting your real code.

  • Thanks RK for this great explanation. This makes things clearer to me. Is it possible to provide some startup code?

Reply Children
No Data
Related