This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

High Frequency Digital Input Sampling with CC2540



Hi guys!

I'm having a problem with sampling a signal using gpio. Hope you can help me.

I'm using as base the stack 1.4.0 and project Keyfob, I modified that project to have one HID service with one characteristic of measurement interval with a data of 2 bytes (uint16) as notification.

The program is currently posting in that characteristic the count of how many pulses I can count (every second) using interruption over port P0.7 as input for the signal. For different signals I'm having the following count results:

* For frequency of 10KHz with pulsewidth of 100ns: a count of 9,980 (approx)

* For frequency of 50KHz with pulsewidth of 100ns: a count of 48,900 (approx)

So as you can see, for higher frequencies the device can't catch all pulses and that seems very reasonable couse each pulse involves interruption from sleep and that takes time in which we could probably lose some pulses. Now, i'm thinking about not using interruption for the input port, but instead check directly the status of P0.7. Now, that involves to not sleep. So here comes the question:

How can I append (maybe) a "task" that can run with high priority that doesn't need an event to run, and sampling this input with the minimum error?

I'd really apreciate your help

  • Hello langelog,

    If you get a input signal of 50kHz with your implementation, you will never have time to enter sleep because to enter and wake up from PM2 will take much longer then the duty cycle of the signal. Also the BLE stack need to have the highest priority and in certain conditions can cause you to miss input edges (interrupt not processed in time before the next occur). For input signals with very low frequency your solution works just fine.

    I would suggest you implement the frequency counter by using the timer compare function and DMA instead. I have attached example code for an example on how to accomplish this. This is how it works:

    • Set up TIEMR1 to capture time on input edge on wanted GPIO.
    • Set up DMA channel to transfer the captured time to a variable (volatile uint16 timerCapture;) automatically for NUM_CAPTURES of times.
    • Store the first capture on the first edge (start of function).
    • wait until NUM_CAPTURES  captures have been completed (wait for DMA interrupt).
    • Retrieve the last captured value from timerCapture.
    • calculate frequency from the first and last capture value. 

    This exploits the DMA interrupt which can be defined to trigger only after a certain number of transfers have been done. The only drawback with this method is that the duration will vary depending on the input frequency as we define the number of samples in advance and not duration. You could adjust this or potentially add an algorithm to vary the number of samples depending on input frequency (for example start with very few number of samples and increase this if the input frequency is very high and you need to increase the accuracy. Then you can start a new measurement with an increased number of samples. to get better accuracy/average).

    Please note that this is a stand alone example and you should implement the wait for dma interrupt with ISR instead of the given while statement:

    //Wait for X edges
    while( ( DMAIRQ & (1<<2) ) == 0);

    FYI1: If you use encryption in your BLE application this will use dam channel 1 and 2. So use DMA channel 3 or 4 (#define T1CAP_DMA_CH 3).

    FYI2: I would say that it takes less than 10 clock cycles to transfer a byte with low or no other traffic on the DMA bus.

    Source files:

     freqCapture.h

    /*
     *  Filename:       freqCapture.c
     *  Revised:        15.12.2015
     *  Revision:       1.0
     *
     *  Description:    Frequency capture sample code
     *
     *
     *  Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ 
     * 
     * 
     *  Redistribution and use in source and binary forms, with or without 
     *  modification, are permitted provided that the following conditions 
     *  are met:
     *
     *    Redistributions of source code must retain the above copyright 
     *    notice, this list of conditions and the following disclaimer.
     *
     *    Redistributions in binary form must reproduce the above copyright 	
     *    notice, this list of conditions and the following disclaimer in the 
     *    documentation and/or other materials provided with the   
     *    distribution.
     *
     *    Neither the name of Texas Instruments Incorporated nor the names of
     *    its contributors may be used to endorse or promote products derived
     *    from this software without specific prior written permission.
     *
     *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
     *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
     *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
     *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
     *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
     *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
     *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
     *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
    */
    
    
    #include "freqCapture.h"
    #include "hal_dma.h"
    
    
    volatile uint16 timerCapture;
    volatile double frequency = 0;
    volatile double ticksAvg;
    volatile double firstCap;
    volatile double lastCap;
    
    
    void timerCaptureArmDMA()
    {
      halDMADesc_t *ch;
    
      ch = HAL_DMA_GET_DESC1234( T1CAP_DMA_CH );
    
      // Abort any pending DMA operations (in case of a soft reset).
      HAL_DMA_ABORT_CH( T1CAP_DMA_CH );
    
      // The start address of the destination.
      HAL_DMA_SET_DEST( ch, &timerCapture );
    
      // Using the length field to determine how many bytes to transfer.
      HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );
    
      // One byte is transferred each time.
      HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );
    
      // The bytes are transferred 1-by-1 on Tx Complete trigger.
      HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
      HAL_DMA_SET_TRIG_SRC( ch, HAL_DMA_TRIG_T1_CH0 );
    
      // The source address is incremented by 1 byte after each transfer.
      HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );
    
      // The destination address is constant - the T1 Compare 1.
      HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 ); //HAL_DMA_DSTINC_1
    
      // The DMA transfer done is serviced by ISR in order to maintain full thruput.
      HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );
    
      // Xfer all 8 bits of a byte xfer.
      HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
    
      // DMA has highest priority for memory access.
      HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH);
      
      HAL_DMA_SET_LEN( ch, NUM_CAPTURES);
      
      HAL_DMA_SET_SOURCE(ch, 0x62A6);
      
      HAL_DMA_ARM_CH( T1CAP_DMA_CH );
      
      HAL_DMA_CLEAR_IRQ( T1CAP_DMA_CH );
    }
    
    
    
    
    void initFreqCapture()
    {
      P0DIR &= ~(1<<2);
      P0SEL |= (1<<2);
      P2DIR |= 0x2 << 6;
      uint8 cnt = 1;
    }
      
     
    double freqCapture()
    {
        // Intialize timer
        T1CTL = 0;
        T1CNTL = 0;
        T1CCTL0 = 0; // Reset Ch0 mode so we don't get spurious '0' DMA-copies on next iteration.
        T1STAT = ~(1 << 0);
    
        // Set up DMA to copy from T1CC0 to someplace.
        timerCaptureArmDMA();
        DMAIRQ = ~(1<<2);
        // Set timer1 input capture
        T1CCTL0 = (0x01 << 0) | (1 << 6); // Capture type, IM=0
        T1CTL = 1;
        while( 0 == (T1STAT & 1) );
        firstCap = T1CC0L; // Get the first one directly.
        
        //Wait for X edges
        while( ( DMAIRQ & (1<<2) ) == 0);
    
        // Turn off timer
        T1CTL = 0;
        T1CNTL = 0;
        T1CCTL0 = 0; // Reset Ch0 mode so we don't get spurious '0' DMA-copies on next iteration.
        T1STAT = ~(1 << 0);
        
        // Calculate frequency
        lastCap = timerCapture;
        ticksAvg = (lastCap-firstCap)/NUM_CAPTURES;
        frequency = 32 / ticksAvg;
        
        asm("nop");
        return frequency;
    }
    

  • Hi Eirik!

    I implemented the solution that you've proposed, with the difference that I don't calculate the frequency, cause I just need the number of pulses that happens in a time-window of 1 seg.

    Using your code, I implemented the following functions, considering that my input frequencies may vary quickly, and counting with "pulseCount" the number of pulses that happens in one second.

    So, Using interruptions for DMAVector I'm having the same gap for counting as previously.

    For my understanding (and hope you can confirm or reject my point of view), The timer is being used to capture the input at a given time, an then connected to the DMA stores that time for X limit of pulses detected, now when the limit of pulses has been reached the interruption DMA is activated. So, if I have a limit of captures of 10 and the interruption happends 100 times per second, I'm watching an average of 100*10 Hz freq.
    I though that this will be a lot more accurate, since I'm dealing with less interruption per second, but I'm still having this gap of:

    * for freq of 50KHz with pulsewidth of 100ns: Im having a count of 49,920 (that is 4,992 interruptions for 10 pulses per interruption) (per second).

    * but for freq of 60KHz with pulsewidth of 100ns: I'm having a count of 30,000 (that is 3,000 interruptions for 10 pulses per interruption) (per second).

    * and even for 100KHz with pulsewidth of 100ns: I'm having a count fo 50,000 (that is 5,000 interruptions for 10 pulses per interruption) (per second).

    Now the biggest concern is that I can't understand  why I'm still having the same problem with different approach.

    Hope you can tell me what I'm doing wrong.

    here is the code:

    static uint8  pulseCounterApp_TaskID;
    static uint8  blecapp_TaskID;
    
    #define T1CAP_DMA_CH 2
    #define NUM_CAPTURES 10
    
    volatile uint16 timerCapture;
    
    void armDMA() {
      halDMADesc_t *ch;
      
      ch = HAL_DMA_GET_DESC1234(T1CAP_DMA_CH);
      HAL_DMA_ABORT_CH(T1CAP_DMA_CH); 
      HAL_DMA_SET_DEST(ch, &timerCapture); 
      HAL_DMA_SET_VLEN(ch, HAL_DMA_VLEN_USE_LEN);  
      HAL_DMA_SET_WORD_SIZE(ch, HAL_DMA_WORDSIZE_WORD); 
      
      HAL_DMA_SET_TRIG_MODE(ch, HAL_DMA_TMODE_SINGLE);
      HAL_DMA_SET_TRIG_SRC(ch, HAL_DMA_TRIG_T1_CH0);
    
      HAL_DMA_SET_SRC_INC(ch, HAL_DMA_SRCINC_0);
      HAL_DMA_SET_DST_INC(ch, HAL_DMA_DSTINC_0); 
      HAL_DMA_SET_IRQ(ch, HAL_DMA_IRQMASK_ENABLE); 
      HAL_DMA_SET_M8(ch, HAL_DMA_M8_USE_8_BITS);
      HAL_DMA_SET_PRIORITY(ch, HAL_DMA_PRI_HIGH); 
      
      HAL_DMA_SET_LEN(ch, NUM_CAPTURES);
      HAL_DMA_SET_SOURCE(ch, 0x62A6);
      HAL_DMA_ARM_CH(T1CAP_DMA_CH);
      HAL_DMA_CLEAR_IRQ(T1CAP_DMA_CH);
      
    }
    
    void initialize() {
      P0DIR  &= ~(1<<2); 
      P0SEL  |= (1<<2);  
      PERCFG &= ~(1<<6); 
      P2DIR |= (0x2<<6);
      
      T1CTL = 0; 
      setupDMA();
      DMAIE = 1; // activate interruption for DMA
      
      T1CCTL0 = 0x01; 
      T1CTL   = 0x01;
    }
    
    HAL_ISR_FUNCTION( DMAIsr, DMA_VECTOR )
    {
      HAL_ENTER_ISR();
      
      pulseCount++; // increase the count that I need.
      DMAIF = 0; // clean flags.
      armDMA(); // arm DMA again.
    
      CLEAR_SLEEP_MODE();
    
      HAL_EXIT_ISR();
    
      return;
    }

  • Hello,
    The general idea behind the sample code was to only service the interrupt once when you want to get the results. For an input of 100 kHz you will have to service the interrupt routine 10.000 times and if any "thread" runs, at higher or equal priority, for more than 100 us you will lose an interrupt. If you want to test your method further you should try a much higher number for NUM_CAPTURES. You should also measure the duration of you DMA ISR function to figure out what your lowest value you can set NUM_CAPTURES to:
    (1/freq_in) * NUM_CAPTURES >> ISR_duration