Initial commit of the project

This commit is contained in:
Niklas Ekström 2020-05-03 17:32:56 +02:00
commit a7a9350ba2
24 changed files with 1763 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.vscode

42
README.md Normal file
View File

@ -0,0 +1,42 @@
# Amiga Parallel Port to SPI Adapter
The goal of this project is to make a cheap and easy to build SPI adapter that connects to the parallel port of an Amiga. Furthermore, the performance of the adapter should be as fast as possible.
## What parts make up the project?
- An AVR microcontroller, provided by an Arduino Nano board
- A parallel port connector that connects to the AVR
- [Instructions](hardware/assembly-instructions.md) for how to assemble the above
- [Code](avr) for the AVR that waits to receive commands from the Amiga, and executes those commands
- A source code library for the Amiga, [*spi-lib*](spi-lib), that communicates with the AVR
- An [example](examples/spisd) of how to use the adapter to connect to an SD card module
| | |
| ------------- |---------------|
| ![Parallel port connector](images/parallel_port_connector.jpg) | ![Assemebled unconnected](images/assembled_unconnected.jpg) |
|![Connected Amiga](images/connected_amiga.jpg) | ![SD card running](images/sdcard_mounted.jpg) |
See the [assembly instructions](hardware/assembly-instructions.md) for how to connect the parts together.
## What can it be used for?
There exists many SPI peripherals that can be connected to this adapter.
The SPI adapter comes with a source code library, spi-lib, that is used to perform reads and writes to the SPI peripheral.
For each kind of SPI peripheral, however, a separate driver needs to be written that uses spi-lib, and exposes the functionality of the SPI peripheral to the operating system using some suitable interface.
In the directory [examples/spisd](examples/spisd) an example of how an SD card module can be connected to the SPI adapter, and a driver is provided that lets AmigaOS mount the SPI card as a file system.
## Performance
The throughput of the adapter is limited by how fast the 68k CPU can access the CIA chips.
The CPU can make one access (a read or a write) to a CIA chip per E-cycle.
An E-cycle is one tenth of the frequency of an original Amiga.
The E-cycle frequency is thus roughly 700 kHz, regardless if the Amiga uses an accelerator or not.
The protocol used by the SPI adapter can communicate one byte every two E-cycles which gives a theoretical upper
limit of 350 kB/s.
I performed a simple benchmark on an A500 with an HC508 accelerator, and an SD card module connected to the SPI adapter.
Copying a 23 MB file from the SD card to the compact flash in the HC508 took 98 seconds, giving a throughput of 225 kB/s.
I think this is a good result, and I believe it will be hard to come much closer to the theoretical limit of 350 kB/s.
There are however some optimizations that could be implemented, such as transfering more than one sector (512 bytes) at a time from the SD card, that could make the throughput come closer to the limit.

21
avr/Makefile Normal file
View File

@ -0,0 +1,21 @@
PORT = /dev/ttyUSB0
BAUD = 115200
MCU = atmega328p
PROGRAMMER = arduino
all: build flash
main.elf: main.c
avr-gcc -Os -mmcu=$(MCU) main.c -o main.elf
main.hex: main.elf
avr-objcopy -O ihex main.elf main.hex
build: main.hex
flash: main.hex
avrdude -p$(MCU) -c$(PROGRAMMER) -P$(PORT) -b$(BAUD) -D -Uflash:w:main.hex:i
clean:
rm main.elf
rm main.hex

15
avr/README.md Normal file
View File

@ -0,0 +1,15 @@
# AVR microcontroller code
The AVR code is carefully timed.
For each byte that is read or written, the AVR has 45 clock cycles (at 16 MHz) to:
- start an SPI tranfer
- wait until the SPI transfer is complete
- wait until the Amiga signals that it is ready to receive or send a byte
- read/write the byte to send/receive
## Building and flashing
On Linux: running `make` will build the hex file and flash it to the AVR using the Arduino boot loader method. You can use `make build` and `make flash` to perform the steps individually.
On Windows: `build.bat` builds the hex file, and `flash.bat` flashes it. These batch files assumes that you have installed the Arduino IDE in the usual location. Note that you have to update which COM port the Arduino is connected to in flash.bat.

2
avr/build.bat Normal file
View File

@ -0,0 +1,2 @@
"C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr/bin/avr-gcc" -Os -mmcu=atmega328p main.c -o main.elf
"C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr/bin/avr-objcopy" -O ihex main.elf main.hex

1
avr/flash.bat Normal file
View File

@ -0,0 +1 @@
"C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avrdude" -C"C:\Program Files (x86)\Arduino\hardware\tools\avr/etc/avrdude.conf" -patmega328p -carduino -PCOM11 -b115200 -D -Uflash:w:main.hex:i

203
avr/main.c Normal file
View File

@ -0,0 +1,203 @@
#include <avr/io.h>
// Amiga pins:
// D0 = A0 = PC0
// D1 = A1 = PC1
// D2 = A2 = PC2
// D3 = A3 = PC3
// D4 = A4 = PC4
// D5 = A5 = PC5
// D6 = D6 = PD6
// D7 = D7 = PD7
// BUSY = D4 = PD4
// POUT = D5 = PD5
// SPI pins in port B.
#define SCK_BIT 5
#define MISO_BIT 4
#define MOSI_BIT 3
#define SS_BIT 2
// These bits are in port D.
#define IDLE_BIT 4
#define CLOCK_BIT 5
int main()
{
DDRB = (1 << SCK_BIT) | (1 << MOSI_BIT) | (1 << SS_BIT);
PORTB = (1 << SS_BIT);
// SPI enabled, master, fosc/64 = 250 kHz
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
SPSR |= (1 << SPI2X);
DDRD = (1 << IDLE_BIT);
DDRC = 0;
PORTD = 0;
PORTC = 0;
uint8_t v;
uint8_t w;
uint8_t next_port_d;
uint8_t next_port_c;
uint16_t byte_count;
main_loop:
if (PIND & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
if (!(PIND & 0x80))
{
if (PIND & 0x40)
goto do_read1;
else
goto do_write1;
}
v = PIND;
w = PINC;
if (!(v & 0x40)) // READ2 or WRITE2
{
byte_count = (w & 0x1f) << 8;
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
v = PIND;
byte_count |= (v & 0xc0) | PINC;
if (w & 0x20)
goto do_read;
else
goto do_write;
}
else if ((w & 0x3e) == 0) // SPEED
{
if (w & 1) // Fast
SPCR = (1 << SPE) | (1 << MSTR);
else // Slow
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
}
goto main_loop;
do_read1:
byte_count = PINC;
do_read:
SPDR = 0xff;
v = PIND;
PORTD = (v & 0xc0) | (1 << IDLE_BIT);
DDRD = 0xc0 | (1 << IDLE_BIT);
PORTC = byte_count & 0x3f;
DDRC = 0x3f;
read_loop:
while (!(SPSR & (1 << SPIF)))
;
next_port_c = SPDR;
next_port_d = (next_port_c & 0xc0) | (1 << IDLE_BIT);
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
PORTD = next_port_d;
PORTC = next_port_c;
v = PIND;
if (byte_count)
{
byte_count--;
SPDR = 0xff;
goto read_loop;
}
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
DDRD = (1 << IDLE_BIT);
DDRC = 0;
PORTD = 0;
PORTC = 0;
goto main_loop;
do_write1:
byte_count = PINC;
v = PIND;
do_write:
PORTD = (1 << IDLE_BIT);
write_loop:
if (v & (1 << CLOCK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
v = PIND;
SPDR = (v & 0xc0) | PINC;
while (!(SPSR & (1 << SPIF)))
;
(void) SPDR;
if (byte_count)
{
byte_count--;
goto write_loop;
}
PORTD = 0;
goto main_loop;
return 0;
}

10
examples/spisd/README.md Normal file
View File

@ -0,0 +1,10 @@
# SD card module
An SD card module can be used with the SPI adapter in order to mount the SD card as a volume in AmigaOS.
This driver uses the code written by Mike Sterling: https://github.com/mikestir/k1208-drivers/tree/master/sd. Thanks Mike!
The file `sd.c` does all the heavy lifiting and remains largely unchanged compared to Mike's original. The file device.c had to be partially changed in order to be compiled with VBCC. The only reason I'm using VBCC instead of using gcc is that I haven't been successful in installing gcc.
The `build.bat` Windows batch file contains the command line used to compile the driver with VBCC, producing the binary `spisd.device` which should go in the DEVS: directory.
Install the [fat95 file system handler](http://aminet.net/package/disk/misc/fat95) in L: and copy the mountfile (available [here](https://github.com/mikestir/k1208-drivers/tree/master/amiga)) to some suitable place where it can be used to mount the SD card (read more about how this works in other places, e.g. the fat95 documentation).

1
examples/spisd/build.bat Normal file
View File

@ -0,0 +1 @@
vc romtag.asm device.c sd.c timer.c ../../spi-lib/spi.c ../../spi-lib/spi_low.asm -I../../spi-lib -nostdlib -o spisd.device

253
examples/spisd/device.c Normal file
View File

@ -0,0 +1,253 @@
/*
* SPI SD device driver for K1208/Amiga 1200
*
* Copyright (C) 2018 Mike Stirling
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Parts of this file were changed in order for it to compile with VBCC.
* The procedures device_get_geometry() and begin_io() are intact.
*/
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/ports.h>
#include <libraries/dos.h>
#include <devices/trackdisk.h>
#include <proto/exec.h>
#include "sd.h"
#include "spi.h"
#ifndef TD_GETGEOMETRY
// Needed to compile with AmigaOS 1.3 headers.
#define TD_GETGEOMETRY (CMD_NONSTD+13)
struct DriveGeometry
{
ULONG dg_SectorSize;
ULONG dg_TotalSectors;
ULONG dg_Cylinders;
ULONG dg_CylSectors;
ULONG dg_Heads;
ULONG dg_TrackSectors;
ULONG dg_BufMemType;
UBYTE dg_DeviceType;
UBYTE dg_Flags;
UWORD dg_Reserved;
};
#endif
char device_name[] = "spisd.device";
char id_string[] = "spisd 0.3 (3 May 2020)";
struct ExecBase *SysBase;
BPTR saved_seg_list;
BOOL is_open;
static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev)
{
SysBase = *(struct ExecBase **)4;
saved_seg_list = seg_list;
dev->lib_Node.ln_Type = NT_DEVICE;
dev->lib_Node.ln_Name = device_name;
dev->lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
dev->lib_Version = 0;
dev->lib_Revision = 2;
dev->lib_IdString = (APTR)id_string;
is_open = FALSE;
return dev;
}
static BPTR expunge(__reg("a6") struct Library *dev)
{
if (dev->lib_OpenCnt != 0)
{
dev->lib_Flags |= LIBF_DELEXP;
return 0;
}
spi_shutdown();
BPTR seg_list = saved_seg_list;
Remove(&dev->lib_Node);
FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
return seg_list;
}
static void open(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
{
ior->io_Error = IOERR_OPENFAIL;
ior->io_Message.mn_Node.ln_Type = NT_REPLYMSG;
if (unitnum != 0)
return;
if (!is_open)
{
spi_initialize();
if (sd_open() != 0)
return;
is_open = TRUE;
}
dev->lib_OpenCnt++;
ior->io_Error = 0;
}
static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
{
ior->io_Device = NULL;
ior->io_Unit = NULL;
dev->lib_OpenCnt--;
if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
return expunge(dev);
return 0;
}
static uint32_t device_get_geometry(struct IOStdReq *iostd)
{
struct DriveGeometry *geom = (struct DriveGeometry*)iostd->io_Data;
const sd_card_info_t *ci = sd_get_card_info();
if (ci->type != sdCardType_None) {
geom->dg_SectorSize = 1 << ci->block_size;
geom->dg_TotalSectors = ci->total_sectors; //ci->capacity >> ci->block_size;
geom->dg_Cylinders = geom->dg_TotalSectors;
geom->dg_CylSectors = 1;
geom->dg_Heads = 1;
geom->dg_TrackSectors = 1;
geom->dg_BufMemType = MEMF_PUBLIC;
geom->dg_DeviceType = 0; //DG_DIRECT_ACCESS;
geom->dg_Flags = 1; //DGF_REMOVABLE;
return 0;
} else {
return TDERR_DiskChanged;
}
}
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ioreq)
{
struct IOStdReq *iostd = (struct IOStdReq*)ioreq;
if (ioreq == NULL) {
return;
}
iostd->io_Error = 0;
switch (iostd->io_Command) {
case CMD_RESET:
case CMD_CLEAR:
case CMD_UPDATE:
case TD_MOTOR:
case TD_REMOVE:
/* NULL commands */
iostd->io_Actual = 0;
break;
case TD_PROTSTATUS:
/* Should return a non-zero value if the card is write protected */
iostd->io_Actual = 0;
break;
case TD_CHANGESTATE:
/* Should return a non-zero value if the card is invalid or not inserted */
iostd->io_Actual = 0;
break;
case TD_CHANGENUM:
/* This should increment each time a disk is inserted */
iostd->io_Actual = 1;
break;
case TD_GETDRIVETYPE:
iostd->io_Actual = 0; //DG_DIRECT_ACCESS;
break;
case TD_GETGEOMETRY:
iostd->io_Actual = 0;
iostd->io_Error = device_get_geometry(iostd);
break;
#if 0
case HD_SCSI_CMD:
break;
case NSCMD_DEVICEQUERY:
break;
case NSCMD_TD_READ64:
break;
case NSCMD_WRITE64:
break;
#endif
case TD_FORMAT:
case CMD_WRITE:
/* FIXME: Should be deferred to task but this did not work reliably - investigate */
if (sd_write((uint8_t *)iostd->io_Data, iostd->io_Offset >> SD_SECTOR_SHIFT, iostd->io_Length >> SD_SECTOR_SHIFT) == 0) {
iostd->io_Actual = iostd->io_Length;
iostd->io_Error = 0;
} else {
iostd->io_Actual = 0;
iostd->io_Error = TDERR_NotSpecified;
}
break;
case CMD_READ:
if (sd_read((uint8_t *)iostd->io_Data, iostd->io_Offset >> SD_SECTOR_SHIFT, iostd->io_Length >> SD_SECTOR_SHIFT) == 0) {
iostd->io_Actual = iostd->io_Length;
iostd->io_Error = 0;
} else {
iostd->io_Actual = 0;
iostd->io_Error = TDERR_NotSpecified;
}
break;
default:
iostd->io_Error = IOERR_NOCMD;
}
if (iostd && !(iostd->io_Flags & IOF_QUICK)) {
/* Reply to message now unless it was deferred to the task or is IOF_QUICK */
ReplyMsg(&iostd->io_Message);
}
}
static ULONG abort_io(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
{
// There is no asynchronous processing of requests so this is always a no-op.
return IOERR_NOCMD;
}
static ULONG device_vectors[] =
{
(ULONG)open,
(ULONG)close,
(ULONG)expunge,
0,
(ULONG)begin_io,
(ULONG)abort_io,
-1,
};
ULONG auto_init_tables[] =
{
sizeof(struct Library),
(ULONG)device_vectors,
0,
(ULONG)init_device,
};

23
examples/spisd/romtag.asm Normal file
View File

@ -0,0 +1,23 @@
RTC_MATCHWORD: equ $4afc
RTF_AUTOINIT: equ (1<<7)
NT_DEVICE: equ 3
VERSION: equ 1
PRIORITY: equ 0
section code,code
moveq #-1,d0
rts
romtag:
dc.w RTC_MATCHWORD
dc.l romtag
dc.l endcode
dc.b RTF_AUTOINIT
dc.b VERSION
dc.b NT_DEVICE
dc.b PRIORITY
dc.l _device_name
dc.l _id_string
dc.l _auto_init_tables
endcode:

549
examples/spisd/sd.c Normal file
View File

@ -0,0 +1,549 @@
/*
* SPI SD device driver for K1208/Amiga 1200
*
* Copyright (C) 2018 Mike Stirling
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <string.h>
//#include "common.h"
#include "spi.h"
#include "sd.h"
#include "timer.h"
#define FUNCTION_TRACE
#define INFO(...)
#define ERROR(...)
#define TRACE(...)
#define SLOW_CLOCK 400000
#define FAST_CLOCK 3000000
#define READY_TIMEOUT_MS 500
#define INIT_TIMEOUT_MS 1000
#define MAX_RESPONSE_POLLS 10
/* MMC/SD command */
#define CMD0 (0) /* GO_IDLE_STATE */
#define CMD1 (1) /* SEND_OP_COND (MMC) */
#define ACMD41 (0x80+41) /* SEND_OP_COND (SDC) */
#define CMD8 (8) /* SEND_IF_COND */
#define CMD9 (9) /* SEND_CSD */
#define CMD10 (10) /* SEND_CID */
#define CMD12 (12) /* STOP_TRANSMISSION */
#define ACMD13 (0x80+13) /* SD_STATUS (SDC) */
#define CMD16 (16) /* SET_BLOCKLEN */
#define CMD17 (17) /* READ_SINGLE_BLOCK */
#define CMD18 (18) /* READ_MULTIPLE_BLOCK */
#define CMD23 (23) /* SET_BLOCK_COUNT (MMC) */
#define ACMD23 (0x80+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24 (24) /* WRITE_BLOCK */
#define CMD25 (25) /* WRITE_MULTIPLE_BLOCK */
#define CMD32 (32) /* ERASE_ER_BLK_START */
#define CMD33 (33) /* ERASE_ER_BLK_END */
#define CMD38 (38) /* ERASE */
#define CMD55 (55) /* APP_CMD */
#define CMD58 (58) /* READ_OCR */
static sd_card_info_t sd_card_info;
/*! Utility function for parsing CSD fields */
static int sd_parse_csd(sd_card_info_t *ci, const uint32_t *bits)
{
sd_card_csd_t *csd = &ci->csd;
memset(csd, 0, sizeof(sd_card_csd_t));
TRACE("CSD: %08X %08X %08X %08X\n",
(unsigned int)bits[0],
(unsigned int)bits[1],
(unsigned int)bits[2],
(unsigned int)bits[3]);
csd->csd_structure = (bits[0] >> 30) & 0x2;
csd->taac = (bits[0] >> 16) & 0xff;
csd->nsac = (bits[0] >> 8) & 0xff;
csd->max_transfer_rate = (bits[0] >> 0) & 0xff;
csd->card_command_classes = (bits[1] >> 20) & 0xfff;
csd->read_block_len = (bits[1] >> 16) & 0xf;
csd->read_partial_blocks = (bits[1] >> 15) & 0x1;
csd->write_block_misalign = (bits[1] >> 14) & 0x1;
csd->read_block_misalign = (bits[1] >> 13) & 0x1;
csd->dsr_implemented = (bits[1] >> 12) & 0x1;
if (ci->type == sdCardType_SD1_x || ci->type == sdCardType_SD2_0) {
csd->device_size = (bits[1] << 2) & 0xffc;
csd->device_size |= (bits[2] >> 30) & 0x3;
csd->max_read_current_vdd_min = (bits[2] >> 27) & 0x7;
csd->max_read_current_vdd_max = (bits[2] >> 24) & 0x7;
csd->max_write_current_vdd_min = (bits[2] >> 21) & 0x7;
csd->max_write_current_vdd_max = (bits[2] >> 18) & 0x7;
csd->device_size_mult = (bits[2] >> 15) & 0x7;
//ci->capacity = (uint64_t)(csd->device_size + 1) << (csd->device_size_mult + csd->read_block_len + 2);
ci->total_sectors = (uint32_t)(csd->device_size + 1) << (csd->device_size_mult + 2);
} else if (ci->type == sdCardType_SDHC) {
csd->device_size = (bits[1] << 16) & 0x3f;
csd->device_size |= (bits[2] >> 16) & 0xffff;
//ci->capacity = (uint64_t)(csd->device_size + 1) << 19;
ci->total_sectors = (uint32_t)(csd->device_size + 1) << (19 - csd->read_block_len);
} else {
ERROR("Card type not supported for CSD decode\n");
return sdError_Unsupported;
}
csd->erase_single_block = (bits[2] >> 14) & 0x1;
csd->erase_sector_size = (bits[2] >> 7) & 0x7f;
csd->write_protect_group_size = (bits[2] >> 0) & 0x7f;
csd->write_protect_group = (bits[3] >> 31) & 0x1;
csd->write_speed_factor = (bits[3] >> 26) & 0x7;
csd->write_block_len = (bits[3] >> 22) & 0xf;
csd->write_partial_blocks = (bits[3] >> 21) & 0x1;
csd->file_format_group = (bits[3] >> 15) & 0x1;
csd->copy_flag = (bits[3] >> 14) & 0x1;
csd->perm_write_prot = (bits[3] >> 13) & 0x1;
csd->temp_write_prot = (bits[3] >> 12) & 0x1;
csd->file_format = (bits[3] >> 10) & 0x3;
csd->crc = (bits[3] >> 1) & 0x7f;
if (csd->read_block_len != csd->write_block_len) {
ERROR("Different read/write block sizes not supported\n");
return sdError_Unsupported;
}
ci->block_size = csd->read_block_len;
INFO("capacity %u MiB block size = %u bytes\n",
(unsigned int)(ci->capacity / 1024 / 1024),
1 << ci->block_size);
/* FIXME: Check CRC */
return 0;
}
/*! Utility function for parsing CID fields */
static int sd_parse_cid(sd_card_info_t *ci, const uint32_t *bits)
{
sd_card_cid_t *cid = &ci->cid;
memset(cid, 0, sizeof(sd_card_cid_t));
TRACE("CID: %08X %08X %08X %08X\n",
(unsigned int)bits[0],
(unsigned int)bits[1],
(unsigned int)bits[2],
(unsigned int)bits[3]);
cid->manufacturer_id = (bits[0] >> 24) & 0xff;
cid->app_id[0] = (bits[0] >> 16) & 0xff;
cid->app_id[1] = (bits[0] >> 8) & 0xff;
cid->product_name[0] = (bits[0] >> 0) & 0xff;
cid->product_name[1] = (bits[1] >> 24) & 0xff;
cid->product_name[2] = (bits[1] >> 16) & 0xff;
cid->product_name[3] = (bits[1] >> 8) & 0xff;
cid->product_name[4] = (bits[1] >> 0) & 0xff;
cid->product_rev = (bits[2] >> 24) & 0xff;
cid->product_sn = (bits[2] << 8) & 0xffffff00;
cid->product_sn |= (bits[3] >> 24) & 0xff;
cid->mfg_date = (bits[3] >> 8) & 0xfff;
cid->crc = (bits[3] >> 1) & 0x7f;
INFO("SD mfg %02X app '%c%c' product '%.5s' rev %02X sn %08X mfg %02u/%04u\n",
cid->manufacturer_id,
cid->app_id[0], cid->app_id[1],
cid->product_name,
cid->product_rev,
(unsigned int)cid->product_sn,
(unsigned int)(cid->mfg_date & 0xf),
(unsigned int)((cid->mfg_date >> 8) + 2000));
/* FIXME: Check CRC */
return 0;
}
static int sd_wait_ready(void)
{
uint32_t timeout;
uint8_t in;
timeout = timer_get_tick_count() + TIMER_MILLIS(READY_TIMEOUT_MS);
do {
spi_read(&in, 1);
} while (in != 0xff && (int32_t)(timer_get_tick_count() - timeout) < 0);
return (in == 0xff) ? 0 : sdError_Timeout;
}
static void sd_deselect(void)
{
spi_deselect();
}
static int sd_select(void)
{
spi_select();
if (sd_wait_ready() == 0) {
return 0;
}
spi_deselect();
ERROR("Timeout waiting for card ready\n");
return sdError_Timeout;
}
static int sd_read_block(uint8_t *buf, unsigned int size)
{
uint32_t timeout;
uint8_t token, crc[2];
/* Wait for data start token */
timeout = timer_get_tick_count() + TIMER_MILLIS(READY_TIMEOUT_MS);
do {
spi_read(&token, 1);
} while (token == 0xff && (int32_t)(timer_get_tick_count() - timeout) < 0);
if (token != 0xfe) {
ERROR("No data token received\n");
return sdError_Timeout;
}
/* Read data */
spi_read(buf, size);
spi_read(crc, 2);
return 0;
}
static int sd_write_block(const uint8_t *buf, uint8_t token)
{
uint8_t crc[2] = {0xff, 0xff};
uint8_t resp;
if (sd_wait_ready() < 0) {
ERROR("Card not ready\n");
return sdError_Timeout;
}
/* Send token */
spi_write(&token, 1);
if (token != 0xfd) {
/* Send data, except for STOP_TRAN */
spi_write(buf, SD_SECTOR_SIZE);
spi_write(crc, 2); /* dummy */
/* Receive data response */
spi_read(&resp, 1);
if ((resp & 0x1f) != 0x05) {
ERROR("Bad response\n");
return sdError_BadResponse;
}
}
return 0;
}
static uint8_t sd_send_cmd(uint8_t cmd, uint32_t arg)
{
uint8_t res;
uint8_t buf[6];
int n;
if (cmd & 0x80) {
/* Send CMD55 prior to ACMD */
cmd &= 0x7f;
res = sd_send_cmd(CMD55, 0);
if (res > 1) {
return res;
}
}
/* Select the card and wait for ready except for abort */
if (cmd != CMD12) {
sd_deselect();
if (sd_select() < 0) {
return 0xff;
}
}
/* Build command */
buf[0] = 0x40 | cmd;
buf[1] = (uint8_t)(arg >> 24);
buf[2] = (uint8_t)(arg >> 16);
buf[3] = (uint8_t)(arg >> 8);
buf[4] = (uint8_t)(arg >> 0);
if (cmd == CMD0) {
buf[5] = 0x95; /* CRC for CMD0 */
} else if (cmd == CMD8) {
buf[5] = 0x87; /* CRC for CMD8 */
} else {
buf[5] = 0x01; /* Dummy CRC and stop */
}
spi_write(buf, sizeof(buf));
/* Receive command response */
if (cmd == CMD12) {
/* Skip first byte */
spi_read(&res, 1);
}
for (n = 0; n < MAX_RESPONSE_POLLS; n++) {
spi_read(&res, 1);
if (!(res & 0x80)) {
break;
}
}
return res;
}
static uint32_t sd_get_r7_resp(void)
{
uint8_t buf[4];
spi_read(buf, 4);
return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3] << 0);
}
int sd_open(void)
{
sd_card_info_t *ci = &sd_card_info;
uint32_t timeout;
uint8_t cmd;
uint32_t resp[4];
int err;
FUNCTION_TRACE;
spi_set_speed(SPI_SPEED_SLOW);
ci->type = sdCardType_None;
//ci->capacity = 0;
ci->total_sectors = 0;
ci->block_size = sdBlockSize_512;
/* Send dummy clocks with CS high (doing this sends 96 clocks) */
sd_deselect();
sd_get_r7_resp();
sd_get_r7_resp();
sd_get_r7_resp();
if (sd_send_cmd(CMD0,0) == 1) {
if (sd_send_cmd(CMD8, 0x1aa) == 1) {
uint32_t ocr = sd_get_r7_resp();
if (ocr == 0x000001aa) {
TRACE("SDv2 - R7 resp = 0x%08X\n", (unsigned int) ocr);
ci->type = sdCardType_SD2_0;
/* Wait for card ready */
timeout = timer_get_tick_count() + TIMER_MILLIS(INIT_TIMEOUT_MS);
while (sd_send_cmd(ACMD41, (1ul << 30)) > 0) {
if ((int32_t)(timer_get_tick_count() - timeout) >= 0) {
/* Init timed out - invalidate card */
ERROR("Init timed out\n");
ci->type = sdCardType_None;
}
}
if (ci->type) {
/* Read OCR */
if (sd_send_cmd(CMD58, 0) == 0) {
ocr = sd_get_r7_resp();
if (ocr & (1ul << 30)) {
/* Card is high capacity */
TRACE("SDHC\n");
ci->type = sdCardType_SDHC;
}
} else {
ERROR("Failed to read OCR\n");
ci->type = sdCardType_None;
}
}
}
} else {
/* Not SDv2 */
if (sd_send_cmd(ACMD41, 0) <= 1) {
TRACE("SDv1\n");
ci->type = sdCardType_SD1_x;
cmd = ACMD41;
} else {
TRACE("MMCv3\n");
ci->type = sdCardType_MMC;
cmd = CMD1;
}
/* Wait for card ready */
timeout = timer_get_tick_count() + TIMER_MILLIS(INIT_TIMEOUT_MS);
while (sd_send_cmd(cmd, 0) > 0) {
if ((int32_t)(timer_get_tick_count() - timeout) >= 0) {
/* Init timed out - invalidate card */
ERROR("Init timed out\n");
ci->type = sdCardType_None;
}
}
if (ci->type) {
/* Set block length */
if (sd_send_cmd(CMD16, SD_SECTOR_SIZE) > 0) {
ERROR("Failed to set block length\n");
ci->type = sdCardType_None;
}
}
}
}
if (ci->type) {
INFO("SD card ready (type %u)\n", ci->type);
/* Read and decode card info */
if (sd_send_cmd(CMD10, 0) == 0) {
err = sd_read_block((uint8_t*)&resp, sizeof(resp));
if (err < 0) {
ERROR("Read CID failed\n");
}
} else {
err = sdError_BadResponse;
}
if (err == 0) {
err = sd_parse_cid(ci, resp);
}
if (err == 0) {
if (sd_send_cmd(CMD9, 0) == 0) {
err = sd_read_block((uint8_t*)&resp, sizeof(resp));
if (err < 0) {
ERROR("Read CSD failed\n");
}
} else {
err = sdError_BadResponse;
}
}
if (err == 0) {
err = sd_parse_csd(ci, resp);
}
/* Switch to fast clock */
spi_set_speed(SPI_SPEED_FAST);
} else {
/* Card not present */
err = sdError_NoCard;
}
sd_deselect();
return err;
}
int sd_read(uint8_t *buf, uint32_t sector, uint32_t count)
{
sd_card_info_t *ci = &sd_card_info;
int err = 0;
if (ci->type == sdCardType_None) {
ERROR("No card\n");
return sdError_NoCard;
}
if (ci->type != sdCardType_SDHC) {
/* Convert sector to byte addressing (x512) */
sector <<= 9;
}
if (count == 1) {
/* Read single sector */
if (sd_send_cmd(CMD17, sector) == 0) {
err = sd_read_block(buf, SD_SECTOR_SIZE);
} else {
err = sdError_BadResponse;
}
} else {
/* Read multiple sectors */
if (sd_send_cmd(CMD18, sector) == 0) {
do {
err = sd_read_block(buf, SD_SECTOR_SIZE);
if (err < 0) {
break;
}
buf += SD_SECTOR_SIZE;
} while (--count);
/* Send CMD12 stop transmission */
if (err == 0) {
err = sd_send_cmd(CMD12, 0);
}
} else {
err = sdError_BadResponse;
}
}
sd_deselect();
return err;
}
int sd_write(const uint8_t *buf, uint32_t sector, uint32_t count)
{
sd_card_info_t *ci = &sd_card_info;
int err = 0;
if (ci->type == sdCardType_None) {
ERROR("No card\n");
return sdError_NoCard;
}
if (ci->type != sdCardType_SDHC) {
/* Convert sector to byte addressing (x512) */
sector <<= 9;
}
if (count == 1) {
/* Write single sector */
if (sd_send_cmd(CMD24, sector) == 0) {
err = sd_write_block(buf, 0xfe);
} else {
err = sdError_BadResponse;
}
} else {
if (ci->type == sdCardType_SD1_x || ci->type == sdCardType_SD2_0 || ci->type == sdCardType_SDHC) {
/* Pre-defined sector count */
sd_send_cmd(ACMD23, count);
}
/* Write multiple sectors */
if (sd_send_cmd(CMD25, sector) == 0) {
do {
err = sd_write_block(buf, 0xfc);
if (err < 0) {
break;
}
buf += SD_SECTOR_SIZE;
} while (--count);
/* Send STOP_TRAN */
if (err == 0) {
err = sd_write_block(0, 0xfd);
}
} else {
err = sdError_BadResponse;
}
}
sd_deselect();
return err;
}
const sd_card_info_t* sd_get_card_info(void)
{
return &sd_card_info;
}

120
examples/spisd/sd.h Normal file
View File

@ -0,0 +1,120 @@
/*
* SPI SD device driver for K1208/Amiga 1200
*
* Copyright (C) 2018 Mike Stirling
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef SD_H_
#define SD_H_
#include <stdint.h>
#define SD_SECTOR_SIZE 512
#define SD_SECTOR_SHIFT 9
typedef enum {
sdError_OK = 0,
sdError_NoCard = -1,
sdError_Timeout = -2,
sdError_BadResponse = -3,
sdError_Unsupported = -4,
} sd_error_t;
typedef enum {
sdCardType_None = 0,
sdCardType_SD1_x,
sdCardType_SD2_0,
sdCardType_SDHC,
sdCardType_MMC,
} sd_card_type_t;
typedef enum {
sdBlockSize_1 = 0,
sdBlockSize_2,
sdBlockSize_4,
sdBlockSize_8,
sdBlockSize_16,
sdBlockSize_32,
sdBlockSize_64,
sdBlockSize_128,
sdBlockSize_256,
sdBlockSize_512,
sdBlockSize_1024,
sdBlockSize_2048,
sdBlockSize_4096,
sdBlockSize_8192,
sdBlockSize_16384,
} sd_blocksize_t;
typedef struct {
uint8_t csd_structure; /*!< CSD structure */
uint8_t spec_version; /*!< MMC spec version (not SD) */
uint8_t taac; /*!< data read access time 1 */
uint8_t nsac; /*!< data read access time 2 in CLK cycles */
uint8_t max_transfer_rate; /*!< max data transfer rate in MHz */
uint16_t card_command_classes; /*!< card command classes */
uint8_t read_block_len; /*!< maximum read data block length */
uint8_t read_partial_blocks; /*!< partial blocks for read allowed */
uint8_t write_block_misalign; /*!< write block misalignment */
uint8_t read_block_misalign; /*!< read block misalignment */
uint8_t dsr_implemented; /*!< DSR implemented */
uint32_t device_size; /*!< device size */
uint8_t max_read_current_vdd_min; /*!< max read current at Vdd min (not SDHC) */
uint8_t max_read_current_vdd_max; /*!< max read current at Vdd max (not SDHC) */
uint8_t max_write_current_vdd_min; /*!< max write current at Vdd min (not SDHC) */
uint8_t max_write_current_vdd_max; /*!< max write current at Vdd max (not SDHC) */
uint8_t device_size_mult; /*!< device size multiplier (not SDHC) */
uint8_t erase_single_block; /*!< erase single block enable */
uint8_t erase_sector_size; /*!< erase sector size */
uint8_t write_protect_group_size; /*!< write protect group size */
uint8_t write_protect_group; /*!< write protect group enable */
uint8_t write_speed_factor; /*!< write speed factor */
uint8_t write_block_len; /*!< max write data block length */
uint8_t write_partial_blocks; /*!< partial blocks for write allowed */
uint8_t file_format_group; /*!< file format group */
uint8_t copy_flag; /*!< copy flag */
uint8_t perm_write_prot; /*!< permanent write protection */
uint8_t temp_write_prot; /*!< temporary write protection */
uint8_t file_format; /*!< file format */
uint8_t crc; /*!< CRC7 */
} sd_card_csd_t;
typedef struct {
uint8_t manufacturer_id; /*!< manufacturer ID byte */
uint8_t app_id[2]; /*!< OEM/application ID, ASCII */
uint8_t product_name[5]; /*!< product name, ASCII */
uint8_t product_rev; /*!< product revision, BCD */
uint32_t product_sn; /*!< product serial number */
uint16_t mfg_date; /*!< date of manufacture (0xYYM offset from 2000, e.g. 0x014 = Apr 2001) */
uint8_t crc; /*!< CRC7 */
} sd_card_cid_t;
typedef struct {
sd_card_type_t type;
//uint64_t capacity;
uint32_t total_sectors;
sd_blocksize_t block_size;
sd_card_csd_t csd;
sd_card_cid_t cid;
} sd_card_info_t;
int sd_open(void);
void sd_close(void);
int sd_read(uint8_t *buf, uint32_t sector, uint32_t count);
int sd_write(const uint8_t *buf, uint32_t sector, uint32_t count);
const sd_card_info_t* sd_get_card_info(void);
#endif

46
examples/spisd/timer.c Normal file
View File

@ -0,0 +1,46 @@
/*
* SPI NET ENC28J60 device driver for K1208/Amiga 1200
*
* Copyright (C) 2018 Mike Stirling
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdint.h>
//#include "common.h"
#include "timer.h"
static volatile uint8_t * const todl = (volatile uint8_t*)0xbfe801;
static volatile uint8_t * const todm = (volatile uint8_t*)0xbfe901;
static volatile uint8_t * const todh = (volatile uint8_t*)0xbfea01;
uint32_t timer_get_tick_count(void)
{
uint8_t l,m,h;
/* TOD registers latch on reading MSB, unlatch on reading LSB */
h = *todh;
m = *todm;
l = *todl;
return ((uint32_t)h << 16) | ((uint32_t)m << 8) | (uint32_t)l;
}
void timer_delay(uint32_t ticks)
{
uint32_t timeout = timer_get_tick_count() + ticks;
while ((int32_t)(timer_get_tick_count() - timeout) < 0) {
}
}

51
examples/spisd/timer.h Normal file
View File

@ -0,0 +1,51 @@
/*
* SPI NET ENC28J60 device driver for K1208/Amiga 1200
*
* Copyright (C) 2018 Mike Stirling
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TIMER_H_
#define TIMER_H_
#include <stdint.h>
#ifndef TIMER_TICK_FREQ
/*! Tick frequency in Hz - defines timer resolution */
#define TIMER_TICK_FREQ 50
#endif
/*! Helper macro calculates number of ticks in specified ms (rounds up) */
#define TIMER_MILLIS(ms) (((uint32_t)(ms) * (TIMER_TICK_FREQ) + 999ul) / 1000ul)
/*! Helper macro calculates number of ticks in specified s */
#define TIMER_SECONDS(s) ((uint32_t)(s) * (TIMER_TICK_FREQ))
/*! Helper macro converts ticks back to milliseconds (rounds down) */
#define TIMER_TO_MILLIS(ticks) ((uint32_t)(ticks) * 1000ul / (TIMER_TICK_FREQ))
/*! Helper macro converts ticks back to seconds (rounds down) */
#define TIMER_TO_SECONDS(ticks) ((uint32_t)(ticks) / (TIMER_TICK_FREQ))
/*!
* Returns current 32-bit tick counter in
* increments of TIMER_TICK_FREQ
*
* \return Current tick count
*/
uint32_t timer_get_tick_count(void);
void timer_delay(uint32_t ticks);
#endif /* TIMER_H_ */

View File

@ -0,0 +1,24 @@
# Assembly instructions
The assembly of the adapter is very simple.
You need a parallel port connector and an Arduino Nano.
Connect the parallel port to the Nano as follows:
| Par. pin # | Par. pin name | Nano pin # | Nano pin name |
|-----------:|--------------:|-----------:|--------------:|
| 2 | D0 | 26 | A0 |
| 3 | D1 | 25 | A1 |
| 4 | D2 | 24 | A2 |
| 5 | D3 | 23 | A3 |
| 6 | D4 | 22 | A4 |
| 7 | D5 | 21 | A5 |
| 8 | D6 | 9 | D6 |
| 9 | D7 | 10 | D7 |
| 11 | BUSY | 7 | D4 |
| 12 | POUT | 8 | D5 |
| 20 | GND | 4 | GND |
Note that pins D0-D5 are connected to port C on the AVR (pins A0-A5 on the Nano) and pins D6-D7 are connected to port D on the AVR (pins D6-D7 on the Nano).
This is identical to how the [plipbox](https://github.com/cnvogelg/plipbox) connects the parallel port to the Arduino Nano, so it should be possible to use an existing plipbox PCB for this adapter (although this has not been tested).
If you are connecting an SD card module to the adapter then connect pin 13 on the parallel port connector (SEL) to the chip select (CS) pin on the SD card module, and connect the other SPI pins as you normally would connect an SPI module to the Arduino Nano.

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
images/connected_amiga.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
images/sdcard_mounted.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

15
spi-lib/README.md Normal file
View File

@ -0,0 +1,15 @@
# Amiga spi-lib
The functionality of the SPI adapter is exposed through the spi-lib source code library.
Just include the three files in the project you are building.
Compilation has been tested to work with VBCC. I would prefer to use gcc, but I haven't gotten around to set it up on my machine. If you know how to set up gcc for cross compiling to Amiga then please let me know.
The spi-lib exposes a small number of methods:
- spi_initialize() - initializes the library. Must be called before any other method.
- spi_shutdown() - should be called when shutting down to reset the parallel port to its unused state.
- spi_speed(long speed) - the SPI adapter can run in slow (250 kHz) or fast (8 MHz) mode. A SPI peripheral may need to run in the slow mode during initialization. The speed is set to slow by default when spi-lib is initialized.
- spi_select() / spi_deselect() - activates/deactivates the SPI chip select pin.
- spi_read(char *buf, long size) - reads size bytes (1 <= size <= 8192) from the SPI peripheral and writes them to the buffer pointed to by buf.
- spi_write(char *buf, long size) - writes size bytes (1 <= size <= 8192) to the SPI peripheral that are taken from the buffer pointed to by buf.

190
spi-lib/spi.c Normal file
View File

@ -0,0 +1,190 @@
#include <exec/types.h>
#include <proto/exec.h>
#include "spi.h"
#define CIAB_PRTRSEL 2
#define CIAB_PRTRPOUT 1
#define CIAB_PRTRBUSY 0
#define CS_BIT CIAB_PRTRSEL
#define CLOCK_BIT CIAB_PRTRPOUT
#define IDLE_BIT CIAB_PRTRBUSY
#define CS_MASK (1 << CS_BIT)
#define CLOCK_MASK (1 << CLOCK_BIT)
#define IDLE_MASK (1 << IDLE_BIT)
static volatile UBYTE *cia_a_prb = (volatile UBYTE *)0xbfe101;
static volatile UBYTE *cia_a_ddrb = (volatile UBYTE *)0xbfe301;
static volatile UBYTE *cia_b_pra = (volatile UBYTE *)0xbfd000;
static volatile UBYTE *cia_b_ddra = (volatile UBYTE *)0xbfd200;
static long current_speed = SPI_SPEED_SLOW;
void spi_initialize()
{
// Should allocate the parallel port in a system friendly way?
*cia_b_pra = (*cia_b_pra & ~IDLE_MASK) | (CS_MASK | CLOCK_MASK);
*cia_b_ddra = (*cia_b_ddra & ~IDLE_MASK) | (CS_MASK | CLOCK_MASK);
*cia_a_prb = 0xff;
*cia_a_ddrb = 0;
}
void spi_shutdown()
{
*cia_b_ddra &= 0xf8;
*cia_a_ddrb = 0;
}
static void wait_until_idle()
{
// This will block forever if the adapter is not present.
// TODO: Should eventually timeout and give up.
UBYTE ctrl = *cia_b_pra;
while (ctrl & IDLE_MASK)
{
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
ctrl = *cia_b_pra;
}
}
void spi_set_speed(long speed)
{
wait_until_idle();
*cia_a_ddrb = 0xff;
*cia_a_prb = speed == SPI_SPEED_FAST ? 0xc1 : 0xc0;
*cia_b_pra ^= CLOCK_MASK;
*cia_a_ddrb = 0;
current_speed = speed;
}
void spi_select(void)
{
*cia_b_pra &= ~CS_MASK;
}
void spi_deselect(void)
{
*cia_b_pra |= CS_MASK;
}
// A slow SPI transfer takes 32 us (8 bits times 4us (250kHz)).
// An E-cycle is 1.4 us.
static void wait_40_us()
{
UBYTE tmp;
for (int i = 0; i < 32; i++)
tmp = *cia_b_pra;
}
static void spi_write_slow(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
{
wait_until_idle();
*cia_a_ddrb = 0xff;
UBYTE ctrl = *cia_b_pra;
if (size <= 64) // WRITE1: 00xxxxxx
{
*cia_a_prb = (size - 1) & 0x3f;
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
}
else // WRITE2: 100xxxxx xxxxxxxx
{
*cia_a_prb = 0x80 | (((size - 1) >> 8) & 0x1f);
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
}
for (int i = 0; i < size; i++)
{
*cia_a_prb = *buf++;
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
wait_40_us();
}
*cia_a_ddrb = 0;
}
static void spi_read_slow(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
wait_until_idle();
*cia_a_ddrb = 0xff;
UBYTE ctrl = *cia_b_pra;
if (size <= 64) // READ1: 01xxxxxx
{
*cia_a_prb = 0x40 | ((size - 1) & 0x3f);
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
}
else // READ2: 101xxxxx xxxxxxxx
{
*cia_a_prb = 0xa0 | (((size - 1) >> 8) & 0x1f);
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
}
*cia_a_ddrb = 0;
for (int i = 0; i < size; i++)
{
wait_40_us();
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
*buf++ = *cia_a_prb;
}
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
}
extern void spi_read_fast(__reg("a0") UBYTE *buf, __reg("d0") ULONG size);
extern void spi_write_fast(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size);
void spi_read(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
if (current_speed == SPI_SPEED_FAST)
spi_read_fast(buf, size);
else
spi_read_slow(buf, size);
}
void spi_write(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
{
if (current_speed == SPI_SPEED_FAST)
spi_write_fast(buf, size);
else
spi_write_slow(buf, size);
}

15
spi-lib/spi.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef SPI_H_
#define SPI_H_
#define SPI_SPEED_SLOW 0
#define SPI_SPEED_FAST 1
void spi_initialize();
void spi_shutdown();
void spi_set_speed(long speed);
void spi_select();
void spi_deselect();
void spi_read(__reg("a0") unsigned char *buf, __reg("d0") unsigned long size);
void spi_write(__reg("a0") const unsigned char *buf, __reg("d0") unsigned long size);
#endif

181
spi-lib/spi_low.asm Normal file
View File

@ -0,0 +1,181 @@
XDEF _spi_read_fast
XDEF _spi_write_fast
CODE
CIAB_PRTRSEL equ (2)
CIAB_PRTRPOUT equ (1)
CIAB_PRTRBUSY equ (0)
CIAA_BASE equ $bfe001
CIAB_BASE equ $bfd000
CIAPRA equ $0000
CIAPRB equ $0100
CIADDRA equ $0200
CIADDRB equ $0300
Disable equ -120
Enable equ -126
CS_BIT equ CIAB_PRTRSEL
CLOCK_BIT equ CIAB_PRTRPOUT
IDLE_BIT equ CIAB_PRTRBUSY
; a0 = unsigned char *buf
; d0 = unsigned int size
; assert: 1 <= size < 2^13 (three top bits are zeros)
_spi_write_fast:
and #$1fff,d0
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
lea.l CIAA_BASE+CIAPRB,a1 ; Data
lea.l CIAB_BASE+CIAPRA,a5 ; Control pins
.idle_wait: move.b (a5),d2
btst #IDLE_BIT,d2
beq.b .is_idle
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .idle_wait
.is_idle: move.b #$ff,$200(a1) ; Start driving data pins
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; WRITE2 = 100xxxxx xxxxxxxx
move d0,d1
lsr #8,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
.one_byte_cmd: ; WRITE1 = 00xxxxxx
move.b d0,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
.cmd_sent: addq #1,d0 ; d0 = size
btst #0,d0
beq.b .even
move.b (a0)+,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
.even: lsr #1,d0
beq.b .done
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
.loop: move.b (a0)+,(a1)
move.b d1,(a5)
move.b (a0)+,(a1)
move.b d2,(a5)
dbra d0,.loop
.done: move.b #0,$200(a1) ; Stop driving data pins
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
rts
; a0 = unsigned char *buf
; d0 = unsigned int size
; assert: 1 <= size < 2^13 (three top bits are zeros)
_spi_read_fast:
and #$1fff,d0
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
lea.l CIAA_BASE+CIAPRB,a1 ; Data
lea.l CIAB_BASE+CIAPRA,a5 ; Control pins
.idle_wait: move.b (a5),d2
btst #IDLE_BIT,d2
beq.b .is_idle
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .idle_wait
.is_idle: move.b #$ff,$200(a1) ; Start driving data pins
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; READ2 = 101xxxxx xxxxxxxx
move d0,d1
lsr #8,d1
or.b #$a0,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
.one_byte_cmd: ; READ1 = 01xxxxxx
move.b d0,d1
or.b #$40,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
move.b d2,(a5)
.cmd_sent: move.b #0,$200(a1) ; Stop driving data pins
addq #1,d0 ; d0 = size
btst #0,d0
beq.b .even
bchg #CLOCK_BIT,d2
move.b d2,(a5)
move.b (a1),(a0)+
.even: lsr #1,d0
beq.b .done
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
.loop: move.b d1,(a5)
move.b (a1),(a0)+
move.b d2,(a5)
move.b (a1),(a0)+
dbra d0,.loop
.done: bchg #CLOCK_BIT,d2
move.b d2,(a5)
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
rts