How do I start with bootloader development?

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

Hi folks,

I'm trying to learn how to write an I2C slave bootloader for Attiny85. What I have so far is a small C test program based on USI TWI Slave by Don Blake that works when it is flashed from address 0. It only blinks a LED to show that it is running and communicates with an I2C master answering to a few "custom commands". If I flash it in a higher memory location with, for example "--section-start = .text = 1800", it starts blinking (it was a surprise for me the first time), but it hangs up as soon as it is "polled" by the I2C master.

 

I would need to know what the basic steps are, and how they are done to get me started on this. While I have had some experience with Z80 asm in the past, I would prefer to do this completely in C, if possible.

What (I think) I know so far is that the memory gets filled with 0xFF from the beginning and up to position 1800, so the AVR is simply wasting time with NOPs until it reaches the beginning of the code (it took me a while to discover this, that's why my surprise when the program simply started blinking at the high address without first redirecting the reset vector).

I also understand that the next step would be to make that the interruption vectors used by TWI Slave (USI start & overflow) point to their corresponding ISRs.

 

If these two assumptions are correct, how do I do that? What is the easiest way to redirect the interrupt vector table? Can this be done completely in C? Should I need to do something else? (I have read that some modify the linker script, which I don't know how)

Thank you for your help!

 

This topic has a solution.
Last Edited: Sat. Jun 30, 2018 - 05:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What is the easiest way to redirect the interrupt vector table?

I don't think that the interrupt vector can be relocated, on an ATtiny85.   Most bootloaders are written to not use any interrupts...  (and to omit the vector table for the bootloader code, to save space.)

 

 

What (I think) I know so far is that the memory gets filled with 0xFF from the beginning and up to position 1800, so the AVR is simply wasting time with NOPs until it reaches the beginning of the code

That's correct.  A bootloader on a chip like the tiny85 (which has self-programmable flash. but no specific bootloader support) usually will do something like "intercept" the write of the first page (containing the RESET vector), and replace the reset vector with a jump to the bootloader, after stashing the actual start address somewhere else that is unlikely to be used by a user program (EE_RDY or SPM_RDY are popular.)

 

You should look at the code for one of the existing bootloaders, like https://github.com/Optiboot/opti...
While serial is a lot more common than I2C, the structure of the rest of the bootloader should be mostly the same.

 

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

Thanks for your answer.

I don't think that the interrupt vector can be relocated

Yes, I am aware that the tiny85 does not have a bootloader section as the atmega has, so there is no IVSEL bit to re-map the ISR vectors.

 

Most bootloaders are written to not use any interrupts...

This could be quite complicated then. Those tiny85 bootloaders that work via USB (V-USB), like micronucleus, do not use interrupts? If so, is there any library that implements interrupt-free I2C slave for the tiny?

My first goal is to have the I2C slave code running from the top of the flash. If I succeed, then I will try to implement the bootloader functionality (erasing, writing, etc), which could be based on the structure of existing ones.

 

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

casanovg wrote:
Those tiny85 bootloaders that work via USB (V-USB), like micronucleus, do not use interrupts?

 

Correct, have a look at:

https://github.com/micronucleus/...


Micronucleus V2.03
==================
...
The V2.0 release is a complete rewrite of the firmware and offers significant improvements over V1.x:
...
 • Interrupt free V-USB: no patching of the user program INT-vector anymore.
...

Ciao, Martin

 

 

 

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

If it were me I would learn bootloader tradecraft on a smpler platform (actually the 328P on an Arduino might be a good choice). Learn there how to do bootloaders "simply" (megas make it MUCH easier than Tiny's!). When you understand that only then move to Tiny where the issues are a little more complex.

 

But I don't understand your design. Surely the bootloader (when it's operating) only has ONE comms channel for delivery (and it sounds a lot like USB) so why does the I2C stuff need to be active when it is bootloading? Remember that bootloading is something you just do once every 6 months for about 30..60 seconds. So it's not worth over-complicating the process with things that do not need to be there (like I2C).

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

Flow for writing a bootloader is probably similar to any new project:

 

1). Find a hand full of examples:

      - Well written / well documented.

      - Similar functionality to what you want.

      - Code that works.

2). Spend some time experimenting with it.

      - Read the code.

      - Read the datasheet (s).

3). Change small things.

4). Work in small steps to what you want it to do.

5). You may want to combine code from 2 or more projects into one.

 

I agree with clason #5.

Bootloaders should be small and simple pieces of code.

They should only have the minimum of functionality needed and they can be very simple because they only have to do 1 thing at a time, and therefore should not need interrupts. There is no need for speed optimisation in a bootloader. Does it really matter if a software update takes 30s or 60s?

 

Bootloaders should be very reliable though. Put effort in a logical flow which is easy to understand.

It is easy to write code that has no obvious errors, but it is very hard to write code that obviously has no errors.

Stick that on a wall, and look at it for a while.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

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

Very interesting, thank you.

 

Looking for a little more about V-USB and its interrupts (or the lack of them), I found some Embedded Creations' notes about the way they found to redirect an interrupt vector to the tiny85 bootloader.

Apparently not always V-USB was free of interrupts. Surely this will not be easy for me, but I will try to understand how it worked when it used interrupts to try to do the same with I2C.

 

Regards!

 

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

The problems of Tiny bootloaders goes further than the IVT. The reason there is only one IVT is because there's only one power on entry point (the IVT(s) are located one word above any execution start point). But the fact that the bootloader is not completely separate from the app means they share an entry point and that is within the "app space", so it is "vulnerable". In megas it really is like two completely separated and isolated programs in one AVR. There's even separate lock bits so app and boot spaces can have different protections applied. But it's a mish-mash in a Tiny. A site that explains the pitfalls and offers solutions is:

 

http://jtxp.org/tech/tinysafeboo...

 

This diagram explains a lot of the "issues":

 

 

Although that tries to separate the app and the boot there is a very small bit of the bootloader in the "app space" and that is the RJMP at 0.

 

As I say you can't really understand what is "difficult" about (secure) tiny bootloaders until you first understand the simplicity of mega bootloaders - then you can start to see what is "missing" from a Tiny and the compromises that then enforces.

 

Personally if I wanted a device with a bootloader I would design it around a mega not a tiny just so it can be "done right".

 

Then again if I was designing something with not only a bootloader but also a USB connection then I'd probably pick a mega-USB chip in the first place.

casanovg wrote:
but I will try to understand how it worked when it used interrupts to try to do the same with I2C.
But again are you saying the I2C and its interrupts have to operate even during those few seconds every few months that you want to perform bootloading? Why can't the bootloader be the simplest comms channel + flash SPMer and forget anything else the app normally does when not in bootloader mode?

 

Again when you write bootloaders for megas, because they CAN be separated, you usually write them as two completely isolated and separate programs so the bootloader has no idea what facilities the app may later offer. The bootloader is literally just a comms channel and an SPM writer and that's all. When its job is done it hands control to the app that then fires up all the fancy other bits like I2C and interrupts and so on.

 

While a Tiny ties the app and bootloader together in a more "connected" way I think you should still treat the bootloading operation as something as separate as possible from what the app does.

 

An old adage I often quite here (though usually concerning Mega bootloaders) is that the bootloader code is the only code that MUST be 100% fault free on day one when the device ships as everything else can easily be delivered/replaced to fix problems - so you want to keep the bootloader design as simple as possible - one way to seriously impact that is to NOT use interrupts (which add an order of complexity to any design).

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

Thank you for your answer clawson,

 

Maybe the title of my OP was a bit optimistic, or pretentious. Now I would rename it to something like "How can I make I2C run from the top of the flash memory". The bootloader part challenge should come after this is working.

 

The point is that the application (already working in test) consist of an ESP8266 as I2C master and the Tiny85 as slave. I have no possibility to use USB in the Tiny. The idea was to ship wirelessly new Tiny FW versions into the SPIFFS file system of the ESP to, in a second step, use the same I2C that communicates both chips in the application to force an I2C bootloader start and update the tiny.

 

I totally agree that I should start with a platform that facilitates the bootloader development, but the above setup is the restrictions I have now. To change the electronics at this point would be my last option. I realize that I took the Tiny inclusion into the design too lightly, maybe motivated by its price and for how easy it was the inclusion of the USI TWI library since the very beginning.

 

 

Last Edited: Thu. Jun 28, 2018 - 03:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

casanovg wrote:
I have no possibility to use USB in the Tiny
Now you have me really confused. What was all the talk of V-USB about - was that simply to prove that some Tiny bootloaders DO use interrupts?

 

As I say most bootloaders are a comms channel and an SPM writer. It's very simple. As nothing else is going on I don't see why the comms would need to use interrupts. You might as well just poll it.

 

BTW do study that TSB linked site - they has a nice design for a bootloader (specifically for Tiny). I can't remember but I think their comms channel must be polled UART but that could easily be switched out for polled I2C I guess.

 

EDIT: interesting - looking at tsb_tinymega.asm it looks like they are doing soft-UART in fact

Last Edited: Thu. Jun 28, 2018 - 03:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What was all the talk of V-USB about - was that simply to prove that some Tiny bootloaders DO use interrupts?

Sorry, I have no intention to cause confusion, nor trying to prove a point, I'm just looking for a solution to use I2C to update the Attiny.

 

Since it appears that there is no option but to use interrupts with I2C on the Tiny, I was looking for examples and ideas for implementing using interrupts, and the Embedded Creations' (http://www.embedded-creations.co...) possibly is one.

 

I'll take a look at the TSB bootloader, but I'm afraid that my current AVR ASM knowledge is not good enough ...

I think I now have an overall idea of what the real challenge is. I hope I can figure out how to solve without modifying the design. 

 

Thank you for your help.

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

casanovg wrote:
Since it appears that there is no option but to use interrupts with I2C on the Tiny,
Eh? All an interrupt really does is turn:

int main(void) {
    while(1) {
        if (REG & (1 << IF_flag)) {
            // stuff
        }
    }
}

into:

int main(void) {
    while(1) {
    }
}

ISR(IF_flag_vect) {
            // stuff
}

If the while(1) in main() does nothing else (as is the case in a bootloader) and no other interrupts are expected (as is the case in a bootloader) then these two are effectively the same anyway.

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

That's the most encouraging suggestion I received so far ... so I should try to code both USI interrupts inside main? What is REG? is the interrupt vector address?

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

Just "psuedo code". I'm talking about the general case, REG just means "the register where the interrupt flag lives". Similarly not all interrupt flags have an "IFxx" name but many do. Oh and the _vect name was generic too. ;-)

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

How do you see the bootloader being "started"?  For instance:

  1. The bootloader is always listening for "bootload me" requests at I2C address xx, even when the application is running.
  2. The bootloader starts, and begins listening for I2C, after reset or poweron for a timeout period.  If there is no boot activity, it shuts itself down and runs the application.  Once the application is running, the bootloader code is completely inactive.  Whether the application does anything with I2C is up to it.
  3. like (2), but only if some additional signal is present ("Insert boot jumper and power on the device")

 

(1) is really difficult; it requires a high degree of cooperation between the bootloader and the application - more of a operating system with a program loader than just a bootloader.

(3) is easy, but ... pins are expensive on a  tiny85.   I like the current crop of SAMD21 bootloaders using "double-tap reset" to "permanently" enter the bootloader.  If you have some switch input, you can also do the "hold this button down DURING POWERUP."

Both (2) and (3) shouldn't require interrupts for I2C; the bootloader, while it is running, doesn't need to be doing anything BUT listening to I2C, right?  You may need an additional protocol on top of I2C to ensure that the Pi is not trying to send data while a flash page is programming; bootloaders where you can just stream data into the chip are pretty rare...

 

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

Well, Yes! I can't thank you enough ...

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

How do you see the bootloader being "started"?

Initially, I thought it should work as described in option 2. With the addition that the application must include a mandatory reset command (of the "protocol" over I2C). Even, ideally, this could be appended by the I2C master at the end of the application code to make sure that the application will reset when requested.

 

Option 3 is not feasible, although for the moment the reset pin is available in the attiny, I do not have how to drive it from the ESP01. In the future, I would also like to have this pin available for the application. The reset jumper is not an option as described, in addition, the user of this application should not open the case to "service" the device.

 

Option 1, as you mention, it would be a mix between a bootloader and the app, a sort of "auto-updatable" app. I'd prefer not to do that, but I would consider it if there is no better solution.

 

Both (2) and (3) shouldn't require interrupts for I2C

How do I do that? following the coding lead suggested in comment #12?

Is there any good example of a polled I2C slave you can suggest me?

 

Thank you!

 

 

 

Last Edited: Fri. Jun 29, 2018 - 02:12 AM