This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Cortex M4 (LPC4370): how do I detect ADC threshold crossing while moving data in a DMA driven double buffer?

Hi to you all,

I'm currently working on a project involving the LPC-Link2 as a eval. board for it's LPC4370 (for a complete explanation see this question).
What I'm trying to do is:

  • Continuously sample external analog signal (using on-board ADCHS)
  • Transfer data to RAM (I'm using a ping-pong buffer: let's call the sub-buffers s[0] and s[1])
  • When a threshold crossing is detected by the ADCHS -> begin data processing

At the moment I managed to be fine with the first 2 bullets, my question is: what is the fastest (and most elegant) way to process data without loosing (too much) samples and whitout interferring with DMA activity?

I tried the following procedure:

  • Use ADCHS interrupt (highest priority) to capture the address at which the DMA is currently pointing (eg. in sub-buffer s[0])
  • Process the data in the DMA ISR (eg. when s[1] is being filled)

BUT, it seems that I'm haviong troubles because of the cortex M4 ISR overhead. A drawing will better explain this:

The * in the image means:

  • ADCHS sampled the threshold-crossing responsible semple in s[1], thus flagging to process s[1]
  • By the time the DMA ISR gets the ADCHS flag s[1] is being filled and the DMA ISR won't process it.

How do I solve this? If anything isn't that clear I will be happy to explain it better.

Any help would be highly appreciated!

Regards,

Andrea

Parents
  • It could be the other DMA writing address, but I would recommend to use a constant buffer of indexes in ROM. If you can trigger the accessory channel based on a sub-frequency of main channel, it will work all the same.

    Two options in this case :
    - you can trigger one channel from another one based on completion
    - you can feed same trigger input on both DMA channels and perform some decimation (do you have something like minor/major loops ?)


    Anyway, maybe, I am driving you crazy because I don't know about this particular DMA ...
    If you cannot do any of previous two options, maybe you should keep the way you were doing it, just keeping the circular buffer thing and expanding it to prevent cases where your data gets rewritten by DMA.
Reply
  • It could be the other DMA writing address, but I would recommend to use a constant buffer of indexes in ROM. If you can trigger the accessory channel based on a sub-frequency of main channel, it will work all the same.

    Two options in this case :
    - you can trigger one channel from another one based on completion
    - you can feed same trigger input on both DMA channels and perform some decimation (do you have something like minor/major loops ?)


    Anyway, maybe, I am driving you crazy because I don't know about this particular DMA ...
    If you cannot do any of previous two options, maybe you should keep the way you were doing it, just keeping the circular buffer thing and expanding it to prevent cases where your data gets rewritten by DMA.
Children
  • I think that your suggestions are good Thibaut, especially about the fact that hardware flags can do the trick.
    Right now I looked at the timer manual and I can trigger DMA transfers from timers: that should not be difficult, but still, I don't know how to catch the position in the buffer.
    Just to clarify.
    This DMA works with Linked List: objects that specify the characteristics of each transfer. I have now 2 linked lists pointing to each other and continuously being called one after the other. That's how I fill my sub-buffers. I searched for minor/major loops in the docs, but no luck.
    Option 1 should be feasible, even if I don't know at the moment how to configure the DMA for this.

    What is not clear to me is: how do I relate (without using software) a constant array in RAM/ROM with the position being accessed by the DMA in another array in RAM/ROM?

  • Perfect ! Timers could help.
     
    If timers trigger DMA channels, then synchronization is provided by timers themselves. You just need to take care about initialization.
    Especially, you will easily be able to tune the accessory channel frequency as a divider of main channel.
     
    DMA transfer should be configurable to only use one big buffer : for a first attempt, your 2nd descriptor can be exactly the same as fisrt one.
     
    Let's picture it like that (let's imagine your big buffer is 1024 bytes deep, your indexes are in [0;255] range, therefore you have a ratio of 4 between your timers):
                             ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^ 
    Timer 1 @ f1             |   |   |   |   |   |   |   |   |   |   | 
                             
                             
                             ^               ^               ^         
    Timer 2 @ f2/4           |               |               |         
                             
    last sample position     0   1   2   3   4   5   6   7   8   9  ...
    
    last transfered index    255             0               1      ...
    
    
                                                       ^
    ADCHS comparison Interrupt                         |
                                                        index = 0
                                                        sample_position = index * 4
     
    Only ADCHS comparison triggers an interrupt, software reads from index variable and computes sample buffer index from it. Then you can start your analysis based on this index.
     
    I hope this will help !
  • Hi Thibaut, sorry for me being this late, but I'm attending lessons every day at university right now and I'm a bit busy. Nevertheless I must keep this project alive.
    Absolutely useful picture on what the firmware should do, thanks Thibaut. Hopefully I'll be able to test it with "real" signals soon enough.
    Therefore I'm trying to implement the timer triggered memory-to-memory DMA transfer. Unfortunately that's not easy to me. At the moment I have one main problem.

    Connect the DMA channel to the timer, to use it (the timer) as a trigger: I looked at an LPCOpenV2.20 example, but I think is not working. Also, after writing the function to setup the timer and trigger the DMA, they kindly commented it. Nice one.
    This is the setup:

    /* Setup DMA M2M transfer to trigger on timer match */
    static void setupDMATrigger(void)
    {
    	/* Initialize GPDMA controller */
    	Chip_GPDMA_Init(LPC_GPDMA);
    
    	/* Get DMA channel */
    	dmaCh = Chip_GPDMA_GetFreeChannel(LPC_GPDMA, GPDMA_CONN_MAT0_0);
    
    	/* Setup DMA transfer */
    	Chip_GPDMA_Transfer(LPC_GPDMA, dmaCh,
    					  (uint32_t) &source[0], GPDMA_CONN_MAT0_0, //(uint32_t) &dest[0],
    					  GPDMA_TRANSFERTYPE_M2M_CONTROLLER_DMA,
    					  sizeof(source));
    
    //			Chip_GPDMA_Stop(LPC_GPDMA, dmaChSSPTx);
    
    	/* Enable GPDMA interrupt */
    	NVIC_EnableIRQ(DMA_IRQn);
    }

    But then:
    int main(void)
    {
    	int numDmaXfers = 0;
    
    	SystemCoreClockUpdate();
    	Board_Init();
    
    	/* Setup tiemr capture input used for DMA trigger */
    	setupTimerCapInput();
    
    	/* Setup timer for match event on each capture event */
    	setupTimerTrigger();
    
    	/* Setup DMA for memory to memory transfer on timer match event */
    //	setupDMATrigger();
    	/* Enable timer interrupt */
    	NVIC_EnableIRQ(TIMER0_IRQn);
    	NVIC_ClearPendingIRQ(TIMER0_IRQn);
    
    	/* Idle in background waiting for DMA events */
    	while (1) { ... etc...

    Am I badly wrong?

    Thank you again for your help Thibaut.

    Regards,
    Andrea

  • Reading the other post, especially the part where you mention that trigger and transfer sources are linked made me wonder if I was not misleading you.
    I am familiar with NXP (ex-Freescale) Kinetis eDMA which is very flexible and allowed me to do very complex hardware treatments ...

    I think that here, the most efficient and "lean" way of tackling things would be to go back to your original technique except for the single buffer part.

    If you feed your big buffer with samples coming from ADCHS at its natural pace, then "capture" DMA write address on ADCHS comparison interrupt, it could work (provided that reading address from running DMA is safe).

    The most important thing here is that you need to bufferize enough samples to allow your treatment to work without corruption.
  • Hi Thibaut, I saw in the past few months that this microcontroller is really rarely used and (I think) a bit underrated. I feel somewhat lost in trying to understand how it works. Anyway, maybe I'll just switch back to my previous solution as you said, but I'm also considering to purchase from NXP a Pro licence to have 1 year of email support.

    I'd really like to thank you for the help and I won't forget to update this post if I'll get this thing work.

    All the best,
    Andrea