/* SOURCE FILE: MPUISR.C * Last modified: Greg Hendershott, 9-13-1986. * * ---------------------------------------------------------------------- * Copyright 1986 Greg Hendershott. Permission is hereby granted to * use and distribute this material for non-commercial purposes. This * code may not be sold to others, nor used to develop a program which * will be sold to others. * ---------------------------------------------------------------------- * * Note: I've used a tab size of 3 in my editor for this file. * * This file contains a set of routines for communicating with the * Roland MPU-401. There are routines for initialization and * de-initialization, for sending MPU commands and data, and for * handling the interrupts the MPU generates when it wishes to * send information or ACKnowledge a command you've sent it. * * The main interrupt handler, main_handler(), dispatches control to one * of several sub-handlers. This scheme is necessary because MIDI * messages are sent over one byte at a time, but individual bytes have * different meanings that are dependent on the order they are sent in. * * This set of functions is written so that some are simply declared * here, since their content may vary from application to application. * For instance, the routine data_handler() is called when there is a * byte of incoming data that needs to be stored. How and where you'll * store this may vary depending on the program you're writing. These * functions, in other words, are designed in as general-purpose a way * as possible. You can compile this, put it in a library, and then * link it to a variety of applications. * * These are: * data_handler() * play_handler() * clock_handler() * measure_handler() * event_handler() * * Your source code must supply these routines for the linker. The * only two you may ever have to deal with are data_handler() and * play_handler(). data_handler() is called by the ISR whenever it * has a byte of recorded data for your program to do something with. * Typically your program should just stuff it in a buffer someplace. * play_handler() is called whenever the MPU needs another MIDI event * to be played. Here your routine must determine the MPU track * number from the lower nibble of the argument and use put_data() to * send out a complete MIDI event including leading timing byte. * * Remember that all these routines are called by the interrupt handler * and that you should avoid things that shouldn't be done inside an * interrupt handler, like make most DOS calls. * * CURIOUS NOTE: As far as the interrupt vector table is concerned, the * MPU-401 generates interrupt 0x0A (10d). Everything else -- manuals, * PIC 8259 chip, articles -- thinks it generates 0x02 (2d), which * corresponds to the IRQ2 non-maskable interrupt line. As for me, * I am clueless, but things seem to work as long as the above parties * are told what they want to believe. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*#define DEBUG*/ /*#define LINT_ARGS*/ #include #include /* Byte typedef */ typedef unsigned char byte; /* Timeout value for attempts to communicate with MPU */ #define MAX_TRIES 5000 /* Constant values for the sub-handlers variable currentHandler */ #define MESSAGE 0 #define REC_FIRST 1 #define REC_SECOND 2 #define REC_THIRD 3 #define REC_SYSX 4 /* Interrupt set/restore constants */ #define IRQ2 0x0A #define PIC_IMR 0x0021 /* PIC 8259 Interrupt Mask Register */ #define STACK_SIZE 256 /* Global variables a calling program may use. * Header file mpuisr.h contains the proper 'extern' declarations. */ int isr_dataInStop; /* flag to just dump in buffer */ int isr_allEnd; /* set true when 0xFC received */ /* Some internal variables */ static int ackFlag; /* set to true when ACK received */ static int currentHandler; /* the current sub-handler */ #ifdef DEBUG static unsigned services; /* # of calls to main_handler() */ #endif /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * lookup routines * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* lookup * * Returns the number of data bytes that follow the voice message status * byte, or zero if argument is not a voice message status byte. */ lookup(status) { switch(status & 0xF0) { case 0x80: return 2; /* note off */ case 0x90: return 2; /* note on */ case 0xA0: return 2; /* polyphonic key pressure */ case 0xB0: return 2; /* control change */ case 0xC0: return 1; /* program change */ case 0xD0: return 1; /* channel pressure */ case 0xE0: return 2; /* wheel change */ default: return 0; /* not a voice message status byte */ } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * send/receive routines * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* waitforDRR * * Waits until statport shows ready to receive data -- is NOT ready if * bit 6 is set to 1 (is ready when cleared). * * Returns zero if MPU was not ready to receive it after MAX_TRIES * checks of the DRR flag; otherwise returns non-zero. * * Used by both put_cmd() and put_data(). */ static waitforDRR() { register int tries; for (tries=0; (inportb(STATPORT) & DRR) && (tries < MAX_TRIES); tries++) ; return( tries < MAX_TRIES ); } /* put_cmd * * Sends a command to the MPU through the COMPORT. Waits for ackFlag to * be set to 1 by the interrupt service routine, which will set this * on receipt of an ACK (0xFE) from the MPU. * Returns: zero if either waitforDRR() timed out or the wait for ackFlag * timed out, else non-zero. */ put_cmd(command) { register int tries; if (!waitforDRR()) return(0); ackFlag = 0; outportb(COMPORT, command); if (command == UART) return 1; /* MPU doesn't ACK UART commands */ /* Wait till handler gets ACK and sets ackFlag to 1 -- but not forever. */ for (tries=0; (!ackFlag) && (tries= 0x80) { /* It's a new status byte */ runningStatus = data; /* running status now this status */ if ((data & 0xF0) == SYSX) currentHandler = REC_SYSX; /* system exclusive? */ else { dataBytesLeft = lookup(data); /* # of data bytes following */ currentHandler = REC_THIRD; } append_evtbuf(data); /* add to our event buffer */ data_handler(data); /* give to data_handler() */ } else { /* More data under the running status byte */ dataBytesLeft = lookup(runningStatus); currentHandler = REC_THIRD; rec_3rd_handler(data); } } } /* rec_3rd_handler */ rec_3rd_handler(data) { append_evtbuf(data); /* add to our event buffer */ data_handler(data); /* give to data_handler() */ if (--dataBytesLeft == 0) { event_handler(evtbuf); /* got a complete event for event_handler() */ currentHandler = MESSAGE; } } /* rec_sysx_handler */ rec_sysx_handler(data) { append_evtbuf(data); /* add to our event buffer */ data_handler(data); /* give to data_handler() */ if (data == EOX) { event_handler(evtbuf); /* got a complete event for event_handler() */ currentHandler = MESSAGE; } /* Actually, the MIDI Spec 1.0 says that a non-real-time status byte * can end a system exclusive message, but this better not happen, * because any new status byte will be prefaced by the MPU with a * timing byte. Ergo, if we see a new status byte here, rather than * at rec_1st_handler, we've already missed the boat. No way out of * this, as far as I can tell. Just have to hope that all SYSX * messages are ended by EOX bytes, nice and kosher. */ } /* main_handler * * This is the main interrupt service routine, which does interrupt * service bookkeeping and dispatches control to one of the sub-handlers. * It is called by c_handler(), which gets a data byte from DATAPORT. * This allows debugging by calling main_handler with imaginary incoming * data in non-realtime. * */ main_handler(data) { #ifdef DEBUG ++services; /* If debugging, record this call */ #endif /* Dispatch control to current sub-handler */ switch (currentHandler) { case MESSAGE: message_handler(data); break; case REC_FIRST: rec_1st_handler(data); break; case REC_SECOND: rec_2nd_handler(data); break; case REC_THIRD: rec_3rd_handler(data); break; case REC_SYSX: rec_sysx_handler(data); break; } } /* c_handler * * This is called by the assembler shell created by intrinit. * It gets the waiting data byte from DATAPORT and calls main_handler(). */ /*far*/ c_handler() { static int data; data = inportb(DATAPORT); /* Get waiting data byte from dataPort */ main_handler(data); /* Pass it to main_handler */ outportb(0x20,0x20); /* send End Of Interrupt to interrupt controller*/ /* required for C86 v2.1 BB */ } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * initialization * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ init_handler() { byte imr; /* Set up main_handler with STACK_SIZE stack reservation to service IRQ2 */ intrinit(c_handler, STACK_SIZE, IRQ2); /* Enable hardware interrupt IRQ2 */ imr = inportb(PIC_IMR); imr = imr & 0xFB; /* 0xFB = 11111011 binary -- unmask 3rd bit for IRQ 2 */ outportb(PIC_IMR, imr); /* Initialize some handler routine variables */ currentHandler = MESSAGE; isr_dataInStop = 0; return 1; /* aok */ } /* can't use this yet - C86 2.1 doesn't have intrrest(). BB void deinit_handler() { byte imr; /* Disable IRQ2 interrupt handling */ imr = inportb(PIC_IMR); imr = (imr | 0x04); /* mask 3rd bit */ outportb(PIC_IMR,imr); /* Restore vector table entry */ intrrest(IRQ2); } */ imr | 0x04); /* mask 3rd bit */ outportb(PIC_IMR,imr); /* Restor