Assembler code for Arduino

Arduino projects on the go
Post Reply
Martin
Posts: 744
Joined: 16 Feb 2018, 14:11
Location: Warwickshire

Assembler code for Arduino

Post by Martin »

I never had to bother with AVR assembler before, but for the FrSkyV8Tx project, I wanted to implement the option to bit-bang SPI output. Normally it uses hardware SPI, but this means you have to use pins 11, 12, 13. Some people might want to use different pins - for example if you have an old AtMega328 based multiprotocol module that you want to run my library on, then it needs to use pins 4, 6, and 7.

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);
}
Then you add a separate file in the same folder - the filename can be anything but it must end with .S - the Arduino IDE automatically assembles and links such files when you compile your sketch.

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
I don't really recommend using assembler unless you really need the speed, or you just want to experience some old-time "back to the bare metal" computing! :ugeek:
Post Reply