real hardware SPI on 328?

If I use the SPI library, am I correct in that I HAVE to use these pins?
SPI: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK).

Is there a speed advantage in writing to a an SPI display if I use this actual SPI rather than the bit bang method many of the MAX72xx libraries seem to use. In other words, does the Atmega328 have actual real hardware SPI support, and how much faster is this?

The pins you mentioned are the real hardware IO, they are used by the SPI library and yes you have to use them.

I don't know about the MAX72xx libraries you mention but real SPI will be a LOT faster than bit banging. Whether or not this makes a difference to the overall data throughput to your "display" depends on how fast it can accept data. With the MAX72xx chips I would think there will be a huge difference.


Rob

Mostly - SS can be any pin, 11-12-13 are committed.

Yes, '328 has real SPI hardware support, it is very fast, you can send out 8 bytes to a MAX7219/7221 very fast;

digitalWrite (SS, LOW);
SPI.transfer(register1address);
SPI.transfer(register1data);
SPI.transfer(register2address);
SPI.transfer(register2data);
SPI.transfer(register3address);
SPI.transfer(register3data);
SPI.transfer(register4address);
SPI.transfer(register4data);
SPI.transfer(register5address);
SPI.transfer(register5data);
SPI.transfer(register6address);
SPI.transfer(register6data);
SPI.transfer(register7address);
SPI.transfer(register7data);
SPI.transfer(register8address);
SPI.transfer(register8data);
digitalWrite(SS, HIGH);

or perhaps this if you have the address and data in arrays

digitalWrite (SS,LOW)
for (x = 0; x<8; x=x+1){
SPI.transfer(registerAddress[x]);
SPI.transfer(Data[x]);
}
digitalWrite (SS,HIGH);

Huh - turns out the digit registers are numbered 0x01 to 0x08, so you could skip the array there:

digitalWrite (SS,LOW)
for (x = 1; x<9; x=x+1){
SPI.transfer(x);
SPI.transfer(Data[x]);
}
digitalWrite (SS,HIGH);

Much appreciated.

I had a single digit display LED on my project using BCD with a 4511. Over the weekend I changed to a 2 digit display with a 7219 using LEDcontrol library. I have MIDI input on this and it was all working fine, but as soon as I try to write the the 7219, I loose MIDi clock signal timing and it all goes bad. I think it's just too slow to write to the 7219 using that library which bitbangs. Maybe it's doing too much other stuff - I'm just not sure what the issue is.

So... I'm thinking of how I can run a 7219 or 7221 faster to hopefully fix this new issue. Hardware SPI?

I remember in an earlier thread you had rolled you own 7221 code instead of using a library. Do you happen to have a small version of that code I could see? All I need to do is display a two digit number, so the simpler the code you have the better :wink:

Any input would be most welcome!

Sure, take the portions of this that you need.
Library would end up with the same, you just don’t see it

number1_to_display = 0;
number2_to_display = 0;

// addresses for the MAX7221, and the values/ranges to write in

#define DECODE_MODE 0x09 // write data 0xFF, Code B Decode for all digits
#define INTENSITY_ADDRESS 0x0A // 0x07 to start, half intensity. valid from 0x00 (min) to 0x0F (max)
#define SCANLIMIT_ADDRESS 0x0B // 0xFF, all 8 digits on
#define SHUTDOWN_ADDRESS 0x0C  // 0x01, normal operation (0x01 = shutdown) - powers up in shutdown mode

#define DISPLAYTEST_ADDRESS 0x0F // 0x01 = all lights on full, 0x00 = normal ops
#define leftscore_tens_address 0x01 // digit 0, leftscore_tens+left_yellow, fill right hand byte with data to display
// data = 0-9, A='-', B='E', C='H', D='L', E='P', F=blank
#define leftscore_ones_address 0x02 // digit 1, leftscore_ones+right_yellow
#define rightscore_tens_address 0x03 // digit 2, rightscore_tens+right_red
#define rightscore_ones_address 0x04 // digit 3, rightscore_ones+right_yellow
#define minutes_tens_address 0x05 // digit 4, minutes_tens+colon
#define minutes_ones_address 0x06 // digit 5, minutes_ones+left_priority
#define seconds_tens_address 0x07 // digit 6, seconds_tens+right_priority
#define seconds_ones_address 0x08 // digit 7, seconds_ones+swap

void setup() // stuff that runs once before looping forever
{
  // start up SPI to talk to the MAX7221
  SPI.begin(); // nothing in () because we are the master
  pinMode(SS, OUTPUT);  // Slave Select for SPI  <--- Need this here before any SPI writes?

  //  MAX7221: write shutdown register  
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(SHUTDOWN_ADDRESS);  // select the Address,
  SPI.transfer(0x00);      // select the data, 0x00 = Outputs turned off
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  // Serial.println("shutdown register, dislays off");
 
  //  write intensity register
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(INTENSITY_ADDRESS);  // select the Address,
  SPI.transfer(intensity);      // select the data
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("intensity register ");

  // write scanlimit register
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(SCANLIMIT_ADDRESS);  // select the Address,
  SPI.transfer(0xFF);      // select the data - FF = all 8 digits
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("scanlimit register ");

  // write decode register
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(DECODE_MODE);  // select the Address,
  SPI.transfer(0xFF);      // select the data - FF = all 8 digits
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("decode register ");
  //display test
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(DISPLAYTEST_ADDRESS);  // select the Address,
  SPI.transfer(0x01);      // select the data
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("digit display test on ");
  delay (100);

  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(DISPLAYTEST_ADDRESS);  // select the Address,
  SPI.transfer(0x00);      // select the data
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("digit display test off ");
  delay (100);

  // write shutdown register for normal display operations
  digitalWrite(SS,LOW);  // take the SS pin low to select the chip:
  SPI.transfer(SHUTDOWN_ADDRESS);  // select the Address,
  SPI.transfer(0x01);      // select the data, 0x01 = Normal Ops
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip:
  //Serial.println("shutdown register, displays on ");
}

then in void loop

// after you've updated your data, set a flag so this portion knows to run

if (update_time == 1) // if = 1, a change was made and we are updating time in here
{
  update_time = 0;  // reset for the next pass thru

  digitalWrite(SS,LOW);  // take the SS pin low to select the chip
  SPI.transfer(minutes_tens_address);  // select the Address,
  SPI.transfer(number1_to_display);      // select the data
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip

 digitalWrite(SS,LOW);  // take the SS pin low to select the chip
  SPI.transfer(minutes_ones_address);  // select the Address,
  SPI.transfer(number2_to_display);      // select the data
  digitalWrite(SS,HIGH);   // take the SS pin high to de-select the chip
} // end Time Display Update section

//

Just curious, does the program trap while exchanging bytes on the SPI or not? If not, great! If yes, using serial.print is better. It's non-trapping with arduino 1.0

Trap? In this case there is no data coming back from the MAX7221, so the data just gets sent out at high speed, much higher speeds due to the built in clock line than the serial port supports which depends on the timing of the serial UART.

The data will "block" in exactly the same way as if you use a UART, except for less time because as a rule SPI is faster than serial (not always but normally).

The only thing that serial has in it's favor at present is that the writers of the serial libraries incorporated buffering, the SPI lib could easily be modified to do the same.


Rob

Thanks CrossRoad and Rob. My concern is that writing say 50 characters in a row with SPI may take longer than writing one character with serial at 115200. If so, then the time the processor is blocked is longer than serial, although the serial will block 49 more times to finish. Then arduino may want to break the 50 characters into smaller chunks and send a chunk at a time so it can do whatever timing in between send. After all, serial send just moves a byte into the out buffer and return, right?

Yes, as the libs are currently written (IIRC) SPI will block for 49 x 8 x bit rate + 50 x function overhead. Serial will block for 50 x function/buffering overhead.

So, as seen from your program, SPI will block for longer than serial, but the data will be transmitted in less time with SPI.

Like all these things you have to weigh up what's important and as you say maybe splitting the SPI data up to allow other processing to occur would be the way to go.

The best thing to do is modify the SPI lib to include buffering. Anyone got a couple of hours to spare :slight_smile:


Rob

50 characters with the SPI clock at 4MHz is not going to take long at all.

In the case of a MAX7221, you will do to the 8 SPI transfers, then go do other stuff, like decide how the pattern is going to change or whatever, using the balance of 4mS to do it, assuming you had a frame rate of 30 Hz and had 8 registers to update (so 240 writes/sec)

115200 is 86 uS/bit I think, and you have 240 bytes @ 10 bits/byte = 20.8mS, so you can't even hit a 30 frame/sec rate.

Graynomad:
The best thing to do is modify the SPI lib to include buffering. Anyone got a couple of hours to spare :slight_smile:

There's not much point. SPI transfers at about 3 uS per byte, and it takes 3.5 uS to service an interrupt. So you don't really gain anything by buffering it.

CrossRoads:
115200 is 86 uS/bit I think, and you have 240 bytes @ 10 bits/byte = 20.8mS, so you can’t even hit a 30 frame/sec rate.

Agreed. SPI is much faster.

SPI transfers at about 3 uS per byte, and it takes 3.5 uS to service an interrupt.

Good point, plus add the function call and buffering overhead and it would actually be a lot slower.

Forget I mentioned it :slight_smile:


Rob

Crossroads,

I really appreciate the code, and will try and get a test in the next couple of days.

As there was some slight differences in 7219 to 7221, do you think the code will work as is with a 7219?
I have a 7219 wired up already..

So far as speed, I'm just updating a display when a knob turns to display 1-99, so hopefully that's not much data to be written.

Probably.
At the worst you change the way chip select is asserted.

liudr:
Thanks CrossRoad and Rob. My concern is that writing say 50 characters in a row with SPI may take longer than writing one character with serial at 115200. If so, then the time the processor is blocked is longer than serial, although the serial will block 49 more times to finish. Then arduino may want to break the 50 characters into smaller chunks and send a chunk at a time so it can do whatever timing in between send. After all, serial send just moves a byte into the out buffer and return, right?

That is a case where the software approach is the fault. Write 1 char per loop() and not much else that time and all tasks will have small latency. IMO it is better to split not-small tasks up than to arrange each to complete in a single loop(). Good ole top-down type code habits can be hard to lose though.