I just rewrote the Arduino Playground Nokia LCD screen code to use hardware SPI instead of ShiftOut()
. (More work to do before releasing the code back to the community. And I know, not everyone will want to use the hardware SPI, but it should be an option.)
It looked like a trivial change, but after making it, the display’s screen remained blank.
But … but … but … the slave-select line is supposed to be held low during each byte of transmission. I know the ATmega’s SPI hardware doesn’t manage it for you, but surely the Arduino’s SPI.transfer()
function does, right???
Working Arduino SPI Transfer
No. You have to manage it manually.
void LcdWrite(byte dc, byte data) {
digitalWrite(PIN_SS, LOW);
digitalWrite(PIN_DC, dc);
SPI.transfer(data);
digitalWrite(PIN_SS, HIGH);
}
SPI support is a perfect candidate to be a real object-oriented class rather than a functional library in OO clothing. Instantiate objects that know which slave-select pin is theirs, which may have different bit orders and clock rates and clock modes, and provide a transfer()
method that sets all the registers, twiddles SS for you, and transfers your byte.
Sigh.
Faster Arduino SPI Transfer
BTW, notice on the second capture that manually bouncing SS using two digitalWrite()
s takes longer than transferring eight bits of data using the hardware SPI. Start to understand why I want to transfer data using hardware instead of bit-banging?
Manipulating the SS bit in the port register directly is much faster … at the cost of being much less clear what’s going on to non-native speakers of C.
void LcdWrite(byte dc, byte data) {
PORTB &= ~ (1 << 2);
digitalWrite(PIN_DC, dc);
SPI.transfer(data);
PORTB |= (1 << 2);
}
ATmega I/O Pin Hardware Toggling for Fastest Arduino SPI Transfer (That Isn't Any Faster)
The C-based bit manipulation code above is already fast enough that the timing constraint has moved back to the programmer-selected SPI clocking speed and the ultimate solution -- using the ATmega's hardware-based pin toggle feature -- doesn't save any noticeable time, but the code sure looks cool but it's the right thing to do and it's a risky game to play to assume you always know correctly the logic level of the pin when you enter the function.
void LcdWrite(byte dc, byte data) {
PINB = _BV(PINB2);
digitalWrite(PIN_DC, dc);
SPI.transfer(data);
PINB = _BV(PINB2);
}
(BTW, I had a dream recently -- which I did not know was not real -- in which the latest Arduino release added digitalWrite(pin, TOGGLE)
to do the above. Imagine my disappointment tonight to learn it was only a dream. And no, I'm not the first to suggest that feature, by a long shot. Just, maybe, the first to think it had actually happened.)
I also recently experimented with SPI on Arduino and had similar results. It appears a function call is quite slow compared to the SPI transfer. It’s even worse if you attempt to use interrupt mode.
http://www.tablix.org/~avian/blog/archives/2012/06/spi_interrupts_versus_polling/