USI data/buffer register.

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

I'm using an attiny44 as an SPI slave. The manual says that the timing becomes less critical if I use the buffer register instead of the "data register".

However, things completely don't work if I simply change the code to use the buffer register. Any ideas what I'm doing wrong?

ISR (USI_OVF_vect)
{
  unsigned char d;
  static unsigned char addr;

  USISR = (1<<USIOIF);
  d = USIDR;
  //d = USIBR;
  if(!pcnt) {
    if ((d & 0xfe) == myaddr) forme = 1;
    else                      forme = 0;
    read = d & 1;

.....

  pcnt++;
}

Debugging is difficult because the tiny doesn't have any output possibilities besides the SPI communication and I don't have an in circuit debugger.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have never used the USI as an SPI slave.
As a Master, you have several methods:

Toggle at full speed.

unsigned char spi(unsigned char val)
{
    USIDR = val;
    USISR = (1<<USIOIF);
    do {
        USICR = (1<<USIWM0)|(1<<USICS1)|(1<<USICLK)|(1<<USITC);
    } while ((USISR & (1<<USIOIF)) == 0);
    return USIDR;
}

Or clock out at variable speed via Timer0

unsigned char spi(unsigned char val)
{
    USIDR = val;
    USISR = (1<<USIOIF);             // clear IRQ
    USICR = (1<<USIWM0)|(1<<USICS0); // start clock
    while ((USISR & (1<<USIOIF)) == 0); // send 8 bits
    USICR = (1<<USIWM0)|(0<<USICS0); // stop clock after
    return USIDR;
}

At a guess, a Slave would just be:

volatile uint8_t spi_in, spi_oout;
ISR (USI_OVF_vect)
{
  USISR = (1<<USIOIF);   // clear irq
  spi_in = USIDR;        // wot we have just received
  USIDR = spi_out;       // ready for next SPI
}

If you still have problems, I will have a go at implementing a Slave. You know that the USI never swaps MOSI/MISO pin directions like the Mega does.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello!
I try to reawake this thread since I have the same problem as described here. I am using an ATtiny1634 and according to the manual the USIBR should be the buffered data from USIDR. I can not read anything in the manual on exactly when the USIDR values are copied to USIBR but the intuitive answer would be on counter overflow.
However, when I try to use the value from USIBR instead of USIDR the value seem to be moved bitwise by one and a one valued bit has been introduced as the least significant bit.
This corresponds to the value I get when I try to sample on negative edgs instead of positive edges. In other words, If I try to sample on negative edges the incomming signal which is actually sent on positive edges I get the same value on both USIDR and USIBR.
Could it be some kind of bug such that USIBR always sample on negative edges independent of the sampling settings that obviously works for USIDR.

The reason I want to use USIBR is to be able to increase the baud rate so I do not need to be able to retrieve the value before next clock cycle.

My USI setup looks like this:

	// Give USI power
	_TURN_OFF( PRR, _BIT_NO(PRUSI) );
	
	// Clear registers
	USICR = 0;		
	
			
	// Setup as three-wire mode
	_TURN_ON( USICR, _BIT_NO( USIWM0 ) );
	
	// Setup as slave (external clock negative edge)
	_TURN_ON( USICR , _BIT_NO(USICS1) );			
	
	// Enable counter overflow interrupt (signals when a character is retrieved)
	_TURN_ON( USICR, _BIT_NO(USIOIE) );
	
	// Clear USIOIF flag (must be done explicitly)
	USISR = 0;
	_TURN_ON( USISR, _BIT_NO(USIOIF) );

My handling of incomming data looks like this:

	// Turn on interrupt			
	cli();
				
	// SS pin should be high so initialize it as that
	uint8_t lSSOn = 1;
	// Set overflow flag as null from start
	uint8_t lOVFFlag = 0;	
	// Initialize command state as idle (not handling any command)
	SPI_CMD_STATE_t lCmdState = SPI_CMD_STATE_IDLE;
	// Initialize command as unknown
	uint8_t lCommand = SPI_RXCMD_UNKNOWN;
	// Initialize counter for number of characters
	uint8_t lCharCnt = 0;
	// Initialize output byte
	uint8_t lOutputChar = SPI_DUMMY_CHARACTER;
	
	// Initialize rx buffer
	BUFFER_circular_buffer_t lRxBuffer;
	BUFFER_Clear(&lRxBuffer);
	// Initialize tx buffer
	BUFFER_circular_buffer_t lTxBuffer;
	BUFFER_Clear(&lTxBuffer);		
	
	// Loop as long as SS is on
	while ( lSSOn )
	{			
		
		// Wait for incomming data
		while (!lOVFFlag && lSSOn )
		{									
			// Get if overflow flag is on
			lOVFFlag = _IS_ON( USISR, _BIT_NO(USIOIF) );
			// Get if SS pin is still on
			lSSOn = !_IS_ON( MCU_SS_N_PIN, _BIT_NO(MCU_SS_N_NUM));			
		}		
	
		// If overflow
		if ( lOVFFlag )
		{
			
			// Get current character in register
			const uint8_t lCharRx = USIDR;
			
			// Set new character in register
			USIDR = lOutputChar;			
						
			/* */
			// Toggle led to show when a character has been handled
			PORT_SetLED(1);
									
			// clear overflow flag
			lOVFFlag = 0;
			_TURN_ON( USISR, _BIT_NO(USIOIF) );		

			

					
			// If this is the very first character received
			if ( lCharCnt == 0 )
			{				
				// Set current byte as command byte
				lCommand = lCharRx;
				// Identify the incomming command and chose appropriate actions and command state
				lCmdState = SPI_IdentifyCommand( lCommand, &lTxBuffer );
			}
			else
			{				
				// If in pause state
				if ( lCmdState == SPI_CMD_STATE_PAUSE )
				{
					// Disregard received data
											
					// Move into handle state
					lCmdState = SPI_CMD_STATE_HANDLE;
				}
				else if ( lCmdState == SPI_CMD_STATE_HANDLE )	// If in handle state
				{
					// Append to Rx buffer
					if ( BUFFER_Append( &lRxBuffer, lCharRx ) )
					{
						// Something went wrong (probably the buffer is full)
						lCmdState = SPI_CMD_STATE_ERROR;
					}					
				}							
			}
			
			// Insert byte to transfer in next turn if there is any
			if ( lTxBuffer.length > 0 )
			{
				lOutputChar = BUFFER_Pop( &lTxBuffer );
			}
			else
			{
				// Insert dummy byte
				lOutputChar = SPI_DUMMY_CHARACTER;
			}
			
			// Advance number of characters received
			if ((++lCharCnt) == UINT8_MAX)
			{
				// something is wrong. Too many characters
				// Set that ss is off
				lSSOn = 0;
				// Set that overflow is off
				lOVFFlag = 0;
				// Set error
				lCmdState = SPI_CMD_STATE_ERROR;
			}
								
			/* */
			// Toggle led to show when a character has been handled
			PORT_SetLED(0);
									
		}	// end of overflow section
		
	}	// end of transmission loop
		
				
	// If there was an error
	if ( lCmdState == SPI_CMD_STATE_ERROR )
	{				
		// Write SPI error
		ERROR_AddError(ERROR_STATE_SPI);
	} 
	
	// If there might be data to handle
	if ( lCmdState == SPI_CMD_STATE_HANDLE )
	{
		// Handle data if any 
		SPI_HandleData( lCommand, &lRxBuffer );
	}		
	
	// Set counter to 0 if it is not zero already
	USISR = 0;
	
	// Prep USIDR with dummy byte
	USIDR = SPI_DUMMY_CHARACTER;
	
	// Turn off interrupt
	sei();
	
	return;

The led is only toggled for debuging purposes

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Please read this.

[I did him a favour and edited some [code] tags in anyway as I was interested to read the code - moderator]

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

   // Turn on interrupt         
   cli();


Well the first that stands out for me is how wrong that comment is. If (as it appears - though you haven't shown global variables and ISRs) that this code is waiting for something in an interrupt to happen it's going to have a fairly long wait ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I presume that Joey is hinting at CODE tags.

If you post a minimal compilable program that shows your problem, someone might see if they can reproduce it.

Looking at my reply from 5 March 2012, you just read USIBR instead of USIDR. Untested.

I might even try it later. Just to see how fast (and reliably) I can drive a USI slave. A regular mega Master will always have gaps between SPI bytes. The USART_MSPI can send continuous SPI bytes.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Quote:

   // Turn on interrupt         
   cli();


Well the first that stands out for me is how wrong that comment is. If (as it appears - though you haven't shown global variables and ISRs) that this code is waiting for something in an interrupt to happen it's going to have a fairly long wait ;-)

Oh sorry. the code was right but the comment wrong :) I mean to turn off interrupts at start of cuntion and later turn on them again and end of function. Since I try to see how fast I am able to respond to the SPI communication I do not want any interrupts to disturb right now.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
I presume that Joey is hinting at CODE tags.

If you post a minimal compilable program that shows your problem, someone might see if they can reproduce it.

Looking at my reply from 5 March 2012, you just read USIBR instead of USIDR. Untested.

I might even try it later. Just to see how fast (and reliably) I can drive a USI slave. A regular mega Master will always have gaps between SPI bytes. The USART_MSPI can send continuous SPI bytes.

David.

Well, since it is only the SPI part that does not work the code below is basically the whole problem. Since it is SPI communication there is a chip select pin that triggers the "SPI_TransmissionStart()"-function.





static void SPI_TransmissionStart()
{	
	// Turn off interrupt			
	cli();
				
	// SS pin should be high so initialize it as that
	uint8_t lSSOn = 1;
	// Set overflow flag as null from start
	uint8_t lOVFFlag = 0;	
	// Initialize counter for number of characters
	uint8_t lCharCnt = 0;
	// Initialize output byte
	uint8_t lOutputChar = SPI_DUMMY_CHARACTER;	
	
	// Loop as long as SS is on
	while ( lSSOn )
	{			
		
		// Wait for incomming data
		while (!lOVFFlag && lSSOn )
		{									
			// Get if overflow flag is on
			lOVFFlag = _IS_ON( USISR, _BIT_NO(USIOIF) );
			// Get if SS pin is still on
			lSSOn = !_IS_ON( MCU_SS_N_PIN, _BIT_NO(MCU_SS_N_NUM));			
		}		

		// Get current character in register
		const uint8_t lCharRx = USIDR;
		/*// Get current character in register
		const uint8_t lCharRx = USIBR;*/
			
		// Set new character in register
		USIDR = lOutputChar;
			
		// If overflow
		if ( lOVFFlag )
		{
									

									
			// clear overflow flag
			lOVFFlag = 0;
			_TURN_ON( USISR, _BIT_NO(USIOIF) );												
							
			// Insert byte to transfer in next turn if there is any
			if ( lTxBuffer.length > 0 )
			{
				lOutputChar = BUFFER_Pop( &lTxBuffer );
			}
			else
			{
				// Insert dummy byte
				lOutputChar = SPI_DUMMY_CHARACTER;
			}
			
			// Advance number of characters received
			if ((++lCharCnt) == UINT8_MAX)
			{
				// something is wrong. Too many characters
				// Set that ss is off
				lSSOn = 0;
				// Set that overflow is off
				lOVFFlag = 0;
			}
								
									
		}	// end of overflow section
		
	}	// end of transmission loop
		
	
	// Set counter to 0 if it is not zero already
	USISR = 0;
	
	// Prep USIDR with dummy byte
	USIDR = SPI_DUMMY_CHARACTER;
	
	// Turn on interrupt
	sei();
	
	return;
}



uint8_t SPI_Init()
{	

	// Give USI power
	_TURN_OFF( PRR, _BIT_NO(PRUSI) );
	
	// Clear registers
	USICR = 0;		
	
			
	// Setup as three-wire mode
	_TURN_ON( USICR, _BIT_NO( USIWM0 ) );
	
	// Setup as slave (external clock positive edge)
	_TURN_ON( USICR , _BIT_NO(USICS1) );			
	
	// Enable counter overflow interrupt (signals when a character is retrieved)
	_TURN_ON( USICR, _BIT_NO(USIOIE) );
	
	// Clear USIOIF flag (must be done explicitly)
	USISR = 0;
	_TURN_ON( USISR, _BIT_NO(USIOIF) );
	
	
	// Return that everything is okay
	return 0;
}




The problem is that the retrieved value is not correct if using USIBR instead of USIDR.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I found a way to handle the problem but I do not fully understand the reason why this happens.

The incomming clock signal was low and started on a positive edge as the first edge introduced during the communication. When I switched the inactive value to be high this introduced a negative edge as the first edge during the communication. This caused the USI counter to overflow on a positive edge. Of some reason this caused the USIBR register to get the correct value. The USIDR register do have the correct value in both cases.
When I read the manual it says that USIBR is a copy of USIDR but it does not seem like USIDR have or ever had the same value as USIBR at the time of the overflow. Could they both sample from some hidden register that causes this discrepancy?