Hola amigos.
Después de varios días peleando con el usb he sacado algunas cosillas en claro.Entre ellas cómo tener mi propia rutina de interrupción cuando se reciben datos por algún endpoint que no sea el endpoint 0,además de saber como acceder a los datos de forma más directa y controlada sin hacer uso de las funciones usb_gets, usb_get_packet o el consabido usb_kbhit.
No se si esto resulta válido para todas las clases de dispositivo,pero bueno,ahí va.
Las rutinas implicadas son
usb_isr (pic18_usb.h),
usb_isr_tok_out_dne (usb.c) y
usb_isr_tok_dne() (pic18_usb.h)
Lo que yo he hecho es traerme a mi fichero principal las rutinas
usb_isr y
usb_isr_tok_dne() desde el fichero pic18_usb.h y colocar al final de
usb_isr_tok_dne() una llamada a la que es mi rutina de interrupción (datos_endpoint)
#int_usb
void usb_isr() {
if (usb_state==USB_STATE_DETACHED) return; //should never happen, though
if (UIR) {
debug_usb(debug_putc,"\r\n\n[%X] ",UIR);
if (UIR_ACTV && UIE_ACTV) { usb_isr_activity();} //activity detected. (only enable after sleep)
if (UCON_SUSPND) return;
if (UIR_UERR && UIE_UERR) {usb_isr_uerr();} //error has been detected
if (UIR_URST && UIE_URST) {usb_isr_rst();} //usb reset has been detected
if (UIR_IDLE && UIE_IDLE) {usb_isr_uidle();} //idle time, we can go to sleep
if (UIR_SOF && UIE_SOF) {usb_isr_sof();}
if (UIR_STALL && UIE_STALL) {usb_isr_stall();} //a stall handshake was sent
if (UIR_TRN && UIE_TRN) {
usb_isr_tok_dne();
UIR_TRN=0; // clear the token done interrupt., 0x190.3
}
}
}
void usb_isr_tok_dne() {
int8 en;
en=USTAT>>3;
debug_usb(debug_putc,"T ");
debug_usb(debug_putc,"%X ", USTAT);
if (USTAT==USTAT_OUT_SETUP_E0) { //new out or setup token in the buffer
debug_usb(debug_putc,"%X ", EP_BDxST_O(0));
if ((EP_BDxST_O(0) & 0x3C)==USB_PIC_PID_SETUP) {
EP_BDxST_I(0)=0; // return the in buffer to us (dequeue any pending requests)
debug_usb(debug_putc,"(%U) ", EP_BDxCNT_O(0));
debug_display_ram(EP_BDxCNT_O(0), usb_ep0_rx_buffer);
usb_isr_tok_setup_dne();
//if setup_0_tx_size==0xFF - stall ep0 (unhandled request)
//if setup_0_tx_size==0xFE - get EP0OUT ready for a data packet, leave EP0IN alone
//else setup_0_tx_size=size of response, get EP0OUT ready for a setup packet, mark EPOIN ready for transmit
if (__setup_0_tx_size==0xFF)
usb_flush_out(0,USB_DTS_STALL);
else {
usb_flush_out(0,USB_DTS_TOGGLE);
if (__setup_0_tx_size!=0xFE) {
usb_flush_in(0,__setup_0_tx_size,USB_DTS_USERX);
}
}
UCON_PKTDIS=0; // UCON,PKT_DIS ; Assuming there is nothing to dequeue, clear the packet disable bit
}
else if ((EP_BDxST_O(0) & 0x3C)==USB_PIC_PID_OUT) {
usb_isr_tok_out_dne(0);
usb_flush_out(0,USB_DTS_TOGGLE);
if ((__setup_0_tx_size!=0xFE)&&(__setup_0_tx_size!=0xFF)) {
usb_flush_in(0,__setup_0_tx_size,USB_DTS_DATA1); //send response (usually a 0len)
}
}
}
else if (USTAT==USTAT_IN_E0) { //pic -> host transfer completed
__setup_0_tx_size=0xFF;
usb_isr_tok_in_dne(0);
if (__setup_0_tx_size!=0xFF)
usb_flush_in(0,__setup_0_tx_size,USB_DTS_TOGGLE);
else
usb_init_ep0_setup();
}
else {
if (bit_test(USTAT,2)) {
usb_isr_tok_in_dne(en);
}
else {
usb_isr_tok_out_dne(en);
datos_endpoint(en);
}
}
}
Dicha rutina se ejecuta cuando se reciben datos del host por el endpoint "en".Este endpoint siempre será distinto del endpoint 0,por lo que las transferencias de control del pic con el host nos serán transparentes y no nos molestarán.
Ahora bien ¿y dónde están los datos recibidos?
Cuando el módulo usb está habilitado,el pic coloca a partir de la posición de memoria 0x400 lo que viene a llamarse "Buffer Descriptor Table",que contiene una serie de registros que permiten gestionar los datos que fluyen a través de los endpoints,ya sea para transferencias OUT (PC -> PIC) como para transferencias IN (PIC -> PC)
Esta tabla de descriptores está ubicada al inicio del banco 4 y contendrá tantos descriptores (Buffer Desciptors) como endpoints use nuestro pic (por partida doble,UN DESCRIPTOR PARA IN ENDPOINT y OTRO PARA OUT ENDPOINT).
O sea,si en nuestro pic sólo usamos el Endpoint 1 (para IN y para OUT) en la tabla de descriptores de buffer tendríamos 4 descriptores,los dos del endpoint 0 (esos siempre deben estar,según las especificaciones) y los dos del endpoint 1.
En la imagen se ve el descriptor de buffer correspondiente al OUT Endpoint 0 (0x400-0x403)...consecutivo a él estaría el correspondiente al IN Endpoint 0 (0x404-0x407)...después estaría el OUT Endpoint 1 (0x408-0x40B)....etc
Hay que recordar que IN y OUT se consideran SIEMPRE desde la perspectiva del host,por lo que una transferencia OUT será siempre en el sentido PC -> PIC....así que los datos que se reciben desde el pc los gestionaremos siempre con el correspondiente EPn OUT Buffer Descriptor (siendo "n" el endpoint en cuestión).
Cada descriptor de buffer está compuesto de 4 registros:
BDnSTAT: Registro de estado del descriptor de buffer.
BDnCNT: Número de bytes (recibidos o a transmitir,según sea un IN Endpoint o un OUT Endpoint).
BDnADRL: Byte menos significativo de la dirección de comienzo de los datos.
BDnADRH: Byte más significativo de la dirección de comienzo de los datos.
Los bits 0 y 1 del registro BDnSTAT (CB8 y CB9) se usan para concatenarlos a los 8 del registro BDnCNT en el caso de transferencias de más de 256 bytes.
El bit 7 (UOWN) del mismo registro indica quien tiene la posesión del descriptor y su correspondiente búffer de datos.
¿y qué quiere decir esto?
Pues sencillo.
Tanto el CORE del micro (el programador y su programa,a fin de cuentas) como la SIE (módulo interno que gestiona el bus usb) tienen acceso a los descriptores y sus búferes,y tanto uno como el otro pueden realizar operaciones de escritura sobre ellos,por lo que el micro implementa una especie de semáforo (bit UOWN) para indicar cuando la SIE tiene el control y no se deben realizar operaciones de escitura ni en el descriptor ni en el búfer.
Los bits restantes del registro de estado y sus respectivas funciones cambian en función de quien tiene el control de los bufers.Os animo a que echeis un ojo al datasheet para saber un poco más de esto (Capítulo 17.4.1.2).
El registro BDnCNT no tiene mayor misterio...indica la cantidad de bytes recibidos tras una transferencia en un OUT Endpoint y deberá ser escrito con el valor adecuado en el caso de una transferencia a través de un IN Endpoint.
Los registros BDnADRH y BDnADRL son lo que son,un puntero para saber donde tenemos que ir a buscar los datos para transferencias OUT o un puntero para que la SIE sepa dónde tiene que ir a buscar los datos en una transferencia IN.
Hay que tener presente una cosa...el pic da la posibilidad de usar los buffers en modo Ping-Pong.Esto permite que,en vez de tener un descriptor y un buffer por cada endpoint (cuando digo "cada" estoy considerando IN Enpoint y OUT Endpoint por separado),tengamos todo por partida doble (descriptores y buffers).Dicha cosa permitiría por ejemplo que mientras la SIE está enviando un paquete de datos al host nosotros estemos escribiendo otro paquete al mismo tiempo.Esto para la agilización de las tasas de transferencia viene de perlas
aunque,por lo visto,el modo Ping-Pong no es fácil de domar.
En función de cual de los 4 modos Ping-Pong se use,la estructura de los descriptores cambia,por lo que hay que conocer el valor de los bits PPB1:PPB0 (registro SFR UCFG ) para determinar donde están nuestros descriptores.
Y bueno,un ejemplo de lo que pudiera ser la rutina de interrupción sería (considerando el modo Ping-Pong desactivado):
#define BD1OSTAT 0x408
#define BD1OCNT 0x409
#define BD1OADRL 0x40A
#define BD1OADRH 0x40B
int8 buffer_usb_in[64];
void datos_endpoint(int8 ep)
{
int8 i,bytes_recibidos,*ptr;
bytes_recibidos = *(BD1OCNT + ep*8);
ptr = 256*(*(BD1OADRH + 8*ep)) + (*(BD1OADRL + ep*8));
for(i = 0; i < bytes_recibidos; i++)
{
buffer_usb_in[i] = *ptr;
ptr++;
}
usb_flush_out(ep, USB_DTS_TOGGLE);
}
No hay que olvidar la llamada a usb_flush_out,que básicamente lo que hace es habilitar el endpoint para seguir recibiendo datos.
Pos lo dicho,espero que sea provechoso.
Saludo.