Tutoriales

PIC - Puerto serial por USB CDC - Código fuente PIC - Inicialización en main.c

Código fuente PIC - Inicialización en main.c

En main.c encontrarás la inicialización del sistema y sus periféricos, incluido el USB. La función SYSTEM_Initialize(SYSTEM_STATE_USB_START) es meramente otro agregado de Microchip a sus librerías para manejar los diversos estados USB en los que el dispositivo se puede encontrar. Puedes usar su switch case por si esos estados te sirven en tu aplicación específica.

La función UserInit() es en donde nosotros inicializaremos nuestro sistema en específico. En mi caso inicializo el TRIS de todo el pinout y echo a andar el Timer 2 (útil para llevar el tiempo real y el redibujado de la matriz).

Una vez inicializado tu hardware, deberás inicializar el periférico USB a través de:

    USBDeviceInit();
    USBDeviceAttach(); 

Esto sólo debe hacerse una vez en toda la ejecución del micro.

Para el caso de la Matriz de Leds, el Timer 2 se configuró para levantar el bit PIR1bits.TMR2IF cada 1ms, basado en el reloj del sistema de 12MIPS.

static void Initialize_Timer_2(void)
{
    PIR1bits.TMR2IF = 0;
    PIE1bits.TMR2IE = 0;
    IPR1bits.TMR2IP = 0;
 
    TMR2 = 0;
    PR2 = TIMER_2_PR2;
 
    T2CON = 0x17; /* Pre 1:3, post 1:16 */
} 

El oscilador es levantado desde 4MHz del cristal externo (buenos así ya que generan menos ruido) a 48MHz internos que son equivalentes a 12MIPS en el PIC.

 

La configuración de los relojes se hace a través de directivas #pragma, incluidas en el archivo system.c.

 

Código fuente PIC - Atrapando mensajes USB CDC

Una vez que el PIC ya fue configurado, el periférico USB empezará a funcionar por su cuenta, si es que no definiste la macro USB_POLLING en ningún momento. Esto es lo más recomendable ya que los eventos USB se atienden casi de inmediato y no tendrás el riesgo de desenumerar tu PIC por estar haciendo otras cosas.

Se crea un ciclo while(1) en el que el micro estará procesando tareas USB y propias de la aplicación. En mi caso de la matriz de LEDs, verifico si el timer 2 ya generó un tick de 1ms. Si es el caso, llamo a la función Process_Timer_2_Tick que se encarga de actualizar el reloj de tiempo real.

Después llamo a la función que siempre debe correrse para redibujar la matriz de LEDs. Esto lo veremos posteriormente.

Por último, se verifica que el dispositivo USB sí esté enumerado:

        if( USBGetDeviceState() < CONFIGURED_STATE )
        {
            /* Jump back to the top of the while loop. */
            continue;
        } 

Si no lo estuviera, vuelve a comenzar la ejecución del ciclo while. Si sí está enumerado, entonces llamamos la función getsUSBUSART(), que se encarga de leer datos del puerto USB en un buffer interno.

count += getsUSBUSART(&ReceivedDataBuffer[count], CDC_DATA_OUT_EP_SIZE - count);

La función pide 2 parámetros:

+ Una dirección en donde deberá empezar a depositar bytes leídos desde USB
+ y un número de bytes a leer desde lo que haya llegado

Devuelve el número de bytes que realmente leyó.

En mi caso, la forcé a siempre leer 64 bytes y que los vaya leyendo poco a poco. Cuando ya juntó 64 bytes, entonces es cuando proceso lo recibido por USB y ejecuto una acción. Esto es útil para que el host USB siempre mande comandos de 64 bytes y nunca se pierda el orden de recepción, tú puedes implementar tu propio protocolo:

        //Check if we have received CDC_DATA_OUT_EP_SIZE bytes
        if(CDC_DATA_OUT_EP_SIZE == count)
        {
           count = 0;
 
            /* Service the command */
            switch((PIC_Command_T)ReceivedDataBuffer[0])
            {
                case DRAW_RAW_FRAME:
                    Current_Command = DRAW_RAW_FRAME;
                    Proc_F = Process_DRAW_RAW_FRAME;
                    break;
 
                case SET_RTC:
                    Proc_F = Process_SET_RTC;
                    break;
 
                case DRAW_A_NUMBER:
                    Current_Command = DRAW_A_NUMBER;
                    Proc_F = Process_DRAW_A_NUMBER;
                    break;
 
                case DRAW_TIME:
                    Current_Command = DRAW_TIME;
                    Proc_F = Process_DRAW_TIME;
                    break;
 
                case SET_ROTATION:
                    Proc_F = Process_SET_ROTATION;
                    break;
 
                case GAME_OF_LIFE:
                    Current_Command = GAME_OF_LIFE;
                    Proc_F = Process_GAME_OF_LIFE;
                    break;
 
                default:
                    Proc_F = Process_UNUSED;
                    break;
            }
            (*Proc_F)(ReceivedDataBuffer, Column_Data);
 
//            if(USBUSARTIsTxTrfReady() == true)
//            {
//               memcpy(ToSendDataBuffer, ReceivedDataBuffer, CDC_DATA_OUT_EP_SIZE);
//               putUSBUSART(ToSendDataBuffer, CDC_DATA_OUT_EP_SIZE);
//            }
 
            CDCTxService(); 

Una vez ejecutada la acción, se puede enviar algo por la UART mediante putUSBUSART como se ve en el código que comenté. En mi aplicación no me ha hecho falta y lo dejé por si luego lo necesito. Al final, no olvides llamar CDCTxService().

Cuando proceso el comando recibido lo hago a través de un apuntador a función. No es complicado, primero defino su tipo...

typedef void (*Process_Function_T)(uint8_t* command, uint8_t* column_data);

Y luego creo un apuntador con ese tipo, el cuál se encargará de llamar la función deseada:

Process_Function_T Proc_F; 

En el caso en el que el host pida poner la hora del reloj de tiempo real:

                case SET_RTC:
                    Proc_F = Process_SET_RTC;

                    break; 

Me encargo de hacer que Proc_F apunte a mi función Process_SET_RTC y después de que apunta a ella, pues que la ejecute, mediante una derreferenciación:

(*Proc_F)(ReceivedDataBuffer, Column_Data); 

Si gustas evitar el uso de apuntadores a funciones, llama directamente la función deseada, esto lo hice por costumbre.