Driver Development Kit (DDK)

cancel
Showing results for 
Search instead for 
Did you mean: 

Slow Sampling Rate in DMA aquisition

I am using a PCI-6250 for continuous AI DMA aquisition. Recently I discovered a strange problem regarding the sampling rate.

 

When I set up AI data aquisition at 400,000 kHz / one channel for example, I would expect the DMA buffer to fill at a rate of 800,000 bytes per second (2 bytes per sample * 400,000).

 

Unfortunately, this calculation seems to be only true for one channel. For multiple channels, there is a significant drop in data aquisition rate which I do not understand. Here are some measurement values i took based on the aiex3 example code (modified for continuous aquisition, of course).

 

 

DMA aquisition at 400,000 kHz for ~1 second.

Bytes in buffer after one second:
16 Ch: 149690 15 Ch: 151038 14 Ch: 141684 13 Ch: 152124 12 Ch: 154020 11 Ch: 153566 10 Ch: 156008 9 Ch: 141470 8 Ch: 159056 7 Ch: 163676 6 Ch: 164468 5 Ch: 172808 4 Ch: 140234 3 Ch: 198700 2 Ch: 264882 1 Ch: 794928

My timing is not exact, so a small deviation is to be expected.

 

What is the explanation for this drop in sampling rate?

 

0 Kudos
Message 1 of 8
(9,096 Views)

Hi reini1981,

 

Could you post the modifications that you have made to aiex3? I'd like to understand how the board is being programmed before commenting on what could be wrong.

Steven K.
National Instruments
Software Engineer
0 Kudos
Message 2 of 8
(9,084 Views)

This is the modified aiex3:

 

void test(iBus *bus, u32 numberOfChannels, u32 numberOfSamples, u32 samplePeriodDivisor, u32 *meas, u32 *errors)
{
    u32 dmaSizeInSamples  = 0; 
    u32 dmaSizeInBytes    = 0; 
    u32 userSizeInSamples = 0; 
    u32 userSizeInBytes   = 0;
    u32 sampleSizeInBytes = 0;

    const tBoolean continuous = kTrue; 
    tDMAError status = kNoError; 
   
    //
    // Specify DMA and user buffer sizes
    //

    dmaSizeInSamples = numberOfChannels; 
    dmaSizeInBytes   = dmaSizeInSamples * sizeof(i16); 
   
    userSizeInSamples = numberOfChannels; 
    userSizeInBytes   = userSizeInSamples * sizeof(i16);
    sampleSizeInBytes = sizeof(i16);

    signed short *rawData = new signed short[16];
           
    //
    //  Register Map objects
    //
    
    tAddressSpace Bar1;
    tMSeries     *board;
    
    Bar1 = bus->createAddressSpace(kPCI_BAR1);
    
    board  = new tMSeries(Bar1);

    //
    // DMA objects
    //
    tAddressSpace bar0;
    tMITE       *mite;
    tDMAChannel *dma;

    bar0 = bus->createAddressSpace(kPCI_BAR0);
    mite = new tMITE(bar0);
    mite->setAddressOffset(0x500);
    
    dma = new tDMAChannel(bus, mite);
    
    //
    //  read eeprom for calibration information
    //
    
    const u32 kEepromSize = 1024;
    u8 eepromMemory[kEepromSize];
    eepromReadMSeries (bus, eepromMemory, kEepromSize);   

    //
    // program device
    //
    
    // ---- AI Reset ----
    //
    
    configureTimebase (board);
    pllReset (board);
    analogTriggerReset (board);
    
    aiReset (board);
    // check ai.h for aiPersonalize allowed values
    aiPersonalize (board, tMSeries::tAI_Output_Control::kAI_CONVERT_Output_SelectActive_Low);
    aiClearFifo (board);
    
    // ADC reset only applies to 625x boards
    adcReset(board);
    
    // ---- End of AI Reset ----
    
    // ---- Start AI task ----
   
    aiDisarm (board);
    aiClearConfigurationMemory (board);
    
    for (u32 i = 0; i < numberOfChannels; i++)
    {        
        aiConfigureChannel (board, 
                            i,  // channel number
                            1,  // gain -- check ai.h for allowed values
                            tMSeries::tAI_Config_FIFO_Data&colon;:kAI_Config_PolarityBipolar,
                            tMSeries::tAI_Config_FIFO_Data&colon;:kAI_Config_Channel_TypeRSE, 
                            (i == numberOfChannels-1)?kTrue:kFalse); // last channel?
    }
    
    aiSetFifoRequestMode (board);    
    
    aiEnvironmentalize (board);
    
    aiHardwareGating (board);
    
    aiTrigger (board,
               tMSeries::tAI_Trigger_Select::kAI_START1_SelectPulse,
               tMSeries::tAI_Trigger_Select::kAI_START1_PolarityRising_Edge,
               tMSeries::tAI_Trigger_Select::kAI_START2_SelectPulse,
               tMSeries::tAI_Trigger_Select::kAI_START2_PolarityRising_Edge);
    
    aiSampleStop (board, 
                  (numberOfChannels > 1)?kTrue:kFalse); // multi channel?
    
    aiNumberOfSamples (board,   
                       numberOfSamples, // posttrigger samples
                       0,               // pretrigger samples
                       continuous);     // continuous?
    
    aiSampleStart (board, 
                   samplePeriodDivisor, 
                   3, 
                   tMSeries::tAI_START_STOP_Select::kAI_START_SelectSI_TC,
                   tMSeries::tAI_START_STOP_Select::kAI_START_PolarityRising_Edge);
    
    aiConvert (board, 
               280,     // convert period divisor
               3,       // convert delay divisor 
               kFalse); // external sample clock?
    
    aiClearFifo (board);	
    
    //
    // Configure DMA on the device
    //
    board->AI_AO_Select.setAI_DMA_Select (1);
    board->AI_AO_Select.flush ();

    //
    // Configure and start DMA Channel
    //
    //    DMA operations use bytes instead of samples.
    //    622x and 625x devices transfer 16-bits at a time (1 sample)
    //    628x devices transfer 32-bits at a time (1 sample)
    //    Start the DMA engine before arming/starting the AI engine
    //
    
    status = dma->config (0, tDMAChannel::kRing, tDMAChannel::kIn, dmaSizeInBytes, tDMAChannel::k16bit);
    if (status != kNoError)
    {
        printf ("Error: dma configuration (%d)\n", status);
    }
    
    status = dma->start();
    if ( kNoError != status )
    {
        printf ("Error: dma start (%d)\n", status);
    }
    else
    {
        //
        // No error - arm and start AI engine
        //
        aiArm (board, kTrue);
        aiStart (board);
    }
    
    //
    // DMA Read
    //
    
    u32 totalNumberOfBytes = numberOfSamples * numberOfChannels * sampleSizeInBytes;

    u32 bytesRead = 0;      // bytes read so far
    u32 bytesAvailable = 0; // bytes in the DMA buffer
    u32 tries = 0;          // number of tries without reading
	u32 numberOfErrors = 0;

	Sleep(1000);
	
	//
    //  Stop DMA process and disable DMA on the device
    //

    dma->stop ();
    	
	status = dma->read(userSizeInBytes, NULL, &bytesAvailable);
	printf("Bytes: %d \n",bytesAvailable);

	board->AI_AO_Select.setAI_DMA_Select (0);
    board->AI_AO_Select.flush ();
    
    // cleanup

    //
    // Delete user buffer
    //
    delete [] rawData; 

    //
    // Reset DMA Channel and AI engine
    //
    dma->reset ();
    
    aiReset(board);
    
    //
    // Destroy register maps and address spaces
    //
    delete dma; 
    delete mite;
    delete board;

    bus->destroyAddressSpace(bar0);
    bus->destroyAddressSpace(Bar1);
}

 

 

This is how test() is called:

 

nSamples	= 1000;
nDivisor	= 50;

for(nChannels = 1; nChannels <= 16; nChannels++)
{	
	printf("%d Ch: ",nChannels);
	test(bus,nChannels,nSamples,nDivisor,&nMeasurements,&nErrors);
}
	
releaseBoard(bus);

 

 

 

0 Kudos
Message 3 of 8
(9,074 Views)

Hey reini1981,

 

Thanks for you program. I think I've found a couple problems with your application that are contributing to your issues.

 

1) One thing to keep in mind with the PCI-6250 is that the Analog Input is multiplexed. This means there is only one Analog to Digital Converter in the device, which means the maximum rate of the ADC is shared between all the channels. Here is a good primer on how a multiplex board works:

 

http://zone.ni.com/reference/en-XX/help/370466V-01/mxcncpts/multisimulsamp/

 

The PCI-6250 is limited to 1.25 MS for a single channel and 1 MS shared for multiple channels. In your application, once you hit 3 channels or over, the card's specification is being violated (400 kS * 3 > 1 MS). You need to ensure that the number of channels multiplied by the sampling rate is lower than or equal to 1MS. At 4 channels, the maximum rate is 250 kS. At 16 channels, the maximum rate is 62.5 kS.

 

2) As a result of the multiplexing, when a sample clock occurs, the sampling doesn't happen all at the same time. The board generates another clock called the Convert Clock, which actually specifies when the ADC should sample for the different channels. In the DDK, this is controlled by aiConvert, which is set to a divisor is 281 by default (convert period divisor + 1), which results in a 71.17 kHz clock (20Mhz / 281). This is lower than the cards maxmium of 1 MS. Try setting this 20 which is 1 MS (20Mhz/20). To set a 20 dividor, set aiConvert to 20 which is [desired divisor] - 1.  Here is some documention for setting this value:

 

http://forums.ni.com/t5/Driver-Development-Kit-DDK/timing/td-p/351518

http://forums.ni.com/t5/Driver-Development-Kit-DDK/How-to-do-register-level-programming-for-NI6025E-...

 

Hope this helps. Please let me know if you have any more questions.

Steven K.
National Instruments
Software Engineer
0 Kudos
Message 4 of 8
(9,066 Views)

Hello Steven_K,

 

thanks for your quick answer. After reading your post, I find this all very plausible even though it will provide some technical difficulty for our project ... but that is a story for another day 🙂

 

Regarding the Convert Clock i wonder if there is any reason to choose a value different from the maximum possible setting (19 in this case)? I am not an expert in analog measurement, but it seems to me if you chose the maximum conversion frequency, you should always be on the safe side.

 

Best Regards,

reini1981

 

0 Kudos
Message 5 of 8
(9,056 Views)

The trade off is accuracy. The longer you give the measument to settle, the closer to the actual value it will be. Here is the settling time chart from the 625x spec doc:

http://www.ni.com/pdf/manuals/371291h.pdf

 

 

Screen Shot 2014-11-10 at 5.03.12 PM.png

Steven K.
National Instruments
Software Engineer
0 Kudos
Message 6 of 8
(9,006 Views)

After implementing all the new settings in our application, I found a new problem:

 

The function tDMAChannel::_getBytesInBuffer() now takes significantly longer to calculate the correct number of bytes in buffer. It takes up to 280 µs for the function to return, and sometimes it even returns with the value "0". In debugging I found out, that the function sometimes fails to get a "stableDAR" within the hard coded 100 tries.

 

If I revert everything back to the original settings, it takes less than 1 µs for the function to return.

 

Is there any way to speed this up?

0 Kudos
Message 7 of 8
(8,918 Views)

Hey reini1981,

 

Looking at the code, they way _getBytesInBuffer could have some issues for higher speed acqistitions, since it does multiple reads to the board to check the value of the number of samples ready to be transfered. At faster rates, these multiple reads won't be stable, so it will hit the failure case in _getBytesInBuffer, which results in the "0" value. I've attached a modifed version of the method that I'd like you to try. It removes the multiple reads for a single read.

 

u32 tDMAChannel::_getBytesInBuffer ()
{
    // 1. Update MITE's location
    
    if (_direction == kIn)
    {
        //
        // the device address indicates the number of bytes that have
        // been transfered from the device to the DMA FIFO.
        // Samples in the FIFO have not been transfered to host memory
        // so correct the number device address with the FIFO count.
        //
        // The loop avoids a _writeIdx underflows.
        //
        // 'tries' is there to have an upper bound, but it should never 
        // exceed 100.
        //
        
        u32 deviceAddress = 0;
        u32 deviceAddress1 = 0;
        u32 fifoCount = 0;

        

        fifoCount      = _miteChannel->FifoCount.readFifoCR();
         deviceAddress1  = _miteChannel->DeviceAddress.readRegister ();


        // Update and store 64-bit DAR values.  This allows us to use 64-bit _writeIdx
        // and not worry about rollover

        if(_lastDAR > deviceAddress1)
        { 
            _realDAR = (_realDAR & nOSINT100_mU64bitLiteral(0xFFFFFFFF00000000)) | deviceAddress1;
            _realDAR +=            nOSINT100_mU64bitLiteral(0x0000000100000000); //u32 maxint + 1
            _lastDAR = deviceAddress1;
        }
        else
        {
            _realDAR = (_realDAR & nOSINT100_mU64bitLiteral(0xFFFFFFFF00000000)) | deviceAddress1;
            _lastDAR = deviceAddress1;
        }

        // Update _writeIdx value.  _realDAR will [practically] never roll
        // over, so it is safe to simply subtract fifoCount.
        _writeIdx = _realDAR - fifoCount;

        // Check if _writeIdx went down from last time.
        // If so, is means that we're headed for an erroneous
        // kDataNotAvailable.  Reset _writeIdx to previous
        // value if necessary.  If not, update _lastwriteIdx

        if( ( _writeIdx < _lastwriteIdx ) || 
            ( (_lastwriteIdx == 0) && (deviceAddress1 < fifoCount) ) )
            _writeIdx = _lastwriteIdx;
        else
            _lastwriteIdx = _writeIdx;
    }

    else
    {
        _readIdx = _miteChannel->DeviceAddress.readRegister ();
    }
    
    // 2. Calculate difference between read and write indexes
    //    checking for rollovers
    
    u32 bytesInBuffer = 0; 
    
    if ( _writeIdx < _readIdx )
    {
        bytesInBuffer = 0xffffffff - (u32)( _readIdx - _writeIdx );
        ++bytesInBuffer;
    }
    else
    {
        bytesInBuffer = (u32)(_writeIdx - _readIdx);   
    }    
    
    return bytesInBuffer;
}

 

Steven K.
National Instruments
Software Engineer
0 Kudos
Message 8 of 8
(8,893 Views)