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

  • Hi Andrea,

    It is not clear to me whether you need to continuously process your data or only when your threshold is crossed.
    In the latter case, you may be able to use ADCHS comparison feature to trigger DMA transfer ...

    Please, give some more information regarding your needs:
    - continuous processing ?
    - if not, fixed number of samples to process ?
    ...

    Basically, you should never try to interact with DMA transfers in SW (except in order to start/stop transfers).
    I usually try to implement a state machine with DMA and other peripherals so that everything happens in hardware and at the end, software is informed about some work to do by a DMA or peripheral interrupt. Software processing is then less time constrained and you can deal with ISR delay by adjusting buffering size.

    Regards,
    Thibaut
  • Hi 
    thanks for your replies.
    I must process a signal which is basically a pulse signal. That is: gaussian shape and almost constant lenght:
    This signal is not periodic, but my firmware should be able to process a 30 kcount/s flow.
    Strictly speaking, no: I don't need to perform a continuous processing, I just need the processing to be done before the next peak is completely acquired. And yes, fixed number of samples to be processed. The pulse length is roughly 10 us, so let's say @30 kcount/s that gives a 30% duty cycle. Since I'm acquiring @40 msps I need to process 400 samples in 20us.

    At the momenti I managed to set up the DMA transfer in a ping-pong buffer which is always running. The DMA isr (raised at the end of a sub-buffer transfer) then contains some if statements that check for the ADCHS flag and eventually process the acquired data.

    I understand what you suggest, but am I wrong thinking that triggering the DMA transfer would be too slow? Is this faster that switching between interrupts?
    I know this question may seem rhetorical,  and I'm sorry for this.

    Thanks again!

  • Sorry if I was not clear enough ...
    I'm not familiar with your microcontroller but it may be possible to trigger/start a DMA transfer through the ADCHS comparison interrupt flag without any software help.
    In fact, I imagine a DMA transfer being configured to transfer from ADCHS value register to RAM for a defined number of samples. This transfer would be disabled until your threshold crossing (detected by ADCHS itself) that would enable the DMA transfer.
    If it is possible with this DMA and ADC, then you only need to handle one interrupt in software, at the end of DMA transfer.

    If it is not possible, I would try to perform the "flagging" operation using everything but software !! Another Analog comparator that would be able to trigger DMA ? Anything else !

    Please have a look at your documentation and let me know if my first proposal seem to be feasible.
  • Hi Thibaut, I looked at the doc (here it is for your reference) and this is what I found.

    In chapter 48, section 48.7.4.1 DMA read:


    The FIFO can be read by DMA channel 8. A DMA transfer request is generated when the FIFO fill level is equal to or more than set by FIFO_LEVEL. This is the same behavior that will raise the interrupt flag FIFO_FULL. The burst size can be set to one in the DMA channel control register (see Table 350). If the FIFO fill level is not equal to one of the other DMA-supported burst sizes (applicable DMA burst sizes are 1, 4, 8 or 16), set the burst size to 1. Reading an empty FIFO will result in a value 0x00008000 or 0x80008000.

    And that's it. The problem is that looking back to scetion 4.6.9 threshold A/B register a detection can occur only after conversion, in fact:

    If, for two successive conversion results on a given channel, one result is below a
    threshold and the other is equal-to or above this threshold, then a threshold crossing has
    occurred. In this case the THCMP_CROSS (see Section 48.6.10) status bits will indicate
    that a threshold crossing has occurred. A threshold crossing event will also generate an
    interrupt 1 request if enabled to do so via the SET_EN1 bits associated with each channel
    in the SET_EN1 register.

    So I suspect the ADC must be up and running all the time.
    Plus, there is the option to external-trigger the ADC conversion, but that can be done only through standard GPIO which I believe are digital comparators. The amplitude of my input signal changes over time (that's where the physical information I'm interested in is hidden): I think I would need to design a specific analog amplifier in order to work this way.

    I'd love to know if there's a way to fill the fifo only after a threshold cross, this should work if combined with the DMA read request at a certain fifo level.

    Many thanks for your help Thibaut.

    Regards,
    Andrea

  • OK, I had a quick look at your document.
    Looks like link between ADCHS and DMA is only about sample transfer (read) or ADCHS conversion descriptor update (write).

    It seems not possible to do what I suggested.
    I don't know if you tried to reach support on NXP community but it seems to me like using such high bandwidth ADC implies good usage of DMA features. Maybe someone can share its own experience there ...


    Going back to your problem, I think you should try to maintain the buffer index directly with DMA.
    I imagine something like:
    - main DMA transferring continuously from ADCHS to RAM buffer (no need to have double buffering, a single circular buffer with a good size would do the trick)
    - accessory DMA channel transferring at a lower pace a simple index indicating at which position in the circular buffer the other channel is writing.

    That way, when the ADCHS comparison triggers an interrupt, your software gets called and "only" needs to read from dedicated index memory location to know which circular buffer portion was lately updated.
    Then you can start reading from circular buffer, starting from given location and manage your algorithms from there.


    I imagine a different pace between two channels because circular buffer would need to be quite huge sample count (compute that size by profiling your treatment algorithm and adding a good margin) and I imagine you can afford to identify starting sample with approximation.
  • Hi Thibaut, thanks for your interesting answer.
    I started looking at the docs to figure out how to set up a "periodic" DMA transfer, from DMA to memory.
    If I understood correctly the idea is to capture the current DMA-main-buffer-address with a certain frequency and then transfer it to a location into the RAM.
    Now, I have one question: in the list of possible DMA sources, the DMA actually does not appear.
    Here it is:

    /**
     * @brief GPDMA request connections
     */
    #define GPDMA_CONN_MEMORY           ((0UL))			/**< MEMORY             */
    #define GPDMA_CONN_MAT0_0           ((1UL))			/**< MAT0.0             */
    #define GPDMA_CONN_UART0_Tx         ((2UL))			/**< UART0 Tx           */
    #define GPDMA_CONN_MAT0_1           ((3UL))			/**< MAT0.1             */
    #define GPDMA_CONN_UART0_Rx         ((4UL))			/**< UART0 Rx           */
    #define GPDMA_CONN_MAT1_0           ((5UL))			/**< MAT1.0             */
    #define GPDMA_CONN_UART1_Tx         ((6UL))			/**< UART1 Tx           */
    #define GPDMA_CONN_MAT1_1           ((7UL))			/**< MAT1.1             */
    #define GPDMA_CONN_UART1_Rx         ((8UL))			/**< UART1 Rx           */
    #define GPDMA_CONN_MAT2_0           ((9UL))			/**< MAT2.0             */
    #define GPDMA_CONN_UART2_Tx         ((10UL))		/**< UART2 Tx           */
    #define GPDMA_CONN_MAT2_1           ((11UL))		/**< MAT2.1             */
    #define GPDMA_CONN_UART2_Rx         ((12UL))		/**< UART2 Rx           */
    #define GPDMA_CONN_MAT3_0           ((13UL))		/**< MAT3.0             */
    #define GPDMA_CONN_UART3_Tx         ((14UL))		/**< UART3 Tx           */
    #define GPDMA_CONN_SCT_0            ((15UL))		/**< SCT timer channel 0*/
    #define GPDMA_CONN_MAT3_1           ((16UL))		/**< MAT3.1             */
    #define GPDMA_CONN_UART3_Rx         ((17UL))		/**< UART3 Rx           */
    #define GPDMA_CONN_SCT_1            ((18UL))		/**< SCT timer channel 1*/
    #define GPDMA_CONN_SSP0_Rx          ((19UL))		/**< SSP0 Rx            */
    #define GPDMA_CONN_I2S_Tx_Channel_0 ((20UL))		/**< I2S0 Tx on channel 0 */
    #define GPDMA_CONN_SSP0_Tx          ((21UL))		/**< SSP0 Tx            */
    #define GPDMA_CONN_I2S_Rx_Channel_1 ((22UL))		/**< I2S0 Rx on channel 0 */
    #define GPDMA_CONN_SSP1_Rx          ((23UL))		/**< SSP1 Rx            */
    #define GPDMA_CONN_SSP1_Tx          ((24UL))		/**< SSP1 Tx            */
    #define GPDMA_CONN_ADC_0            ((25UL))		/**< ADC 0              */
    #define GPDMA_CONN_ADC_1            ((26UL))		/**< ADC 1              */
    #define GPDMA_CONN_DAC              ((27UL))		/**< DAC                */
    #define GPDMA_CONN_I2S1_Tx_Channel_0 ((28UL))		/**< I2S1 Tx on channel 0 */
    #define GPDMA_CONN_I2S1_Rx_Channel_1 ((29UL))		/**< I2S1 Rx on channel 0 */
    // mch: added for HSADC
    #define GPDMA_CONN_HSADC_READ 		((30UL))		/**< HSADC Samples 		*/
    #define GPDMA_CONN_HSADC_WRITE 		((31UL))		/**< HSADC Descriptors 0 */

    How am I supposed to set up a transfer from DMA to memory? Maybe I just misunderstood your suggestions and in that case I apologize for this.
    The problem is that I can imagine the

    a simple index indicating at which position in the circular buffer the other channel is writing

    just as the DMA writing address. Am I right?

  • 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.
  • 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