Feel free to try it on the mbed LPC11U24
Code: Select all
// ===================================================
// QL microdrive emulator
// by Gert van der Knokke (c)2013
// adapted for LPC11U24 @ 48 MHz
// ===================================================
#include "mbed.h"
#include "SDHCFileSystem.h"
#include "FATFileSystem.h"
#define VERSION 0.01
// setup a pointer to the GPIO Toggle register
#define LPC_GPIO_PORT0_NOT0 ((volatile uint32_t *) 0x50002300)
// setup pointer to GPIO byte pin register for P0.7 (MD_DATA1) and P0.17 (MD_DATA2)
#define LPC_GPIO_BYTE_DATA1 ((volatile uint8_t *) 0x50000000+7)
#define LPC_GPIO_BYTE_DATA2 ((volatile uint8_t *) 0x50000000+17)
extern "C" void get_bit();
// debug/status leds
DigitalOut myled1(LED1);
DigitalOut myled2(LED2);
DigitalOut myled3(LED3);
DigitalOut myled4(LED4);
// IO pin definition
DigitalInOut MD_DATA1(p20); // MD track1 data P0.22
DigitalInOut MD_DATA2(p19); // MD track2 data P0.16
DigitalIn MD_RW(p18); // MD Read/Write line P0.14
InterruptIn MD_COMMS_CLK(p17); // MD Selection clock P0.13
DigitalIn MD_COMMS_IN(p16); // MD Selection data P0.12
// debug output to PC
Serial linktopc(USBTX, USBRX); // tx, rx
// pointer to 32k buffer space (second 32k RAM in mbed)
unsigned char buffer[2048];
// some standard definitions
#define DEBUG_BLOCK_SIZE 32
#define NR_ZERO_BITS 24
#define NR_ONE_BITS 8
#define SAMPLE_DELAY 25 // 250 ns
//#define HALF_BIT_TIME 480 // 5 usec (500/1.04)
#define HALF_BIT_TIME (430/2) // 5 usec (500/1.04)
#define BIT_TIME (HALF_BIT_TIME * 2)
//#define GAP1_TIME 346154 // original
#define GAP1_TIME (370000/2) // longer to be able to sample R/W
#define GAP2_TIME (530769/2)
#define BIT_SHIFT 4 // define the number of bits between track 1 and 2
#define TRACK_OFFSET 120
#define SELECT_IGNORE_TIME 100 // ignore select signals shorter than this
// QL microdrive parameters
#define BLOCK_SIZE 538 // 538
#define HEADER_SIZE 28 // 28
#define PREAMBLE_SIZE 12 // 12 bytes sync data
#define MDV_SECTOR_SIZE 686 // sector offset in MDV file
// Start sector to emit first after reset
#define START_SECTOR 127
// how many devices do we support
#define MAX_DEVICES 8
// which is the first device we emulate
#define START_DEVICE 3
// numbers of sectors to skip after motor off command received
#define OVERSHOOT 10
// persistent space for bit storage
volatile uint8_t old1,old2;
// setup our track1 and track2 shift registers
volatile uint8_t track1_register,track2_register;
// setup some pointers to the pseudo shift registers
volatile uint8_t *track1_register_address=&track1_register;
volatile uint8_t *track2_register_address=&track2_register;
extern int stdio_retargeting_module;
/**
* Initialize the system for use with the RC oscillator
* originally by Chris
* @param none
* @return none
*
* @brief Setup the microcontroller system.
* Initialize the System.
*/
extern "C" void $Sub$$SystemInit (void)
{
// select the PLL input
LPC_SYSCON->SYSPLLCLKSEL = 0x0; // Select PLL Input source 0=IRC, 1=OSC
LPC_SYSCON->SYSPLLCLKUEN = 0x01; /* Update Clock Source */
LPC_SYSCON->SYSPLLCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->SYSPLLCLKUEN = 0x01;
while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01)); /* Wait Until Updated */
// Power up the system PLL
LPC_SYSCON->SYSPLLCTRL = 0x00000023;
LPC_SYSCON->PDRUNCFG &= ~(1 << 7); /* Power-up SYSPLL */
while (!(LPC_SYSCON->SYSPLLSTAT & 0x01)); /* Wait Until PLL Locked */
// Select the main clock source
LPC_SYSCON->MAINCLKSEL = 0x3; // Select main Clock source, 0=IRC, 1=PLLin, 2=WDO, 3=PLLout
LPC_SYSCON->MAINCLKUEN = 0x01; /* Update MCLK Clock Source */
LPC_SYSCON->MAINCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->MAINCLKUEN = 0x01;
while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated */
LPC_SYSCON->SYSAHBCLKDIV = 0x00000001;
LPC_SYSCON->PDRUNCFG &= ~(1 << 10); /* Power-up USB PHY */
LPC_SYSCON->PDRUNCFG &= ~(1 << 8); /* Power-up USB PLL */
LPC_SYSCON->USBPLLCLKSEL = 0x0; // 0=IRC, 1=System clock, only good for low speed
LPC_SYSCON->USBPLLCLKUEN = 0x01; /* Update Clock Source */
LPC_SYSCON->USBPLLCLKUEN = 0x00; /* Toggle Update Register */
LPC_SYSCON->USBPLLCLKUEN = 0x01;
while (!(LPC_SYSCON->USBPLLCLKUEN & 0x01)); /* Wait Until Updated */
LPC_SYSCON->USBPLLCTRL = 0x00000023;
while (!(LPC_SYSCON->USBPLLSTAT & 0x01)); /* Wait Until PLL Locked */
LPC_SYSCON->USBCLKSEL = 0x00; /* Select USB PLL */
LPC_SYSCON->USBCLKSEL = 0x00000000; /* Select USB Clock */
LPC_SYSCON->USBCLKDIV = 0x00000001; /* Set USB clock divider */
/* System clock to the IOCON needs to be enabled or
most of the I/O related peripherals won't work. */
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16);
// stdio_retargeting_module = 1;
}
// ---------------------------------------------------------
// FM decode single bits and store them in track1 and track2
// shift registers
// ---------------------------------------------------------
// see receive.s
// show me the data (aka hexdump)
void hexdump(unsigned char *start, int size)
{
int n,m;
unsigned char d;
for (m=0; m<size; m+=16) {
// print offset in 4 bytes hex
printf("%04x: ",m);
// print individual bytes in hex
for (n=0; n<16; n++) {
d=start[m+n];
printf("%02x ",d);
}
// and translate the readable ones into ASCII
for (n=0; n<16; n++) {
d=start[m+n];
if ((d>31) && (d<128)) putchar(d);
else putchar('?');
}
printf("\n\r");
}
printf("\n\r");
}
// ---------------------------------------------------------------
// wait for tape preamble: 10 bytes of $00 then two bytes of $ff
// ---------------------------------------------------------------
void wait_for_preamble()
{
bool found;
int n;
found=0;
// myled2=0;
// try to find a header, loop until found flag is set
while (!found) {
// wait for defined number of zero bits
track1_register=0xff;
track2_register=0xff;
while (track1_register | track2_register) get_bit();
track1_register=0xff;
track2_register=0xff;
while (track1_register | track2_register) get_bit();
// signal zeroes found
// myled3=1;
// wait for $FF (first bit of the two $FF bytes)
do {
get_bit();
} while (!(track1_register & 0x80));
// read to end of FF FF header
for (n=0; n<(NR_ONE_BITS-1); n++) {
get_bit();
}
// if we have a $FF in register 1 we have arrived
if ((track1_register & 0xff) == 0xff) found=1;
// else printf("T1:%04x ",track1_register);
}
// signal sync found
// myled4=0;
}
// send two bits on track 1 and track 2 FM modulated
void send_bit(int tr1, int tr2)
{
LPC_CT32B0->TC=0; // reset timer
// toggle both pins simultanously by using the NOT register
// create start edge
*LPC_GPIO_PORT0_NOT0=((1<<22) | (1<<16));
// wait half bit time
while (LPC_CT32B0->TC < HALF_BIT_TIME);
// if a '1' then reverse output halfway
// selectively toggle pins according to value
*LPC_GPIO_PORT0_NOT0=( (tr1<<22) | (tr2<<16) );
// wait until end of bit time
while (LPC_CT32B0->TC < BIT_TIME);
}
// send bytes from the buffer in FM format on two tracks
void send_buffer(unsigned char *buffer, int size)
{
int n,m;
unsigned int track1=0,track2=0;
// send them bytes
n=0;
while (n<size) {
track1 = buffer[n++];
for (m=0; m<4; m++) {
send_bit(track1 & 1, track2 & 1);
track1>>=1;
track2>>=1;
}
track2 |= buffer[n++];
for (m=0; m<4; m++) {
send_bit(track1 & 1, track2 & 1);
track1>>=1;
track2>>=1;
}
}
// send last bits of track 2
for (m=0; m<4; m++) {
send_bit(track1 & 1, track2 & 1);
track1>>=1;
track2>>=1;
}
// set idle level (high)
MD_DATA1=1;
MD_DATA2=1;
}
// receive updated sector information
void do_receive()
{
int m,n;
// wait for pre-amble
wait_for_preamble();
// we are now at the start of the block data (minus preamble!)
// track1 and track2 register still contain the FF
// copy data to buffer
for (m=0; m<(BLOCK_SIZE-PREAMBLE_SIZE); m+=2) {
// at t0
buffer[m]=track1_register;
// get 4 bits
for (n=0; n<4; n++) get_bit();
// at t4
buffer[m+1]=track2_register;
// wait 4 bits
for (n=0; n<4; n++) get_bit();
// at t8
// printf("R%d\n\r",m);
}
// save the last 4 bits of track 1
buffer[m]=track1_register;
// get 4 more bits
for (n=0; n<4; n++) get_bit();
// and store track2 in buffer
buffer[m+1]=track2_register;
}
// send a complete sector to the QL
// during the sending the QL can pull the write line down
// to update the current sector!
void do_send_sector(int sector, FILE *fp)
{
bool update=0;
// myled4=0;
// set pins to output
MD_DATA1.output();
MD_DATA2.output();
// send the header
send_buffer(buffer,HEADER_SIZE);
// reset timer
LPC_CT32B0->TC=0;
// wait at least GAP1 time
// while waiting check R/W line for update and break if so
while (LPC_CT32B0->TC < GAP1_TIME) {
if (!MD_RW) {
myled4=1;
update=1;
break;
}
}
// check if we should rewrite this sector
if (update) {
MD_DATA1.input();
MD_DATA2.input();
// computer wants to update this sector
// so receive the data from the QL
do_receive();
// reset timer
LPC_CT32B0->TC=0;
// seek to start of data block of this sector in SD card file
fseek(fp, (sector*MDV_SECTOR_SIZE)+HEADER_SIZE+PREAMBLE_SIZE, SEEK_SET);
// write data block part from buffer (skip first two FF bytes)
fwrite(buffer+2,BLOCK_SIZE-PREAMBLE_SIZE,1,fp);
myled4=0;
} else {
// else send data block from buffer
send_buffer(buffer+HEADER_SIZE,BLOCK_SIZE);
// reset timer
LPC_CT32B0->TC=0;
}
// wait GAP2 time
while (LPC_CT32B0->TC < GAP2_TIME);
// reset pins to input
MD_DATA1.input();
MD_DATA2.input();
}
// volatile storage for MD select
volatile unsigned char select;
volatile unsigned char jk_data;
// ISR for MD selection register
// each Microdrive has a single bit shift register inside
// in fact a JK flipflop so we need to emulate that..
void md_comms_clock_isr()
{
// shift register
select<<=1;
// or in the previous data bit
select|=jk_data;
// save current data bit
jk_data=MD_COMMS_IN;
}
// =================================================================================
// the main event...
// =================================================================================
int main()
{
unsigned int current_sector[MAX_DEVICES];
DIR *mydir;
struct dirent *p;
char filename_buffer[MAX_DEVICES][32];
FILE *fp[MAX_DEVICES];
int c;
int l;
int index=0xff;
int old_select=0;
// setup debug port
//linktopc.baud(115200);
linktopc.baud(921600);
setbuf(stdout, NULL); // no buffering for this filehandle
// init SD card system
SDFileSystem sd(p11, p12, p13, p14, "sd"); // mosi, miso, sclk, cs
// Init 32 bit counter to run at 1:1 CPU clock (48 MHz)
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9); //enables clock for the 32-bit counter/timer 0
LPC_CT32B0->TCR = 2; // reset timer
LPC_CT32B0->PR = 0; // set Prescale to 0
LPC_CT32B0->PC = 0; // set Prescale counter to 0
LPC_CT32B0->TCR = 1; // start timer
// announce ourselves to the debug host
// printf("\n\rQL microdrive emulator V%1.2f (c)2013 KGE\n\r",VERSION);
myled1=0;
myled2=0;
myled3=0;
myled4=0;
// set COMMS_IN data line to 'pull down' otherwise the motor
// of the previous MD in the chain will run when we are idle
MD_COMMS_IN.mode(PullDown);
// reset the select shift register
select=0;
jk_data=0;
// init the interrupt driven select routine
MD_COMMS_CLK.fall(&md_comms_clock_isr);
c=0;
// open SD card storage and show file list
// assign up to MAX_DRIVES to mdv devices
mydir = opendir("/sd");
if (mydir != NULL) {
while ((p = readdir(mydir)) != NULL) {
// printf("%s\n\r",p->d_name);
l=strlen(p->d_name);
if ((!strncmp(p->d_name+(l-4),".MDV",4))||(!strncmp(p->d_name+(l-4),".mdv",4))) {
if (c<MAX_DEVICES) {
// printf("found MD file: %s assigned mdv%d_\n\r",p->d_name,c+3);
strncpy(filename_buffer[c],"/sd/",32);
strncpy(filename_buffer[c++]+4,p->d_name,32);
}
}
}
} else {
// Panic mode.. no usable SD card found..
while(1) {
myled1=1;
wait(0.2);
myled1=0;
wait(0.2);
}
}
// reset sector counters
for (c=0; c<MAX_DEVICES; c++) {
current_sector[c]=START_SECTOR;
}
// preset old_select
old_select=select;
// set data pins to input
MD_DATA1.input();
MD_DATA2.input();
// main endless loop
while(1) {
// we could go to sleep here while waiting for
// select to become active....
// deepsleep(); // Deep sleep until external interrupt
// are we selected ? (wait a small time to ignore reset glitches)
if (select) {
old_select=select;
wait_us(SELECT_IGNORE_TIME);
}
// if select is stable then continue
if (select==old_select) {
// printf("SEL:%02x\n\r",select);
index=0xff;
// if (old_select & 0x80) index=7;
// if (old_select & 0x40) index=6;
if (old_select & 0x20) index=5;
if (old_select & 0x10) index=4;
if (old_select & 0x08) index=3;
if (old_select & 0x04) index=2;
if (old_select & 0x02) index=1;
if (old_select & 0x01) index=0;
// if we have a valid index
if (index != 0xff) {
// printf("index: %d using file: %s\n\r",index,filename_buffer[index]);
// open the file image
fp[index] = fopen(filename_buffer[index], "r+");
// file opened succsfully ?
if (fp[index]) {
// switch on 'drive active' indicator
myled1=1;
// loop while we are selected simulating the tape running
// the sector counter will roll over at 255 to zero
do {
// seek start of current sector
fseek(fp[index], current_sector[index]*MDV_SECTOR_SIZE, SEEK_SET);
// debug output
// printf("Reading sector %d for index %d\n\r",current_sector[index],index);
// read sector in buffer
fread(buffer,MDV_SECTOR_SIZE,1,fp[index]);
// send sector to QL
do_send_sector(current_sector[index]--, fp[index]);
// limit sector counter..
current_sector[index] &= 0xff;
} while (select);
// simulate sector overshoot and close file
// current_sector[index]-=OVERSHOOT;
// current_sector[index] &= 0xff;
fclose(fp[index]);
}
else
{
// printf("file not found..\n\r");
}
}
// switch off 'drive active' led
myled1=0;
}
}
}
Code: Select all
AREA asm_func, CODE, READONLY
; Export my_asm function location so that C compiler can find it and link
EXPORT get_bit
IMPORT track1_register_address
IMPORT track2_register_address
ALIGN
get_bit
push {r4,r5}
ldr R0,=0x50000000 ; pointer to LPC_GPIO_BYTE_DATA
ldr R3,=0x40014008 ; pointer to Timer 0 counter register
; wait for an edge on DATA1
ldrb R1,[R0,#22] ; sample pin 0.7
wait
ldrb R2,[R0,#22] ; compare with pin 0.7
cmp R1,R2
beq wait
; reset timer
ldr R1,=0x00000000 ; reset value
str R1,[R3] ; store in timer
ldrb R1,[R0,#22] ; sample DATA 1 (old1)
ldrb R2,[R0,#16] ; sample DATA 2 (old2)
; wait a fixed half bit time then sample in the middle
ldr R5,=240 ; compare value
wait2
ldr R4,[R3] ; get timer value
cmp R4,R5 ; wait until timeout
blt wait2
; sample data lines again
ldrb r4,[r0,#22] ; sample DATA 1
ldrb r5,[r0,#16] ; sample DATA 2
eors R1,R4 ; FM decode DATA 1
ldr r0,=track1_register_address ; point to track 1 shift register
ldr r0,[r0]
ldrb r4,[r0] ; get current value
lsrs R4,#1 ; shift right track register
lsls R1,#7 ; shift left decoded bit
orrs R4,R1 ; logic OR over R4
strb r4,[r0] ; and store back into track 1 register
eors R2,R5 ; FM decode DATA 2
ldr r0,=track2_register_address ; point to track 2 shift register
ldr r0,[r0]
ldrb r4,[r0] ; get value
lsrs R4,#1 ; shift right track register
lsls R2,#7 ; shift left decoded bit
orrs R4,R2 ; logic OR over R4
strb r4,[r0] ; and store back into track 2 register
pop {r4,r5}
BX LR ; return to caller
ALIGN
END
Gert