Mostrando entradas con la etiqueta C32. Mostrar todas las entradas
Mostrando entradas con la etiqueta C32. Mostrar todas las entradas

sábado, 2 de marzo de 2013

Ejemplos MMB. Acelerómetro SPI + Bluetooth UART.

Siguiendo con la entrada anterior, en la que os mostré como configurar el módulo SPI del PIC32 para enviar y recibir datos del acelerómetro, lo siguiente que nos quedaba era utilizar esos datos de aceleraciones que hemos recibido para algo. Lo más sencillo, y que además nos servirá para depurar un poco la aplicación, será mostrar por la pantalla de la MMB. Para ello, utilizamos la libreria LCDTerminal.h, y el código seria el siguiente:
sprintf(s,"Eje X:\t 0d \n", (AccX));
LCDPutString( s);
sprintf(s,"Eje Y:\t 0d \n", AccY);
LCDPutString( s);
sprintf(s,"Eje Z:\t 0d \n", AccZ);
LCDPutString( s);
Como veis el código es extremadamente sencillo, y el resultado es bastante bueno, aunque podemos hacer cosas más interesantes.
Una vez desarrollada la aplicación, y mostradas las aceleraciones en los 3 ejes en la pantalla, se me ocurre que lo siguiente que podríamos hacer, es enviar esos datos al PC, y procesarlos desde un programa en VB.NET.
Para ello lo primer que debemos hacer es convertir los datos que nos devuelven las funciones de lectura del acelerómetro, ya que como os comenté, nos puede devolver tanto valores positivos como negativos, además de un valor que numéricamente no significa nada, ya que el valor que leemos va desde –260 cuando la aceleración es –g, y 260 cuando la aceleración es +g. Estos valores los podemos convertir en un valor que vaya, por ejemplo desde 0 a 180, de forma que nos devuelva el ángulo que tenemos girada la placa en cada dirección. para ello, debemos realizar las siguientes operaciones:
AccX_tx= (AccX + 260) / 2.88;
AccY_tx= (AccY + 260) / 2.88;
AccZ_tx= (AccZ + 260) / 2.88;
Sumándole un offset para obtener valores solo positivos, y multiplicándolo por una ganancia, obtenemos los valores que queremos. Lo siguiente es enviar estos 3 valores, que ahora nos caben en un byte, por el puerto serie del PIC32. Configurar el puerto UART es muy sencillo, y lo hacemos de la siguiente forma. Lo primero nos creamos una definiciones generales que nos pueden servir para cualquier puerto UART de nuestro PIC.
// definiciones RS232
#define BAUDRATE 9600
#define BRGDATA (GetPeripheralClock()/4/BAUDRATE-1)
#define U_ENABLE 0x8008 // enable,BREGH=1, 1 stop, no parity
#define U_TX 0x1400 // enable tx & rx, clear all flags
Para luego configurar el puerto que queramos de forma más sencilla.
//CONFIGURAMOS EL MODULO USART
U2MODE = U_ENABLE; // initialize the UART module
U2STA = U_TX; // enable the Transmitter and Receiver
U2BRG = BRGDATA;
Una vez configurado, solo nos queda enviar los datos, pero para que el PIC no esté enviando los datos de forma continua, lo que hacemos es esperar a recibir un carácter, por ejemplo la ‘e’, desde el PC, y entonces el PIC32 le responde con los 3 valores de los ángulos
if( U2STAbits.URXDA){ //esperamos que llegue un dato nuevo
  if(U2RXREG == 'e'){
      LD2 = !LD2;
      U2TXREG = AccX_tx;
       while(!U2STAbits.TRMT);  //esperamos a que se envíe el dato
       U2TXREG = AccY_tx;
       while(!U2STAbits.TRMT);  //esperamos a que se envíe el dato
       U2TXREG = AccZ_tx;
       while(!U2STAbits.TRMT);  //esperamos a que se envíe el dato
       }
}
En este momento, ya tenemos los datos en el PC, y lo que queda es procesarlos. Buscándo por internet encontré un aplicación que es bastante llamativa. Se trata de un programa en visual basic, que dibuja un cubo 3d en el formulario, y este cubo va girando de forma automática en sus 3 ángulos. El programa lo encontré en la página CodeNTronix. Si a este código lo añadimos en primer lugar la comunicación UART, mediante un objeto SerialPort, le añadimos un Timer que mande al microcontrolador cada 100ms la letra ‘e’, un par de botones que nos permitan conectar y desconectar la placa, y le pasamos los datos que recibimos a los ángulos del cubo, nos podría quedar algo bonito… y así fué! este es el resultado.
Espero que os haya resultado interesante!! en breve subiré todos los archivos para que os lo podáis descargar.

sábado, 23 de febrero de 2013

Ejemplos MMB. Acelerómetro SPI

Hace ya un tiempo que empecé una serie de entradas en las que quería ir mostrándoos cada una de las aplicaciones que podemos desarrollar con la placa de desarrollo MMB for PIC32, de Mikroe. En la primera de ellas, os mostré como utilizando la pantalla tft que lleva incorporada, y el sensor de temperatura, podíamos mostrar la lectura del sensor por el TFT. En esta ocasión , os quiero mostrar una aplicación que utiliza 2 tipos de comunicaciones diferentes. En primer lugar, vamos a utilizar la comunicación SPI (Serial Peripheral Interface), el cual se basa en una comunicación serie síncrona, parecida al I2C (Inter Initegrated Circuits), pero con algunas diferencias significativas. En primer lugar, en general utilizamos 3 hilos para la comunicación, uno para la transmisión de datos (SDO), otro para la recepción (SDI), y otro para enviar la señal de reloj (SCK), mientras que en el I2C, utilizamos tan solo dos, uno bidireccional para datos, y otro para el reloj, con lo que nos limitamos a una comunicación del tipo half-duplex, mientras que con el SPI podemos hacer comunicaciones del tipo full-duplex, aunque no es lo habitual. El sistema de direccionamiento del SPI respecto al I2C, cambia drásticamente, mientras que en el I2C, la dirección del receptor la metemos dentro de la trama de datos, en el SPI debemos utilizar una línea extra (CS), por cada receptor, de esta forma, poniendo la línea CS del receptor con el que queremos comunicarnos a nivel bajo, es este el que empieza a escuchar al bus. Y por ultimo, otra diferencia entre ambos buses es el número de bits que mandamos, mientras que en el I2C, el número máximo de bits que podemos transmitir es de 8 de una vez, el protocolo SPI nos permite mandar, en el caso de PIC32, hasta 32 bits.
Configurar el módulo SPI es sencillo, pero para hacerlo más sencillo todavía, voy a utilizar una libreria para PIC32, que facilita muchísimo las cosas, la plib.h. La línea de configuración quedaría de la siguiente manera:
OpenSPI2( PRI_PRESCAL_64_1 | SEC_PRESCAL_8_1 | MASTER_ENABLE_ON | CLK_POL_ACTIVE_LOW | SPI_SMP_ON , SPI_ENABLE);
SPI2BRG = 3;
Con este trozo de código, y mediante los OR, vamos poniendo a 1 diferentes bits de los registros SPIxCON y SPIxSTAT. Las dos primeras opciones hacen referencia al registro SPI2BRG, que en la configuración se deja a 0, para luego configurarlo en 3, que corresponde con una frecuencia del SPI de 1MHz.
Una vez configurado el módulo, debemos leer y escribir en el bus, para ello debemos leer o escribir en el registro SPI2BUF. Para hacerlo más entendible, declaramos estos dos #defines
#define ReadSPI2() (SPI2BUF)
#define WriteSPI2(data_out) (SPI2BUF=(data_out))
Una vez definidas las funciones, no nos queda más que probar nuestro invento, y para ello voy a utilizar el acelerómetro que lleva incorporado la MMB, el cual se comunica con el PIC32 mediante SPI. Lo primero que tenemos que hacer es configurar el acelerómetro, y para ello hay que leer su datasheet, pero básicamente lo que configuramos es el formato de los datos de salida, y la resolución, y queda así:
// Read DEVID register (test)
    CS_ACC_LAT_BIT = 0; 
    WriteSPI2( 0x80 | 0x00 );   // read DEVID register
    while( !DataRdySPI2());
    w = ReadSPI2();             // dummy

    WriteSPI2( 0);              // dummy
    while( !DataRdySPI2());
    w = ReadSPI2();             // read DEVID
    CS_ACC_LAT_BIT = 1;
    if ( w != 0xE5) 
        return 0;               // Failed 

    // enable measurement
    CS_ACC_LAT_BIT = 0; 
    WriteSPI2( 0x00 | 0x2D );   // write to POWER_CTL register
    while( !DataRdySPI2());
    w = ReadSPI2();             // dummy
    
    WriteSPI2( 0x08);           // set Measure bit ON
    while( !DataRdySPI2());
    w = ReadSPI2();             // dummy
    CS_ACC_LAT_BIT = 1;
Como veis, siempre se hacen operaciones de lectura y escritura, de esta forma simplificamos el código ya que siempre hacemos las mismas operaciones. En primer lugar mandamos el byte, y una vez enviado leemos el buffer, este dato no nos proporciona información, es un byte dummy. Cuando el acelerómetro nos debe mandar los datos, esta vez enviamos desde el micro un byte dummy, y en esta ocasión, el dato que recibimos si que es válido. Utilizando esta misma forma, podemos también, una vez configurado el módulo SPI, leer los registros de las aceleraciones X, Y y Z. Para ello mandamos la dirección del registro y el acelerómetro nos devuelve su valor. Como en acelerómetro nos devuelve un valor de 12 bits, para cada dirección leemos 2 bytes, X1 y X0, Y1 e Y0 y Z1 y Z0.
void MMBAccRead( int* x, int* y, int*z)
{
    int w;
    
    // read acceleration values
    CS_ACC_LAT_BIT = 0; 
    WriteSPI2(0xC0 | 0x32 ); // read DataX0/X1/Y0/Y1/Z0/Z1
    while( !DataRdySPI2());
    w = ReadSPI2();          // dummy

    WriteSPI2(0x00);
    while( !DataRdySPI2());
    *x = ReadSPI2();         // read lsb
    WriteSPI2(0x00);
    while( !DataRdySPI2());

    *x |= ReadSPI2()<<8;     // read msb
    if (*x >32767) *x-=65536;

    WriteSPI2(0x00);
    while( !DataRdySPI2());
    *y = ReadSPI2();         // read lsb
    WriteSPI2(0x00);
    while( !DataRdySPI2());

    *y |= ReadSPI2()<<8;     // read msb
    if (*y >32767) *y-=65536;

    WriteSPI2(0x00);
    while( !DataRdySPI2());
    *z = ReadSPI2();         // read lsb
    WriteSPI2(0x00);
    while( !DataRdySPI2());
 
    *z |= ReadSPI2()<<8;     //read msb
    if (*z >32767) *z-=65536;

    CS_ACC_LAT_BIT = 1;
}
Con este código, almacenamos los valores de X Y y Z en los punteros *x, *y y *z.  En cada lectura, como veis, se comprueba si el valor es mayor a 32767, y si lo es se le resta 65536, de esta forma centramos la medida en 0, y obtenemos valores negativos y positivos de aceleración.
Tengo preparada una aplicación con el módulo SPI y la placa MMB, pero como esta entrada me ha quedado bastante larga dejo la aplicación y su vídeo para la próxima. ya veréis que interesante!!

viernes, 23 de noviembre de 2012

Ejemplos MMB. Lectura de temperatura.

Como comenté ya hace algún tiempo, en las navidades pasadas me compré la placa MMB for PIC32 de Mikroe. Esta placa monta un PIC32MX460f512 que funciona a 80MHz. La placa lleva un buen puñado de periféricos como son memoria FLASH y EEPROM, acelerometro de 3 ejes, sensor de temperatura analógico… así que he decidido crear un mini sección en el blog dedicada a esta placa. En esa sección iré  colgando diferentes ejemplos en los que se utilicen los distintos periféricos que trae la placa. En una entrada anterior ya os presenté la librería LCDTerminal.h, la cual nos permitía utilizar la pantalla TFT que trae la placa de una forma muy sencilla como si de un terminal se tratase, cosa que nos vendrá muy bien para visualizar diferentes datos.
En la primera de esta serie de entradas, he diseñado un programa que, como no podía ser de otra manera para ser el primero, hace una lectura del sensor de temperatura de la placa y lo muestra por pantalla. La programación que he utilizado es una “especie” de programación orientada a objetos por dos razones, la primera es que el código es más entendible, y la segunda es que muchas de las funciones que utilicemos las podremos luego meter en una librería y reutilizarlas. Cabe decir que con la placa ya viene una librería con funciones para todos los periféricos, pero en mi caso, he preferido crearme yo una librería propia. Así pues, empecemos.
El sensor de temperatura que leva la placa es un MCP9700A, el cual nos da una tensión de 10mV/ºC, con un offset de 0,5 voltios, lo que hace que podamos medir también temperaturas negativas. Para leer esta tensión necesitamos utilizar el módulo ADC del PIC32, por lo que será lo primero que configuremos. Para ello creamos la función initADC().
void initADC( int amask)
{
   AD1PCFG = amask;    // selección de pines analógicos
   AD1CON1 = 0x00E0; // conversión automática despues del muestreo
   AD1CON2 = 0;     // utilizamos MUXA, vdd y vss como referencias
   AD1CON3 = 0x1F3F;  // Tsamp = 32 x Tad;
   AD1CON1bits.ADON = 1;  // ponemos en marcha el ADC

} //initADC
A esta función le debemos pasar la mascara en la que configuramos los pines como analógicos o digitales. La siguiente función que vamos a crear es la que nos va a devolver el valor leído del ADC.
int readADC( int ch)
{
   AD1CHSbits.CH0SA = ch; // 1. select input channel
   AD1CON1bits.SAMP = 1; // 2. start sampling
   while (!AD1CON1bits.DONE); // 3. wait conversion complete
   return ADC1BUF0; // 4. read conversion result
} // readADC
A esta función le debemos pasar el canal analógico que queremos leer, y esta nos devuelve un entero entre 0 y 1024. Una vez tenemos este valor digital, debemos calcular a partir de el la temperatura, la cual almacenaremos en una variable float. Esto lo haremos mediante la función siguiente.
float readTemp(){
 
   float temp;
   temp = readADC(8);
   temp = temp * 330 / 1024;
   temp = temp - 50;
   return temp;

}
Una vez definidas las funciones auxiliares nos queda tan solo escribir el código del main(), en la que llamaremos a las funciones auxiliares. El código es el siguiente.
void main( void)
{
   float temp;
   char s[5];

// INICIALIZAMOS LA PLACA Y EL LCD
   MMBInit();          
   LCDInit();
   BacklightOn();

// SELECCIONAMOS UN COLOR PARA LAS LETRAS
   SetColor( LIGHTBLUE);     

// CONFIGURAMOS EL MÓDULO ADC
   initADC(0xFEFF); 

// MENSAJE
   LCDPutString( "Termometro LCD\nMIPS AND CHIPS\nP. Trujillo");
   LCDSetXY(0,5);

   while( 1){
  
      LCDPutString( "Temperatura:\t");
      temp = readTemp();  
      sprintf( s, "%.2f degC\n", temp);
      LCDPutString( s); 
      delay();
      LCDSetXY(0,5);

 }   
} 
Al código main se han añadido otras funciones para la configuración de la placa, además de todo el código de la visualización. En próximas entradas os podré el código para empezar a almacenar datos en la EEPROM y en la FLASH.

lunes, 3 de septiembre de 2012

Interrupciones en PIC32 I (single vectored).

Una de las limitaciones que tiene cualquier tipo de procesador mono-core es que “solo” podemos seguir un hilo de ejecución, es decir, en cada ciclo de instrucción solo podemos ejecutar una instrucción, con lo que nos es imposible ejecutar 2 acciones a la vez. Ojo! tan solo hablo de los procesos en los que interviene el procesador, si por ejemplo tenemos un modulo CCP en nuestro micro, este se encarga el solo de generar la señal PWM sin que el procesador tenga nada que ver, al igual que ocurre con los canales DMA de los PIC24 y PIC32. Por razones de eficiencia y, al fin y al cabo, coste de la aplicación, en aplicaciones en las que necesitamos mucha velocidad y el tratamiento de grandes volúmenes de datos, como por ejemplo en el control embebido, no nos podemos permitir implementar un sistema multitarea como un PC industrial, por lo que debemos utilizar las interrupciones para, de alguna forma, “dividir” la atención del procesador entre todos los módulos que requieren su atención. Un dato curioso es que a pesar de que en los microcontroladores, las interrupciones son algo que está a la orden del día, no olvidemos que el lenguaje C está pensado para ordenadores, por lo que, nativamente, no lleva incorporado el concepto de interrupción.
Las interrupciones, como ya expliqué en otra entrada, son eventos internos o externos, asíncronos, que requieren la atención rápida de la CPU. la arquitectura PIC32 puede manejar hasta 64 fuentes de interrupción diferentes, y cada una de estas, si fuera necesario, le corresponden unas líneas de código que se conocen como Interrupt Service Routine (ISR) o Interrupt Handler. Las fuentes de interrupción se pueden consultar en el documento PIC32 Reference Manual. Los registros que utilizamos para configurar las interrupciones son en primer lugar el registro de Interrupt Enable, que nos servirá para habilitar la interrupción, y lo identificamos por el sufijo –IE, por ejemplo TMR0IE, otro de los registros de las interrupciones es el registro de flag, que se pondrá a 1 cuando salte la interrupción. En este caso lo identificamos por el sufijo –IF, por ejemplo TMR0IF. Estos dos registros lo encontramos de forma idéntica en los micros de 8 bits, ahora vienen las diferencias . El siguiente registro es el de la prioridad. En los micros de 8 bits, las interrupciones se pueden configurar como alta prioridad,  o, baja prioridad. En el caso de PIC32 eso se extiende hasta 7 prioridades diferentes, siendo las de prioridad 7 las mas prioritarias. ESto lo hacemos mediante los registros con el sufijo –IP, por ejemplo TMR2IP, que son 3 bits dentro del registro IPCx. Pero no solo esto, sino que además nos ermite crear una sub-prioridad entre las 7 prioridades, es decir, si saltan 2 interrupciones con la misma prioridad, será esta la que decida quien va primero, en este caso los registros tienen el sufijo –IS, y los encontramos, como en el caso anterior, en los registros IPCx.
Una vez explicado el funcionamiento y los registros correspondientes a las interrupciones, queda saber como declararlas, es decir, definir el propio manejador de la interrupción, que en el caso de C32 nos deja hacerlo de varias maneras, aunque la más sencilla es utilizando la sintaxis #pragma interrupt. Quedaría de la siguiente manera:
#pragma interrupt InterruptHandler single
void InterruptHandler( void)
{
// código manejador interrupciones aquí
} 

Como veis, al final de la declaración pone single, esto es porque estamos utilizando una vectorización de las interrupciones simple (single vectored), que es de la que os voy a hablar en esta entrada. Para aumentar la eficiencia del procesador, en pic32 existe otro tipo de vectorización que es multiple (multi vectored), de forma  que existe un manejador diferente para cada fuente de interrupción, lo que hace mucho más rápido su tratamiento. En el caso de single vectored, tan solo tenemos un manejador de interrupciones y somos nosotros los que por software decidimos la prioridad de estas.
Otro asunto interesante de las interrupciones en C32 es la libreria int.h,  que podemos encontrar dentro de la libreria de periféricos plib.h. Esta librería nos facilita instrucciones en las que normalmente accedemos a un registro, por ejemplo, para borrar el flag de la interrupción del Timer 2, normalmente lo haríamos de la siguiente manera:

IFS0bits.T2IF = 0;
...
sin embargo utilizando la librería sería algo así:

mT2ClearIntFlag();
...
Lo único que nos ahorramos es saber en que registro está. A continuación tenéis un código completo de un programa que realiza una interrupción cada segundo y otra cada medio segundo, un programa muy sencillo pero que sirve como ejemplo de utilización:

#include <p32xxxx.h>
#include <plib.h> //incluimos la libreria plib que 
                     //lleva metida la libreria int.h
// Bits de configuración 
#pragma config POSCMOD = XT, FNOSC = PRIPLL, FSOSCEN = ON
#pragma config FPLLIDIV = DIV_2, FPLLMUL = MUL_20, FPLLODIV = DIV_1, FPBDIV = DIV_8
#pragma config OSCIOFNC = ON, CP = OFF, BWP = OFF, PWP = OFF

#define LED1_IO _LATG6
#define LED2_IO _LATD6
#define LED1_TRIS _TRISG6
#define LED2_TRIS _TRISD6
 
#define GetSystemClock() 80000000u
#define GetPeripheralClock() (GetSystemClock()/(OSCCONbits.PBDIV))    //80MHz / 8 = 10MHz

#define TMR_CONF 0x8070    //TON, 1:256, Internal, 16 bit

#pragma interrupt InterruptHandler single 
void InterruptHandler( void)
{
    LED1_IO = 1;
    if ( mT3GetIntFlag()){    //IFS0bits.T3IF == 1            //código de interrupción de T3
        mT3ClearIntFlag(); //IFS0bits.T3IF = 0;
    }    
    else if ( mT2GetIntFlag()){ //IFS0bits.T2IF == 1
        //código de interrupción de T2
        mT2ClearIntFlag(); //IFS0bits.T2IF = 0;
    }    
}
void main()
{
    AD1PCFG = 0xFFFF;    // Configuramos todos lo pines como digitales
    LED1_TRIS = 0;
    LED2_TRIS = 0;


    T2CON = TMR_CONF;
    mT2IntEnable( 1); // IEC0bits.T2IE = 1;
    PR2 = 39063;    //1 segundo
    mT2SetIntPriority( 1); //IPC2bits.T2IP = 1; asignamos la prioridad
 
    T3CON = TMR_CONF;
    mT3IntEnable( 1);    // IEC0bits.T3IE = 1;
    PR3 = 19531; //0,5 segundos
    mT3SetIntPriority( 3);    //IPC3bits.T3IP = 3;    asignamos la prioridad

    INTEnableSystemSingleVectoredInt();   //le decimos que vamos a utilizar 
                                          //vectorización simple
    while(1);
}

Por último os quiero hacer una pregunta, ¿Que pasa si salta una interrupción de prioridad 1, y mientras esta está siendo tratada salta otra de prioridad 3?, lo normal sería pensar que, el micro deja de lado la interrupción de baja prioridad y se ponga con la de alta prioridad, pero esto no es así. En la próxima entrada os explicaré como resolver este problema y os hablaré de la vectorización múltiple.

lunes, 13 de agosto de 2012

Comunicación asíncrona. RS232

Aunque, indirectamente ya he utilizado en el blog protocolos de comunicaciones, como en la entrada Proyectos USB (II), hasta ahora no os había hablado en profundidad de ninguno de ellos.
La mayoría de los sistemas formados por uno o varios microcontroladores necesitan que estos se comuniquen entre ellos, que se comuniquen con periféricos externos, como memorias, y en algunos casos, que se comuniquen también con un sistema superior como un PC. La forma en que los distintos dispositivos envían y reciben los datos, así como el medio físico por el que se transmiten se conoce como protocolo. Así pues existen 2 grandes grupos de distintos protocolos, los protocolos paralelo, los cuales presentan los datos de forma paralela y los protocolos serie. Ojo! cuando digo paralelo no significa que si tenemos que enviar 128 bits necesitemos 128 hilos, sino que enviamos cada 8 o 16 bits de forma conjunta, necesitando por tanto 8 o 16 hilos al menos. Las ventajas y desventajas de cada grupo de protocolos están claras, en las comunicaciones en paralelo su mayor ventaja es la velocidad, ya que para mandar 64 bits con un bus de 16 bits tan solo necesitamos enviar 4 datos, mientras que si lo enviamos en serie enviamos uno a uno. En cuanto a las comunicaciones en serie su mayor ventaja reside en que solo necesitamos un solo hilo para transmitir los datos. Debido a la gran velocidad que permiten las comunicaciones serie, llegando a MHz en algunos casos, la velocidad de transmisión no representa un problema, por lo que es el tipo de comunicación que más se utiliza.
Centrándonos en los protocolos serie existen otros dos grandes grupos, los síncronos, que utilizan una línea para enviar la señal de reloj, lo cual incluye un hilo más, y los asíncronos, los cuales los dos dispositivos se sincronizan sin necesidad de línea de reloj. De los protocolos síncronos los más utilizados son el SPI y el I2C, y en cuanto a los asíncronos tenemos el RS232 y RS485. En esta entrada voy a hablaros del más utilizado a nivel doméstico, el RS232.
Aunque poco a poco está siendo sustituido por el USB, todavía queda espacio para este protocolo con dispositivos como el MCP2200 o los chips FTDI, que transforman una comunicación USB en una simple RS232, y no solo podemos convertirlo en USB, sino que con la ayuda de módulos BLUETOOTH podemos crearnos una comunicación inalámbrica entre PC y microcontrolador. La mayor ventaja de File:RS-232.jpegtrabajar con este protocolo es que la mayoría de microcontroladores lo implementan por hardware, lo que hace muy fácil la programación, de hecho solo hacen falta 3 o 4 registros para tener el módulo funcionando, sin que intervengan librerías ni paquetes especiales. Los registros cambian según si utilizamos un PIC un DSPIC o un PIC32, por ejemplo, para un PIC18F2550 existen 4 registros de configuración: TXSTA, que controla la configuración de la transmisión a parte de características básicas de la comunicación, RCSTA que hace lo mismo con la recepción, BAUDCON que junto al registro BRG establece la velocidad de la comunicación, y luego los registros de envío y recepción que son el TXREG y el RCREG. Una configuración de estos registros podría ser la siguiente:
void main (void)
{
    //configuracion eusart
    BAUDCON=0x00;  //modo 8 bits
    TXSTA=0x24;   //8 bits, transmision habilitada
    RCSTA=0x90;     //8 bits, canal eusart habilitado, recepcion habilitada
    SPBRG=129;  //velocidad real de comunicacion 9600 baudios 20MHz
 
    //configuracion de interrupciones
    INTCON=0xE0; //interrupciones habilitadas a nivel global, interrupcion de timer 0 habilitada
    PIE1bits.RCIE=1; //habilitacion de la interrupcion por recepcion
    
    while (1); // Bucle sin fin
}

En este código hemos incluido la habilitación de la interrupción de recepción para que cuando en el buffer de entrada haya un dato listo para ser leído salte la interrupción.
En el caso de PIC32, el nombre de los registros cambia, al igual que, evidentemente, su tamaño, así pues para PIC32 existen tan solo 2 registros de configuración, UxMODE, con el que se realiza la configuración básica del módulo, así como su comportamiento en los modos de reposo del microcontrolador, el UxSTA, mediante el cual realizamos la configuración de la comunicación, tanto del envío, como de la recepción, y luego tenemos el registro por el cual establecemos la velocidad de comunicación que es el UxBRG. La x de cada registro hace referencia a los distintos módulos de comunicaciones que dispone un PIC32, llegando hasta 5 módulos UART. Una configuración básica de estos registros podría ser la siguiente:

#define GetSystemClock() 80000000u
#define GetPeripheralClock() (GetSystemClock()/(1<<OSCCONbits.PBDIV))    //80MHz / 8 = 10MHz
 
#define BAUDRATE 9600
#define BRGDATA (GetPeripheralClock()/4/BAUDRATE-1)
#define U_ENABLE 0x8008 // enable,BREGH=1, 1 stop, no parity
#define U_TX 0x1400 // enable tx & rx, clear all flags
 
 
void main()
    {
    
    AD1PCFG = 0xFFFF;    // Configuramos todos lo pines como digitales
    LED1_TRIS = 0;
    
    U2MODE = U_ENABLE; // initialize the UART module
    U2STA = U_TX; // enable the Transmitter and Receiver
    U2BRG = BRGDATA;
    while(1);
    }

El micro, en este caso está funcionando a 80MHz, y el bus de periféricos esta dividido entre 8, por lo que la frecuencia de los periféricos es de 10MHz, que es un valor mucho más manejable para generar retardos o velocidades de comunicación bajas. El valor del registro U2BRG viene dado por la ecuación siguiente:

image
Que es la que realizamos en el #define. Una vez inicializados correctamente los registros, solo nos queda leer el buffer de entrada y salida, que está formado por los registros UxRXREG y UxTXREG, de forma que un programa sencillo podría ser el siguiente:

#include <p32xxxx.h>
//#include <plib.h>    //incluimos la libreria de periféricos
 
// Bits de configuración 
#pragma config POSCMOD = XT, FNOSC = PRIPLL, FSOSCEN = ON
#pragma config FPLLIDIV = DIV_2, FPLLMUL = MUL_20, FPLLODIV = DIV_1, FPBDIV = DIV_8
#pragma config OSCIOFNC = ON, CP = OFF, BWP = OFF, PWP = OFF
 
#define LED1_IO _LATG6
#define LED2_IO _LATD6
#define LED1_TRIS _TRISG6
#define LED2_TRIS _TRISD6
 
#define GetSystemClock() 80000000u
#define GetPeripheralClock() (GetSystemClock()/(1<<OSCCONbits.PBDIV))    //80MHz / 8 = 10MHz
 
#define BAUDRATE 9600
#define BRGDATA (GetPeripheralClock()/4/BAUDRATE-1)
#define U_ENABLE 0x8008 // enable,BREGH=1, 1 stop, no parity
#define U_TX 0x1400 // enable tx & rx, clear all flags
 
 
void main()
    {
    
    AD1PCFG = 0xFFFF;    // Configuramos todos lo pines como digitales
    LED1_TRIS = 0;
    
    U2MODE = U_ENABLE; // initialize the UART module
    U2STA = U_TX; // enable the Transmitter and Receiver
    U2BRG = BRGDATA;
    while(1)
        {
        while (!U2STAbits.URXDA);    //esperamos que llegue un dato nuevo
        if(U2RXREG == 'e')
            LED1_IO = 1;
        else
            LED1_IO = 0;
        }
    
    }

miércoles, 18 de julio de 2012

Osciladores y PLLs.

Después de 2 entradas hablando de nuestro servidor WEB, para cambiar un poco de tercio vamos a hablar de un elemento que es fundamental en nuestros diseños con microcontroladores, tanto que sin uno de estos, nuestro micro simplemente no funciona. Este elemento tan importante es el oscilador el cual nos va a dictar a la velocidad que que va a ejecutar nuestro microcontrolador las instrucciones, lo que permitirá que nuestro micro pueda realizar o no ciertas tareas.
El bloque del oscilador en todos los micros es muy parecido, y está compuesto por un cristal de cuarzo, que formará nuestro circuito tanque, y una puerta inversora para que se mantenga la oscilación. Si queremos frecuencias muy bajas de oscilación podemos sustituir el cristal de cuarzo por una red RC, aunque es muchísimo menos estable y no recomendable. Una vez tenemos generada nuestra señal de reloj, los microcontroladores nos ofrecen muchas posibilidades para poder modificar esa señal de reloj a nuestro antojo. A continuación tenéis el esquema del circuito oscilador de un PIC24H:
image
Como veis el bloque del oscilador es bastante complejo, pero sencillo de entender y configurar. En primer lugar tenemos el Bloque oscilador principal, en el que tenemos los dos pines OSC1 y OSC2 que van a la puerta inversora que nos generara la oscilación a la frecuencia del cristal. De la salida de este bloque, tenemos 2 caminos diferentes, podemos utilizar esta frecuencia como frecuencia de oscilación directamente, poniendo el registro NOSC en la posición XT, HS o EC, dependiendo de la velocidad del cristal, o podemos, mediante S3, entrar al PLL, del cual hablaremos más tarde, para modificar esta frecuencia. Una vez tenemos seleccionada la frecuencia de oscilación FOSC, pasamos por un divisor por 2 (recordemos que los PIC24 ejecutan una instrucción por cada 2 ciclos de reloj), y sacamos directamente la frecuencia de oscilación de los periféricos, y mediante el registro DOZE dividimos FOSC para seleccionar una frecuencia para la CPU.
Además de este path (camino) tenemos otros que utilizan  los osciladores internos del micro. Por una parte tenemos el oscilador RC de alta velocidad FRC, que mediante el registro FRCDIV podemos dividir para introducir en el multiplexor principal una frecuencia menor a FRC, o igual a FRC. La frecuencia principal de este oscilador la seleccionamos mediante el registro TUN.  Además la salida del multiplexor FRCDIVC la podemos introducir en el PLL para modificarla y adecuarla a nuestro propósito.
Los otros dos osciladores que quedan son el RC de baja frecuencia y el oscilador secundario. Estos osciladores, en general serán de baja frecuencia y se utilizarán para los relojes en tiempo real, para temporizaciones grandes mediante el TIMER1 o para el WDT.
Hasta ahora hemos hablado del PLL sin que os haya explicado lo que es realmente, así que de os voy a hablar ahora. Como hemos dicho el PLL es un bloque que nos modifica la frecuencia de oscilación, en la mayoría de los casos aumentándola pero, ¿No basta con utilizar un cristal de mayor frecuencia?, si, hasta cierto punto.
image
Existen limitaciones debidas a la fabricación de los propios cristales que hacen que sea difícil hacer osciladores de frecuencias muy elevadas, y los que hay a veces tienen precios excesivos, así que para suplir ese costo existen los PLL. Aunque los PLLs originalmente son elementos puramente analógicos, los que montan los micros son PLLs digitales, aunque su funcionamiento es bastante parecido. El esquema que teneis arriba corresponde al PLL de los PIC24h, y como veis tiene varios bloques. En primer lugar tenemos el registro PLLPRE el cual tenemos que seleccionar para que la frecuencia de entrada al bucle PLL esté comprendida entre 0.8 y 8MHz. A continuación tenemos el PLL, el cual es un bucle. Es este bloque el que vamos a utilizar para multiplicar la frecuencia. El factor de multiplicación lo establece el registro PLLDIV. Los valores que puede tener este registro depende del PIC, siendo tan solo de un valor (4), para algunos PIC18, o pudiendo valer hasta 512 valores para los PIC24, por lo que podemos conseguir cualquier frecuencia de oscilación. El ultimo bloque es un divisor de frecuencia. El valor minimo por el que podemos dividir la frecuencia es 2, por lo que la salda del PLL deberá ser del doble de la que queramos. Así pues la frecuencia de oscilación del micro vendrá dad por la siguiente ecuación:
image
Por ejemplo, si teneos un reloj de 10MHz, PLLDIV = 32, PLLPRE = PLLPOST = 2, tenemos:
image
Con lo que con un reloj de 10MHz, conseguimos 80MHz, que corresponden a 40MIPS.
Por último decir que la mayoria de las caracteristicas del bloque oscilador se pueden configurar mediante los bits de configuración, por lo que no es necesario trabajar con los registros.

viernes, 6 de julio de 2012

LCDTerminal.h. TFT muy sencillo.

Cuando me compré la placa MMB for PIC32 de Mikroelektronica, de la cual hablé aquí, algo que eché en falta es una interfaz visual que fuera sencilla de utilizar, tipo un LCD de 16x2, para, por ejemplo, mostrar una temperatura, o que nos indique que todo está funcionando bien, al igual que encontramos en la placa EXPLORER16 de Microchip. Esta placa, como la comenté, monta un TFT de 320x240 puntos, lo cual es un periférico muy potente, y por tanto, algo tedioso de manipular, hasta que trabajé con la librería LCDTerminal.h.
La librería en cuestión, nos facilita mucho la labor de mostrar cadenas de texto en la pantalla TFT como si de un terminal se tratara. Esta librería viene en el CD que traen las placas MMB, aunque también la podéis encontrar en la página de Mikroe, y vale para muchos tipos de pantallas TFT con diferentes controladores. Para crear nuestro proyecto, a parte de añadir los archivos LCDTerminal.h, y LCDTerminal.c, hay que añadir las librerias referentes al control de pantallas que encontramos en las Microchip Aplication Libraries/ Graphics, como son graphics.h, Primitive.h y DisplayDriver.h, a parte de sus correspondientes archivos .c.
Como en el caso del STACK TCPIP, disponemos también de un archivo de configuración llamado GraphicsConfig.h, en el cual le decimos que elementos queremos tener disponibles y cuales no, de forma que ahorremos espacio en el micro. Para el caso de utilizar solo la pantalla como terminal, la configuración es la siguiente, que como veréis, es la más sencilla posible.
   1: #ifndef _GRAPHICSCONFIG_H
   2: #define _GRAPHICSCONFIG_H
   3:  
   4: //////////////////// COMPILE OPTIONS AND DEFAULTS ////////////////////
   5:  
   6: /*********************************************************************
   7: * Overview: Blocking and Non-Blocking configuration selection. To
   8: *            enable non-blocking configuration USE_NONBLOCKING_CONFIG
   9: *            must be defined. If this is not defined, blocking 
  10: *            configuration is assumed.
  11: *
  12: ********************************************************************/
  13: //#define USE_NONBLOCKING_CONFIG // Comment this line to use blocking configuration
  14:  
  15: /*********************************************************************
  16: * Overview: Keyboard control on some objects can be used by enabling
  17: *            the GOL Focus (USE_FOCUS)support. 
  18: *
  19: *********************************************************************/
  20: //#define USE_FOCUS
  21:  
  22: /*********************************************************************
  23: * Overview: Input devices used defines the messages that Objects will
  24: *            process. The following definitions indicate the usage of 
  25: *            the different input device:
  26: *            - USE_TOUCHSCREEN - enables the touch screen support.
  27: *            - USE_KEYBOARD - enables the key board support.
  28: *
  29: *********************************************************************/
  30: //#define USE_TOUCHSCREEN            // Enable touch screen support.
  31: //#define USE_KEYBOARD            // Enable key board support.
  32:  
  33: /*********************************************************************
  34: * Overview: To save program memory, unused Widgets or Objects can be 
  35: *            removed at compile time. 
  36: *
  37: *********************************************************************/
  38: //#define USE_GOL                 // Enable Graphics Object Layer.
  39: //#define USE_BUTTON                // Enable Button Object.
  40: // USE_WINDOW                // Enable Window Object.
  41: //#define USE_CHECKBOX            // Enable Checkbox Object.
  42: //#define USE_RADIOBUTTON            // Enable Radio Button Object.
  43: //#define USE_EDITBOX                // Enable Edit Box Object.
  44: //#define USE_LISTBOX                // Enable List Box Object.
  45: //#define USE_SLIDER                 // Enable Slider or Scroll Bar Object.
  46: //#define USE_PROGRESSBAR            // Enable Progress Bar Object.
  47: //#define USE_STATICTEXT            // Enable Static Text Object.
  48: //#define USE_PICTURE                // Enable Picture Object.
  49: //#define USE_GROUPBOX            // Enable Group Box Object.
  50: //#define USE_ROUNDDIAL            // Enable Dial Object.
  51: //#define USE_METER                // Enable Meter Object.
  52: //#define USE_CUSTOM                // Enable Custom Control Object (an example to create customized Object).
  53:  
  54: /*********************************************************************
  55: * Overview: To enable support for unicode fonts, USE_MULTIBYTECHAR  
  56: *            must be defined. This changes XCHAR definition. See XCHAR 
  57: *            for details.
  58: *
  59: *********************************************************************/
  60: //#define USE_MULTIBYTECHAR
  61:  
  62: /*********************************************************************
  63: * Overview: Font data can be placed in two locations. One is in 
  64: *            FLASH memory and the other is from external memory.
  65: *            Definining one or both enables the support for fonts located
  66: *            in internal flash and external memory. 
  67: *    - USE_FONT_FLASH - Font in internal flash memory support.    
  68: *    - USE_FONT_EXTERNAL - Font in external memory support.    
  69: *
  70: *********************************************************************/
  71: #define USE_FONT_FLASH             // Support for fonts located in internal flash
  72: //#define USE_FONT_EXTERNAL        // Support for fonts located in external memory
  73:  
  74: /*********************************************************************
  75: * Overview: Similar to Font data bitmaps can also be placed in 
  76: *            two locations. One is in FLASH memory and the other is 
  77: *            from external memory.
  78: *            Definining one or both enables the support for bitmaps located
  79: *            in internal flash and external memory. 
  80: *    - USE_BITMAP_FLASH - Font in internal flash memory support.    
  81: *    - USE_BITMAP_EXTERNAL - Font in external memory support.    
  82: *
  83: *********************************************************************/
  84: #define USE_BITMAP_FLASH        // Support for bitmaps located in internal flash
  85: //#define USE_BITMAP_EXTERNAL        // Support for bitmaps located in external memory
  86:  
  87: /*********************************************************************
  88: * Overview:
  89: *   Graphics on the MMB Board 
  90: *                                        
  91: ********************************************************************/
  92: #define LCD_WRAP
  93: #define LCD_SCROLL
  94: //#defin LCD_OVERLAY
  95: #define LCD_BACK    BLACK
  96: #define LCD_FORE    WHITE
  97:  
  98: #endif // _GRAPHICSCONFIG_H
Para ahorrar la faena de añadir los archivos .c y .h, os recomiendo que os descarguéis los ejempos de MMBforPIC34MX4, y ahi dentro encontrareis uno que se llama TEST, ese proyecto utiliza la pantalla como terminal, además de utilizar muchos de los periféricos que trae a placa, por lo que es muy sencillo, a partir de este ejemplo, empezar a crear nuestros proyectos.
Las funciones básicas que encontramos en la libreria LCDTerminal son las siguientes :
  • LCDCenterString( n, cadena): Muestra un texto centrado horizontamente en la pantalla. El valor n, es el numero de lineas por encima o por debajo de la linea central horizontal donde aparecerá nuestro texto.
  • LCDPutString( cadena): Muestra un texto con alineación a la izquierda.
  • SetColor( color): Establece el color del texto siguiente.
  • LCDSetXY( x, y): Mueve el cursor hasta la posición X, Y seleccionada.
  • LCDClear(): Borra el contenido de la pantalla y lleva el cursr a la posición X = 0, Y = 0.
  • LCDHome(): Lleva el cursor a la posición X = 0, Y = 0.
  • LCDLineHome(): Mueve el cursor al valor X = 0, el valor Y lo deja como está.
Con todo esto ya podemos crear un pequeño ejemplo para ver el funcionamiento de la librería. El ejemplo que he creado nos muestra una pantalla inicial, y cuando movemos el joystick hacia cualquier posición, pasamos a un menú desde el cual podemos encender o apagar los leds. Veréis que aparecen otras funciones como MMBGetKey(), la cual nos devuelve la posición a la que se ha movido el joystick.

   1:  
   2: #include "MMB.h"
   3: #include "LCDTerminal.h"
   4: #include "Graphics\Graphics.h"
   5: //#include "MDD File System\FSIO.h"
   6: //#include "SerialFLash.h"
   7:  
   8: // Configuration bits 
   9: #pragma config POSCMOD = XT, FNOSC = PRIPLL, FSOSCEN = ON
  10: #pragma config FPLLIDIV = DIV_2, FPLLMUL = MUL_20, FPLLODIV = DIV_1, FPBDIV = DIV_1
  11: #pragma config OSCIOFNC = ON, CP = OFF, BWP = OFF, PWP = OFF
  12:  
  13: // fonts
  14: extern const FONT_FLASH TerminalFont;
  15:  
  16: int main( void)
  17: {
  18:     char opc = 0, sel = 0;
  19:     int i = 0;
  20:     char dir = 0;
  21:  
  22:     MMBInit();          // Inicialización de la placa MMB
  23:     LCDInit();            // Inicialización de la libreria gráfica.
  24:  
  25: //---- TEXTO DE LA PANTALLA INICIAL.
  26:  
  27:     SetColor( LIGHTBLUE);     
  28:  
  29:     LCDCenterString( -5, "EJEMPLO MENU LCD");
  30:     LCDCenterString( -3, "P.Trujillo");
  31:     LCDCenterString( -2, "MIPS AND CHIPS");
  32:     SetColor( BRIGHTYELLOW);
  33:     LCDCenterString( 0, "www.mipsandchips.blogspot.com.es");
  34:     SetColor( WHITE);
  35:     LCDCenterString(  2, "(Mueva el joystick ");  
  36:     LCDCenterString(  3, "para continuar) ");  
  37:     MMBFadeIn( 250);    
  38:     MMBGetKey();    //esperamos a que se pulse un botón.
  39:     LCDClear();     
  40:     
  41:     LCDSetXY(3,2);
  42:     LCDPutString("Selecciona una opcion:\n");
  43:     LCDSetXY(0,5);
  44:     LCDPutString(" > Encender LEDs.\n   Detener LEDs.");
  45:     while( 1)
  46:     {      
  47:         LCDSetXY(0,5);
  48:  
  49:         switch(MMBGetKey()){
  50:     
  51:             case JOY_UP:
  52:                 LCDPutString(" > Encender LEDs.\n   Detener LEDs.");
  53:                 opc = 1;
  54:                 break;
  55:             case JOY_DOWN:
  56:                 LCDPutString("   Encender LEDs.\n > Detener LEDs.");
  57:                 opc = 2;
  58:                 break;
  59:             case JOY_SELECT:
  60:                 sel = opc;
  61:                 break;
  62:                 
  63:         }
  64:             if (sel == 1)
  65:                 LATA = 0x0;
  66:             else
  67:                 LATA = 0xf;    
  68:     
  69:     } 
  70: } 
Aquí tenéis unas imágenes de las dos pantallas que tenemos en el proyecto, ya que en el video no se ve demasiado bien.
IMG_1965

IMG_1966

Y a continuación un video demostrativo.


Haré más proyectos con esta librería ya que resulta muy sencillo hacer un proyecto que, a simple vista, parecen muy complejos.