attiny2313 SCPR register?

Hi,

I was just finally getting around to coding some code for this microcontroller in C, but was having a hard time getting it properly initialized.

I had thought that this:

//setup SPCR register

//1 1 0 1 0 0 0 0

//int spi msb master leading rising freq to work at

//en en first device clock edge (f_osc/4)

SPCR = 0xD0;

Would properly setup the device as a master SPI.

I included <avr/io.h>, but it is appearing that the attiny2313 doesn’t have a SCPR register defined in its .h file that gets included from io.h when compiling with avr-gcc for the attiny2313.

Any ideas how to do this then?

Well looking a little further around it appears that the way to do SPI on a attiny2313 is via USI.

It seems like you can do a 3 wire implementation according to AppNote AVR319.

The source file they provide is intended for a atmega series microcontroller. I attempted to adapt it to compile with avr-libc and with attiny2313 registers.

It appears to still not be working though. How do you communicate via 3 wires when SPI normally expects 6? Makes no sense to me.

Here is what i’ve modified the source file to be so far:

// This file has been prepared for Doxygen automatic documentation generation.

/*! \file ********************************************************************
*
* Atmel Corporation
*
* \li File:               spi_via_usi_driver.c
* \li Compiler:           IAR EWAAVR 3.10c
* \li Support mail:       avr@atmel.com
*
* \li Supported devices:  All devices with Universal Serial Interface (USI)
*                         capabilities can be used.
*                         The example is written for ATmega169.
*
* \li AppNote:            AVR319 - Using the USI module for SPI communication.
*
* \li Description:        Example on how to use the USI module for communicating
*                         with SPI compatible devices. The functions and variables
*                         prefixed 'spiX_' can be renamed to be able to use several
*                         spi drivers (using different interfaces) with similar names.
*                         Some basic SPI knowledge is assumed.
*
*                         $Revision: 1.4 $
*                         $Date: Monday, September 13, 2004 12:08:54 UTC $
****************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>



/* USI port and pin definitions.
 */
#define USI_OUT_REG	PORTB	//!< USI port output register.
#define USI_IN_REG	PINB	//!< USI port input register.
#define USI_DIR_REG	DDRB	//!< USI port direction register.
#define USI_CLOCK_PIN	PB7	//!< USI clock I/O pin.
#define USI_DATAIN_PIN	PB5	//!< USI data input pin.
#define USI_DATAOUT_PIN	PB6	//!< USI data output pin.




/*  Speed configuration:
 *  Bits per second = CPUSPEED / PRESCALER / (COMPAREVALUE+1) / 2.
 *  Maximum = CPUSPEED / 64.
 */
#define TC0_PRESCALER_VALUE 256	//!< Must be 1, 8, 64, 256 or 1024.
#define TC0_COMPARE_VALUE   1	//!< Must be 0 to 255. Minimum 31 with prescaler CLK/1.




/*  Prescaler value converted to bit settings.
 */
#if TC0_PRESCALER_VALUE == 1
	#define TC0_PS_SETTING (1<<CS00)
#elif TC0_PRESCALER_VALUE == 8
	#define TC0_PS_SETTING (1<<CS01)
#elif TC0_PRESCALER_VALUE == 64
	#define TC0_PS_SETTING (1<<CS01)|(1<<CS00)
#elif TC0_PRESCALER_VALUE == 256
	#define TC0_PS_SETTING (1<<CS02)
#elif TC0_PRESCALER_VALUE == 1024
	#define TC0_PS_SETTING (1<<CS02)|(1<<CS00)
#else
	#error Invalid T/C0 prescaler setting.
#endif



/*! \brief  Data input register buffer.
 *
 *  Incoming bytes are stored in this byte until the next transfer is complete.
 *  This byte can be used the same way as the SPI data register in the native
 *  SPI module, which means that the byte must be read before the next transfer
 *  completes and overwrites the current value.
 */
unsigned char storedUSIDR;



/*! \brief  Driver status bit structure.
 *
 *  This struct contains status flags for the driver.
 *  The flags have the same meaning as the corresponding status flags
 *  for the native SPI module. The flags should not be changed by the user.
 *  The driver takes care of updating the flags when required.
 */
struct usidriverStatus_t {
	unsigned char masterMode : 1;       //!< True if in master mode.
	unsigned char transferComplete : 1; //!< True when transfer completed.
	unsigned char writeCollision : 1;   //!< True if put attempted during transfer.
};

volatile struct usidriverStatus_t spiX_status; //!< The driver status bits.



/*! \brief  Timer/Counter 0 Compare Match Interrupt handler.
 *
 *  This interrupt handler is only enabled when transferring data
 *  in master mode. It toggles the USI clock pin, i.e. two interrupts
 *  results in one clock period on the clock pin and for the USI counter.
 */
ISR(TIMER0_COMP_vect)
{
	USICR |= (1<<USITC);	// Toggle clock output pin.
}



/*! \brief  USI Timer Overflow Interrupt handler.
 *
 *  This handler disables the compare match interrupt if in master mode.
 *  When the USI counter overflows, a byte has been transferred, and we
 *  have to stop the timer tick.
 *  For all modes the USIDR contents are stored and flags are updated.
 */
ISR(USI_OVF_vect)
{
	// Master must now disable the compare match interrupt
	// to prevent more USI counter clocks.
	if( spiX_status.masterMode == 1 ) {
		TIMSK &= ~(1<<OCIE0A);
	}
	
	// Update flags and clear USI counter
	USISR = (1<<USIOIF);
	spiX_status.transferComplete = 1;

	// Copy USIDR to buffer to prevent overwrite on next transfer.
	storedUSIDR = USIDR;
}



/*! \brief  Initialize USI as SPI master.
 *
 *  This function sets up all pin directions and module configurations.
 *  Use this function initially or when changing from slave to master mode.
 *  Note that the stored USIDR value is cleared.
 *
 *  \param spi_mode  Required SPI mode, must be 0 or 1.
 */
void spiX_initmaster( char spi_mode )
{
	// Configure port directions.
	USI_DIR_REG |= (1<<USI_DATAOUT_PIN) | (1<<USI_CLOCK_PIN); // Outputs.
	USI_DIR_REG &= ~(1<<USI_DATAIN_PIN);                      // Inputs.
	USI_OUT_REG |= (1<<USI_DATAIN_PIN);                       // Pull-ups.
	
	// Configure USI to 3-wire master mode with overflow interrupt.
	USICR = (1<<USIOIE) | (1<<USIWM0) |
	        (1<<USICS1) | (spi_mode<<USICS0) |
	        (1<<USICLK);

	// Enable 'Clear Timer on Compare match' and init prescaler.
	TCCR0A = (1<<WGM01) | TC0_PS_SETTING;
	
	// Init Output Compare Register.
	OCR0A = TC0_COMPARE_VALUE;
	
	// Init driver status register.
	spiX_status.masterMode       = 1;
	spiX_status.transferComplete = 0;
	spiX_status.writeCollision   = 0;
	
	storedUSIDR = 0;
}



/*! \brief  Initialize USI as SPI slave.
 *
 *  This function sets up all pin directions and module configurations.
 *  Use this function initially or when changing from master to slave mode.
 *  Note that the stored USIDR value is cleared.
 *
 *  \param spi_mode  Required SPI mode, must be 0 or 1.
 */
void spiX_initslave( char spi_mode )
{
	// Configure port directions.
	USI_DIR_REG |= (1<<USI_DATAOUT_PIN);                      // Outputs.
	USI_DIR_REG &= ~(1<<USI_DATAIN_PIN) | (1<<USI_CLOCK_PIN); // Inputs.
	USI_OUT_REG |= (1<<USI_DATAIN_PIN) | (1<<USI_CLOCK_PIN);  // Pull-ups.
	
	// Configure USI to 3-wire slave mode with overflow interrupt.
	USICR = (1<<USIOIE) | (1<<USIWM0) |
	        (1<<USICS1) | (spi_mode<<USICS0);
	
	// Init driver status register.
	spiX_status.masterMode       = 0;
	spiX_status.transferComplete = 0;
	spiX_status.writeCollision   = 0;
	
	storedUSIDR = 0;
}



/*! \brief  Put one byte on bus.
 *
 *  Use this function like you would write to the SPDR register in the native SPI module.
 *  Calling this function in master mode starts a transfer, while in slave mode, a
 *  byte will be prepared for the next transfer initiated by the master device.
 *  If a transfer is in progress, this function will set the write collision flag
 *  and return without altering the data registers.
 *
 *  \returns  0 if a write collision occurred, 1 otherwise.
 */
char spiX_put( unsigned char val )
{
	// Check if transmission in progress,
	// i.e. USI counter unequal to zero.
	if( (USISR & 0x0F) != 0 ) {
		// Indicate write collision and return.
		spiX_status.writeCollision = 1;
		return;
	}
	
	// Reinit flags.
	spiX_status.transferComplete = 0;
	spiX_status.writeCollision = 0;

	// Put data in USI data register.
	USIDR = val;
	
	// Master should now enable compare match interrupts.
	if( spiX_status.masterMode == 1 ) {
		TIFR |= (1<<OCF0A);   // Clear compare match flag.
		TIMSK |= (1<<OCIE0A); // Enable compare match interrupt.
	}

	if( spiX_status.writeCollision == 0 ) return 1;
	return 0;
}



/*! \brief  Get one byte from bus.
 *
 *  This function only returns the previous stored USIDR value.
 *  The transfer complete flag is not checked. Use this function
 *  like you would read from the SPDR register in the native SPI module.
 */
unsigned char spiX_get()
{
	return storedUSIDR;
}



/*! \brief  Wait for transfer to complete.
 *
 *  This function waits until the transfer complete flag is set.
 *  Use this function like you would wait for the native SPI interrupt flag.
 */
void spiX_wait()
{
	do {} while( spiX_status.transferComplete == 0 );
}



// end of file

SPI has four connections, not six. If you need a chip select with the USI you could use another output pin.

Leon

MOSI, MISO, SCLK, CSN, IRQ, CE

Isn’t that 6?

So only MOSI, MISO, SCLK, and what is the 4th necessary one then?

Also does that code look sane? Particularly the interrupt handler routine. When I obtained it from AVR319, it was intended to be used with IAR. I’m using avr-libc, so I tried to translate it as best as I could.

Sorry must have missed the end of your response. I think that the device I was trying to program (mirf v2) was looking for chip select to be used as well, but I will need to double check still.

Yes, the Mirf V2 needs CS. You need the other connections, of course, but they are not SPI.

Leon

I’m running into this same issue myself, and trying to work it out. There’s a couple of issues with that driver code you posted - the first one is that there’s no declaration for TIMER0_COMP_vect in the io file. Plenty of stuff for timer 1 though - not sure why that is, off the top of my head. Also, what is USI_OVF_vect? The documentation says that that triggers when a byte has been transferred… did you mean USI_OVERFLOW_vect ?

Anyway, I’m gonna poke at this some more and I’ll let you know if I come up with something.

Halfelf:
I’m running into this same issue myself, and trying to work it out. There’s a couple of issues with that driver code you posted - the first one is that there’s no declaration for TIMER0_COMP_vect in the io file. Plenty of stuff for timer 1 though - not sure why that is, off the top of my head. Also, what is USI_OVF_vect? The documentation says that that triggers when a byte has been transferred… did you mean USI_OVERFLOW_vect ?

Anyway, I’m gonna poke at this some more and I’ll let you know if I come up with something.

Those do both appear to be typos (and possibly why my interrupts aren't working)

Looking over http://www.nongnu.org/avr-libc/user-man … rupts.html

USI_OVF_vect isn’t available on the 2313, only USI_OVERFLOW_vect

Also you’re right, TIMER0_COMP_vect isn’t avail on the 2313 either. It looks like it instead has TIMER0_COMPA_vect and TIMER0_COMPB_vect. Does anything special need to be done to use these instead?

I was messing with the same stuff and found no pre-fab code around.

it’s not much, but it should be enough to work with:

/*
simple USI implementation for WINAVR with interrupts
for ATTiny2313, basid on Atmel Application Note 319

Eric Toering - 25-3-2008
*/

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

unsigned char output;


void blink ( void) {

		PORTB |= 0x01;						// turn LED on

		_delay_ms(100);						// wait 100 mS
	
		PORTB &= FE;						// and off again

		_delay_ms(100);

}



int main(void){

	PORTB = 0x00;							// reset PORTB
	DDRB |= 0x01;							// make PB0 output (for LED)

	TCCR0A = 0;								// Timer0 settings
	TCCR0B = 0b00000001;					// Turn on Timer0 with no prescaling: every 1/(Fosc/256) Sec overflow 
											// So with 12 Mhz: 12000000/256 * 2 is 23437 bits/S

	TIMSK = 0b00000000;						// Timer interrupts

	USIDR = 0x00;							// clear USI register

	DDRB |= (1<<PB6) | (1<<PB7); 			// Outputs.
	DDRB &= ~(1<<PB5);                      // Inputs.
	PORTB |= (1<<PB5);                       // Pull-ups. 

	USICR = (1<<USIOIE) | (1<<USIWM0) | 	// enable usi three wire master mode, overflow interrupt
           (1<<USICS1) | (0<<USICS0) | 		// setup on rising edge, using clock strobe
           (1<<USICLK);

	sei();									// turn on interrupts

	while(1){

   		if( (USISR & 0x0F) != 0 ) { 		// test if no transmission is blocking
      		blink(); 						// otherwire blink led
   		}


		USIDR = 0x55;						// <---- this is the data that is sent 
		
		TCNT0 = 0x00;						// reset Timer0
		TIMSK |= (1<<TOIE0);				// turn on Timer0 interrupt
		
		_delay_ms(45);						// wait 45 mS and do it all again
				
	}

}

ISR(TIMER0_OVF_vect){
	
	USICR |= (1<<USITC);   					// Toggle clock output pin

}

ISR(USI_OVERFLOW_vect){

	TIMSK &= ~(1<<TOIE0);					// turn off Timer0 interrupt
	USISR = (1<<USIOIF); 					// clear USI transmission flag

	output = USIDR;							// <----- this is the data that is received
	
}