Saltar al contenido
Control Automático Educación

Comunicación USB CDC con PIC + CCS C

Hola controleros y controleras, en la entrada del día de hoy vamos a realizar una comunicación USB con PIC 18F4550 en forma CDC donde podremos emular un COM desde la puerta USB de nuestro computador.

Antes de comenzar te hago la invitación para que veas nuestro CURSO GRATUITO DE MICROCONTROLADORES PIC.

Y también que te suscribas al canal en YouTUBE para que sigas aprendiendo sobre estos temas de programación de dispositivos embebidos, control de procesos, análisis de sistemas, instrumentación entre otros.

Comunicación USB con PIC

El bus de comunicación USB (Universal Serial Bus, en sus siglas en ingles) es un bus serie con una estructura de árbol que permite la conexión de diversos dispositivos en cadena permitiendo la transferencia síncrona y asíncrona entre estos.

Al momento de conectar un dispositivo USB, nuestro computador pregunta sobre su velocidad de funcionamiento y el controlador (driver) que debe cargarse para realizar una configuración adecuada.

La comunicación USB es un bus punto a punto, que comienza en el HOST y cuyo destino es el HUB. El HOST es el dispositivo maestro que inicia la comunicación y el HUB es el dispositivo que contiene uno o más conectores o conexiones hacia otros dispositivos USB. Cada conector es un puerto USB.

Además, cada dispositivo USB tiene una jerarquía de descriptores que informan al host sobre como se constituye el dispositivo y sus características de funcionamiento, tales como: la clase, el número de serie del producto, la identificación del fabricante, el tipo de dispositivo, etc.

El cable USB posee internamente un par trenzado (D+ y D-) además de la tierra y la alimentación (+5V). Los conectores están sujetos al estándar (Tipo A, Tipo B).

Clases de comunicación USB

Básicamente, hay tres tipos de clases más utilizadas: Human Interface Device (HID), Mass Storage Device (MSC), Communications Device Class(CDC).

Nuestro enfoque de estudio estará en la Clase de Dispositivo de Comunicaciones (CDC). Los dispositivos de este tipo de clase implementan un mecanismo de comunicación de propósito general que se puede utilizar para la comunicación entre la mayoría de los dispositivos. Normalmente, los dispositivos clasificados como CDC son: módems, dispositivos de red, comunicación inalámbrica, interfaces de hardware dedicadas, etc.

Pueden existir diferentes configuraciones en un sistema USB, y son los dispositivos conectados a este sistema los encargados de suministrar esta información a través de los descriptores los cuales poseen campos que permiten al sistema clasificar al dispositivo y asignarle un driver.

La primera información relevante corresponden al fabricante del dispositivo USB y el producto. Estas dos informaciones se dan a través de dos numeros conocidos como VIP (USB vendor ID) y PID (Product ID).

El VID es un número de 16 bits asignado por el fabricante del hardware a conectar. En nuestro caso podemos utilizar el número 04D8h que identifica a Microchip. O para este ejemplo, usar el número propio del CCS C 2405h que viene por defecto en el compilador.

El PID es un número de 16 bits que identifica al dispositivo en concreto a conectar. En nuestro caso utilizamos el número 000Bh que identifica a la familia de los PIC18 de microchip.

USB en CCS C

La comunicación USB es bastante compleja y su programación requiere entender adecuadamente este protocolo, debido a esto tanto Microchip como otros compiladores tales como el CCS proporcionan librerías (llamadas “Stacks” o pila de software) para facilitar la programación de los microcontroladores.

Para la comunicación CDC (Communications Device Class), se utiliza la libreria usb_cdc.h y algunas de las funciones disponibles de esta librería son:

  • usb_cdc_kbhit(): retorna TRUE si hay uno o mas caracteres esperando en el buffer de recepción.
  • usb_cdc_getc(): Obtiene el carácter recibido en el Buffer de recepción.
  • usb_cdc_putc(char c): Coloca el carácter que recibe como parámetro en el buffer de transmisión para ser enviado.
  • usb_cdc_putc_fast (char c) – Similar a usb_cdc_putc (), excepto si el búfer de transmisión está lleno, se saltará el char.
  • USB_CDC_ISR () se puede definir si desea que una rutina específica cuando haya datos entrantes por el USB CDC (puerto virtual virtual). Esta es una rutina de “interrupción serial” la cual posee algunas limitaciones.
  • usb_init(): Inicializa el hardware USB, esperando hasta que el periférico USB esté conectado al bus (Lo que NO significa que haya sido enumerado por el PC). Habilita y utiliza la interrupción USB.
  • usb_enummerated(): Devuelve TRUE si el dispositivo ha sido enumerado por el PC. En este caso el dispositivo entra en modo de operación normal y puede enviar y recibir paquetes de datos.
  • usb_init_cs(): Inicializa el protocolo USB y debe usarse cuando se utiliza una fuente externa para alimentar el microcontrolador. Esta función requiere el uso de el pin USB_CON_SENSE, el cual puede definirse al comienzo del programa y ser asignado a cualquier entrada digital del PIC, en este caso a modo de ejemplo, aunque no es necesario lo vamos a colocar en el PIN RB2.
  • usb_task(): se usa para verificar el estado lógico del pin USB_CON_SENSE y debe llamarse como máximo cada 1 ms cuando se usa una fuente externa. Si se alimenta el PIC a través del propio USB NO es necesario llamar esta función cada 1 ms.

La librería posee más funciones, y se recomienda ver la cabecera del archivo usb_cdc.h para conocer el restante de funciones.

Es importante también ver los ejemplos disponibles por el compilador CCS C, los cuales puedes encontrar en la carpeta de instalación del software dentro de la carpeta “examples”. Se recomienda darle un vistazo al ejemplo ex_usb_serial2.c y también al ejemplo ex_usb_serial3.c

Para iniciar la comunicación USB se usa la función: usb_cdc_init() y usb_init() en el main()

Es importante también estar llamando periodicamente la función usb_task() para que la comunicación USB se mantenga por lo que nuestro programa tendrá que tener un bucle infinito principal donde deberemos incluir la llamada a esta función.

LIMITACIONES DE INTERRUPCIÓN

Esta sección solo es relevante si está utilizando interrupciones USB.

El manejo del USB es complejo, y a menudo requiere varias transmisiones de paquetes para lograr la transferencia de un bloque de datos. La mayor parte de este procesamiento se realiza en el USB ISR. Debido a esto, no se puede llamar la función usb_cdc_putc() dentro de otro ISR, el USB ISR o cuando los ISR están deshabilitados. Para solucionar este problema, se recomienda usar usb_cdc_putc_fast() y la opción USB_CDC_DELAYED_FLUSH.

Esto no garantiza una comunicación perfecta, porque si usas usb_cdc_putc_fast() para desbordar el búfer TX, los datos se perderán.

Tampoco se puede llamar a usb_cdc_getc() dentro de otro ISR, el USB ISR, USB_CDC_ISR() o cuando las interrupciones están desactivadas A MENOS QUE la función usb_cdc_kbhit() devuelva un VERDADERO.

Frecuencia de Oscilación del PIC – USB 2.0

La frecuencia de oscilación necesaria para el USB 2.0 es de 48 Mhz. A continuación se muestra el diagrama de bloques del reloj del PIC18F4550:

Para conseguir los 48Mhz de oscilación, vamos a colocarle en este caso a nuestro microcontrolador un cristal de cuarzo de 20MHz y utilizaremos el PLL (Phase Locked Loop).

El PLL del circuito de reloj es un complejo circuito que nos permite modificar la frecuencia de entrada del oscilador externo primario. Dentro del circuito de reloj se tiene un divisor de frecuencia. Para ello utilizamos el fuse HSPLL. Como el módulo PLL requiere una oscilación de entrada de 4 Mhz debemos utilizar el divisor 1:5 indicado con el fuse PLL5 para obtener los 20:5 = 4 Mhz requeridos. Habilitamos el switche USBDIV para utilizar la frecuencia del PLL. En este caso también utilizaremos la velocidad de la CPU del PIC a 48Mhz, por lo tanto en la división del postcaler la vamos a dejar intacta, o sea colocando CPUDIV1.

Ejemplo USB-CDC PIC18 usando CCS C Compiler

Vamos a implementar el siguiente circuito, donde enviaremos a través de la comunicación USB CDC el valor leido en el pin análogo RA0 hacia el computador. Adicionalmente el PIC recibirá ordenes para prender o apagar los LEDs conectados en RB4 (Led1) y RB5 (Led2), para eso se enviará una trama de datos dada por: S101$ para realizar el toggle del primer LED y la trama S102$ para realizar el toggle del segundo LED. Utilizaremos la misma interfaz gráfica en MATLAB, utilizada en la entrada del CONTROL PID de un HORNO usando la Estructura de Predictor de Smith.

En este caso la alimentación del PIC se hará directamente desde el USB.

Interfaz:

Hercules: Es un software que permite mostrar en el computador los datos que están siendo enviados por el puerto serial. Es una interfaz grafica de usuario que permite visualizar los datos que nuestro PIC está enviando para nosotros (esta es una alternativa si no deseas hacer la interfaz gráfica). Puedes descargarlo dando CLICK AQUI.

Para obtener los códigos basta simplemente compartir el contenido de este post con cualquiera de los siguientes botones, de esa forma ayudas que más personas aprendan sobre estos temas.

>> DESCARGAR TODOS LOS ARCHIVOS <<

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Comentarios (2)

Un saludo Sergio Andres Castaño.

Gracias por compartir tus conocimientos en temas tan interesantes.

En este código para recepción de datos por USB veo esta función (también implementada en otras entradas y vídeos).

Me refiero a…

//Busco el valor de los datos recibidos
for(i=0;i<5;i++){
if(dat[i]=='S'){
ini=i+1;
i=10;
}
}
for(i=ini;i<5;i++){
if(dat[i]=='$'){
fin=i-1;
i=10;
}
}

Mi pregunta es ¿por que igualas i a 10. (i=10) al final de cada ciclo for?.

Gracias!!!.

Responder

Hola Manuel, al final coloco i=10 es para que se salga del for. pues en el arreglo dat estoy buscando S o $, cuando los encuentro, no me interesa seguir buscando, por eso coloco i=10 para salir del for. Saludos.

Responder