Chapter 8. Other Features and Capabilities

8.1. Revision Control

Dno projects are simply directory systems. As such they are compatible with all known, sane, revision control systems. Git in particular, works just fine.

8.2. Configuration Options

Each Arduino board has a number of user-selectable configuration options. For some boards, like the esp32, there can be many, many options.

To view and modify the options for your selected Arduino board, use dno menu. This presents the user with a menu-based interface for selecting from the available options.

The selected options become compiler flags for the next build. They are recorded in a file called BOARD_OPTIONS, which becomes a dependency for all object files in the build. This means that changing an option will cause the next build to recompile everything.

8.3. .ino files

Typically, Arduino sketches are presented as .ino files. They need to contain only the functions setup() and loop().

Dno, naturally, supports the .ino file format but it does not require it.

If you would rather code your own main() function, you can. There does not have to be a .ino file in your Arduino program.

8.4. Tags

Tag files are reference files that tell your favourite editor where various symbols can be found in your source code. They enable you to rapidly move to places of interest in your code.

There are 2 major flavours of tag files: ctags and etags. If you have a need for tagfiles, you will know which you prefer.

Dno creates tagfiles using the targets: ctags and etags. These targets should be invoked in a code directory: either a board directory, or the project directory if you are not using board directories.

The ctags target will create a file called tags, while the etags target creates a file called TAGS.

Once these files have been created, dno will automatically keep them up to date. Note that the clean target will leave any tags files in place, while the pristine target will remove them.

8.5. Serial Communication

8.5.1. Using screen (the monitor target)

You can monitor and interact with your Arduino's serial connection using dno monitor.

This creates a connection to your Arduino using the Linux screen command.

You should read the screen man page if you are unfamiliar with screen.

8.5.1.1. Getting Out of screen

If you are unfamiliar with screen you will quickly find yourself wondering, "how do I get out of this"?

If you find yourself in the monitor and want to get out, use C-a k. That is Control-A followed by k.

Another way to kill the screen program is by running dno noscreen from another session.

8.5.1.2. Why screen?

The screen utility provides multiplexed virtual terminals. This allows you to run multiple sessions from a single terminal.

By using screen as our monitor tool, we allow the user to switch between a command line session and the connection to the Arduino serial interface. This seemed like a good idea.

8.5.2. Output to terminal (using the cat target)

If you just to watch the output from your Arduino's serial connection and do not need to provide input, you can use the cat target.

This acts as though you were catting the output of the connected board to your terminal. Just as with the standard cat command, that output can be redirected to files, piped through filters, etc.

8.6. Custom dno Functionality

Should you wish to enhance dno's functionality with extra targets, or make subtle changes to its behaviour without hacking the dno executable itself, you can add a dno.mk file to the directory where you are running dno.

This is a makefile that will be included into dno before it starts its directory discovery process. It can provide extra target and variable definitions, and so can change and add to dno's functionality.

Since this is loaded quite early in the process of discovering dependencies and definitions, it is possible that the definitions you may want to customise will be overwritten by the load of a subsequent dependency file, in particular the BOARD_INFO file.

If you need your definitions to be evaluated at the end of makefile processing, after all other dependency files have been updated and evaluated, you can provide a definition for INO_FINAL. This will be evaluated after everything else but before beginning the build process proper. Here is an example:

# The following will override the definition of upload.speed in
# BOARD_INFO.
define INO_FINAL
  upload.speed=9600
endef
      

Note that because this is eval'd using make's eval function, you may need to escape any "$" characters. See the Gnu make manual for details.

8.7. Using External Programmers

8.7.1. Limitations of Serial Uploading

Most Arduino programming can be done by simply uploading sketches using the board's serial interface. In this case, the uploader (eg avrdude) negotiates with the board's built-in bootloader, which loads the incoming sketch into the board.

In some cases though serial uploading may be inadequate for your needs:

  • You may be using a board which doesn't provide a serial interface.

    Many inexpensive minimalist boards are available with no serial interfaces. Or you may have built your own board, with custom hardware and no serial interface.

  • Your board's serial interface pins may be being used for something else.

    The Rx/Tx pins can be used as digital I/O pins. Maybe your project needs extra I/O pins and does not need a serial interface.

  • Your board may not have a bootloader.

    In this case, serial uploading is simply not possible.

  • Your bootloader may have become corrupted.

    It is not entirely clear how this can happen, but it is certainly known to happen.

  • You may want to load a new bootloader.

    You may want to change the boot behaviour, particularly startup timeouts.

  • You may need to load a bigger sketch than your current bootloader allows.

    By burning a smaller-than-standard bootloader, you can free up space that can be used by sketches.

It is also claimed that loading sketches using an external programmer may be faster than by uploading through the serial interface.

8.7.2. External Programmer Options

There are many options available for external programmers. The file programmers.txt which can be found, for each architecture, in the same installed arduino platform directory as the boards.txt and platform.txtfiles, lists and provides parameters for driving each supported external programmer.

To list the set of available programmers use dno list-programmers. The set of available programmers will depend on the architecture of the board you are using.

8.7.3. Using an Arduino as an External Programmer

If you are using an Arduino board and have a spare arduino, you can use that spare as an external programmer.

This is done by loading the ArduinoISP sketch onto the Arduino that will be your programmer, and then wiring your programmer to the target Arduino.

8.7.3.1. Uploading the ArduinoISP sketch using dno

  • Locate the ArduinoISP sketch code.

    The ArduinoISP sketch is part of the standard Arduino IDE installation. Typically it will be found in /usr/share/doc/arduino/examples/11.ArduinoISP/ArduinoISP/ArduinoISP.ino.

  • Create a project directory for it

    Move to the directory where you want to create your ArduinoISP project. Then create a project directory, and move into it:

    ~$ cd projects
    projects$ mkdir ArduinoISP
    projects$ cd ArduinoISP
    ArduinoISP$ 
    		

  • Copy the sketch into your new directory

    ArduinoISP$ cp usr/share/doc/arduino/examples/11.ArduinoISP/ArduinoISP/ArduinoISP.ino .
    ArduinoISP$
    		

  • Compile it.

    You will need to specify the board type. In the following example we will use an uno.

    ArduinoISP$ dno BOARD=uno
      Creating BOARD_TYPE...
      Creating BOARD_INFO...
      C++  ./ArduinoISP.ino 
      C++  [SPI] SPI.cpp
      AS  [core] wiring_pulse.S
      C  [core] wiring_shift.c
      C  [core] wiring_pulse.c
      C  [core] wiring_digital.c
      C  [core] wiring.c
      C  [core] wiring_analog.c
      C  [core] WInterrupts.c
      C  [core] hooks.c
      C++  [core] WString.cpp
      C++  [core] WMath.cpp
      C++  [core] USBCore.cpp
      C++  [core] Tone.cpp
      C++  [core] Stream.cpp
      C++  [core] Print.cpp
      C++  [core] PluggableUSB.cpp
      C++  [core] new.cpp
      C++  [core] main.cpp
      C++  [core] IPAddress.cpp
      C++  [core] HardwareSerial.cpp
      C++  [core] HardwareSerial3.cpp
      C++  [core] HardwareSerial2.cpp
      C++  [core] HardwareSerial1.cpp
      C++  [core] HardwareSerial0.cpp
      C++  [core] CDC.cpp
      C++  [core] abi.cpp
      AR [libcore] abi.o...
      LD ArduinoISP.ino.o SPI.o
      OBJCOPY (hex) ArduinoISP.elf
    Program size:   4402 out of 32256  (13%: 27854 remaining)
       Data size:    482 out of 2048   (23%: 1566 remaining)
       ArduinoISP$
    		

  • Upload it.

    ArduinoISP$ dno upload
      Resetting device attached to (/dev/ttyACM0)...
        Uploading ArduinoISP.hex to /dev/ttyACM0
    
    avrdude: AVR device initialized and ready to accept instructions
    
    Reading | ################################################## | 100% 0.00s
    
    avrdude: Device signature = 0x1e950f (probably m328p)
    avrdude: reading input file "build/ArduinoISP.hex"
    avrdude: writing flash (4402 bytes):
    
    Writing | ################################################## | 100% 0.72s
    
    avrdude: 4402 bytes of flash written
    avrdude: verifying flash memory against build/ArduinoISP.hex:
    avrdude: load data flash data from input file build/ArduinoISP.hex:
    avrdude: input file build/ArduinoISP.hex contains 4402 bytes
    avrdude: reading on-chip flash data:
    
    Reading | ################################################## | 100% 0.57s
    
    avrdude: verifying ...
    avrdude: 4402 bytes of flash verified
    
    avrdude: safemode: Fuses OK (E:00, H:00, L:00)
    
    avrdude done.  Thank you.
    ArduinoISP$
    		

8.7.3.2. Wiring the Arduino Programmer

This github page describes the wiring and also provides good background information on this type of programming.

8.7.4. Uploading Sketches Using an External Programmer

Let's use our version of the blink sketch with the Deferal library. We'll create a board directory pro.8MHzatmega328 where we'll compile our sketch.

With our sketch compiled, we try:

pro.8MHzatmega328$ dno uploadp
  Creating PROGRAMMER...

  ERROR: PROGRAMMER not defined
    add PROGRAMMER=<programmer name> to command line
    (use dno list-programmers for values)

make: *** [/usr/local/bin/dno:1405: PROGRAMMER] Error 1
pro.8MHzatmega328$ 
	

This tells us we need to define our programmer. Our choices are given by dno list-programmers:

pro.8MHzatmega328$ dno list-programmers
[arduino:avr]
  avrisp                 AVR ISP
  avrispmkii             AVRISP mkII
  usbtinyisp             USBtinyISP
  arduinoisp             ArduinoISP
  arduinoisporg          ArduinoISP.org
  usbasp                 USBasp
  parallel               Parallel Programmer
  arduinoasisp           Arduino as ISP
  arduinoasispatmega32u4 Arduino as ISP (ATmega32U4)
  usbGemma               Arduino Gemma
  buspirate              BusPirate as ISP
  stk500                 Atmel STK500 development board
  jtag3isp               Atmel JTAGICE3 (ISP mode)
  jtag3                  Atmel JTAGICE3 (JTAG mode)
  atmel_ice              Atmel-ICE (AVR)
pro.8MHzatmega328$ 
	

We will use the arduinoasisp option, which is for an Arduino wired as a programmer as described here. Let's try again:

pro.8MHzatmega328$ dno uploadp PROGRAMMER=arduinoasisp
  Creating PROGRAMMER...
    Uploading blink.hex to /dev/ttyACM0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "build/blink.hex"
avrdude: writing flash (1726 bytes):

Writing | ################################################## | 100% 1.95s

avrdude: 1726 bytes of flash written
avrdude: verifying flash memory against build/blink.hex:
avrdude: load data flash data from input file build/blink.hex:
avrdude: input file build/blink.hex contains 1726 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 1.09s

avrdude: verifying ...
avrdude: 1726 bytes of flash verified

avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF)

avrdude done.  Thank you.

pro.8MHzatmega328$ 
	

And we see the LED flashing (on both Arduinos). Note that a new file PROGRAMMER has been created which records the type of programmer we are using. From here on it is no longer necessary to specify PROGRAMMER=... on the dno invocation.

8.7.5. Installing Bootloaders

Typically you will want to install a new bootloader for one of two reasons: your bootloader may have stopped working, in which case you will want to try to restore it to its original working state; or you may need functionality that is not built into your current bootloader.

To install a bootloader the dno burn target is used. If this is done in a board directory, dno will attempt to burn the standard (default) bootloader image. If you are instead in a boot directory (a directory named boot, within a board directory), dno will attempt burn a custom bootloader image.

8.7.5.1. Restoring (Burning) the Standard Bootloader

The Arduino IDE is packaged with standard bootloader images. The path to such images is given by {runtime.platform.path}/bootloaders/{bootloader.file} as defined in platform.txt and boards.txt.

We re-install the standard bootloader as follows (from within our blink project):

pro.8MHzatmega328$ dno burn
About to burn bootloader: /usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex
Press Y to continue? y
  Burning bootloader: /usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "/usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex"
avrdude: writing flash (32206 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 32206 bytes of flash written
avrdude: verifying flash memory against /usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex:
avrdude: load data flash data from input file /usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex:
avrdude: input file /usr/share/arduino/hardware/arduino/avr/bootloaders/atmega/ATmegaBOOT_168_atmega328_pro_8MHz.hex contains 32206 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 32206 bytes of flash verified
avrdude: reading input file "0x0F"
avrdude: writing lock (1 bytes):

Writing |                                                    | 0% 0.00s ***failed;  
Writing | ################################################## | 100% 0.07s

avrdude: 1 bytes of lock written
avrdude: verifying lock memory against 0x0F:
avrdude: load data lock data from input file 0x0F:
avrdude: input file 0x0F contains 1 bytes
avrdude: reading on-chip lock data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x0000
         0xcf != 0x0f
avrdude: verification error; content mismatch

avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF)

avrdude done.  Thank you.

make: *** [/usr/local/bin/dno:1718: burn] Error 1
pro.8MHzatmega328$ 
	  

The verification error above is confusing. The bootloader burned in this operation appears to work just fine.

Note that, since burning a bootloader is an operation with consequences, the user is asked to confirm the operation by pressing Y.

8.7.5.2. Burning a Custom Bootloader

Custom bootloaders may be built in a special directory called boot within a board directory.

Creating custom bootloader images is described here.

Once a suitable image has been created, running dno burn from the boot directory will burn the image. The external programmer must be connected and the PROGRAMMERS file should have been created.

Note that a PROGRAMMERS file from the parent board directory will be used if there is no local version. If neither file exists, a local version will have to be created.

The following shows the burning of an optiboot bootloader image for a 3.3V Arduino pro mini using an Arduino Uno as our external programmer:

boot$ dno burn PROGRAMMER=arduinoasisp
  Creating PROGRAMMER...
  Creating BOARD_INFO...
  CREATE BOOTIMAGE:
    "/usr/bin/make" atmega328 AVR_FREQ=8000000L LED_START_FLASHES=3
    avr-gcc (GCC) 5.4.0
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    BAUD RATE CHECK: Desired: 115200, Real: 111111, UBRRL = 8, Difference=-3.5%
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=115200 -DLED_START_FLASHES=3                   -c -o optiboot.o optiboot.c
optiboot.c:422:2: warning: #warning BAUD_RATE off by greater than -2% [-Wcpp]
 #warning BAUD_RATE off by greater than -2%
  ^
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=115200 -DLED_START_FLASHES=3                 -Wl,-Tlink_optiboot.ld -Wl,--relax -nostartfiles -o optiboot_atmega328.elf optiboot.o 
    avr-size optiboot_atmega328.elf
       text	   data	    bss	    dec	    hex	filename
        482	      0	      0	    482	    1e2	optiboot_atmega328.elf
    avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_atmega328.elf optiboot_atmega328.hex
    avr-objdump -h -S optiboot_atmega328.elf > optiboot_atmega328.lst
    rm optiboot.o
    mv optiboot_atmega328.hex optiboot_atmega328_pro_8MHz.hex
    mv optiboot_atmega328.lst optiboot_atmega328_pro_8MHz.lst
About to burn bootloader: optiboot_atmega328_pro_8MHz.hex
Press Y to continue? y

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "optiboot_atmega328_pro_8MHz.hex"
avrdude: writing flash (32768 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 32768 bytes of flash written
avrdude: verifying flash memory against optiboot_atmega328_pro_8MHz.hex:
avrdude: load data flash data from input file optiboot_atmega328_pro_8MHz.hex:
avrdude: input file optiboot_atmega328_pro_8MHz.hex contains 32768 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 32768 bytes of flash verified
avrdude: reading input file "0x0F"
avrdude: writing lock (1 bytes):

Writing |                                                    | 0% 0.00s ***failed;  
Writing | ################################################## | 100% 0.07s

avrdude: 1 bytes of lock written
avrdude: verifying lock memory against 0x0F:
avrdude: load data lock data from input file 0x0F:
avrdude: input file 0x0F contains 1 bytes
avrdude: reading on-chip lock data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x0000
         0xcf != 0x0f
avrdude: verification error; content mismatch

avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF)

avrdude done.  Thank you.

make: *** [/usr/local/bin/dno:1695: burn] Error 1
boot$ 
	  

As with the burn for the standard bootloader, the verification step seems to have failed, however the updated board appeared to work just fine.

8.8. Building Custom Bootloaders

Custom bootloaders can be used in place of the standard bootloader in order to change your Arduino's standard boot behaviour.

8.8.1. Building Custom Bootloaders With Dno

Since there are many ways of putting together a custom bootloader, dno cannot know them all. Instead, it makes some, reasonable, assumptions about how you will want to construct your bootloader image and provides some relatively simple ways to customise the build process.

In fact dno does not need to be involved in the bootloader image build at all, but it will need to know what the image is called in order to be able to burn it to your board. That said, because dno is always supposed to do the right thing by default, it will try to find a way to build your board for you.

8.8.1.1. Default Bootloader Image Build Mechanism

If you invoke dno with no target in a boot directory, it will assume you want to build a bootloader image. The way it does this, by default, is to simply call make. It assumes that any boot directory worth its salt will, in addition to source code, contain a Makefile that describes how to build the bootloader.

Note that since dno uses an external makefile, it has no visibility itself on whether the bootloader image is up to date with respect to its dependencies. For this reason the burn target has, by default, a dependency on the boot target.

8.8.1.2. Bootloader Image File Identification

By default dno assumes that the first file it can find in the boot directory that is named <something>.hex or <something>.bin is the bootloader image file. This is the file that the burn target will attempt to upload to the programmer.

8.8.1.3. Customising Bootloader Image Handling Behaviour

A number of make variables are used to allow customisation of dno's build process for bootloader images. These can be specified in a dno.mk file which should be placed in the boot directory.

  • BOOT_BUILDER

    This is the command that is used to build the bootloader image file. By default it is a recursive make but it could be anything.

  • BOOT_TARGET

    This is the target that is passed to the make invocation in BOOT_BUILDER.

  • DEFAULT_BOOT_TARGET

    This is the default target used when dno is called with no explicit target in a boot directory and calls BOOT_BUILDER.

    It defaults to default_boot_target which is a PHONY target. The explicit target boot is an alias.

  • BURN_DEPS

    This defaults to DEFAULT_BOOT_TARGET and provides the dependency for the burn target to the boot target. If you don't want the burn target to rebuild the boot image, you should redefine this as an empty definition (BURN_DEPS = ).

  • BOOTLOADER_IMAGE

    This identifies the bootloader image file. It defaults to a shell command that looks for the first file in the directory named <something>.hex or <something>.bin.

8.8.2. Worked Example: Building an Optiboot Bootloader Image

A number of pre-built bootloader images are available for many different Arduino boards. One of the more popular is optiboot.

This bootloader is much smaller than the standard bootloader allowing more space for sketches, provides faster baud rates for sketch uploads, and implements a "fastboot" that immediately starts your loaded sketch when the board is powered-up.

We will use our previous blink project for this, and build a bootloader for an Arduino pro mini. We'll start by cleaning up the project directories and then creating a boot directory under the pro.8MHzatmega328 directory.

~$ cd <path-to-blink-project>
blink$ dno pristine
Super-cleaning .
Super-cleaning ./pro.8MHzatmega328
Super-cleaning ./Deferal
Super-cleaning ./Deferal/tests
Super-cleaning ./Deferal/docs
blink$ cd pro.8MHzatmega328
pro.8MHzatmega328$ ls
pro.8MHzatmega328$ mkdir boot
pro.8MHzatmega328$ cd boot
boot$ 
	

8.8.2.1. Getting a copy

You can get the latest optiboot from github.

Once you have cloned this, copy the contents of the optiboot/bootloaders/optiboot directory into your boot directory:

boot$ cp <path-to-optiboot-directory>/optiboot/bootloaders/optiboot/* .
boot$ 
	  

Reading README.TXT (and Makefile), we discover that the make target we want for the build is atmega328_pro8. Although we can simply run make with this target we'll integrate the build with dno by creating a dno.mk:

boot$ echo "BOOT_TARGET = atmega328_pro8" >dno.mk
boot$ 	    
	  

Now, we can simply run dno in the boot directory to create our bootimage:

boot$ dno
  CREATE BOOTIMAGE:
    "/usr/bin/make" atmega328 AVR_FREQ=8000000L LED_START_FLASHES=3
    avr-gcc (GCC) 5.4.0
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    BAUD RATE CHECK: Desired: 115200, Real: 111111, UBRRL = 8, Difference=-3.5%
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=115200 -DLED_START_FLASHES=3                   -c -o optiboot.o optiboot.c
optiboot.c:422:2: warning: #warning BAUD_RATE off by greater than -2% [-Wcpp]
 #warning BAUD_RATE off by greater than -2%
  ^
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=115200 -DLED_START_FLASHES=3                 -Wl,-Tlink_optiboot.ld -Wl,--relax -nostartfiles -o optiboot_atmega328.elf optiboot.o 
    avr-size optiboot_atmega328.elf
       text	   data	    bss	    dec	    hex	filename
        482	      0	      0	    482	    1e2	optiboot_atmega328.elf
    avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_atmega328.elf optiboot_atmega328.hex
    avr-objdump -h -S optiboot_atmega328.elf > optiboot_atmega328.lst
    rm optiboot.o
    mv optiboot_atmega328.hex optiboot_atmega328_pro_8MHz.hex
    mv optiboot_atmega328.lst optiboot_atmega328_pro_8MHz.lst
boot$ 
	  

8.8.2.2. Burning It

See above.

8.8.2.3. Using It

Once you have installed a new bootloader, with new capabilities, you will have changed your board type: it will probably support a different baud rate; it may free up space that can be used by sketches. This means you will need to change the board type and rebuild your BOARD_INFO file. This, in turn, means you will need to install a new set of platform.txt and boards.txt files.

8.8.2.4. Installing Optiboot Configuration Files

First, we must find the .json file that describes to the Arduino IDE, where to find information for boards with an Optiboot bootloader.

As described in the Optiboot github page, this can be found on the Optiboot Release page, and is called package_optiboot_optiboot-additional_index.json. Copy the link to this file.

Open up the Arduino IDE, and select File->Preferences. Paste the link into the "Additional Boards Manager URLs:" field.

Now, Go into the boards manager Tools->Board...->Boards Manager... in the IDE. In the search bar, type Optiboot. An entry for the current version of Optiboot should be shown. Press the Install button within this entry.

8.8.2.5. Add Optiboot boards to dno

Now we need to find where the Arduino IDE installed the new board files. We know that the IDE installs these under the .arduino15 directory under your home directory. Let's look there:

~$ cd .arduino15/
.arduino15$ find . -name boards.txt
./packages/esp32/hardware/esp32/3.3.8/boards.txt
./packages/Optiboot/hardware/avr/0.8.0/boards.txt
.arduino15$ 
	  

Now we tell dno about this directory:

.arduino15$ dno install_extra_boards BOARDS_PATH=~/.arduino15/packages/Optiboot/hardware/avr/0.8.0
	  

And we check that we can see the new boards:

.arduino15$ dno show_boards BOARD_TYPE=Optiboot
	      
    Supported Arduino Board Types
============================================================
Optiboot.avr.optiboot28:             Optiboot on 28-pin cpus
Optiboot.avr.optiboot32:             Optiboot on 32-pin cpus
Optiboot.avr.optiboot1280:           Optiboot on Mega1280
Optiboot.avr.optiboot1284:           Optiboot on (generic) Mega1284
Optiboot.avr.optibootm32:            Optiboot on Mega32
Optiboot.avr.optiboott84:            Optiboot on Tiny84
Optiboot.avr.optiboot2560:           Optiboot on Mega2560
Optiboot.avr.optibootxmini168b:      Optiboot Xplained Mini 168pb
Optiboot.avr.optibootxmini328pb:     Optiboot Xplained Mini 328pb
Optiboot.avr.optibootxmini328p:      Optiboot Xplained Mini 328p
.arduino15$ 	      
	  

8.8.2.6. Renaming our board type directory

First, we need to identify the full board type for our 3.3v pro mini. The basic type, from the list given above, will be Optiboot.avr.optiboot32. Let's see what options we have for that board:

.arduino15$ dno show_boards BOARD_TYPE=optiboot32 OPTIONS=y

    Supported Arduino Board Types
============================================================
Optiboot.avr.optiboot32:             Optiboot on 32-pin cpus
  mhz=16MHz                            16MHz
  mhz=8MHz                             8MHz (int)
  mhz=1MHz                             1MHz (int)
  cpu=atmega328p                       ATmega328p
  cpu=atmega328                        ATmega328
  cpu=atmega168                        ATmega168
  cpu=atmega168p                       ATmega168p
.arduino15$ 
	  

It looks like we need to additionally specify both the speed and cpu type. That would give us the name: optiboot32.8MHz,atmega328p.

We'll rename our board directory and re-run dno:

.arduino15~$ cd <path-to-blink-project>
blink$ mv pro.8MHzatmega328 optiboot32.8MHz,atmega328p	    
blink$ cd optiboot32.8MHz,atmega328p
optiboot32.8MHz,atmega328p$ dno
  Creating BOARD_INFO...
  C++  ../blink.ino 
  C++  [Deferal] Deferal.cpp
  AS  [core] wiring_pulse.S
  C  [core] wiring_shift.c
  C  [core] wiring_pulse.c
  C  [core] wiring_digital.c
  C  [core] wiring.c
  C  [core] wiring_analog.c
  C  [core] WInterrupts.c
  C  [core] hooks.c
  C++  [core] WString.cpp
  C++  [core] WMath.cpp
  C++  [core] USBCore.cpp
  C++  [core] Tone.cpp
  C++  [core] Stream.cpp
  C++  [core] Print.cpp
  C++  [core] PluggableUSB.cpp
  C++  [core] new.cpp
  C++  [core] main.cpp
  C++  [core] IPAddress.cpp
  C++  [core] HardwareSerial.cpp
  C++  [core] HardwareSerial3.cpp
  C++  [core] HardwareSerial2.cpp
  C++  [core] HardwareSerial1.cpp
  C++  [core] HardwareSerial0.cpp
  C++  [core] CDC.cpp
  C++  [core] abi.cpp
  AR [libcore] abi.o...
  LD blink.ino.o Deferal.o
  OBJCOPY (hex) blink.elf
Program size:   1726 out of 32256  (5%: 30530 remaining)
   Data size:     35 out of 2048   (1%: 2013 remaining)
optiboot32.8MHz,atmega328p$ 
	  

Note that as we have changed the name of the board, everything must be rebuilt.

8.8.2.7. Making it Work

We tried uploading our new sketch and were unable to make contact with the board. Hmmm.

Looking back at our boot build, we see this:

. . .	    
    BAUD RATE CHECK: Desired: 115200, Real: 111111, UBRRL = 8, Difference=-3.5%
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=115200 -DLED_START_FLASHES=3                   -c -o optiboot.o optiboot.c
optiboot.c:422:2: warning: #warning BAUD_RATE off by greater than -2% [-Wcpp]
 #warning BAUD_RATE off by greater than -2%
. . .
	  

So, maybe the baud rate is a problem. Let's try building a bootloader image with a lower baud rate. We'll do this by modifying our dno.mk file to add a BAUD_RATE setting. This is what it looks once we have done this:

	    BOOT_TARGET = atmega328_pro8 BAUD_RATE=57600
	  

Let's rebuild:

boot$ dno boot
  CREATE BOOTIMAGE:
    "/usr/bin/make" atmega328 AVR_FREQ=8000000L LED_START_FLASHES=3
    avr-gcc (GCC) 5.4.0
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    BAUD RATE CHECK: Desired: 57600, Real: 58823, UBRRL = 16, Difference=2.1%
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=57600 -DLED_START_FLASHES=3                   -c -o optiboot.o optiboot.c
optiboot.c:429:2: warning: #warning BAUD_RATE off by greater than 2% [-Wcpp]
 #warning BAUD_RATE off by greater than 2%
  ^
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=57600 -DLED_START_FLASHES=3                 -Wl,-Tlink_optiboot.ld -Wl,--relax -nostartfiles -o optiboot_atmega328.elf optiboot.o 
    avr-size optiboot_atmega328.elf
       text	   data	    bss	    dec	    hex	filename
        482	      0	      0	    482	    1e2	optiboot_atmega328.elf
    avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_atmega328.elf optiboot_atmega328.hex
    avr-objdump -h -S optiboot_atmega328.elf > optiboot_atmega328.lst
    rm optiboot.o
    mv optiboot_atmega328.hex optiboot_atmega328_pro_8MHz.hex
    mv optiboot_atmega328.lst optiboot_atmega328_pro_8MHz.lst
boot$ 
	  

So, our wanted baud rate is still a little bit off. Let's burn it anyway and see if we can make it work:

boot$ dno burn
  CREATE BOOTIMAGE:
    "/usr/bin/make" atmega328 AVR_FREQ=8000000L LED_START_FLASHES=3
    avr-gcc (GCC) 5.4.0
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    BAUD RATE CHECK: Desired: 57600, Real: 58823, UBRRL = 16, Difference=2.1%
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=57600 -DLED_START_FLASHES=3                   -c -o optiboot.o optiboot.c
optiboot.c:429:2: warning: #warning BAUD_RATE off by greater than 2% [-Wcpp]
 #warning BAUD_RATE off by greater than 2%
  ^
    avr-gcc -g -Wall -Os -fno-split-wide-types -mrelax -mmcu=atmega328p -DF_CPU=8000000L  -DBAUD_RATE=57600 -DLED_START_FLASHES=3                 -Wl,-Tlink_optiboot.ld -Wl,--relax -nostartfiles -o optiboot_atmega328.elf optiboot.o 
    avr-size optiboot_atmega328.elf
       text	   data	    bss	    dec	    hex	filename
        482	      0	      0	    482	    1e2	optiboot_atmega328.elf
    avr-objcopy -j .text -j .data -j .version --set-section-flags .version=alloc,load -O ihex optiboot_atmega328.elf optiboot_atmega328.hex
    avr-objdump -h -S optiboot_atmega328.elf > optiboot_atmega328.lst
    rm optiboot.o
    mv optiboot_atmega328.hex optiboot_atmega328_pro_8MHz.hex
    mv optiboot_atmega328.lst optiboot_atmega328_pro_8MHz.lst
About to burn bootloader: optiboot_atmega328_pro_8MHz.hex
Press Y to continue? y


avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "optiboot_atmega328_pro_8MHz.hex"
avrdude: writing flash (32768 bytes):

Writing | ################################################## | 100% 0.00s

avrdude: 32768 bytes of flash written
avrdude: verifying flash memory against optiboot_atmega328_pro_8MHz.hex:
avrdude: load data flash data from input file optiboot_atmega328_pro_8MHz.hex:
avrdude: input file optiboot_atmega328_pro_8MHz.hex contains 32768 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.00s

avrdude: verifying ...
avrdude: 32768 bytes of flash verified
avrdude: reading input file "0x2F"
avrdude: writing lock (1 bytes):

Writing |                                                    | 0% 0.00s ***failed;  
Writing | ################################################## | 100% 0.07s

avrdude: 1 bytes of lock written
avrdude: verifying lock memory against 0x2F:
avrdude: load data lock data from input file 0x2F:
avrdude: input file 0x2F contains 1 bytes
avrdude: reading on-chip lock data:

Reading | ################################################## | 100% 0.01s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x0000
         0xef != 0x2f
avrdude: verification error; content mismatch

avrdude: safemode: Fuses OK (E:FD, H:DA, L:FF)

avrdude done.  Thank you.

make: *** [/usr/local/bin/dno:1706: burn] Error 1
boot$ 
	  

Maybe that has worked. On a side note, notice that the burn target also causes the boot target to be run, and that this always rebuilds our bootloader. We'll try to fix this later. For now we'll want to make our upload work with the new bootloader.

Looking at the BOARD_INFO file in the parent, optiboot32.8MHz,atmega328p, directory we see this definition:

upload.speed=115200
	  

We'll want to override this to set it to 57600. We could simply edit the BOARD_INFO file but it would be reset and our edits lost any time that dno decides that it needs to be updated. Instead, we will create a dno.mk file and use that to override the definition. Here is our new dno.mk:

define DNO_FINAL
    upload.speed=57600
endef
	  

We have defined a macro, DNO_FINAL, which provides our updated upload.speed definition. This macro is expanded by dno as the last step before building targets. This means that the definition will override the previous definition from BOARD_INFO. Let's build again, and try an upload:

optiboot32.8MHz,atmega328p$ dno
  Creating BOARD_INFO...
  AS  [core] wiring_pulse.S
  C  [core] wiring_shift.c
  C  [core] wiring_pulse.c
  C  [core] wiring_digital.c
  C  [core] wiring.c
  C  [core] wiring_analog.c
  C  [core] WInterrupts.c
  C  [core] hooks.c
  C++  [core] WString.cpp
  C++  [core] WMath.cpp
  C++  [core] USBCore.cpp
  C++  [core] Tone.cpp
  C++  [core] Stream.cpp
  C++  [core] Print.cpp
  C++  [core] PluggableUSB.cpp
  C++  [core] new.cpp
  C++  [core] main.cpp
  C++  [core] IPAddress.cpp
  C++  [core] HardwareSerial.cpp
  C++  [core] HardwareSerial3.cpp
  C++  [core] HardwareSerial2.cpp
  C++  [core] HardwareSerial1.cpp
  C++  [core] HardwareSerial0.cpp
  C++  [core] CDC.cpp
  C++  [core] abi.cpp
  C++  [Deferal] Deferal.cpp
  C++  ../blink.ino 
  AR [libcore] abi.o...
  LD blink.ino.o Deferal.o
  OBJCOPY (hex) blink.elf
Program size:   1726 out of 32256  (5%: 30530 remaining)
   Data size:     35 out of 2048   (1%: 2013 remaining)
marc:optiboot32.8MHz,atmega328p$ dno upload
  Resetting device attached to (/dev/ttyUSB0)...
    Uploading blink.hex to /dev/ttyUSB0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "build/blink.hex"
avrdude: writing flash (1726 bytes):

Writing | ################################################## | 100% 0.90s

avrdude: 1726 bytes of flash written
avrdude: verifying flash memory against build/blink.hex:
avrdude: load data flash data from input file build/blink.hex:
avrdude: input file build/blink.hex contains 1726 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.76s

avrdude: verifying ...
avrdude: 1726 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

optiboot32.8MHz,atmega328p$ 
	  

So, the upload succeeded and we see our LED blinking. We can call that a success.

8.8.2.8. Some Final Cleanup

We noted above that whenever we build the burn target, the boot target is performed, and each time that is done it recompiles and relinks the boot image regardless of whether it needs to.

Let's fix that because that's how we roll.

What is actually happening is that our burn target depends on our boot target, through the definition of BURN_DEPS. We could simply eliminate this dependency by setting BURN_DEPS to an empty string but if the boot image did need updating it then wouldn't happen. The underlying problem is that optiboot's Makefile always does a full rebuild, whether it needs to or not.

We would like the bootimage to only be rebuilt if something has changed. Here is what we will do:

  • create an explicit target for the boot image;
  • make that depend on all of the local source files;
  • add an additional dependency on dno.mk;
  • make burn depend on the boot image.

This is our modified dno.mk:

TARGET_ARCH = atmega328_pro8
TARGET_ARCHNAME = atmega328_pro_8MHz
BOOT_TARGET = $(TARGET_ARCH) BAUD_RATE=57600
TARGET_IMAGE = optiboot_$(TARGET_ARCHNAME).hex

# Our boot image needs to be rebuilt if any Makefile, source file, or
# this file is updated.  The build command is the same as for the
# explicit boot target.  Note that the explicit boot target wil
# continue to build the boot image unconditionally.  This target is
# just as a dependency for the burn target.
#
$(TARGET_IMAGE): $(wildcard *.c *.h Makefile.* *.mk) Makefile
	$(TARGET)
	$(FEEDBACK) CREATE BOOTIMAGE:
	$(AT) $(call indent,$(BOOT_BUILDER))

BURN_DEPS = $(TARGET_IMAGE)	    
	  

Now, our burn target will only rebuild the boot image if it has to: if the code or build instructions are more up to date than the image file. The boot target though, will still unconditionally rebuild the boot image.

8.9. EEPROMs

Dno provides limited built-in support for writing to an Arduino's EEPROM.

The reason that it is limited is that the platform.txt file (and the Arduino platform specification) does not provide support.

For AVR-architecture boards (the traditional Arduinos), dno provides the eeprom target, which will attempt to burn an eeprom image to a connected Arduino. Use this in much the same way as you use the upload target.

For non-AVR architectures eeprom support can be added by providing a suitable definition for recipe.eep.upload.pattern in a dno.mk (see Custom dno Functionality). See the built-in definition for this in the dno executable for an example of how this can be defined.

A typical use-case for EEPROMs would be to give each Arduino in a connected cluster its own unique identifier or key. This would mean the same image could be uploaded to each member of the cluster, but a separate eeprom image provided for the individual members.

8.9.1. Coding For EEPROMs

There is much online documentation about using Arduino EEPROMs. Little of it seems to cover how to create eeprom images for directly writing to a board.

We'll take our blink sketch as the basis for the example below.

8.9.1.1. Include the eeprom Header

Add #include <avr/eeprom.h>. Something like this:

. . . 

#include <Deferal.h>
#include <avr/eeprom.h>

. . . 
	  

8.9.1.2. Create a Struct Representing the EEPROM Contents

We add a struct definition, with some initialisation. Note the use of the EEMEM directive. This is the crucial element, and for more information on using Arduino EEPROMs, this appears to be the best term to search for on the internet.

Our code now looks like this:

. . . 

#include <Deferal.h>
#include <avr/eeprom.h>

static bool led_is_on;
static Deferal myTimer(500);

#define SENTINEL 0xdeadbeef

struct {
    uint32_t sentinel;
    char my_nodename[40];
    uint16_t my_node_id;
} eeprom EEMEM = {
  	          SENTINEL,
		  "Here, put this fish in your ear",
		  42}; 
. . . 
	  

8.9.1.3. Using the EEPROM Contents

Now, we need to make use of the contents. We will read the eeprom structure, and write the values we have found to our serial line. Let's do this on each blink.

To ensure that we are not reading uninitialised junk, we will test that the sentinel field contains our expected value. If not, then the EEPROM has not been written.

This is the final program. Note that it is poorly commented. Adding Doxygen comments is left as an exercise for the reader.

/*
  Blink

*/

#include <Deferal.h>
#include <avr/eeprom.h>
static bool led_is_on;
static Deferal myTimer(500);

#define SENTINEL 0xdeadbeef
#define SERIAL_BAUD_RATE 57600

struct {
    uint32_t sentinel;
    char my_msg[40];
    uint16_t my_node_id;
} eeprom EEMEM = {
  	          SENTINEL,
		  "Here, put this fish in your ear",
		  42}; 

static void
led_on(int led)
{
    digitalWrite(led, HIGH);
    led_is_on = true;
}

static void
led_off(int led)
{
    digitalWrite(led, LOW);
    led_is_on = false;
}

static void
toggle_led(int led)
{
    if (led_is_on) {
	led_off(led);
    }
    else {
	led_on(led);
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    led_off(LED_BUILTIN);
    Serial.begin(SERIAL_BAUD_RATE);
}


void
send_msg()
{
    char c;
    if (eeprom_read_dword(&eeprom.sentinel) == SENTINEL) {
        Serial.print("Node ");
        Serial.print(eeprom_read_word(&eeprom.my_node_id));
        Serial.print(" says ");
	for (int i = 0;
	     c = eeprom_read_byte(&(eeprom.my_msg[i]));
	     i++) {
	    Serial.print(c);
	}
	Serial.println();
    }
    else {
        Serial.print("eeprom not initialised: ");
        Serial.println(eeprom.sentinel);
    }
}

void loop()
{
    if (!myTimer.running()) {
	toggle_led(LED_BUILTIN);
	myTimer.again();
	send_msg();
    }
    // We can add code to do all sorts of things here, without the
    // timing of our led blinks being affected.
    
}
	  

8.9.1.4. Build and Test

Let's compile and upload:

pro.8MHzatmega328$ dno
  C++  [..] blink.ino
  LD blink.ino.o Deferal.o
  OBJCOPY (hex) blink.elf
pro.8MHzatmega328$ dno upload
  Resetting device attached to (/dev/ttyUSB0)...
/usr/local/bin/dno do_upload
make[1]: Entering directory '.../blink/pro.8MHzatmega328'
 
    Uploading blink.hex to /dev/ttyUSB0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "build/blink.hex"
avrdude: writing flash (2740 bytes):

Writing | ################################################## | 100% 1.41s

avrdude: 2740 bytes of flash written
avrdude: verifying flash memory against build/blink.hex:
avrdude: load data flash data from input file build/blink.hex:
avrdude: input file build/blink.hex contains 2740 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 1.19s

avrdude: verifying ...
avrdude: 2740 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

make[1]: Leaving directory '.../pro.8MHzatmega328'
pro.8MHzatmega328$ 
	  

Now, we connect out monitor using dno monitor. This is what we see:

�eeprom not initialised
eeprom not initialised
eeprom not initialised
eeprom not initialised
eeprom not initialised
eeprom not initialised
	  

So, it appears to work, though the eeprom is uninitialised.

8.9.1.5. Burn the Eeprom

Please note again that this operation is only supported under the AVR architecture. It may be possible to do something similar with other architectures but since the Arduino platform specification does not seem to offer it, its unlikely that dno will support it.

To burn the eeprom (for an AVR architecture Arduino board) we can simply use dno eeprom. However for demonstration and documentation purposes we will split this into two steps: dno eeprom_image and then dno eeprom.

The eeprom_image target creates the image that will be written to the board. The eeprom target writes the image.

pro.8MHzatmega328$ dno eeprom_image
  OBJCOPY (eep) blink.elf
pro.8MHzatmega328$ dno eeprom
  Resetting device attached to (/dev/ttyUSB0)...
/usr/local/bin/dno do_eeprom
make[1]: Entering directory '.../blink/pro.8MHzatmega328'
 
    writing eeprom from blink.eep using /dev/ttyUSB0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "build/blink.eep"
avrdude: writing eeprom (46 bytes):

Writing | ################################################## | 100% 0.38s

avrdude: 46 bytes of eeprom written
avrdude: verifying eeprom memory against build/blink.eep:
avrdude: load data eeprom data from input file build/blink.eep:
avrdude: input file build/blink.eep contains 46 bytes
avrdude: reading on-chip eeprom data:

Reading | ################################################## | 100% 0.38s

avrdude: verifying ...
avrdude: verification error, first mismatch at byte 0x001a
         0x22 != 0x20
avrdude: verification error; content mismatch

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

make[1]: *** [/usr/local/bin/dno:1420: do_eeprom] Error 1
make[1]: Leaving directory '.../blink/pro.8MHzatmega328'
make: *** [/usr/local/bin/dno:1414: eeprom] Error 2
pro.8MHzatmega328$ 
	  

8.9.1.6. Are We There Yet?

Well, let's try the monitor again:

	    Node 554 says Here, put this fish in your ear
Node 554 says Here, put this fish in your ear
Node 554 says Here, put this fish in your ear
Node 554 says Here, put this fish in your ear
Node 42 says Here, put this fish in your ear
Node 554 says Here, put this fish in your ear
Node 554 says Here, put this fish in your ear
. . .
	  

Well, something is a bit off , but it has mostly worked. The node_id value is usually wrong but sometimes ok. Whether this is a hardware problem (tired eeprom), or something else is difficult to say [2] . Either way, writing to the eeprom achieved something at least.



[2] It turns out the eeprom in my board was faulty. Running with a different board solved the problem.