USART in SPI mode on ATmega328P and delays

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

Hello everyone, I want to share my experience using USART in SPI mode on ATmega328P using internal RC oscillator as clock (8 MHz).

I use USART in SPI mode (Mode 1: CPOL=0, CPHA=1) to interface the MCU with a DAC (AD5623).

This is the pin-out I use:

PORTD1: TxD0: DAC_DIN

RxD0: not used

PORTD4: XCK0: DAC_SCLK

PORTD2: ~DAC_SYNC (aka ~CS)

 

This is how I set up USART in SPI mode:

// Taken from AVR317 Application Note Code
/*! \brief  Initialize USART as SPI Master in interrupt mode.
 *
 *  Initialize on-chip USART0 in Master SPI Mode and set the
 *  required port directions. There are no other masters on the
 *  bus, so the XCK line is configured as output.
 *
 *  \param spimode  SPI mode. Must be 0, 1, 2 or 3.
 *  \param brreg  SPI speed. Value to put into USART Baud Rate Register.
 *
 *  \note  SPI speed in bits per second: bps = CPUFREQ/(2*(brreg+1)).
 *  \note  brreg value: brreg = (CPUFREQ/(2*bps))-1.
 */
void USART_SetupSPI(uint8_t spimode, uint32_t bitRate)
{
	uint16_t toUBRR;

	// Baud rate must be set to 0 prior to enabling the USART as SPI
	// master, to ensure proper initialization of the XCK line.
	UBRR0 = 0;

	// Set XCK line to output, ie. set USART in master mode.
	USART_DDR |= (1 << USART_XCK);

	// Set USART to Master SPI mode.
	UCSR0C = (1 << UMSEL01) | (1 << UMSEL00);

	// Set clock polarity and phase to correct SPI mode.
	if( spimode & 0x01 ) UCSR0C |= (1 << UCPHA0);
	if( spimode & 0x02 ) UCSR0C |= (1 << UCPOL0);

	// Enable RX and TX
	UCSR0B = (1 << RXEN0) | (1 << TXEN0);

	// Set baud rate. Must be set _after_ enabling the transmitter.
	toUBRR = (F_CPU/(2*bitRate))-1;
	UBRR0H = (uint8_t)(toUBRR >> 8);
	UBRR0L = (uint8_t)toUBRR;
}

with the above function called like this:

USART_SetupSPI(USART_SPI_MODE1, F_CPU/2);	// max. clock rate is F_CPU/2; USART_SPI_MODE1 is defined as 1

and this is the function that sends data to USART in SPI mode:

// USART_SPIReadWrite(uint8_t data)
//
// Taken from ATmega328P datasheet (Atmel-42735B-ATmega328/P_Datasheet_Complete-11/2016, p. 258)
// and with a couple of tips from: https://feilipu.me/2015/02/17/avr-atmega-usart-spi-mspim/
void USART_SPIWrite(uint8_t* data, uint8_t noOfBytes)
{
	while (noOfBytes > 0)
	{
		// Wait for empty transmit buffer
		while ( !( UCSR0A & (1 << UDRE0)) )
			;

		// Put data into buffer (this sends the data)
		UDR0 = *(data++);
		noOfBytes--;
	}	

	_delay_us(1); // *** NOTE: this is necessary for proper sync of signals ***
	// Wait until transmission is complete
	while ( !( UCSR0A & (1 << TXC0)) )
		;
}

 

When I send a command to the DAC through USART in SPI mode, I notice the following things that I did not expect:

1. A delay of roughly 3 us exists from the moment when ~SYNC (think of it as chip-select exactly as in SPI) is taken LOW to the first falling edge of clock, i.e. to the moment when the first bit is sent.

2. That delay of 1 us seems to be necessary in order to have all signals properly synchronized. In the following screenshot from my logic analyzer, the top plot shows what happens WITHOUT that delay; the ~SYNC line is reasserted before data transmission ends, therefore losing 3 bits or so. The bottom plot shows what happens WITH the extra delay of 1 us right after sending all data.

 

 

Has anyone encountered a similar behavior of USART in SPI mode? Is there any way to get rid of that delay of 3 us in the beginning of each transmission?

Is there another way of configuring and / or using USART in SPI mode?

This topic has a solution.
Last Edited: Wed. Nov 8, 2017 - 12:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The datasheet says:

Note: To keep the input buffer in sync with the number of data bytes transmitted, the UDRn register must be read once for
each byte transmitted. The input buffer operation is identical to normal USART mode, i.e. if an overflow occurs the
character last received will be lost, not the first data in the buffer. This means that if four bytes are transferred, byte
1 first, then byte 2, 3, and 4, and the UDRn is not read before all transfers are completed, then byte 3 to be received
will be lost, and not byte 1.

 

Your not reading the input data between bytes sent as required above, even if you don't need the data, poll the RXC0 flag and read the data.

See if that helps.

Jim

 

Edit: RCX0 flag

Last Edited: Mon. Nov 6, 2017 - 02:59 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your reply, OM (SV1DEQ here).

I did what you suggested - here is the code I used, taken from Atmel's datasheet:

void USART_SPIReadWrite(uint8_t* TXData, uint8_t* RXData, uint8_t noOfBytes)
{
	while (noOfBytes > 0)
	{
		// Wait for empty transmit buffer
		do
		{
		}
		while ( !( UCSR0A & (1 << UDRE0)) );

		// Put data into buffer (this sends the data)
		UDR0 = *(TXData++);
		noOfBytes--;
		// Wait for data to be received
		do
		{
		}
		while ( !(UCSR0A & (1 << RXC0)) );

		// Get received data
		*(RXData++) =  UDR0;
	}

	// Wait until transmission is complete - added by GM
	//while ( !( UCSR0A & (1 << TXC0)) )
	//	;
}

 

That last while loop that is shown as commented out had an impact on code performance. Below is a screenshot of my logic analyzer showing: on the top, the case when TXC0 is polled; on the bottom, the case when TXC0 is not polled.

That 3 us delay from ~SYNC to the first bit sent is still there.

Timing waveform seems better in the previous post, though; I do not want to have 3 blocks of 8 clock pulses, but 24 clock pulses; furthermore, that 3 us delay is excessive, since the minimum setup time according to AD5623 datasheet is only 13 ns; if it were, say, about 1 us, it would be far better.

What else can I do to shrink timing?

 

Last Edited: Tue. Nov 7, 2017 - 07:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Explain what you WANT to achieve.
The USART_MSPI can produce gap-less transmission. There is no need for any delays.
You can check the TXC complete flag to ensure that CS becomes inactive at the right time.
.
David.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Thanks to all posters here; you pointed me to the right direction.

Here is what I did:

// USART_SPIWrite(void USART_SPIWrite(uint8_t* data, uint8_t noOfBytes)
//
// Taken from ATmega328P datasheet (Atmel-42735B-ATmega328/P_Datasheet_Complete-11/2016, p. 258)
// and with a couple of tips from: https://feilipu.me/2015/02/17/avr-atmega-usart-spi-mspim/
void USART_SPIWrite(uint8_t* data, uint8_t noOfBytes)
{
	uint8_t portDTemp;
	
	// Clear TXC0 flag - all other bits set LOW
	UCSR0A = (1 << TXC0);
	
	//_DAC_SYNC_LOW
	port_D &= ~_BV(_DAC_SYNC);
	
	while (noOfBytes > 0)
	{
		// Wait for empty transmit buffer
		while ( !( UCSR0A & (1 << UDRE0)) )
			;
		
		// Put data into buffer (this sends the data)
		UDR0 = *(data++);
		noOfBytes--;
	}	
	
	// Wait until transmission is complete
	while ( !( UCSR0A & (1 << TXC0)) )
		;
		
	//_DAC_SYNC_HIGH
	port_D |= _BV(_DAC_SYNC);
}

The major change is to clear the TXC0 flag before sending data to UDR0. In this way, no delays are necessary to have all bits sent out correctly. This is a screenshot from the logic analyzer:

 

Last Edited: Wed. Nov 8, 2017 - 12:25 PM