AVR DualBoot Bootloader

This is part 2 in a series of 3 blog-posts describing how I make Marlin and Klipper coexist on my 3-Printers:

In the first post in this series,  I described how I wanted to have my 3D-printer be able to run Marlin firmware and the Klipper firmware without having to reflash a new firmware to the ATmega2560.

The idea was to relocate the secondary firmware to the upper half of the Flash. The Bootloader would set IVSEL before passing control to the secondary firmware, and the bootloader interrupt vector table would call the corresponding interrupt routines in the secondary firmware.  This worked great for the test program I had written that did not use trampolines, but neither Klipper nor Marlin would run in the upper address space when relocated there.  So even though my concept for DualBoot firmware on an ATmega2560 worked as expected in reality it was a FAIL, but there is a

Solution:
Compile the secondary firmware so that it does not rely on trampolines,
redirect read of data/code in flash to the upper address space
or simply avoid using flash above 128kbyte

 

The ATmega2560 is an 8-bit micro-processor working in a 16-bit address space (Harward architecture), but this processor has 256 kbyte of FLASH. Most address-related machine instructions are done in 16-bit, and it is more efficient to jump to an address in the lower range than jumping to an upper address. avr-gcc takes advantage of this and implements a trampoline area just above the interrupt vectors with jumps that will send you to code in the upper address space when you call the trampoline.

Trampolines are only needed if there is a call to some code in a different 16-bit bank, For smaller programs, like my test programs. The linker can be directed to avoid jump-tables,

Klipper is a fairly small (32kbyte flash) hence this point of the problem can be solved for Klipper.

Marlin can be larger than 128kbyte so this does not apply to Marlin, Marlin still needs to have its trampoline area in the lower address space.

So there is light at the end of the tunnel, as long as we have Marlin as the primary firmware, and can figure out how to relocate Klipper to the upper address space.

Flash is not only used for storing the program, read-only variables/tables/text-strings are often saved in FLASH since there is limited RAM. As this is a Harward CPU routines like pgm_read_byte( 16bit pointer) is used to read them. When the secondary firmware is relocated to the upper address space, the read of Flash-content will still return data from the lower flash, unless pgm_read_byte_far(32-bit address) is used.  The good news is that we probably only need to patch the implementation of pgm_read_byte(16-bit address) on the secondary firmware to get around this.

The AVR DualBoot bootloader I presented in the previous blog post works as expected, we just need to make sure the secondary firmware is patched and compiled in a way so that trampolines are avoided and flash-reads are directed to the right area.

The AVR program counter and thereby address is 16-bit but this addresses words, hence the normal limit is 128 kbyte.

DualBoot:
As long as you stay below 128k,
relocation is the only modification needed for the secondary firmware
the primary firmware need no changes

For now, I will continue using only the lower 128k flash area.

A stripped-down Marlin (no sd-card support) as primary firmware, and Klipper as secondary firmware is compiled,  so they can  fit within the lower 128k flash.

 
avr-size ../build.marlin/ramps/firmware.elf ../build.klipper/ramps_2nd/klipper.elf
text data bss dec hex filename
89846 208 2696 92750 16a4e ../build.marlin/ramps/firmware.elf
32202 42 366 32610 7f62 ../build.klipper/ramps_2nd/klipper.elf

Success – Marlin @ 0x0000 – Klipper @ 0x16000

To test this out a version of the DualBoot code presented in my previous posting was made, this time the secondary firmware would be assumed to start at 0x16000. A stripped-down but otherwise unmodified version of Marlin was flashed to 0x0000, and Klipper was relocated to 0x16000 and flashed there, which worked as expected. HURRAH we now have a working – For the first time in world history a 3D printer is running Marlin and Klipper on the same firmware.

DualBoot with secondary firmware on a fixed base address

DualBoot has the load address for the secondary firmware hardcoded into the bootloader, which means that if you want to install a larger primary firmware you have to modify and flash a new bootloader, which again would mean that you would have to have physical access to the mainboard, and connect f.ex an USBasp for this, pretty inconvenient. More able people than me might be able to have the bootloader modify itself, but even if that would be possible, I prefer to have the bootloader untouched, it should after all be our backup plan.  But there is an easy fix for this

DualBoot with secondary firmware dynamic base address

The problem is that the interrupt table in the bootloader has to forward the interrupt calls to the secondary firmware, so the bootloader needs to know where to jump to, this is hardcoded into the bootloader.

The interrupt vector table in the secondary firmware is itself an array of 4-byte assembly codes that call the relevant interrupt routine.   Hence we can have our cake and eat if we:

  • Copy the interrupt table from the secondary firmware to a fixed location just below the DualBoot bootloader.
  • Let the DualBoot firmware interrupt table always call the entries just below the bootloader, this would even work with the reset address.

Hence no changes to the DualBoot bootloader presented in the previous post are necessary, just compile a version of DualBoot that expects the interrupt-table to be just below the bootloader.

dualtool.sh

I have written a small tool, to check whether the trampolines are located, in the lower 16-bit address area. dualtool.sh also checks bounds and copies the interrupt vector table to the area just below the bootloader, finally, the name of the files and the base address for the secondary firmware are saved there too.

Usage: dualtool.sh [option] [primary_firmware.elf] [secondary_firmware.elf]
           prints current firmware-map
    file.elf       firmware (primary and/or secondary) to load
    file.hex       primary firmware can be in hex format
    -w             program device with given .elf fiiles
    -e             erase device
    -E eeprom.hex  program EEPROM        
    -c usbasp      use usbasp as ISP
    -b baud
    -p mcu
    -v             verbose
check for trampolines,handle vector_table, suggest DUAL_BASE

Below is the output of such a check,  it uses Avrdude to access the flash in the microprocessor.

# MCU=atmega2560 FLASH=262144 VECT_BASE=0x3fb00 VECT_SZ=256 BOOT_BASE=0x3fc00 BOOT_SZ=1024
# This Device is runing DualBoot based on optiboot 8.3
# NAME[0]=marlin BASE[0]=0x00000 SIZE[0]=90054
# TRAMPOLINES_START[0]=0x000001b2
# TRAMPOLINES_END[0]=0x00000322
# ../build.klipper/ramps_2nd/klipper.elf do not have trampolines
# NAME[1]=klipper BASE[1]=0x16000 SIZE[1]=32244
0x00000 - 0x16000 1st ../build.marlin/ramps/firmware.elf 90054 bytes DEFAULT
0x16000 - 0x3fb00 2nd ../build.klipper/ramps_2nd/klipper.elf 32244 bytes 
0x3fb00 - 0x3fc00 irq vector table... 256 bytes
0x3fc00 - 0x40000 DualBoot bootloader 1024 bytes
# Replace primary firmware,
DUAL_BASE min 0x15fc6 -> 0x16000
# Replace secondary firmware,
no trampolines in secondary firmware
DUAL_BASE max 0x37d0c -> 0x37d00

dualtool.sh can also flash firmware to the chip when given the -w option

dualtool.sh ../build.marlin/ramps/firmware.elf ../build.klipper/ramps_2nd/klipper.elf

That was it, my 3D printer can now run Klipper when connected to Linux computer, but still works in standalone mode under Marlin, all it takes is the flip of a switch.

The main limitation is that half the flash is not usable, but Marlin without SD-card support is removing one of the features I like about Marlin.

The Klipper firmware is much smaller than Marlin. In my next blog post, I will describe what it takes to make the Klipper firmware run flawlessly from the upper address space. Allowing us to have a full versio of Marlin running as primary firmware.

This means that in most cases we can keep our old trusted Marlin already installed on the printer, and just install Klipper alongside it.  Hence we try out Klipper without burning any bridges. Just as I had envisioned from the beginning, it was probably a little more complicated than I had thought.

The source code for avr-dualboot bootloader and tool is available on:

https://github.com/StorePeter/avr-dualboot

In the last blog-post we will make Klipper run in the upper address space

Install Klipper on your old 3D-Printer and still keep Marlin

This entry was posted in 3D printer, Arduino, Embedded. Bookmark the permalink.