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

Reply
  • 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?

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