Proyecto original
Los archivos del proyecto los bajé hace mucho tiempo de aquí pero el driver usado es una versión vieja que no soporta windows 7 (debido a las limitaciones propias del driver y la librería de microchip). Este ejemplo está basado en otro proyecto que sí funciona en Windows Vista en la dirección http://www.todopic.com.ar/foros/index.php?topic=2260.0 está la nueva versión y esta hecha por “J1M”.Lo que hice fue lo mismo que hizo el autor del post gu1llermo en Visual C pero en python: tomar de la API de microchip mpusbapi.dll versión 1.0.0.0 las funciones para comunicarse con el PIC. Las funciones se pueden tomar del código fuente proporcionado por gu1llermo o desde el archivo funciones_dll.txt.
Si se tiene instalado mingw instalado en Windows, se pueden obtener los nombres de las funciones con:
objdump -p mpusbapi.dll
El proyecto original utiliza una aplicación llamada PicUSB que se muestra a continuación:Al presionar el botón Nro. de dispositivos busca los dispositivos conectados que tengan el valor PID = 0925 y VID = 1231, si encuentra alguno entonces se mostrará un 1 al lado, si no, mostrará un 0. En la primer caja de texto se escribe un valor que representa a un comando, si el número escrito es 1 el LED conectado al PIC se enciende, si es un 2 el LED se apaga, el comando enviado aparece al lado. Las cajas de texto restantes contienen “parámetros”, que son solo números que al ser enviados con el botón Transmite2 regresa su valor multiplicado por 2.
El circuito original es el siguiente:
Pero este circuito tiene algunos problemas: no siempre funciona y a veces deja de funcionar completamente.
Proyecto pythonizado
Primero necesitamos comprender el código fuente:Se declaran los fuses y la velocidad del reloj. Cambie el reloj interno por un cristal de 12 mhz, así que los fuses quedan de la siguiente manera:
#fuses HSPLL,NOWDT,NOPROTECT,NOLVP,NODEBUG,USBDIV,PLL3,CPUDIV1,VREGEN,NOPBADEN
los valores de configuración del oscilador para que funcione el USB se ven en la tabla “TABLE 2-3: OSCILLATOR CONFIGURATION OPTIONS FOR USB OPERATION” en la hoja de datos.
Estos valores son:
HSPLL, PLL3, CPUDIV1
y dan a partir de un cristal de 12 mHz una velocidad de reloj de 48 mHz. Con la palabra clave USBDIV indicamos que utilizaremos el PLL del uC, si no lo ponemos, entonces se usaría el cristal externo como fuente para el USB.
Después definimos los parámetros de la comunicación USB:
No es un dispositivo HID.
Habilitamos el endpoint 1 de transmisión.
Habilitamos el endpoint 1 de recepción.
El numero de bytes que serán enviados es de 5.
El numero de bytes que serán recibidos es de 5.
La transferencia de salida es tipo BULK.
La transferencia de entrada es tipo BULK.
esto traducido a código es:
#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 5 //size to allocate for the tx endpoint 1 buffer #define USB_EP1_RX_SIZE 5 //size to allocate for the rx endpoint 1 buffer
Definimos los alias para las funciones de encendido y apagado, así como el PIN del LED, y le asignamos a cada byte de las variables (5 bytes por variable), un significado:
#define LED PIN_D2 #define LED_ON output_high #define LED_OFF output_low #define comando recibe[0] #define param1 recibe[1] #define param2 recibe[2] #define param3 recibe[3] #define param4 recibe[4] #define eco envia[0]// Hace un eco del comando recibido #define result1 envia[1] #define result2 envia[2] #define result3 envia[3] #define result4 envia[4]
Ahora seguimos con las funciones USB del uC
usb_init(); //inicializamos el USB usb_task(); //habilita periferico usb e interrupciones usb_wait_for_enumeration(); //esperamos hasta que el PicUSB sea configurado por el hostTenemos que tener en cuenta que la instrucción usb_init() se utiliza cuando el uC esta alimentado por el bus, y esta ligada también a la instruccion usb_task() que es la que habilita las interrupciones. Como en este ejemplo el uC se alimenta por el bus y no hace otras operaciones mientras espera datos por el puerto USB, se puede dejar usb_task() fuera del bucle principal, si el circuito se alimentara de una fuente externa, entonces se tiene que estar llamando usb_task() para comprobar si el uC esta conectado al PC y se usa la instrucción usb_init_cs(), para que no espere datos por el puerto USB y pueda realizar otras tareas.Por último esperamos que la PC reconozca al PIC.
Ya que el PIC esta conectado y enumerado podemos iniciar el bucle principal. Con la instrucción usb_kbhit(endpoint) verificamos si el endpoint de SALIDA tiene datos del host.
usb_get_packet(1, recibe, 5);con la instrucción anterior recogemos los datos del endpoint 1, en la variable recibe, y deben ser 5 bytes recibidos en total.
Por último almacenamos el comando recibido en la variable eco que será enviado a la PC junto con los valores multiplicados por dos. Si el comando recibido es un 1 (0x01), entonces se enciende el LED, si el comando recibido es un 2 (0x02) entonces el LED se apaga.
eco = comando; //Regresa el comando recibido result1 = param1*2; result2 = param2*2; result3 = param3*2; result4 = param4*2; switch (comando) { case 1:// Enciende LED LED_ON(LED); break; case 2:// Apaga LED LED_OFF(LED); break; } usb_put_packet(1, envia, 5, USB_DTS_TOGGLE);
con la instruccion usb_put_packet(endpoint, var, 5, param) se envían los datos de la variable var que debe ser de 5 bytes de tamaño por el endpoint.
Se debe tener muy en cuenta en que parte del código se pone la instrucción para enviar datos por el USB, por que si tarda demasiado en ejecutarse o el host no espera lo suficiente, entonces la comunicación no se realizará.
Para la configuración de los descriptores USB del PIC, se toma el archivo de ejemplo usb_desc_scope.h y se modifica el PID y el VID, además de los descriptores String
0x25,0x09, //vendor id (0x0925) 0x31,0x12, //product id, (0x1231)
En los descriptores String no solo se cambia el texto sino el tamaño del texto (length of string index) como se ve a continuación:
char const USB_STRING_DESC[]={ //string 0 4, //length of string index USB_DESC_STRING_TYPE, //descriptor type 0x03 (STRING) 0x09,0x04, //Microsoft Defined for US-English //string 1 --> la compañia del producto ??? 8, //length of string index USB_DESC_STRING_TYPE, //descriptor type 0x03 (STRING) 'J',0, '1',0, 'M',0, //string 2 --> nombre del dispositivo 22, //length of string index USB_DESC_STRING_TYPE, //descriptor type 0x03 (STRING) 'J',0, '1',0, 'M',0, ' ',0, 'P',0, 'i',0, 'c',0, 'U',0, 'S',0, 'B',0 };
Se cambió el circuito original añadiéndole un capacitor de 470 uF al pin VUSB y un cristal de 12 mHz.
Aplicación en python
Primero hice una aplicación en Python en modo texto para probar la ctypes y la API de Microchip:cada número se lee desde la línea de comandos y debe ser un número de un solo dígito, este número es un tipo String, que se convierte a su valor ASCII y se le resta 0x30 de manera que para los primeros 5 números quedaría así:
Número | ASCII | ASCII - 0x30 |
1 | 0x31 | 0x01 |
2 | 0x32 | 0x02 |
3 | 0x33 | 0x03 |
4 | 0x34 | 0x04 |
5 | 0x35 | 0x05 |
… |
La variable cadena es tipo String y contiene todos los bytes que han sido ingresados y convertidos con a un String con chr.
Se crean las variables necesarias para la transmisión:
SendData = ctypes.create_string_buffer(cadena) SentDataLength = (ctypes.c_ulong*1)() ctypes.cast(SentDataLength, ctypes.POINTER(ctypes.c_ulong)) SendDelay = 10 SendLength = 5SendData contiene la cadena que será mandada por el puerto USB. Esta variable se crea con la instrucción create_string_buffer() por que en la función original de la API de Microchip no se envía la cadena directamente, sino que es un puntero a una cadena. Este es el equivalente a crear un puntero a una cadena.
SendDataLength es un tipo long que contiene el numero de caracteres que han sido enviados. Como en la función original, se pasa como parámetro un puntero, se convierte con la función cast().
SendDelay es el numero de milisegundos que esperará el host antes de cortar la transmisión.
SendLength es el número de bytes que serán enviados.
Después se crean las variables necesarias para la recepción:
ReceiveData = ctypes.create_string_buffer(5) ReceiveLength = (ctypes.c_ulong*1)() ctypes.cast(ReceiveLength, ctypes.POINTER(ctypes.c_ulong)) ReceiveDelay = 10 ExpectedReceiveLength = 5ReceiveData es la variable que contendrá a los datos recibidos del PIC.
ReceiveLength es la variable que contendrá el número de datos recibidos.
ReceiveDelay es el número de milisegundos que el host esperará para recibir los datos.
ExpectedReceiveLength es el numero de bytes que el host espera recibir.
Antes de iniciar la transmisión y recepción de datos se debe de cargar la API con la instrucción CDLL().
mcpUsbApi = ctypes.CDLL("mpusbapi.dll") #con WinDLL marca errorse obtiene el numero de dispositivos, se abre el pipe de SALIDA y el pipe de ENTRADA
devcount = mcpUsbApi._MPUSBGetDeviceCount(vid_pid_norm) myOutPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, out_pipe, 0, 0) myInPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, in_pipe, 1, 0)con:
_MPUSBOpen(nro de dispositivo,vid&pid, numero de endpoint, tipo de endpoint, parámetros)el tipo de endpoint puede ser de entrada (1) o de salida (0).
Por último se transmiten los datos leídos de la línea de comando, y se leen los datos recibidos del PIC, para después imprimirse:
mcpUsbApi._MPUSBWrite(myOutPipe,SendData, SendLength, SentDataLength, SendDelay) #time.sleep(1) ## si se tarda mucho en ejecutar la sentencia usb_put_packet mcpUsbApi._MPUSBRead(myInPipe, ReceiveData, ExpectedReceiveLength, ReceiveLength, ReceiveDelay) mcpUsbApi._MPUSBClose(myOutPipe) mcpUsbApi._MPUSBClose(myInPipe) print print "Version de la API: " + str(hex(version)[2:]) print "Numero de dispositivos: " + str(devcount) print "Comando recibido: " + str(ord(ReceiveData[0])) print "Param1 resultado: " + str(ord(ReceiveData[1])) print "Param2 resultado: " + str(ord(ReceiveData[2])) print "Param3 resultado: " + str(ord(ReceiveData[3])) print "Param4 resultado: " + str(ord(ReceiveData[4]))
Aplicación gráfica
Creamos en QtDesigner la interfaz:Modificamos las propiedade inputMask = 9 y maxLength = 1 de las cajas de texto para que solo acepten un número.
Creamos un archivo en python desde donde serán llamadas las funciones de la API de Microchip:
import ctypes vid_pid_norm = "vid_0925&pid_1231" out_pipe = "\\MCHP_EP1" in_pipe = "\\MCHP_EP1" selection = 0 global mcpUsbApi global myInPipe global myOutPipe def loadAPI(): global mcpUsbApi mcpUsbApi = ctypes.CDLL("mpusbapi.dll") #con WinDLL marca error #loadAPI def dispositivos(): global mcpUsbApi devcount = mcpUsbApi._MPUSBGetDeviceCount(vid_pid_norm) return devcount #dispositivos def openPipes(): global mcpUsbApi global myInPipe global myOutPipe myOutPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, out_pipe, 0, 0) myInPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, in_pipe, 1, 0) #openPipes def closePipes(): global mcpUsbApi global myInPipe global myOutPipe mcpUsbApi._MPUSBClose(myOutPipe) mcpUsbApi._MPUSBClose(myInPipe) #closePipes def sendPacket(SendData): global mcpUsbApi global myInPipe global myOutPipe SendDelay = 10 SendLength = 5 #es un valor fijo por que en el pic es fijo SentDataLength = (ctypes.c_ulong*1)() ctypes.cast(SentDataLength, ctypes.POINTER(ctypes.c_ulong)) mcpUsbApi._MPUSBWrite(myOutPipe,SendData, SendLength, SentDataLength, SendDelay) return SentDataLength[0] #sendPacket def receivePacket(): global mcpUsbApi global myInPipe global myOutPipe ReceiveData = ctypes.create_string_buffer(5) ExpectedReceiveLength = 5 #es un valor fijo por que en el pic es fijo ReceiveDelay = 10 ReceiveLength = (ctypes.c_ulong*1)() ctypes.cast(ReceiveLength, ctypes.POINTER(ctypes.c_ulong)) mcpUsbApi._MPUSBRead(myInPipe, ReceiveData, ExpectedReceiveLength, ReceiveLength, ReceiveDelay) recibido = ReceiveData[0] + str(ReceiveData[1]) + str(ReceiveData[2]) + \ str(ReceiveData[3]) + str(ReceiveData[4]) return recibido #ReceivePacketY al final el programa principal:
import sys import PPicUSBAPI from PyQt4 import QtCore, QtGui from Ui_PPicUSBgui import Ui_mainWindow class PPicUSB(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_mainWindow() self.ui.setupUi(self) #connect self.ui.btnDispositivos.clicked.connect(self.btnDispositivosClicked) self.ui.btnTransmite.clicked.connect(self.btnTransmiteClicked) PPicUSBAPI.loadAPI() #__init__ def btnDispositivosClicked(self): nDisp = PPicUSBAPI.dispositivos() self.ui.lblDispositivos.setText(str(nDisp)) #btnDispositivosClicked def btnTransmiteClicked(self): comando = str(self.ui.txtComando.text()) param1 = str(self.ui.txtParam1.text()) param2 = str(self.ui.txtParam2.text()) param3 = str(self.ui.txtParam3.text()) param4 = str(self.ui.txtParam4.text()) comando = ord(comando) - 0x30 param1 = ord(param1) - 0x30 param2 = ord(param2) - 0x30 param3 = ord(param3) - 0x30 param4 = ord(param4) - 0x30 cadena = chr(comando) + chr(param1) + chr(param2) + chr(param3) + chr(param4) PPicUSBAPI.openPipes() PPicUSBAPI.sendPacket(cadena) recibido = PPicUSBAPI.receivePacket() PPicUSBAPI.closePipes() comandoRec = ord(recibido[0]) paramRec1 = ord(recibido[1]) paramRec2 = ord(recibido[2]) paramRec3 = ord(recibido[3]) paramRec4 = ord(recibido[4]) self.ui.lblComando.setText(str(comandoRec)) self.ui.lblResult1.setText(str(paramRec1)) self.ui.lblResult2.setText(str(paramRec2)) self.ui.lblResult3.setText(str(paramRec3)) self.ui.lblResult4.setText(str(paramRec4)) #btnTransmiteClicked if __name__ == "__main__": app = QtGui.QApplication(sys.argv) myapp = PPicUSB() myapp.show() sys.exit(app.exec_())El resultado final se muestra a continuación:
Compatibilidad con otros sistemas operativos
El ejemplo anterior no funciona en Windows Vista ni en Windows 7. Para que funcione debemos instalar el nuevo driver y utilizar la librería mpusbapi.dll version 6.0. Esa se encuentra dentro del directorio USB\Tools\MCHPUSB Custom Driver despues de instalar las Microchip application libraries.Descargar ejemplo
2 comentarios:
Hola amigo,
Antes de nada felicitarte por los estupendo trabajos que publicas. Están concienzudamente currados.
Me interesa "pitonizar" el tema USB en modo bulk transfer. Hay un parte de la página en la que creo que debería salir un código y al menos a mi no me sale. Es en la que controlas el tema en modo texto, donde dices:
<<
Primero hice una aplicación en Python en modo texto para probar la ctypes y la API de Microchip:
Mostrar/ocultar
>>
Cuando hago clic a ese botón en concreto "Mostrar/Ocultar" no me sale nada; los demás sí funcionan. ¿Hay alguna manera de poder ver ese código?
Muchas gracias!
Al final he conseguido hacerlo funcionar y lo hace perfecto. Muchas gracias por esta publicación, muy necesaria para mi y que casi no hay información en Inet, y tu artículo es el único en español, que además es el mejor artículo que he leído en cualquier idioma.
Quiero añadir varias cosas y evitar quebraderos de cabeza para quien desee hacer un proyecto parecido:
1.) En versiones Python 3.x no funciona. Tuve que instalar una versión anterior, en mi caso la 2.7.6
2.) Como la DLL (mpusbapi.dll) está diseñada para 32 bits, no puede funcionar en Python de 64 bits. Es decir, que cuando instales Python ha de ser de 32 bits y una versión anterior a las 3.x (por ejemplo la que uso, que es la ver: 2.7.6).
3.) La pregunta que hice arriba (primer post después del artículo) donde preguntaba por el código fuente porque no me salía lo resolví mirando el código fuente de la página, pude de esa forma extraer el programa y que pongo aquí. Es la opción en modo texto, primer programa:
import ctypes
#import time
out_pipe = "\\MCHP_EP1"
in_pipe = "\\MCHP_EP1"
vid_pid_norm = "vid_0925&pid_1231"
selection = 0 #numero de instancia. Por si hay mas dispositivos con igual pid&vid
##comandos extra para mantener la compatibilidad con el firmware
comando = ord(raw_input("Comando: ")) - 0x30
param1 = ord(raw_input("Param1: ")) - 0x30
param2 = ord(raw_input("Param2: ")) - 0x30
param3 = ord(raw_input("Param3: ")) - 0x30
param4 = ord(raw_input("Param4: ")) - 0x30
cadena = chr(comando) + chr(param1) + chr(param2) + chr(param3) + chr(param4)
SendData = ctypes.create_string_buffer(cadena)
SentDataLength = (ctypes.c_ulong*1)()
ctypes.cast(SentDataLength, ctypes.POINTER(ctypes.c_ulong))
SendDelay = 10
SendLength = 5
ReceiveData = ctypes.create_string_buffer(5)
ReceiveLength = (ctypes.c_ulong*1)()
ctypes.cast(ReceiveLength, ctypes.POINTER(ctypes.c_ulong))
ReceiveDelay = 10
ExpectedReceiveLength = 5
mcpUsbApi = ctypes.CDLL("mpusbapi.dll") #con WinDLL marca error
version = mcpUsbApi._MPUSBGetDLLVersion()
devcount = mcpUsbApi._MPUSBGetDeviceCount(vid_pid_norm)
myOutPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, out_pipe, 0, 0)
myInPipe = mcpUsbApi._MPUSBOpen(selection, vid_pid_norm, in_pipe, 1, 0)
mcpUsbApi._MPUSBWrite(myOutPipe,SendData, SendLength, SentDataLength, SendDelay)
#time.sleep(1) ## si se tarda mucho en ejecutar la sentencia usb_put_packet
mcpUsbApi._MPUSBRead(myInPipe, ReceiveData, ExpectedReceiveLength, ReceiveLength, ReceiveDelay)
mcpUsbApi._MPUSBClose(myOutPipe)
mcpUsbApi._MPUSBClose(myInPipe)
print
print "Version de la API: " + str(hex(version)[2:])
print "Numero de dispositivos: " + str(devcount)
print "Comando recibido: " + str(ord(ReceiveData[0]))
print "Param1 resultado: " + str(ord(ReceiveData[1]))
print "Param2 resultado: " + str(ord(ReceiveData[2]))
print "Param3 resultado: " + str(ord(ReceiveData[3]))
print "Param4 resultado: " + str(ord(ReceiveData[4]))
Publicar un comentario