24-bit / 32-bit UDB Timer DMA Transfer

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
shepdog87
Level 3
Level 3
25 sign-ins 10 questions asked 10 replies posted

Hello,

I am using a time-stamp method of measuring a frequency of a signal. The signal is a square wave, and causes a DMA transfer to start when a rising edge is detected. There is a circular RAM buffer that holds the timestamps / counts of the timer each time the input signal has a rising edge.  A function is called periodically to subtract the counts from each other to estimate the frequency.

I used the example project from the CONSULTRON which is posted here:

https://community.cypress.com/t5/Code-Examples/Multi-Input-Frequency-Measurement-Tutorial/m-p/183038

Everything works as expected when I use the "Fixed Function" timer block with 16-bits. However, when I try changing it to a 24 or 32-bit UDB timer block, the counts do not make sense. The least-significant-byte of the RAM buffer always has the same value for some reason. When I look at the debug window for the component, the count matches what is expected in the MEMORY viewer, but it seems like the value at that address is not being transferred via DMA? 

I referenced AN61102 which shows how to transfer 32-bit values from 16-bit spokes. I added another DMA channel as an intermediate step, and from the intermediate step goes to the larger buffer.

I was using the following #define in the DMA configuration to retrieve the counts, which is 0x4000_6508: Timer_Count_COUNTER_LSB_PTR_8BIT

0 Likes
1 Solution

shepdog,

I believe I have a solution that's workable.

Below you will see a recommended Topdesign circuit.   It takes the one input from 1Hz to 5KHz and splits it simultaneously with and without Frequency prescaling.

This technique of prescaling was used in Project #8 of my tutorial.

The input frequencies of 150Hz to 5KHz are used prescaled to the lower frequencies to significantly improve the resolution you are looking for.  (At 5KHz: resolution = 0.745Hz).

The input frequencies of 1Hz to 150Hz are computed without the prescaler.  The resolution at 150Hz is 0.687Hz.

Len_CONSULTRON_0-1614270434334.png

Note: The Clk_Cnt frequency driving the Timer is 32768!   This is great.  Why?  If you use a watch crystal on the WCO inputs of the PSoC5, you get <100 PPM accuracy for your time base.   Remember:   Garbage in => Garbage out.   sub-100 PPM is not Garbage!

I hope this helps.

Len
"Engineering is an Art. The Art of Compromise."

View solution in original post

7 Replies
Len_CONSULTRON
Level 9
Level 9
Beta tester 500 solutions authored 1000 replies posted

shepdog,

Are you willing to share the project with the forum to reproduce your results?

Len
"Engineering is an Art. The Art of Compromise."
0 Likes

Hi Len,

Thanks for responding to the post, and thanks for the detailed frequency measurement tutorial you created. That is one of the best documents I have seen in this field.

In terms of my problem, I was able to resolve it when I created a separate workspace with separate projects for the different bit-resolutions of the timer component.  It seems like the issue was with how I configured the DMA for the 24-bit conversion.  I used a transfer of 3 bytes rather than 4. Once I made it a 4-byte transfer everything started working.

Here is the DMA configuration for the 24-bit counter:

#define BUFFER_SIZE (6u)
volatile uint32_t freq_intermediate __attribute__ ((aligned (4))) = 0;
volatile uint32_t freq_buffer[BUFFER_SIZE] = {0};
volatile uint32_t current_count = 0;
volatile uint32_t current_freq = 0;
volatile uint32_t current_max_match_cnt = 0;



void Initialize_Counter_DMA()
{
/* We need to setup an intermediate DMA transfer due to a 16-bit wide spoke, and 32-bits of data
to move. See AN61102 */
/* Move these variable declarations to the top of the function */
uint8_t DMA_ch[2];
uint8_t DMA_td[2];

/* DMA Configuration for DMA_1 */
#define DMA_0_BYTES_PER_BURST (4u) // 32-bits per burst (from a 24-bit component)
#define DMA_0_REQUEST_PER_BURST (1u)
#define DMA_0_SRC_BASE (CYDEV_PERIPH_BASE)
#define DMA_0_DST_BASE (CYDEV_SRAM_BASE)

#define DMA_1_BYTES_PER_REQUEST (4u)
#define DMA_1_REQUEST_PER_BURST (1u)
#define DMA_1_SRC_BASE (CYDEV_SRAM_BASE)
#define DMA_1_DST_BASE (CYDEV_SRAM_BASE)

#define STORE_TD_CFG_ONCMPLT (1u)
#define TRANSFER_COUNT (BUFFER_SIZE * 4)

DMA_ch[0] = DMA_CntToMem_DmaInitialize(
DMA_0_BYTES_PER_BURST,
DMA_0_REQUEST_PER_BURST,
HI16(DMA_0_SRC_BASE),
HI16(DMA_0_DST_BASE));

DMA_td[0] = CyDmaTdAllocate();

CyDmaTdSetConfiguration(
DMA_td[0],
4, // Transfer 4 bytes at a time
DMA_td[0], // Go back to this TD after completion
DMA_CntToMem__TD_TERMOUT_EN | TD_INC_SRC_ADR );

/* From the COUNTER to SRAM */
CyDmaTdSetAddress(DMA_td[0], LO16((uint32) Timer_Count_COUNTER_LSB_PTR), LO16((uint32)&freq_intermediate));


/* Associate the TD with the channel */
CyDmaChSetInitialTd(DMA_ch[0], DMA_td[0]);

DMA_ch[1] = DMA_MemToMem_DmaInitialize(
DMA_1_BYTES_PER_REQUEST,
DMA_1_REQUEST_PER_BURST,
HI16(DMA_1_SRC_BASE),
HI16(DMA_1_DST_BASE));

DMA_td[1] = CyDmaTdAllocate();

CyDmaTdSetConfiguration(
DMA_td[1],
TRANSFER_COUNT,
DMA_td[1],
TD_INC_DST_ADR);

CyDmaTdSetAddress(
DMA_td[1],
LO16((uint32)&freq_intermediate),
LO16((uint32)&freq_buffer[0]));

CyDmaChSetInitialTd(DMA_ch[1], DMA_td[1]);

CyDmaChEnable(DMA_ch[0], STORE_TD_CFG_ONCMPLT);
CyDmaChEnable(DMA_ch[1], STORE_TD_CFG_ONCMPLT);
}


 

shepdog,

Do you have any further questions?

I've done a bit more research on this issue.

The PSoC UDB subsystem is very elegant but consequentially very complicated.

Here is the progress of my study:

Each UDB block can be accessed as 8-bit or 16-bit.    To create a virtual component of 24- to 32-bits requires UDB block chaining.

The counter value which increments or, as in the case of the Timer component, decrements is a part of the ALU (Arithmetic Logic Unit) part of the UDB.  The counter value is directly readable but as a 16-bit value.   This is why a 8-bit or 16-bit Timer implementation works in my Frequency Measurement Tutorial examples.

Chaining 2 UDB blocks allows for extending the Timer to 24-bit or 32-bit.  You can try reading the larger Timer counter value directly from the two 16-bit ALU registers.   This can be done with the CPU.  However, there is a possibility of coherency loss.  If you're not familiar with 'coherency', this is where the two 16-bit values being read is not atomic (read at the same time).  For example, let's say you read the MSW of the Timer first, it is 0x0000.  However before you are able to read the LSW of the Timer, it was 0x0000 but the Timer decremented the count value.   The new 32-bit Timer count value is 0xFFFFFFFF.   Therefore the read of the LSW will be 0xFFFF.  This will provide a 'false' value.

If you preform these two counter value reads with the DMA, this coherence problem is significantly minimized but still possible.

The use of the chained FIFOs in the Timer UDB component is that there is a HW feature to capture the 32-bit count value into the FIFO atomically.   As long as you can get to the FIFO captured value before the FIFO is recycled, the 32-bit value is coherent.

Question:   Is there a reason you need 24- or 32-bit accuracy?  Please reconsider.  In my tutorial I discuss the limits of a 16-bit implementation.  For most cases, 16-bit is very useful.   If more accuracy is needed, there are other methods such as frequency pre-scaling.

Len
"Engineering is an Art. The Art of Compromise."

Hi Len,

Thanks for the very detailed response and the investigation on your part. The reason I needed to go to 24-bit is because I need to poll the frequency twice a second and be able to measure within 1 Hz of the input from a range of 5 Hz to 5000 Hz.

At a clock frequency of 32 MHz driving the counter:

16-bits: Minimum measured frequency is 488 Hz, resolution is 0.78125 at 5 khz.

24-bits: Minimum measured frequency is 1.91 Hz, same resolution at 5 khz as above (<1 Hz).

If I reduce the clock frequency below 32 MHz, then the resolution at 5 khz goes above 1 hz / bit.

Do you have any suggestions on how to meet the requirements using a 16-bit counter with what I described above?

0 Likes

shepdog,

I believe I have a solution that's workable.

Below you will see a recommended Topdesign circuit.   It takes the one input from 1Hz to 5KHz and splits it simultaneously with and without Frequency prescaling.

This technique of prescaling was used in Project #8 of my tutorial.

The input frequencies of 150Hz to 5KHz are used prescaled to the lower frequencies to significantly improve the resolution you are looking for.  (At 5KHz: resolution = 0.745Hz).

The input frequencies of 1Hz to 150Hz are computed without the prescaler.  The resolution at 150Hz is 0.687Hz.

Len_CONSULTRON_0-1614270434334.png

Note: The Clk_Cnt frequency driving the Timer is 32768!   This is great.  Why?  If you use a watch crystal on the WCO inputs of the PSoC5, you get <100 PPM accuracy for your time base.   Remember:   Garbage in => Garbage out.   sub-100 PPM is not Garbage!

I hope this helps.

Len
"Engineering is an Art. The Art of Compromise."

Len,

Thanks for the suggestion! Great idea to split the same frequency signal into two different DMA transfers. Thanks for your help!

0 Likes
Len_CONSULTRON
Level 9
Level 9
Beta tester 500 solutions authored 1000 replies posted

shepdog,

Take a look at the _ReadCounter() function.  You will notice the comments about needing to do a "dummy" 8-bit read of a register called Timer_Count_COUNTER_LSB_PTR_8BIT.  This read forces a dump of the counter value into the FIFO.   Then the counter value in the FIFO can be read as a 32-bit value.

 

 

/*******************************************************************************
* Function Name: Timer_Count_ReadCounter
********************************************************************************
*
* Summary:
*  This function returns the current counter value.
*
* Parameters:
*  void
*
* Return:
*  Present compare value.
*
*******************************************************************************/
uint32 Timer_Count_ReadCounter(void) 
{
    /* Force capture by reading Accumulator */
    /* Must first do a software capture to be able to read the counter */
    /* It is up to the user code to make sure there isn't already captured data in the FIFO */
    #if(Timer_Count_UsingFixedFunction)
        (void)CY_GET_REG16(Timer_Count_COUNTER_LSB_PTR);
    #else
        (void)CY_GET_REG8(Timer_Count_COUNTER_LSB_PTR_8BIT);
    #endif/* (Timer_Count_UsingFixedFunction) */

    /* Read the data from the FIFO (or capture register for Fixed Function)*/
    #if(Timer_Count_UsingFixedFunction)
        return ((uint32)CY_GET_REG16(Timer_Count_CAPTURE_LSB_PTR));
    #else
        return (CY_GET_REG32(Timer_Count_CAPTURE_LSB_PTR));
    #endif /* (Timer_Count_UsingFixedFunction) */
}

 

Len
"Engineering is an Art. The Art of Compromise."