Skip to content

3. API Changes for XMega

Bob Frazier edited this page Feb 27, 2019 · 34 revisions

A few APIs have changed, out of necessity, to support the XMega.

void pinMode(pin, mode);   (enhanced)

  pin is the I/O pin number, 0 through 'n'

  mode is the pin mode
  example: INPUT, OUTPUT, INPUT_PULLUP, etc.

In the original Arduino IDE, the pinMode() function could only assign INPUT, OUTPUT, or INPUT_PULLUP. To facilitate the enhanced modes of the XMega I/O pins, you can assign one of the following for 'mode':

INPUT - totem pole, input (no pull up/down)
OUTPUT - totem pole, output
INPUT_BUS_KEEPER - weak pull up/down to maintain state when switched to or in input mode
INPUT_PULLUP - pull up resistor on input
INPUT_PULLDOWN - pull down resistor on input
OUTPUT_OR - output open 'P' drain/collector, no pull down
OUTPUT_AND - output open 'N' drain/collector, no pull up
INPUT_OR_PULLDOWN - input with open 'P' drain/collector and pulldown
INPUT_AND_PULLUP - input with open 'N' drain/collector and pullup
OUTPUT_OR_PULLDOWN - output with open 'P' drain/collector and pulldown
OUTPUT_AND_PULLUP - output with open 'N' drain/collector and pullup

The following values can be 'or'd with 'mode' to change the 'sense' behavior. this is particularly important for interrupt pins, and can also affect how input pins behave. For normal I/O pin assignment, use 'INPUT_SENSE_DEFAULT'. For analog inputs, use 'INPUT_SENSE_DISABLED'.

INPUT_SENSE_DEFAULT - input sense default - currently 'BOTH' (value = 0)
INPUT_SENSE_RISING - rising level change
INPUT_SENSE_FALLING - falling level change
INPUT_SENSE_BOTH - both rising AND falling level change - needed for normal INPUT
INPUT_SENSE_LEVEL - high level (or low if 'inverted' I/O)
INPUT_SENSE_DISABLED - buffered input disabled (use for analog inputs only)
INPUT_OUTPUT_INVERT - set this bit for 'inverted' I/O

NOTE: 'inverted' I/O is 're-inverted' by digitalRead and digitalWrite for consistency. The effect of the 'INVERT' bit is particularly important for LEVEL sense.



void attachInterrupt(port, callback, mode);   (modified)

  port is the I/O port and int number
  example: PORTC_INT0, PORTD_INT0, PORTR_INT1, etc.

  callback is a pointer to a user callback function

  mode (new) is a bit-flag indicating the following:
    trigger mode - LOW, HIGH, RISING, FALLING, CHANGE
    interrupt pin - INT_MODE_PIN_DEFAULT, INT_MODE_PIN0, etc.
    priority - INT_MODE_PRI_DEFAULT, INT_MODE_PRI_HIGH, etc.

typical usage:
    attachInterrupt(PORTD_INT0,
                          my_callback,
                          RISING
                          | INT_MODE_PIN_DEFAULT
                          | INT_MODE_PRI_DEFAULT);

The port (formerly 'interrupt number') is passed to 'detachInterrupt' and detaches ALL interrupts for that particular port and interrupt combination. Some CPUs have 2 interrupts per port, some only one. The first will be labeled 'INT0', and subsequent interrupts 'INT1', etc. Each port+interrupt combination can only have a single callback, so you should assign the callback and all of the interrupt pins at the same time. It will be up to the callback functino to determine which pins is responsible for the interrupt.

There is a small exception with hardware flow control, which manages its interrupts separately, in conjuction with 'attachInterrupt()' and 'detachInterrupt()' calls. It is possible to assign an interrupt on the same port as the hardware flow control pins, without interfering. However, its priority will not be lowered from 'HIGH'.


uint8_t readCalibrationData(iIndex);   (new)

  iIndex is the index within the 'calibration data' to be read

This function reads the 'calibration data', which is assigned by the manufacturer to contain the calibration data necessary for certain functions. This also includes CPU identification information that can be used to uniquely identify the processor. One practical use is found in setting up the A:D converter, by reading the calibration data and assigning it to the 'CAL' register for the A:D converter during setup.



void adc_setup(void);   (new)

This function should be called whenever you exit from 'sleep' mode to re-assign the correct values to the A:D converter. On the XMega, sleep mode pretty much clears the slate for all peripherals. Serial ports and other peripherals are automatically initialized by 'Start' and similar member functions. But for the A:D converter, there was no official way to reset it. So this function was added.

typical usage:

// interrupt callback for 'wake-up'
void wake_up(void)
{
  sleep_disable(); // must do this
  detachInterrupt(PORTC_INT0);
}

  ...

  // serial port shutdown
  Serial.end();
  Serial2.end();
  set_sleep_mode(SLEEP_MODE_EXT_STANDBY);
  // wake up with LOW level on PORTC pin 2
  attachInterrupt(PORTC_INT0, wake_up, LOW);
  // disable interrupts - see avr/sleep.h
  cli();
  power_all_disable();
  sleep_enable(); // only if ints are off
  // optionally disable BOD while sleeping
  #ifdef sleep_bod_disable
  sleep_bod_disable();
  #endif

  sei(); // enable interrupts
  sleep_cpu();

  // when I awake, I'll be here
  sleep_disable(); // make sure
  sei(); // ints on (make sure)
  power_all_enable(); // see avr/power.h

  ...

  adc_setup(); // re-init A:D



void analogReference(mode);   (enhanced)


By default, the analog reference is assigned to 1/2 VCC, with a gain of 2, such that the reference is effectively "rail to rail" on the supply voltage, just like the ATmega Arduino.

With this function, you can assign a different reference voltage for 'full scale' on analog read. The analog reference assigned needs to be one of the following constants:
enum _analogReference_  
{  
  analogReference_INT1V       // use internal 1V reference  
  analogReference_PORTA0      // PORT A pin 0 is the AREF  
  analogReference_PORTB0      // PORT B pin 0 is the AREF  
  analogReference_VCC         // VCC / 10, actually  

  analogReference_VCCDIV2     // VCC / 2 - using THIS forces gain to 1/2  
};  



## int analogReadDeltaWithGain(pin,negpin,gain);   (new)

This function allows you to do a 'differential input' between two analog pins. The available pins for a 'differential read with gain' differ from CPU to CPU. Typically, you'll be able to specify A0 through A7 for the 'pin' value, and A4 through A7 for the 'negpin' value.

pin
  This is the analog pin on the 'positive' side of the comparison

negpin
  This is the analog pin on the 'negative' side of the comparison

gain
  This is the gain factor (one of the ADC_CH_GAIN_xxx constants), such as "ADC_CH_GAIN_1X_gc".

The 'negpin' may also be "ANALOG_READ_DELTA_USE_GND", the same as it is for a normal 'analogRead()', thus allowing you to read a voltage with respect to ground, but using a different gain factor.

usage:
  int x = analogReadDeltaWithGain(A0, A4, ADC_CH_GAIN_1X_gc);



void wait_for_interrupt(void);   (new)

Uses 'IDLE' sleep mode to pause CPU and NVRAM until an interrupt occurs. VERY useful for cutting down on current consumption during wait states. Granularity will be about the same as the system clock. It is especially useful if you wait until a specific 'millis()' value to perform some timed task, as the millis clock will update via an interrupt (which will then trigger the wakeup from sleep mode).

A simple test showed that using a 'delay 2msec' loop involving 'wait_for_interrupt()' within another loop that performs a task every 1000 msecs, ~990 msecs were spent in 'IDLE' state. So instead of 'delay(2)' at the end of the 'loop()' function, using similar code with 'wait_for_interrupt()' stayed asleep 99% of the time. In theory, this can be a significant reduction in actual CPU usage (and current consumption). As an example, 'idle' current (in the 'sleep' state) is nominally 3.8ma for the 64D4, and 'active' current is ~10ma. Assuming that 99% of the time it runs at 3.8ma, the average would then be ~4ma vs ~10ma, a reduction of 60 percent in actual current consumption.


void low_power_delay(msec);   (new)

Similar to 'delay' API function, except that it uses 'wait_for_interrupt' internally to minimize power while waiting for the specified number of milliseconds 'msec'.


void dac_setup(void);   (new)

Configure the DAC (for xmega CPUs that support it). Call one time within the 'setup()' function if you want the functionality of a DAC.

The 'e5' series has a single DAC with 2 channels. The 'a4' and 'a4u' series' have two DACs with 2 channels each. The 'd4' series has no DAC, and for the 'd4' series, this function is not supported. Other CPUs vary depending on their architecture.

The default implementation uses the internal 1V reference for the output on the DAC. So a value of 4095 should generate a 'full scale' output voltage of 1.00V. To change this behavior, you can modify DACA.CTRLC register (and if available, the DACB.CTRLC register) to use a different reference voltage.

For more information, see the appropriate xmega CPU documentation, and 'A' and 'E' series documentation, regarding the DAC.


void analogWriteDAC(pin, val);   (new)

  pin is the I/O pin number, such as DACA_CH0_PIN

  val is the 12-bit analog value, 0-4095

Writes a 12 bit unsigned integer value to the specified DAC channel's output pin. This is similar to analogWrite() except that it uses the DAC to generate the output, with a value range of 0-4095.

NOTE: values less than 64 and greater than 4031 may become grossly inaccurate and should not be relied upon. See the CPU documentation for the various xmega processors as to why this is. In short, when you get close to the limits of the A:D output, particularly GND and Vdd, the output voltage becomes non-linear.

TODO: additional documentation