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.

Timing problem for DMA read out from sequence ADC



I am now writing code using timer1 tirgger 4 channel of ADC and read it out via DMA. First i write a standalnoe project to test it. Everything goes on well. But when i combine it with bluetooth project. The result in the DMA sequence gets disorder(meaning the result array can start from any channel). I can see in the result array(from the result shown at bluetooth central), the fisrt element can be result from every channel which is supposed to be only channel 0. I think the reason is beceause i am checking the dma flag every 1ms, and stops the timer1 if there is flag. In the interval between DMA is ready and the ready is recognized, timer1 closed(stops ADC). There are still ADC events carrying on.This is my code:

 static char resultbuf[41]={255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
 static char resultbuf1[41]={255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
 static uint8 toggle = 0;
 static halDMADesc_t *dma_channel;


void SimpleBLEPeripheral_Init( uint8 task_id )
{
  simpleBLEPeripheral_TaskID = task_id;
  
  //init ADC p0.0 p0.1 p0.4 p0.5
  APCFG |=0x33;
  P0DIR &=~ 0x33;
  ADCCON1 = 0x23;// timer1 channel 0 will trigger the ADC
  //ADCCON1 = 0x33;// timer1 channel 0 will trigger the ADC
  ADCCON2 = 0xB5;
  //ADCCON3 = 0xB1;
  
  PERCFG |= 0x40; // Select Timer 1 Alternative 1 location
  P2SEL = (P2SEL & ~0x10); // Give priority to Timer 1
  P1DIR |= 0x04;  // Set P1.2 to output
  P1SEL |= 0x04;  // Set P1.2 to peripheral
  
  T1CC0L = 0x06;   // PWM signal period
  T1CC0H = 0x01;  
  T1CCTL0 = (T1CCTL0 & ~0x63) | 0x14; // PWM output  0x23
  T1CTL |= 0x0E; // divide with 128 and to do Modulo Mode  ************************TIMER1 WORKING PWM ON P1.2
  
 
  dma_channel = HAL_DMA_GET_CH3_ADDR();
//  //dma_channel = HAL_DMA_GET_DESC0();
//  
////  
////    // rearm is needed when 40 byte is read out.
////  // also clear interrupt
////  // Setup DMA configuration.
////  //source address
  dma_channel->srcAddrH  = (uint8)((uint16)(&X_ADCH) >> 8); //X_ADCH
  dma_channel->srcAddrL  = (uint8)((uint16)(&X_ADCH));// & 0xFF); //X_ADCL
  //destination adress
  dma_channel->dstAddrH = (uint8)(((uint16)&resultbuf[1]) >> 8);
  dma_channel->dstAddrL = (uint8)(((uint16)&resultbuf[1]) & 0xFF);
//  // 000 use len for transfer countr
//  // 00000
  dma_channel->xferLenV      = 0x00;
//  //lenl is the length of transfer byte
  dma_channel->xferLenL      = 40;                   // Tranfer Count 4 byte
//  // wordsize 8bit 0   00 repeated single, every trigger read out 1, and awaits the next trigger. until 24 times.
//  // 20 10100 means ADC_ALL_READY
  dma_channel->ctrlA = 0x14;
//  //source no increment , dest increment, disable transfer(single) interrupt , high proi
  dma_channel->ctrlB = 0x16;
//  //DMAARM |=0x08;
  DMAARM |=0x08;

....
....
....
}

every 1ms: 
static void performPeriodicTask( void )
{
  // 10ms ADC data
  if(DMAIRQ&0x09)
  {
    DMAIRQ &=~ 0x08; 
    T1CTL &=  ~0x02;    // Stop timer
    if(toggle == 0)
    {
        dma_channel->dstAddrH = (uint8)(((uint16)&resultbuf1[1]) >> 8);
        dma_channel->dstAddrL = (uint8)(((uint16)&resultbuf1[1]) & 0xFF);
        DMAARM |=0x08;      // rearm dma
        T1CNTL =  0x00;     // Reset counter value
        T1CTL |=  0x02;     // Start timer
        emgProfile_SetParameter(  EMGPROFILE_DATA,EMGPROFILE_DATA_LEN, resultbuf );
        emgProfile_SetParameter(  EMGPROFILE_DATA,EMGPROFILE_DATA_LEN, &resultbuf[20] );
        toggle = 1;
    }
    else
    {
        dma_channel->dstAddrH = (uint8)(((uint16)&resultbuf[1]) >> 8);
        dma_channel->dstAddrL = (uint8)(((uint16)&resultbuf[1]) & 0xFF);
        DMAARM |=0x08;      // rearm dma
        T1CNTL =  0x00;     // Reset counter value
        T1CTL |=  0x02;     // Start timer
        emgProfile_SetParameter(  EMGPROFILE_DATA,EMGPROFILE_DATA_LEN, resultbuf1 );
        emgProfile_SetParameter(  EMGPROFILE_DATA,EMGPROFILE_DATA_LEN, &resultbuf1[20] );
        toggle = 0;
    }
  }


  
}

I think up of 2 ways, 1 is calculate the interrupt of dma, if it's 40, then stops the timer1,but it takes CUP time and ADC is still working when interrupt is there. 2 one is restart ADC, but i don't know how to do that. Is there any solutions?

  • Can't you just configure the DMA channel to be in a different mode instead?

    It seems single mode instead of repeated single is a better choice for your application. This will prevent new data from overwriting your old data.

    Single: On a trigger, a single DMA transfer occurs, and the DMA channel awaits the next trigger. After
    the number of transfers specified by the transfer count is completed, the CPU is notified, and the DMA
    channel is disarmed.

    Block: On a trigger, the number of DMA transfers specified by the transfer count is performed as
    quickly as possible, after which the CPU is notified and the DMA channel is disarmed.

    Repeated single: On a trigger, a single DMA transfer occurs, and the DMA channel awaits the next
    trigger. After the number of transfers specified by the transfer count is completed, the CPU is notified,
    and the DMA channel is rearmed.

    Repeated block: On a trigger, the number of DMA transfers specified by the transfer count is
    performed as quickly as possible, after which the CPU is notified and the DMA channel is rearmed.

    Once you get a DMA done interrupt, stop and reset the timer and wait for the current ADC conversion to finish.

    Regards,
    Svend

  • Hi Svend,

    I am using single, the comment is wrong. I add some   

    if(DMAIRQ&0x09)
      {
        T1CTL &=  ~0x02;  
      }

    in the routine of osal  tasks, but still not perfect.

    Regards,

    Bo

  • Hi,

    There seems to be a few strange things with your setup. In general, since you should probably change to word mode (16 bit) since that is what you want to copy every transfer.

    The results array you have should be of uint16/int16 type.

    ctrlA = 0x14 = 0b00010100

    WORDSIZE = 0b0 (8 bit DMA transfers, should be 1=16 bit)?
    TMODE = 0b00 (single)
    TRIG = 0b10100=0x14 (End of a conversion in a sequence)

    ctrlB = 0x16 translates to 0b00010110

    SRCINC = 0b00 (no inc)
    DESTINC = 0b01 (increment by 1 word)
    IRQMASK = 0 (disable interrupt?)
    M8 = 1 (8 bit bytes)
    PRIORITY = 0b10 (high priority, reconsider using 0b01 instead which does arbitration)

    What is the macro HAL_DMA_GET_CH3_ADDR() doing? And also, why are you checking DMA interrupts on more than one channel (0x09)

     

    Regards,
    Svend

  • Hi Svend,

    i use 8 bit as  i only want to copy the 8 most significant bits into my char array result. I am reading from X_ADCH.  HAL_DMA_GET_CH3_ADDR() is the same as HAL_DMA_GET_DESC1234(3). I set DMIRQ because the first bit is always 1, i don't know why. So 0x09 is checked.

    In the loop if(DMAIRQ = 0x09),

    ADCCON1 = 0x23; 

    ADCCON2=0xB0;

    ADCCON1 = 0x23; 

    ADCCON2=0xB5;

    is added, and the result looks ok now.

    However, By reconfiguring dma and adc, packet loss is detected