NTP Update; XMEGA thoughts
Just a minor update about the NTP server project, the board has been reworked a fair bit and this has resulted in a much cleaner design. There's some parts coming so I can better prove the changes, before I get the PCB made. In the meantime, I've been playing more with the XMEGA chip I intend to use on the next revision.
One of the features I like about the AVR XMEGA series of MCUs is having a ton of peripherals is the design freedom you get - most digital blocks of pins have a couple of USARTs, SPI, and four to six channels of PWM output.
Compared to the older MEGA series chips, where you were lucky to get two USARTs and had no choice about where they were, it's much easier to allocate pins to where you need them close to other components.
There's other details they've really thought through from the code side as well. For many uses you'll want to just turn particular pins on and off, the way I tended to do this on the MEGA series was something like:
PORTA |= (1 << PORTA3);
which works, and it's okay for readability. (Aside: I could replace (1 << PORTA3) with _BV(PORTA3) or a bunch of other methods, I'm not saying that's the only way to do it.). The compiler can optimize this where it's in IO space down to a single instruction. What happens when you want to raise a couple of lines at once tho?
PORTA |= (1 << PORTA3) | (1 << PORTA4);
The compiler can't reduce that to a single instruction (CBI and SBI only accept a single bit to clear/set), instead it has to do a read-modify-write cycle. On the XMEGAs however you have bitmask ranges on ports. Setting two bits on PORTA can be done by:
PORTA.OUTSET = PIN3_bm | PIN4_bm;
This is just a write to IO space, not a read-modify-write, and the chip takes care of applying the appropriate changes. (PINn_bm macros are new and replace the old PORTxn macros. They more correctly refer to the pin within any port, which technically was just as true of the PORTxn macros, just not so obvious.) The XMEGA line also includes some other nifty features, which the line above hints at: ports and peripherals are structs from a base, instead of random named addresses.
This means considerably less rewrite for different chips (you can either macro away the PORTA chunk, or pass it into functions as a pointer) and better code reuse. For example, on the old MEGAs if we were setting up a pin as an output and then toggling it once a second (ie, blinky LEDs!), we'd do something like:
#include
#include#define LED_DDR DDRA
#define LED_PORT PORTA
#define LED_PIN (1 << PORTA2)LED_DDR |= LED_PIN; /* pin is output */
LED_PORT &= ~(LED_PIN); /* off by default */
while (1) {
LED_PORT |= LED_PIN; /* LED on, assuming pin to anode */
_delay_ms(1000);
LED_PORT &= ~(LED_PIN); /* LED off */
_delay_ms(1000);
}
It does the job. It blinks the LEDs. On the XMEGA you can do this a bit differently:
#include
#include#define LED_PORT PORTA
#define LED_PIN PIN2_bmLED_PORT.DIRSET = LED_PIN; /* pin is output */
LED_PORT.OUTCLR = LED_PIN; /* make it off by default */while (1) {
LED_PORT.OUTTGL = LED_PIN; /* toggle it! */
_delay_ms(1000);
}
This has exactly the same result, 1 second on then 1 second off looped. But since the XMEGA has an explicit pin toggling IO register, we can shorten it to using that. Note that we're also more clearly setting and using features on the port (setting current levels, direction, toggling), rather than ungrouped assignments. Our LED_PORT define is how we access all the features of the port, not just setting state.