Meta:Te recomiendo que uses el PIC18F4550 para poder tener más pines para controlar dipositivos ....además viene con el SPP que se puede usar para transmitir datos a través de la SIE del USB directamente sin necesidad de control directo del PIC. Espero que mi nueva entrenadora USB tenga ese microcontrolador.
Bueno ya tengo la estructura principal del programa lista, como pueden ver más abajo hice todo en C# por que hay muchos más usuarios que manejan este lenguaje...aunque pasarlo a VB.net no sería tan complejo.
Como verán faltan funciones por desarrollar pero de a poco las estoy metiendo dentro del programa.
En cuanto al firmware le estoy haciendo modificaciones para obtener un óptimo funcionamiento.
Bueno ya tengo la estructura principal del programa lista, como pueden ver más abajo hice todo en C# por que hay muchos más usuarios que manejan este lenguaje...aunque pasarlo a VB.net no sería tan complejo.
Código:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Threading;
namespace HID_DEMO
{
public partial class Form1 : Form
{
#region // Constantes, estructuras, llamados a funciones..(SISTEMA)
// Constantes de setupapi.h que no están permitidas añadirlas directamente
// por tratarse de lenguaje C#.
internal const uint DIGCF_PRESENT = 0x02;
internal const uint DIGCF_DEVICEINTERFACE = 0x10;
// Constante para la función CreateFile() y otras funciones de I/O.
internal const short FILE_ATTRIBUTE_NORMAL = 0x80;
internal const short INVALID_HANDLE_VALUE = -1;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint CREATE_NEW = 1;
internal const uint CREATE_ALWAYS = 2;
internal const uint OPEN_EXISTING = 3;
internal const uint FILE_SHARE_READ = 0x00000001;
internal const uint FILE_SHARE_WRITE = 0x00000002;
// Constantes que indican los diferentes mensajes WM_DEVICECHANGE.
internal const uint WM_DEVICECHANGE = 0x0219;
internal const uint DBT_DEVICEARRIVAL = 0x8000;
internal const uint DBT_DEVICEREMOVEPENDING = 0x8003;
internal const uint DBT_DEVICEREMOVECOMPLETE = 0x8004;
internal const uint DBT_CONFIGCHANGED = 0x0018;
// Otras definiciones...
internal const uint DBT_DEVTYP_DEVICEINTERFACE = 0x05;
internal const uint DEVICE_NOTIFY_WINDOW_HANDLE = 0x00;
internal const uint ERROR_SUCCESS = 0x00;
internal const uint ERROR_NO_MORE_ITEMS = 0x00000103;
internal const uint SPDRP_HARDWAREID = 0x00000001;
// Algunas estructuras utilizadas por el programa:
internal struct SP_DEVICE_INTERFACE_DATA
{
internal uint cbSize;
internal Guid InterfaceClassGuid;
internal uint Flags;
internal uint Reserved;
}
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal uint cbSize;
internal char[] DevicePath;
}
internal struct SP_DEVINFO_DATA
{
internal uint cbSize;
internal Guid ClassGuid;
internal uint DevInst;
internal uint Reserved;
}
internal struct DEV_BROADCAST_DEVICEINTERFACE
{
internal uint dbcc_size;
internal uint dbcc_devicetype;
internal uint dbcc_reserved;
internal Guid dbcc_classguid;
internal char[] dbcc_name;
}
// DLL importadas:
// Retorna información sobre el dispositivo. Esta función la necesitamos para llamar a otras
// funciones que se encuentran dentro de setupdixxx().
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid, //LPGUID Entrada: Necesaria para suministrar la GUID class.
IntPtr Enumerator, //PCTSTR Entrada: Usar NULL, no es necesario para nuestros propositos.
IntPtr hwndParent, //HWND Entrada: Usar NULL, no es necesario para nuestros propositos.
uint Flags); //DWORD Entrada: Flag que indica que clase de filtrado se usará.
// Nos da la "PSP_DEVICE_INTERFACE_DATA", la cual contiene la GUID específica de interfase del dispositivo (Diferente a la GUID de clase).
// Necesitamos la GUID de interfase para encontrar la ruta de acceso del dispositivo.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiEnumDeviceInterfaces(
IntPtr DeviceInfoSet, // Entrada: entrega el HDEVINFO que obtuvimos de SetupDiGetClassDevs ()
IntPtr DeviceInfoData, // Entrada: (opcional)
ref Guid InterfaceClassGuid, // Entrada:
uint MemberIndex, // Entrada: "Indice" de los dispositivos de los cuales queremos obtener la ruta de acceso.
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); // Salida: Esta función se encuentra en la estructura "SP_DEVICE_INTERFACE_DATA".
// SetupDiDestroyDeviceInfoList() Libera memoria destruyendo la lista de información de dispositivo.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiDestroyDeviceInfoList(
IntPtr DeviceInfoSet); // Entrada: Da un identificador de una lista de información de dispositivo para cancelar la asignación de memoria RAM.
// SetupDiEnumDeviceInfo() Guarda en una estructura "SP_DEVINFO_DATA" structure, Lo que necesitamos para SetupDiGetDeviceRegistryProperty()
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet,
uint MemberIndex,
ref SP_DEVINFO_DATA DeviceInterfaceData);
// SetupDiGetDeviceRegistryProperty() Nos dá el ID de hardware, El cuál usamos para chequear si coincide con el VID/PID.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiGetDeviceRegistryProperty(
IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData,
uint Property,
ref uint PropertyRegDataType,
IntPtr PropertyBuffer,
uint PropertyBufferSize,
ref uint RequiredSize);
// SetupDiGetDeviceInterfaceDetail() Nos dá la ruta de acceso del dispositivo, Necesaria antes de poder usar CreateFile().
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiGetDeviceInterfaceDetail(
IntPtr DeviceInfoSet, // Entrada: Espera por HDEVINFO que puede ser obtenido desde SetupDiGetClassDevs().
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, // Entrada: Puntero a estructura que define la interfase de dispositivo.
IntPtr DeviceInterfaceDetailData, // Salida: Puntero a la estructura SP_DEVICE_INTERFACE_DETAIL_DATA, la cuál recivirá la ruta de acceso del dispositivo.
uint DeviceInterfaceDetailDataSize, // Entrada: Número de bytes a recuperar.
ref uint RequiredSize, // Salida: (Opcional): El número de bytes necesarios para sostener toda la estructura.
IntPtr DeviceInfoData); // Salida: (Opcional): Puntero a la estructura SP_DEVINFO_DATA.
// Sobrecarga para SetupDiGetDeviceInterfaceDetail(). Necesitamos esto ya que no podemos pasar punteros NULL directamente en C #.
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetupDiGetDeviceInterfaceDetail(
IntPtr DeviceInfoSet, // Entrada: Espera por HDEVINFO que puede ser obtenido desde SetupDiGetClassDevs()
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, // Entrada: Puntero a estructura que define la interfase de dispositivo.
IntPtr DeviceInterfaceDetailData, // Salida: Puntero a la estructura SP_DEVICE_INTERFACE_DETAIL_DATA, la cuál recivirá la ruta de acceso del dispositivo.
uint DeviceInterfaceDetailDataSize, // Entrada: Número de bytes a recuperar.
IntPtr RequiredSize, // Salida (Opcional): Puntero a variable del tipo DWORD para decirnos el número de bytes necesarios para sostener toda la estructura.
IntPtr DeviceInfoData); // Salida: (Opcional): Puntero a la estructura SP_DEVINFO_DATA.
// Necesitamos esta función para leer todos los mensajes de WM_DEVICECHANGE.
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr RegisterDeviceNotification(
IntPtr hRecipient,
IntPtr NotificationFilter,
uint Flags);
// Toma una ruta al dispositivo y abre un handler.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
// Usa un handler(Creado con CreateFile()), y nos permite escribir datos en el dispositivo USB.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool WriteFile(
SafeFileHandle hFile,
byte[] lpBuffer,
uint nNumberOfBytesToWrite,
ref uint lpNumberOfBytesWritten,
IntPtr lpOverlapped);
// Usa un handler(Creado con CreateFile()), y nos permite leer datos desde el dispositivo USB.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool ReadFile(
SafeFileHandle hFile,
IntPtr lpBuffer,
uint nNumberOfBytesToRead,
ref uint lpNumberOfBytesRead,
IntPtr lpOverlapped);
#endregion
#region // Variables globales.
bool EstadodeConexion = false; // Necesario para tener a vista el estado de conección del USB y asegurar un buen funcionamiento.
bool ConectadoPeroFallido = false;
SafeFileHandle WriteHandleToUSBDevice = null;
SafeFileHandle ReadHandleToUSBDevice = null;
String RutaAccesoDispositivo = null; // Es necesario encontrar la ruta de acceso del dispositivo antes de abrir los handlers.
// Variables usadas por la aplicación.
bool PushbuttonPressed = false; //Updated by ReadWriteThread, read by FormUpdateTimer tick handler (needs to be atomic)
bool ToggleLEDsPending = false; //Updated by ToggleLED(s) button click event handler, used by ReadWriteThread (needs to be atomic)
uint ADCValue = 0; //Updated by ReadWriteThread, read by FormUpdateTimer tick handler (needs to be atomic)
//Indentificador único global(GUID) para la clase HID. Windows usa las GUID's para identificar objetos.
Guid InterfaceClassGuid = new Guid(0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
#endregion
public unsafe Form1()
{
InitializeComponent();
// Registro para las notificaciones WM_DEVICECHANGE.Este código es usado para detectar los enventos de conección/desconección plug and play de los dispositivos USB.
DEV_BROADCAST_DEVICEINTERFACE DeviceBroadcastHeader = new DEV_BROADCAST_DEVICEINTERFACE();
DeviceBroadcastHeader.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
DeviceBroadcastHeader.dbcc_size = (uint)Marshal.SizeOf(DeviceBroadcastHeader);
DeviceBroadcastHeader.dbcc_reserved = 0;
DeviceBroadcastHeader.dbcc_classguid = InterfaceClassGuid;
// Necesario para obtener la dirección de DeviceBroadcastHeader y llamar a RegisterDeviceNotification().
IntPtr pDeviceBroadcastHeader = IntPtr.Zero; // Crea un puntero..
pDeviceBroadcastHeader = Marshal.AllocHGlobal(Marshal.SizeOf(DeviceBroadcastHeader)); // asinga memoria para una nueva estructura DEV_BROADCAST_DEVICEINTERFACE y retorna la dirección.
Marshal.StructureToPtr(DeviceBroadcastHeader, pDeviceBroadcastHeader, false); // Copia la estructura DeviceBroadcastHeader en la memoria ya asignada a DeviceBroadcastHeaderWithPointer.
RegisterDeviceNotification(this.Handle, pDeviceBroadcastHeader, DEVICE_NOTIFY_WINDOW_HANDLE);
// Ahora hace un intento inicial por encontrar el dispositivo USB, en caso de ya esté conectado a la PC y enumerado por el host antes de la lanzar la aplicación.
// En caso de que esté conectado y presente, Nosotros debemos abrir los handles de lesctura/escriturar en el dispositivo para así luego podernos comunicarnos con el.
// Si no está conectado , tendremos que esperar a que el usuario conecte el dispositivo, y que las funciones WM_DEVICECHANGE llamen a los diferentes procesos que analizarán los
// mensajes y luego buscarán el dispositivo.
if (CheckIfPresentAndGetUSBDevicePath()) // Chequea y se asegura que el último dispositivo que coincide con el VID/PID está conectado.
{
uint ErrorStatusWrite;
uint ErrorStatusRead;
// Ahora tenemos la ruta de acceso del dispositivo correcta, entonces ahora podemos abrir en el dispositivo los hanles de lectura/escritura.
WriteHandleToUSBDevice = CreateFile(DevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
ErrorStatusWrite = (uint)Marshal.GetLastWin32Error();
ReadHandleToUSBDevice = CreateFile(DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
ErrorStatusRead = (uint)Marshal.GetLastWin32Error();
if ((ErrorStatusWrite == ERROR_SUCCESS) && (ErrorStatusRead == ERROR_SUCCESS))
{
bool EstadodeConexion = true;
bool ConectadoPeroFallido = false;
Estado_USB.Text = "Dispositivo Encontrado, Estado de conección = CONECTADO";
}
else // Por alguna razón el dispositivo está conectado fisicamente pero uno de los 2 handles (lectura o escritura) no se abrió correctamente.
{
bool EstadodeConexion = false; // Deja que el resto de la aplicación sepa que no hay que realizar operaciones de LECTURA/ESCRITURA.
bool ConectadoPeroFallido = true; // Esperamos que ocurra el siguiente mensaje de WM_DEVICECHANGE luego podemos reintentar abrir los pipes de lectura/escritura.
if (ErrorStatusWrite == ERROR_SUCCESS)
WriteHandleToUSBDevice.Close();
if (ErrorStatusRead == ERROR_SUCCESS)
ReadHandleToUSBDevice.Close();
}
}
else // El dispositivo no debe estar conectado (O no tiene grabado el firmware correcto).
{
EstadodeConexion = false;
ConectadoPeroFallido = false;
}
if (AttachedState == true)
{
Estado_USB.Text = "Dispositivo Encontrado, Estado de conección = CONECTADO";
}
else
{
Estado_USB.Text = "Dispositivo no encontrado, Estado de conección = NO CONECTADO";
}
ReadWriteThread.RunWorkerAsync(); // Recomienda ejecutar las opereciones de lectura/escritura en un hilo de ejecución diferente. De otra manera
// las operaciones de lectura/escritura van a bloquear las operaciones
// de usuario y la interfase en caso de que las operaciones de IO tarden mucho tiempo en completarse.
}
}
Como verán faltan funciones por desarrollar pero de a poco las estoy metiendo dentro del programa.
En cuanto al firmware le estoy haciendo modificaciones para obtener un óptimo funcionamiento.