En esta práctica vamos a escribir y leer en la memoria RAM del reloj calendario PCF8583 con el PIC 18F4550 mediante el Bus I2C.
El bus I2C fue diseñado por Philips en los años 80 para comunicar circuitos integrados.
Las líneas que utiliza son las siguientes:
-SCL (System Clock) es el reloj para sincronizar.
-SDA(System Data) línea por la que se transfieren los datos .
-GND(Masa) Referencia para todos los dispositivos interconectados.
SDA y SCL son líneas de drenador abierto y tenemos que conectar unas resistencias “Pull-Up”.
La máxima velocidad de transmisión es de aproximadamente 100Kbits por segundo.
El bus I2C está organizado de la siguiente manera.
-Maestro (Master): Es el único que aplica los pulsos de reloj en la línea SCL y determina la temporización y dirección del tráfico de datos del bus. Si se conectan varios maestros a un bus se denomina “multi-maestro”.
-Esclavo (Slave): Son los dispositivos conectados al bus incapaces de generar pulsos de reloj, reciben el reloj del maestro.
-Bus desocupado (Bus Free): En este estado SDA y SCL están inactivas, en este estado es cuando un maestro puede comenzar a utilizar el bus.
-Comienzo (Start): Es el momento en el cual un maestro ocupa el bus, SDA se pone en 0 y SCL permanece en 1.
-Parada (Stop): Es generada por un maestro cuando deja de utilizar el bus, SDA y SCL permanecen altos.
-Dato Válido (Valid Data):es el momento en el cual un dato en SDA es estable mientras la línea SCL está en 1.
-Formato de Datos (Data Format): La transmisión de datos por el bus es de 8 bits= 1 byte. Después de estos 8 bits hay un noveno bit durante el cual el dispositivo receptor genera un pulso de reconocimiento (ACK).
-Dirección (Address): Algunos dispositivos vienen con su propia dirección de acceso establecida por el fabricante, otros permiten que se modifique la dirección de acceso. Todos los dispositivos responden ante la dirección 00 por defecto.
-Lectura / Escritura (Bit R/W): El octavo bit, que es el menos significativo, se utiliza para decir si va a leer o escribir, si está en 1 el maestro lee información del esclavo , si es 0 el maestro escribe información en el esclavo.
Para programar el PIC utilizaremos el lenguaje C y nos ayudaremos de las librerías que trae PIC C Compiler en “C:\Program Files (x86)\PICC\Drivers“ si existe para nuestro disposivo .
En este caso la librería utilizada es la siguiente:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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] =
{
{"Luns"},
{"Marts"},
{"Miercs"},
{"Juevs"},
{"Vierns"},
{"Sabd"},
{"Dom"}
};
// 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);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
También utilizaremos otra librería para controlar fácilmente el LCD alfanumérico.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Y aquí finalmente el programa principal.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// AUTOR: José Antonio Pelayo NOVIEMBRE/2011
////////////////////////////////////////////////////////////////////////////////////
// PROGRAMA: ESTUDIO PCF 8583 (RELOJ-CALENDARIO-RAM VERSIÓN: 1.0
// DISPOSITIVO: PIC 18F4550 COMPILADOR: CCS vs4.109
// Entorno IDE: MPLAB SIMULADOR: Proteus 7.7 sp2
// TARJETA DE APLICACIÓN: ___ DEBUGGER: ICD3
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
//
// .-Grabar datos en RAM
//
// .-Leer RAM y visualizarlo
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// CABECERA ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
#include <c:\h\18F4550.h>
#fuses INTHS //Selecciona el oscilador interno
//#FUSES HS //External clock
#FUSES MCLR //Master Clear pin enabled
//#use delay(clock=20000000)
#use delay(internal=1000000hz) // Selecciona la velocidad del oscilador interno
//#use i2c(Master, sda=pin_B0, scl=pin_B1) ***esto lo tiene la librería del PCF8583***
#include <LCD_flexible_1.c>
#include <PCF8583.c>
//#byte ucfg= 0xf6f //Para poder utilizar RC4 y RC5 (en modo TTL, sólo pueden ser entradas)
////////////////////////////////////////////////////////////////////////////////////
// VARIABLES GLOBALES //////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
int dato[10];//AQUI INTRODUCIRE LOS VALORES LEIDOS EN LA RAM
int x=0;//VARIABLE PARA INCREMENTAR LA DIRECCION DE MEMORIA
////////////////////////////////////////////////////////////////////////////////////
// PRINCIPAL ///////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
void main()
{
PCF8583_init(); //INICIALIZO EL PCF8583
lcd_init();
PCF8583_write_byte(0x00,0x00);
delay_ms(60);
PCF8583_write_byte(0x0f,x);//FUNCION DE ESCRITURA EN LA RAM, TAMBIÉS LO PODRIAMOS HACER CON UN FOR
x++;
delay_ms(60);
PCF8583_write_byte(0x10,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x11,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x12,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x13,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x14,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x15,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x16,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x17,x);
x++;
delay_ms(60);
PCF8583_write_byte(0x18,x);
x++;
delay_ms(60);
dato[0]=PCF8583_read_byte(0x0f);//FUNCION DE LECTURA DE LA RAM, TAMBIÉN SE PODRIA HACER CON UN FOR
dato[1]=PCF8583_read_byte(0x10);
dato[2]=PCF8583_read_byte(0x11);
dato[3]=PCF8583_read_byte(0x12);
dato[4]=PCF8583_read_byte(0x13);
dato[5]=PCF8583_read_byte(0x14);
dato[6]=PCF8583_read_byte(0x15);
dato[7]=PCF8583_read_byte(0x16);
dato[8]=PCF8583_read_byte(0x17);
dato[9]=PCF8583_read_byte(0x18);
lcd_gotoxy(1,1);
printf(lcd_putc,"%d%d%d%d%d%d%d%d%d%d",dato[0],dato[1],dato[2],dato[3],dato[4],dato[5],dato[6],dato[7],dato[8],dato[9]);//Lo muestro en ascendente
lcd_gotoxy(1,2);
printf(lcd_putc,"%d%d%d%d%d%d%d%d%d%d",dato[9],dato[8],dato[7],dato[6],dato[5],dato[4],dato[3],dato[2],dato[1],dato[0]);//lo muestro en descendente
for(;;);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Una vez terminado el programa haremos la simulación en proteus.
Nos tiene que mostrar los números del 0 al 9 en ascendente y en descendente.
Y el funcionamiento real: