1
\$\begingroup\$

I want to sample a 4kHz signal that is produced by a signal generator.

I read 2000 samples in a row and then I send it to computer via virtual serial that is provided by USB of STM32F103C8T6. I set timer 3 in order to trigger the ADC and then I set up a 2000 length buffer for DMA.

When I plot signal on the computer the data is not continuous and there is a problem between the signal.

I also stop ADC at the beginning of the PeriodElapsedCallback and start it again at the end of it.

Here is the image of my signal:

enter image description here

The ADC clock is 12MHz. The cycles is set to 28.5 and I use a 12bit ADC. My timer clock is 48MHZ and it counts 30 clocks to trigger the ADC. DMA is set to circular mode. I do not expect these disruptions in my signal because I am taking 2000 samples without any stop.

EDIT: This is my code. As I mentioned before I stop the DMA in the beginning of the interrupt and start it again at the end.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  HAL_ADC_Stop_DMA(&hadc1);
  HAL_TIM_Base_Stop(&htim3);

  usb_put_arr_int(arr,ARRAY_LENGTH);
  HAL_GPIO_TogglePin(GPIOC, 1<<13);

  HAL_TIM_Base_Start(&htim3);
  HAL_ADC_Start_DMA(&hadc1, arr , ARRAY_LENGTH);

}


I also change the timer period from 30 to 400 and I got a better result but the problem still occurs. I am suspicious about DMA speed.

enter image description here

Based on my calculation, the timer triggers the interrupt every: 400/48000000 = 8.33us and the speed of my ADC is about (28.5+12.5+2.5)/12000000 = 3.62us

So the ADC speed is faster than the timer. Thus, I expect the timer cannot trigger the ADC before the last sampling had completed.


I changed the CDC function according to what @JiříMaier said (I did not change my interrupt code.)

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
  uint8_t result = USBD_OK;
  /* USER CODE BEGIN 7 */
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
  if (hcdc->TxState != 0){
    return USBD_BUSY;
  }
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
  result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);

  for (uint32_t usbTimeout = 100000; usbTimeout > 0; usbTimeout--) {
    if (hcdc->TxState == 0)
      break;
  }



  /* USER CODE END 7 */
  return result;
}

enter image description here


I also changed my code structure and changed my interrupt code according to @JiříMaier advice and I got the following result. The problem is that if I do not open the serial plotter on the computer in the beginning of the microcontroller start, microcontroller gets stuck if I open the serial plotter later.

enter image description here


When I change the buffer size from 2000 to 1000 everything is OK but I do not know why it has problem with the size of 2000.

In order to complete the documentation, this is put_arr_int function:

void usb_put_arr_int(uint16_t * number,int len)
{
    #define batch_size 50

        for (int j=0; j < (len/batch_size) ; j++)
        {
            count += j*batch_size;
            char f[batch_size*7];
            sprintf(f,"%hu\n",number[0 + j*batch_size]);
            for(int i=1;i<(batch_size);i++)
            {
                sprintf(f,"%s%hu\n",f,number[i + j*batch_size]);
                count+=i;
            }
            CDC_Transmit_FS(f,strlen(f));
}

len is always bigger than batch size.

\$\endgroup\$
8
  • \$\begingroup\$ You'll have to post code to get an answer. I strongly suspect you have a buffer length/handling problem with the DMA buffer but it is impossible to tell without seeing the code. If you have the RAM, set up two buffers in memory and in the interrupt handler at the end of the conversions just swap to the other buffer and set a flag. In a main polling loop, check the flag and process the complete buffer outside the interrupt handler. \$\endgroup\$ Commented May 7, 2021 at 2:31
  • \$\begingroup\$ Something else is running at 1/200 of the sample rate. Disable any interrupts with slow handlers while sampling; re-enable them after. \$\endgroup\$
    – user16324
    Commented May 7, 2021 at 11:20
  • \$\begingroup\$ @DeanFranks, I updated the question. \$\endgroup\$
    – SeAlGhz
    Commented May 7, 2021 at 11:37
  • \$\begingroup\$ @user_1818839, I updated the question \$\endgroup\$
    – SeAlGhz
    Commented May 7, 2021 at 11:37
  • \$\begingroup\$ How is the usb_put_arr_int implemented? The CDC_Transmit_FS((uint8_t*) buf, len) function (which I suppose you use for transmitting buffer) doesn't block until the transmit is finished. It only starts the transfer and code continues immediately. So you are probably overwriting the buffer by new measurement while it is being transmited. \$\endgroup\$ Commented May 7, 2021 at 11:50

3 Answers 3

0
\$\begingroup\$

The way I would do this:

in the callback function, stop the timer and DMA and set some "flag" variable:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
   HAL_ADC_Stop_DMA(&hadc1);
   HAL_TIM_Base_Stop(&htim3);

   samplingFinished = 1;
}

(samplingFinished is uint8_t global variable)

and in main while loop check if samplingFinished is 1.

while (1) {
  if(samplingFinished){
    samplingFinished = 0;
    
    // Send data
    
    // Start timer and DMA again

}

You can send data using CDC_Transmit_FS from HAL libraries. That function however doesn't wait until transmit is finished.

You can modify the function (in USB_DEVICE->App->usbd_cdc_if.c) so that it will wait until all data is transmitted.

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
   uint8_t result = USBD_OK;
   /* USER CODE BEGIN 7 */
     USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) hUsbDeviceFS.pClassData;
     if (hcdc->TxState != 0) {
        return USBD_BUSY;
     }
     USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
     result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);

     // New code -------------------
     for (uint32_t usbTimeout = 100000; usbTimeout > 0; usbTimeout--) {
       if (hcdc->TxState == 0)
         break;
     } 
    // ---------------------------
   /* USER CODE END 7 */
   return result;
}

The code waits until hcdc->TxState is zero, which means it is finished. If device is not connected to PC (virtual com port is not open), it would never finish, that is why I use the for loop instead of infinite wait.

\$\endgroup\$
16
  • \$\begingroup\$ I updated the question with your recommend. \$\endgroup\$
    – SeAlGhz
    Commented May 7, 2021 at 12:20
  • \$\begingroup\$ It is not a good idea to use blocking functions (which the modified CDC_Transmit_FS is) in interrupt service routines. Try the method with "flag". \$\endgroup\$ Commented May 7, 2021 at 12:26
  • \$\begingroup\$ I updated the code but the point is that maybe the timeout is not enough for 2000 samples. I also tried while (hcdc->TxState != 0); but it get stuck here. \$\endgroup\$
    – SeAlGhz
    Commented May 7, 2021 at 12:27
  • \$\begingroup\$ I do it like this in my application with 4000 samples and it works fine. \$\endgroup\$ Commented May 7, 2021 at 12:29
  • \$\begingroup\$ Is your DMA set in circular or normal mode? It can get in an error state if it attempts to write outside the length of buffer, which happens in normal mode because ADC can read one more sample before you manage to stop it. \$\endgroup\$ Commented May 7, 2021 at 12:37
2
\$\begingroup\$

A little late to the party on this one but the STM32 DMA has a half-complete callback for exactly this use case.

Basically you set your system up normally, in circular mode for your 2,000 sample example. Then enable and add a handler for your half-complete and complete callbacks.

In the half-complete handler, you transmit the first half of the buffer while the DMA is busy filling the second half.

In the filly complete handler, you transmit the second half of the buffer while the DMA is busy filling up the first half.

This continues for as long as you want to sample, and you just alternate between which buffer you are sending - which is where the term ping-pong buffers comes from :-)

\$\endgroup\$
0
\$\begingroup\$

Agree I suspect the USB transfer is taking longer than you expect. Set the ADC up as a separate DMA/interrupt task to fill buffers and never stop transferring into clean buffers. Then let the USB just transfer the buffers to the PC as they are filled

\$\endgroup\$
1
  • \$\begingroup\$ I updated the question. \$\endgroup\$
    – SeAlGhz
    Commented May 7, 2021 at 11:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.