2005/03/22
No Comments
Reading time: 11 – 18 minutes
Perquè no soni tant a japonès el que dic ho intentaré explicar per parts. Es tracte de fer el que plantejava en l’article: Problema amb OVTDTool: Omnivision 2610 ECX. O sigui, que disposem d’un kit de desenvolupament de la camara Omnivision OV2610 que porta un sensor d’imatges CMOS i un xip Cypress que comunica el sensor CMOS amb el PC a través d’una interficie USB 2.0. Així doncs per tal d’accedir als registres propietaris del CMOS s’ha de fer usant ‘usb vendor requests’. És a dir, enviar paquest de ‘request’ al xip de USB indicant que volem accedir a registres propietaris del CMOS. Quna parlo de registres propietaris el que vull dir és que no són registres estàndars USB 2.0 sinó que són específics del fabricant per aquest producte.
En aquest cas el que això ens permet fer és activar/desactivar funcions d’autoajustament: AGC (auto gain control), AWB (auto white balance), etc. també podem fer altres coses com fixar paràmetres que per defecte tenen altres valors, com la lluminositat, contrast, components de color RGB, etc. O sigui, que podem fer treballar la camara (sensor CMOS) com a nosaltres ens interessi.
Una mica de teoria per saber el que hem de fer
Per tal d’entendre com funciona la tecnologia USB sino us voleu comprar cap llibre hi ha una web que ho explica d’una forma extraordinaria: USB in a NutShell ( pdf ). Allà podreu veure que hi ha uns elements lògics dins els dispositius USB que s’anomenen ENDPOINTS concretament hi ha un tipus d’ENDPOINTS que s’anomenen “Control Transfers” aquests són els que s’usen per enviar comandes de configuració. Funcionalitats dels ENDPOINT de tipus CONTROL TRANSFER seria:
Control transfers are typically used for command and status operations. They are essential to set up a USB device with all enumeration functions being performed using control transfers. They are typically bursty, random packets which are initiated by the host and use best effort delivery. The packet length of control transfers in low speed devices must be 8 bytes, high speed devices allow a packet size of 8, 16, 32 or 64 bytes and full speed devices must have a packet size of 64 bytes.
Així doncs aquests són els paquets de més baix nivell que hem de generar per configurar el dispositiu. Com que el volem és no complicar-nos tan la vida el que fem és mirar de generar aquests paquets des de capes més elevades i així no baixar tan de nivell en problemes que no són el nostre. Per exmple, retransmissions, confirmacions de recepció, control de finestres d’enviament, etc. Per tant, el que ens interessarà és el concepte de “USB Descriptors”:
All USB devices have a hierarchy of descriptors which describe to the host information such as what the device is, who makes it, what version of USB it supports, how many ways it can be configured, the number of endpoints and their types etc
Concretament els descriptors s’organitzen segons la següent gerarquia:
Com podem observar en el gràfic el descriptor que ens interessa és el “Endpoint Descriptor” que són els que ens permet accedir als CONTROL TRANSFERs que he comentat abans. Si mirem l’estructura de dades que defineix un ENDPOINT DESCRIPTOR podem observar que té un camp de 8 bits anomenat “bmAttributes” en aquest camp els primers dos bits ens permeten definir el “Transfer Type” i d’aquí podem extreure que el valor que ens interessa és “00” per aquests dos bits, o sigui, “CONTROL”. Amb això li estarem dient al paquet sigui del tipus CONTROL TRANSFER quan surti de l’ENDPOINT.
Per poder configurar el nostre paquet no ens queda més remei que seguir escalant en l’stack fins arribar als “Setup packets”:
Every USB device must respond to setup packets on the default pipe. The setup packets are used for detection and configuration of the device and carry out common functions such as setting the USB device’s address, requesting a device descriptor or checking the status of a endpoint.
I aquí tenim el que voliem, el format del paquet que realment volem enviar com podem veure es tracta d’un paquet molt senzill de només 8 bytes, que hem de configurar bit a bit. El primer camp de 8 bits: bmRequestType serveix per definir el tipus de request que anem a fer, aquests 8 bits es divideixen en 3 grups i cal escollir els grups de bits adients per la nostre petició. O sigui:
- 1er grup: Data Phase Transfer Direction (1bit) 0 Host to device i 1 Device to Host
- 2on grup: Type (2 bits) 00 Standard, 01 Class, 10 Vendor, 11 reserved. Pel nostre cas el que ens interessa seria el 10 Vendor, perquè volem accedir a registres no estàndars USB sinó propietaris del fabricant del CMOS.
- 3er grup: Recipient (5 bits) 00000 Device, 00001 Interface, 00010 Endpoint, 00011 Ohter, la resta estan reservats. Pel nostre cas el que ens interessa és el 00000 Device.
Fins aquí tenim els primers 8 bits definits, o sigui: 0xc0 (11000000) ‘llegim’ del device i 0x40 (1000000) ‘escribim’ al device. Aquests serien els valors que cal que recordem. Els altres 4 camps:
- bRequest (8bits) posarem el registre amb valor 0x2 per escriure i amb valor 0x3 per llegir. Cal que us fixeu en el datasheet del vostre producte per saber exactament el que voleu posar en aquest camp, ja que això com ja he dit, són comandes específiques del fabricant.
- wValue (16bits) aquí posarem el valor que volem que tingui el registre que volem escriure.
- wIndex (16bits) indiquem el registre que volem accedir, ja sigui per escriure o per llegir
- wLength (16bits) llargada del missatge que volem rebre quan llegim. Normalment són 8 o 16 bits. Acostumen a ser valors fixes, quan estem parlant de registres propietaris del venedor.
Fins aquí la teoria. Tot sembla molt confús per tant, aprofito per tornar a recomanar el llegir-se i comprendre profundament el document que he referenciat. He explicat tot això perquè sinó seria impossible entendre que volem fer realment. Ja que a la practica tot s’acaba resumint a unes poques linies de codi.
Objectiu pràctic
Com que ja sabem el que volem fer, ara només falta saber com ho hem de fer. El primer que ens cal és fer un programa que ens permeti accedir directament al dispositu a baix nivell i ens doni una interficie a nivell d’usuari per accedir a les funcions concretes d’això se’n diu un driver. Cosa que no sembla que ens interessi gaire fer perquè ja en tenim un de driver i ja va prou bé, no es tracta de reprogramar-lo. Així doncs com que no tenim les funcions d’espai d’usuari del nostre driver documentades aquest se’ns queda petit. El que podriem fer és programar un filter driver, aquests només funcionen en arquitectures basades en NT (2K, XP i 2k3) el que fan és col·locar-se sobre el driver existent i canviar el seu comportament només en alguns aspectes i quan algún aplicatiu d’espai d’usuari ho requereix, així doncs per la resta d’aplicatius són transparents i aquests podem treballar amb el driver natiu sense problemes.
Obviament això també pot representar una feinada i més per algú que no domina el DDK (Device Drivers Kit) de M$ i l’arquitectura WDM (Windows Driver Model). Però he trobat una llibreria d’espai d’usuari per accedir de forma genèrica a dispositius USB i a més aquesta implementa un filter driver que pot instal·lar-se sobre el driver genèric del dispositiu. Això ens permet programar amb 4 linies el que volguem i tenir el driver genèric del fabricant amb el que funcionen tots els programes disponibles per aquest.
Pel nostre problema només hem de fer 4 ratlles en C invocant la llibreria que he anomenat i ella mateixa ja s’encarrega de treballar contra el filter driver. A més tenim completament especificada l’API d’aquesta llibreria. Si tot això us sembla poc us puc dir que es basa en un projecte de multiplataforma Unix i que és 100% portable el codi amb ben poques modificacions. A més amb llicència GPL, és clar. Així doncs aquí teniu la gran troballa libusb-win32. De serie veureu que porta un aplicatiu que ens descobreix els dispositius USB connectats al nostre sistema. És un bon exemple per començar-se a familiaritzar amb com usar la llibreria.
NOTA: les libusb-win32 també ens permeten crear drivers en espai d’usuari i no només treballar amb filter drivers, només hem de programar el nostre codi usant l’API del driver genèric que implementen.
Com treballar amb libusb-win32
El primer que heu de fer és baixar-vos això libusb-win32-filter-bin-0.1.10.1.exe (local) ho instal·leu sense cap dispositiu USB connectat al sistema, si pot ser. Això el que farà és instal·lar-vos el filter driver, si no el veieu tranquils no és trivial, -penseu que no és un driver sinó que esta sobre el mateix- i 3 fitxers .lib un per usar a través del compilador GCC, un altre pel compilador de Borland (BCC) i finalment pel Visual Studio (MSVC). El darrer cas ha estat el meu cas. També ens instal·la un fitxer anomenat usb.h que és el que hem d’usar per compilar el nostre programa i no pas el usb.h que porta el DDK. Així doncs aneu en compte al definir les dependències del compilador i el linkador del MSVC.
Jo he instal·lat el paquet a c:\drivers\libusb, això ho comento perquè a continuació us poso unes captures de pantalla de com he modificat els ‘settings’ del projecte de MSVC perquè aquest sigui capaç de compilar i linkar el meu programa.
Dins el MSVC si anem a Project -> Settings:
Podem definir els includes especials del nostre projecte, necessaris pel procés de compilació:
I les llibreries a usar en el procés de linkat:
Ara ja sóm capaços de fer un programa que treballi amb les libusb-win32, només hem d’afegir el #include <usb.h> al principi del nostre codi i a disfrutar de l’API de les libusb.
Posem la teoria en pràctica
En poques paraules anem a escriure el codi que em feia falta, després de tanta història. Anem per passos, el primer que cal fer és saber com inicialitzar el dispositiu i buscar els dispositius USB connectats al sistema:
Un cop tenim la llista de dispositius USB connectats al sistema iterem pels mateixos en busca de la nostre càmara. Com que no espero tenir més d’una càmara connectada al mateix ordinador no ho he tingut en compte en el codi. El que he fet és agafar una aplicació d’analisis de busos USB i descobrir el identificador del fabricant (idVendor) i del producte (idProduct). En el meu cas: idVendor = 0x05a9 (Omnvision) i idProduct = 0x2800 (OV2610). Així doncs busquem això:
Un cop hem identificat el nostre dispositiu arriba l’hora de posar en pràctica tot el que haviem après fa una estona. Així doncs anem a construir els nostres paquet de control (setup packets) que accedeixen a registres pròpis del fabricant. En aquest exemple el que faig és accedir al registre número 0x13 i guardar el seu valor al buffer, després el mostro per pantalla. Després modifiquem el valor de registre 0x13 i li donem el valor 0x15 seguidament el tornem a llegir i a mostrar per pantalla per comprobar que ha canviat de valor.
Per saber què hi ha a cada registre, quins valors pot tenir i què signifiquen, heu de buscar-ho a la documentació de cada fabricant. En el meu cas això ho he trobat al datasheet del CMOS d’Omnivision que és de les poques coses que ens va donar el fabricant al comprar el ‘development kit’.
El codi l’he comentat moltíssim per tant, considero que queda més que clar com funciona el mateix. Si el vodeu descarregar: ov.c. Si voleu el executable directament per provar-lo: ov.exe, tot i que dubto que us serveixi de res ja que aquest .exe només funciona amb el development kit que he usat.
La sortida del programa és la següent:
Podem comprobar com el valor anterior del registre 0x13 és 0xcf i com el nostre programa canvia el valor a 0x15, així doncs. FUNCIONA!!!
Conclusions
Podeu comprobar com tot el ‘rollo’ de teoria inicial serveix per entendre com hem de formar les peticions usb_vendor_msg() de la llibreria libusb-win32. Sense comprendre com funciona l’arquitectura USB a fons era impossible saber com s’havien de formar aquestes peticions. De fet, us he de confessar que jo el que vaig fer va ser aplicar enginyeria inversa ja que disposava d’una eina de debugging del producte (OVTDTool.exe) que em permetia accedir a aquests registres i després gràcies a un assistent de creació de drivers –Jungo– vaig poder posar a la pràctica la teoria que he explicat. El problema era que aquest software no em permetia crear filter drivers que treballessin amb el driver que ja tenia de la càmara i m’obligava a crear el meu propi driver des de 0.
Així doncs, a grans problemes grans solucions i després de continuar formant-me vaig descobrir aquest gran invent dels ‘filter drivers’ i el que ja em va fer veure la llum va ser la llibreria libusb-win32 que m’ha permès amb molt poc temps trobar la sortida a aquest ‘aparenment complicat problema’. Ara es pot veure que no ho era tan.