Skip to content
Snippets Groups Projects
Select Git revision
  • 664c739e89d49b7d2c63fc78a6b45222749bd247
  • main default protected
  • revert-9d4bb3d3
3 results

bin

Programming

The Xmega doesn't work right out of the box. It needs some updated avr-gcc and libusb installs:

On linux, I needed to install libusb-dev: sudo apt-get install libusb-dev

On osx, I needed to install updated avr-gcc and avrdude:

brew tap osx-cross/avr

brew install avr-gcc

brew install avrdude

In my case, I also had to overwrite existing links: (Be careful with this if you care about your avr-gcc/avrdude install… Mine was pretty broken to begin with so I had no problem replacing them.)

brew link --overwrite avrdude

brew link --overwrite avr-gcc

brew link --overwrite avr-binutils

Then I got the usbdev_open() error that Sam also encountered on OS X. I followed the same fix detailed on this page and then restarted my computer… and it worked!

Clocks

I should be able to setup a 48MHz clock using the internal PLL like so:

OSC.PLLCTRL = OSC_PLLFAC4_bm | OSC_PLLFAC3_bm; // 2 MHz * 24 = 48 MHz
OSC.CTRL = OSC_PLLEN_bm; // enable PLL
while (!(OSC.STATUS & OSC_PLLRDY_bm)); // wait for PLL to be ready
CCP = CCP_IOREG_gc; // enable protected register change
CLK.CTRL = CLK_SCLKSEL_PLL_gc; // switch to PLL

but this seems to go almost 2x too fast. To say this more precisely, I'm guessing the delay is using the 32MHz F_CPU constant and not the updated 48MHz (which would mean it's precisely 48/32 or 1.5x too fast). I can check the actual speed the CPU is running at by having the clock output on PC/D/E7 and using a scope to probe it:

PORTCFG.CLKEVOUT = PORTCFG_CLKOUT_PD7_gc;

For the time being, I'm just going to use the internal 32MHz oscillator like so:

OSC.CTRL = OSC_RC32MEN_bm; // enable 32MHz clock
while (!(OSC.STATUS & OSC_RC32MRDY_bm)); // wait for clock to be ready
CCP = CCP_IOREG_gc; // enable protected register change
CLK.CTRL = CLK_SCLKSEL_RC32M_gc; // switch to 32MHz clock

USART

bare minimum:

#include <stdio.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define serialPort PORTC
#define txPin PIN3_bm
#define rxPin PIN2_bm
#define ledPort PORTC
#define ledPin PIN1_bm

int main(void) {
    // set up clock
    OSC.CTRL = OSC_RC32MEN_bm; // enable 32MHz clock
    while (!(OSC.STATUS & OSC_RC32MRDY_bm)); // wait for clock to be ready
    CCP = CCP_IOREG_gc; // enable protected register change
    CLK.CTRL = CLK_SCLKSEL_RC32M_gc; // switch to 32MHz clock

    ledPort.DIRSET = ledPin;

    serialPort.OUTSET = txPin;
    serialPort.DIRSET = txPin;
    serialPort.DIRCLR = rxPin;

    //set baudrate and frame format
    // 115200 with -0.1% error
    uint16_t bsel = 131;
    USARTC0.BAUDCTRLA = ((uint8_t)bsel); // lower 8 bits of 12 bit baud
    USARTC0.BAUDCTRLB = (-3 << USART_BSCALE0_bp)|(bsel >> 8); // upper 4 bits of 12 bit baud

    //set mode of operation
    // by default CMODE is asynchronoous USART
    USARTC0.CTRLC = ((uint8_t) USART_CHSIZE_8BIT_gc) | (USART_PMODE_DISABLED_gc);

    //enable tx or rx
    USARTC0.CTRLB |= USART_TXEN_bm;

    while (1) {
        ledPort.OUT ^= ledPin;
        USARTC0.STATUS |= USART_DREIF_bm;
        USARTC0.DATA = 'h';
        while (!(USARTC0.STATUS&USART_TXCIF_bm)) { }
        USARTC0.STATUS |= USART_DREIF_bm;
        USARTC0.DATA = 'i';
        while (!(USARTC0.STATUS&USART_TXCIF_bm)) { }
        USARTC0.STATUS |= USART_DREIF_bm;
        USARTC0.DATA = '\n';
        while (!(USARTC0.STATUS&USART_TXCIF_bm)) { }
        _delay_ms(100);
    }
}

interrupt driven sending and receiving example

Real Time Counter

Add this in main:

//setup real time counter
CLK.RTCCTRL	= CLK_RTCSRC_RCOSC_gc|CLK_RTCEN_bm;
RTC.CTRL |= RTC_PRESCALER_DIV1_gc;

Here, we're setting up the RTC with no frequency division so it will run at 1.024kHz.

and then do something like this in the while loop:

while (1) {
	if (RTC.CNT - last_tx_rtc > 100){
		writeToBuffer(&output_buffer[0],"count=%d\n", counter++); 
		send_packet(output_buffer,maxBufferSize); 
		last_tx_rtc = RTC.CNT;
	}
}

this will printout once every ~100ms.

16-bit Timer/Counter (Pulse Generation)

Configuring the timer-counter to generate PWM is relatively straighforward:

// setup pwm
TCC0.PER = 0x0400; //set up 1024 resolution
TCC0.CTRLB |= TC_WGMODE_SS_gc; //single slope
TCC0.CTRLB |= TC0_CCBEN_bm; //enable compare channel on OC0B
TCC0.CTRLA |= TC_CLKSEL_DIV2_gc;  //set clock divider to /2 (16 KHz)
set_pwm(512) // 50% duty

and I just copied Sam's helpful set_pwm() routine to update the duty cycle:

void set_pwm(uint16_t duty){
	TCC0.CCBBUF = duty; //set compare value
	do {} while(TCC0.INTFLAGS && TC0_OVFIF_bm == 0 );  //wait
	TCC0.INTFLAGS = TC0_OVFIF_bm;
}

This waits to perform the update at the start of the pwm period (to ensure there are no glitches arising form changing the capture/compare value mid-period).

AWEX (Advanced Waveform Extension)

This feature lets me take a PWM signal generated from TCC0 and map it onto my charge and discharge pins with deadtime insertion and fault protection.

Unfortunately I misunderstood how this perhipheral works and I ended up needing to use the transmit pin to generate the require waveform. This means I'll have to move my transmit (and probably receive) pins… likely to PE2 and PE3 (or PD2 and PD3 since it seems like I may have to rewire VOUT to a proper analog pin anyway).

To set the deadtime between the falling edge of the high side and the rising edge of the low side, we use the DTBOTH parameter of the AWEX peripheral. The amount of time is measured in clock cycles:

AWEXC.DTBOTH = 4; // set deadtime (measured in clock cyles)

4 clock cycles —> ~119ns (4 * 1 / (32MHz) = 125ns)

32 clock cycles —> ~953ns (32 * 1 / (32MHz) = 1us)

128 clock cycles —> ~3.81us (128 * 1 / (32MHz) = 4us)

Pin Remapping

This actually saved me! Little did I know XMega lets you remap some of the pins within a port. I was able to move the timer/counters on Port C from my serial pins to, the place where I actually want them, my charge and discharge pins. Now I can communicate via USART while using the AWEX to generate my charge and discharge signals… or I can now forgo AWEX entirely and just configure the Timer/Counters myself.

// remap OCOC and OCOD to our charge and discharge pins
chargePort.REMAP |= PORT_TC0D_bm | PORT_TC0C_bm;

ADC (Analog to Digital)

I got this working but it turns out that the XMega is a little weird with its ADC. It can't really do a standard 0 to Vcc single-ended analog reading. Instead, the maximum reference voltage (either external or internal) needs to be Vcc-0.6 (or 2.7V, in my case). Additionally, there's a strange ADC offset when using unsigned mode. Apparently this is to allow you to measure zero-crossings with a single-ended, unsigned measurement… but seems like more of a headache than a feature.

I applied a 0->500mV sinusoid (generated with my scope's AFG). Clearly 0V doesn't correspond to a 0 ADC reading, but rather to something around 200.

According to the ADC application note, I need to manually measure the offset by grounding the ADC pin and taking a number of measurements and then subtract this value from all future measurements.

Next step is to test the maximum frequency I can get reasonable measurements at.