Implementing Direct Memory Access (DMA) in C



May 01, 1992
URL:http://www.drdobbs.com/implementing-direct-memory-access-dma-in/184402519

May 1992/Implementing Direct Memory Access (DMA) in C

Don C. Bradley is the technical manager for Melville Software, a software consulting firm specializing in the development of custom data acquisition, analysis, simulation, and process control applications. Don has seven years experience on IBM PCs and VAXs using C and assembly language. His current projects include a Sleeping Disorder Analyzer and a Universal Instrument Interface. Don can be reached at Melville Software, Suite 1007, 350 Sparks St., Ottawa, Ontario, Canada, KIR-7S8, (613) 238-1840, or at Compuserve ID 70410, 405.

Direct Memory Access (DMA) has long been thought of as the best method to transfer data to and from a computer's memory. This notion held true as long as the data transfer was simple. As data transfers became complex, DMA quickly faded out of the picture, and interrupts became the favored method. Complex data transfers were implemented only by the best system developers if permitted by the hardware. With the release of the latest "smart" cards (peripheral boards programmable completely by software), developers are choosing DMA for complex data transfers. Since these boards give the developer complete flexibility in controlling the hardware, what was once left to seasoned developers is now easily attainable by most.

This article shows how to implement DMA transfers in C that will handle both simple and complex data transfers from a smart card called the Lab Master AD Data Acquisition board from Scientific Solution. I chose this device, in part, for the variety of simple and complex data transfers imposed on a data acquisition system. The routines presented here form a complete high-speed data acquisition application and can easily be ported to other devices that support DMA.

Polling, Interrupts, and DMA

The simplest method of transferring data is called polling. In this method the CPU checks for when data is ready to be transferred. When ready, the data is transferred from the I/O device to memory. This method is the most versatile of the three methods since it is the least dependent on hardware design. This method requires the CPU to check regularly for when data is ready. Depending on the frequency at which the data will be transferred, no other tasks can be performed. If other tasks (i.e., saving or displaying data) are executed during the checking phase, valid data points might be missed altogether. Missing data is known as a data overrun condition.

Interrupts allow the CPU to switch temporarily to processing the transfer and then return to the interrupted task. The CPU can perform other tasks without missing a transfer during the time the task executes. Data overruns will occur if the transfer interrupt service routine doesn't complete before the next interrupt occurs. Interrupts are, however, more restrictive than polling in their ability to handle conditions imposed on the transfer. As with polling, all tasks run through the CPU.

Direct Memory Access (DMA) transfers data from an I/O device to the computer's memory. This method bypasses the CPU, using instead a device on the system board called a DMA controller. To transfer data without executing commands through the CPU, the card that implements the DMA signals the DMA controller when data is ready for transfer. During the actual transfer, the DMA controller acquires control over the CPU's data and address busses. To signal the end of the actual transfer, the DMA controller is programmed with the amount of data (number of words) to be transferred. The transfer can be prematurely terminated by initializing either the I/O device or the DMA controller during the transfer.

There are three limiting factors in DMA transfers: no program intervention by the CPU, long transfer latency times, and 64KB transfer boundaries. The first limiting factor, lack of program intervention by the CPU, is the reason that DMA is utilized in the first place. In order for conditions to be implemented that effect how data is selected for transfer, the peripheral card must be "smart" enough to detect when to change these selections. The second limiting factor, transfer latency time, occurs due to the fact that the CPU must finish its current instruction before yielding the bus to the DMA controller. If the CPU is executing a string or I/O instruction with a rep prefix, up to 128KB memory and/or I/O cycles could occur before the DMA controller gains access to the bus. Empirical testing has found some "AT" style computers with latencies between the DMA request and DMA cycle of up to 16 microseconds, thus limiting instantaneous transfer rates to 62KHz. This limitation can be overcome with peripheral FIFOs, allowing an average A/D transfer rate up to one million samples/second on an AT and up to four million samples/second on EISA computers. The third limiting factor, 64KB transfer boundaries, can be overcome by programming techniques described later in this article.

DMA is the best method of transferring data since it is the most efficient method for transferring data and the least likely to cause a data overrun with a properly designed peripheral card. Since DMA can run in the background, the CPU can handle other tasks in an uninterrupted way. On the other hand, the transfer must be handled within the limitations of the hardware. In most circumstances, the transfer conditions are very restrictive. With the introduction of smart cards and their software controlled operations, the limitations imposed by the hardware are becoming less restrictive.

Smart cards offer almost full programmability. All the card's possible configurations are achieved through software, rather than dip switches.

Types of Data Transfers

The simplest DMA transfer is one where a device sends the same type of data. A data acquisition card, for example, would transfer one channel's data at a fixed frequency and gain for the duration of the transfer. Most data acquisition cards however allow DMA data transfers from a series of channels starting at the lowest channel to a programmed channel number, at a fixed rate.

Transfers become complex when transfer conditions do not adhere to the design of the DMA transfer in the hardware. Data acquisition sessions where the channels to be sampled are not in a simple sequence and where the gain varies between channels, are very common.

DMA Controller on the PC

The DMA controller implemented on the PC and compatibles (8237A or its equivalent) transfers a maximum of 64KB words using DMA channels 5 to 7. This controller does not address memory in the same manner as the CPU. Figure 1 illustrates the two methods of addressing memory. The CPU addresses memory in real mode by shifting the Base register four bits to the left and adding the Offset register forming a 20-bit address. The DMA controller addresses memory by appending the Offset register to the first four bits in the Page register. This addressing scheme can cause problems when allocating a buffer for the DMA transfer. The allocated buffer should not contain more than one DMA page. For example, allocating a 16KB buffer at address 0x4D1A4 would cover two DMA pages, page 4 and 5, since the buffer would stretch from 0x4D1A4 to 0x511A3.

The 8237 DMA controller transfers data in three main modes: single, block, and demand. Upon receiving a transfer signal from the I/O device, the DMA controller in single mode transfers one word at a time until the number of words transmitted rolls over from zero to 0xFFFF. The single transfer mode allows at least one full CPU cycle between DMA transfers. In block mode the controller sends the entire amount of requested data before the CPU accesses the data and address busses. Finally, in demand mode, the DMA controller transfers data while the I/O device has data ready. As soon as there is no data, the CPU regains access to the data and address busses. The transfer automatically resumes when more data is ready for transfer. The block transfer mode terminates in the same manner as the other two methods, as soon as the word count rolls over. Each of the modes can be programmed so that they repeat once the word count rolls over. Thus the DMA controller can transfer an unlimited amount of data to a limited region of memory.

Lab Master AD

The Lab Master AD is one of the first smart cards for data acquisition available for the IBM ISA or EISA bus computers. It is equipped with 16 single or eight differential fully-programmable Analog-to-Digital (AD) channels with 12-bit resolution, two Digital-to-Analog (DA) channels, eight digital-input and eight digital-output channels, eight digital-expansion channels, and five 16-bit programmable timer/counters tied into a 4MHz base frequency. This card has a 2048 word FIFO buffer for AD and one 1024 word FIFO buffer shared by both the DA channels. The Lab Master makes use of both the Single and Demand transfer modes for AD and DA DMA transfers. The number of AD channels can be expanded to 64 single or 32 differential AD channels.

Data Acquisition Application

The routines in this article use DMA to create a working, high-speed data-acquisition system. I designed the routines to make it easy to port them to any type of data acquisition application. You can easily modify the code to provide variable channel, frequency, and gain sequences, a pseudo real-time display, and some signal processing capabilities.

As previously described, you can implement a DMA transfer in two ways: a one-time transfer of a specified amount of data, or a cyclic transfer of a specified amount of data to the same memory buffer. However, your decision must take into account the amount of data being requested and the amount of free memory available for the allocation of a buffer.

Most DMA transfers for data acquisition consist of cyclic transfers to a circular buffer divided into two equal sections. During the transfer, a buffer is written to disk when it is full (see Figure 2) . This method does not allow any flexibility in performing real-time tasks with the data, however. Figure 3 displays a more flexible approach where the user can access the data at two levels. The first allows some real-time signal processing, such as a moving average filter or basic signal conversions. The second makes the most recent signal for each channel available for a real-time display, or further processing or testing.

The routines presented here take the second approach. The length of time a routine requires to execute a task becomes more critical as the speed and length of the transfer increases. With this in mind I designed the routines so they could be easily optimized for any type of data acquisition process.

The actual application is called ADCTEST. It is implemented on the PC-DOS command line by typing the command

ADCTEST <number of channels> <number of samples> <frequency>
where <number of channels> is the number of channels to sample, starting from channel zero, <number of samples> is the number of conversions performed for each channel, and <frequency> is the frequency at which each sample will be taken.

For example, the command

ADCTEST 10 1000 100.0
causes the program to sample channels 0 to 9 1000 times each at a rate of 100.0 Hz per sample, storing the raw data in the file DATAFILE.DAT. A sample is defined as a series of channels.

The main routine in Listing 1 takes the command line arguments and initializes both the DMA and Lab Master AD for a collection process. Before initializing these devices, main validates the command line arguments. main then verifies that a Lab Master AD is present. If present, the Lab Master AD is reset and enabled. The main routine then allocates memory for a file buffer and opens the file DATAFILE.DAT for writing.

The DMA buffer allocation routine alloc_dma_buffer (Listing 4) allocates the memory buffer such that it contains no DMA page boundaries. You can increase the buffer's maximum size of 32KB to the maximum DMA transfer limit of 128KB by allocating a huge buffer.

The main routine then initializes both the DMA and Lab Master AD by calling init_adc_dma. This routine (Listing 2) consists of allocating the special DMA buffer (Listing 4) , setting up the frequency (Listing 6) , initializing the channel gain array, and calling the DMA initializing routines (Listing 4) . init_adc_dma allows you to pass the address of the function that will process your data. In this application, I pass the address of the function dma_handler (Listing 1) . dma_handler is called from within get_next_adc_values, which gets each value from the DMA buffer. get_next_adc_values retains control until the DMA buffer contains no data.

Once everything has been initialized, the program waits for the user to press the space bar to start the data acquisition process. The application then enters the main loop. This loop is exited once the data acquisition process has finished. Each cycle through this loop calls the get_next_adc_values routine, which checks for DMA buffer overruns, calls the dma_handler routine, and keeps track of the number of conversions processed. Overruns are determined by writing a non-possible value into the buffer after the value is retrieved. This value is checked the next time the get_next_adc_values loop is executed. If this value is not the same, a data overrun error has occured.

dma_handler, as it is presented here, transfers the value passed into the file buffer, writes the file buffer if full, and transfers the value into the appropriate channels storage location in the channel data buffer. The channel data buffer contains the most recent value obtained from a channel.

If time permits during the data acquisition process, the main loop will be executed many times. Tasks that require only the most recent channel's data should be executed within the main loop. In the example, I have presented a section of code that has been commented out. This section would be used for printing the most recent values from the channel data buffer.

Once the data acquisition process is complete, any data remaining in the file buffer is written to disk, the data file is closed, the results of the process displayed to the user, and the Lab Master AD board is disabled. The actual DMA and data acquisition sections of the board are not disabled since the data acquisition process is self-terminating.

The results displayed after the collection process contain the number of times the main loop was executed, the status of the Lab Master AD, and the number of samples collected. If the number of samples collected is less than the number requested, an error message is displayed indicting a data overrun error condition.

Optimizing Performance

The frequency used for multiple channels is actually the specified frequency multiplied by the number of channels to sample. This method causes a lag in the data within each sample. This lag can be virtually eliminated by setting the timer/counter to a burst mode (outlined in [2]). The frequency initialization routine returns the actual frequency to which the timer was initialized and is the closest approximation possible, given the base frequency present at the timer/counter chip.

The present setup limits the sampling frequency to the speed at which the computer can transfer information to disk. If your program requires a disk transfer, you should maximize the size of your disk buffers and try to reduce the rate at which the data is collected. The DMA initializing routines automatically sets up the DMA controller for repetitive cycling if you request an amount of data that is greater than the size of the DMA buffer. You will have to experiment with different sizes of the DMA buffer and the file buffer in order to optimize performance if you request a large amount of data at high frequencies. You should pay special attention to reducing processing times throughout the data acquisition routines wherever possible.

The major drawback to the DMA method implemented here is that an overrun can still occur in the data. The best solution is to tie the DMA buffer full signal to an interrupt that calls a routine that writes the DMA buffer to disk.

If you desire a series of channels that does not conform to a simple series of sampling (channel zero to number of channels minus one), or if you want to use the on-board amplifier at different gains for a channel, you could modify the channel gain array by reading parameters for the data acquisition process from an ASCII configuration file.

Debugging DMA Routines

DMA applications are one of the most difficult programs to debug, since all important actions occur in the background, without the CPU. This situation presents a formidable task when tracking down a bug.

One of the best ways to debug is to check the buffer for changes in values. To determine if the DMA transfer occurs at all, you should fill the DMA buffer with a non-possible value and check the first memory location in the buffer for any change once everything is initialized and started. If your bug is located here, your best strategy is to familiarize yourself with the hardware as much as possible. You can then trace and verify all steps that occur in initializing and starting the process. If possible, write subprograms that test the individual functionality of a routine. For example, a test procedure could be written to verify that the timer routine is working properly.

You can track down problems that arise during the process fairly easily by analyzing the contents of the DMA buffer. Unpredictable errors most likely mean that the DMA controller is not writing to the proper locations. Recognizing the DMA controller's addressing method and the possibility of pageframe boundary errors is also important. The DMA method implemented here allows for easy trapping of overrun errors in the DMA buffer.

Porting the Routines

As with any application, porting routines specific to hardware can be difficult. Keeping this in mind, I have tried to layer them so that porting is as easy as possible. The program's general functionality should not have to change when porting. The most important routines are found in Listing 4 and Listing 8. These DMA controller routines will work with most I/O devices that support DMA and are specific to the 8237 type DMA controller. The routines that control the Lab Master AD are hardware specific and must be modified to work with other types of I/O devices. You should keep the main procedure in Listing 1 the same as you port the application.

Conclusion

DMA is by far the best method of transferring data from an I/O device to memory. With the introduction of new smart cards, the restrictions usually associated with this type of data transfer are disappearing. The routines presented here create a fully functional data acquisition application that is easy to modify, debug, and port. I hope that this article will help anyone considering implementing a DMA transfer process.

References

[1] Operations Manual Lab Master Advanced Design Scientific Solutions, Solon, Ohio.

[2] Microsystem Components Handbook Microprocessors Volume 1, Intel, 1986 p 2-52 2-65.

[3] The Programmer's PC Sourcebook, Thom Hogan, Microsoft Press, 1988, p398, p469-470.

[4] AMD 9513 Data Book, 1985.

Listing 3

Listing 5

Listing 7

Listing 9

May 1992/Implementing Direct Memory Access (DMA) in C/Figure 1

Figure 1: Physical Address Calculation Comparison between the CP and the DMA Controller

May 1992/Implementing Direct Memory Access (DMA) in C/Figure 2

Figure 2 Simple Dual Buffer DMA Implementation

May 1992/Implementing Direct Memory Access (DMA) in C/Figure 3

Figure 3 Real-Time Processing DMA Implementation

May 1992/Implementing Direct Memory Access (DMA) in C/Listing 1

Listing 1 (adtest.c) Application for High-Speed Data Acquisition Using DMA on the Lab Master AD

/****************************************************

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

***********************************************/

#include <math.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <fcntl.h>
#include <io.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>

#include "labmastr.h"
#include "dma.h"

#define ESC 0x1B
#define FILEBUFFSIZE 0x4000

#define OK_EXIT                      0
#define MISSING_PARAMETERS_ERROR     1
#define NOT_ENABLED_ERROR            2
#define CANT_OPEN_FILE_ERROR         3
#define CANT_INITIALIZE_ERROR        4

void main(int argc, char *argv[]);
void dma_handler(int value);

static int file_buffer[FILEBUFFSIZE];
static int file_handle;
static int file_buff_ptr = 0;
static int channel_buffer[MAX_CHANNELS];
static int channel_number = 0;


static double freq = 1000.0;
static long num_of_samples: = 6000L;
static int dma_channel = 5;
static int num_of_channels = 1;


void main(int argc, char *argv[])
       {
       unsigned long num_collected = 0L;
       long loop_count = 0l;
       int ch;

       if(argc < 4){
              printf("Format:\n\n");
              printf("   ADCTEST <number of channels> ");
              printf("<number of samples> <sampling ");
              printf("frequency (Hz)>\n\n");
              exit(MISSING_PARAMETERS_ERROR);
              }

       num_of_channels = atoi(argv[1]);
       num_of_samples = atol(argv[2]);
       freq = atof(argv[3]);

       if (!LabMasterAD_Enable()) {
              printf("LabMasterAD %u V%u was not enabled.\n",
                      LabMasterAD_Product(),
                      LabMasterAD_Version());
              exit(NOT_ENABLED_ERROR);
              }

       printf("\n\nLabMasterAD %u", LabMasterAD_Product());
       printf(" V%u Enabled for\n", LabMasterAD_Version());
       printf("%d channels, ", num_of_channels);
       printf("%ld samples at ", num_of_samples);
       printf("at %lf Hz.\n", freq);

       if((file_handle = open("DATAFILE.DAT", 0_BINARY |
                              0_WRONLY | 0_CREAT | 0 TRUNC, S_IWRITE)) < 0){
              printf("DATAFILE.DAT could not be opened.\n\n");
              exit(CANT_OPEN_FILE_ERROR);
              }

       timer_reset();
       if(!init_adc_dma(num_of_channels, num_of_samples,
                    &freq, dma_channel, dma_handler)){
              printf("Errors initializing DMA transfer.\n\n");
              exit(CANT_INITIALIZE_ERROR);
              }

       printf("\nPress SPACE bar to start.\n\n");

       while ((ch = getch()) != ' ' && ch != 0x1b)
              ;

       if(ch == ESC) {
              printf("Execution Prematurly Terminated.\n\n");
              exit(OK_EXIT);
              }

       printf("ADC DMA has been started.\n");
       printf("Press ESC to exit from ADC DMA.\n\n");

       enable_dma(dma_channel);
       enable_adc();

       while (!adc_dma_done()) {
              if (kbhit())
                     if (getch() == ESC)
                            terminate_adc_dma();

              get_next_adc_values();

              /*
              // Insert here required main loop processing.
              {
              int i;

              for(i=0; i<num_of_channels; i++)
                    printf("%4X    ", channel_buffer[i]);
              for(i=0; i<num_of_channels; i++)
                    printf("\b\b\b\b\b\b\b\b");
              }
              */

              loop_count++;
              }

       // Write remaining values in file buffer.
       write(file_handle, file_buffer, file_buff_ptr*
              sizeof(int));

       printf("\n\nADC DMA was initialized for\n");
       printf("\t%d channels, %ld samples at %lf Hz\n\n",
              num_of_channels, num_of_samples, freq);

       printf("    Status:%8X\n", adc_status());
       printf(" Collected:%8ld\n",
              adc_dma_conversion_count());
       printf("Loop Count:%8ld\n\n", loop_count);
       printf("Finished...    ");

       if(adc_dma_conversion_count() < num_of_channels *
                    num_of_samples)
              printf("OVERRUN ERROR\n\n");
       else
              printf("NO ERRORS\n\n");

       LabMasterAD_Disable();
       exit(OK_EXIT);
       }

void dma_handler(int value)
/*& DMA handler routine. Used for processing each piece
        of     data. */
       {

       file_buffer[file_buff_ptr] = value;

       if(++file_buff_ptr >= FILEBUFFSIZE) {

              write(file_handle, file_buffer,
                    FILEBUFFSIZE*sizeof(int));

              file_buff_ptr = 0;
              }

       channel_buffer[channel_number] = value;

       if(++channel_number >= num_of_channels)
              channel_number = 0;

       // Insert here any further process that may be
       // required.
       // Be carful that the new code does not cause the
       // duration of this routine to exceed the required
       // limits for the selected data acquisition
       // specification.
       }
/*  End of File */
May 1992/Implementing Direct Memory Access (DMA) in C/Listing 2

Listing 2 (adcdma.c) Routines for Data Acquisition through DMA on the Lab Master

/*****************************************************

       Routines for Data acquisition through DMA on the
       Lab Master AD.

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

*****************************************************/

#include <io.h>
#include <errno.h>
#include <conio.h>

#include "dma.h"
#include "labmastr.h"

// DMA buffer
static int far *dma_buffer;

// DMA buffer index
static unsigned int dma_buff_index;
// previous DMA buffer index
static unsigned int old_dma_buff_index;

// number of conversions to collect
static long num_to_collect;
// number of conversions collected
static long num_collected;

// DMA finished flag
static int dma_finished;
// DMA channel number
static int dma_chn;

void (*process)(int);

/*****************************************************
       init_adc_dma is a routine used for dma controlled
       analogue voltage input.

       called by:

       init_adc_dma(num_chn, num_samples, *freq, dma_chn,
                            proc_func);

       where
              int num_chn           # of channels to sample.
                                       Start channel is always
                                       chn 0. Thus chans collect
                                       are 0 ... num_chn-1
              long num_samples     # of samples to collect.
                                       A sample is the group of
                                       channels
              double *freq          Frequency of sampling in Hz.
              int dma_chn                DMA channel number from 5-7.
              void(*proc_func)(int) process function to be
                                called after each value is
                                                   retrieved.
*****************************************************/
int init_adc_dma(unsigned int num_chn,
              long num_samples, double *freq, int dma_channel,
              void (*proc_func)(int))
/*& Setup adc for dma transfer. */
       {
       long length;
       unsigned int i;
       unsigned int dma_mode;

       // check for proper passed values
       if (dma_channel < 5 || dma_channel > 7)
              return (0);

       dma_chn = dma_channel;

       disable_dma(dma_chn);

       disable_adc();

       if(num_chn > MAX_CHANNELS)
              return(FALSE);

       if (*freq == 0.0)
              return (FALSE);

       process = proc_func;

       /* create buffer for storing data in */

       if(!(dma_buffer = alloc_dma_buffer(dma_chn,
                ADC_DMA_BUFFER_LEN)))
              return (FALSE);

       clr_adc_dma_buffer();

       // Frequency adjusted for number of channels.
       // Next version will have a burst mode enabled.
       *freq *= num_chn;

       *freq = timerad(*freq);

       // Readjust frequency for number of channels.
       *freq /= num_chn;


       // Setup the channel gain array.
       for (i = 0;  i < num_chn;  i++) {
              outp(ADC_VIRTCHAN, i);
              outp(ADC_CHN_GAIN_ARRAY,
                       ADC_GAIN_1 | ADC_BANK_0 | i);
              }

       // set adc control register
       outp(ADC_CONTROL, ADC_FIFO_ENABLE | ADC_SINGLE_ENDED | ((dma_chn - 4) << 2));

       outp(ADC_LASTCHAN, num_chn-1);

       // Start converstions at channel zero
       outp(ADC_VIRTCHAN, 0);

       num_to_collect = num_chn * num_samples;
       num_collected = 0L;

       length = (num_to_collect < (long)ADC_DMA_BUFFER_LEN) ? 
          num_to_collect : (long)ADC_DMA_BUFFER_LEN;

       // Setup dma transfer.
       if(num_to_collect > (long)ADC_DMA_BUFFER_LEN)
              dma_mode = DMA_DEMAND_MODE | DMA_ADDRESS_INC |
                      DMA_CONTINUOUS_ENABLE | DMA_ADC_TRANSFER;
       else
              dma_mode = DMA_DEMAND_MODE | DMA_ADDRESS_INC |
                      DMA_CONTINUOUS_DISABLE | DMA_ADC_TRANSFER;

       dma(dma_chn, dma_mode, dma_buffer, (unsigned int) length);

       dma_finished = FALSE;

       return (TRUE);
       }

void get_next adc_values()
/*& Returns the pointer to the next memory location,
        increments appropriate buffer pointers. */
       {

       if(dma_buffer[dma_buff_index] == NON_ADC_VALUE)
              return;

       // wait until sample has been recorded
       while(dma_buffer[dma_buff_index] != NON_ADC_VALUE) {

              // Check for overrun by looking at last adc
              // buffer location. If this     location has a valid
              // adc value then an overrun has occurred. An
              // over run condtion will result in automatic
              // termination of adc collection. To detect an
              // overrun the number of samples collected will
              // be less than those requested.

              if(dma_buffer[old_dma_buff_index] != NON_ADC_VALUE) {
                    terminate_adc_dma();
                    return;
                    }

              process(dma_buffer[dma_buff_index]);

              // clear collected value from DMA buffer
              dma_buffer[dma_buff_index] = NON_ADC_VALUE;

              // increment buffer pointer
              old_dma_buff_index = dma_buff_index;
              if (++dma_buff_index >= ADC_DMA_BUFFER_LEN)
                    dma_buff_index = 0;

              // Increment number of conversions collected.
              // Used in automatic termination for repetitive
              // sampling under DMA.
              if(++num_collected >= num_to_collect) {
                    terminate_adc_dma();
                    return;
                    }
              }
       }

void terminate_adc_dma()
/* Terminates adc dma collection. */
       {
       disable_dma(dma_chn);
       outp(ADC_CONTROL, 0);

       free_dma_buffer(dma_chn);

       dma_finished = TRUE;
       }

void clr_adc_dma_buffer()
/*& Clears the adc buffer to a non possible value. */
{
       unsigned int i;

       for (i = 0;  i < ADC_ DMA_BUFFER_LEN; i++)
              dma_buffer[i] = NON_ADC_VALUE;

       dma_buff_index = 0;
       old_dma_buff_index = ADC_DMA_BUFFER_LEN-1;
}

int adc_dma_done(void)
/*& Returns TRUE if all conversions are done. */
       {
       return(dma_finished);
       }

long adc_dma_conversion_count()
/*& Returns the number of samples collected. */
       {
       return(num_collected);
       }

/* End of File */
May 1992/Implementing Direct Memory Access (DMA) in C/Listing 3

Listing 3 (adc.c) Routines for Basic Control of the ADC on the Lab Master AD

/*****************************************************

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

*******************************************************/

#include <conio.h>

#include "labmastr.h"

void disable_adc()
/*& Disable adc through stopping timer 5's output. */
       {
       timer5_off();
       }

void enable_adc()
/*& Enable adc through starting timer 5's output. */
       {
       timer5_on();
       }

int adc_ status()
/*& Return the ADC status */
       {
       return (inp(ADC_STATUS));
       }
/* End of File */
May 1992/Implementing Direct Memory Access (DMA) in C/Listing 4

Listing 4 (dma.c) Generic Routines for Programming the DMA Controller 8237 on the IBM PC or Compatible

/*******************************************************

       Generic Routines for programming the DMA controller
       8237, on the IBM PC or compatible

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is 
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

*******************************************************/

#include <conio.h>
#include <malloc.h>
#include <stdio.h>

#include "dma.h"

#define MASK_REG                      0xd4
#define MODE_REG                      0xd6
#define COUNT_REG                     0xc6
#define MEM_PAGE5_REG          0x8b
#define MEM_PAGE6_REG          0x89
#define MEM_PAGE7_REG          0x8a
#define MEM_OFFSET_REG         0xc4

static int far *dma_buffer[10];

int dma(int dma_chan, int mode, int far *buffer,
              unsigned int buffer_len)
       {
       long physaddr;
       int port, page_port;

       // values passed OK?
       if (dma_chan < 5 || dma_chan > 7)
              return (FALSE);

       if (buffer == 0)
              return (FALSE);

       // Initalize 8237 DMA Controller

       // Disable DMA Channel first
       disable_dma(dma_chan);

       // Setup DMA mode register
       outp(MODE_REG, mode | (dma_chan - 4));

       /* Setup page and offset regs */
       switch (dma_chan) {
       case 5:
              page_port = MEM_PAGE5_REG;
              break;
       case 6:
              page_port = MEM_PAGE6_REG;
              break;
       case 7:
              page_port = MEM_PAGE7_REG;
              break;
              }

       physaddr = (((long)buffer>>12) & 0xFFFF0L) + ((long) buffer & 0xFFFFL);

       // output DMA buffer page number.
       outp(page_port, (int) (physaddr >> 16));

       // Shift left 1 bit for working with words (ints)
       physaddr /= 2;

       // Output DMA buffer offset.
       port = MEM_OFFSET_REG + (dma_chan - 5) * 4;
       outp(port, (int)physaddr & 0xFF);
       outp(port, (int)(physaddr>>8) & 0xFF);

       // Transfer Count, low byte first then high byte.
       port = COUNT_REG+(dma_chan-5)*4;
       outp(port, (buffer_len & 0xFF));
       outp(port, ((buffer_len >> 8) & 0xFF));

       return (TRUE);
       }

void disable_dma(int chan)
/*& Disable DMA channel */
       {
       if (chan < 5 || chan > 7)
              return;

       outp(MASK_REG, DMA_DISABLE | (chan - 4));
       }

void enable_dma(int chan)
/*& Enable DMA channel */
       {
       if (chan < 5 || chan > 7)
              return;

       outp(MASK_REG, DMA_ENABLE | (chan - 4));
       }

int far *alloc_dma_buffer(int dma_chn,
              unsigned int size)
/*& Allocates a DMA buffer containing no page
        boundary */
       {
       long physaddr, page, offset, extra;
       int far *buffer;

       // Values passed OK?
       if (dma_chn < 5 || dma_chn > 7)
              return (NULL);

       if(!size)
              return(NULL);

       // Create dma buffer
       size *= sizeof(int);

       if(!(buffer = (int far *)_fmalloc(size)))
              return (NULL);

       physaddr = (((long)buffer & 0xFFFF0000L)>>12) +
              ((long)buffer & 0xFFFFL);

       page = (physaddr & 0xFFFF0000L) >> 16;
       offset = physaddr & 0xFFFFL;

       if(offset + size > 0xFFFFL) {
              extra = offset + size -0xFFFFL + 10;
              size += extra;

              // can't have a buffer > 64K
              if(size > 0xFFFFL)
                    return(NULL);

              if(!(buffer = (int far *)_expand(buffer,
                                               size)))
                    return(NULL);

              page++;
              offset = 0;
              }

       // save actual buffer address for deallocation
       dma_buffer[dma_chn] = buffer;

       // return proper buffer address for DMA usage
       buffer = (int far *)((page<<28) + offset);
       return(buffer);
       }

void free_dma_buffer(int dma_chn)
       {
       // values passed OK?
       if (dma_chn < 5 || dma_chn > 7)
              return;

       _ffree(dma_buffer[dma_chn]);
       }
/* End of File */
May 1992/Implementing Direct Memory Access (DMA) in C/Listing 5

Listing 5 (labmastr.c) Basic Routines Required for Programming the Lab Master AD

/*****************************************************

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

*****************************************************/

#include <conio.h>

#include "labmastr.h"

int LabMasterAD_Enable()
/*& Enables the Labmaster AD card. Returns Enable
        Status. */
       {
       if(LabMasterAD_Product() != LM_ID)
              return (0);

       outp(BRD_ENABLE, 1);
       return (inp(BRD_ENABLE) & 1);
       }

int LabMasterAD_Disable()
/*& Disables the Labmaster AD card. Returns Enable
        Status. */
       {
       outp(BRD_ENABLE, 0);
       return (inp(BRD_ENABLE) & 1);
       }

int LabMasterAD_Product()
/*& Returns the Labmaster AD Product Code */
       {
       return (inp(PRODUCT));
       }

int LabMasterAD_Version()
/*& Returns the Labmaster AD Version Number */
       {
       return (inp(VERSION));
       }

/* End of File */
May 1992/Implementing Direct Memory Access (DMA) in C/Listing 6

Listing 6 (timer.c) Basic Routines for Programming the 9513 Timer Counter on the Lab Master AD

/*****************************************************

       Basic Routines for programming the 9513 timer
       counter on the Lab Master AD.

       Copyright Don Bradley, 1991.

       Permission is granted for used of these routines
       in any manner as long as this copyright notice is
       included.

       Tested using Quick C 2.5 and MSC 6.0 on a
       Toshiba T5200.

*****************************************************/

#include <math.h>
#include <conio.h>

#include "labmastr.h"

void timer_reset()
       {
       /* reset 9513 timer\counter */
       outp(TIMER_CONTROL, 0xFF);

       /* Enable 16-bit Data Access */
       outp(TIMER_CONTROL, 0xEF);

       /* initalize master mode reg */
       outp(TIMER_CONTROL, 0x17);

       /* BCD Division, 16 Bit Data */
       outpw(TIMER_DATA, 0xA000);
       }

void timer5_on()
/*& Turns timer 5 on. Used to start ADC output from the
        fifo thru manual or DMA control. */
       {
       /* turn timer 5 on */
       outp(TIMER_CONTROL, 0x30);
       }

void timer5_off()
/*& Turns timer 5 off. Used to stop or disable ADC
        output from the fifo thru manual or DMA control. */
       {
       /* turn timer 5 off */
       outp(TIMER_CONTROL, 0xD0);
       }

#define COUNTSTART 0xb
#define COUNTHIGH 0xf

double timerad(double freq)
/*& Sets timer 5 of the labmaster board to the desired
        frequency for data collection. */
       {
       double time;
       unsigned count = COUNTSTART;

       time = TIMER_F1 / freq;
       while (1) {
              if (time < 65536.0)
                     break;
              time /= TIMER_DIVISOR;
              ++count;
              }
       if (count <= COUNTHIGH) {
              timer5_off();

              freq = TM_TIMER F1 / ((unsigned) (time) *
                    pow(10.0, (double) (count - COUNTSTART)));

              // initalize master mode register
              outp(TIMER_CONTROL, 0x17);
              // BCD Division, 16 Bit Data Bus counter 5 setup
              outpw(TIMER_DATA, 0xA000);
              outp(TIMER_CONTROL, 0x5);

              // No Gating, Count on Rising Edge,
              // F(count-COUNTSTART), Count Repetitively,
              // Count Down, Active High Terminal Count Pulse
              // frequency range 400Hz to 4MHz
              outpw(TIMER_DATA, (count << 8) | 0x21);

              outpw(TIMER_DATA, (int) time);

              timer5_on();
              }
       else
              freq = 0.0;
       return (freq);
       }

void step_timerad()
       {

       timer5_off();

       // Point to Counter 5
       outp(TIMER_CONTROL, 0x5);

       // Active High output, Count Down, Binary Counting,
       // Count Repeatedly, Reload from Load Register
       // only, No Gateing, No Special Gate, Count Falling
       // Edges and Use Source 5.
       outpw(TIMER_DATA, 0x1521);
       outpw(TIMER_DATA, 2);

       // Load Counter 5
       outp(TIMER_CONTROL, 0x50);
       timer5_on()
       // Step Counter 5
       outp(TIMER_CONTROL, 0xF5);
       }
/* End of File */

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.