martes, 3 de enero de 2012

Lectura/escritura en RAM PCF 8583

Esta es la segunda práctica del reloj calendario, como os comenté antes. Lo que vamos a hacer en ella es escribir y leer directamente en la RAM de nuestro PCF8583, para eso, se necesita tener un PIC, en nuestro caso el 18f4550 y una manera de mantenerlos comunicados, es decir, con nuestro famoso BUS I2C.

Como ya expliqué de una manera más amplia el Reloj calendario, me voy a limitar a copiar lo de la entrada anterior para que no os perdais, y seguidamente, comenzaremos con la práctica.
Características I2C:
Bus de comunicación síncrono.
�� La comunicación es controlada por una señal de reloj común.
�� Bus formado por 2 hilos:
�� SDA (Serial DAta Line): datos
�� SCL (Serial CLock line): reloj
�� También es necesaria una referencia común de masa.
�� Velocidad de transmisión.
�� Standard: hasta 100 Kbits/s.
�� Fast: hasta 400 Kbits/s.
�� High-speed: hasta 3,4 Mbits/s.
�� Cada dispositivo del bus tiene una dirección única.
�� 7 bits, I2C estándar
�� 11 bits, I2C mejorado.
�� Distancia y número de dispositivos
�� Limitado por la capacidad del bus (inferior a 400pF). Normalmente 2 o 3 metros.
�� Drivers para largas distancias (centenares de metros).
�� Protocolo de acceso al bus:
�� Maestro – esclavo.
�� I2C soporta protocolo multimaestro.
Características del PCF8583:
Este componente es un reloj calendario que posee en su interior una RAM de 2048 bits que a su vez están organizados en 256 palabras, cada una de 8 bits. Sus datos son transferidos mediante la conexión del BUS i2c.
Este es su esquema:
Todo lo demás que necesiteis saber, lo teneis aquí, en el propio PDF.
DATASHEET: PCF8583


Recordad que, como os he dicho esta práctica es una continuación de la anterior del Reloj calendario,lo digo, porque, voy a trabajar en el entorno de proteus con exactamente el mismo archivo, es decir, tendrá un PIC, el bus I2C, un LCD alfanumérico 2x16, un reset y demás. 

El siguiente paso es el trabajo de C, en este programa se utilizan 2 librerías que nos vienen en el propio CCSC compiler, una es la del PCF8583, y la del LCD alfanumérico ("LCD_flexible.c") que, como se dijo, éste tiene un micro incorporado el cual debe estar configurado antes de usarse.


En primer lugar, vamos con el código en C del PCF8583:





// PCF8583.C

#ifndef PCF8583_SDA
#define PCF8583_SDA  PIN_B0
#define PCF8583_SCL  PIN_B1
#endif

#use i2c(master, sda=PCF8583_SDA, scl=PCF8583_SCL)
//La configuración de ADDRESS es A0=0
#ifndef PCF8583_WRITE_ADDRESS
#define PCF8583_WRITE_ADDRESS 0xA0
#define PCF8583_READ_ADDRESS  0xA1
#endif

// Register addresses
#define PCF8583_CTRL_STATUS_REG    0x00
#define PCF8583_100S_REG           0x01
#define PCF8583_SECONDS_REG        0x02
#define PCF8583_MINUTES_REG        0x03
#define PCF8583_HOURS_REG          0x04
#define PCF8583_DATE_REG           0x05
#define PCF8583_MONTHS_REG         0x06
#define PCF8583_TIMER_REG          0x07

#define PCF8583_ALARM_CONTROL_REG  0x08
#define PCF8583_ALARM_100S_REG     0x09
#define PCF8583_ALARM_SECS_REG     0x0A
#define PCF8583_ALARM_MINS_REG     0x0B
#define PCF8583_ALARM_HOURS_REG    0x0C
#define PCF8583_ALARM_DATE_REG     0x0D
#define PCF8583_ALARM_MONTHS_REG   0x0E
#define PCF8583_ALARM_TIMER_REG    0x0F

// Use the first NVRAM address for the year byte.
#define PCF8583_YEAR_REG           0x10


// Commands for the Control/Status register.
#define PCF8583_START_COUNTING     0x00
#define PCF8583_STOP_COUNTING      0x80



char const weekday_names[7][10] =
{
{"Sun"},
{"Mon"},
{"Tues"},
{"Wednes"},
{"Thurs"},
{"Fri"},
{"Satur"}
};  
              
// This structure defines the user's date and time data.
// The values are stored as unsigned integers.  The user
// should declare a structure of this type in the application
// program. Then the address of the structure should be
// passed to the PCF8583 read/write functions in this
// driver, whenever you want to talk to the chip.
typedef struct
{
int8 seconds;    // 0 to 59
int8 minutes;    // 0 to 59
int8 hours;      // 0 to 23  (24-hour time)
int8 day;        // 1 to 31
int8 month;      // 1 to 12
int8 year;       // 00 to 99
int8 weekday;    // 0 = Sunday, 1 = Monday, etc.
}date_time_t;


//----------------------------------------------
void PCF8583_write_byte(int8 address, int8 data)
{
disable_interrupts(GLOBAL);
i2c_start();
i2c_write(PCF8583_WRITE_ADDRESS);
i2c_write(address);
i2c_write(data);
i2c_stop();
enable_interrupts(GLOBAL);
}  

//----------------------------------------------
int8 PCF8583_read_byte(int8 address)
{
int8 retval;

disable_interrupts(GLOBAL);
i2c_start();
i2c_write(PCF8583_WRITE_ADDRESS);
i2c_write(address);
i2c_start();
i2c_write(PCF8583_READ_ADDRESS);
retval = i2c_read(0);
i2c_stop();
enable_interrupts(GLOBAL);
return(retval);
}  


void PCF8583_init(void)
{
PCF8583_write_byte(PCF8583_CTRL_STATUS_REG,
                              PCF8583_START_COUNTING);
}  

//----------------------------------------------
// This function converts an 8 bit binary value
// to an 8 bit BCD value.
// The input range must be from 0 to 99.

int8 bin2bcd(int8 value)
{
char retval;

retval = 0;

while(1)
  {
   // Get the tens digit by doing multiple subtraction
   // of 10 from the binary value.
   if(value >= 10)
     {
      value -= 10;
      retval += 0x10;
     }
   else // Get the ones digit by adding the remainder.
     {
      retval += value;
      break;
     }
   }

return(retval);
}

//----------------------------------------------
// This function converts an 8 bit BCD value to
// an 8 bit binary value.
// The input range must be from 00 to 99.

char bcd2bin(char bcd_value)
{
char temp;

temp = bcd_value;

// Shifting the upper digit right by 1 is
// the same as multiplying it by 8.
temp >>= 1;

// Isolate the bits for the upper digit.
temp &= 0x78;

// Now return: (Tens * 8) + (Tens * 2) + Ones
return(temp + (temp >> 2) + (bcd_value & 0x0f));

}

//----------------------------------------------
void PCF8583_set_datetime(date_time_t *dt)
{
int8 bcd_sec;
int8 bcd_min;
int8 bcd_hrs;
int8 bcd_day;
int8 bcd_mon;

// Convert the input date/time into BCD values
// that are formatted for the PCF8583 registers.
bcd_sec = bin2bcd(dt->seconds);
bcd_min = bin2bcd(dt->minutes);
bcd_hrs = bin2bcd(dt->hours);  
bcd_day = bin2bcd(dt->day) | (dt->year << 6);
bcd_mon = bin2bcd(dt->month) | (dt->weekday << 5);

// Stop the RTC from counting, before we write to
// the date and time registers.
PCF8583_write_byte(PCF8583_CTRL_STATUS_REG,
                              PCF8583_STOP_COUNTING);

// Write to the date and time registers.  Disable interrupts
// so they can't disrupt the i2c operations.
disable_interrupts(GLOBAL);
i2c_start();
i2c_write(PCF8583_WRITE_ADDRESS);
i2c_write(PCF8583_100S_REG);   // Start at 100's reg.  
i2c_write(0x00);               // Set 100's reg = 0
i2c_write(bcd_sec);
i2c_write(bcd_min);
i2c_write(bcd_hrs);  
i2c_write(bcd_day);
i2c_write(bcd_mon);
i2c_stop();
enable_interrupts(GLOBAL);

// Write the year byte to the first NVRAM location.
// Leave it in binary format.
PCF8583_write_byte(PCF8583_YEAR_REG, dt->year);

// Now allow the PCF8583 to start counting again.
PCF8583_write_byte(PCF8583_CTRL_STATUS_REG,
                              PCF8583_START_COUNTING);
}

//----------------------------------------------
// Read the Date and Time from the hardware registers
// in the PCF8583.   We don't have to disable counting
// during read operations, because according to the data
// sheet, if any of the lower registers (1 to 7) is read,
// all of them are loaded into "capture" registers.
// All further reading within that cycle is done from
// those registers.

void PCF8583_read_datetime(date_time_t *dt)
{
int8 year_bits;
int8 year;

int8 bcd_sec;
int8 bcd_min;
int8 bcd_hrs;
int8 bcd_day;
int8 bcd_mon;

// Disable interrupts so the i2c process is not disrupted.
disable_interrupts(GLOBAL);

// Read the date/time registers inside the PCF8583.
i2c_start();
i2c_write(PCF8583_WRITE_ADDRESS);
i2c_write(PCF8583_SECONDS_REG);   // Start at seconds reg.
i2c_start();
i2c_write(PCF8583_READ_ADDRESS);

bcd_sec = i2c_read();    
bcd_min = i2c_read();    
bcd_hrs = i2c_read();
bcd_day = i2c_read();
bcd_mon = i2c_read(0);
i2c_stop();

enable_interrupts(GLOBAL);

// Convert the date/time values from BCD to
// unsigned 8-bit integers.  Unpack the bits
// in the PCF8583 registers where required.
dt->seconds = bcd2bin(bcd_sec);    
dt->minutes = bcd2bin(bcd_min);    
dt->hours   = bcd2bin(bcd_hrs & 0x3F);
dt->day     = bcd2bin(bcd_day & 0x3F);
dt->month   = bcd2bin(bcd_mon & 0x1F);
dt->weekday = bcd_mon >> 5;
year_bits   = bcd_day >> 6;  

// Read the year byte from NVRAM.
// This is an added feature of this driver.
year = PCF8583_read_byte(PCF8583_YEAR_REG);

// Check if the two "year bits" were incremented by
// the PCF8583.  If so, increment the 8-bit year
// byte (read from NVRAM) by the same amount.
while(year_bits != (year & 3))
      year++;

dt->year = year;

// Now update the year byte in the NVRAM
// inside the PCF8583.
PCF8583_write_byte(PCF8583_YEAR_REG, year);

}





Y en segundo lugar con el del LCD_flexible.c:
// flex_lcd.c

// These pins are for the Microchip PicDem2-Plus board,
// which is what I used to test the driver.  Change these
// pins to fit your own board.

#define LCD_DB4   PIN_D0
#define LCD_DB5   PIN_D1
#define LCD_DB6   PIN_D2
#define LCD_DB7   PIN_D3
//
#define LCD_RS    PIN_A3
#define LCD_RW    PIN_A2
#define LCD_E     PIN_A1

// If you only want a 6-pin interface to your LCD, then
// connect the R/W pin on the LCD to ground, and comment
// out the following line.

#define USE_LCD_RW   1

//========================================

#define lcd_type 2        // 0=5x7, 1=5x10, 2=2 lines
#define lcd_line_two 0x40 // LCD RAM address for the 2nd line


int8 const LCD_INIT_STRING[4] =
{
 0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8 dots
 0xc,                    // Display on
 1,                      // Clear display
 6                       // Increment cursor
 };


//-------------------------------------
void lcd_send_nibble(int8 nibble)
{

// Note:  !! converts an integer expression
// to a boolean (1 or 0).
 output_bit(LCD_DB4, !!(nibble & 1));
 output_bit(LCD_DB5, !!(nibble & 2));
 output_bit(LCD_DB6, !!(nibble & 4));
 output_bit(LCD_DB7, !!(nibble & 8));

 delay_cycles(1);
 output_high(LCD_E);
 delay_us(2);
 output_low(LCD_E);
}

//-----------------------------------
// This sub-routine is only called by lcd_read_byte().
// It's not a stand-alone routine.  For example, the
// R/W signal is set high by lcd_read_byte() before
// this routine is called.

#ifdef USE_LCD_RW
int8 lcd_read_nibble(void)
{
int8 retval;
// Create bit variables so that we can easily set
// individual bits in the retval variable.
#bit retval_0 = retval.0
#bit retval_1 = retval.1
#bit retval_2 = retval.2
#bit retval_3 = retval.3

retval = 0;

output_high(LCD_E);
delay_cycles(1);

retval_0 = input(LCD_DB4);
retval_1 = input(LCD_DB5);
retval_2 = input(LCD_DB6);
retval_3 = input(LCD_DB7);

output_low(LCD_E);

return(retval);
}
#endif

//---------------------------------------
// Read a byte from the LCD and return it.

#ifdef USE_LCD_RW
int8 lcd_read_byte(void)
{
int8 low;
int8 high;

output_high(LCD_RW);
delay_cycles(1);

high = lcd_read_nibble();

low = lcd_read_nibble();

return( (high<<4) | low);
}
#endif

//----------------------------------------
// Send a byte to the LCD.
void lcd_send_byte(int8 address, int8 n)
{
output_low(LCD_RS);

#ifdef USE_LCD_RW
while(bit_test(lcd_read_byte(),7)) ;
#else
delay_us(60);
#endif

if(address)
   output_high(LCD_RS);
else
   output_low(LCD_RS);

 delay_cycles(1);

#ifdef USE_LCD_RW
output_low(LCD_RW);
delay_cycles(1);
#endif

output_low(LCD_E);

lcd_send_nibble(n >> 4);
lcd_send_nibble(n & 0xf);
}

//----------------------------
void lcd_init(void)
{
int8 i;

output_low(LCD_RS);

#ifdef USE_LCD_RW
output_low(LCD_RW);
#endif

output_low(LCD_E);

delay_ms(15);

for(i=0 ;i < 3; i++)
   {
    lcd_send_nibble(0x03);
    delay_ms(5);
   }

lcd_send_nibble(0x02);

for(i=0; i < sizeof(LCD_INIT_STRING); i++)
   {
    lcd_send_byte(0, LCD_INIT_STRING[i]);

    // If the R/W signal is not used, then
    // the busy bit can't be polled.  One of
    // the init commands takes longer than
    // the hard-coded delay of 60 us, so in
    // that case, lets just do a 5 ms delay
    // after all four of them.
    #ifndef USE_LCD_RW
    delay_ms(5);
    #endif
   }

}

//----------------------------

void lcd_gotoxy(int8 x, int8 y)
{
int8 address;

if(y != 1)
   address = lcd_line_two;
else
   address=0;

address += x-1;
lcd_send_byte(0, 0x80 | address);
}

//-----------------------------
void lcd_putc(char c)
{
 switch(c)
   {
    case '\f':
      lcd_send_byte(0,1);
      delay_ms(2);
      break;

    case '\n':
       lcd_gotoxy(1,2);
       break;

    case '\b':
       lcd_send_byte(0,0x10);
       break;

    default:
       lcd_send_byte(1,c);
       break;
   }
}

//------------------------------
#ifdef USE_LCD_RW
char lcd_getc(int8 x, int8 y)
{
char value;

lcd_gotoxy(x,y);

// Wait until busy flag is low.
while(bit_test(lcd_read_byte(),7));

output_high(LCD_RS);
value = lcd_read_byte();
output_low(lcd_RS);

return(value);
}
#endif

void lcd_setcursor_vb(short visible, short blink) {
  lcd_send_byte(0, 0xC|(visible<<1)|blink);
}



Una vez tenemos la información de las librerías necesarias, será el momento de desarrollar el programa.



IMPORTANTE:
Estoy teniendo problemas para mostrar los "includes" del incio, al parecer cuando los meto, no aparecen en la página de blog, así que, aunque no es lo mismo, os diré que, en el primer include, meto la librería del PIC 18F4550, después en el segundo, meto la del LCD flexible y por último meto la librería del PCF8583. Creo que todo lo demás está bien.


AQUÍ EL PROGRAMA:



////////////////////////////////////////////////////////////////////////////////////
//   AUTOR: gabi allende                                    Noviembre/2011    //
////////////////////////////////////////////////////////////////////////////////////
//   PROGRAMA:    RAM_calendario                    VERSIÓN:    1.0
//   DISPOSITIVO: PIC 18F4550                           COMPILADOR:    CCS vs4.023
//   Entorno IDE: MPLAB ID v8.56                        SIMULADOR:    Proteus 7.7 sp2
//   TARJETA DE APLICACIÓN: PIC_CONTROL                 DEBUGGER:   ICD3 
////////////////////////////////////////////////////////////////////////////////////
//                                                                                //
////////////////////////////////////////////////////////////////////////////////////
//         Escribir y leer en la RAM del PCF8583 del 0 al 9 y del 9 al 0          //
////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////////
// CABECERA ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////


#include
//#FUSES INTHS    //reloj interno      
#FUSES HS     //reloj de alta velocidad
#FUSES MCLR    //Master clear pin enable
#use delay(internal=8Mhz)  //20MHz
//use delay(internal= 1Mhz)  //velocidad del oscilador interno
//use i2c (Master, sda= pin_B0, scl= pin_B1) Esto lo tiene la librería del PCF8583
#include
#include
//#byte ucfg= 0xF6F          //Para poder utilizar RC4 y RC5 como entrada


////////////////////////////////////////////////////////////////////////////////////
// VARIABLES GLOBALES //////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////


int8 i, dato;
int8 address= 0x10;


////////////////////////////////////////////////////////////////////////////////////
// FUNCIONES ///////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////


   


////////////////////////////////////////////////////////////////////////////////////
// PRINCIPAL ///////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
void main()
{
// bit_set(ucfg,3);             //rc4 u rc5 como entradas
// set_tris_c(0xf0); //configuramos el puerto c como entradas o salida. Esto se hace automatico pero aumenta los tiempos de ejcución.

// Escribimos en la RAM del 0 al 9 incrementando la dirección:
for(i=0; i<11; i++){
PCF8583_write_byte(address, i);
delay_ms(10);                       //retardo para dar tiempo a escribir la RAM
address++;
}


//   Leemos el contenido de la RAM incrementando su direccion:
    lcd_init();


    address= 0x0F;                   //Empieza a leer en el primera direccion que escribimos
    for(i=0; i<11; i++){
  dato= PCF8583_read_byte(address);
  lcd_gotoxy(i, 1);    
    printf(lcd_putc, "%u", dato);
    address++;
    }
   
    //decrementamos la dirrecion para mostrar el contenido alreves:
    for(i=0; i<11; i++){
  dato= PCF8583_read_byte(address);
      lcd_gotoxy(i, 2);
      printf(lcd_putc, "%u", dato);
      address--;
      }
  while(1); //Bucle de control del flujo
}


Y con esto ya debería mostrarnos en el LCD el flujo de números en orden ascendente y descendente.




El algoritmo en sí es muy sencillo, en primer lugar lo que hago es ir escribiendo en la ram, tan solo escribo en un sentido, ascendente. El proceso de escritura está basado en bucle de for, de tal manera que, cada vez que se llega a la función de "i2c_write", se suma en 1 el bucle y varía tanto de valor como de dirección "addres".
Una vez acabado el bucle pasamos a la lectura y muestra en lcd. Por tanto, lo primero que hacemos es leer el conjunto de datos anteriormente metidos en la ram en un orden ascendente, también con un bucle de for, después hacemos que se muestre ene l lcd.


Lo único que nos queda es crear un bucle prácticamente igual pero con una variabla decrementativa, por lo que va a ir escribiendo los números, pero en orden descendente.


Una vez hecho esto, ya tenemos la sucesión de números creada y mostrada en el lcd y por tanto, hemos acabado con la escritura y lectura de la RAM.


Aquí os dejo las imágenes de sus conexiones y el vídeo demostrativo.








Esto sería las conexiones del "expansion" de la RAM. Los dos de la izquierda son rojo (alimentación) y marrón (masa), mientras que los otros dos son las conexiones i2c, azul (sda) y verde(scl).








Esto es la conexión en el puerto de la picdem, concretamente la salida del sda y scl en el puerto C. IMPORTANTE, a mí me dio constantes problemas sacar estas dos líneas por el puerto B, no sñe exactamente por qué, yo creo que se debe a que la placa internamente, como tiene el puerto B relacionado con una serie de leds, estos a lo mejor generan una pequeña caída de tensión que nos da problemas. En el momento en el que cambié el puerte, el programa me funcionó perfectamente.









Y esto sería el programa en la placa y funcionando.













Y este es el vídeo demostrativo.





No hay comentarios:

Publicar un comentario