NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » SPI Programming

February 10, 2010
by Mujda
Mujda's Avatar

I am using a MAX6675 (googling this gets the datasheet) to take a K-type thermocouple input which then converts it to a SPI format which you should be able to interface directly with the ATmega168. I'm not having much luck though.

I've taken the standard thermometer code and hardware from the Nerdkits kit, and simply added the MAX6675 on a breakout board (it's surface mount). There are very few connections to make, simply: 1) Gnd (checked) 2) Vcc (checked) 3) 2xThermocouple inputs - currently unconnected, but chip should communicate this on the SPI interface 4) SS (Slave Select), SCK (SPI clock), SO/MISO (Data)

Basically, I'm not getting the SPIF (SPI Interupt Flag) set, which means it's not receiving any data. In 'Main' x remains zero, so neither MSB or LSB were received.

When checking the hardware with a digital oscilloscope, it looks like SS is permanently high, and I can't seem to detect a clock signal on SCK.

Can anyone help me?

Thanks,

Mujda

See code below: I've not deleted any of the previous code, just added:

#define SPI_PORT PORTB
#define SPI_DDR DDRB
......
void SPI_MasterInit() {
  // Set SCK & SS for output, everything else for input
  SPI_DDR = (1<<PB5)|(1<<PB2);
  // Set SS to high to ensure slave captures temp
  SPI_PORT |= (1<<PB2);
  // Enable SPI, Master, set clock rate fck/16 
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
}

uint16_t SPI_MasterReceive() {
  uint16_t data;
  int i;

  data = 0;
  // Pull SS low to initiate data transmission
  SPI_PORT &= ~(1<<PB2);
  // Wait for reception to complete, MSB
  // Only loop 100 times max incase SPI flag does't set
  i=0;
  while(i<100){
    i++;
  if(SPSR & (1<<SPIF)){
        data++;
    i=100;
      }
  }
  // Wait for reception to complete, LSB
  // Only loop 100 times max incase SPI flag doesn't set
  while(i<100){
    i++;
    if(SPSR & (1<<SPIF)){
      data++;
  i=100;
    }
  }
  // Pull SS high to synchronise Slave
  SPI_PORT |= (1<<PB2);
  // Return Data Register
  return data;
}

main{
......
SPI_MasterInit();
.....
data = SPI_MasterReceive();
LCD_write_int16(PSTR(data));
......
February 10, 2010
by N3Roaster
N3Roaster's Avatar

It looks like you want the MCU to be in master mode. In order to receive data over SPI in master mode, you must also send data. I know that might not make much sense if the device you're trying to communicate with doesn't even have a MOSI pin to connect to, but that's how it is. If you aren't trying to send data, you won't generate a clock signal and you won't get any data back on MISO. You might also have some trouble with the loops while waiting. The clock rate requested is pretty slow (that chip supports much faster), so it's entirely possible that you're running through those loops faster than you're transmitting/receiving the data, in which case you'll also miss detecting the data. I'm also not seeing where you're getting the data from SPDR. It looks as though your function should always return 0, 1, or 2, but I assume that's just for debugging purposes.

February 10, 2010
by Mujda
Mujda's Avatar

Thanks N3Roaster,

I had assumed that as the MCU was receiving I shouldn't write anything to SPDR. I've adjusted the code to write a byte to SPDR before looking to receive a byte on the SPI. I've also included 'flag' which should be 0,1 or 2 depending on how many SPIF are received and increased the waiting loops to 1000. Is it correct to say that SCK will stop functioning after every byte received, so because I am receiving a 2 byte number, I need to write a byte to SPDR between receieved bytes?

First run I set the function to 'return flag;', which was 2 (this is good news :-) ) Then I changed it to 'return data;', which is contantly zero.

Frustratingly, my digital scope has stopped working. My digital multimeter gives SCK - Low SS - High MISO - Wandering between 1 & 2 volts (could mean a data flow?)

My code is below:

uint16_t SPI_MasterReceive() {
   uint16_t data;
   int i,flag;

   flag = 0;
   data = 0;
// Write a byte of data to SPDR to startup clock
   SPDR = 0xFF;
// Pull SS low to initiate data transmission
   SPI_PORT &= ~(1<<PB2);
// Wait for reception to complete, MSB
// Only loop 1000 times max incase SPI flag does't set
   i=0;
   while(i<1000){
      i++;
  if(SPSR & (1<<SPIF)){
// Record byte, shifted 8 bits to left
         data = (SPDR<<8);
         flag++;
     i=1000;
      }
   }
// Wait for reception to complete, LSB
// Only loop 1000 times max incase SPI flag doesn't set
   i=0;
// SPI clock will have stopped after first byte?
// So write another byte to SPDR
   SPDR = 0xFF;
   while(i<1000){
      i++;
      if(SPSR & (1<<SPIF)){
// Record byte
         data = data + SPDR;
         flag++;
         i=1000;
      }
   }
// Pull SS high to synchronise Slave
   SPI_PORT |= (1<<PB2);
// Return Data Register
   return data;
}
February 10, 2010
by pbfy0
pbfy0's Avatar

I don't think that code is quite right, you don't need to add to data as far as I know, just get SPDR.

February 11, 2010
by N3Roaster
N3Roaster's Avatar

No, that part of the code is fine. Since two bytes are being read, the MSB is read first and is shifted to the high byte of data, so what's expected is data to have the top 8 bits set to whatever was read and the lower 8 bits clear. Since the lower byte is all 0, adding SPDR to data just sets the bits in the lower byte. There's no way the addition can carry into the upper byte, so that should be fine. One thing that does concern me a bit is that SS is being pulled low after writing a byte to SPDR. That would typically be done first. Having 0 as the measurement is unexpected, however, as with the TC disconnected, bit D2 should be high. The result should show 4 or 5 if I'm reading the datasheet correctly. One thing that I am noticing that will interfere is that you haven't set CPOL or CPHA. Compare the timing diagrams on page 168 of the ATmega168 datasheet with the timing diagram for the MAX6675. You'll note that you want to sample on the falling edge, not the rising edge of the clock signal. The little blip where the clock signal stops while you load your next byte to write shouldn't matter. It's very short and you aren't setting SS high during that.

February 11, 2010
by Mujda
Mujda's Avatar

I've swapped the lines of code so now SS is pulled low before enabling SCK. Looking at the timing diagrams I see why I need to do this. I've also set CPOL=0 (which is what is was before, but now I understand why) and CPHA=1 to enable sampling on the falling edge. And, I'm still getting data=0....but I think we're much closer now to the solution.

I'll try to get the scope working again (power supply issues) and see what the hardware is actually producing.

Here's the modified code:

void SPI_MasterInit() {
// Set SCK & SS for output, everything else for input
  SPI_DDR = (1<<PB5)|(1<<PB2);
// Set SS to high to ensure slave captures temp
  SPI_PORT |= (1<<PB2);
// Enable SPI, Master, set clock rate fck/16
// CPOL=0 - SCK Low when idle
// CPHA=1 - Sample on trailing edge
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPHA)|(1<<SPR0);
}

uint16_t SPI_MasterReceive() {
  uint16_t data;
  int i,flag;

  flag = 0;
  data = 0;
// Pull SS low to initiate data transmission
  SPI_PORT &= ~(1<<PB2);
// Write a byte of data to SPDR to startup clock
  SPDR = 0xFF;
// Wait for reception to complete, MSB
// Only loop 1000 times max incase SPI flag does't set
  i=0;
  while(i<1000){
    i++;
    if(SPSR & (1<<SPIF)){
// Record byte, shifted 8 bits to left
      data = (SPDR<<8);
      flag++;
      i=1000;
    }
  }
// Wait for reception to complete, LSB
// Only loop 1000 times max incase SPI flag doesn't set
  i=0;
// SPI clock will have stopped after first byte?
// So write another byte to SPDR
  SPDR = 0xFF;
  while(i<1000){
    i++;
    if(SPSR & (1<<SPIF)){
// Record byte
      data = data + SPDR;
      flag++;
      i=1000;
}
  }
// Pull SS high to synchronise Slave
  SPI_PORT |= (1<<PB2);
// Return Data Register
  return data;
}
February 12, 2010
by Mujda
Mujda's Avatar

Got the digital oscilloscope working, so have some diagnostic info.

The SCK is working, but not quite how I'd expect. - I'm getting a sawtooth waveform, not a square waveform. - I'm getting 2 +ve pulses, then a small delay of about a cycle, then 8 +ve pulses.

This is a little odd. I was expecting to get 8 pulses, then a short delay, then 8 pulses (MSB then LSB).

SCK seems to produce this output even when all the external SPI connections are disconnected.

Any tboughts?

February 14, 2010
by N3Roaster
N3Roaster's Avatar

The SCK thing is definitely weird. I just hooked a scope up to a project with a working SPI component and I'm definitely getting square waves. Eight square peaks per byte of communication with a little delay between each group of 8 on SCK. MOSI and MISO are also square, but only on 1 bits (and consecutive 1 bits result in a wider square). In my case I'm using the NK crystal and setting the clock divider to 8 for an expected clock rate of 1.8432MHz. Observed at the scope on SCK, it looks like it takes about 4µs for eight clock cycles and about 2µs delay between each group of 8 which is, checking the math, about exactly what's expected.

February 15, 2010
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi Mujda,

The MOSI (Master Out, Slave In) pin should also be an output when you're using the ATmega168 as an SPI master. The "sawtooth" you're seeing might be the capacitance of the pin / wire being charged up very slowly by the pull-up-resistor only -- although you do seem to be setting SCK to be an output. Any chance you could post a photo of the scope watching the SCK pin (labeled with voltage and time)?

Mike

February 19, 2010
by Mujda
Mujda's Avatar

Ok, I think the sawtooth SCK trace I was see was simply due to the limits of my scope (Velleman digit HP10) which has 10MS/s. It states its max measurement frequency to be 2MHz, although a square wave of this frequency will have much higher components, so it chops off the corners.

Anyway, I've done the obvious and lowered the clock frequency to the lowest possible (f/128), and now I'm getting something looking like a square wave (approx 9microseconds/cycle = 0.11MHz = 14.2/128). I'm getting 15 pulses, but I think the scope may simply be missing the first one when it triggers.

I have also been getting the number 32764 out, which from looking at the datasheet, is sensible. With MSB recieved first,

Bit
15 (Sign) = 0
14 to 3 (MSB to LSB) = 1
2 (Open t/c) = 1
1 Dev ID = 0
0 = 0

This looks like what you should get, for an opencircuit thermocouple. Unfortunately, connecting a thermocouple doesn't change the outputted number.

I think some connections might be loose somewhere, so I'll tidy it all up. I'll also change the code to ensure MOSI is set an an output. I'll then take a shot of the traces I'm getting and post them, see if it'll help.

Thanks.

February 29, 2012
by RogerFL
RogerFL's Avatar

Old thread here but I'm trying to do the same project now so I'll hijack it. :) I wonder if it is necessary to "synchronize" using the SS. If you only have one slave, I thought you could just connect that pin to ground and forget about it. No?

Did you ever get it working?

Want to share the final code?

Post a Reply

Please log in to post a reply.

Did you know that you can generate hundreds of volts AC from your microcontroller with a little bit of circuitry? Learn more...