Lab Guide: Arduino Hardware

In this document I distinguish between Arduino code and AVR code.  In fact they are the same thing: what I call Arduino code is a C/C++ function library written using AVR code and targeting the specific hardware used in Arduino microcontrollers.  The purpose of Arduino code is to hide AVR code as much as possible, ostensibly to make programs easier to read and write.  You will need to read and write code in the Arduino style, and may need to use the AVR style sometimes.

The AVR Hardware Specification

The Seeduino is based on the ATmega1280 AVR microcontroller.  The most important reference document for programming the ATmega1280 is its hardware specification:

https://www.atmel.com/dyn/resources/prod_documents/doc2549.pdf [PDF, 9 MB]

It’s 400+ pages, you don’t need to know the whole thing, but you can’t read or write AVR code without it.  For example, the Arduino I2C module contains the following line of AVR code:

// send start condition
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

This general pattern indicates a bunch of bits being set in a register.  In this case, the TWEN, TWIE, TWEA, TWINT, and TWSTA bits are being set in the TWCR register (the other three bits, whatever they are called, are implicitly cleared to 0).  In order to figure out what the heck TWCR means and what the bits in question do, refer to the register description in the hardware specification (look it up now, it’s not interesting but it’s a useful exercise).

The _BV(…) macro transforms a pin identifier into its Bit Value.  For example, the TWEN identifier refers to bit 2 of the TWCR register and resolves to a numerical value of 2.  The _BV(TWEN) expression produces an 8-bit integer with bit 2 set (i.e. 0b00000100).  In other words, _BV(TWEN) is a left bit shift by the value of TWEN: _BV(TWEN) ==> 1 << TWEN ==> 1 << 2.  There’s probably a very intelligent reason why it’s done this way, but don’t ask me what it is.

You can’t always just search the document.  For example, if you want to know what the UCSR1A register does, a search in the hardware spec will only return a meaningless reference in a big table of registers.  The document refers to UCSRnA instead, to cover UCSR0A, UCSR1A, UCSR2A, etc.  Similarly, I/O pins are often referred to as PORTXn or PXn, where X is the port and n is the pin number.

Arduino Pins vs. AVR Pins

As I alluded to in the page on the software environment, AVR refers to I/O pins in two different ways, and Arduino refers to them in a totally different way.  There is no straightforward function relating a particular AVR pin to its Arduino equivalent.  An Arduino pin maps to whatever AVR pin provides the functionality needed by Arduino.

AVR: Package Position Number

Look at page 2 of the ATmega1280 hardware specification.  The pins are numbered from 1 to 100.  This numbering is never used in software, but you’ll occasionally see it referred to in hardware-related documents.

AVR: Port and Pin Number

Each I/O pin is mapped to an 8-bit port that is assigned a letter.  The ATmega1280 has ports A through L, each of which has 8 pins.  If you look at page 2 of the hardware spec again you’ll see each pin labelled in the format PXn, where X is the port letter and n is the pin number.

That diagram also gives each pin’s alternate functions.  By default each pin is a general I/O pin, but by configuring registers using software many pins can be re-assigned to alternate functions. For example, if you turn on the analog-digital converter and select channel 0, then pin PF0 will no longer be available as a general I/O pin; instead it will be assigned to its alternate function as pin ADC0, as shown in the diagram.  Arduino provides functions that enable the alternate function on many pins.

Arduino: Digital or Analog Pin Number

The basic Arduino board (e.g. Arduino Uno) provides fourteen digital I/O pins numbered from 0 to 13, and six analog I/O pins numbered from A0 to A5.  The Arduino Mega provides 54 digital I/O pins numbered from 0 to 53, and sixteen analog I/O pins numbered from A0 to A15.  This numbering system makes life quite easy if you’re only programming in Arduino, but once you start mixing Arduino with raw AVR code it starts to get confusing.

Each digital and analog I/O pin on the Arduino corresponds to a port and pin number in AVR.  In fact, the analog pins can be used as digital pins, since they’re just general I/O pins until the analog-digital converter is enabled.

The Seeeduino Mega that we use has digital I/O pins 0 through 53, and in addition it breaks out 16 digital I/O pins that the Arduino library doesn’t expose to the user.  The only way to use these pins is to refer to them in the AVR style (e.g. PD4, PJ5, and PH7).

How to Reconcile the Different Pin Numbers

Arduino provides a handy schematic for its hardware.  Its version for the Arduino Mega, which also applies to the Seeeduino Mega, can be found here:

https://arduino.cc/en/uploads/Main/arduino-mega-schematic.pdf [PDF]

The schematic for the Arduino Uno can be found here:

https://arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf [PDF]

These documents map all three pin numbering styles to each other:

  • Package position number: grey numbers around the rectangular object representing the CPU.
  • Port and pin number: grey labels in the format PXn.
  • Arduino pin number: green numbers throughout the document.

For example, if you examine the schematic you will see that the following lines will have the same effect, as pin PB7 is connected to Arduino pin 13:

digitalWrite(13, HIGH);    // Arduino style to turn on the LED connected to Arduino pin 13
PORTB |= _BV(PB7);         // AVR style to turn on the LED connected to Arduino pin 13