This chapter is all about what happens when you compile an Arduino sketch and how the various header files are used. Hopefully, by the time you have read (and understood) this chapter of the book, you’ll have a much better idea of what happens during the compilation of an Arduino sketch. However, before we dive into the gory details of a sketch’s compilation, we need to understand a bit about some of the text files that live in and around the $ARDINST directory.
These files are used to set up the IDE’s menu options and to define the AVR microcontroller and Arduino board to be used. Additionally, the IDE needs to know how to compile and upload sketches, and with lots of different boards nowadays, not just those with AVR microcontrollers, these numerous text files help the IDE configure the build tools and so on, for the specific board chosen from the Boards menu in the IDE.
Once we have discussed the various text files, we can then get down and dirty in the compilation process and also take a look at the hidden C++ files that the Arduino environment keeps well away from us.
2.1 Preferences.txt
The file preferences.txt holds all the preferences for the Arduino IDE and under recent versions of the IDE is no longer found within the location of the various IDE files, but in a separate area so that future upgrades to the IDE do not overwrite any changes that you make to the file. This explains why, when you configure the IDE on one version, an upgrade will pick up your preferences without you having to reapply them all every time you upgrade.
You should find the file in one of the following locations, however, as the file is created when you first run the IDE. If you have not yet done so, there will not be a preferences.txt file to be found. The initial set of defaults is defined in the file $ARDINST/lib/preferences.txt so those are the ones that will get written to the preferences.txt file, in the appropriate location, on first execution of the IDE. You may also delete the preferences.txt file if your edits have rendered it unusable, and it will be recreated by the IDE next time you open it.
You could edit $ARDINST/lib/preferences.txt to set your own preferred default settings, but as the file will be overwritten by IDE updates, it’s probably not a good option to consider.
On Linux – Look in /home/<YOUR_NAME>/.arduino15.
On Windows 7 – Look in C:Users<YOUR_NAME>AppDataLocalArduino15. I believe that on older versions of Windows, the file can be found in c:Documents and Settings<YOUR_NAME>Application DataArduino or even C:Program Files (x86)Arduinolib which I believe is where 32-bit applications get installed on 64-bit machines.
On MacOS – I believe you can look in /Users/<YOUR_NAME>/Library/Arduino, but I don’t have access to a Mac to check, sorry.
The easiest way to determine the location of the preferences.txt file is to open the IDE and select File ➤ Preferences; and on the “Settings” tab, at the bottom, you will see the full path to the preferences.txt file documented. Take heed of the warning to only edit the file when the IDE is closed – the IDE writes to the file when you shut it down and will overwrite any changes you made if the IDE was open when you changed the file.
The preferences.txt file contains all the configuration changes that you made using the IDE. The changes you make here will be saved between IDE upgrades. There are some additional preference changes that you need to make by editing the preferences file directly as the IDE doesn’t “surface” those options. A couple of examples follow.
If you have your preferences nicely set up, beware if you subsequently install and use the new Arduino command-line utility arduino-cli (see Chapter 6, Section 6.2, “Arduino Command Line,” for details). It uses the same location for all its files and will pick up whatever preferences you have configured for the IDE.
If, by some chance, you make a mistake editing the file and things stop working (properly), you can reset everything to defaults by simply deleting the preferences.txt file while the IDE is closed and running the IDE again.
2.1.1 Using an ICSP for All Uploads
Close the IDE if it is open.
Edit the preferences.txt file in your favorite text editor. (No, do not use Microsoft Word!)
Search for “upload.using”. It should currently look like this:
upload.using=bootloaderChange it to use the name of the device you are using. The device name you change it to must match one of the device names in the file $ARDINST/programmers.txt. In my case, I use a USB Tiny clone, from eBay, and I set my option to the following:
upload.using=USBtinyISPSave the file.
With this done, all sketches will now default to using the ICSP rather than the bootloader. This means that I no longer have to worry about remembering to change the programmer in the IDE, and, as a bonus, I will always overwrite the bootloader area and regain the use of that part of the Flash RAM for my own use. My Uno board will have an extra 512 bytes (1.5625% of the total) of flash for my programs, while my Duemilanove will regain an extra 2 Kb or 6.35% from the bootloader space.
Uploading with any ICSP device does still require you to press the Shift key when you click the upload button or to select Sketch ➤ Upload Using Programmer, even with this preference set.
I can still set the IDE to use a bootloader though. I just have to remember to select it from the Tools menu when I wish to create a sketch for a system that I cannot, or don’t want to, use the ICSP for uploads.
What’s an ICSP? Normally beginners would use a USB cable between the computer and the Arduino to upload programs. However, look at your Arduino and see if you can see a set of six pins in two rows of three. My Duemilanove has them beneath the reset switch. Those pins are where an ICSP can be plugged in to program the Arduino. Using one of these frees up the space taken by the bootloader program and gives you a bit more program space.
Unfortunately, it does prevent the Arduino from talking back to your computer using the Serial Monitor facility – Tools ➤ Serial Monitor. You will need an ICSP if you have to replace the ATmega328P on your board, and it comes with the default fuse settings. You will need to purchase one with an Uno bootloader already programmed in or use an ICSP to program your own.
My ICSP is from an eBay seller “finetech007” which no longer exists. There are lots of them if you search for “usbtiny isp” – here’s one example [www.ebay.co.uk/itm/USBTiny-USBtinyISP-AVR-ISP-programmer-for-Arduino-bootloader-Meag2560-uno-r3-CF/191780957944?epid=1138358692&hash=item2ca7092ef8:g:4-UAAOSwhvpd-fY7], identical to mine. (Sorry about the length of that URL!)
2.1.2 Change the Action of Home and End Keys
I’m reliably informed that this applies to Apple Mac users. It certainly has no effect on Windows 7 or Linux.
In the editor, when you press the Home key, the caret jumps to the very start of the sketch. When you press the End key, it jumps to the very end of the sketch. Apparently, this gets quite annoying when you expect the caret to be positioned at the start or end of the line you are editing. This sort of thing definitely needs changing!
Close the IDE, if it is open.
Edit the preferences.txt file.
Look for the following setting:
editor.keys.home_and_end_beginning_end_of_doc = trueChange it to the following and save the file:
editor.keys.home_and_end_beginning_end_of_doc = false
When you next open the IDE and load a sketch, the Home and End keys should now do your bidding.
Issue 3715 on the GitHub issues page for the IDE has some interesting details about this preference. It only exists from version 1.6.6 onwards. Prior to that, it was called editor.keys.home_and_end_travel_far.
In the first incarnations of version 1.6.6, the setting was coded backward. Setting it to false meant that the Home and End keys sent the cursor to the start or end of the document. You had to set it to true to get it to go to the beginning or end of the current line.
Since August 28, 2015, that was fixed; and it now works as it should. You can find all the details at https://github.com/arduino/Arduino/issues/3715.
2.1.3 Setting Tab Stops
Now, you would think that an editor, for writing code, would at least allow you the ability to adjust the width of the tab stops and whether or not they are to be converted into spaces. Not so the Arduino IDE!
Close the IDE if you currently have it open.
Edit the preferences.txt file.
Look for the following two lines:
editor.tabs.expand=trueeditor.tabs.size=2Change the second line as per the following:
editor.tabs.expand=trueeditor.tabs.size=4Save the file.
This causes tabs to indent four characters from the default of two characters. I don’t know about you, but I find two-character indents quite unreadable when looking at the structure of a sketch. I use four for just about everything I do. The preceding first line, which was not changed, determines if the IDE will convert tab characters into spaces. When set to true, the IDE will convert tabs to spaces, while false will leave the tab characters as they are, unchanged.
This makes editing in the IDE a little more comfortable, in my opinion.
2.2 Globally Defined Paths
Globally defined properties
Property Name | Description |
---|---|
{runtime.hardware.path} | The absolute path of the hardware directory which is the folder containing the current platform.txt file. |
{runtime.ide.path} | The absolute path of the directory where the arduino (or arduino.exe) application, the Arduino IDE, is found. |
{runtime.ide.version} | The version number of the Arduino IDE as a valid number. Each component of the version number will be converted to use two digits. Then all the dots are stripped out, and finally, any leading zeros are removed leaving the final value. For example, the Arduino IDE version 1.8.5 will become “01.08.05” which becomes “010805” before finally being assigned as runtime.ide.version=10805. IDE versions prior to version 1.6.0 used a single digit for the IDE version number. For example, version 1.5.6 was 156 as opposed to 10506. |
{ide_version} | Compatibility alias for runtime.ide.version. |
{runtime.os} | The operating system that the IDE is currently executing on. The values are “linux”, “windows”, and “macosx”. |
These global settings may be used in platform.txt, boards.txt, or, perhaps, but not very likely, programmers.txt. You may also use these paths in your amendments to the configuration files or in the various “local” versions that you create.
Various configuration files can have a local version; boards.txt, for example, may have boards.local.txt. This local version allows you to make changes to the system configuration and not have to reconfigure every time the Arduino IDE is updated. Unfortunately, not all of the configuration files have a local version – programmers.txt is one that I have come across that doesn’t. See https://github.com/arduino/Arduino/issues/8556 for details, if you are interested.
2.3 Boards.txt
The $ARDINST/boards.txt file defines the various menu options for different types of microcontroller devices. These options either will appear on the Boards menu in the Arduino IDE or will be used when a specific board is selected from that menu. The file is read, and the various options are decoded and used by the IDE at startup. New boards can be added quite simply, if desired, by editing this file. Let’s look inside at the entry for the Arduino Uno.
2.3.1 Arduino Uno Example
① Board name.
② This section defines identification settings used to determine the board’s identity when it is plugged into the USB port on your computer.
③ These settings define parameters used for uploading compiled code to the board.
④ Bootloader settings are listed in this section.
⑤ Various build options are specified here.
The Arduino Wiki at https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification mentions, at least for IDE version 1.5.3 which appears to be the most recently documented, that
This file contains definitions and meta-data for the boards supported. Every board must be referred through its short name, the board ID. The settings for a board are defined through a set of properties with keys having the board ID as prefix.
What it doesn’t mention is how the system is supposed to know that “uno”, for example, refers to the Arduino/Genuino Uno device.
From the preceding listing, it is pretty obvious that the Uno’s short name must be “uno” as that is the prefix in use for every entry in this section of the file.
2.3.1.1 Board Identifier
2.3.1.2 Identification Settings
Vid is the vendor identifier.
Pid is the product identifier for the specific vendor.
From the preceding text, we can clearly see two vendors – “0x2431” and “0x2A03” – and the appropriate product identifiers to suit each vendor. Bear in mind that it isn’t necessarily the actual manufacturer of the Arduino board that is being identified; it is most likely to be the chip that converts the data on the USB port into the correct format for the microcontroller. Some Uno boards have another AVR microcontroller taking care of the communications, while others have an FTDI chip – both will register as different pids.
Genuine boards, such as my own Duemilanove, which use an FTDI chip for communications, will not necessarily be recognized as the correct board. This is due to the FTDI chip which uses a generic pid and vid and is used by numerous different boards. However, this is nothing to worry about.
2.3.1.3 Upload Settings
To specify the tool to be used to carry out the upload, the upload.tool parameter is used. In this example, the tool in use is the program named avrdude. This tool is installed at the same time as the Arduino IDE.
The communications protocol to be used when uploading is defined in the upload.protocol parameter, while the maximum Flash and Static RAM (SRAM) sizes for the particular AVR microcontroller in use are defined in upload.maximum_size and upload.maximum_data_size parameters, respectively.
The maximum size of an ATmega328P’s Flash RAM is 32,768 bytes, so why does upload.maximum_size only allow 32,256 bytes? It’s because the remaining 512 bytes are used for the bootloader. The Optiboot bootloader is 512 bytes in size, so that amount of Flash RAM needs to be reserved from the maximum available.
Actually, the Optiboot bootloader is only 500 bytes in size. You can see this when you look at the start and end addresses in the compilation listing file, $ARDINST/bootloaders/opitiboot/optiboot_atmega328.lst, which are 7E00hex and 7FF3hex, respectively. Subtracting gives 1F3hex which is 499decimal, but we need to add one because we started counting from zero.
Communications will be carried out at the baud rate specified in upload.speed. For this Uno example, that will be at 115,200 baud.
2.3.1.4 Bootloader Settings
This section of the boards.txt file defines various parameters to be used when you choose Burn bootloader from the IDE menu.
It should be obvious (shouldn’t it?) that burning a bootloader will require an In-Circuit System Programmer (ISCP) device as the AVR microcontroller you are burning a bootloader into doesn’t yet have a bootloader to allow uploading via the normal USB connection to the board!
You should be very careful to ensure that you have selected the correct board when burning a bootloader – on a good day, it will simply fail to work. On a bad day, it will set the fuses to something that might cause you some grief trying to unravel and get reprogrammed. On a really bad day, it could convert your prized Arduino board into something resembling a brick.
Okay, it’s probably not that bad, but you might end up with a need to purchase a new ATmega328P, and hopefully, it will be one that comes complete with an Uno bootloader burned in. Otherwise, you’ll have to do the bootloader burning exercise all over again.
Yes, I admit it. I did brick an Arduino board, so that’s how I know. It was a Digispark board with an ATtiny85 microcontroller, but I bricked it anyway! Investigation showed that I set the fuse to disable the RESET pin so that it could be used as a normal I/O pin. No amount of programming with a high-voltage programmer would rescue it, so there must have been some other settings that I broke as well.
I still have the device, somewhere, and one day, I will find out what I did wrong and, hopefully, fix it. Perhaps.
To specify the tool to be used to carry out the upload, the bootloader.tool parameter is defined. In the case of the Uno we are looking at here, the tool in use is the program named avrdude – the same as in the preceding text for uploading compiled sketches.
As described in Chapter 7, Section 7.1, “ATmega328P Fuses,” the AVR microcontroller has a number of fuses that can be utilized to set various configurations of the AVR microcontroller itself. The bootloader.low_fuses, bootloader.high_fuses, and bootloader.extended_fuses parameters define the required hardware settings for the microcontroller on the board.
Finally in this section, the bootloader.file parameter defines which of the many bootloaders supplied with the IDE is to be used for this board. The Uno uses the file optiboot/optiboot_atmega328.hex which is to be found in the $ARDINST/bootloader/ directory.
You can, if you wish, change the bootloader by either editing the boards.txt file to change the appropriate parameter or duplicating an existing section and changing the bootloader. The latter option is preferred. It’s worth bearing in mind that any updates to the IDE will most likely overwrite your changes to boards.txt, so how do we avoid this problem?
2.3.1.4.1 Boards.local.txt
2.3.1.5 Build Settings
The build.mcu setting defines the name of the microcontroller for this particular board. For the Uno, only an ATmega328P is defined. For other boards, the Nano, for example, there are two different microcontrollers available, the ATmega168 and the ATmega328P. Within each of those two boards, there are two different configurations, and the boards.txt has entries for each variant with global settings for all Nanos as well as the specific settings for the different microcontroller boards and the variants thereof.
The parameter build.f_cpu defines the system clock (CLKcpu) for the board. The Uno has a 16 MHz crystal installed, so that’s the speed that is defined in this example. This setting is used in your sketches, although you won’t actually see it, as the F_CPU variable is used, for example, if calculating the desired baud rate when using the Serial interface.
The build.board property is used to set a compile-time variable ARDUINO_{build.board} to allow the use of conditional code between #ifdefs in sketches and/or header files. The Arduino IDE automatically generates a build.board value if not defined. In this example, the variable defined at compile time will be ARDUINO_AVR_UNO.
To determine which file path is to be used when the compiler is looking for various files, main.cpp, for example, the build.core setting is used. The parameter is used to build a path to the files in $ARDINST/cores/<uno.build.core>/, which, for the Uno in this example, will be $ARDINST/cores/arduino/.
The variant of the board is then defined using the build.variant setting. This is used to build a path to the files that live in $ARDINST/variants/<uno.build.variant>/ and is where you will find the file named pins_arduino.h which defines any variations over the standard settings that apply to this particular board. For this example of an Uno, the path defined will be $ARDINST/variants/standard/.
The IDE defines a number of global settings for the various paths to the cores and variants. These are available in other configuration files, but they don’t have the board’s prefix, so uno.build.core would correspond to the IDE’s global setting of build.core. If the board doesn’t specify a setting, the global one will be used; however, where a board does have an appropriate setting, that will override the global one created by the IDE, when the appropriate board is selected from the Boards menu in the IDE.
You can see some of these global settings in the file platform.txt.
2.3.1.6 Configuring an ICSP
The name you use here is one of the ones that are to be found in the $ARDINST/programmers.txt file which is itself described later in this chapter, in Section 2.5, “Programmers.txt.”
You should make this change while the IDE is closed. When you next open the IDE, any time you select the Uno device as your board, it will automatically select the USB Tiny ISP device, in this case, to perform the uploads, rather than the bootloader.
If you wish to make this change as the default for all boards, then you should edit the preferences.txt file, as documented in Section 2.1, “Preferences.txt,” earlier in this chapter.
2.4 Platform.txt
The $ARDINST/platform.txt file defines platform-specific features and command-line tools, where libraries live and what they are called, and so on. It contains the various recipes used by the IDE in order to compile, build, upload, and/or program various devices and boards according to their different needs.
What is a platform? Well, in the case of the ATmega328P, or other AVR microcontrollers, the platform defines all the tools, compilers, linkers, command lines to be used and so on, for Atmel AVR microcontrollers. Other non-AVR microcontroller boards will have their own platform to define the specific tools and others for that particular microcontroller. Arduino boards with, for example, an ARM chip on them will use a different platform from those with the AVR microcontrollers.
Using this method allows for a fairly simple manner in updating the system to cope with new boards.
The name will be shown in the Tools ➤ Boards menu of the Arduino IDE, in grayed-out text, above the list of boards that conform to this particular platform. According to the documentation on the Arduino web site at https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification, the version is currently unused and is reserved for future use.
Obviously, the version number of the platform can, and does, differ from the version of the IDE. Don’t be confused if you see something different.
2.4.1 Build Recipes
The platform.txt file, as mentioned earlier, contains a large amount of meta-data that configures the IDE to be able to compile sketches and upload them, among other things, for the Arduino boards running with AVR microcontrollers. It does this using recipes. Having different recipes for all the different platforms allows the IDE to be used for a myriad of different devices.
build.path – The path to the temporary folder to store various files created by the build process.
build.project_name – The project (sketch) name.
build.arch – The microcontroller architecture, which in our case is “avr” but, depending on the board, may be “sam”, “arm” and so on. The IDE gets this from the paths to $ARDINST.
On my system, $ARDINST is defined as $ARDBASE/hardware/arduino/avr – and the path portions define the hardware folder location, $ARDBASE/hardware; then the vendor name, arduino; and finally the architecture, avr for boards with the ATmega328P. For Arduino SAM boards, the path would be $ARDBASE/hardware/arduino/sam instead. The final part of the path gives the build.arch name.
A number of additional settings are defined within the $ARDINSToards.txt file based on the particular board chosen on the Tools ➤ Boards menu – see Section 2.3, “Boards.txt,” for those details – and the IDE global variables can be used within this file too. You can find more details on those variables in Section 2.2, “Globally Defined Paths.”
The compilation process can read source files written in plain C – these are the .c source files, C++ (.cpp), or even assembly language (.S). It has to know how to convert these files into object files (.o) which can be gathered together by the linker to create an executable file. The way this happens is by using the recipes within the platform.txt file.
recipe.c.o.pattern – To convert C files to object files
recipe.cpp.o.pattern – To convert C++ files to object files
recipe.S.o.pattern – To convert assembly language files to object files
You will notice, I hope, that the source format in each is case sensitive. Assembly language files must have an upper case .S extension.
"{compiler.path}{compiler.c.cmd}" defines where the compiler tool can be found – {compiler.path} – and what it is called, {compiler.c.cmd}. The use of double quotes allows for spaces and other non-alphanumeric characters in the path or command name.
In the IDE, I see that {compiler.path} is defined as {runtime.tools.avr-gcc.path}/bin/; and as you can see, these recipes can refer to other variables defined in this file or elsewhere. The {compiler.c.cmd} is defined as avr-gcc, and this is not actually the compiler, but a front end to all phases of the compilation process and which can be used to control the whole process.
{compiler.c.flags} defines a list of flags and options to be passed to the {compiler.c.cmd} utility to define how the build should progress, what outputs are required and so on. There are numerous options in IDE 1.8.5; but one in particular, -c, tells the compiler front end to stop compiling after the object file has been created and not to run the link phase.
-mmcu={build.mcu} defines another compiler option. It tells the C compiler which microcontroller is in use on the board. It comes from $ARDINST/boards.txt and, for the Uno, is defined as uno.build.mcu=atmega328p. The name part, uno, is stripped off first.
-DF_CPU={build.f_cpu} is very useful when writing code for multiple boards. The speed of the AVR microcontroller clock is defined in the compilation process, as opposed to being hardcoded in the actual source files, for example, #define F_CPU 16000000L. This would require editing before running on a board with a different clock speed.
The variable is defined in $ARDINST/boards.txt and, for the Uno, is uno.build.f_cpu=16000000L. The name part, “uno.”, is stripped off first.
-DARDUINO={runtime.ide.version} defines a numeric value for the Arduino IDE version in use. It is created automatically by the IDE and is described in Section 2.2, “Globally Defined Paths.” For IDE version 1.8.5, for example, it becomes 10805.
-DARDUINO_{build.board} references the uno.build.board=AVR_UNO variable from the $ARDINST/boards.txt – the example shown is, once more, for the Uno. This can be used in conditional code to determine the board in use and, from that, whether certain features are available or otherwise. In this example, it would define a variable named ARDUINO_AVR_UNO.
-DARDUINO_ARCH_{build.arch}, as described earlier, defines the architecture we are building for. For the purposes of this book, this will be “avr” giving ARDUINO_ARCH_avr.
{compiler.c.extra_flags} are some additional flags that you or I can define in $ARDINST/platform.local.txt to be added to the command line for this recipe. By default, these are blank.
{build.extra_flags} are some additional flags that you or I can define in $ARDINST/boards.local.txt to be added to the command line for this recipe. By default, these are blank.
{includes} is the list of paths that the compiler will use to search for files #included in the various source files. The format is -I/include/path and so on. You can have more than one path. The documentation online has this to say:
Note that older IDE versions used the recipe.preproc.includes recipe to determine includes, which is undocumented here. Since Arduino IDE 1.6.7 (arduino-builder 1.2.0) this was changed and recipe.preproc.includes is no longer used.
This is not really very helpful, as includes remains undocumented, and even the “no longer used” recipe recipe.preproc.includes actually has {includes} as part of its definition.
"{source_file}" is the path to the single source file being compiled. The double quotes allow for spaces and other non-alphanumeric characters in the file name.
-o "{object_file}" is the path to the single object file which will be created by the compilation phase. The double quotes allow for spaces and other non-alphanumeric characters in the file name.
2.4.2 Pre- and Post-build Hooks
recipe.hooks.sketch.prebuild.NUMBER.pattern – Called before sketch compilation
recipe.hooks.sketch.postbuild.NUMBER.pattern – Called after sketch compilation
recipe.hooks.libraries.prebuild.NUMBER.pattern – Called before libraries compilation
recipe.hooks.libraries.postbuild.NUMBER.pattern – Called after libraries compilation
recipe.hooks.core.prebuild.NUMBER.pattern – Called before core compilation
recipe.hooks.core.postbuild.NUMBER.pattern – Called after core compilation
recipe.hooks.linking.prelink.NUMBER.pattern – Called before linking
recipe.hooks.linking.postlink.NUMBER.pattern – Called after linking
recipe.hooks.objcopy.preobjcopy.NUMBER.pattern – Called before objcopy recipe execution
recipe.hooks.objcopy.postobjcopy.NUMBER.pattern – Called after objcopy recipe execution
recipe.hooks.savehex.presavehex.NUMBER.pattern – Called before savehex recipe execution
recipe.hooks.savehex.postsavehex.NUMBER.pattern – Called after savehex recipe execution
These are identified by the recipe.hooks part. The next part determines which stage in the compilation the hook will be called. Prexxxxx and postxxxxx indicate that the pattern will be called before the appropriate stage or afterward.
In order that multiple hooks can be called at any stage, the NUMBER part is a sequence number which should be 1, 2, 3, 4, and so on – there’s one number for each hook to execute at a given stage in proceedings. The end of the recipe is always the word pattern.
If you find that you require ten or more hooks, then your NUMBER parts should be 01, 02, ..., 10, 11, and so on.
I noticed that some, but not all, variables do not get expanded. Using "{source_file}", for example, doesn’t expand, but {build.source.path} does. Also, the entire text after the equals sign becomes part of the message, not just the command’s output. The preceding code, on Linux, displays the text “echo Compiling sketch: /full/path/to/my/sketch/here” rather than just “Compiling sketch:
/full/path/to/my/sketch/here”. A similar thing happens with Windows.
The command used cannot be a built-in command and must be found on your system’s path ($PATH on Linux and MacOS, %path% on Windows.)
On Windows, the echo command is a built-in command. It cannot be found on %path% when the compilation is started, so the whole compilation fails because echo can’t be found. This is because of the way that the Java command exec, from Runtime.getRuntime(), works.
So how did the preceding echo command work for Windows? I created an echo.bat file and put it on my Windows %PATH%.
Commands to be executed in the hooks must be found on the $PATH; if not, they will not be executed, and the recipe will fail.
2.5 Programmers.txt
The programmers.txt file is very much the least documented of the various text files used by the Arduino IDE. The Wiki pages describe the other files, but nothing at all, other than a brief mention of its name, for programmers.txt.
It is assumed, possibly incorrectly, that people creating and building new ICSP devices know what all the parameters mean and will supply a list of required entries, for their device, to be added to programmers.txt.
It is also likely that whenever anyone creates or updates a programming device, or settings, it would be submitted to the Arduino maintainers for inclusion in the next release of software.
The programmers.txt file will be overwritten by each new IDE update, so if you have made any changes, you really should keep a record of them prior to upgrading.
$ARDINST/programmers.txt holds details about the various programming devices that the Arduino IDE can use to upload code to your Arduino board. Unlike boards.txt and platform.txt, the IDE doesn’t seem to recognize a local variant, programmers.local.txt, even if one exists. Therefore, any changes that you make to your own installation will need to be made to the supplied $ARDINST/programmers.txt file, and this will be overwritten when the IDE is upgraded.
As this is a bit of a nuisance, I logged issue 8556 about it at https://github.com/arduino/Arduino/issues/8556. It could be that this is by design and not an actual problem. We shall see what transpires.
The file contains parameters that are relevant to the various programming devices that can be used, and depending on the settings, these may appear in the various menu options under the Tools menu in the IDE.
The device name, as it will appear in the Tools ➤ Programmer menu. In this case, it is “USBtinyISP.” You can change this if you prefer to use a different name.
The protocol to be used when executing the IDE option to “Upload using programmer.”
The tool that will be used when uploading. Here we can see that it is defined as using the avrdude utility.
Any extra parameters that may be needed to do the upload. In this example, there are none. However, if any were needed, they would be required to be consistent with the syntax of the programming tool in use.
This would allow the command line passed to avrdude to be supplied with the -P option, to select a serial port, and it would be set to the value chosen in the IDE on the Tools ➤ Port menu option.
This is obviously just an example; the USB Tiny device doesn’t need a serial port.
2.6 Compiling a Sketch
When you open a project in the Arduino IDE, you will notice that all files in the project directory with an .ino, .h, .c, or .cpp extension get placed on a tab of their own. These are assumed to be all the source files that make up your project. You can, if you wish, open other files within the IDE, but these will not automatically open in separate tabs when you subsequently reopen the project. They will have to be manually opened if editing or viewing is required.
You should also be aware that there is not a function called main() in any of the files open in the project. Anyone who has programmed in C or C++ will know that main() is the program’s entry point. What’s going on?
The Arduino IDE supplies its own main() function, so that you don’t have to. In order to make life easier for budding microcontroller makers and developers, the Arduino system hides a lot of stuff from you. I’ll be taking a look at the main() function soon.
When you compile a project in the Arduino IDE, a number of things take place, and these separate processes are described in the remainder of this chapter. Your sketch will be converted into a C++ file by the Arduino Preprocessor.
2.6.1 Arduino Sketch (*.ino) Preprocessing
Maybe create a temporary compiler working folder in the system’s main temporary folder or directory. This will be within /tmp on Linux and something along the lines of c:users<your_name>AppDataLocalTemparduino_build_<some_number> on Windows. This happens only on the very first compilation of this particular sketch. For the rest of this discussion, I shall refer to this folder as $TMP.
If your sketch is composed of a number of .ino files, those files are concatenated into a single .ino.cpp file, in the $TMP/sketch subfolder, starting with the main sketch file which is the .ino file with the same name as the sketch’s folder name. The remainder of the .ino files are appended to the end of the main one, in alphabetical order. If your sketch was named Blink.ino, then the generated file would be named $TMP/sketch/Blink.ino.cpp.
The line #include <Arduino.h> is added at the beginning of the .ino.cpp file, if not already present.
All libraries used in the sketch are detected, and the include paths for those libraries are discovered. This is done by running a dummy compilation with the output being discarded (to the null device on Windows and /dev/null on Linux) and processing any relevant error messages.
Prototypes for all functions in the .ino.cpp file are generated. If, as occasionally happens, a valid function prototype cannot be automatically generated, you will need to add one explicitly to the .ino file that defines the failing function.
The .ino.cpp file is processed so that there are relevant compiler preprocessor #line and #file directives so that error reporting will be accurate and refer to the correct lines in the correct source files, as opposed to referencing the lines within the concatenated .ino.cpp file.
These actions are performed by the arduino-preprocessor tool which lives on GitHub at https://github.com/arduino/arduino-preprocessor.
2.6.2 Arduino Sketch (*.ino) Build
Compiles the .ino.cpp file, created by the preprocessing stage, into a module with a .ino.cpp.o extension. This module file is stored in the $TMP/sketch subfolder created by the Arduino Preprocessor tool described earlier.
Compiles all other .c or .cpp files, including main.cpp, into separate modules in the $TMP/sketch subfolder.
If the sketch’s configuration – the board and so on – has not changed since the previous compilation, then some of these modules may be reused rather than recompiled. This saves time on the second and subsequent compilations. This is only done if the source file(s) for the module to be reused has not been edited or changed of course.
Any libraries used by the sketch will be compiled as separate modules too. Once again, these will be written as .o files in the $TMP/libraries subfolder.
The Arduino core files are compiled as .o files into $TMP/core. These core files are the like of wiring_analog.c, wiring_digital.c and so on, as installed under the IDE.
The individual core modules (*.o) are then built into a single static library, core.a, in the $TMP/core subfolder – for example, on Windows, c:users<your_name>AppDataLocalTemparduino_build_<some_number>corecore.a.
After all the modules have been created, the linker combines them all into a single elf format binary file. This file lives in the main temporary folder created earlier and will be named $TMP/<sketch_name>.ino.elf, $TMP/Blink.ino.elf, for example.
The $TMP/<sketch_name>.ino.elf file is then used to create a file named $TMP/<sketch_name>.ino.eep which contains data to be written to the AVR microcontroller’s EEPROM area.
The $TMP/<sketch_name>.ino.elf file is also used to create a file named $TMP/<sketch_name>.ino.hex which contains the code used to flash the AVR microcontroller with your sketch. This code is in “Intel Hex” format.
This ends the compilation process. If the upload button is clicked in the IDE, rather than the compile (or verify) button, then the $TMP/<sketch_name>.ino.hex file is uploaded to the AVR microcontroller, using an Arduino-specific version of the avrdude tool which can be found on GitHub at https://github.com/arduino/avrdude-build-script.
Sketch_name.ino.standard.hex – This is the hex file to upload your code and only your code.
Sketch_name.ino.with_bootloader.standard.hex – This file, if uploaded, will write both the bootloader and your application code.
If you have a bootloader installed on your ATmega328P, then you can use it to upload the files using avrdude. Normally, when using an ICSP to program your device, the bootloader will be overwritten when the chip is wiped. However, if you use the ICSP to upload the preceding file with the bootloader, then you effectively burn a bootloader as well as your application’s code into the Arduino board.
Either file can be uploaded using the bootloader – if it is still installed on your device – and after doing so, regardless of which of the two files you upload, the bootloader will still be present afterward.
All this “just works” and it makes life easy; however, what is the Arduino system hiding from you?
You can see all of this happening, before your very eyes, if you edit the preferences in the IDE to show verbose compiling and/or upload messages.
The following chapters describe the various files that your sketch ends up including when the compilation process has completed.
2.7 The Arduino main() Function
The Arduino main() function
① The first point to note is the inclusion of the file Arduino.h (found in the folder $ARDINC), and this is where numerous constants and other definitions specific to the Arduino are declared. If you look at this file, it makes interesting reading; there are numerous tests to determine which board is in use and which features of the AVR microcontroller can be used. The Arduino.h header file is described in Section 2.8, “Header File Arduino.h.”
② Within the main() function itself, there is a call to init() which is found in $ARDINC/wiring.c. This initializes a whole raft of features for the Arduino and carries out this based on the actual microcontroller in use on the board. If you decide to dive into this function, make sure that you are armed with a copy of the data sheet for your specific microcontroller; otherwise, nothing much will make sense.
③ The next function call, initVariant(), carries out any special initialization for boards that are possibly not covered by the standard initialization. The function defaults to doing nothing (you can see it at the top of main.cpp); but as it is declared as weak, it can be overridden, as required, in a sketch.
④ Some boards like the Leonardo use USB for serial communications and thus require USB setting up, so there is a test to see if this is required. If so, then the USBDevice.attach() function is called to do the needful.
⑤ The sketch’s setup() function is called next. This is where the sketch’s own initialization gets carried out.
⑥ After the call to setup(), an endless loop is entered where the sketch’s loop() function is called once on every pass through the loop.
⑦ The function serialEventRun() is called each time through the loop as well. This in itself calls out to another (weak) function named serialEvent(), if it exists in the sketch, and this is used to collect up any data that has been received into the Serial input buffer but not yet read by the sketch.
There is an example of its use on the Arduino Tutorials web site at www.arduino.cc/en/Tutorial/SerialEvent.
This is how the sketch’s ino file fits into the real world: the setup() function is called once, and the loop() gets called repeatedly until the Arduino runs out of power or is turned off.
A “never returning” loop() function
Bear in mind that if you do decide to create your own loop in the manner described earlier, you might cause problems with any serial communications that would have been processed by the call to serialEventRun() in the main() function.
Check the documentation on the Arduino Tutorials web site at www.arduino.cc/en/Tutorial/SerialEvent to be sure that you will not be causing yourself any worries.
2.8 Header File Arduino.h
As mentioned previously, when you compile a sketch in the Arduino IDE, there is a certain amount of reorganization taking place to convert your sketch into something resembling a proper C/C++ program. The file Arduino.h , which can be found in $ARDINC as can all the other Arduino-specific header files, is included at the top of the converted source code. It is in, or from, this file that much of the initialization of a sketch takes place.
Various standard C/C++ header files are included. I will not be discussing those here.
- A number of AVR-specific header files are included from the AVRLib sources, in $AVRINC. These are as follows:
avr/pgmspace.h
avr/io.h
avr/interrupt.h
A strange header file, binary.h, is included next.
All the Arduino-specific function headers are defined, for example, pinMode(), digitalWrite() and so on, along with a number of useful constants such as HIGH, LOW, INPUT, OUTPUT amongst others.
- If the compilation is using the C++ compiler (avr-g++) as opposed to the C compiler (avr-gcc), then
WCharacter.h is included.
WString.h is included.
HardwareSerial.h is included.
USBAPI.h is included.
If the compiler discovers that the microcontroller in use has both hardware serial and CDC serial, then it cannot continue with the compilation, so an error is displayed and the compilation ends.
Finally, the header file pins_arduino.h is included.
The relevant header files are described in the following sections, as are any other headers that they themselves include.
2.8.1 Header File avrpgmspace.h
This header file is included from $AVRINC, to allow pins_arduino.h, as described in the following, to create lookup tables within the program space – in flash memory, as opposed to in the scarce Static RAM (SRAM) on the device. It defines a number of typdefs and functions to copy data between the program space, in flash, and the variable space in RAM. It doesn’t make for very interesting reading I’m afraid.
2.8.2 Header File avrio.h
This file, from $AVRINC, sets up all the AVR-specific stuff for the appropriate AVR microcontroller that is in use on the Arduino board. The settings you chose in the IDE for Tools ➤ Boards will determine the specific device file that will be included. In the majority of cases, and for our purposes here, this will be the ATmega328P, and so this file simply causes the AVR definitions for that microcontroller to be read in from the file avr/iom328p.h.
This file also includes sfr_defs.h to set up numerous macros for memory address simplification, some functions to handle looping until a bit is clear (or set) and so on. These are not discussed here.
<avr/portpins.h>
<avr/common.h>
<avr/version.h>
<avr/xmega.h> (only if we are compiling for an XMega device, which we are not, so this header will not be discussed further)
<avr/fuse.h>
<avr/lock.h>
The AVRLib header files are to be found in $AVRINC, under your Arduino 1.8.5 installation, unless otherwise stated. These header files are not part of the Arduino IDE per se, but are supplied as part of the AVRLib, which the IDE uses.
2.8.2.1 Header File avr/iom328p.h
If you’ve ever looked at the data sheet for the ATmega328P, then you will notice that the various registers, and the bits thereof, have strange-sounding acronyms. This header file, from $AVRINC, is the one which creates all the constants so that you can refer to those acronyms in your code. In addition to these acronyms, various other constants are defined to manage RAM sizes, fuse bits, sleep modes, interrupt vectors and so on. This is another important header file, but it really doesn’t make for good bedtime reading.
If your Arduino board uses a different AVR microcontroller device, then a different iomxxx.h file will be included, rather than this one, so the definitions will be suitable for the board and/or microcontroller in use.
The exact file which will be included is defined by the IDE’s Tools ➤ Boards settings.
2.8.2.2 Header File avr/portpins.h
This header file, from $AVRINC, is a continuation of the device-specific avr/iom328p.h file and defines some additional constants which are common to all of the other devices. Some of the definitions in this file will not be relevant to all devices, but the code in this file does do some checks to see if a definition will be relevant, before defining it. It does this by checking for constants defined in the avr/iom328p.h header file and, if defined, sets up the additional constants.
Obviously, if the board in use is not based on the ATmega328P, then the reference to avr/iom328p.h in the preceding text would of course be to a different header file for the device actually in use on the board.
2.8.2.3 Header File <avr/common.h>
According to the comments in this header, This [sic] purpose of this header is to define registers that have not been previously defined in the individual device IO header files, and to define other symbols that are common across AVR device families.
I think that about covers it!
The file can be found in $AVRINC.
2.8.2.4 Header File <avr/version.h>
AVRLib constants
You could use these constants to check whether a specific version of the library is in use and, from that, determine if some function can be used or otherwise.
Once again, the file can be found in $AVRINC.
2.8.2.5 Header File <avr/fuse.h>
Fuses are programmable bits in 1, 2, or 3 bytes inside the AVR microcontroller. These are used to set various features of the hardware and are covered in some detail in Chapter 7, Section 7.1, “ATmega328P Fuses.” The data sheet for the appropriate AVR device has full details, and warnings!
This header file, from $AVRINC, sets up a structure type (__fuse_t) that corresponds to the fuse bits for the appropriate board.
2.8.2.6 Header File <avr/lock.h>
This header file, from $AVRINC, sets up lock bit details for the specific AVR microcontroller in use. This will not be discussed further here, so see the data sheet for full details.
2.8.3 Header File avrinterrupt.h
Because the init() function, as described in the following, sets up Timer 0 with an interrupt routine to keep track of the number of milliseconds (millis) that have passed since a sketch started, this header file is required. In essence, and among many checks, it creates the ISR() macro which allows you to create interrupt handlers when using AVR-specific C/C++ code. The Arduino Language uses a slightly different system, attachInterrupt(), for example, for external interrupts. Arduino interrupts will be described in Chapter 3, Section 3.5, “Interrupts.”
This file is also part of the AVRLib and is found in $AVRINC.
2.8.4 Header File binary.h
It looks strange, yes? The header is defining as many different binary style constants for every number between 0 and 255. Why does 0 get so many different constants while 255 only has one? This allows the programmer to specify zero in as many ways as there are leading zeros in the binary representation of the number zero. This applies to all the numbers, but once you reach 128, there are no more leading zeros, so those values only have a single constant defined.
Only one line of code? To do all that? Yes, one line of AVR code can correspond to numerous lines of Arduino code. This is another example of how the Arduino makes life easier for the beginner – which of the two preceding code sections is the easier to read and understand?
2.8.5 Header File WCharacter.h
This header file defines a number of inlined functions which can be used to determine if a character is numeric, alphanumeric, and so on. This is not specific to the Arduino software and will not be discussed further.
What are inlined functions?
Inlined functions get copied verbatim into the code where they are called. Normally, functions are set up in the executable once and called from many places. Inlining them improves runtime efficiency but at the expense of code size.
Avoiding inlined code to save space
Then call the isAlphaNum() function frequently from your own code, rather than calling the isAlphaNumeric() function frequently.
2.8.6 Header File WString.h
The WString.h header file defines a C++ class named String. I have to admit to not seeing any Arduino code that uses this class, but maybe I haven’t been reading enough code. As this isn’t specifically Arduino code, even though the class has been written for Arduino, it will not be discussed further.
Okay, I lied. String is used by the Serial class, but deep down. Serial inherits from Stream which inherits from Print, and Print uses String internally.
Using this class will seriously increase the size of your sketches and may result in very difficult to diagnose runtime errors if too much dynamic memory is allocated – which this class does internally.
2.8.7 Header File HardwareSerial.h
This is the header file that defines the Serial interface whereby your Arduino board can talk back to your main computer over the USB cable. There’s a lot going on in this file, and it makes interesting reading to see how the Arduino library works.
If your device has less than 1024 bytes of RAM, then two buffers of 16 characters each are created. One is for serial receiving and the other is for serial transmission. The buffer sizes are increased to 64 characters if you have more than 1024 bytes of RAM available. The ATmega328P has 2048 bytes, so the larger-sized buffers are created.
The buffers are set up as what is known as circular buffers. They have a pointer to the first free character for insertion into the buffer and a pointer to the next character to be removed from the buffer. Hopefully, never the twain shall meet, but if they do happen to meet, the sketch will suspend for a while until some data has been removed from the buffer allowing the new data to be inserted.
Circular buffers are described in some detail at https://en.wikipedia.org/wiki/Circular_buffer.
Also set up in this file are a couple of interrupt routines which are called automatically by the AVR microcontroller whenever there is an empty transmit or a full receive buffer for the built-in USART device. The Serial class will, in the case of a transmission, read the next character from the Arduino transmit buffer and write it into the hardware register as appropriate to have it transmitted out via the USART. A similar process takes place for the receive interrupt. The hardware buffers on the AVR microcontroller are a single byte in size.
In the case where the AVR device has more than one USART, the Mega 2560 series, for example, then these are also set up from other header files included by this one. These additional hardware serial devices are not discussed further as the default Arduino board using the ATmega328P only has a single USART and they are all similar.
2.8.8 Header File USBAPI.h
This is a header specifically for devices which have built-in hardware USB features – these being boards based around the ATmega32U4 microcontroller which is on board the Arduino Leonardo, Pro Micro, Micro, and a few other models. As the ATmega328P doesn’t have hardware USB on board, this file will not be discussed any further.
2.8.9 Header File pins_arduino.h
The version of pins_arduino.h that is included is dependent on the Arduino board in use and, thus, the AVR microcontroller in use on that specific board. For the default board, the file is located in $ARDINST/variants/standard, using an ATmega328P, while the Adafruit Gemma board has its pins_arduino.h in $ARDINST/variants/gemma and uses an ATtiny85 device. The included file sets up the pin assignments for the appropriate device.
The board is chosen by the developer using the IDE’s Tools ➤ Boards menu option.
It is this header file that defines constants for the analogue pins, A0 which has the value 14 through A7 which is defined as 21, for example.
Convert an AVR port name (Px) to the Data Direction Register for that port (DDRx). This is table port_to_mode_PGM.
Convert an AVR port name (Px) to the output port register for that port (PORTx). This is table port_to_output_PGM.
Convert an AVR port name (Px) to the input port register for that port (PINx). This is table port_to_input_PGM.
Convert a digital pin number (0–13) to the AVR port (PORTB, PORTC, or PORTD). This is table digital_pin_to_port_PGM.
Convert a digital pin number (0–13) to the specific pin number (or bit number) on the AVR port (PORTB, PORTC, or PORTD). This is table digital_pin_to_bit_mask_PGM. The entry stored in this table is a bitmask with only 1 bit set, the bit that corresponds to the pin number.
Convert a digital pin number (0–13) to one of the timer outputs on the device (6 on the standard Arduino board). This is table digital_pin_to_timer_PGM and is used in the analogueWrite() function for pulse width modulation (PWM).
2.9 The init() Function
This function is located within the file $ARDINC/wiring.c.
Enabling the global interrupt flag
Configuring Timer/counter 0 to provide PWM on pins D5 and D6 and initiating the millis() counter facility by setting up the Timer/counter 0 Overflow interrupt handler
Configuring Timer/counter 1 to provide PWM on pins D9 and D10
Configuring Timer/counter 2 to provide PWM on pins D3 and D11
Initializing the Analogue to Digital Converter
Disabling the USART from pins D0 and D1
2.9.1 Enabling the Global Interrupt Flag
The function init() begins by enabling interrupts globally as shown in Listing 2-5. Arduino boards require interrupts to be enabled so that the millis() function can begin counting, once the appropriate timer (Timer/counter 0) is configured and started.
Setting interrupts on
① Turns on global interrupts. This is required to make functions such as millis() and micros() work.
The code continues to enable Timer/counter 0 next.
2.9.2 Enabling Timer/counter 0
The prescaler for Timer/counter 0 is set to divide the system clock (16 MHz) by 64 so that every 64 ticks of the system clock, the timer/counter’s own clock will tick once and increment the counter value by 1. As this is an 8-bit timer, it can only count from 0 to 255 and then roll over, or overflow, to 0 again and so on. The overflow will occur every 256 timer/counter clock ticks which equates to 64 * 256 system clock ticks.
1/ (CPU Frequency / prescaler) * Timer ticks until overflow= 1/(F_CPU / 64) * 256= 1/(16000000 / 64) * 256= 1/250000 * 256= 4 microseconds * 256= 1024 microseconds= 1 millisecond plus 24 microseconds.The interrupt takes account of those extra 24 microseconds and will adjust the millis() result to account for them whenever they accumulate enough to add an extra millisecond to the timer.
The interrupt on Timer/counter 0 overflow is set up and enabled. The interrupt will fire every time the timer/counter’s value overflows from 255 to 0. The Timer/counter 0 Overflow interrupt will update the millis counter once every 256 timer clock ticks (256 Timer/counter 0 clock ticks.) This is calculated as
Timer/counter 0 is also used to provide 8-bit PWM (pulse width modulation for analogueWrite() ) on pins D5 and D6.
Timer/counter 0 configuration
① Setting these 2 bits in the TCCR0A register ensures that the PWM waveform generator is running in Fast Hardware PWM mode, instead of Phase Correct PWM mode, which would interfere with the timer for the millis() function.
② Setting these 2 bits in register TCCR0B sets the timer clock to be the system clock divided by 64.
That equates to 16 MHz for the system clock, divided down to 250 MHz, or one tick of the timer clock for every 64 ticks of the system clock.
③ Setting this bit in the TIMSK0 register enables the Timer 0 Overflow interrupt. Now every time the timer goes from 255 to 0, the interrupt routine will be called to accumulate counts for millis() and micros().
Listing 2-7 shows the source for the Timer 0 Overflow interrupt routine, which is separate from the code in the init() function. The remainder of the init() function walk-through follows later.
2.9.3 Timer/counter 0 Overflow Interrupt
The Timer 0 Overflow interrupt is used to update the millis() count. It does this every 1.024 milliseconds, and, as this is slightly over 1 millisecond, it accumulates these extra fractions; and when there are enough accumulated, the millis count gets incremented by an extra leap millisecond. This takes place roughly every 42 interrupt handler executions – it’s actually every 41.666 (recurring) executions, but you cannot have a fraction of an execution!
Timer/counter 0 Overflow interrupt handler
① The current values of the variables timer0_millis and timer0_fract are copied locally from memory (Static RAM) so that they can be used in registers for faster processing.
② The current timer0_millis count, in m, is incremented by MILLIS_INC. The current accumulated fractions of a millisecond, timer0_fract used locally in variable f, are incremented by FRACT_INC. If f is then larger than FRACT_MAX, then an extra “leap” millisecond is accumulated and the counts adjusted accordingly.
③ The new values are copied back to the original two variables.
④ A counter, timer0_overflow_count, keeps track of the number of times the ISR has been fired. This counter is used in the millis() function, and that is itself used in the delay() function.
“What are MILLIS_INC, FRACT_INC, and FRACT_MAX?”, I hear you ask.
Variables used in counting millis
So, working backward, we see that the number of clockCyclesPerMicrosecond is 16e6/1e6 or 16. From that, we can then see that MICROSECONDS_PER_TIMER0_OVERFLOW is (64 * 256)/16, which gives us 1024.
This then allows MILLIS_INC to be calculated as 1024/1000 which is a solitary one, as this is integer division, not floating point. And, finally, FRACT_INC is 1024 – (1024/16) >> 3 or 24/8 which gives us 3.
FRACT_MAX is easy; it’s effectively 1000/8 or 125.
So, every 256 Timer/counter 0 clock ticks, we increment the number of millis by 1 and add 3 to the fractions accumulator, and if that is more than 125, we add an extra 1 to millis and reduce the fractions accumulator by 125, thus holding on to any spare fractions. Eventually, these will add up and generate another millisecond.
If you are wondering why we add 3 and check for 125, then consider that there are 24 microseconds spare each time through the interrupt handler – that’s 41.666 (recurring) to gain an extra millisecond. 125/3 is exactly the same value – 41.666 (recurring) – so it works out the same.
Is adding 3 and checking against 125 more efficient than adding 24 and checking against 1000? Yes, indeed, the former method fits a byte, while the latter requires a 16-bit value, and the ATmega328P is an 8-bit device without a 16-bit compare instruction.
2.9.4 Configuring Timer/counter 1 and Timer/counter 2
On the ATmega328P, Timer/counter 1 is a 16-bit timer; however, the Arduino system sets it up so that it appears as an 8-bit timer which makes it similar to Timer/counter 0 and Timer/counter 2.
Timer/counters 1 and 2 are used to provide PWM on four of the six pins that are PWM enabled on an ATmega328P.
Both timers have their prescaler set to divide the system clock by 64 and are set up in 8-bit Phase Correct PWM mode.
Timer/counter 1 provides PWM on pins D9 and D10, while Timer/counter 2 provides PWM on pins D3 and D11.
Timer/counter 1 configuration
① This shouldn’t be necessary as init() is called at the start of a sketch, after a reset, or on power on, so the default for register TCCR1B is zero anyway. However, sometimes it’s best to be explicit.
② Setting only the CS11 bit sets the timer’s prescaler to divide by 8, which is fine for slow system clock speeds. This would give a standard Arduino board a 2 MHz timer clock speed – a tad excessive perhaps!
③ For faster clock speeds, setting CS10, plus the preceding CS11, finally sets the prescaler to divide by 64, giving the required 250 KHz timer clock speed.
④ The WGM10 bit, in the TCCR1A register, sets the PWM waveform generator to run in 8-bit Phase Correct PWM mode.
Timer/counter 2 configuration
① Setting bit CS22 in register TCCR2B sets the timer’s prescaler to divide the system clock by 64. This results in a 250 KHz timer clock.
② Setting the WGM20 bit, in the TCCR2A register, sets the PWM waveform generator to run in 8-bit Phase Correct PWM mode.
The function continues, now that all three timers are configured, to set up the Analogue to Digital Converter (ADC) so that the Arduino analogRead() function will work.
2.9.5 Initializing the Analogue to Digital Converter
According to the data sheet for the ATmega328P, the ADC (Analogue to Digital Converter) runs best, and most accurately, when it is running at a speed between 50 and 200 KHz. The system clock on the microcontroller is running at 16 MHz, so is a little on the speedy side.
In order to get the ADC into a valid speed range, it has its prescaler set to divide the system clock by 128. This puts the speed at 125 KHz, which is within the desired range specified by the data sheet.
ADC configuration
① The system clock needs to be divided down to obtain an ADC clock speed in the range 50–200 KHz which, according to the data sheet, is the optimal clock range for the ADC. For the standard Arduino boards, this requires a divisor of 128 to get the 16 MHz system clock into this range. The resulting ADC clock speed is 125 KHz, which is well within the requirement.
② Setting the ADEN bit in the ADCSRA register ensures that the ADC is enabled. It will not start converting until it is told to do so by analogRead().
The preceding code implies that even if you don’t want the ADC in your sketches, it is active and consuming additional power that might be better used keeping your batteries from running down!
This uses the AVRLib facility to turn off the power to the ADC clock and, in my opinion, is a lot more readable, and understandable, than the preceding one.
2.9.6 Disabling the USART
The final task for the init() function is to disable the Universal Synchronous/Asynchronous Receiver/Transmitter or USART for short.
This is left attached to Arduino pins D0 and D1 by the bootloader, and the two pins used need to be disconnected so that they can be reused for digitalRead() and/or digitalWrite() in sketches. On the ATmega328P, these digital pins are the physical pins 2 and 3.
USART configuration
This concludes the initialization that occurs at the start of every sketch and the walk-through of the init() function’s source code.