Skip to content

Commit

Permalink
Implement optimized SPI read and write routines as a library.
Browse files Browse the repository at this point in the history
  • Loading branch information
whitequark committed Jan 28, 2019
1 parent bee8274 commit 6b8e90d
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __getattr__(cls, name):
'../firmware/library/include', [
'usb.h', 'usbmicrosoft.h', 'usbdfu.h', 'usbcdc.h', 'usbmassstor.h',
'fx2regs.h', 'fx2ints.h', 'fx2lib.h',
'fx2delay.h', 'fx2i2c.h', 'fx2eeprom.h',
'fx2delay.h', 'fx2i2c.h', 'fx2eeprom.h', 'fx2spi.h',
'fx2usb.h', 'fx2usbdfu.h', 'fx2usbmassstor.h', 'fx2uf2.h',
]
)
Expand Down
1 change: 1 addition & 0 deletions docs/device_library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Device-side library reference
fx2delay_h
fx2i2c_h
fx2eeprom_h
fx2spi_h
fx2usb_h
fx2usbdfu_h
fx2usbmassstor_h
Expand Down
9 changes: 9 additions & 0 deletions docs/fx2spi_h.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fx2spi.h
========

The ``fx2spi.h`` header contains templated SPI bitbang routines for the Cypress FX2 series implemented in assembly. This header is the complete implementation of the SPI interface and does not have a corresponding library.

Reference
---------

.. autodoxygenfile:: fx2spi.h
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#ifndef _ASMARGS_H
#define _ASMARGS_H

#define _ASM_HASH #

#if defined(__SDCC_MODEL_SMALL)
#define GET_PARM(rA, rB, parm) \
#define _ASM_GET_PARM(rA, rB, parm) \
mov rA, parm+0 \
mov rB, parm+1
#elif defined(__SDCC_MODEL_MEDIUM)
#define HASH #
#define GET_PARM(rA, rB, parm) \
mov r0, HASH parm \
#define _ASM_GET_PARM(rA, rB, parm) \
mov r0, _ASM_HASH parm \
movx a, @r0 \
mov rA, a \
inc r0 \
movx a, @r0 \
mov rB, a
#elif defined(__SDCC_MODEL_LARGE) || defined(__SDCC_MODEL_HUGE)
#define HASH #
#define GET_PARM(rA, rB, parm) \
mov dptr, HASH parm \
#define _ASM_GET_PARM(rA, rB, parm) \
mov dptr, _ASM_HASH parm \
movx a, @dptr \
mov rA, a \
inc dptr \
Expand Down
97 changes: 97 additions & 0 deletions firmware/library/include/fx2spi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#ifndef FX2SPI_H
#define FX2SPI_H

#include <stdint.h>
#include <fx2regs.h>
#include <bits/asmargs.h>

#ifndef DOXYGEN

// See the implementation of xmemcpy for a detailed explanation of the loop structure below.
#define _SPI_FN(name, bit, sck, si, so, aldr, atlr) \
void name(__xdata uint8_t *data, uint16_t len) { \
data; \
len; \
__asm \
push dpl \
push dph \
\
mov _AUTOPTRSETUP, _ASM_HASH 0b11 \
mov _AUTOPTRL1, dpl \
mov _AUTOPTRH1, dph \
\
_ASM_GET_PARM(r0, r1, _##name##_PARM_2) \
\
mov a, r0 \
jz 00000$ \
inc r1 \
00000$: \
mov a, r1 \
jz 00002$ \
\
mov dptr, _ASM_HASH _XAUTODAT1 \
00001$: \
aldr ; 8c+s \
bit(sck, si, so, 7) ; 8c \
bit(sck, si, so, 6) ; 8c \
bit(sck, si, so, 5) ; 8c \
bit(sck, si, so, 4) ; 8c \
bit(sck, si, so, 3) ; 8c \
bit(sck, si, so, 2) ; 8c \
bit(sck, si, so, 1) ; 8c \
bit(sck, si, so, 0) ; 8c \
atlr ; 8c+s \
djnz r0, 00001$ ; 4c \
djnz r1, 00001$ ; 4c \
\
00002$: \
pop dpl \
pop dph \
__endasm; \
}

#define _SPI_DUMMY

#define _SPI_WR_LDR movx a, @dptr
#define _SPI_WR_BIT(sck, si, so, num) \
mov c, acc+num ; 2c \
clr sck ; 2c \
mov si, c ; 2c \
setb sck ; 2c

#define _SPI_RD_TLR movx @dptr, a
#define _SPI_RD_BIT(sck, si, so, num) \
clr sck ; 2c \
mov c, so ; 2c \
setb sck ; 2c \
mov acc+num, c ; 2c

#endif

/**
* This macro defines a function `void name(__xdata uint8_t *data, uint16_t len)` that implements
* an optimized (76 clock cycles per iteration; ~5 MHz at 48 MHz CLKOUT) SPI Mode 3 write routine.
* The `sck` and `si` parameters may point to any pins, and are defined in the format `_IOx+n`
* (note the underscore).
*
* For example, invoking the macro as `DEFINE_SPI_WR_FN(flash_write, _IOA+1, _IOB+6)` defines
* a routine `void flash_write()` that assumes an SPI device's SCK pin is connected to A1 and
* MOSI pin is connected to B6.
*/
#define DEFINE_SPI_WR_FN(name, sck, si) \
_SPI_FN(name, _SPI_WR_BIT, sck, si, 0, _SPI_WR_LDR, _SPI_DUMMY)

/**
* This macro defines a function `void name(__xdata uint8_t *data, uint16_t len)` that implements
* an optimized (76 clock cycles per iteration; ~5 MHz at 48 MHz CLKOUT) SPI Mode 3 read routine.
* The `sck` and `so` parameters may point to any pins, and are defined in the format `_IOx+n`
* (note the underscore).
*
* For example, invoking the macro as `DEFINE_SPI_RD_FN(flash_read, _IOA+1, _IOB+5)` defines
* a routine `void flash_read()` that assumes an SPI device's SCK pin is connected to A1 and
* MISO pin is connected to B5.
*/
#define DEFINE_SPI_RD_FN(name, sck, so) \
_SPI_FN(name, _SPI_RD_BIT, sck, 0, so, _SPI_DUMMY, _SPI_RD_TLR)

#endif
6 changes: 3 additions & 3 deletions firmware/library/xmemclr.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <fx2lib.h>
#include <fx2regs.h>
#include "asmargs.h"
#include <bits/asmargs.h>

__xdata void *xmemclr(__xdata void *dest, uint16_t length) {
dest;
Expand All @@ -10,10 +10,10 @@ __xdata void *xmemclr(__xdata void *dest, uint16_t length) {
push dph

// Retrieve arguments.
// GET_PARM may use dptr, so save that first.
// _ASM_GET_PARM may use dptr, so save that first.
mov r2, dpl
mov r3, dph
GET_PARM(r4, r5, _xmemclr_PARM_2)
_ASM_GET_PARM(r4, r5, _xmemclr_PARM_2)

// Handle edge conditions.
// Skip the entire function if r7:r6=0.
Expand Down
8 changes: 4 additions & 4 deletions firmware/library/xmemcpy.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <fx2lib.h>
#include <fx2regs.h>
#include "asmargs.h"
#include <bits/asmargs.h>

__xdata void *xmemcpy(__xdata void *dest, __xdata void *src, uint16_t length) {
dest;
Expand All @@ -11,11 +11,11 @@ __xdata void *xmemcpy(__xdata void *dest, __xdata void *src, uint16_t length) {
push dph

// Retrieve arguments.
// GET_PARM may use dptr, so save that first.
// _ASM_GET_PARM may use dptr, so save that first.
mov r2, dpl
mov r3, dph
GET_PARM(r4, r5, _xmemcpy_PARM_2)
GET_PARM(r6, r7, _xmemcpy_PARM_3)
_ASM_GET_PARM(r4, r5, _xmemcpy_PARM_2)
_ASM_GET_PARM(r6, r7, _xmemcpy_PARM_3)

// Handle edge conditions.
// Skip the entire function if r7:r6=0.
Expand Down

0 comments on commit 6b8e90d

Please sign in to comment.