SPI bug in SPI_MODE1,3?

Hello,

After banging my head for a couple of days attempting to implement SPI between an Arduino Mega 2560 and an AD7706 ADC, I’ve come across the following issue:

When I set up the SPCR in mode 1 or 3, (CPHA = 1), the MOSI pin idles LOW whenever I send a byte of data that ends with a 0. I confirmed this on an oscilloscope and it occurs in both the Mega and Duemilanove. I’ve set the SPCR directly in code as well as with the SPI library, but both produce the same effect. However, when I use the AD770X library from Kerry D. Wong, the MOSI pin idles HIGH, as expected. Is this a compiler issue?

Here is my code:

#include <SPI.h>
const int ADC_CS = 10; // 53 for Mega

void setup() {
  pinMode(ADC_CS, OUTPUT);

  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.setDataMode(SPI_MODE3); // change this
  delay(10);
  digitalWrite(ADC_CS, HIGH);
}

void loop() {
  digitalWrite(ADC_CS, LOW);
  SPI.transfer(0xfe);  
  digitalWrite(ADC_CS, HIGH);
  delay(100);
}

Any help is much appreciated!

Thanks a lot,
Ari

I confirm your results, but what is the issue exactly? The MOSI pin "idles low" - where in the SPI spec does it say that the pin should change to one level or another when idle? And how do you even define "idle"?

If the pins are in the correct state, when the clock changes from one defined level to another, that is what is supposed to happen.

I don't know how you would specify that the MOSI pin should change state (from its previous state) after some period (what period?) after the last clock pulse. As far as I can see the hardware is behaving correctly.

The difference between the two libraries is that the SPI library seems to define the MOSI pin as an oupt and then set the state to a LOW whereas the other library you reference does not.

void SPIClass::begin() {
  // Set direction register for SCK and MOSI pin.
  // MISO pin automatically overrides to INPUT.
  // When the SS pin is set as OUTPUT, it can be used as
  // a general purpose output port (it doesn't influence
  // SPI operations).

  pinMode(SCK, OUTPUT);
  pinMode(MOSI, OUTPUT);
  pinMode(SS, OUTPUT);
  
  digitalWrite(SCK, LOW);
[b]  digitalWrite(MOSI, LOW);[/b]
  digitalWrite(SS, HIGH);

  // Warning: if the SS pin ever becomes a LOW INPUT then SPI 
  // automatically switches to Slave, so the data direction of 
  // the SS pin MUST be kept as OUTPUT.
  SPCR |= _BV(MSTR);
  SPCR |= _BV(SPE);
}

I'm not sure why the library sets up initial states for these pins, especially the SCK pin which should be defined by the CPOL bit. If you REM'd this line out and recompiled you'll probably see the same results as the other library.

Thank you Nick and wayneft for your quick responses.

From the SPI data transfer diagrams as well as from other forum questions, I assumed that the atmega 2560 sets the MOSI pin HIGH when it is not sending data (figure 21-3 and 21-4 from the Atmega 2560 user manual). Even with the MOSI pin idling LOW, I understand that since the correct pulses are being applied during the relevant clock pulse edges, this should not be an issue.

My consideration is that by setting the MOSI pin low during a data transfer, I somehow influence the data transfer. The AD7706 manual clearly states that the data register is read-only and writing to this register is ignored (though it must be a complete write), so I do not have a clear understanding of whether this is an actual issue.

Regarding altering the library to set the MOSI pin HIGH in software, once I enable the SPI I lose control of the MOSI pin. Changing the level of the MOSI pin with "digitalWrite" does not change the output while sending data. I see this in my setup, but is it generally true?

Perhaps I have some other timing issues that are causing my communication problems. Thanks for your help!

I got the library running with the AD7706 - changed port manipulation of the CS pin from digitalWrite’s to bit manipulation and also padded everything with delays. So far so good.

Here is the simple proof of concept:

const int ADC_MISO = 50;
const int ADC_MOSI = 51;
const int ADC_SCK = 52;
const int ADC_CS = 0;  //PB0

byte b1;
byte b2;
unsigned int val;

void setup() {

  pinMode(ADC_MISO, INPUT);
  pinMode(ADC_MOSI, OUTPUT);
  pinMode(ADC_SCK, OUTPUT);
  //pinMode(ADC_CS, OUTPUT);

  //digitalWrite(ADC_CS, HIGH);
  DDRB |= _BV(ADC_CS);
  PORTB |= _BV(ADC_CS);


  SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPOL) | _BV(CPHA) | _BV(SPR1) | _BV(SPR0);
  SPSR = _BV(SPI2X);

  delay(100);

  calibrateADC();
  delay(100);
  
  Serial.begin(9600);

}

void loop() {  
  val = getData();
  Serial.println(val);
  delay(100);
}



byte spiTransfer(byte data){
  SPDR = data;
  while(!(SPSR & (1 << SPIF)));
  return SPDR;
}

void calibrateADC(){
  //digitalWrite(ADC_CS, LOW);
  PORTB &= ~_BV(ADC_CS);
  spiTransfer(0x20);
  PORTB |= _BV(ADC_CS);
  //digitalWrite(ADC_CS, HIGH);
  delayMicroseconds(10);

  //digitalWrite(ADC_CS, LOW);
  PORTB &= ~_BV(ADC_CS);
  spiTransfer(0x04);
  PORTB |= _BV(ADC_CS);
  //digitalWrite(ADC_CS, HIGH);
  delayMicroseconds(10);

  //digitalWrite(ADC_CS, LOW);
  PORTB &= ~_BV(ADC_CS);
  spiTransfer(0x10);
  PORTB |= _BV(ADC_CS);
  //digitalWrite(ADC_CS, HIGH);
  delayMicroseconds(10);

  //digitalWrite(ADC_CS, LOW);
  PORTB &= ~_BV(ADC_CS);
  spiTransfer(0x40);
  PORTB |= _BV(ADC_CS);
  //digitalWrite(ADC_CS, HIGH);
  delayMicroseconds(10);

}

unsigned int getData(){
  
  unsigned int r;
  PORTB &= ~_BV(ADC_CS);
  b1=spiTransfer(0x38);
  delayMicroseconds(100);
  b1=spiTransfer(0xff);
  delayMicroseconds(100);
  b2=spiTransfer(0xff);
  delayMicroseconds(100);
  
  PORTB |= _BV(ADC_CS);

  r = b1 << 8 | b2;
  return r;
}

Thanks for everyone’s help!