This is a 16-bit dsPIC33FJ64GS606 microcontroller with lots of 5V-tolerant inputs. The basic idea is that it captures the data bus and A0 address bit whenever /WR and /CE are low at the same time. I have a 74LS02 NOR gate taking in /WR and /CE, then a rising edge interrupt on the output of the NOR gate. (Not sure if this keyboard might do writes to other stuff with YM2413 /CE high.) The microcontroller queues the captured data up in its RAM, and can do so quite rapidly. Over time, it pops the data out of the queue and sends over serial at 57600 baud via the blue serial-to-USB adapter.
I did some pretty dirty magic to get the external rising edge interrupt as fast as possible. The very first instruction captures everything (data and address), and this happens after 224nsec (interrupt latency). I clear the interrupt flag at the end after a total of 348nsec, which means that I can do captures at a maximum frequency of 2.87 MHz. Actually a bit slower than that because it probably has to complete exiting the interrupt before re-entering. I think that part should be fast enough but my interrupt latency might be too long, not sure. It can't be made faster unless I overclock the micro, which does screw up the UART serial output of this micro. (I tried it.)
Anyway, each capture saves 4 bytes:
- address bit A0, plus one additional input pin reserved. This byte encodes as 0x55 or 0xAA based on the 1 bit to find the beginning of 4-byte packets easily, but can be changed to use both bits if I find another bit I need to record for some reason.
- data bus
- 16-bit 44.1kHz counter high byte
- counter low byte
Here is the source code I came up with:
main.c:
Code: Select all
/*
* File: main.c
* Author: benboldt
*/
#include "p33FJ64GS606.h"
_FBS( BSS_NO_BOOT_CODE & BWRP_WRPROTECT_OFF);
_FGS( GWRP_OFF & GSS_OFF );
_FOSCSEL( FNOSC_FRC & IESO_ON );
_FOSC( POSCMD_NONE & OSCIOFNC_ON & FCKSM_CSECMD );
_FWDT( WDTPOST_PS256 & WDTPRE_PR128 & WINDIS_OFF & FWDTEN_OFF );
_FPOR( FPWRT_PWR1 & ALTSS1_OFF & ALTQIO_OFF);
_FICD( ICS_PGD2 & JTAGEN_OFF );
_FCMP(HYST0_HYST0 & CMPPOL0_POL_RISE & HYST1_HYST0 & CMPPOL1_POL_RISE)
typedef signed char int8_t;
typedef signed int int16_t;
typedef signed long int32_t;
typedef signed long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
typedef unsigned long long uint64_t;
#define OUTP_CPU_DATA_BUS LATD
#define INP_CPU_DATA_BUS PORTD // bits 0..7
#define INP_ADDRESS_BIT PORTDbits.RD8
#define INP_ALL_ITEMS PORTD
// From datasheet:
// FOSC = FIN * ( M / ( N1 * N2 ) )
#define MHZ *(1.0e6)
#define KD_CPU_CLK_FRC (7.37 MHZ) // FIN: Internal FRC Nominal Frequency
#define KD_CPU_CLK_PLLFBD 43.0 // M: PLLFBD for 39.61375 MHz FJ part
//#define KD_CPU_CLK_PLLFBD 53.0 // M: PLLFBD for 48.826248 MHz Overclocked FJ part
//#define KD_CPU_CLK_PLLFBD 65.0 // M: PLLFBD for 59.88125 MHz EP part
#define KD_CPU_CLK_N1 2.0 // N1: PLLPRE
#define KD_CPU_CLK_N2 2.0 // N2: PLLPOST
// Note: 100 MHz < (FIN * M) < 200 MHz on FJ part
// 120 MHZ < (FIN * M) < 230 MHz on EP part
// Note: Change corresponding APSTSCLR bits in ACLKCON if you change the following:
#define KD_ACLK_POST 1.0 // ACLK Postscaler (for PWM timebase)
#define KD_CPU_FOSC ( KD_CPU_CLK_FRC * ( KD_CPU_CLK_PLLFBD / (KD_CPU_CLK_N1 * KD_CPU_CLK_N2) ) )
// From datasheet:
// Instruction Clock FCY = FOSC / 2
#define KD_CPU_CLK_FREQ (KD_CPU_FOSC / 2.0) // 39.61375 MHz
#define KD_UART_BAUD_RATE 57600.0
#define K16_U1BRG_BRGH_0_VAL ((KD_CPU_CLK_FREQ / (16.0 * KD_UART_BAUD_RATE)) - 1.0)
#define K16_U1BRG_BRGH_1_VAL ((KD_CPU_CLK_FREQ / (4.0 * KD_UART_BAUD_RATE)) - 1.0)
volatile uint8_t uart_buffer[4096]; // (4096 = 2^12, 11-bit) Mask is 0xFFF.
volatile uint16_t next_capture_index = 0;
uint16_t next_uart_tx_index = 0;
//volatile uint16_t timestamp = 0;
volatile uint16_t timestamp_enabled = 0;
int main( void )
{
uint16_t byte_order_index;
// ** Initialize Oscillator **
PLLFBD = (uint16_t)(KD_CPU_CLK_PLLFBD - 2);
CLKDIV = 0b1011000000000000 | (((uint16_t)(KD_CPU_CLK_N2 - 2)) << 6) | (uint16_t)(KD_CPU_CLK_N1 - 2);
__builtin_write_OSCCONH(0x01);
__builtin_write_OSCCONL(0x01);
ACLKCON = 0b1010011101000000;
while(OSCCONbits.COSC != 0b001);
while(OSCCONbits.LOCK != 1);
while(ACLKCONbits.APLLCK != 1);
CORCON = 0b0000000011110100;
// ** Initialize Hardware Timer: **
T1CONbits.TON = 0; // Disable Timer
T1CONbits.TCS = 0; // Select internal instruction cycle clock
T1CONbits.TGATE = 0; // Disable Gated Timer mode
//T1CONbits.TCKPS = 0b01; // Select 1:8 Prescaler
T1CONbits.TCKPS = 0b00; // Select 1:1 Prescaler
TMR1 = 0x0000; // Clear timer register
PR1 = 901; // Load the period value. Tuned with scope to 44.1kHz.
IPC0bits.T1IP = 0x01; // Set Timer1 Interrupt Priority Level (lowest possible)
IFS0bits.T1IF = 0; // Clear Timer1 Interrupt Flag
IEC0bits.T1IE = 1; // Enable Timer1 interrupt
T1CONbits.TON = 1; // Start Timer
// ** Initialize GPIO: **
// There is no Port A in this micro.
TRISB = 0b0000000000000000; // [15:0] CPU Address Bus
TRISC = 0b0000000000000000; // Not Used
TRISD = 0b0000000111111111; // [7:0] CPU Data Bus
TRISE = 0b0000000000000000; // Not Used
TRISF = 0b0000000001001000; // [6] INT0, [3] UART Tx
TRISG = 0b0000000000000000; // Not Used
// ** Initialize External Interrupt on Port F6: **
//INTCON2bits.INT0EP = 1; // Falling edge trigger.
INTCON2bits.INT0EP = 0; // Rising edge trigger.
IPC0bits.INT0IP = 7; // Set interrupt priority highest possible.
IFS0bits.INT0IF = 0; // Clear Interrupt Flag.
IEC0bits.INT0IE = 1; // Enable INT0 Interrupt.
// Note that INTCON1bits.NSTDIS = 0 by default, allowing the INT0 IRQ handler to start within the Timer1 IRQ handler.
// ** Initialize UART Tx: **
U1MODE = 0b1000100010001000;
U1BRG = 171;//K16_U1BRG_BRGH_1_VAL; 57600 baud, fine-tuned with scope.
U1STA = 0b0010000000000000;
U1STAbits.UTXEN = 1;
uint8_t sampleByte = 0x00;
// Initialize registers that get used exclusively by non-C code:
asm volatile( " MOV _next_capture_index, W12 "); // Initialize W12.
asm volatile( " MOV #_uart_buffer, W10 ");
asm volatile( " MOV #_uart_buffer+2, W6 ");
asm volatile( " MOV #0, W4 ");
// Main Program Loop
for(;;)
{
//LATE = 0x00; // Debug.
//#define ENABLE_DEBUG_DATA
#if defined( ENABLE_DEBUG_DATA )
if( 0 == U1STAbits.UTXBF )
{
U1TXREG = sampleByte;
sampleByte++;
}
#else
asm volatile( " MOV W12, _next_capture_index ");
if( next_uart_tx_index != next_capture_index )
{
while ( 0 != U1STAbits.UTXBF ); // Wait for UART Tx buffer to be ready.
// Transmit next byte from buffer.
byte_order_index = next_uart_tx_index ^ 1;
if( 0b00 == (next_uart_tx_index & 0b11) )
{
if( 1 == (uart_buffer[byte_order_index] & 1) )
{
U1TXREG = 0xAA;
}
else
{
U1TXREG = 0x55;
}
}
else
{
U1TXREG = uart_buffer[byte_order_index];
}
next_uart_tx_index = (next_uart_tx_index + 1) & 0xFFF; // Increment index and rollover 4095 -> 0
//LATEbits.LATE5 = !LATEbits.LATE5;
}
#endif
}
return 0;
}
//void __attribute__((interrupt, no_auto_psv)) _INT0Interrupt( void ) // High Priority
//{
// // External interrupt on rising edge of YM2413 (/CS NOR /WE).
//
// // Grab Data Bus and Address Bit really fast:
// LATE = 0xFF;
// uint16_t all_data = INP_ALL_ITEMS;
//
// *((uint16_t*)&(uart_buffer[next_capture_index])) = all_data; // Record address bit and data bus.
// *((uint16_t*)&(uart_buffer[next_capture_index + 2])) = timestamp; // Record 44.1kHz timestamp.
//
// next_capture_index = (next_capture_index + 4) & 0xFFF; // Rollover 4095 -> 0.
// // Since bytes are added in multiples of 4, it should be fine to do it at the end like this.
//
// //LATEbits.LATE5 = !LATEbits.LATE5;
// //LATE = 0xFF;
// IFS0bits.INT0IF = 0; // Clear INT0 Interrupt Flag.
//}
void __attribute__((interrupt, no_auto_psv)) _T1Interrupt( void ) // Low priority
{
if( 0 != next_capture_index ) // Hold the timestamp at 0 until the first item gets logged.
{
timestamp_enabled = 1;
}
if( timestamp_enabled )
{
asm volatile( " INC W4, W4 ");
//timestamp++;
}
//LATEbits.LATE5 = !LATEbits.LATE5; // Debug for measuring 44.1kHz, tweaking value written to PR1.
IFS0bits.T1IF = 0; // Clear Timer1 interrupt flag.
}
Code: Select all
.include "p33FJ64GS606.inc"
.global __INT0Interrupt
; ** INT0 IRQ Handler **
__INT0Interrupt:
;BSET LATE, #5 ; Debug.
MOV PORTD, W8 ; Grab Port D
;MOV #0x800, W10 ; W10 is untouched anywhere else, so no point reloading it.
;MOV _next_capture_index, W12 ; W12 is untouched anywhere else, so no point reloading it.
MOV W8, [W10 + W12]
;MOV _timestamp, W8 ; Dedicate W4 to the timestamp instead of using the RAM variable.
;MOV #0x802, W6 ; W6 is untouched anywhere else, so no point reloading it.
MOV W4, [W6 + W12]
ADD W12, #4, W12
BCLR W12, #12 ;Since rollover to zero is triggered specifically at value 0x1000, can just clear bit 12.
;MOV #0xFFF, W10 ; Old way of doing rollover with AND 0xFFF.
;AND W12, W10, W12
;MOV W12, _next_capture_index ; This can be done in the main loop when checking.
;BSET LATE, #5 ; Debug.
BCLR IFS0, #0 ; Clear INT0 interrupt flag.
RETFIE