NRF bootloader on ATmega16 - problems

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

 

I'm trying to develop a bootloader for ATmega16 and, yeah I got a lot of problems.. I want to flash the micros using the NRF24L01 Radio, so I had to share some code between bootloader and application - anyway the NRF24 code is around 3KB and in BLS is not enough space, also I want to be able to use the NRF24 code from application without to duplicate the whole library, so the FLASH layout is something like:

- Bootloader section (0x3800 address) 2KB

- NRF library (0x2840 address) 4KB

- NRF APIs pointers table (0x2800 address) 64B

- Application - 10KB

 

So I managed to write the bootloader, I'm able to write some pages in FLASH and the application partially can use the NRF library, but the problem is that I got some troubles with the ISRs... The NRF code is compiled alongside bootloader, this means the ISR routine is placed somewhere between 0x2800 - 0x3800 in FLASH, because I want to use the same routine in application, I just replace the Application's flash data at 0x04  address (INT0_vect) with the address of ISR routine from bootloader.. but I got a strange behavior, after few interrupts (or the first one) the MCU starts to act weird - I suspect that is due the stack overflow, or it just jumps somewhere random in FLASH and the things just turn wrong.. but I can't check this since I don't have a debugger - also I moved the .data section at 800090, the NRF library needs at least 32B, so from 800060-800090 is 48B, should be enough.

 Any idea how I can properly use the same ISR routine for both, Bootloader and Application? - I don't compile the NRF and Application together, so I just put the Bootloader + NRF code starting with 0x2800 address. 

Also what is the correct way to move the .data section to make space for NRF data? I used the following in the miscellaneous tab from Linker settings: -Wl,-section-start=.data_nrf=0x800060 -Wl,-section-start=.data_nrf=0x800090, then I checked the .map file and I saw that the .data begins from 0x800090, and after that is .bss...

Also any manual editing in HEX is fine for me, I intention to use a python script to write the application, so I can make any changes in HEX file.

 

Based on your answers, I'll provide you more info. 

Thanks

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How do you actually build this? I'm guessing it cannot be the usual bootloader situation of two separate programs with two base addresses: a bootloader built as a standalone entity at BLS and an app built alone at 0. Presumably you build it all as one big program but for later app updates you then just isolate the bottom 10K bit and deliver updates of that? As such all the code in the device must surely use the same .data/.bss layout? Similarly the ISR entries that need to point to vectors in the 2840 radio library presumably just have direct vector jumps?

 

But if you do build it that way why do you need the jump-table? The linker can link direct as long as the radio library code is never changed.

 

Having said that you said:

mestereaga wrote:
he NRF code is compiled alongside bootloader, this means..

So are you saying you do build it as two separate projects - each with their own .data/.bss and vectors ?

 

As you have stuff beneath the BLS I don't really see the point in trying to build two entities if that is what you are trying. As you have found you will have issues both with linking the vectors and with two different .data/.bss layouts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi.

Sorry for confusion, currently I have just one project, and yes I just replace the application each time when I want to update it to a newer version (the first 10KB) and I usee the E2P just to make sure that the newest application still use the same NRF version (otherwise I have to include the also the NRF code, 10K + 4K). But I want to make the application a standalone program that starts at 0x0, so I want to do something like:

 

1. NRF + bootloader and the NRF vectors are defined in a separate file which is built by application, e.g:

radio_shared.c

radio_error_code (* const __flash *fptr_nrfRadio_TransmitMode)(void) = (radio_error_code(* const __flash *)(void))(NRF_LIB_HOOKS_BASE_ADDR + 2);

Where NRF_LIB_HOOKS_BASE_ADDR address is 0x2800.

 

2. Application + radio_shared.c (just to know where to jump)

 

I just wanted to simplify the things for the end user... using shared NRF library is optional, so the user can decide to not use these, so there is no need to add everything in application project. And yes this comes with a huge cost, so if this doesn't worth then, yeah I'll go with what currently I have, but I hope this will work somehow.

 

So yes... I have troubles with the data/BSS sections and ISR ... All  NRF data is placed in just one variable (a structure which is 32B long) and it is declared as:

radio_context _radio_instance __attribute__((section(".radio_data")));

The radio data_data is a RAM section and it starts with 0x800060, so actually the variable is initialized at run time when the NRF library is initialized - there is no init code in my application to do this job, hmm should I add that?

Also I moved the .data section in both, bootloader project and application project to 0x800090, so actually when I compile the bootloader + NRF code, the resulted code should use just the 0060-0090 RAM.. so in application if I put everything above 0090 then I guess it should be OK. 

It works... partially until I get the first interrupt.. in application I just replaced the resulted vector for INT0 with the address of NRF's ISR... But seems like when the application jumps to that ISR something bad is happening ...

 

Andrei

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think I'd use an external memory chip. Have the main program receive the new code using the NRF (is that "Nerf?") and store it in the external memory. When all is received and checked, then launch the bootloader that reads data from the external chip and flashes the new program.

The largest known prime number: 282589933-1

Without adult supervision.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No, NRF stands for Nordic Radio Frequency, it's a low power radio module mostly used in embedded projects for mesh communication... Thanks but I don't intend to use an external memory for my project.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've done this sort of thing.

 

Only two things need to be in the bootloader section,

not to be confused with a linker section:

Its vector table and the SPMs.

Have two output sections, .text and .other .

By default, code ends up in the .text output section.

Use attributes or avr-objcopy to place some code in .other .

Use the linker option --section-start to place

the output sections where you want them.

Note that this mechanism does not deal with initialization

data that the compiler places in program memory.

It will still end up in the .text output section.

 

Connecting the application to only the

desired bootloader stuff is also interesting.

Use avr-nm and a python script to generate an

assembly file that only defines the desired symbols.

An @file for the linker would also do the trick.

 

Shared RAM:

As I think you are trying to do,

place the shared data in its own section above the stack.

In both the application and the bootloader,

adjust the starting stack pointer accordingly.

There is a linker symbol for this.

Let the shared data default to zero.

In the bootloader,define its initialization data in  program memory.

In the bootloader and the application, use memcpy_P to assign the shared data.

 

Regarding linker sections:

Attributes and --function-sections affect input section names.

--section-start affects output sections.

Unless the linker is told otherwise, input sections become output sections.

The linker script is what tells the linker otherwise.

The default linker scripts do not mention .other .

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for tips... Mostly I already did..

Could you extend a little bit this: " adjust the starting stack pointer accordingly" ?

I didn't change anything regarding to the stack, so I suspect that the application just push somewhere outside the stack when it jumps in ISR - there are 2 calls to other functions (that are placed as well at 0x2840) that are used to clear the interrupt flags in nRF radio...So I think that a very first step is to avoid to call these functions and just replace them with the actual code...

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The stack pointer aka SP, normally starts at the end of RAM aka RAMEND.

The startup code will set SP even if a reset does not.

You need it to start at RAMEND-32 or less.

Otherwise stack usage will write over your shared data.

You can use the linker's --defsym to specify the desired initial value.

Finding the correct syntax and the correct symbol

to define are left as exercises for the reader.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok... in my project actually I put my shared data starting with 0060 and then I moved .data section to 0090 - the bss is starting just after .data section ends. Is it better to put the shared data just above the stack ? 

Regarding to SP, I think that is the startup code that you mentioned:

 

00000054 <__ctors_end>:
  54:    11 24           eor    r1, r1
  56:    1f be           out    0x3f, r1    ; 63
  58:    cf e5           ldi    r28, 0x5F    ; 95
  5a:    d4 e0           ldi    r29, 0x04    ; 4
  5c:    de bf           out    0x3e, r29    ; 62
  5e:    cd bf           out    0x3d, r28    ; 61

 

0x045F is the RAMEND for ATmega16 ( 1024B + 96B regs & SFR)

 

Will be the linker happy with:  --defsym RAMEND=0x43F ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mestereaga wrote:
Will be the linker happy with:  --defsym RAMEND=0x43F ?
Yes, but you won't.

Nothing else references or defines linker symbol RAMEND, so no damage will be done.

No good either.

The preprocessor symbol RAMEND is normally #defined to address the last byte of internal RAM.

'Tain't all that adjustable.

I do not remember the linker symbol to use.

Searching suggests that -minit-stack=666

on the link line will cause the appropriate --defsym to be passed to the linker.

You will likely want a different number.

 

Edit: suggests

Iluvatar is the better part of Valar.

Last Edited: Mon. Dec 16, 2019 - 05:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks,

 

Anyway, I think is better just to shift the .data with some offset like it starts from 0090, so the 0060-0090 will be used by my library... then I don't have to worry from where the SP starts and how is defined the RAMEND by the linker.

 

 

The problem now is that I still got some unexpected behavior... the application is written fine in APP space, but when it executes the interrupts (I think that's the problem), something goes wrong... I forget to do something? I doubled checked and in my library I have just one variable that is placed in 0060-0090, so the generated code use just this RAM section ... the interrupt is already defined at a specific address e.g. 1A80, so in my application I just replace the interrupt vector table with: 0C94 801A (this starts at 0x0002 address, is the INT0_vect).. is that enough?

Also I optimized the ISR implementation so it doesn't jump into other functions, just few instructions like saving few flags and clearing interrupt flags..

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mestereaga wrote:
but when it executes the interrupts (I think that's the problem), something goes wrong... I forget to do something?
Are you touching IVSEL at all? If you do move the active IVT when bootloading you have to remember to put it back (and disable the active interrupt sources) just before you enter the app code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, I modify the IVSEL, isn't this put back if I do a reset? After I'm finishing to write the APP, I configure the watchdog to reset the microcontroller (I'm using a flag when I initialize the NRF library so if I'm in bootloader then I set IVSEL to one just to move the interrupt table in BLS) .. when the application starts, it doesn't touch the IVCSEL.. so on short if I touch any SFR in bootloader, then I do a reset, otherwise I just jump to zero...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mestereaga wrote:
Yes, I modify the IVSEL, isn't this put back if I do a reset?
Depends what you mean by "reset". Are you talking about a watchdog induced reset? To be honest I can't remember if a WDT reset does reset IVSEL - because the WDT may be using interrupts it could be a case of pulling the run from under its feet if it did so. Therefore it might as well to manually clear it anyway.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, watchdog reset... anyway the behavior is still present after a power cycle of MCU - considering that I don't modify the IVSEL in this case, then the problem should be somewhere else ...  

Also I think is not ok to move the .data section, I think this also is causing problems, maybe I can put a pointer in each NRF APIs to my variable and then the whole context will be stored in application... something like this:

 

APP:

RadioInstance radio_instance = {0};

nrfRadio_Init(&radio_instance, &cfg);

 

in general I'll have nrfRadio_function(&radio_instance, parameter1, parameter2 ...)

 

And the nrfRadio_Init function is placed in my section (2840 flash) and it programmed alongside the bootloader. 

 

Any thoughts are welcomed!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Note that both the bootloader and the app need their SPs adjusted.

Variables placed in .data will be initialized.

Variables in the .data of both the bootloader

and the app will be initialized by both.

Moving the .noinit section might do what you want.

Whatever you do will require a linker option.

It seems to me that the simplest is to only

declare the shared variable in the C programs.

Define its symbol with -Wl,--defsym=aziraphale=0x666 .

"Initialize" the shared variable by hand.

 

Do you have any global or static variables other than the shared variable?

If so, moving .data will move those as well.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do you have any global or static variables other than the shared variable?

If so, moving .data will move those as well

Actually yes, the bootloade does... is something like

NRF library: is using 0060-0090 section, there is one variable which is 32B long (so it should fit in this space) linker options: -Wl,--section-start=.radio_data=0x800060

Bootloader: .data is shifted with 48B so it starts from 0090, linker options: -Wl,--section-start=.data=0x800090

Actually the NRF is compiled together with Bootloader in the same project, so actually I have:  -Wl,--section-start=.radio_data=0x800060  -Wl,--section-start=.data=0x800090

 

In application (another standalone project) I have:  -Wl,--section-start=.data=0x800090... 

Also I checked the map file and .noinit and bss are starting just from where the .data left... anyway I attached the .map file resulted  https://gofile.io/?c=7Fp270

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, actually I figured out... the problem was neither the interrupt (IVSEL), nor the shared RAM.. I had a problem in my flashing tool...actually I messed up the hex file and in the end the data went shifted in my micro, so everything goes down and the behavior was mixed... time to add the CRC verification :)

Anyway, I decided to not use the linker to define the shared section... actually what I did was to put my NRF data into a separate .c file which is shared by Bootloader and Application.. so each NRF API takes as argument the address where my data is placed...so it is like:

 

APP project  ------->  Shared .C file <----------- Bootloader project

 

So actually the linker will decide where it puts the radio_context variable and I don't care where it puts it since each function from my shared library takes as argument the address of my data e.g:

 

void nrfRadio_Init( radio_context * instance, radio_config cfg);

Then in code: nrfRadio_Init(&_radio_instance, _cfg); where _radio_instance is declared in the shared .C file.

 

So then to simplify the interface and to not to change my whole bootloader/application implementation I did something like:

#define __nrfRadio_Init( cfg)  nrfRadio_Init(&_radio_instance, cfg) 

 

So this work really fine... and I don't have to care about _radio_instance ... and also I'm safe, nobody will call the library APIs using the wrong instance variable (since I can't support multiple radios in the same time, it must be singleton)

 

I suggest to close this topic unless there are some people who are interested to find out more about this solution. Maybe I can upload an example somewhere, so this whole thing APP / Bootloader can be a tutorial.

Thanks for everything guys!

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Huh?

The variable in shared.c is not necessarily going to be

linked to the same place in the bootloader and the app.

The result can be two separate variables with two distinct addresses.

If that is acceptable, I do not understand the reason for this thread.

Also, absent attributes or something else special,

the variable will be initialized by both the bootloader and the app.

Again, if that is acceptable, I do not see the reason for this thread.

 

Iluvatar is the better part of Valar.