Vengo a regalarte un día de vida si estás intentando usar tu BluePill como CDC o Virtual COM Port. Lo normal, te compras una Bluepill en Aliexpress, te llega y dices, voy a hacer que funcione como un VCP (Virtual COM Port). Pues cuidado, qué no es tan sencillo como te puedes pensar, aquí te explico todos los problemas que puedes tener.

Configuración con CubeMX

Lo primero que tenemos que hacer es generarnos una configuración del hardware y para eso, desde mi punto de vista, el STM32CubeMX, es la mejor herramienta. Te permite de manera gráfica y muy cómoda, configurar los periféricos y todo lo relacionado con el microcontrolador. Sin embargo, los ficheros que genera no son muy amigables, por lo que yo generalmente luego copio y pego las configuraciones en mis proyectos de verdad (que trabajo en platformIO, como explico aquí).

Para poder tener USB necesitamos:

  • Generar una configuración de reloj correcta
  • Activar el USB_FS del microcontrolador.
  • Incluir las librerías de ST con el protocolo USB.

Configuración del HW

Para poder sacar todo el partido al STM32, necesitamos activar el RCC, para poder activar mayores velocidad de reloj, tanto en los periféricos, como en el USB. Activamos el RCC y elegimos tanto para el HSE como para el LSE, resonadores externos.

Activación y configuración del RCC

Lo siguiente, ya que estamos en este apartado, es activar el USB_FS y el USB_DEVICE, que están un poco más abajo. El primero, no tiene mucha historia, se activa y punto. Esta parte solo nos activa el hardware, pero aun no tenemos el protocolo USB incluido.

Activación del Hw USB.

Lo siguiente que haremos es activar el protocolo, esto es, añadir una librería preconfigurada para lo que configuremos. De esta manera tendremos el trabajo mucho más sencillo, y nos abstraemos de todo el protocolo (Que no es poco). En nuestro caso queremos usar el USB como un Puerto COM virtual, así que seleccionamos la opción ‘Communication Device Class (Virtual Port Com)’

Configuración del Middleware USB_DEVICE de STM32CubeMX

Configuración de Relojes

Esta que os enseño aquí, es la configuración de Reloj que más frecuencia hace llegar a los periféricos, partiendo de un reloj HSE de 8 MHz. Es importante, para este ejemplo, que la frecuencia que llega al USB sea de 48 MHz

Configuración de Relojes en la bluepill con STM32CubeMX.

Entendiendo el código

Mandando datos por USB

Si generamos el proyecto que acabamos de configurar, se nos generarán varios ficheros que no me voy a poner a explicar en profundidad, porque daría para otro post entero. Los que nos interesan son:

  • La carpeta STM32_USB_Device_Library, que contiene todo el protocolo USB.
  • Todos los documentos usb_XXX y usbd_XXX, en concreto, el fichero usbd_cdc_if.cpp ya que tendremos que modificarlo.
  • Y por último el main.cpp
  • Si, como yo, estáis usando platformIO, entonces tendréis que ir copiando y pegando todos los ficheros anteriores, y fijaros de activar el reloj del USB en vuestra configuración.

En nuestro fichero main veremos que hay una llamada a MX_USB_DEVICE_Init() que es la encargada de inicializar todo lo que necesita el USB. Así que, sin más dilación, podéis compilar el código y hacer una pequeña prueba, para ver que todo funciona, y que podemos mandar cosas a nuestro PC. Para ello usamos la funcion CDC_Transmit_FS(buffer, longitud).

int main(void){
    // Inicializacion normal..
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_USB_DEVICE_Init();

    while(1){
        // Saludar es de educados
        CDC_Transmit_FS((uint8_t*)"Hola Mundo!\n",12);       
        // Pero no hay que ser pesado
        HAL_Delay(1000);
    }
}

Si ahora programas y conectas tu Bluepill a tu ordenador pueden pasar varias cosas:

  1. Tu Sistema Operativo te detecta el dispositivo y tienes un nuevo puerto COM/ttySx en tu ordenador Enhorabuena!
  2. Te detecta un nuevo dispositivo, pero con errores.
  3. No pasa nada

Si tu caso es el primero, no des saltos de alegría todavía, que puede ser suerte. Cuando hayas podido conectar y desconectar la Bluepill varias veces sin problema, es cuando de verdad te estará funcionando bien. En la mayor parte de los casos tendrás problemas que veremos como solucionarlo más abajo. Pero antes de eso, vamos a preparar las cosas para poder recibir datos de nuestro PC a través de USB.

Recibiendo datos por USB del STM32f103

El sistema para recibir datos no es como la transmisión, con una llamada a una función. En este middleware, la recepción está implementada mediante callBacks, es decir, que hay ciertas funciones que se ejecutarán cuando sucedan ciertos eventos.

Esas funciones están dentro del fichero usbd_cdc_if.cpp. Como veréis, aunque dentro del fichero fuente hay varias funciones de control del CDC, en el encabezado (fichero .h) solo está expuesta la de transmisión. Eso se debe a que es la única que se puede llamar desde otras partes del código. El resto de funciones de este fichero se ejecutarán cuando sucedan ciertos eventos.

En nuestro caso nos interesa recibir datos del puerto USB, así que tendremos que modificar la función CDC_Receive_FS. Es decir, cada vez que el USB reciba un bloque de datos, esta función será llamada con los datos que ha recibido y la longitud de estos. Así que una opción sería, copiar esos datos a una estructura nuestra de datos, que luego ya en nuestro programa trataremos. En mi caso, por cuestiones del proyecto me he implementado un Buffer Circular muy básico, del cual tenéis el código en mi GitHub. Pero que servirá para que veáis qué se puede hacer en esta función:

//Declaramos como externo nuestro buffer que tenemos en el bucle principal
extern CircularBuffer<uint8_t> usbBuffer;

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  //Metemos todos los bytes en nuestro buffer
  for( uint32_t i = 0; i < *Len; i++){
    usbBuffer.Write(Buf[i]);
  }

  // Otra opcion, hacer un memcpy a otro buffer
  // memcpy(bufer_principal, Buf, Len);

  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
}

Una vez hecho esto, ya tendríamos en nuestro buffer los datos recibidos por el USB (simulando un puerto virtual COM), y tendríamos que ver que hacer con ellos en el bucle principal.

Configuración del VCP

Lo que aun no sabemos es la velocidad y configuración de nuestro puerto serie virtual. Todos los puertos series necesitan que si la velocidad de transmisión, de si va a tener bit de parada o no, el de paridad… Para ello en el mismo fichero usbd_cdc_if.cpp tenemos que cambiar un par de líneas de código, para que cuando el Host pregunte, le respondamos con los datos que nosotros queremos.

Dentro de la función CDC_Control_FS, hay dos casos que nos interesan, el CDC_SET_LINE_CODING y el CDC_GET_LINE_CODING. En mi versión del driver USB que me genera el CubeMX, te incluyen una tabla de cómo hay que rellenar los bytes para configurarlo bien:

  • Los primeros 4 bytes para elegir la velocidad en bits por segundo
  • El siguiente el Stop bit:
    • 0 = 1 Stop bit
    • 1 = 1.5 Stop bits
    • 2 = 2 Stops bits
  • Luego la paridad:
    • 0 – None
    • 1 – Impar
    • 2 – Par
    • 3 – Marca
    • 4 – Espacio
  • Por último número de bits de información:
    • 5,6,7,8 o 16

Así que habría que enviar un mensaje con esa configuración, cada vez que nos preguntaran. Para eso nos hemos generado el mensaje, y vamos a hacer que se envíe, cada vez que nos pregunten algo sobre la configuración:

static uint8_t vcpConfigLine[7]
// 115200bps, 1stop, sin paridad, 8bit
= {0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08};

static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
  /* USER CODE BEGIN 5 */
  switch(cmd)
  {
    ...
    case CDC_SET_LINE_CODING: 
      memcpy( vcpConfigLine, pbuf, sizeof(vcpConfigLine) );
      break;
    case CDC_GET_LINE_CODING: 
      memcpy( pbuf, vcpConfigLine, sizeof(vcpConfigLine) );
      break;
    ...
  }

}

Y con esto tenéis todo lo que necesitáis para tener vuestro STM32f103 funcionando como VCP usando el CDC del CubeMX.

Soluciones a los problemas

Recuerda que problemas que vamos a ver salen con la mayoría de tarjetas de desarrollo como esta, aunque otro son genéricos al middleware de ST. Tened en cuenta que, qué existan estos problemas nos ayuda a aprender mucho más de cómo funcionan las cosas. hay que ver el lado positivo de todo.

1) El USB no me enumera el dispositivo

Casi todas las placas de desarrollo BluePill tienen la resistencia de Pull-Up del USB mal. La resistencia R10, es la encargada de hacer el Pull-Up de la línea D+ del USB, para que el Host/PC sepa que se ha conectado algo, y que tiene que empezar el emparejamiento. Esta resistencia debería tener un valor de 1.5 kOhms, sin embargo generalmente tienen 10k o 4k7, y para muestra un botón de la que tengo por aquí rondando.

Detalle R10 STM32 BluePill

Se puede ver que en la R10, le he tenido que dar la vuelta para poder leer los número mejor, hay una resistencia de ‘103’, lo que significa 10k. Por lo tanto, es muy probable, que enumerar este dispositivo sea un poco complicado. Para ello hay varias soluciones

Solución A: Cambiar la resistencia

No hay mucho que decir, la primera solución sería cambiar la resistencia R10 por una resistencia de un valor de 1.5K. Quitas la que está, y pones la nueva, sencillo es, pero no rápido

Solución B: Usar truco Software

El pin PA12, es el que está conectado a D+ del USB, así que podemos forzar una bajada a cero, simplemente poniendo el PIN PA12 a bajo:

   __HAL_RCC_GPIOA_CLK_ENABLE();
   GpioUSBHack.Mode= GPIO_MODE_OUTPUT_PP;
   GpioUSBHack.Pin = GPIO_PIN_12;
   GpioUSBHack.Pull = GPIO_PULLUP;
   GpioUSBHack.Speed = GPIO_SPEED_FREQ_HIGH;
   HAL_GPIO_Init(GPIOA, &GpioUSBHack);

   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
   HAL_Delay(50);

    MX_USB_DEVICE_Init();

2) No se envía ni se recibe nada

Puede ser que tu configuración base no tenga suficiente espacio en la heap para funcionar. El middleware del USB necesita de bastante memoria, y por defecto muchos compiladores/IDEs tienen un tamaño de HEAP escaso para esta función. Para ello existen dos opciones, o aumentas el tamaño de la pila, o disminuyes la memoria necesaria por el USB.

La opción más recomendable si sabes hacerlo es aumentar el tamaño del heap en tu compilador. Sin embargo, dado que eso depende de tu sistema y hay que andar tocando ficheros más sensibles, lo dejo a tu elección

Solución 1: Aumentar el heap size

Para esta solución, tendrá que buscarte un poco más las castañas, porque depende de tantas cosas, que es imposible decirte que hacer exactamente. En reglas generales, hay que modificar o bien un fichero .ld o uno .s, el primero indica cómo se distribuye la memoria en el momento de compilar, y los .s, son ficheros en lenguaje ensamblador, que también configuran parámetros del microcontrolador a muy bajo nivel

Solución 2: Disminuir la memoria de USB de CubeMX

Vamos a disminuir el tamaño de los paquetes de los endpoints del USB, de esta manera podemos reducir significativamente la memoria usada por el middleware. Para ello hay que ir al fichero usbd_cdc.h y cambiar el define:

//#define CDC_DATA_HS_MAX_PACKET_SIZE                 512U
#define CDC_DATA_HS_MAX_PACKET_SIZE                 256U

Conclusión, tenemos el BluePill como VCP

Espero que os haya servido de ayuda, y sobre todo que no hayáis “perdido” tanto tiempo como yo buscando una solución a este problema!

Nos vemos en la próxima!

Categorías: ProgramacionSTM32

Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.