Alright, after much frustration and self doubt I've figured out what was wrong. I ended up writing my own initialization function:
spi_init(void){
unsigned char a;
//setup pins
#define MISO PB3
#define MOSI PB2
#define SCK PB1
#define SS PB0
DDRB |= (1<<MOSI)|(1<<SCK)|(1<<SS);
DDRB &= ~(1<<MISO);
PORTB |= (1<<SS);//set SS high
PORTB &= ~(1<<MISO);//turn off internal pullup for MISO
PORTB &= ~(1<<MOSI);//pull MOSI low
PORTB &= ~(1<<SCK);//pull SCK low
//setup registers
SPSR = 0b00000000;//see datasheet
SPCR = 0b01010000;//see datasheet
//clear out any junk in SPI buffer
a = SPSR;
}
And the corresponding function for sending+receiving data:
unsigned char spi_transfer(unsigned char data){
//transmits data over SPI lines and returns the response
SPDR = data;
while(!(SPSR & (1<<SPIF)));//block untill data transmission/reception completes
return SPDR;
}
It turns out that setting up the software side of the interface wasn't my problem... it was the hardware. The axon_schematic.pdf labels the ISP programming headers (the SPI pins are also the programmer pins) as follows:
Left Right
VTG MISO
RST MOSI
GND SCK
However, the actual layout of the programming pins is:
Left Right
MISO VTG
SCK MOSI
RST GND

Works perfect for me now. Hope this helps anyone else who is having issues.