Most peripherals on the ATmega328P are customizable by the user. This means that different features of each peripheral can be customized in the program code. This is accomplished by means of writing data to 8-bit peripheral registers. Some peripherals may also have registers that read or write some type of data. For example, the analog to digital converter (introduced in Sectionย 6.4) has three registers used to configure the peripheral:
Some bits in a peripheral register may be labeled as โreserved.โ According to [16.14]: โ[f]or compatibility with future devices, reserved bits should be written to zero if accessed.โ
The peripheral registers are stored in I/O and extended I/O data memory (described in Sectionย 13.2). A list of all peripheral registers is included in [16.14].
There are a few peripheral registers that are used to store 16ย bits of information. All peripheral registers are stored in data memory, which has a word size of 8ย bits. This means that the โ16-bit registersโ are in reality composed of two 8-bit registers and organized into high and low bytes. In particular, timer/counterย 1 (introduced in Sectionย 9.2) has multiple 16-bit registers to store the value in the 16-bit counter. There are a few important things to note when reading from or writing to these registers.
Each read from or write to a 16-bit register must occur without interruption between the two bytes. If the process is interrupted between bytes, then itโs possible the contents of the registers can be modified during the interruption, which would cause the value written to or read from the second byte not to correspond to the data written to or read from the first byte. When reading from or writing to any 16-bit register, the global interrupt flag must be cleared to disable interrupts before the read or write operation. The global interrupt flag must then be set again after the read or write operation has concluded.
When writing code in C using a compiler such as Atmel Studio or the Arduino IDE, each 16-bit register can be accessed directly. The compiler will then generate the corresponding assembly code to break the write or read into the two corresponding operations.
The following code demonstrates writing to the timer/counterย 1 output compare A register (OCR1A) using C. The cli() function globally disables interrupts and is used prior to writing to the 16-bit register. The sei() function globally enables interrupts and is used after writing to the 16-bit register.
Example4.4.2.Reading from a 16-bit register in assembly.
The following code demonstrates reading from the timer/counterย 1 register (TCNT1) using assembly. The CLI instruction globally disables interrupts and is used prior to reading from the register pair. The low byte is read prior to the high byte. The SEI instruction globally enables interrupts and is used after reading from the register pair.