_REAL-TIME DATA ACQUISITION USING DMA_ by Tom Nolan [LISTING ONE /*--------------------------------------------------------------------------*/ /* dma.c -- subroutines for dma data acquisition * The calling routine must declare the variables in the "extern" list * below, and the reset_irq() function. Communication from the main * program to the subroutines is mostly through these global variables. * The calling routine must give values to dma_chan, dma_irq and buf_size, * then call alloc_dma_buf(), dma_setup(), and start_dma(). As each * dma buffer fills up, the interrupt service routine calls start_dma() on * the next buffer. The calling routine can wait for buf_index to * change, then process data pointed to by curr_buf. Cleanup is done * by dma_finish(), which is called automatically when the program exits. * Compiler: Microsoft C Version 5.0 * Set /Gs switch to remove stack probes (a necessity for any * function called at interrupt state!) * Tom Nolan - 8/7/89 */ #include /* DMA Register Definitions */ #define DMA0_BASE 0x00 /* address of dma controller (chan 0-3) */ #define DMA1_BASE 0xC0 /* address of dma controller (chan 4-7) */ /* Interrupt Controller Definitions */ #define INTA00 0x20 /* base address of int ctrlr */ #define INTA01 0x21 /* address of int ctrlr 2nd reg */ #define EOI 0x20 /* code for non-specific end-of-int */ /* External Variables */ extern char far *dma_buffers[; /* array containing buffer addresses */ extern int buf_index; /* index of current buffer in array */ extern char far *curr_buf; /* pointer to just-filled buffer */ extern unsigned buf_size; /* size of buffers in bytes */ extern int lost_buffers; /* count of buffers unable to be written */ extern int dma_irq; /* h/w interrupt when dma complete (0-7) */ extern int dma_chan; /* channel number for dma operation */ extern int file_handle; /* handle of archive file (0=no file) */ extern void reset_irq(); /* function to reset interrupt request */ /* local variables - placed in static storage to * avoid excessive stack usage in interrupt routines */ static union REGS r; /* general registers */ static struct SREGS s; /* segment registers */ static int sel; /* dma channel select bits */ static int basereg; /* dma controller base address register */ static int cntreg; /* dma controller count register */ static int maskreg; /* dma controller mask register */ static int modereg; /* dma controller mode register */ static int pagereg; /* dma page address register */ static int page_tbl[ = /* table of page register addresses */ „ 0x87, 0x83, 0x81, 0x82, /* for dma channels 0, 1, 2, 3 */ 0x8f, 0x8b, 0x89, 0x8a †; /* 4, 5, 6, 7 */ char far *dos_crit_addr; /* address of DOS critical section flag */ static void /* space for saved int vector contents */ (interrupt far *dma_int_save)(); /* macros for extracting bytes from 20-bit addresses */ #define LSB(x) *((unsigned char *) &x) #define MSB(x) *(((unsigned char *) &x) + 1) #define PAGE(x) *(((unsigned char *) &x) + 2) /* Function Prototypes */ void dma_setup(void); void dma_finish(void); int alloc_dma_buf(void); void start_dma(char far *, unsigned); void interrupt far dma_isr(void); int write_buf(); /*--------------------------------------------------------------------------*/ int alloc_dma_buf() /* allocate a pair of dma buffers */ „ unsigned buf; /* temp variables for various */ unsigned max; /* paragraph addreses */ unsigned seg; unsigned size; /* buffer size in paragraphs */ /* This routine allocates a pair of buffers that can be * filled by dma. The buffers are guaranteed to be * aligned so that they do not cross physical page boundaries. * Before calling this routine, set the value of buf_size to * the required number of bytes in each buffer. The maximum * buffer size is 64K bytes, which can be allocated * by specifying a buf_size of zero. The byte count is converted * to paragraphs, which are the units the DOS memory allocation * functions work with. Buffer addresses returned in dma_buffers[0 * and dma_buffers[1. Return value is zero if allocation succeeded, * non-zero (an MS-DOS error code) otherwise. */ size = (buf_size == 0) ? /* convert bytes to paragraphs */ 0x1000 : buf_size >> 4; /* ..by dividing by 16 */ _dos_allocmem(0xffff, &max); /* get max paragraphs from dos */ _dos_allocmem(max, &seg); /* now grab it all */ buf = seg; /* initial attempt at buffer segment */ if( ((buf + size - 1) & 0xf000) /* if buffer crosses */ != (buf & 0xf000) ) /* phys page bdry */ buf = (buf & 0xf000) + 0x1000; /*...adjust to next phys page */ dma_buffers[0 = (char far *) /* convert buffer segment */ ((long) buf << 16); /*... to far pointer for return */ buf += size; /* initial attempt at next buffer */ if( ((buf + size - 1) & 0xf000) != (buf & 0xf000) ) buf = (buf & 0xf000) + 0x1000; /* adjust if crosses page bdry */ dma_buffers[1 = (char far *) /* return it as a far pointer */ ((long) buf << 16); size = buf + size - seg; /* compute actual size needed */ return /* free unneeded memory and */ _dos_setblock(size, seg, &max); /* return error if not enough */ † /*--------------------------------------------------------------------------*/ void dma_setup() /* set up for dma operations */ „ /* Before calling this routine set the following variables: * dma_chan - channel number (hardware dependent) * dma_irq - interrupt request number 0-7 (hardware dependent) */ int intmsk; sel = dma_chan & 3; /* isolate channel select bits */ pagereg = page_tbl[dma_chan; /* locate corresponding page reg */ if(dma_chan < 4) /* setup depends on chan number */ „ basereg = DMA0_BASE + sel * 2; /* standard dma controller */ cntreg = basereg + 1; /* note that this controller */ maskreg = DMA0_BASE + 10; /* is addressed on byte */ modereg = DMA0_BASE + 11; /* boundaries */ † else „ basereg = DMA1_BASE + sel * 4; /* alternate dma ctrlr (AT only) */ cntreg = basereg + 2; /* note that this controller */ maskreg = DMA1_BASE + 20; /* is addressed on word */ modereg = DMA1_BASE + 22; /* boundaries */ † r.h.ah = 0x34; /* dos "get critical flag addr" function */ intdosx(&r, &r, &s); dos_crit_addr = (char far *) /* save its address so it can be tested */ (((long) s.es << 16) ” r.x.bx); /* ... as a far pointer */ if(dma_irq < 0 ”” dma_irq > 7) /* validate interrupt number */ return; dma_int_save = /* save current contents of dma int vec */ _dos_getvect(dma_irq + 8); _dos_setvect(dma_irq+8, dma_isr); /* set up new int service routine */ intmsk = inp(INTA01); /* get current interrupt enable mask */ intmsk &= ~(1 << dma_irq); /* clear mask bit for dma interrupt */ outp(INTA01, intmsk); /* output new mask, enabling interrupt */ atexit(dma_finish); /* register exit function */ † /*--------------------------------------------------------------------------*/ static void dma_finish() /* called via atexit() mechanism */ „ int intmsk; intmsk = inp(INTA01); /* get current interrupt enable mask */ intmsk ”= (1 << dma_irq); /* set mask bit for dma interrupt */ outp(INTA01, intmsk); /* output new mask, disabling interrupt */ _dos_setvect(dma_irq+8, dma_int_save);/* restore old vector contents */ † /*--------------------------------------------------------------------------*/ void start_dma(buf, count) /* start a dma operation */ char far *buf; /* address of buffer to be filled */ unsigned count; /* size of buffer in bytes */ „ int page; unsigned long addr = /* 20-bit address of dma buffer */ FP_OFF(buf) + (long) FP_SEG(buf) << 4; /* This routine starts a dma operation. It needs to know: * - the address where the dma buffer starts; * - the number of bytes to transfer; * The dma buffer address is supplied in segmented, far-pointer * form (as returned by alloc_dma_buf()). In this routine it is * converted to a 20-bit address by combining the segment and * offset. The upper four bits are known as the page number, and * are handled separately from the lower 16 bits. The transfer * count is decremented by 1 because the dma controller reaches * terminal count when the count rolls over from 0000 to ffff. * * The dma transfer stops when the channel reaches terminal count. * The terminal count signal is turned around in the interface * hardware to produce an interrupt when dma is complete. * * Channels 4-7 are on a separate dma controller, available on * the PC-AT only. They perform 16-bit transfers instead of 8-bit * transfers, and they are addressed in words instead of bytes. * This routine handles the addressing requirements based * on the channel number. * * dma_setup() needs to be called before start_dma() in order to * assign values to maskreg, modereg, etc. */ page = PAGE(addr); /* extract upper bits of address */ if(dma_chan >= 4) /* for word-oriented channels... */ „ count >>= 1; /* convert count to words */ addr >>= 1; /* convert address to words */ page &= 0x7e; /* address bit 16 is now in 'addr' */ † count--; /* compute count-1 (xfr stops at ffff) */ outp(maskreg, sel ” 0x04); /* set mask bit to disable dma */ outp(modereg, sel ” 0x44);/* xfr mode (sngl, inc, noinit, write) */ outp(basereg, LSB(addr) ); /* output base address lsb */ outp(basereg, MSB(addr) ); /* output base address msb */ outp(pagereg, page );/* output page number to page register */ outp(cntreg, LSB(count)); /* output count lsb */ outp(cntreg, MSB(count)); /* output count msb */ outp(maskreg, sel ); /* clear mask bit, enabling dma */ † /*--------------------------------------------------------------------------*/ static void interrupt far dma_isr() „ /* This routine is entered upon completion of a dma operation. * At this point the current dma buffer is full and we can * write it to disk. We set the "available data" pointer * to point to the just-filled buffer, and start the next dma * operation on the other buffer. At the conclusion of * operations, we output a non-specific end-of-interrupt * to the interrupt controller. * * The PC bus provides no mechanism for "unlatching" an * interrupt request once it has been serviced. In order to * enable the next interrupt, the hardware must be designed * so that the request can be reset, by a write to an i/o * port, for example. The external routine reset_irq() * must be coded to perform this function. * * Declaring this routine as type 'interrupt', ensures * that all registers are saved, the C data segment is set * correctly, and that the routine returns with an IRET * instruction. Further interrupts are disabled during the * execution of this routine. */ curr_buf = dma_buffers[buf_index; /* post just-filled buf address */ buf_index ^= 1; /* index next buffer */ start_dma(dma_buffers[buf_index, /* start dma on next buffer */ buf_size); if( file_handle ) /* if disk is enabled.. */ write_buf(); /* write buffer to disk */ reset_irq(); /* do hardware-specific reset */ outp(INTA00, EOI); /* signal end of int */ † /*--------------------------------------------------------------------------*/ static int write_buf() /* write buffer to disk file */ „ if( *dos_crit_addr ) /* first check dos critical section flag */ „ lost_buffers++; /* ..if set, skip writing this buffer */ return 0; /* ..not really an error in this case */ † r.x.dx = FP_OFF(curr_buf); /* ok to write now, set address in */ s.ds = FP_SEG(curr_buf); /* proper registers for dos call */ r.x.bx = file_handle; /* set file handle to write to */ r.x.cx = buf_size; /* set byte count for write */ /* WARNING - can't write 64K! */ r.h.ah = 0x40; /* dos write-to-file-handle function */ if(intdosx(&r, &r, &s) == buf_size /* check return value and.. */ && r.x.cflag == 0) /* ..carry flag for success code */ return 0; /* return success */ else „ lost_buffers++; /* didn't write this buffer */ return 1; /* return failure */ † † [LISTING TWO /*--------------------------------------------------------------------------*/ /* test.c -- test dma data acquisition * Compiler: Microsoft C Version 5.0 * Must compile with -Gs option because * reset_irq() is called from interrupt. * Tom Nolan - 8/7/89 */ #include int far *dma_buffers[2; /* pointers to two buffers */ int far *curr_buf; /* pointer to current buffer */ int buf_size; /* buffer size */ int buf_index; /* index of current buffer */ int dma_irq = 3; /* hardware int request line */ int dma_chan = 1; /* hardware dma channel number */ int file_handle = 0; /* file handle */ int lost_buffers = 0; /* write errors */ /* In this program, each dma buffer will be filled with * NUMFR "frames", each of size FRSIZE (in words). The second * word of each frame is a frame counter, which increments * modulo 256. The program checks the frame counters to make * sure they are sequential and no data was lost. */ #define FRSIZE 64 /* words per frame */ #define NUMFR 8 /* frames per dma buffer */ /*--------------------------------------------------------------------------*/ main() „ int temp; int i; unsigned char frame; int far *cp; reset_irq(); /* clear interrupt request */ buf_size = FRSIZE * NUMFR * sizeof(int); /* figure out buffer size */ alloc_dma_buf(); /* allocate buffers */ printf("buf1 = %p buf2 = %p™n", /* informational output */ dma_buffers[0, dma_buffers[1); dma_setup(); /* set up for dma operations */ outp(0x3a0,0); /* reset fifo on hw interface */ start_dma(dma_buffers[0, buf_size); /* start up the data acq */ file_handle = creat("tmp.dat"); /* open a file for raw data */ temp = buf_index; while( !_bios_keybrd(_KEYBRD_READY) ) /* quit on next keystroke */ „ if(temp != buf_index) /* wait for dma complete */ „ printf("%d: ",temp); /* print buffer index */ for(i=0, cp = curr_buf+1; i