1 votos

STM32 ADC muestreo con temporizador y DMA y enviar los datos a la computadora con USB

Quiero muestrear una señal de 4kHz producida por un generador de señales.

Leo 2000 muestras seguidas y luego las envío al ordenador a través del serial virtual que proporciona el USB del STM32F103C8T6. He configurado el temporizador 3 con el fin de activar el ADC y luego me puse un búfer de longitud 2000 para DMA.

Cuando trazo la señal en el ordenador los datos no son continuos y hay un problema entre la señal.

También detengo el ADC al principio del PeriodElapsedCallback y lo inicio de nuevo al final del mismo.

Aquí está la imagen de mi señal:

enter image description here

El reloj del ADC es de 12MHz. Los ciclos están ajustados a 28,5 y utilizo un ADC de 12 bits. Mi reloj temporizador es de 48MHZ y cuenta 30 relojes para disparar el ADC. El DMA está configurado en modo circular. No espero estas interrupciones en mi señal porque estoy tomando 2000 muestras sin ninguna parada.

EDITAR: Este es mi código. Como mencioné antes, detengo el DMA al principio de la interrupción y lo vuelvo a iniciar al final.

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);

}

También cambié el periodo del temporizador de 30 a 400 y obtuve un mejor resultado pero el problema sigue ocurriendo. Sospecho de la velocidad DMA.

enter image description here

Según mis cálculos, el temporizador activa la interrupción cada 400/48000000 = 8.33us y la velocidad de mi ADC es de (28.5+12.5+2.5)/12000000 = 3.62us

Así que la velocidad del ADC es más rápida que la del temporizador. Por lo tanto, espero que el temporizador no pueda disparar el ADC antes de que el último muestreo se haya completado.


Cambié la función CDC según lo que dijo @JiríMaier (no cambié mi código de interrupción).

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


También cambié la estructura de mi código y cambié mi código de interrupción según los consejos de @JiríMaier y obtuve el siguiente resultado. El problema es que si no abro el plotter serie en el ordenador al principio del arranque del microcontrolador, éste se queda atascado si abro el plotter serie después.

enter image description here


Cuando cambio el tamaño del buffer de 2000 a 1000 todo va bien pero no sé por qué tiene problema con el tamaño de 2000.

Para completar la documentación, esto es put_arr_int función:

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 es siempre mayor que batch size .

0 votos

Tendrás que enviar el código para obtener una respuesta. Sospecho fuertemente que tienes un problema de longitud/manejo del buffer DMA pero es imposible decirlo sin ver el código. Si tienes la RAM, configura dos buffers en memoria y en el manejador de interrupciones al final de las conversiones sólo cambia al otro buffer y establece una bandera. En un bucle de sondeo principal, comprueba la bandera y procesa el buffer completo fuera del manejador de interrupciones.

0 votos

Algo más está funcionando a 1/200 de la frecuencia de muestreo. Desactiva cualquier interrupción con manejadores lentos mientras se realiza el muestreo; vuelve a activarlos después.

0 votos

@DeanFranks, he actualizado la pregunta.

0voto

Simon Peacock Puntos 1

Estoy de acuerdo en que sospecho que la transferencia USB está tardando más de lo que esperas. Configura el ADC como una tarea DMA/interrupción separada para llenar los búferes y nunca dejar de transferir a los búferes limpios. Entonces deja que el USB sólo transfiera los buffers al PC a medida que se llenan

0 votos

He actualizado la pregunta.

0voto

Bartuc Puntos 40

La forma en que yo haría esto:

en la función callback, detener el temporizador y el DMA y establecer alguna variable "flag":

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

   samplingFinished = 1;
}

(samplingFinished es una variable global uint8_t)

y en el bucle while principal comprobar si samplingFinished es 1.

while (1) {
  if(samplingFinished){
    samplingFinished = 0;

    // Send data

    // Start timer and DMA again

}

Puedes enviar datos usando CDC_Transmit_FS desde las bibliotecas HAL. Esta función, sin embargo, no espera hasta que la transmisión haya terminado.

Puede modificar la función (en USB_DEVICE->App->usbd_cdc_if.c) para que espere hasta que se transmitan todos los datos.

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;
}

El código espera hasta que hcdc->TxState sea cero, lo que significa que ha terminado. Si el dispositivo no está conectado al PC (el puerto com virtual no está abierto), nunca terminaría, por eso uso el bucle for en lugar de la espera infinita.

0 votos

He actualizado la pregunta con tu recomendación.

0 votos

No es una buena idea utilizar funciones de bloqueo (como lo es la CDC_Transmit_FS modificada) en rutinas de servicio de interrupción. Pruebe el método con "bandera".

0 votos

He actualizado el código pero la cuestión es que quizás el tiempo de espera no es suficiente para 2000 muestras. También he probado while (hcdc->TxState != 0); pero se atasca aquí.

0voto

Ralph Hempel Puntos 1

Llego un poco tarde a la fiesta, pero el DMA de STM32 tiene un callback medio completo para exactamente este caso de uso.

Básicamente, usted configura su sistema normalmente, en modo circular para su ejemplo de 2.000 muestras. A continuación, activar y añadir un controlador para su medio completo y completo callbacks.

En el manejador medio completo, se transmite la primera mitad del buffer mientras el DMA está ocupado llenando la segunda mitad.

En el manejador de llenado completo, se transmite la segunda mitad del buffer mientras el DMA está ocupado llenando la primera mitad.

Esto continúa durante todo el tiempo que quieras muestrear, y sólo tienes que alternar entre el búfer que estás enviando - que es de donde viene el término búferes ping-pong :-)

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X