USB Bulk Transfer

Control de dispositivos a través de USB con la librería MPUSBAPI.DLL

Casi todos los programas para PC que desean conectarse a un PIC con USB suelen estar realizados en VisualBasic, C++ ó Delphi. Apena existe información de cómo hacerlo en FreeBasic. Este apartado está dedicado a los usuarios de este lenguaje de programación. De todas formas, justo al final de esta página, encontrarás enlaces muy bien documentados de proyectos parecidos hechos en VisualBasic, C++ ó Delphi.

Se trata de manejar el modo "Bulk Transfer" gracias a la librería MPUSBAPI.DLL desde FreeBasic, así evitamos usar el modo (o clase) CDC (puerto virtual de comunicaciones) que nos obliga ir al "Panel de Control" de Windows para averiguar el número de puerto COM creado o conectado. También sucede que en la clase CDC obtenemos un número de puerto distinto para cada puerto USB que tengamos en el ordenador. Esto no sucede en la clase (o modo) Bulk Transfer; el PIC se identifica en cualquier puerto USB sólo a través del VID&PID. En este modo obtenemos velocidades de transferencias muy superiores a la clase CDC.

Propongo hacer un ejercicio muy sencillo: una simple suma. A partir de este ejemplo puedes añadir y modificar los programas (del PC y del PIC) para que hagan otras funciones, como por ejemplo controlar luces, motores, sensores, etc.

Ejercicio:

1.) Enviar al PIC dos números para sumar.

2.) El PIC realiza la suma y devuelve el resultado al PC, también visualiza el resultado a través de 8 LEDs.

FreeBasic code:

The translation could modify the code. Use the code without translating.
#Include Once "USB_Bulk_Transfer.bi"  ' Adjunta una parte del programa que se encarga de manejar la librería MPUSBAPI.DLL
Screen 12
Print
Print "Suma de dos numero. Han de ser menores de 128."
Print
Input "Cifra 1 (Ejemplo: 0..127)"; Put2PIC(0)
Input "Cifra 2 (Ejemplo: 0..127)"; Put2PIC(1)
MPUSBWrite(myOutPipe, @Put2PIC(0), 2, @SentDataLength, 1000) ' Envía al PIC las dos cifras.
MPUSBRead (myInpPipe, @Get2PIC(0), 1, @RecdDataLendth, 1000) ' Recibe del PIC el resultado.
Print "La inteligencia artificial del PIC piensa que es...: "; Get2PIC(0)
MPUSBClose ( MyInpPipe )
MPUSBClose ( MyOutPipe ) ' Cierra las "pipes" al salir del programa.
Sleep
End

Para simplificar el programa usé un truco en el que pongo las definiciones de la librería "MPUSBAPI.DLL" como fichero externo para ser invocado y unirse en el momento de la compilación. Se llama:

USB_Bulk_Transfer.bi

De esta forma nos concentraremos en lo esencial, que es enviar y recibir datos del PIC. Este fichero externo no está el código expuesto aquí, es necesario que lo descargues (lo encontrarás en la descarga de todo el proyecto), si no, evidentemente, no podría compilar.

Cabe destacar tres cuestiones:

1.) En los arrays "Put2PIC" y "Get2PIC" se almacenarán los valores de entrada y salida respectivamente, y lo hará en bytes (sin signo). Al ser un array, el primer byte se almacenará en 0, el segundo en 1, el tercero en 2, así hasta 64 posiciones posibles contando desde cero (0..63), pero sólo usamos dos bytes para enviar ( Put2PIC(0) y Put2PIC(1) ) y uno para recibir ( Get2PIC(0) ).

2.) La segunda es esta:

MPUSBWrite(myOutPipe, @Put2PIC(0), 2, @SentDataLength, 1000) ' Envía al PIC las dos cifras.
MPUSBRead(myInpPipe, @Get2PIC(0), 1, @RecdDataLendth, 1000) ' Recibe del PIC el resultado.

Aquí nos interesa tres detalles:

  1. Los arrays "@Put2PIC(0)" y "@Get2PIC(0)" siempre ha de comenzar por el valor cero, porque son punteros (por eso tiene la arroba delante de la variable) y apunta a la dirección donde comienza los datos, pero no es necesario que sepas de punteros.
  2. El valor 2 en "MPUSBWrite" son los bytes para enviar al PIC, y el 1 en "MPUSBRead" es el único byte para recibir.
  3. El 1000 final es la latencia en milisegundos, es decir, que se espera si fuese necesario 1 segundo tanto para enviar como para recibir. Puedes ponerle el valor que quieras, pero para este ejemplo recomiendo dejarlo tal como está.

Todo lo demás que ves en esas funciones son cuestiones técnicas que de momento no nos interesa, pero si deseas profundizar haz clic aquí.

3.) El tercer punto es mostrar el resultado. Como sólo recibimos un byte, mostramos el primer byte del array:

Print "La inteligencia artificial del PIC piensa que es...: "; Get2PIC(0)

Pasemos ahora al código en CCS C.

Código CCS:

The translation could modify the code. Use the code without translating.
#include <18F4550.h>
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,USBDIV,PLL5,CPUDIV1,VREGEN // Configurado para cristal de 20MHz.
#use delay(clock=48000000)
#define USB_HID_DEVICE     FALSE             // Deshabilitamos el uso de las directivas HID.
#define USB_EP1_TX_ENABLE  USB_ENABLE_BULK   // turn on EP1(EndPoint1) for IN bulk/interrupt transfers.
#define USB_EP1_RX_ENABLE  USB_ENABLE_BULK   // turn on EP1(EndPoint1) for OUT bulk/interrupt transfers.
#define USB_EP1_TX_SIZE    1                 // Un byte para enviar.
#define USB_EP1_RX_SIZE    2                 // Dos bytes para recibir.
#include <pic18_usb.h>      //Microchip PIC18Fxx5x Hardware layer for CCS's PIC USB driver.
#include "usb_desc_scope.h" //Enumerador USB, de Pedro, alias "Palitroquez!"
#include <usb.c>            //handles usb setup tokens and get descriptor reports.
#use fast_io(b)
void main(void)
{
   int8 recibe[2];
   int8 envia[1];
   
   set_tris_b(0x00); // Todo el puerto B como salidas.
   output_b(0x00);   // Pone a cero el puerto B.
   
   setup_adc (adc_clock_div_32); // Sin ADC, sin comparadores, etc...
   setup_adc_ports(NO_ANALOGS);
   setup_adc(ADC_OFF);
   setup_comparator(NC_NC_NC_NC);
   setup_vref(FALSE);
   port_b_pullups(FALSE);
   
   usb_init();  //init USB.
   usb_task();  //enable usb device and interruptions.
   usb_wait_for_enumeration();  //wait.
   
   output_high(PIN_B7);  // PortB.7 tiene un LED que parpadea tres veces al comienzo.
   delay_ms(500);
   output_low(PIN_B7);
   delay_ms(500);
   output_high(PIN_B7);
   delay_ms(500);
   output_low(PIN_B7);
   delay_ms(500);
   output_high(PIN_B7);
   delay_ms(500);
   output_low(PIN_B7);
   delay_ms(500);
   
   while (true)
   {
      if(usb_enumerated())                // if PicUSB is configurated.
      {
         if (usb_kbhit(1))                // Si recibe algún dato, entonces...
         {
            usb_get_packet(1, recibe, 2); // Guarda los 2 bytes recibidos en la variable "recibe".
                                          // El primer byte queda en recibe[0] y segundo byte en
                                             recibe[1].
            envia[0]=recibe[0]+recibe[1]; // Suma los dos bytes y el resultado lo guarda en "envia".
            
            output_b(envia[0]); // El resultado de la suma lo saca por el puerto B (8 LEDs).
            
            usb_put_packet(1, envia, 1, USB_DTS_TOGGLE); // Y también envía el resultado al PC.                                             
         }
      }
   }
}

Puedes cambiar el PIC poniendo la librería (.h) correspondiente. Por ejemplo, en vez de usar el PIC 18F4550 queremos usar el PIC 18F2550, entonces hacemos lo siguiente, donde pone:

#Include <18F4550.h>

Lo cambiamos por:

#Include <18F2550.h>  

Te servirá cualquier PIC de la serie 18Fxx5x mientras respete ese '5' en posición segunda comenzando por la derecha porque significa que soporta comunicación USB.

Nota:

Los "#fuses" del programa CCS están configurados para poner un cristal de 20 MHz. Si quieres poner cualquier otro cristal (de 4 a 20 MHz) haz clic aquí.

(Si usas antivirus Avast has de añadir una exclusión para poder ejecutarlo. Para analizar este o cualquier otro archivo puedes hacer clic aquí)

Está todo lo necesario, es decir, el código fuente y ejecutable de FreeBasic y CCS (.C y .HEX), los drivers de instalación para el PIC y el fichero "usb_desc_scope.h"

Si vas a compilar el código fuente hecho en FreeBasic y tienes una versión antigua de este compilador has de tener la librería "libmpusbapi.dll.a". Este fichero lo encontarás en el zip de descarga y has de copiarlo o moverlo a la carpeta (...)\FreeBASIC\lib\win32. Aunque si tienes actualizado el compilador no creo que sea necesaria esta acción.

Está probado en Windows XP, Colossus, Windows 7, 8 y 10.

Windows 7 da un error si la librería "MPUSBAPI.DLL" es antigua, cosa que no ha de sucederte porque he puesto en el zip de descarga la última versión (versión 6).

En el ZIP de descarga hay una carpeta con el driver necesario para hacer funcionar el modo "Bulk". Windows 8.x y 10 no permite por defecto instalar ningún driver que no esté firmado y ni siquiera te preguntará. Hay que lidiar un poco pero si sigues los pasos que indica esta web resolverás ese problema y podrás instalar el driver de la manera acostumbrada. Cuando lo hagas y reinicies has de ir directamente a la instalación del driver y ya no tendrás problemas.

El PIC tiene una patilla en el que es necesario poner un condensador (mayor de 450 nF) para estabilizar la tensión interna del USB; en los PICs 18F4550 sería entre el pin 18 y masa. De otro modo te daría fallos intermitentes o sencillamente no funcionaría la comunicación USB.

Haz clic aquí y tendrás un ejemplo de aplicación, donde se lee 5 encoders con un sólo PIC a través de USB (usando la librería MPUSBAPI.DLL) y un programa de PC toma esa información para mover un brazo robot virtual.

Agradecimientos y enlaces relacionados con este artículo:

Reconocer a MichaelW de FreeBasic Forum la ayuda que me ofreció para poder implementar la librería MPUSBAPI.DLL en FreeBasic.

A continuación encontrarás proyectos parecidos, en los que yo también me he servido, perfectamente documentados realizados en VisualBasic, C++ ó Delphi.

* Pedro PalitroqueZ Mis primeros pasos con el PIC 18F4550.

* RedRaven http://picmania.garcia-cuervo.net/usb_1_bulktransfers.php

* J1M hobbypic (antiguamente ese era el nombre de su web). J1M fue al parecer quien abrió camino sobre el tema PIC USB en foros de habla hispana.

* MigSantiago http://www.migsantiago.com/index.php?option=com_content&view=article&id=9:pic-usb-para-principiantes&catid=1:tutorial&Itemid=23

* Slalen del foro www.todopic.com.ar