Compare commits

...

15 Commits
v1.0 ... master

Author SHA1 Message Date
Patrik Axelsson
d56fbaaaf4 Discard undefined byte at end of write multiple block
This byte needs to be read before the card goes busy, but more importantly,
it would otherwise be read out by sd_wait_ready():

For cards where this byte is 0xff, sd_wait_ready() would think the card is
immediately ready and let the code send it the next command, while the read
actually made the card go busy.

This makes the card end up in a state the code cannot recover it from - even
if you reboot the machine so spisd.device is restarted, it still requires
the card to be power cycled by ejecting and re-inserting it.

How to reproduce for affected cards:
1. Use spisd.device v2.2
2. Mount SD0:
3. Write a file larger than 512Bytes to trigger a write multiple block
4. Write or read another file, this will trigger the next command and show
   the issue as a read or write error
2023-08-30 16:56:48 +02:00
Stefan
a8488355cb Fixed option
vbcc uses -O + number only
2023-03-15 14:13:28 +01:00
Patrik Axelsson
e9278dcc7a Move version string related values to defines in one file 2023-03-09 00:46:14 +01:00
Patrik Axelsson
c64fc9f5a3 Bump version string to match next repo tag 2023-03-09 00:46:14 +01:00
Patrik Axelsson
dadaab55d8 Fix size calculation for 32GB+ SD cards 2023-03-09 00:46:14 +01:00
Patrik Axelsson
4ed29c88d6 Report a sane geometry for the CHS part 2023-03-09 00:46:14 +01:00
Patrik Axelsson
14b7884aef Do not lock up SD card on read/writes of zero size
This is for example triggered by Check4GB when it attempts to TD64
capabilities in the device.
2023-03-09 00:46:14 +01:00
Patrik Axelsson
dc0f8cb0cd Add NSD and TD64 command support for 4GB+ SD cards 2023-03-09 00:46:14 +01:00
Patrik Axelsson
6cd50a311a Use NDK 3.2 headers also with kick13 target
This avoids pasting a lot from the NDK includes into the source.

If you want to avoid const warnings for some functions like
exec.library/RawDoFmt(), also use the m68k-amigaos VBCC target headers,
which also enables you to write mixed 1.3/2.0+ code where you can call
2.0+ functions if needed.
2023-03-09 00:46:14 +01:00
Patrik Axelsson
0ae311f713 Fix version and missing include 2023-03-09 00:46:14 +01:00
Niklas Ekström
e377afae11 Add support for RP2040 microcontroller 2022-10-18 10:40:41 +02:00
Nicolas Baumgardt
30736877bd Update spi_low.asm
There were read/write issues on A3000(D) and A4000(D) machines before
this change.
2022-08-31 21:43:06 +02:00
Niklas Ekström
f7493de0b0 Revert pin assignment of BUSY and POUT signals
The assignment of the BUSY and POUT pins to the microcontroller had
changed from the previous version to the new version. User amigos500 on
the a1k forum noted that if the assignment of those two pins had not
changed, then it would be easier to patch an old PCB to work with
the updated design.

After testing, it seems that the old pin assignment of BUSY and POUT
works equally well with the updated design, and I therefore reverted
the assignment back.
2021-07-22 21:48:59 +02:00
Niklas Ekström
27d6ad85fa Update README and assembly instructions 2021-07-20 19:21:29 +02:00
Niklas Ekström
e766d41e37 Add support for eject/insert sd card 2021-07-20 19:21:24 +02:00
20 changed files with 1282 additions and 494 deletions

View File

@ -2,6 +2,22 @@
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.
## Please note: new hardware incompatible version!
What you are looking at is a new version of this design. It is hardware
incompatible with the original version. Updating the software without making
the corresponding hardware changes will not work.
Old versions of this design are available as tags/releases.
### Changes with latest version
Support has been added that allows the SPI device to interrupt the Amiga via the adapter.
The SD card example driver has been modified to use this interrupt so that the SD card
can be inserted/ejected as part of normal operations.
A video demonstrating this can be seen here: https://www.youtube.com/watch?v=zJDtiFQgrn0.
## What parts make up the project?
- An AVR microcontroller, provided by an Arduino Nano board

View File

@ -1,119 +1,147 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#include <avr/interrupt.h>
#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
// Par P-name Arduino AVR SD Name Description
// 2 D0 A0 PC0 Parallel port data lines
// 3 D1 A1 PC1
// 4 D2 A2 PC2
// 5 D3 A3 PC3
// 6 D4 A4 PC4
// 7 D5 A5 PC5
// 8 D6 D6 PD6
// 9 D7 D7 PD7
// 10 ACK D9 PB1 IRQ Interrupt request to Amiga
// 11 BUSY D4 PD4 ACT Indicate command running
// 12 POUT D5 PD5 CLK Clock to advance command
// 13 SEL D2 PD2 REQ Amiga wants to execute command
// D3 PD3 CD CP Card Present
// D10 PB2 SS
// D11 PB3 MOSI
// D12 PB4 MISO
// D13 PB5 SCK
// SPI pins in port B.
#define SCK_BIT 5
#define MISO_BIT 4
#define MOSI_BIT 3
#define SS_BIT 2
// Pins in port B.
#define SCK_BIT 5 // Output.
#define MISO_BIT 4 // Input.
#define MOSI_BIT 3 // Output.
#define SS_BIT_n 2 // Output, active low.
#define IRQ_BIT_n 1 // Output, active low, open collector.
// These bits are in port D.
#define IDLE_BIT 4
#define CLOCK_BIT 5
// Pins in port D.
#define CLK_BIT 5 // Input.
#define ACT_BIT_n 4 // Output, active low.
#define CP_BIT_n 3 // Input, active low, internal pull-up enabled.
#define REQ_BIT_n 2 // Input, active low, internal pull-up enabled.
int main()
void start_command()
{
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 dval;
uint8_t cval;
uint8_t next_port_d;
uint8_t next_port_c;
uint16_t byte_count;
main_loop:
if (PIND & (1 << CLOCK_BIT))
dval = PIND;
cval = PINC;
if (!(dval & 0x80)) // READ1 or WRITE1
{
while (PIND & (1 << CLOCK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
;
}
byte_count = cval;
if (!(PIND & 0x80))
{
if (PIND & 0x40)
goto do_read1;
else
goto do_write1;
}
PORTD &= ~(1 << ACT_BIT_n);
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)
if (dval & 0x40)
goto do_read;
else
goto do_write;
}
else if ((w & 0x3e) == 0) // SPEED
else if (!(dval & 0x40)) // READ2 or WRITE2
{
if (w & 1) // Fast
SPCR = (1 << SPE) | (1 << MSTR);
else // Slow
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
byte_count = cval << 7;
PORTD &= ~(1 << ACT_BIT_n);
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLK_BIT)))
;
}
dval = PIND;
cval = PINC;
byte_count |= (dval & 0x40) | cval;
if (dval & 0x80)
goto do_read;
else
goto do_write;
}
else
{
uint8_t cmd = (cval & 0x3e) >> 1;
if (cmd == 0) // SPI_SELECT
{
if (cval & 1) // Select
PORTB &= ~(1 << SS_BIT_n);
else // Deselect
PORTB |= (1 << SS_BIT_n);
goto main_loop;
PORTD &= ~(1 << ACT_BIT_n);
}
else if (cmd == 1) // CARD_PRESENT
{
DDRB &= ~(1 << IRQ_BIT_n);
PORTD &= ~(1 << ACT_BIT_n);
do_read1:
byte_count = PINC;
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLK_BIT)))
;
}
DDRD = 0xc0 | (1 << ACT_BIT_n);
DDRC = 0x3f;
if (!(PIND & (1 << CP_BIT_n)))
PORTC = 1;
else
PORTC = 0;
}
else if (cmd == 2) // SPEED
{
if (cval & 1) // Fast
SPCR = (1 << SPE) | (1 << MSTR);
else // Slow
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
PORTD &= ~(1 << ACT_BIT_n);
}
while (1)
;
}
do_read:
SPDR = 0xff;
v = PIND;
PORTD = (dval & 0xc0) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRD = 0xc0 | (1 << ACT_BIT_n);
PORTD = (v & 0xc0) | (1 << IDLE_BIT);
DDRD = 0xc0 | (1 << IDLE_BIT);
PORTC = byte_count & 0x3f;
PORTC = cval;
DDRC = 0x3f;
read_loop:
@ -121,23 +149,23 @@ read_loop:
;
next_port_c = SPDR;
next_port_d = (next_port_c & 0xc0) | (1 << IDLE_BIT);
next_port_d = (next_port_c & 0xc0) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
if (v & (1 << CLOCK_BIT))
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
while (!(PIND & (1 << CLK_BIT)))
;
}
PORTD = next_port_d;
PORTC = next_port_c;
v = PIND;
dval = PIND;
if (byte_count)
{
@ -146,46 +174,24 @@ read_loop:
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;
while (1)
;
do_write:
PORTD = (1 << IDLE_BIT);
write_loop:
if (v & (1 << CLOCK_BIT))
if (dval & (1 << CLK_BIT))
{
while (PIND & (1 << CLOCK_BIT))
while (PIND & (1 << CLK_BIT))
;
}
else
{
while (!(PIND & (1 << CLOCK_BIT)))
while (!(PIND & (1 << CLK_BIT)))
;
}
v = PIND;
SPDR = (v & 0xc0) | PINC;
dval = PIND;
SPDR = (dval & 0xc0) | PINC;
while (!(SPSR & (1 << SPIF)))
;
@ -198,9 +204,79 @@ write_loop:
goto write_loop;
}
PORTD = 0;
goto main_loop;
return 0;
while (1)
;
}
void busy_wait()
{
while (1)
;
}
// Interrupt handler for REQ signal changes (INT0).
ISR(INT0_vect, ISR_NAKED)
{
void (*next_fn)();
if (PIND & (1 << REQ_BIT_n))
{
DDRD = (1 << ACT_BIT_n);
PORTD = (1 << ACT_BIT_n) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRC = 0;
PORTC = 0;
EIMSK |= (1 << 1);
next_fn = &busy_wait;
}
else
{
EIMSK &= ~(1 << 1);
next_fn = &start_command;
}
uint16_t fn_int = (uint16_t)next_fn;
uint16_t sp = ((SPH << 8) | SPL) + 1;
uint8_t *p = (uint8_t *)sp;
*p++ = fn_int >> 8;
*p++ = fn_int & 0xff;
reti();
}
// Interrupt handler for CP signal changes (INT1).
ISR(INT1_vect, ISR_NAKED)
{
DDRB |= (1 << IRQ_BIT_n);
reti();
}
void main()
{
DDRB = (1 << SCK_BIT) | (1 << MOSI_BIT) | (1 << SS_BIT_n);
PORTB = (1 << SS_BIT_n);
// SPI enabled, master, fosc/64 = 250 kHz
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 << SPR0);
SPSR |= (1 << SPI2X);
DDRD = (1 << ACT_BIT_n);
PORTD = (1 << ACT_BIT_n) | (1 << CP_BIT_n) | (1 << REQ_BIT_n);
DDRC = 0;
PORTC = 0;
// Enable interrupts
EICRA = (1 << 2) | (1 << 0);
EIFR = (1 << 1) | (1 << 0);
EIMSK = (1 << 1) | (1 << 0);
sei();
while (1)
;
}

View File

@ -1 +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
vc romtag.c version.c device.c sd.c timer.c ../../spi-lib/spi.c ../../spi-lib/spi_low.asm -I../../spi-lib -O2 -nostdlib -lamiga -o spisd.device

View File

@ -1,253 +1,429 @@
/*
* SPI SD device driver for K1208/Amiga 1200
* Written by Niklas Ekström in July 2021.
*
* 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.
* Previous version of this file (device.c) used code written by
* Mike Sterling in 2018 for the SPI SD device driver for K1208/Amiga 1200.
*
* In order to handle sd card removal this file was rewritten almost entirely.
*/
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <exec/execbase.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <libraries/dos.h>
#include <devices/timer.h>
#include <devices/trackdisk.h>
#include <devices/newstyle.h>
#include <proto/exec.h>
#include <proto/alib.h>
#include "version.h"
#include "sd.h"
#include "spi.h"
#ifndef TD_GETGEOMETRY
// Needed to compile with AmigaOS 1.3 headers.
#define TD_GETGEOMETRY (CMD_NONSTD+13)
#define TASK_STACK_SIZE 2048
#define TASK_PRIORITY 10
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
#define DEBOUNCE_TIMEOUT_US 100000
char device_name[] = "spisd.device";
char id_string[] = "spisd 0.3 (3 May 2020)";
#define SIGB_CARD_CHANGE 30
#define SIGB_OP_REQUEST 29
#define SIGB_TIMER 28
#define SIGF_CARD_CHANGE (1 << SIGB_CARD_CHANGE)
#define SIGF_OP_REQUEST (1 << SIGB_OP_REQUEST)
#define SIGF_OP_TIMER (1 << SIGB_TIMER)
// How much of struct NSDeviceQueryResult we use/need. It could be extended
// and we don't want that to change the behaviour of the code.
#define NSD_QUERY_RESULT_LENGTH_REQUIRED 16
struct ExecBase *SysBase;
BPTR saved_seg_list;
static BPTR saved_seg_list;
static struct timerequest tr;
static struct Task *task;
static struct MsgPort mp;
static struct MsgPort timer_mp;
static volatile BOOL card_present;
static volatile BOOL card_opened;
static volatile ULONG card_change_num;
BOOL is_open;
static struct Interrupt *remove_int;
static struct IOStdReq *change_int;
static struct Library *init_device(__reg("a6") struct ExecBase *sys_base, __reg("a0") BPTR seg_list, __reg("d0") struct Library *dev)
static uint32_t device_get_geometry(struct IOStdReq *ior)
{
SysBase = *(struct ExecBase **)4;
saved_seg_list = seg_list;
struct DriveGeometry *geom = (struct DriveGeometry*)ior->io_Data;
const sd_card_info_t *ci = sd_get_card_info();
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;
if (ci->type == sdCardType_None)
return TDERR_DiskChanged;
is_open = FALSE;
return dev;
geom->dg_SectorSize = 1 << ci->block_size;
geom->dg_TotalSectors = ci->total_sectors;
geom->dg_Cylinders = geom->dg_TotalSectors / 4096;
geom->dg_CylSectors = 4096;
geom->dg_Heads = 16;
geom->dg_TrackSectors = geom->dg_Cylinders / 16;
geom->dg_BufMemType = MEMF_PUBLIC;
geom->dg_DeviceType = DG_DIRECT_ACCESS;
geom->dg_Flags = DGF_REMOVABLE;
return 0;
}
static BPTR expunge(__reg("a6") struct Library *dev)
static void handle_changed()
{
if (dev->lib_OpenCnt != 0)
{
dev->lib_Flags |= LIBF_DELEXP;
return 0;
}
// Wait to debounce the card detect switch.
tr.tr_node.io_Command = TR_ADDREQUEST;
tr.tr_time.tv_secs = 0;
tr.tr_time.tv_micro = DEBOUNCE_TIMEOUT_US;
DoIO((struct IORequest *)&tr);
spi_shutdown();
int res = spi_get_card_present();
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;
if (res == 1 && sd_open() == 0)
card_opened = TRUE;
else
card_opened = FALSE;
Forbid();
card_present = res == 1;
card_change_num++;
Permit();
if (remove_int)
Cause(remove_int);
if (change_int)
Cause((struct Interrupt *)change_int->io_Data);
}
static void open(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior, __reg("d0") ULONG unitnum, __reg("d1") ULONG flags)
static uint32_t offset_to_sd_sectors(uint32_t high_offset, uint32_t low_offset)
{
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;
return (high_offset << (32 - SD_SECTOR_SHIFT)) | (low_offset >> SD_SECTOR_SHIFT);
}
static BPTR close(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ior)
static void process_request(struct IOStdReq *ior)
{
ior->io_Device = NULL;
ior->io_Unit = NULL;
if (!card_present)
ior->io_Error = TDERR_DiskChanged;
else if (!card_opened)
ior->io_Error = TDERR_NotSpecified;
else
{
switch (ior->io_Command)
{
case TD_GETGEOMETRY:
ior->io_Error = device_get_geometry(ior);
break;
dev->lib_OpenCnt--;
case TD_FORMAT:
case CMD_WRITE:
ior->io_Actual = 0;
case TD_FORMAT64:
case TD_WRITE64:
case NSCMD_TD_FORMAT64:
case NSCMD_TD_WRITE64:
if (sd_write((uint8_t *)ior->io_Data, offset_to_sd_sectors(ior->io_Actual, ior->io_Offset), ior->io_Length >> SD_SECTOR_SHIFT) == 0)
ior->io_Actual = ior->io_Length;
else
ior->io_Error = TDERR_NotSpecified;
break;
if (dev->lib_OpenCnt == 0 && (dev->lib_Flags & LIBF_DELEXP))
return expunge(dev);
case CMD_READ:
ior->io_Actual = 0;
case TD_READ64:
case NSCMD_TD_READ64:
if (sd_read((uint8_t *)ior->io_Data, offset_to_sd_sectors(ior->io_Actual, ior->io_Offset), ior->io_Length >> SD_SECTOR_SHIFT) == 0)
ior->io_Actual = ior->io_Length;
else
ior->io_Error = TDERR_NotSpecified;
break;
}
}
return 0;
ReplyMsg(&ior->io_Message);
}
static uint32_t device_get_geometry(struct IOStdReq *iostd)
static void task_run()
{
struct DriveGeometry *geom = (struct DriveGeometry*)iostd->io_Data;
const sd_card_info_t *ci = sd_get_card_info();
if (card_present && sd_open() == 0)
card_opened = TRUE;
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;
}
while (1)
{
ULONG sigs = Wait(SIGF_CARD_CHANGE | SIGF_OP_REQUEST);
if (sigs & SIGF_CARD_CHANGE)
handle_changed();
if (sigs & SIGF_OP_REQUEST)
{
BOOL first = TRUE;
struct IOStdReq *ior;
while ((ior = (struct IOStdReq *)GetMsg(&mp)))
{
if (!first && (SetSignal(0, SIGF_CARD_CHANGE) & SIGF_CARD_CHANGE))
handle_changed();
process_request(ior);
first = FALSE;
}
}
}
}
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IORequest *ioreq)
static void change_isr()
{
struct IOStdReq *iostd = (struct IOStdReq*)ioreq;
Signal(task, SIGF_CARD_CHANGE);
}
if (ioreq == NULL) {
return;
}
static const UWORD supported_commands[] =
{
CMD_RESET,
CMD_READ,
CMD_WRITE,
CMD_UPDATE,
CMD_CLEAR,
TD_MOTOR,
TD_FORMAT,
TD_REMOVE,
TD_CHANGENUM,
TD_CHANGESTATE,
TD_PROTSTATUS,
TD_GETDRIVETYPE,
TD_ADDCHANGEINT,
TD_REMCHANGEINT,
TD_GETGEOMETRY,
TD_READ64,
TD_WRITE64,
TD_FORMAT64,
NSCMD_DEVICEQUERY,
NSCMD_TD_READ64,
NSCMD_TD_WRITE64,
NSCMD_TD_FORMAT64,
0
};
iostd->io_Error = 0;
static void begin_io(__reg("a6") struct Library *dev, __reg("a1") struct IOStdReq *ior)
{
if (!ior)
return;
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
ior->io_Error = 0;
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;
}
switch (ior->io_Command)
{
case CMD_RESET:
case CMD_CLEAR:
case CMD_UPDATE:
case TD_MOTOR:
case TD_PROTSTATUS:
ior->io_Actual = 0;
break;
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);
}
case TD_CHANGESTATE:
ior->io_Actual = card_present ? 0 : 1;
break;
case TD_CHANGENUM:
ior->io_Actual = card_change_num;
break;
case TD_GETDRIVETYPE:
ior->io_Actual = DG_DIRECT_ACCESS;
break;
case TD_REMOVE:
remove_int = (struct Interrupt *)ior->io_Data;
break;
case TD_ADDCHANGEINT:
if (change_int)
ior->io_Error = IOERR_ABORTED;
else
{
change_int = ior;
ior->io_Flags &= ~IOF_QUICK;
ior = NULL;
}
break;
case TD_REMCHANGEINT:
if (change_int == ior)
change_int = NULL;
break;
case NSCMD_DEVICEQUERY:
if (ior->io_Length >= NSD_QUERY_RESULT_LENGTH_REQUIRED)
{
struct NSDeviceQueryResult *result = ior->io_Data;
result->nsdqr_DevQueryFormat = 0;
result->nsdqr_SizeAvailable = NSD_QUERY_RESULT_LENGTH_REQUIRED;
result->nsdqr_DeviceType = NSDEVTYPE_TRACKDISK;
result->nsdqr_DeviceSubType = 0;
result->nsdqr_SupportedCommands = supported_commands;
ior->io_Actual = NSD_QUERY_RESULT_LENGTH_REQUIRED;
}
else {
ior->io_Error = IOERR_BADLENGTH;
}
break;
case TD_GETGEOMETRY:
case TD_FORMAT:
case CMD_WRITE:
case CMD_READ:
case TD_READ64:
case TD_WRITE64:
case NSCMD_TD_READ64:
case NSCMD_TD_WRITE64:
case NSCMD_TD_FORMAT64:
PutMsg(&mp, (struct Message *)&ior->io_Message);
ior->io_Flags &= ~IOF_QUICK;
ior = NULL;
break;
default:
ior->io_Error = IOERR_NOCMD;
}
if (ior && !(ior->io_Flags & IOF_QUICK))
ReplyMsg(&ior->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;
return IOERR_NOCMD;
}
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 = VERSION;
dev->lib_Revision = REVISION;
dev->lib_IdString = (APTR)id_string;
Forbid();
tr.tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
tr.tr_node.io_Message.mn_ReplyPort = &timer_mp;
tr.tr_node.io_Message.mn_Length = sizeof(tr);
if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)&tr, 0))
goto fail1;
task = CreateTask(device_name, TASK_PRIORITY, (char *)&task_run, TASK_STACK_SIZE);
if (!task)
goto fail2;
int res = spi_initialize(&change_isr);
if (res < 0)
goto fail3;
card_present = res == 1;
mp.mp_Node.ln_Type = NT_MSGPORT;
mp.mp_Flags = PA_SIGNAL;
mp.mp_SigBit = SIGB_OP_REQUEST;
mp.mp_SigTask = task;
NewList(&mp.mp_MsgList);
timer_mp.mp_Node.ln_Type = NT_MSGPORT;
timer_mp.mp_Flags = PA_SIGNAL;
timer_mp.mp_SigBit = SIGB_TIMER;
timer_mp.mp_SigTask = task;
NewList(&timer_mp.mp_MsgList);
Permit();
return dev;
fail3:
DeleteTask(task);
fail2:
CloseDevice((struct IORequest *)&tr);
fail1:
Permit();
FreeMem((char *)dev - dev->lib_NegSize, dev->lib_NegSize + dev->lib_PosSize);
return NULL;
}
static BPTR expunge(__reg("a6") struct Library *dev)
{
if (dev->lib_OpenCnt != 0)
{
dev->lib_Flags |= LIBF_DELEXP;
return 0;
}
// This could be improved on.
// There is a risk that the task has an outstanding debounce timer,
// and deleting the task at that point will probably cause a crash.
spi_shutdown();
DeleteTask(task);
CloseDevice((struct IORequest *)&tr);
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;
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 ULONG device_vectors[] =
{
(ULONG)open,
(ULONG)close,
(ULONG)expunge,
0,
(ULONG)begin_io,
(ULONG)abort_io,
-1,
(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,
sizeof(struct Library),
(ULONG)device_vectors,
0,
(ULONG)init_device,
};

View File

@ -1,23 +0,0 @@
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:

26
examples/spisd/romtag.c Normal file
View File

@ -0,0 +1,26 @@
#include <exec/types.h>
#include <exec/resident.h>
#include <exec/nodes.h>
#include "version.h"
extern ULONG auto_init_tables[];
LONG noexec(void) {
return -1;
}
// Need to be const to be placed as constant in code segment
const struct Resident romtag =
{
.rt_MatchWord = RTC_MATCHWORD,
.rt_MatchTag = (void *)&romtag,
.rt_EndSkip = &romtag + 1,
.rt_Flags = RTF_AUTOINIT,
.rt_Version = VERSION,
.rt_Type = NT_DEVICE,
.rt_Pri = 0,
.rt_Name = device_name,
.rt_IdString = id_string,
.rt_Init = auto_init_tables
};

View File

@ -85,8 +85,7 @@ static int sd_parse_csd(sd_card_info_t *ci, const uint32_t *bits)
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->device_size = ((bits[1] & 0x3ff) << 2) | (bits[2] >> 30);
csd->max_read_current_vdd_min = (bits[2] >> 27) & 0x7;
csd->max_read_current_vdd_max = (bits[2] >> 24) & 0x7;
@ -97,8 +96,7 @@ static int sd_parse_csd(sd_card_info_t *ci, const uint32_t *bits)
//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;
csd->device_size = ((bits[1] & 0x3f) << 16) | (bits[2] >> 16);
//ci->capacity = (uint64_t)(csd->device_size + 1) << 19;
ci->total_sectors = (uint32_t)(csd->device_size + 1) << (19 - csd->read_block_len);
@ -241,8 +239,16 @@ static int sd_write_block(const uint8_t *buf, uint8_t token)
/* Send token */
spi_write(&token, 1);
if (token != 0xfd) {
/* Send data, except for STOP_TRAN */
if (token == 0xfd) {
/* After sending STOP_TRAN, a byte needs to be read before the card
* goes busy. This byte is undefined, so unless we read it now, the
* next sd_wait_ready() will read it and could erronously decide
* that the card is immediately ready.
*/
spi_read(&resp, 1);
}
else {
/* Send data */
spi_write(buf, SD_SECTOR_SIZE);
spi_write(crc, 2); /* dummy */
@ -468,7 +474,7 @@ int sd_read(uint8_t *buf, uint32_t sector, uint32_t count)
} else {
err = sdError_BadResponse;
}
} else {
} else if (count > 1) {
/* Read multiple sectors */
if (sd_send_cmd(CMD18, sector) == 0) {
do {
@ -514,7 +520,7 @@ int sd_write(const uint8_t *buf, uint32_t sector, uint32_t count)
} else {
err = sdError_BadResponse;
}
} else {
} else if (count > 1) {
if (ci->type == sdCardType_SD1_x || ci->type == sdCardType_SD2_0 || ci->type == sdCardType_SDHC) {
/* Pre-defined sector count */
sd_send_cmd(ACMD23, count);

8
examples/spisd/version.c Normal file
View File

@ -0,0 +1,8 @@
#include "version.h"
#define STR(x) #x
#define XSTR(x) STR(x)
char device_name[] = NAME ".device";
char id_string[] = NAME " " XSTR(VERSION) "." XSTR(REVISION) " (" DATE ")\n\r";

12
examples/spisd/version.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef VERSION_H_
#define VERSION_H_
#define NAME "spisd"
#define VERSION 2
#define REVISION 2
#define DATE "5.3.2023"
extern char device_name[];
extern char id_string[];
#endif

View File

@ -1,24 +1,40 @@
# Assembly instructions
The assembly of the adapter is very simple.
The assembly of the adapter is quite 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 |
| Par. pin # | Par. pin | Nano pin | Atmega328P |
|---:|-----:|----:|----:|
| 2 | D0 | A0 | PC0 |
| 3 | D1 | A1 | PC1 |
| 4 | D2 | A2 | PC2 |
| 5 | D3 | A3 | PC3 |
| 6 | D4 | A4 | PC4 |
| 7 | D5 | A5 | PC5 |
| 8 | D6 | D6 | PD6 |
| 9 | D7 | D7 | PD7 |
| 10 | ACK | D9 | PB1 |
| 11 | BUSY | D4 | PD4 |
| 12 | POUT | D5 | PD5 |
| 13 | SEL | D2 | PD2 |
| 18..25 | GND | GND | 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).
## Connecting SD card module to Nano
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.
To use the adapter with the example SD card driver (spisd.device),
connect the SD card module to the Nano as follows:
| SD card | Nano pin | Atmega328P |
|-----:|----:|----:|
| SS | D10 | PB2 |
| MOSI | D11 | PB3 |
| MISO | D12 | PB4 |
| SCK | D13 | PB5 |
| CD | D3 | PD3 |
| VCC | VCC | |
| GND | GND | GND |
CD = Card Detect. Not all modules has this pin available, but since the Micro SD card connectors typically has this pin it should be possible to solder a wire to the connector.
Note that the SD card module must have voltage level translation between the +5V used by the Amiga and the Nano, and the +3.3V used by the SD card.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 146 KiB

15
rp2040/CMakeLists.txt Normal file
View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(par_spi C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(par_spi par_spi.c)
pico_add_extra_outputs(par_spi)
target_link_libraries(par_spi pico_stdlib hardware_spi)

19
rp2040/README.md Normal file
View File

@ -0,0 +1,19 @@
# RP2040 version
The code for the AVR microcontroller has been ported to the RP2040 microcontroller.
RP2040 is the microcontroller used on the Raspberry Pi Pico board.
## Build instructions
The [Raspberry Pi Pico SDK](https://github.com/raspberrypi/pico-sdk) must be installed.
Then, standing in the rp2040 subdirectory, execute:
```bash
mkdir build
cd build
export PICO_SDK_PATH=~/pico/pico-sdk
cmake ..
make
```
Copy the generated file `build/par_spi.uf2` to the microcontroller's flash.

206
rp2040/par_spi.c Normal file
View File

@ -0,0 +1,206 @@
/*
* Written in October 2022 by Niklas Ekström.
*
* Runs on RP2040 microcontroller instead of AVR as before,
* but uses the same protocol and Amiga software.
*/
#include "hardware/gpio.h"
#include "hardware/spi.h"
// Pin name GPIO Direction Comment Description
#define PIN_D(x) (0+x) // In/out
#define PIN_IRQ 8 // Output Active low
#define PIN_ACT 9 // Output Active low
#define PIN_CLK 10 // Input
#define PIN_REQ 11 // Input Active low
#define PIN_MISO 16 // Input Pull-up
#define PIN_SS 17 // Output Active low
#define PIN_SCK 18 // Output
#define PIN_MOSI 19 // Output
#define PIN_CDET 20 // Input Pull-up Card Detect
#define SPI_SLOW_FREQUENCY (400*1000)
#define SPI_FAST_FREQUENCY (16*1000*1000)
static uint32_t prev_cdet;
static void handle_request() {
uint32_t pins;
while (1) {
pins = gpio_get_all();
if (!(pins & (1 << PIN_REQ)))
break;
if ((pins & (1 << PIN_CDET)) != prev_cdet) {
gpio_put(PIN_IRQ, false);
gpio_set_dir(PIN_IRQ, true);
prev_cdet = pins & (1 << PIN_CDET);
}
}
uint32_t prev_clk = pins & (1 << PIN_CLK);
if ((pins & 0xc0) != 0xc0) {
uint32_t byte_count = 0;
bool read = false;
if (!(pins & 0x80)) { // READ1 or WRITE1
read = !!(pins & 0x40);
byte_count = pins & 0x3f;
gpio_put(PIN_ACT, 0);
} else { // READ2 or WRITE2
byte_count = (pins & 0x3f) << 7;
gpio_put(PIN_ACT, 0);
while (1) {
pins = gpio_get_all();
if ((pins & (1 << PIN_CLK)) != prev_clk)
break;
if (pins & (1 << PIN_REQ))
return;
}
read = !!(pins & 0x80);
byte_count |= pins & 0x7f;
prev_clk = pins & (1 << PIN_CLK);
}
if (read) {
spi_get_hw(spi0)->dr = 0xff;
uint32_t prev_ss = pins & (1 << PIN_SS);
while (1) {
while (!spi_is_readable(spi0))
tight_loop_contents();
uint32_t value = spi_get_hw(spi0)->dr;
while (1) {
pins = gpio_get_all();
if ((pins & (1 << PIN_CLK)) != prev_clk)
break;
if (pins & (1 << PIN_REQ))
return;
}
gpio_put_all(prev_ss | value);
gpio_set_dir_out_masked(0xff);
if (!byte_count)
break;
spi_get_hw(spi0)->dr = 0xff;
prev_clk = pins & (1 << PIN_CLK);
byte_count--;
}
} else {
while (1) {
while (1) {
pins = gpio_get_all();
if ((pins & (1 << PIN_CLK)) != prev_clk)
break;
if (pins & (1 << PIN_REQ))
return;
}
spi_get_hw(spi0)->dr = pins & 0xff;
while (!spi_is_readable(spi0))
tight_loop_contents();
(void)spi_get_hw(spi0)->dr;
if (!byte_count)
break;
prev_clk = pins & (1 << PIN_CLK);
byte_count--;
}
}
} else {
switch ((pins & 0x3e) >> 1) {
case 0: { // SPI_SELECT
gpio_put(PIN_SS, !(pins & 1));
gpio_put(PIN_ACT, 0);
break;
}
case 1: { // CARD_PRESENT
gpio_set_dir(PIN_IRQ, false);
gpio_put(PIN_ACT, 0);
while (1) {
pins = gpio_get_all();
if ((pins & (1 << PIN_CLK)) != prev_clk)
break;
if (pins & (1 << PIN_REQ))
return;
}
gpio_put(PIN_D(0), !gpio_get(PIN_CDET));
gpio_set_dir_out_masked(0xff);
break;
}
case 2: { // SPEED
spi_set_baudrate(spi0, pins & 1 ?
SPI_FAST_FREQUENCY :
SPI_SLOW_FREQUENCY);
gpio_put(PIN_ACT, 0);
break;
}
}
}
while (1) {
pins = gpio_get_all();
if (pins & (1 << PIN_REQ))
break;
}
}
int main() {
spi_init(spi0, SPI_SLOW_FREQUENCY);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
gpio_pull_up(PIN_MISO);
gpio_init(PIN_SS);
gpio_put(PIN_SS, 1);
gpio_set_dir(PIN_SS, GPIO_OUT);
gpio_init(PIN_CDET);
gpio_pull_up(PIN_CDET);
for (int i = 0; i < 12; i++)
gpio_init(i);
gpio_put(PIN_ACT, 1);
gpio_set_dir(PIN_ACT, GPIO_OUT);
prev_cdet = gpio_get_all() & (1 << PIN_CDET);
while (1) {
handle_request();
gpio_set_dir_in_masked(0xff);
gpio_clr_mask(0xff);
gpio_put(PIN_ACT, 1);
while (spi_is_busy(spi0))
tight_loop_contents();
if (spi_is_readable(spi0))
(void)spi_get_hw(spi0)->dr;
}
}

View File

@ -0,0 +1,73 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

16
spi-lib/cia_protos.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef CIA_PROTOS_H
#define CIA_PROTOS_H
#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/libraries.h>
struct Interrupt *AddICRVector(__reg("a6") struct Library *resource, __reg("d0") LONG iCRBit, __reg("a1") struct Interrupt *interrupt)="\tjsr\t-6(a6)";
void RemICRVector(__reg("a6") struct Library *resource, __reg("d0") LONG iCRBit, __reg("a1") struct Interrupt *interrupt)="\tjsr\t-12(a6)";
WORD AbleICR(__reg("a6") struct Library *resource, __reg("d0") LONG mask)="\tjsr\t-18(a6)";
WORD SetICR(__reg("a6") struct Library *resource, __reg("d0") LONG mask)="\tjsr\t-24(a6)";
#endif /* CIA_PROTOS_H */

10
spi-lib/misc_protos.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef MISC_PROTOS_H
#define MISC_PROTOS_H
#include <exec/types.h>
UBYTE *AllocMiscResource(__reg("a6") struct Library *resource, __reg("d0") ULONG unitNum, __reg("a1") const char *name)="\tjsr\t-6(a6)";
void FreeMiscResource(__reg("a6") struct Library *resource, __reg("d0") ULONG unitNum)="\tjsr\t-12(a6)";
#endif /* MISC_PROTOS_H */

View File

@ -1,22 +1,30 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/libraries.h>
#include <hardware/cia.h>
#include <resources/cia.h>
#include <resources/misc.h>
#include <proto/exec.h>
#include "cia_protos.h"
#include "misc_protos.h"
#include "spi.h"
#define CIAB_PRTRSEL 2
#define CIAB_PRTRPOUT 1
#define CIAB_PRTRBUSY 0
#define REQ_BIT CIAB_PRTRSEL
#define CLK_BIT CIAB_PRTRPOUT
#define ACT_BIT CIAB_PRTRBUSY
#define CS_BIT CIAB_PRTRSEL
#define CLOCK_BIT CIAB_PRTRPOUT
#define IDLE_BIT CIAB_PRTRBUSY
#define REQ_MASK (1 << REQ_BIT)
#define CLK_MASK (1 << CLK_BIT)
#define ACT_MASK (1 << ACT_BIT)
#define CS_MASK (1 << CS_BIT)
#define CLOCK_MASK (1 << CLOCK_BIT)
#define IDLE_MASK (1 << IDLE_BIT)
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);
static volatile UBYTE *cia_a_prb = (volatile UBYTE *)0xbfe101;
static volatile UBYTE *cia_a_ddrb = (volatile UBYTE *)0xbfe301;
@ -26,59 +34,93 @@ static volatile UBYTE *cia_b_ddra = (volatile UBYTE *)0xbfd200;
static long current_speed = SPI_SPEED_SLOW;
void spi_initialize()
static const char spi_lib_name[] = "spi-lib";
static struct Library *miscbase;
static struct Library *ciaabase;
static struct Interrupt flag_interrupt;
static int wait_until_active()
{
// 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.
int count = 32;
UBYTE ctrl = *cia_b_pra;
while (ctrl & IDLE_MASK)
while (count > 0 && (ctrl & ACT_MASK))
{
ctrl ^= CLOCK_MASK;
*cia_b_pra = ctrl;
count--;
ctrl = *cia_b_pra;
}
return count;
}
void spi_select()
{
*cia_a_prb = 0xc1;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
}
void spi_deselect()
{
*cia_a_prb = 0xc0;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
}
int spi_get_card_present()
{
*cia_a_prb = 0xc2;
UBYTE ctrl = *cia_b_pra;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
if (!wait_until_active())
{
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
return -1;
}
*cia_a_ddrb = 0x00;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
int present = *cia_a_prb & 1;
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_ddrb = 0xff;
return present;
}
void spi_set_speed(long speed)
{
wait_until_idle();
*cia_a_prb = speed == SPI_SPEED_FAST ? 0xc5 : 0xc4;
*cia_a_ddrb = 0xff;
*cia_a_prb = speed == SPI_SPEED_FAST ? 0xc1 : 0xc0;
*cia_b_pra ^= CLOCK_MASK;
*cia_a_ddrb = 0;
UBYTE prev = *cia_b_pra;
*cia_b_pra = prev & ~REQ_MASK;
wait_until_active();
*cia_b_pra = prev;
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()
@ -90,29 +132,29 @@ static void wait_40_us()
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;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
wait_until_active();
}
else // WRITE2: 100xxxxx xxxxxxxx
else // WRITE2: 10xxxxxx 0xxxxxxx
{
*cia_a_prb = 0x80 | (((size - 1) >> 8) & 0x1f);
*cia_a_prb = 0x80 | (((size - 1) >> 7) & 0x3f);
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
wait_until_active();
ctrl ^= CLOCK_MASK;
*cia_a_prb = (size - 1) & 0x7f;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
}
@ -120,40 +162,41 @@ static void spi_write_slow(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
{
*cia_a_prb = *buf++;
ctrl ^= CLOCK_MASK;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
wait_40_us();
}
*cia_a_ddrb = 0;
ctrl |= REQ_MASK;
*cia_b_pra = ctrl;
}
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;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
wait_until_active();
}
else // READ2: 101xxxxx xxxxxxxx
else // READ2: 10xxxxxx 1xxxxxxx
{
*cia_a_prb = 0xa0 | (((size - 1) >> 8) & 0x1f);
*cia_a_prb = 0x80 | (((size - 1) >> 7) & 0x3f);
ctrl ^= CLOCK_MASK;
ctrl &= ~REQ_MASK;
*cia_b_pra = ctrl;
*cia_a_prb = (size - 1) & 0xff;
wait_until_active();
ctrl ^= CLOCK_MASK;
*cia_a_prb = 0x80 | ((size - 1) & 0x7f);
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
}
@ -163,18 +206,17 @@ static void spi_read_slow(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
wait_40_us();
ctrl ^= CLOCK_MASK;
ctrl ^= CLK_MASK;
*cia_b_pra = ctrl;
*buf++ = *cia_a_prb;
}
ctrl ^= CLOCK_MASK;
ctrl |= REQ_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);
*cia_a_ddrb = 0xff;
}
void spi_read(__reg("a0") UBYTE *buf, __reg("d0") ULONG size)
{
@ -191,3 +233,95 @@ void spi_write(__reg("a0") const UBYTE *buf, __reg("d0") ULONG size)
else
spi_write_slow(buf, size);
}
int spi_initialize(void (*change_isr)())
{
int success = 0;
miscbase = (struct Library *)OpenResource(MISCNAME);
if (!miscbase)
{
success = -1;
goto fail_out1;
}
ciaabase = (struct Library *)OpenResource(CIAANAME);
if (!ciaabase)
{
success = -2;
goto fail_out1;
}
if (AllocMiscResource(miscbase, MR_PARALLELPORT, spi_lib_name))
{
success = -3;
goto fail_out1;
}
if (AllocMiscResource(miscbase, MR_PARALLELBITS, spi_lib_name))
{
success = -4;
goto fail_out2;
}
flag_interrupt.is_Node.ln_Name = (char *)spi_lib_name;
flag_interrupt.is_Node.ln_Type = NT_INTERRUPT;
flag_interrupt.is_Code = change_isr;
Disable();
if (AddICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt))
{
Enable();
success = -5;
goto fail_out3;
}
AbleICR(ciaabase, CIAICRF_FLG);
SetICR(ciaabase, CIAICRF_FLG);
Enable();
*cia_b_pra = (*cia_b_pra & ~ACT_MASK) | (REQ_MASK | CLK_MASK);
*cia_b_ddra = (*cia_b_ddra & ~ACT_MASK) | (REQ_MASK | CLK_MASK);
*cia_a_prb = 0xff;
*cia_a_ddrb = 0xff;
int card_present = spi_get_card_present();
if (card_present < 0)
{
success = -6;
goto fail_out4;
}
AbleICR(ciaabase, CIAICRF_SETCLR | CIAICRF_FLG);
return card_present;
fail_out4:
*cia_b_ddra &= ~(ACT_MASK | REQ_MASK | CLK_MASK);
*cia_a_ddrb = 0;
RemICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt);
fail_out3:
FreeMiscResource(miscbase, MR_PARALLELBITS);
fail_out2:
FreeMiscResource(miscbase, MR_PARALLELPORT);
fail_out1:
return success;
}
void spi_shutdown()
{
AbleICR(ciaabase, CIAICRF_FLG);
*cia_b_ddra &= ~(ACT_MASK | REQ_MASK | CLK_MASK);
*cia_a_ddrb = 0;
RemICRVector(ciaabase, CIAICRB_FLG, &flag_interrupt);
FreeMiscResource(miscbase, MR_PARALLELBITS);
FreeMiscResource(miscbase, MR_PARALLELPORT);
}

View File

@ -1,5 +1,6 @@
/*
* Written in the end of April 2020 by Niklas Ekström.
* Updated in July 2021 by Niklas Ekström to handle Card Present signal.
*/
#ifndef SPI_H_
#define SPI_H_
@ -7,7 +8,8 @@
#define SPI_SPEED_SLOW 0
#define SPI_SPEED_FAST 1
void spi_initialize();
int spi_initialize(void (*change_isr)());
int spi_get_card_present();
void spi_shutdown();
void spi_set_speed(long speed);
void spi_select();

View File

@ -1,4 +1,5 @@
; Written in the end of April 2020 by Niklas Ekström.
; Updated in July 2021 by Niklas Ekström to handle Card Present signal.
XDEF _spi_read_fast
XDEF _spi_write_fast
@ -16,12 +17,9 @@ 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
REQ_BIT equ CIAB_PRTRSEL
CLK_BIT equ CIAB_PRTRPOUT
ACT_BIT equ CIAB_PRTRBUSY
; a0 = unsigned char *buf
; d0 = unsigned int size
@ -32,53 +30,53 @@ _spi_write_fast:
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
movem.l d2/a5,-(a7)
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
move.b (a5),d2
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; WRITE2 = 100xxxxx xxxxxxxx
; WRITE2 = 10xxxxxx 0xxxxxxx
move d0,d1
lsr #8,d1
lsr #7,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
.act_wait2: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait2
move.b d0,d1
and.b #$7f,d1
move.b d1,(a1)
bchg #CLK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
.one_byte_cmd: ; WRITE1 = 00xxxxxx
move.b d0,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
.act_wait1: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait1
.cmd_sent: addq #1,d0 ; d0 = size
btst #0,d0
beq.b .even
move.b (a0)+,(a1)
bchg #CLOCK_BIT,d2
bchg #CLK_BIT,d2
move.b d2,(a5)
.even: lsr #1,d0
@ -86,7 +84,7 @@ _spi_write_fast:
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
bchg #CLK_BIT,d1
.loop: move.b (a0)+,(a1)
move.b d1,(a5)
@ -94,10 +92,11 @@ _spi_write_fast:
move.b d2,(a5)
dbra d0,.loop
.done: move.b #0,$200(a1) ; Stop driving data pins
.done: move.b d2,(a5) ; Delay to allow write to complete
bset #REQ_BIT,d2
move.b d2,(a5)
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
movem.l (a7)+,d2/a5
rts
; a0 = unsigned char *buf
@ -109,38 +108,34 @@ _spi_read_fast:
bne.b .not_zero
rts
.not_zero:
movem.l d2/a5-a6,-(a7)
move.l 4.w,a6
jsr Disable(a6)
movem.l d2/a5,-(a7)
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
move.b (a5),d2
subq #1,d0 ; d0 = size - 1
cmp #63,d0
ble.b .one_byte_cmd
; READ2 = 101xxxxx xxxxxxxx
; READ2 = 10xxxxxx 1xxxxxxx
move d0,d1
lsr #8,d1
or.b #$a0,d1
lsr #7,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
move.b d0,(a1)
bchg #CLOCK_BIT,d2
.act_wait2: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait2
move.b d0,d1
or.b #$80,d1
move.b d1,(a1)
bchg #CLK_BIT,d2
move.b d2,(a5)
bra.b .cmd_sent
@ -148,9 +143,13 @@ _spi_read_fast:
move.b d0,d1
or.b #$40,d1
move.b d1,(a1)
bchg #CLOCK_BIT,d2
bclr #REQ_BIT,d2
move.b d2,(a5)
.act_wait1: move.b (a5),d2
btst #ACT_BIT,d2
bne.b .act_wait1
.cmd_sent: move.b #0,$200(a1) ; Stop driving data pins
addq #1,d0 ; d0 = size
@ -158,7 +157,7 @@ _spi_read_fast:
btst #0,d0
beq.b .even
bchg #CLOCK_BIT,d2
bchg #CLK_BIT,d2
move.b d2,(a5)
move.b (a1),(a0)+
@ -167,7 +166,7 @@ _spi_read_fast:
subq #1,d0
move.b d2,d1
bchg #CLOCK_BIT,d1
bchg #CLK_BIT,d1
.loop: move.b d1,(a5)
move.b (a1),(a0)+
@ -175,9 +174,10 @@ _spi_read_fast:
move.b (a1),(a0)+
dbra d0,.loop
.done: bchg #CLOCK_BIT,d2
.done: bset #REQ_BIT,d2
move.b d2,(a5)
jsr Enable(a6)
movem.l (a7)+,d2/a5-a6
move.b #$ff,$200(a1) ; Start driving data pins
movem.l (a7)+,d2/a5
rts