h1

Getting started with Xmega: differences from ATmega (part 2)

2010/06/27

In Part 1 I explained some of the high-level differences between the older ATmega and the newer Xmega chips.  This includes things like pinout cleanliness, enhanced peripheral count, and much less arbitrary overlap between functions.  In Part 2, I’ll be delving deeper into the architectural changes that result from this design, and how they make writing software for the Xmega much more manageable.

The issue at hand now is not where the peripherals are placed physically on the chip, but how they’re interacted with by software, logically.  As with any other microcontroller, this is done via registers.  These are specific locations in memory (or sometimes a “third” address space, besides memory and code) that when read from or written to will cause some behavior within the peripheral that the register is associated with.  For instance, writing to a USART data register will typically push the written byte into a temporary buffer and start transmitting that byte over the serial port.  Reading from the same register will pull from a different temporary buffer and retrieve the byte that was most recently received.  Other registers contain flags, such as the Transmit Enable flag in one of the USART’s control registers.

To start off with, we’ll again go back to the venerable ATmega*8 as used in the Arduino.  Let’s list all the registers that have anything to do with any of the Port D pins, and what their register address is:

  • 0xC6 UDR0
  • 0xC5 UBRR0H
  • 0xC4 UBRR0L
  • 0xC2 UCSR0C
  • 0xC1 UCSR0B
  • 0xC0 UCSR0A
  • 0xB4 OCR2B
  • 0xB3 OCR2A
  • 0xB2 TCNT2
  • 0xB1 TCCR2B
  • 0xB0 TCCR2A
  • 0x7F DIDR1
  • 0x7B ADCSRB
  • 0x70 TIMSK2
  • 0x6E TIMSK1
  • 0x6D PCMSK2
  • 0x69 EICRA
  • 0x50 ACSR
  • 0x48 OCR0B
  • 0x47 OCR0A
  • 0x46 TCNT0
  • 0x45 TCCR0B
  • 0x44 TCCR0A
  • 0x3D EIMSK
  • 0x3E EIFR
  • 0x3B PCIFR
  • 0x37 TIFR2
  • 0x35 TIFR0
  • 0x2B PORTD
  • 0x2A DRD
  • 0x29 PIND

That’s a lot of registers!  Now while I’m not going to claim that the Xmega uses particularly fewer registers than the ATmega, I challenge you to tell me quickly what every one of those registers does…  In comparison, the registers needed for Port D on an Xmega:

  • PORTCFG.
    • MPCMASK
    • CLKEVOUT
  • PORTD.
    • DIR,DIRSET,DIRCLR,DIRTGL
    • OUT,OUTSET,OUTCLR,OUTTGL
    • IN
    • INTCTRL
    • INT0MASK,INT1MASK
    • INTFLAGS
    • PIN0CTRL,PIN1CTRL,PIN2CTRL,PIN3CTRL,PIN4CTRL,PIN5CTRL,PIN6CTRL,PIN7CTRL
  • TCD0.
    • CTRLA
    • CTRLA,CTRLB,CTRLC,CTRLD,
    • CTRLE
    • INTCTRLA
    • INTCTRLB
    • CTRLFCLR,CTRLFSET
    • CTRLGCLR,CTRLGSET
    • INTFLAGS
    • TEMP
    • CNTH,CNTL
    • PERH,PERL
    • CC0H,CC0L,CC1H,CC1L,CC2H,CC2L,CC3H,CC3L
    • PERBUFH,PERBUFL
    • CC0BUFH,CC0BUFL,CC1BUFH,CC1BUFL,CC2BUFH,CC2BUFL,CC3BUFH,CC3BUFL
  • TCD1.*
  • HIRESD.CTRLA
  • USARTD0.
    • CTRLA,CTRLB,CTRLC
    • DATA
    • STATUS
    • BAUDCTRLA,BAUDCTRLB
  • USARTD1.*
  • TWID.
    • CTRL
    • MASTER.
      • CTRLA,CTRLB,CTRLC
      • STATUS
      • BAUD
      • ADDR
      • DATA
    • SLAVE.
      • CTRLA,CTRLB
      • STATUS
      • ADDR
      • DATA
      • ADDRMASK
  • SPID.
    • CTRL
    • INTCTRL
    • STATUS
    • DATA

Now this is somewhat more comprehensible.  Yes, there are a metric ton more registers, but they all represent significantly enhanced capabilities.  More importantly, they’re all grouped very clearly by module.  If you want to use the first USART on Port D, you start by setting USARTD0.CTRLA, and work from there, rather than trying to remember UCSR0A.  Good luck remembering which UCSR* goes with which port on a bigger chip like the ATmega128…

You’ll notice that both TCD1 and USARTD1 aren’t enumerated, but just listed with a *.  That’s because they have the exact same registers as their D0 counterparts (except that TCx1 drop the 2nd and 3rd compare registers).  Compared to the ATmega, that’s a major bonus: all the peripherals are the same, both between multiple instances in the same chip and between chips in the series.

Delving even deeper, let’s look at how the SPI port is described first in the ATmega8 header file:

/* SPI */
#define SPCR    _SFR_IO8(0x0D)
#define SPSR    _SFR_IO8(0x0E)
#define SPDR    _SFR_IO8(0x0F)

…and now how it’s defined in the Xmega headers:

/* Serial Peripheral Interface */
typedef struct SPI_struct
{
    register8_t CTRL;  /* Control Register */
    register8_t INTCTRL;  /* Interrupt Control Register */
    register8_t STATUS;  /* Status Register */
    register8_t DATA;  /* Data Register */
} SPI_t;
#define SPIC    (*(SPI_t *) 0x08C0)  /* Serial Peripheral Interface C */
#define SPID    (*(SPI_t *) 0x09C0)  /* Serial Peripheral Interface D */

In the Xmega, every peripheral is given a block of register space, and all the individual registers are allocated within that block.  The SPID register block looks exactly like the SPIC, and SPIE, and SPIF register blocks, except for the starting address.  Thus, the only difference between the ATxmega*A4 and ATxmega*A3 is the following:

#define SPIE    (*(SPI_t *) 0x0AC0)  /* Serial Peripheral Interface E */
#define SPIF    (*(SPI_t *) 0x0BC0)  /* Serial Peripheral Interface F */

A major side effect of all these structures is that you can now easily construct functions and other code structures that can operate on one of these peripherals purely by address:

void spi_init(SPI_t *port) {
    port->CTRL = 0xd0;
    port->STATUS = 0x80;
}
spi_init(&SPID);

With simply dot notation, you can significantly simplify your hardware configuration:

#define LED_PORT     PORTC
#define LED_RED_bp   0
#define LED_GREEN_bp 0
#define LED_BLUE_bp  0

LED_PORT.DIRSET = _BV(LED_RED_bp) | _BV(LED_GREEN_bp) | _BV(LED_BLUE_bp);
LED_PORT.OUTCLR = _BV(LED_RED_bp) | _BV(LED_GREEN_bp) | _BV(LED_BLUE_bp);
// . . .
LED_PORT.OUTSET = _BV(LED_BLUE_bp);

In comparison, the same for the ATmega8 would be:

#define LED_PORT    PORTC
#define LED_DIR     DDRC
// . . .

If you are using peripherals more complex than just an output port, you can imagine how having to #define all the various registers to keep track of which of potentially several similar peripherals (e.g. USARTs) is used by that logical device would get rather obnoxious.

This particular feature has saved me uncounted hours on the product I’m developing, by allowing me to keep the same codebase across multiple revisions of the hardware.  “hardware.h” contains a switchout that loads “hardware-rev1.h” or “hardware-rev2.h” or whichever.  All these files list the same board-level peripherals (the LED, the debug and RS-485 ports, I2C for the clock PLL, etc.), and as long as I use them exclusively in my actual code, all I have to do when I switch around the hardware layout is to generate a new file and change which ports and such are referenced.

(Part 3: how this peripheral interchangeability can make your project design radically more flexible)

Advertisements

5 comments

  1. Thanks for taking the time. Very well explained


  2. Not finished?

    Where’s PT3?


    • Still no PT3 link?


      • I try to get around to it sometime soon, but the product I’m working on is in the final stretch and I’m swamped with getting all sorts of things working ;-(


  3. Hi,
    thanks for your work on showing us the differences between MEGA and XMEGA.

    Very unfortunate to see part 3 still is missing. Hoping you’ll finish your product soon, so we can continue reading! ;-)



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: