Saltar al contenido

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, el cual puede ser el PIC 18F4550 o PIC18F2550, 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).

En una futura entrada abordaremos el uso del USB HID con PIC18F4550.

Nuestro enfoque de estudio en esta entrada 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 USB-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.

PIC USB en CCS C

La comunicación USB con PIC 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 USB-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 PIC 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 comunicación USB con PIC 18f4550, o cualquier otro de la familia 18, 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 PIC 18F4550 USB:

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

Para este ejemplo vamos a implementar la comunicación USB con el PIC 18F4550 en ccs c, sin embargo este ejemplo lo puedes extrapolar al uso del PIC18F2550 USB.

PIC18f4550 USB tutorial

Vamos a implementar el siguiente circuito, donde enviaremos a través de la comunicación PIC 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. El circuito para la Comunicación USB PIC18f4550 CCS se muestra a continuación:

comunicación usb pic18f4550 ccs
Comunicación USB PIC18f4550 CCS

El driver USB CDC para el pic18f4550 o cualquier otro pic o dispositivo ya viene instalado por defecto en Windows 10, ya que es un driver genérico.

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 del 18F4550 USB 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 <<

#include <18F4550.h>
#device ADC=10
#fuses HSPLL, NOWDT, NOPROTECT, NODEBUG, USBDIV, PLL5, CPUDIV1, VREGEN
#use delay(clock=48000000)

#byte porta = 0xf80 // Identificador para el puerto A. 
#byte portb = 0xf81 // Identificador para el puerto B. 
#byte portc = 0xf82 // Identificador para el puerto C. 
#byte portd = 0xf83 // Identificador para el puerto D. 
#byte porte = 0xf84 // Identificador para el puerto E.

//#define  USB_CONFIG_PID       0x000A
//#define  USB_CONFIG_VID       0x04D8


// if USB_CDC_ISR is defined, then this function will be called
// by the USB ISR when there incoming CDC (virtual com port) data.
// this is useful if you want to port old RS232 code that was use
// #int_rda to CDC.
#define USB_CDC_ISR() RDA_isr()

// in order for handle_incoming_usb() to be able to transmit the entire
// USB message in one pass, we need to increase the CDC buffer size from
// the normal size and use the USB_CDC_DELAYED_FLUSH option.
// failure to do this would cause some loss of data.
#define USB_CDC_DELAYED_FLUSH
#define USB_CDC_DATA_LOCAL_SIZE  128

static void RDA_isr(void);

// Includes all USB code and interrupts, as well as the CDC API
#include <usb_cdc.h>
#include <stdlib.h>
#include <string.h>

#define USB_CON_SENSE_PIN PIN_B2 //No usado cuando alimentado desde el USB
#define LED1 PIN_B4
#define LED2 PIN_B5


int deg=0;

//Define la interrupción por recepción Serial
static void RDA_isr(void)
{  
 while(usb_cdc_kbhit())
   {
    int i=0,ini=0,fin=0;
    char dat[5];
    char degC[5];
    
    // Almacena 5 datos leidos del USB CDC
     for(i=0;i<5;i++){
        dat[i]=usb_cdc_getc();
       }
   
   
   
   //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;
     }
    }
    if(ini!=0 && fin!=0){
       // salvo en degC el caracter con el escalon
       for(i=ini;i<=fin;i++){
        degC[i-ini]=dat[i];
       }
       
        deg = atol(degC); //Convierte el String en un valor numerico
        
        if(deg==101)
         output_toggle(LED1);
         
        if(deg==102)
         output_toggle(LED2);
    }
  }
}


void main(){   
   int16 v=0;
   float p;
   char msg[32]; 
   
   setup_adc_ports(AN0);
   setup_adc(ADC_CLOCK_INTERNAL);
   set_adc_channel(0);
   
   set_tris_b(0b00000100);
   bit_clear(portb,4);
   bit_clear(portb,5);

   usb_cdc_init();
   usb_init();
   
   //enable_interrupts(INT_RDA); //Habilita Interrupción por serial (Recepcion USB_CDC)
   //enable_interrupts(GLOBAL);  //Habilita todas las interrupciones
   
   while(true){
      usb_task();  //Verifica la comunicación USB
      if(usb_enumerated()) {
         v = read_adc();
         p=5.0 * v / 1023.0;
         sprintf(msg,"I%1.2fFI%1.2fFI%1.2fF",p,p,p); 
         printf(usb_cdc_putc,"%s",msg); 
         delay_ms(1000);
      }
   }
}

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 (8)

hola, no logro realizar la conexion y que el puerto usb me aparezca como puerto com, por que es el motivo?

Responder

buenas noche como logro para conecta un rs 232 a W9425G6JH ocon un usb

Responder

Buenas Noches Sergio Castaño. Tus cusos son excelentes, estoy tomando el de Simulink y estoy aprendiendo mucho. Te escribo para preguntar como se usa el USB_CON_SENSE_PIN y como se conecta el PIN B2 cuando el micro se polariza con una fuente externa. Te agradezco de antemano. Mil Gracias

Responder

Hola José, en el ejemplo de la entrada, si observas el circuito, este ya tiene conectado el pin de sensado USB_CON_SENSE_PIN en el Pin RB2. De esa forma puedes conectarlo en tu proyecto. También el código está adaptado para esto, ya que dentro del while se usa llama constantemente la funcion USB_TASK() el cual siempre va a estar verificando el estado lógico de este pin, para saber si todavía está conectado el usb.

Responder

Mil Gracias Sergio. Me considero un ingeniero algo experimentado… tengo más de 50 años, pero lo cierto es que la tecnologia avanza mas rapido y este tipo de esfuerzos ayudan mucho a mantener el ritmo. Te agradezco mucho.

Responder

De nada José, que bueno que te sirve el contenido del sitio web para mantener el ritmo. Saludos y muchos éxitos.

Responder

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