The Arduino platform simplifies programming by providing easy-to-use function calls to hide complex, low-level hardware functions. But some applications need to bypass the friendly access functions to get directly at hardware, either because that’s the only way to get the needed functionality or because higher performance is required. This chapter shows how to access and use hardware functions that are not fully exposed through the Arduino programming environment.
Changing register values can change the behavior of some Arduino functions (e.g., millis
). The low-level capabilities described in this chapter require care, attention, and testing if you want your code to function correctly.
There are four key hardware features that you need to understand before getting deeper into the hardware. Registers are memory locations in the microcontroller that can be used to alter its behavior. When you work with the other hardware features, you will often be manipulating registers to configure them. Interrupts are signals generated by the microcontroller, generally in response to an external event, allowing Arduino sketches to respond immediately when something happens. Timers can generate a signal after a predetermined delay, or repeatedly generate a signal based on a duration you specify. As with interrupts, you can take an action in your sketch in response to a timer. You’ve already seen how to work with analog and digital pins, but the recipes in this chapter will show you how to work with them at much faster speeds.
Registers are variables that refer to hardware memory locations. They are used by the chip to configure hardware functions or for storing the results of hardware operations. The contents of registers can be read and written by your sketch. Changing register values will change the way the hardware operates, or the state of something (such as the output of a pin). Some registers represent a numerical value (the number a timer will count to). Registers can control or report on hardware status; for example, the state of a pin or if an interrupt has occurred. Registers are referenced in code using their names—these are documented in the datasheet for the microcontrollers. Setting a register to a wrong value usually results in a sketch functioning incorrectly, so carefully check the documentation to ensure that you are using registers correctly.
Interrupts are signals that enable the controller chip to stop the normal flow of a sketch and handle a task that requires immediate attention before continuing with what it was doing. Arduino core software uses interrupts to handle incoming data from the serial port, to maintain the time for the delay
and millis
functions, and to trigger the attach
Interrupt
function. Libraries, such as Wire and Servo, use interrupts when an event occurs, so the code doesn’t have to constantly check to see if the event has happened. This constant checking, called polling, can complicate the logic of your sketch. Interrupts can be a reliable way to detect signals of very short duration. Recipe 18.2 explains how to use interrupts to determine if a digital pin has changed state.
Two or more interrupts may occur before the handling of the first interrupt is completed; for example, if two switches are pressed at the same time and each generates a different interrupt. The interrupt handler for the first switch must be completed before the second interrupt can get started. Interrupts should be brief, because an interrupt routine that takes too much time can cause other interrupt handlers to be delayed or to miss events.
Arduino services one interrupt at a time. It suspends pending interrupts while it deals with an interrupt that has happened. Code to handle interrupts (called the interrupt handler, or interrupt service routine) should be brief to prevent undue delays to pending interrupts. An interrupt routine that takes too much time can cause other interrupt handlers to miss events. Activities that take a relatively long time, such as blinking an LED or even serial printing, should be avoided in an interrupt handler. If you need to perform such an activity, you can set a global flag in the interrupt handler, and use the flag to tell your code to perform the action in the main loop.
The Arduino Uno (and compatible boards based on the ATmega328) has three hardware timers for managing time-based tasks (the Mega has six). The timers are used in a number of Arduino functions:
Used for millis
and delay
; also analogWrite
on pins 5 and 6
analogWrite
functions on pins 9 and 10; also driving servos using the Servo library
analogWrite
functions on pins 3 and 11
The Servo library uses the same timer as analogWrite
on pins 9 and 10, so you can’t use analogWrite
with these pins when using the Servo library.
The Mega has three additional 16-bit timers and uses different pin numbers with analogWrite
:
analogWrite
functions on pins 4 and 13
analogWrite
functions on pins 11 and 12
analogWrite
functions on pins 9 and 10
analogWrite
functions on pins 2, 3, and 5
analogWrite
functions on pins 6, 7, and 8
analogWrite
functions on pins 45 and 46
The megaAVR-based boards such as the Arduino WiFi Rev2 and the Arduino Nano Every use a different timer system than the ATmega-based boards like the Uno and Mega. This application note from Microchip explains how timers are implemented on the megaAVR. ARM-based Arduino boards use an entirely different approach to handling timers based on two facilities: Timer Counter (TC) and Timer Counter for Control Applications (TCC). The following application notes cover each of those for the SAMD chips: Timer Counter (TC) Driver and Timer Counter for Control Applications (TCC) Driver. Although this library is not intended (or supported) for use by anyone outside Adafruit, they have published their Adafruit_ZeroTimer library on GitHub, which includes wrapper code for Timer Control modules 3, 4, and 5 on both the SAMD21 (ARM Cortex-M0 core) and SAMD51 (Arm Cortex-M4 core) platforms.
Timers are counters that count pulses from a time source, called a timebase. The timer hardware consists of 8-bit or 16-bit digital counters that can be programmed to determine the mode the timer uses to count. The most common mode is to count pulses from the timebase on the Arduino board, usually 16 MHz derived from a crystal; 16 MHz pulses repeat every 62.5 nanoseconds, and this is too fast for many timing applications, so the timebase rate is reduced by a divider called a prescaler. Dividing the timebase by 8, for example, increases the duration of each count to half a microsecond. For applications in which this is still too fast, other prescale values can be used (see Table 18-1).
Timer operation is controlled by values held in registers that can be read and written by Arduino code. The values in these registers set the timer frequency (the number of system timebase pulses between each count) and the method of counting (up, down, up and down, or using an external signal).
Here is an overview of the timer registers (n is the timer number):
Determines the operating mode
Determines the prescale value
Contains the timer count
Interrupt can be triggered on this count
Interrupt can be triggered on this count
Sets the conditions for triggering an interrupt
Indicates if the trigger condition has occurred
Table 18-1 is an overview of the bit values used to set the timer precision. Details of the functions of the registers are explained in the recipes where they are used.
Prescale factor | CSx2, CSx1, CSx0 | Precision | Time to overflow | |
---|---|---|---|---|
8-bit timer | 16-bit timer | |||
1 |
B001 |
62.5 ns |
16 µs |
4.096 ms |
8 |
B010 |
500 ns |
128 µs |
32.768 ms |
64 |
B011 |
4 µs |
1,024 µs |
262.144 ms |
256 |
B100 |
16 µs |
4,096 µs |
1048.576 ms |
1,024 |
B101 |
64 µs |
16,384 µs |
4194.304 ms |
B110 |
External clock, falling edge |
|||
B111 |
External clock, rising edge |
All timers are initialized for a prescale of 64.
Precision in nanoseconds is equal to the CPU period (time for one CPU cycle) multiplied by the prescale.
Chapter 5 described the standard Arduino functions to read and write (to/from) digital and analog pins. This chapter explains how you can control pins faster than using the Arduino read and write functions and make changes to analog methods to improve performance.
Some of the code in this chapter is more difficult to understand than the other recipes in this book, as it is moving beyond Arduino syntax and closer to the underlying hardware. These recipes work directly with the tersely named registers in the chip and use bit shifting and masking to manipulate bits in them. The benefit from this complexity is enhanced performance and functionality.
Use the EEPROM library to read and write values in EEPROM memory. This sketch blinks an LED using values read from EEPROM and allows the values to be changed using the Serial Monitor:
/*
* EEPROM sketch based on Blink without Delay
* uses EEPROM to store blink values
*/
#include <EEPROM.h>
// these values are saved in EEPROM
const
byte
EEPROM
_ID
=
0x99
;
// used to identify if valid data in EEPROM
byte
ledPin
=
LED_BUILTIN
;
// the LED pin
int
interval
=
1000
;
// interval at which to blink (milliseconds)
// variables that do not need to be saved
int
ledState
=
LOW
;
// ledState used to set the LED
long
previousMillis
=
0
;
// will store last time LED was updated
//constants used to identify EEPROM addresses
const
int
ID_ADDR
=
0
;
// the EEPROM address used to store the ID
const
int
PIN_ADDR
=
1
;
// the EEPROM address used to store the pin
const
int
INTERVAL_ADDR
=
2
;
// the EEPROM address used to store the interval
void
setup
()
{
Serial
.
begin
(
9600
);
byte
id
=
EEPROM
.
read
(
ID_ADDR
);
// read the first byte from the EEPROM
if
(
id
==
EEPROM
_ID
)
{
// here if the id value read matches the value saved when writing eeprom
Serial
.
println
(
"Using data from EEPROM"
);
ledPin
=
EEPROM
.
read
(
PIN_ADDR
);
byte
hiByte
=
EEPROM
.
read
(
INTERVAL_ADDR
);
byte
lowByte
=
EEPROM
.
read
(
INTERVAL_ADDR
+
1
);
interval
=
word
(
hiByte
,
lowByte
);
// see word function in Recipe 3.15
}
else
{
// here if the ID is not found, so write the default data
Serial
.
println
(
"Writing default data to EEPROM"
);
EEPROM
.
write
(
ID_ADDR
,
EEPROM
_ID
);
// write the ID to indicate valid data
EEPROM
.
write
(
PIN_ADDR
,
ledPin
);
// save the pin in eeprom
byte
hiByte
=
highByte
(
interval
);
byte
loByte
=
lowByte
(
interval
);
EEPROM
.
write
(
INTERVAL_ADDR
,
hiByte
);
EEPROM
.
write
(
INTERVAL_ADDR
+
1
,
loByte
);
}
Serial
.
(
"Setting pin to "
);
Serial
.
println
(
ledPin
,
DEC
);
Serial
.
(
"Setting interval to "
);
Serial
.
println
(
interval
);
pinMode
(
ledPin
,
OUTPUT
);
}
void
loop
()
{
// this is the same code as the BlinkWithoutDelay example sketch
if
(
millis
()
-
previousMillis
>
interval
)
{
previousMillis
=
millis
();
// save the last time you blinked the LED
// if the LED is off turn it on and vice versa:
if
(
ledState
==
LOW
)
ledState
=
HIGH
;
else
ledState
=
LOW
;
digitalWrite
(
ledPin
,
ledState
);
// set LED using value of ledState
}
processSerial
();
}
// function to get duration or pin values from Serial Monitor
// value followed by i is interval, p is pin number
int
value
=
0
;
void
processSerial
()
{
if
(
Serial
.
available
())
{
char
ch
=
Serial
.
read
();
if
(
ch
>=
'0'
&&
ch
<=
'9'
)
// is this an ascii digit between 0 and 9?
{
value
=
(
value
*
10
)
+
(
ch
-
'0'
);
// yes, accumulate the value
}
else
if
(
ch
==
'i'
)
// is this the interval
{
interval
=
value
;
Serial
.
(
"Setting interval to "
);
Serial
.
println
(
interval
);
byte
hiByte
=
highByte
(
interval
);
byte
loByte
=
lowByte
(
interval
);
EEPROM
.
write
(
INTERVAL_ADDR
,
hiByte
);
EEPROM
.
write
(
INTERVAL_ADDR
+
1
,
loByte
);
value
=
0
;
// reset to 0 ready for the next sequence of digits
}
else
if
(
ch
==
'p'
)
// is this the pin number
{
ledPin
=
value
;
Serial
.
(
"Setting pin to "
);
Serial
.
println
(
ledPin
,
DEC
);
pinMode
(
ledPin
,
OUTPUT
);
EEPROM
.
write
(
PIN_ADDR
,
ledPin
);
// save the pin in eeprom
value
=
0
;
// reset to 0 ready for the next sequence of digits
}
}
}
Open the Serial Monitor. As the sketch starts, it tells you whether it is using values previously saved to EEPROM or defaults, if this is the first time the sketch is started.
You can change values by typing a number followed by a letter to indicate the action. A number followed by the letter i changes the blink interval; a number followed by a p changes the pin number for the LED.
Arduino contains EEPROM memory that will store values even when power is switched off. There are 1,024 bytes of EEPROM on the Arduino Uno and 4K bytes in a Mega. The Arduino Uno WiFi R2 and the Nano Every have only 256 bytes of EEPROM memory. Most ARM-based boards do not have EEPROM memory.
The sketch uses the EEPROM library to read and write values in EEPROM memory. Once the library is included in the sketch, an EEPROM object is available that accesses the memory. The library provides methods to read
, write
, and clear
. EEPROM.clear()
is not used in this sketch because it erases all the EEPROM memory.
The EEPROM library requires you to specify the address in memory that you want to read or write. This means you need to keep track of where each value is written so that when you access the value it is from the correct address. To write a value, you use EEPROM.write(address, value)
. The address is from 0 to 1,023 (on the Uno), and the value is a single byte. To read, you use EEPROM.read(address)
. The byte content of that memory address is returned.
The sketch stores three values in EEPROM. The first value stored is an ID
value that is used only in setup
to identify if the EEPROM has been previously written with valid data. If the value stored matches the expected value, the other variables are read from EEPROM and used in the sketch. If it doesn’t match, this sketch has not been run on this board (otherwise, the ID
would have been written), so the default values are written, including the ID
value.
The sketch monitors the serial port, and new values received are written to EEPROM. The sketch stores the ID
value in EEPROM address 0, the pin number in address 1, and the two bytes for the interval start in address 2. The following line writes the pin number to EEPROM. The variable ledPin
is a byte, so it fits into a single EEPROM address:
EEPROM
.
write
(
PIN_ADDR
,
ledPin
);
// save the pin in eeprom
Because interval is an int
, it requires two bytes of memory to store the value:
byte
hiByte
=
highByte
(
interval
);
byte
loByte
=
lowByte
(
interval
);
EEPROM
.
write
(
INTERVAL_ADDR
,
hiByte
);
EEPROM
.
write
(
INTERVAL_ADDR
+
1
,
loByte
);
The preceding code splits the value into two bytes that are stored in two consecutive addresses. Any additional variables to be added to EEPROM would need to be placed in addresses that follow these two bytes.
Here is the code used to rebuild the int
variable from EEPROM:
ledPin
=
EEPROM
.
read
(
PIN_ADDR
);
byte
hiByte
=
EEPROM
.
read
(
INTERVAL_ADDR
);
byte
lowByte
=
EEPROM
.
read
(
INTERVAL_ADDR
+
1
);
interval
=
word
(
hiByte
,
lowByte
);
See Chapter 3 for more on using the word
expression to create an integer from two bytes.
For more complicated use of EEPROM, it is advisable to draw out a map of what is being saved where, to ensure that no address is used by more than one value, and that multibyte values don’t overwrite other information.
Recipe 3.14 provides more information on converting 16- and 32-bit values into bytes.
This sketch monitors pulses on pin 2 and stores the duration in an array. When the array has been filled (32 pulses have been received), the duration of each pulse is displayed on the Serial Monitor:
If you are using a board from the Arduino MKR family, change both the wiring and the code to use digital pin 4.
/* * Interrupts sketch * see
Recipe 10.1for connection diagram */
const
int
pin
=
2
;
// pin the receiver is connected to
const
int
numberOfEntries
=
32
;
// set this number to any convenient value
volatile
unsigned
long
microseconds
;
volatile
byte
idx
=
0
;
volatile
unsigned
long
results
[
numberOfEntries
]
;
void
setup
(
)
{
pinMode
(
pin
,
INPUT_PULLUP
)
;
Serial
.
begin
(
9600
)
;
// Use the pin's interrupt to monitor for changes
attachInterrupt
(
digitalPinToInterrupt
(
pin
)
,
analyze
,
CHANGE
)
;
results
[
0
]
=
0
;
}
void
loop
(
)
{
if
(
idx
>
=
numberOfEntries
)
{
Serial
.
println
(
"
Durations in Microseconds are:
"
)
;
for
(
byte
i
=
0
;
i
<
numberOfEntries
;
i
+
+
)
{
Serial
.
(
i
)
;
Serial
.
(
"
:
"
)
;
Serial
.
println
(
results
[
i
]
)
;
}
idx
=
0
;
// start analyzing again
}
delay
(
1000
)
;
}
void
analyze
(
)
{
if
(
idx
<
numberOfEntries
)
{
if
(
idx
>
0
)
{
results
[
idx
]
=
micros
(
)
-
microseconds
;
}
idx
=
idx
+
1
;
}
microseconds
=
micros
(
)
;
}
If you have an infrared receiver module, you can use the wiring in Recipe 10.1 to measure the pulse width from an infrared remote control. You could also use the wiring in Recipe 6.12 to measure pulses from a rotary encoder or connect a switch to pin 2 (see Recipe 5.1) to test with a pushbutton.
In setup
, the attachInterrupt(digitalPinToInterrupt(pin), analyze, CHANGE);
call enables the sketch to handle interrupts. The first argument in the call specifies which interrupt to initialize. The actual interrupt for a given pin varies from board to board, so you should use the digitalPinToInterrupt
function to determine it, rather than hardcoding the interrupt value in your sketch.
The next parameter specifies what function to call (sometimes called an interrupt handler) when the interrupt event happens; analyze
in this sketch.
The final parameter specifies what should trigger the interrupt. CHANGE
means whenever the pin level changes (goes from low to high, or high to low). The other options are:
HIGH
When the pin is high (Due, Zero, and MKR ARM boards only)
LOW
When the pin is low
RISING
When the pin goes from low to high
FALLING
When the pin goes from high to low
When reading code that uses interrupts, bear in mind that it may not be obvious when values in the sketch change because the sketch does not directly call the interrupt handler; it’s called when the interrupt conditions occur.
In this sketch, the main loop
checks the index
variable to see if all the entries have been set by the interrupt handler. Nothing in loop
changes the value of index
. index
is changed inside the analyze
function when the interrupt condition occurs (pin 2 changing state). The index
value is used to store the time since the last state change into the next slot in the results
array. The time is calculated by subtracting the last time the state changed from the current time in microseconds. The current time is then saved as the last time a change happened. (Chapter 12 describes this method for obtaining elapsed time using the millis
function; here micros
is used to get elapsed microseconds instead of milliseconds.)
The variables that are changed in an interrupt function are declared as volatile
; this lets the compiler know that the values could change at any time (by an interrupt handler). Without using the volatile
keyword, the compiler may store the values in registers that can be accidentally overwritten by an interrupt handler. To prevent this, the volatile
keyword tells the compiler to store the values in RAM rather than registers.
Each time an interrupt is triggered, index
is incremented and the current time is saved. The time difference is calculated and saved in the array (except for the first time the interrupt is triggered, when index
is 0
). When the maximum number of entries has occurred, the inner block in loop
runs, and it prints out all the values to the serial port. The code stays in the while
loop at the end of the inner block, so you need to reset the board when you want to do another run.
Recipe 6.12 has an example of external interrupts used to detect movement in a rotary encoder.
The easiest way to use a timer is through a library. The uTimerLib library can generate a pulse with a regular period. You can install it with the Library Manager. This sketch flashes the built-in LED at a rate that can be set using the Serial Monitor:
/*
* Timer pulse with uTimerLib
* pulse the onboard LED at a rate set from serial input
*/
#include "uTimerLib.h"
const
int
pulsePin
=
LED_BUILTIN
;
int
period
=
100
;
// period in milliseconds
volatile
bool
output
=
HIGH
;
// the state of the pulse pin
void
setup
()
{
pinMode
(
pulsePin
,
OUTPUT
);
Serial
.
begin
(
9600
);
TimerLib
.
setInterval_us
(
flash
,
period
/
2
*
1000L
);
}
void
loop
()
{
if
(
Serial
.
available
())
{
int
period
=
Serial
.
parseInt
();
if
(
period
)
{
Serial
.
(
"Setting period to "
);
Serial
.
println
(
period
);
TimerLib
.
setInterval_us
(
flash
,
period
/
2
*
1000L
);
}
}
}
void
flash
()
{
digitalWrite
(
pulsePin
,
output
);
output
=
!
output
;
// invert the output
}
Enter digits for the desired period in milliseconds using the Serial Monitor and press Return/Enter to send it to the sketch. The sketch uses parseInt
to read the digits and divides the received value by 2 to calculate the duration of the on and off states (the period is the sum of the on time and off time, so the smallest value you can use is 2). Bear in mind that an LED flashing very quickly may not appear to be flashing to the human eye. Because TimerLib.setInterval_us
specifies an interval in microseconds, we multiply the period (specified in milliseconds) by 1,000.
On ATmega-based boards such as the Uno, this library uses Timer2, so it will interfere with the operation of analogWrite
on pins 3 and 11 (pins 9 and 10 for the Arduino Mega). At the time of this writing, uTimerLib does not work with the Arduino Uno WiFi Rev 2 or the Arduino Nano Every, which are based on a Mega AVR chip that uses a different timer architecture. It does support several 32-bit platforms, including SAM (Arduino Due), SAMD21 (Zero and M0 boards from Adafruit and SparkFun), ESP8266, and more.
This library enables you to use a timer by providing the timing interval and the name of the function to call when the interval has elapsed:
TimerLib
.
setInterval_us
(
flash
,
period
/
2
*
1000L
);
This sets up the timer. The first parameter is the function to call when the timer gets to the end of that time (the function is named flash
in this recipe). The second parameter is the time for the timer to run in microseconds.
As in Recipe 18.2, the sketch code does not directly call the function to perform the action. The LED is turned on and off in the flash
function that is called by the timer each time it gets to the end of its time setting. The code in loop
deals with any serial messages and changes the timer settings based on it.
Using a library to control timers is much easier than accessing the registers directly. Here is an overview of the inner workings of this library on ATmega: Timers work by constantly counting to a value, signaling that they have reached the value, then starting again. Each timer has a prescaler that determines the counting frequency. The prescaler divides the system timebase by a factor such as 1, 8, 64, 256, or 1,024. The lower the prescale factor, the higher the counting frequency and the quicker the timebase reaches its maximum count. The combination of how fast to count, and what value to count to, gives the time for the timer. Timer2 is an 8-bit timer; this means it can count up to 255 before starting again from 0. (Timer1 and Timers 3, 4, and 5 on the Mega use 16 bits and can count up to 65,535.)
On AVR, for intervals over 4,095 microseconds, the library uses a prescale factor of 64. On a 16 MHz Arduino board, each CPU cycle is 62.5 nanoseconds long, and when this is divided by the prescale factor of 64, each count of the timer will be 4,000 nanoseconds (62.5 * 64 = 4,000, which is four microseconds).
This sketch generates pulses within the frequency range of 1 MHz to 1 Hz using Timer1 PWM on pin 9. The library it uses supports only Arduino Uno, Mega, Leonardo, and a number of Teensy boards. See this Arduino page to determine which pins are available for use with this library on your board.
/*
* Pulse width duration sketch
* Set period and width of pulses
*/
#include <TimerOne.h>
const
int
outPin
=
9
;
// pin; use 3-4 on Teensy 3.x, 11-13 on Arduino Mega
long
period
=
40
;
// 40-microsecond period (25 KHz)
long
width
=
20
;
// 20-microsecond width (50% duty cycle)
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
outPin
,
OUTPUT
);
Timer1
.
initialize
(
period
);
// initialize timer1, 10000 microseconds
int
dutyCycle
=
map
(
width
,
0
,
period
,
0
,
1023
);
Timer1
.
pwm
(
outPin
,
dutyCycle
);
// PWM on output pin
}
void
loop
()
{
}
You set the pulse period to a value from 1 to 1 million microseconds by setting the value of the period at the top of the sketch. You can set the pulse width to any value in microseconds that is less than the period by setting the value of width
.
The sketch uses the Timer1 library.
Timer1 is a 16-bit timer (it counts from 0 to 65,535). On the Arduino Uno, it’s the same timer used by analogWrite
to control pins 9 and 10 (so you can’t use this library and analogWrite
on those pins at the same time). The sketch generates a pulse on pin 9 with a period and pulse width given by the values of the variables named period
and pulseWidth
.
OCR1A
and OCR1B
are constants that are defined in the code included by the Arduino core software (OCR stands for Output Compare Register).
On AVR, the Timer1 library uses a variety of registers. Many different hardware registers in the Arduino hardware are not usually needed by a sketch (the friendly Arduino commands hide the actual register names). But when you need to access the hardware directly to get at functionality not provided by Arduino commands, these registers need to be accessed. Full details on the registers are in the Microchip datasheet for the chip. On ARM-based Teensy boards, the high-level concepts are more or less the same, but ARM uses an entirely different scheme for generating pulses.
ICR1
(Input Compare Register for Timer1) determines the period of the pulse. This register contains a 16-bit value that is used as the maximum count for the timer. When the timer count reaches this value it will be reset and start counting again from 0. In the sketch in this recipe’s Solution, if each count takes 1 microsecond and the ICR1
value is set to 1000
, the duration of each count cycle is 1,000 microseconds.
OCR1A
(or OCR1B
depending on which pin you want to use) is the Output Compare Register for Timer1. When the timer count reaches this value (and the timer is in PWM mode as it is here), the output pin will be set low—this determines the pulse width. For example, if each count takes one microsecond and the ICR1
value is set to 1000
and OCR1A
is set to 100
, the output pin will be HIGH
for 100 microseconds and LOW
for 900 microseconds (the total period is 1,000 microseconds).
The duration of each count is determined by the Arduino controller timebase frequency (typically 16 MHz) and the prescale value. The prescale is the value that the timebase is divided by. For example, with a prescale of 64, the timebase will be four microseconds.
You can initialize the Timer1 library with a period (Timer1.initialize(period);
). It is over this period that the pulse width is expressed. You set the pulse width (indirectly) by using the Timer.pwm
function to set the duty cycle as a value between 0 and 1,023, which is exactly how you work with analogWrite
(see Recipe 7.2). The difference here is that you’re able to use Timer1 to control the period. So, if you want 20-microsecond pulses spaced evenly apart, a 40-microsecond period with a 50% duty cycle will provide this. The sketch allows you to specify the period and pulse width, and it uses the map
function to calculate the value between 0 and 1,023 to pass to Timer1.pwm()
. A 40-microsecond period is equivalent to 25 kHz (1,000,000/40).
See the “See Also” for links to datasheets and other references for timers.
This is an enhanced version of Recipe 18.4 that enables the frequency, period, pulse width, and duty cycle to be set from the serial port:
/*
* Configurable Pulse Generator sketch
*/
#include <TimerOne.h>
const
char
SET_PERIOD_HEADER
=
'p'
;
const
char
SET_FREQUENCY_HEADER
=
'f'
;
const
char
SET_PULSE_WIDTH_HEADER
=
'w'
;
const
char
SET_DUTY_CYCLE_HEADER
=
'c'
;
const
int
outPin
=
9
;
// pin; use 3-4 on Teensy 3.x, 11-13 on Arduino Mega
long
period
=
40
;
// 40-microsecond period (25 KHz)
int
duty
=
512
;
// duty as a range from 0 to 1023, 512 is 50% duty cycle
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
outPin
,
OUTPUT
);
Timer1
.
initialize
(
period
);
// initialize timer1, 1000 microseconds
Timer1
.
pwm
(
outPin
,
duty
);
// PWM on output pin
}
void
loop
()
{
processSerial
();
}
void
processSerial
()
{
static
long
val
=
0
;
if
(
Serial
.
available
())
{
val
=
Serial
.
parseInt
();
// Find the first number
if
(
val
)
{
char
ch
=
Serial
.
read
();
switch
(
ch
)
{
case
SET_PERIOD_HEADER
:
period
=
val
;
Serial
.
(
"Setting period to "
);
Serial
.
println
(
period
);
Timer1
.
setPeriod
(
period
);
Timer1
.
pwm
(
outPin
,
duty
);
show
();
break
;
case
SET_FREQUENCY_HEADER
:
if
(
val
>
0
)
{
Serial
.
(
"Setting frequency to "
);
Serial
.
println
(
val
);
period
=
1000000
/
val
;
Timer1
.
setPeriod
(
period
);
Timer1
.
pwm
(
outPin
,
duty
);
}
show
();
break
;
case
SET_PULSE_WIDTH_HEADER
:
if
(
val
<
period
&&
val
>
0
)
{
long
width
=
val
;
Serial
.
(
"Setting Pulse width to "
);
Serial
.
println
(
width
);
duty
=
map
(
width
,
0
,
period
,
0
,
1023
);
Timer1
.
pwm
(
outPin
,
duty
);
}
else
Serial
.
println
(
"Pulse width too long for current period"
);
show
();
break
;
case
SET_DUTY_CYCLE_HEADER
:
if
(
val
>
0
&&
val
<
100
)
{
Serial
.
(
"Setting Duty Cycle to "
);
Serial
.
println
(
val
);
duty
=
map
(
val
,
0
,
99
,
0
,
1023
);
Timer1
.
pwm
(
outPin
,
duty
);
show
();
}
}
}
}
}
void
show
()
{
Serial
.
(
"The period is "
);
Serial
.
println
(
period
);
Serial
.
(
"Duty cycle is "
);
Serial
.
(
map
(
duty
,
0
,
1023
,
0
,
99
));
Serial
.
println
(
"%"
);
Serial
.
println
();
}
This sketch is based on Recipe 18.4, with the addition of serial code to interpret commands to receive and set the frequency, period, pulse width, and duty cycle percent. Chapter 4 explains the technique used to accumulate the variable val
that is then used for the desired parameter, based on the command letter.
You can add this function and invoke it from setup
or show
if you want to print instructions to the serial port:
void
instructions
()
{
Serial
.
println
(
"Send values followed by one of the following tags:"
);
Serial
.
println
(
" p - sets period in microseconds"
);
Serial
.
println
(
" f - sets frequency in Hz"
);
Serial
.
println
(
" w - sets pulse width in microseconds"
);
Serial
.
println
(
" c - sets duty cycle in %"
);
Serial
.
println
(
"
\n
(duty cycle can have one decimal place)
\n
"
);
}
You need to increase or decrease the Pulse Width Modulation (PWM) frequency used with analogWrite
(see Chapter 7). For example, you are using analogWrite
to control a motor speed and there is an audible hum because the PWM frequency is too high, or you are multiplexing LEDs and the light is uneven because PWM frequency is too low.
You can adjust the PWM frequency by changing a register value. The register values and associated frequencies are shown in Table 18-2. This solution will work on ATmega-based boards such as the Arduino Uno, but not on ARM-based boards.
Timer1 (Uno pins 9 and 10, Mega pins 11 and 12) | ||
---|---|---|
TCCR1B prescale value | Prescale factor (divisor) | Frequency |
1 |
1 |
312500 |
2 |
8 |
3906.25 |
3 |
64 |
488.28125 |
4 |
256 |
122.0703125 |
5 |
1,024 |
30.517578125 |
Timer2 (Uno pins 11 and 3, Mega pins 9 and 10) | ||
---|---|---|
TCCR2B value | Prescale factor (divisor) | Frequency |
1 |
1 |
312500 |
2 |
8 |
3906.25 |
3 |
64 |
488.28125 |
4 |
256 |
122.0703125 |
5 |
1,024 |
30.517578125 |
All frequencies are in hertz and assume a 16 MHz system timebase. The default prescale factor of 64 is shown in bold.
This sketch enables you to select a timer frequency from the Serial Monitor. Enter a digit from 1 to 7 using the value in the lefthand column of Table 18-2 and follow this with character a for Timer0, b for Timer1, and c for Timer2:
/*
* Set PWM Frequency sketch.
* Frequency is set via the serial port.
* A digit from 1-7 followed by a, b, or c Timer1, 2, 3 adjusts frequency
*/
const
byte
mask
=
B11111000
;
// mask bits that are not prescale values
int
prescale
=
0
;
void
setup
()
{
Serial
.
begin
(
9600
);
analogWrite
(
3
,
128
);
analogWrite
(
5
,
128
);
analogWrite
(
6
,
128
);
analogWrite
(
9
,
128
);
analogWrite
(
10
,
128
);
analogWrite
(
11
,
128
);
}
void
loop
()
{
if
(
Serial
.
available
())
{
char
ch
=
Serial
.
read
();
if
(
ch
>=
'1'
&&
ch
<=
'7'
)
// is ch a valid digit?
{
prescale
=
ch
-
'0'
;
}
else
if
(
ch
==
'a'
&&
prescale
)
// timer 0;
{
TCCR0B
=
(
TCCR0B
&
mask
)
|
prescale
;
}
else
if
(
ch
==
'b'
&&
prescale
)
// timer 1;
{
TCCR1B
=
(
TCCR1B
&
mask
)
|
prescale
;
}
else
if
(
ch
==
'c'
&&
prescale
)
// timer 2;
{
TCCR2B
=
(
TCCR2B
&
mask
)
|
prescale
;
}
}
}
Avoid changing the frequency of Timer0 (used for analogWrite
pins 5 and 6) because it will result in incorrect timing using delay
and millis
.
If you just have LEDs connected to the analog pins in this sketch, you will not see any noticeable change to the brightness as you change the PWM speed. You are changing the speed as they are turning on and off, not the ratio of the on/off time. If this is unclear, see the introduction to Chapter 7 for more on PWM.
You change the PWM frequency of a timer by setting the TCCR
n
B
register, where n
is the register number. On a Mega board you also have TCCR3B
, TCCR4B
, and TCCR5B
for timers 3 through 5.
All analog output (PWM) pins on a timer use the same frequency, so changing timer frequency will affect all output pins for that timer.
See the “See Also” for links to datasheets and other references for timers.
Teensy 3.x boards support an analogWriteFrequency
function that allows you to specify PWM frequency in Hz. See this PJRC page.
Use the pulse counter built into the Timer1 hardware. This technique works on ATmega-based boards such as the Uno:
/*
* HardwareCounting sketch
*
* uses pin 5 on 168/328
*/
const
int
hardwareCounterPin
=
5
;
// input pin fixed to internal Timer
const
int
ledPin
=
LED_BUILTIN
;
const
int
samplePeriod
=
1000
;
// the sample period in milliseconds
unsigned
int
count
;
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
ledPin
,
OUTPUT
);
// hardware counter setup (see ATmega datasheet for details)
TCCR1A
=
0
;
// reset timer/counter control register A
}
void
loop
()
{
digitalWrite
(
ledPin
,
LOW
);
delay
(
samplePeriod
);
digitalWrite
(
ledPin
,
HIGH
);
// start the counting
bitSet
(
TCCR1B
,
CS12
);
// Counter Clock source is external pin
bitSet
(
TCCR1B
,
CS11
);
// Clock on rising edge
delay
(
samplePeriod
);
// stop the counting
TCCR1B
=
0
;
count
=
TCNT1
;
TCNT1
=
0
;
// reset the hardware counter
if
(
count
>
0
)
Serial
.
println
(
count
);
}
You can test this sketch by connecting the serial receive pin (pin 0) to the input pin (pin 5 on a the Uno). Each character sent should show an increase in the count—the specific increase depends on the number of pulses needed to represent the ASCII value of the characters (bear in mind that serial characters are sandwiched between start and stop pulses). Some interesting character patterns are:
'u'
=
01010101
'3'
=
00110011
'~'
=
01111110
'@'
=
01000000
If you have two Arduino boards, you can run one of the pulse generator sketches from previous recipes in this chapter and connect the pulse output (pin 9) to the input. The pulse generator also uses Timer1 (the only 16-bit timer on the Uno and other boards based on the ATmega328), so you can combine the functionality using a single board.
Hardware pulse counting uses a pin that is internally wired within the hardware and cannot be changed. Use pin 5 on an Arduino Uno. The Mega uses Timer5 that is on pin 47; change TCCR1A
to TCCR5A
and TCCR1B
to TCCR5B
.
The timer’s TCCR1B
register controls the counting behavior, setting it so 0 stops counting. The values used in the loop
code enable count in the rising edge of pulses on the input pin. TCNT1
is the Timer1 register declared in the Arduino core code that accumulates the count value.
In loop
, the current count is printed once per second. If no pulses are detected on pin 5, the values will be 0
.
The FreqCount library uses the method discussed in this recipe.
You can use an interrupt to take an action when a pin state changes. See Recipe 18.2.
See the “See Also” for links to datasheets and other references for timers.
You want to measure the period between pulses or the duration of the on or off time of a pulse. You need this as accurate as possible, so you don’t want any delay due to calling an interrupt handler (as in Recipe 18.2), as this will affect the measurements.
Use the hardware pulse measuring capability built into the Timer1 hardware. This solution uses AVR-specific facilities, and will only work on ATmega-based boards like the Arduino Uno:
/*
* InputCapture Sketch
* uses timer hardware to measure pulses on pin 8 on 168/328
*/
/* some interesting ASCII bit patterns:
u 01010101
3 00110011
~ 01111110
@ 01000000
*/
const
int
inputCapturePin
=
8
;
// input pin fixed to internal Timer
const
int
ledPin
=
LED_BUILTIN
;
const
int
prescale
=
8
;
// prescale factor (each tick 0.5 us @16MHz)
const
byte
prescaleBits
=
B010
;
// see Table 18-1 or Datasheet
// calculate time per counter tick in ns
const
long
precision
=
(
1000000
/
(
F_CPU
/
1000.0
))
*
prescale
;
const
int
numberOfEntries
=
64
;
// the max number of pulses to measure
const
int
gateSamplePeriod
=
1000
;
// the sample period in milliseconds
volatile
byte
index
=
0
;
// index to the stored readings
volatile
byte
gate
=
0
;
// 0 disables capture, 1 enables
volatile
unsigned
int
results
[
numberOfEntries
];
// note this is 16-bit value
/* ICR interrupt vector */
ISR
(
TIMER1_CAPT_vect
)
{
TCNT1
=
0
;
// reset the counter
if
(
gate
)
{
if
(
index
!=
0
||
bitRead
(
TCCR1B
,
ICES1
)
==
true
)
// wait for rising edge
{
// falling edge was detected
if
(
index
<
numberOfEntries
)
{
results
[
index
]
=
ICR1
;
// save the input capture value
index
++
;
}
}
}
TCCR1B
^=
_BV
(
ICES1
);
// toggle bit to trigger on the other edge
}
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
ledPin
,
OUTPUT
);
pinMode
(
inputCapturePin
,
INPUT
);
// ICP pin (digital pin 8 on Arduino) as input
TCCR1A
=
0
;
// Normal counting mode
TCCR1B
=
prescaleBits
;
// set prescale bits
TCCR1C
=
0
;
bitSet
(
TCCR1B
,
ICES1
);
// init input capture
bitSet
(
TIFR1
,
ICF1
);
// clear pending
bitSet
(
TIMSK1
,
ICIE1
);
// enable
Serial
.
println
(
"pulses are sampled while LED is lit"
);
Serial
.
(
precision
);
// report duration of each tick in microseconds
Serial
.
println
(
" microseconds per tick"
);
}
// this loop prints the duration of pulses detected in the last second
void
loop
()
{
digitalWrite
(
ledPin
,
LOW
);
delay
(
gateSamplePeriod
);
digitalWrite
(
ledPin
,
HIGH
);
index
=
0
;
gate
=
1
;
// enable sampling
delay
(
gateSamplePeriod
);
gate
=
0
;
// disable sampling
if
(
index
>
0
)
{
Serial
.
println
(
"Durations in Microseconds are:"
)
;
for
(
byte
i
=
0
;
i
<
index
;
i
++
)
{
long
duration
;
duration
=
results
[
i
]
*
precision
;
// pulse duration in nanoseconds
if
(
duration
>
0
)
{
Serial
.
println
(
duration
/
1000
);
// duration in microseconds
results
[
i
]
=
0
;
// clear value for next reading
}
}
index
=
0
;
}
}
This sketch uses a timer facility called Input Capture to measure the duration of a pulse. Only 16-bit timers support this capability and this only works with pin 8 on an Arduino Uno or compatible board.
Input Capture uses a pin that is internally wired within the hardware and cannot be changed. Use pin 8 on an Uno and pin 48 on a Mega (using Timer5 instead of Timer1).
Because Input Capture is implemented entirely in the controller chip hardware, no time is wasted in interrupt handling, so this technique is more accurate for very short pulses (less than tens of microseconds).
The sketch uses a gate
variable that enables measurements (when nonzero) every other second. The LED is illuminated to indicate that measurement is active. The input capture interrupt handler stores the pulse durations for up to 64 pulse transitions.
The edge that triggers the timer measurement is determined by the ICES1
bit of the TCCR1B
timer register. The line:
TCCR1B
^=
_BV
(
ICES1
);
toggles the edge that triggers the handler so that the duration of both high and low pulses is measured.
If the count goes higher than the maximum value for the timer, you can monitor overflow to increment a variable to extend the counting range. The following code increments a variable named overflow
each time the counter overflows:
volatile
int
overflows
=
0
;
/* Overflow interrupt vector */
ISR
(
TIMER1_OVF_vect
)
// here if no input pulse detected
{
overflows
++
;
// increment overflow count
}
Change the code in setup
as follows:
TIMSK1
=
_BV
(
ICIE1
);
// enable input capture interrupt for timer 1
TIMSK1
|=
_BV
(
TOIE1
);
// Add this line to enable overflow interrupt
See the “See Also” for links to datasheets and other references for timers.
You want to read an analog value as quickly as possible without decreasing the accuracy.
You can increase the analogRead
sampling rate by changing register values that determine the sampling frequency. This sketch will work on ATmega-based boards such as the Arduino Uno:
/*
* Sampling rate sketch
* Increases the sampling rate for analogRead
*/
const
int
sensorPin
=
0
;
// pin the receiver is connected to
const
int
numberOfEntries
=
100
;
unsigned
long
microseconds
;
unsigned
long
duration
;
int
results
[
numberOfEntries
];
void
setup
()
{
Serial
.
begin
(
9600
);
while
(
!
Serial
);
// Needed for Leonardo
// standard analogRead performance (prescale = 128)
microseconds
=
micros
();
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++
)
{
results
[
i
]
=
analogRead
(
sensorPin
);
}
duration
=
micros
()
-
microseconds
;
Serial
.
(
numberOfEntries
);
Serial
.
(
" readings took "
);
Serial
.
println
(
duration
);
// running with high speed clock (set prescale to 16)
bitClear
(
ADCSRA
,
ADPS0
);
bitClear
(
ADCSRA
,
ADPS1
);
bitSet
(
ADCSRA
,
ADPS2
);
microseconds
=
micros
();
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++
)
{
results
[
i
]
=
analogRead
(
sensorPin
);
}
duration
=
micros
()
-
microseconds
;
Serial
.
(
numberOfEntries
);
Serial
.
(
" readings took "
);
Serial
.
println
(
duration
);
}
void
loop
()
{
}
Running the sketch on a 16 MHz Arduino will produce output similar to the following:
100
readings
took
11308
100
readings
took
1704
analogRead
takes around 110 microseconds to complete a reading. This may not be fast enough for rapidly changing values, such as capturing the higher range of audio frequencies. The sketch measures the time in microseconds for the standard analogRead
and then adjusts the timebase used by the analog-to-digital converter (ADC) to perform the conversion faster. With a 16 MHz board, the timebase rate is increased from 125 kHz to 1 MHz. The actual performance improvement is slightly less than eight times because there is some overhead in the Arduino analogRead
function that is not improved by the timebase change. The reduction of time from 113 microseconds to 17 microseconds is a significant improvement.
The ADCSRA
register is used to configure the ADC, and the bits set in the sketch (ADPS0
, ADPS1
, and ADPS2
) set the ADC clock divisor to 16.
Microchip has an application note that provides a detailed explanation of performance aspects of the ADC.
On ARM-based Arduino boards such as the Zero and compatibles, analogRead
is quite a bit slower than on AVR. The AnalogReadFast library can read about 5 times faster than the AVR’s analogRead
and about 20 times faster than a SAMD21-based board like the Arduino Zero. You can install it from the Library Manager and you can find it on this GitHub page.
This Solution uses Adafruit’s SleepyDog library, which supports Uno, Mega, Zero, Adafruit M0 boards, Leonardo, and (partial support) Teensy 3.X:
/* * Low power sketch * Reduce power usage with the Adafruit SleepyDog library */ #include <Adafruit_SleepyDog.h> void setup() { Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); int sleepTimeMillis1 = Watchdog.sleep(500); digitalWrite(LED_BUILTIN, LOW); int sleepTimeMillis2 = Watchdog.sleep(500); // Try to restore USB connection on Leonardo and other boards // with Native USB. #if defined(USBCON) && !defined(USE_TINYUSB) USBDevice.attach(); #endif Serial.print("Slept for "); Serial.print(sleepTimeMillis1); Serial.print("ms and "); Serial.print(sleepTimeMillis2); Serial.println("ms"); delay(100); // Give the serial buffer time to transmit }
If you use this on an M0-based board, such as the Arduino Zero or MKR series, you may not be able to restore the USB connection to your board after the first time it powers down. If you need to re-flash the board, you can quickly double-click the reset button to put it in a bootloader mode (the sketch will not be running in this mode).
The Arduino Uno would run down a 9-volt alkaline battery in a few weeks. Significant power savings can be achieved if your application can suspend operation for a period of time—Arduino hardware can be put to sleep for a preset period of time, and this reduces the power consumption of the chip to less than one one-hundredth of 1 percent (from around 15 mA to around 0.001 mA) during sleep.
The library used in this recipe provides easy access to the hardware sleep function. The sleep time can range from 15 to 8,000 ms (eight seconds). To sleep for longer periods, you can repeat the delay intervals until you get the period you want:
unsigned
long
longDelay
(
long
milliseconds
)
{
unsigned
long
sleptFor
=
0
;
while
(
milliseconds
>
0
)
{
if
(
milliseconds
>
8000
)
{
milliseconds
-=
8000
;
sleptFor
+=
Watchdog
.
sleep
(
8000
);
}
else
{
sleptFor
+=
Watchdog
.
sleep
(
milliseconds
);
break
;
}
}
return
sleptFor
;
}
For each interval, the library will round the requested sleep period down to the closest sleep time supported by the underlying hardware. So, for example, if you were to request 8,600 ms on AVR, the first time through the loop, you’d get 8,000, but on the second, you’d get 500 because 500 is a valid sleep period on AVR followed by the next highest (1,000). See these .cpp files for the specific implementations.
Sleep mode can reduce the power consumption of the controller chip, but if you are looking to run for as long as possible on a battery, you should minimize current drain through external components such as inefficient voltage regulators, pull-up or pull-down resistors, LEDs, and other components that draw current when the chip is in sleep mode.
The narcoleptic library can sleep for a period of time, and can also sleep until the board receives input on a particular pin. However, it will only work with AVR-based boards.
The Arduino Low Power library supports sleep modes on ARM SAMD-based boards as well as NRF52-based boards such as the Nano 33 BLE and Nano 33 BLE Sense. You can install it from the Library Manager and find more information at this Arduino page.
For an example of very low power operation, see this Lab3 page.
You need to set or clear digital pins much faster than enabled by the Arduino digitalWrite
command.
Arduino digitalWrite
provides a safe and easy-to-use method of setting and clearing pins, but it is more than 30 times slower than directly accessing the controller hardware. You can set and clear pins by directly setting bits on the hardware registers that are controlling digital pins.
This sketch uses direct hardware I/O to send Morse code (the word arduino) to an AM radio tuned to approximately 1 MHz. The technique used here is 30 times faster than Arduino digitalWrite
:
/* * Morse sketch * * Direct port I/O used to send AM radio carrier at 1MHz */
const
int
sendPin
=
2
;
const
byte
WPM
=
12
;
// sending speed in words per minute
const
long
repeatCount
=
1200000
/
WPM
;
// count determines dot/dash duration
const
byte
dot
=
1
;
const
byte
dash
=
3
;
const
byte
gap
=
3
;
const
byte
wordGap
=
7
;
byte
letter
=
0
;
// the letter to send
char
*
arduino
=
"
.- .-. -.. ..- .. -. ---
"
;
void
setup
(
)
{
pinMode
(
sendPin
,
OUTPUT
)
;
Serial
.
begin
(
9600
)
;
}
void
loop
(
)
{
sendMorse
(
arduino
)
;
delay
(
2000
)
;
}
void
sendMorse
(
char
*
string
)
{
letter
=
0
;
while
(
string
[
letter
]
!
=
0
)
{
if
(
string
[
letter
]
=
=
'.'
)
{
sendDot
(
)
;
}
else
if
(
string
[
letter
]
=
=
'-'
)
{
sendDash
(
)
;
}
else
if
(
string
[
letter
]
=
=
' '
)
{
sendGap
(
)
;
}
else
if
(
string
[
letter
]
=
=
0
)
{
sendWordGap
(
)
;
}
letter
=
letter
+
1
;
}
}
void
sendDot
(
)
{
transmitCarrier
(
dot
*
repeatCount
)
;
sendGap
(
)
;
}
void
sendDash
(
)
{
transmitCarrier
(
dash
*
repeatCount
)
;
sendGap
(
)
;
}
void
sendGap
(
)
{
transmitNoCarrier
(
gap
*
repeatCount
)
;
}
void
sendWordGap
(
)
{
transmitNoCarrier
(
wordGap
*
repeatCount
)
;
}
void
transmitCarrier
(
long
count
)
{
while
(
count
-
-
)
{
bitSet
(
PORTD
,
sendPin
)
;
bitSet
(
PORTD
,
sendPin
)
;
bitSet
(
PORTD
,
sendPin
)
;
bitSet
(
PORTD
,
sendPin
)
;
bitClear
(
PORTD
,
sendPin
)
;
}
}
void
transmitNoCarrier
(
long
count
)
{
while
(
count
-
-
)
{
bitClear
(
PORTD
,
sendPin
)
;
bitClear
(
PORTD
,
sendPin
)
;
bitClear
(
PORTD
,
sendPin
)
;
bitClear
(
PORTD
,
sendPin
)
;
bitClear
(
PORTD
,
sendPin
)
;
}
}
Connect one end of a piece of wire to pin 2 and place the other end near the antenna of a medium wave AM radio tuned to 1 MHz (1,000 kHz).
The sketch generates a 1 MHz signal to produce dot and dash sounds that can be received by an AM radio tuned to this frequency. The frequency is determined by the duration of the bitSet
and bitClear
commands that set the pin HIGH
and LOW
to generate the radio signal. bitSet
and bitClear
are not functions, they are macros. Macros substitute an expression for executable code—in this case, code that changes a single bit in register PORTD
given by the value of sendPin
.
Digital pins 0 through 7 are controlled by the register named PORTD
. Each bit in PORTD
corresponds to a digital pin. Pins 8 through 13 are on register PORTB
, and pins 14 through 19 are on PORTA
. The sketch uses the bitSet
and bitClear
commands to set and clear bits on the port (see Recipe 3.12). Each register supports up to eight bits (although not all bits correspond to Arduino pins). If you want to use Arduino pin 13 instead of pin 2, you need to set and clear PORTB
as follows:
const
int
sendPin
=
13
;
bitSet
(
PORTB
,
sendPin
-
8
);
bitClear
(
PORTB
,
sendPin
-
8
);
You subtract 8 from the value of the pin because bit 0 of the PORTB
register is pin 8, bit 1 is pin 9, and so on, to bit 5 controlling pin 13.
Setting and clearing bits using bitSet
is done in a single instruction of the Arduino controller. On a 16 MHz Arduino, that is 62.5 nanoseconds. This is around 30 times faster than using digitalWrite
.
The transmit functions in the sketch actually need more time updating and checking the count
variable than it takes to set and clear the register bits, which is why the transmitCarrier
function has four bitSet
commands and only one bitClear
command—the additional bitClear
commands are not needed because of the time it takes to update and check the count
variable.
You want to upload sketches to an AVR-based Arduino (such as the Uno) using a programmer instead of the bootloader. Perhaps you want the shortest upload time, or you don’t have a serial connection to your computer suitable for bootloading, or you want to use the space normally reserved for the bootloader to increase the program memory available to your sketch.
Connect an external in-system programmer (ISP) to the Arduino programming ICSP (In-Circuit Serial Programming) connector. Programmers intended for use with Arduino have a 6-pin cable that attaches to the 6-pin ICSP connector as shown in Figure 18-1.
Ensure that pin 1 from the programmer (usually marked with different color than the other wires) is connected to pin 1 on the ICSP connector. The programmer may have a switch or jumper to enable it to power the Arduino board; read the instructions for your programmer to ensure that the Arduino is powered correctly.
Select your programmer from the Tools menu (AVRISP, AVRISPII, USBtinyISP, Parallel programmer, or Arduino as ISP) and double-check that you have the correct Arduino board selected. From the File menu, select Upload Using Programmer to perform the upload.
There are a number of different programmers available, including expensive devices aimed at professional developers that offer various debugging options, low-cost self-build kits, and utilizing an additional Arduino board to perform this function. The programmer may be a native USB device, or appear as a serial port. Check the documentation for your device to see what kind it is, and whether you need to install drivers for it.
The serial Rx and Tx LEDs on the Arduino will not flicker during upload because the programmer is not using the hardware serial port.
Uploading using a programmer removes the bootloader code from the chip. This frees up the space the bootloader occupies and gives a little more room for your sketch code. WIth your ISP connected, select Tools→Burn Bootloader to restore it.
Code to convert an Arduino into an ISP programmer can be found in the sketch example named ArduinoISP. The comments in the sketch describe the connections to use.
Connect a programmer and select it as discussed in Recipe 18.12. Double-check that you have the correct board selected and click Burn Bootloader from the Tools menu.
A message will appear in the IDE saying “Burning bootloader to I/O board (this may take a minute)…”. Programmers with status lights should indicate that the bootloader is being written to the board. On the Uno, you should see the built-in LED flash as the board is programmed (pin 13 happens to be connected to one of the ICSP signal pins). If all goes well, you should get a message saying “Done Loading Bootloader.”
Disconnect the programmer and try uploading code through the IDE to verify it is working.
The bootloader is a small program that runs on the chip and briefly checks each time the chip powers up to see if the IDE is trying upload code to the board. If so, the bootloader takes over and replaces the code on the chip with new code being uploaded through the serial port. If the bootloader does not detect a request to upload, it relinquishes control to the sketch code already on the board.
If you have used a serial programmer, you will need to switch the serial port back to the correct one for your Arduino board as described in Recipe 1.4.
Optiloader, maintained by Bill Westfield, is another way to update or install the bootloader. It uses an Arduino connected as an ISP programmer, but all the bootloaders are included in the Arduino sketch code. This means an Arduino with Optiloader can program another chip automatically when power is applied—no external computer needed. The code identifies the chip and loads the correct bootloader onto it.
Optiboot is an upgrade to the Arduino bootloader that increases available sketch size, improves sketch upload speeds, and supports a number of other features. Typically, you do not install Optiboot directly to your board. Consult Optiboot’s README file for a list of Optiboot-based Arduino cores you can install with the Boards Manager and then flash using Tools→Burn Bootloader.
You want Arduino to interact with an application on your computer by moving the mouse cursor. Perhaps you want to move the mouse position in response to Arduino information. For example, suppose you have connected input devices such as potentiometers or a Wii nunchuck (see Recipe 13.6) to your Arduino and you want to control the position of the mouse cursor in a program running on a PC.
ARM-based boards such as the Arduino Zero, Adafruit Metro M0 Express, and SparkFun RedBoard Turbo, as well as ATmega32u4-based boards like the Leonardo can appear like a USB mouse to your computer using the built-in Mouse library. This will not work on the Uno or directly compatible boards. Here is a sketch that moves the mouse cursor based on the position of two potentiometers.
Wire up two potentiometers (see Recipe 5.6), one to analog input 4 (A4) and the other to analog input 5 (A5). Connect a switch to digital pin 2 (as described in Recipe 5.2) to act as the left mouse button, then run the sketch:
/*
* Mouse Emulation sketch
* Use the Mouse library to emulate a USB mouse device
*/
#include "Mouse.h"
const
int
buttonPin
=
2
;
// left click
const
int
potXPin
=
A4
;
// analog pins for pots
const
int
potYPin
=
A5
;
int
last_x
=
0
;
int
last_y
=
0
;
void
setup
()
{
Serial
.
begin
(
9600
);
pinMode
(
buttonPin
,
INPUT_PULLUP
);
// Get initial potentiometer positions. Range is -127 to 127
last_x
=
(
512
-
(
int
)
analogRead
(
potXPin
))
/
4
;
last_y
=
(
512
-
(
int
)
analogRead
(
potYPin
))
/
4
;
Mouse
.
begin
();
}
void
loop
()
{
// Get current positions.
int
x
=
(
512
-
(
int
)
analogRead
(
potXPin
))
/
4
;
int
y
=
(
512
-
(
int
)
analogRead
(
potYPin
))
/
4
;
Serial
.
(
"last_x: "
);
Serial
.
println
(
last_x
);
Serial
.
(
"last_y: "
);
Serial
.
println
(
last_y
);
Serial
.
(
"x: "
);
Serial
.
println
(
x
);
Serial
.
(
"y: "
);
Serial
.
println
(
y
);
// calculate the movement distance based on the potentiometer state
int
xDistance
=
last_x
-
x
;
int
yDistance
=
last_y
-
y
;
// Update last known positions of the potentiometer
last_x
=
x
;
last_y
=
y
;
// if X or Y movement is greater than 3, move:
if
(
abs
(
xDistance
)
>
3
||
abs
(
yDistance
)
>
3
)
{
Serial
.
(
"x move: "
);
Serial
.
println
(
xDistance
);
Serial
.
(
"y move: "
);
Serial
.
println
(
yDistance
);
Mouse
.
move
(
xDistance
,
yDistance
,
0
);
}
// if the mouse button is pressed:
if
(
digitalRead
(
buttonPin
)
==
LOW
)
{
if
(
!
Mouse
.
isPressed
(
MOUSE_LEFT
))
{
Mouse
.
press
(
MOUSE_LEFT
);
// Click
}
}
else
{
if
(
Mouse
.
isPressed
(
MOUSE_LEFT
))
{
Mouse
.
release
(
MOUSE_LEFT
);
// Release
}
}
Serial
.
println
();
delay
(
10
);
}
This technique for controlling applications running on your computer is easy to implement and should work with any operating system that can supports USB mice. If you need to invert the direction of movement on the x- or y-axis, you can do this by changing the sign of xDistance
and yDistance
:
Mouse
.
move
(
-
xDistance
,
-
yDistance
,
0
);
Reference for the Mouse library
The ArduinoJoystick library allows ATmega32u4-based boards to emulate a USB joystick.
The MIDIUSB library allows ATmega32u4-based and ARM boards to emulate a USB MIDI device.
The built-in Keyboard library allows ATmega32u4-based and ARM boards to emulate a USB keyboard.
The HID library allows offers emulation of a variety of USB devices.