Dno projects are simply directory systems. As such they are compatible with all known, sane, revision control systems. Git in particular, works just fine.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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$
This github page describes the wiring and also provides good background information on this type of programming.
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.
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.
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.
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.
Custom bootloaders can be used in place of the standard bootloader in order to change your Arduino's standard boot behaviour.
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.
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.
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.
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.
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$
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$
See above.
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.
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.
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$
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.
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.
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:
dno.mk;
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.
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.
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.
Add #include <avr/eeprom.h>.
Something like this:
. . . #include <Deferal.h> #include <avr/eeprom.h> . . .
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};
. . .
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.
}
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.
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$
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.