I wrote a C (normal Arduino language) bit bang routine, and it worked fine on a 16MHz Arduino, but didn't work with the CC2500 module on an 8MHz one. I'm still not really sure why - according to the data sheet, there's no minimum speed limit for talking SPI to the CC2500 module, providing you can manage the required data throughput (which it could, easily).
Anyway, I decided to get my hands dirty with some AVR assembler, and it was surprisingly easy. The AVR chips have a nice clean architecture and a simple instruction set.
All you need to do in your (normal C++) sketch is declare a function extern "C" like this
Code: Select all
extern "C" {
void bitBangSPIout(volatile uint8_t* mosiPort, uint8_t mosiMask, volatile uint8_t* sckPort, uint8_t sckMask, uint8_t b);
}
Parameters passed to the function are stuffed into registers r25, r24, r23, ..., where the assembly code can read and work with them. If you want to return a value (which I didn't) then it also goes in the r24 r25 pair if it's 16 bits or fewer.
There are a few rules about which registers your assembler code has to preserve, but r0, r18-r27, and r30-r31 are fair game, so I stuck to using those. The r30 and r31 pair also work as the Z pointer which is able to access any port register. There are faster ways of manipulating ports directly, but you need to know which bit of which port at assembly time - and I didn't want that for my library.
Here's my assembler code. I was amazed that once I got rid of the typos so that it would assemble, it ran first time!
Code: Select all
.global bitBangSPIout
bitBangSPIout: ; (mosiPort, mosiMask, sckPort, sckMask, b)
mov r23, r22 ; r22 is mosiOrMask
com r23 ; make r23 mosiAndmask
mov r19, r18 ; r18 is sckOrMask
com r19 ; make r19 sckAndMask
mov r0, r16 ; byte to transmit in r0
ldi ZH, 0 ; high byte of Port addresses always zero
ldi r25, 8 ; loop counter
loop:
mov ZL, r24
ld r21, Z ; existing mosiPort contents to r21
lsl r0 ; transmitting MSB first - this puts the next bit to send into the carry
brcs bitHigh
and r21, r23 ; mask the mosi bit low
rjmp driveMosi
bitHigh:
or r21, r22 ; mask the mosi bit high
driveMosi:
st Z, r21 ; write to mosiPort
mov ZL, r20
ld r21, Z ; existing sckPort contents to r21
or r21, r18 ; mask the sck bit high
st Z, r21 ; write to sckPort - pulse SCK high
and r21, r19 ; mask sck low again
st Z, r21 ; write to sckPort - make SCK low again
dec r25
brne loop ; loop 8 bits
mov ZL, r24 ; finish by seting MOSI high
ld r21, Z
or r21, r22
st Z, r21
ret