El DS3231 es un chip autónomo. Una vez que le configuras la hora, se encarga de incrementarla mecánicamente apoyado en su propia batería de respaldo (Coin Cell CR2032). Para hablar con él, debes dirigirte a su dirección estandarizada de fábrica: 0x68.
Una de las confusiones más grandes en el desarrollo Bare-Metal I2C es la dirección. El protocolo estipula que la dirección del esclavo se envía en los 7 bits más significativos del primer byte. El bit 0 (LSB) se reserva para indicar si la transacción es una ESCRITURA (0) o una LECTURA (1).
0x68 (En binario: 1101000)(0x68 << 1) | 0 = 0xD0 (En binario: 11010000)(0x68 << 1) | 1 = 0xD1 (En binario: 11010001)Los registros del DS3231 (como los minutos o los meses) no están en formato decimal o hexadecimal normal. Utilizan BCD. En BCD, un solo byte se divide en dos "nibbles" (4 bits y 4 bits). El nibble alto representa las decenas, y el bajo representa las unidades.
Si el reloj dice que son el minuto 45, el chip no te enviará el valor 0x2D (45 decimal). Te enviará el valor 0x45 (que en decimal normal equivaldría a 69). El nibble alto es `4` (0100) y el bajo es `5` (0101). Para usar estos números en C, necesitaremos macros de conversión.
Ejercicio 1: Crea el Driver C para comunicarse con el DS3231 a través del bus I2C1 (inicializado en el Día 123). Necesitaremos construir las macros de conversión BCD a Decimal, y ejecutar la secuencia I2C estándar para leer múltiples registros simultáneos (Segundos, Minutos, Horas, etc.).
/**
* @file rtc_ds3231.c
* @brief Driver I2C Bare-Metal para el Reloj de Tiempo Real DS3231
*/
#include "stm32f401xe.h"
// Direcciones del esclavo
#define DS3231_ADDR_WRITE 0xD0 // 0x68 << 1
#define DS3231_ADDR_READ 0xD1 // (0x68 << 1) | 1
// Macros matemáticas para formato BCD (Rápidas, sin divisiones complejas)
#define BCD_A_DEC(val) (((val >> 4) * 10) + (val & 0x0F))
#define DEC_A_BCD(val) (((val / 10) << 4) | (val % 10))
// Estructura para contener el tiempo legible
typedef struct {
uint8_t segundos;
uint8_t minutos;
uint8_t horas;
uint8_t dia_semana;
uint8_t dia_mes;
uint8_t mes;
uint8_t año; // Base 2000
} TiempoRTC;
/**
* @brief Función auxiliar para enviar START y Dirección
*/
void i2c_iniciar_transaccion(uint8_t direccion) {
// Generar START condition
I2C1->CR1 |= I2C_CR1_START;
while(!(I2C1->SR1 & I2C_SR1_SB)); // Esperar confirmación de Start
// Enviar Dirección del esclavo
I2C1->DR = direccion;
// Esperar a que el esclavo reconozca (ACK) la dirección
while(!(I2C1->SR1 & I2C_SR1_ADDR));
// IMPORTANTE EN STM32: Limpiar bandera ADDR leyendo SR1 y SR2
(void)I2C1->SR1;
(void)I2C1->SR2;
}
/**
* @brief Lee la fecha y hora completa desde el DS3231
*/
void leer_tiempo_ds3231(TiempoRTC* tiempo) {
uint8_t buffer_bcd[7];
// 1. INICIAR TRANSACCIÓN DE ESCRITURA PARA FIJAR EL PUNTERO DEL REGISTRO
i2c_iniciar_transaccion(DS3231_ADDR_WRITE);
// Enviar la dirección del registro '0x00' (Segundos)
while(!(I2C1->SR1 & I2C_SR1_TXE));
I2C1->DR = 0x00;
while(!(I2C1->SR1 & I2C_SR1_BTF)); // Esperar a que el byte termine de salir
// 2. REPEATED START PARA CAMBIAR A MODO LECTURA
i2c_iniciar_transaccion(DS3231_ADDR_READ);
// Activar generación de ACKs automáticos para los bytes entrantes
I2C1->CR1 |= I2C_CR1_ACK;
// 3. LEER LOS PRIMEROS 6 BYTES CON ACK
for (int i = 0; i < 6; i++) {
while(!(I2C1->SR1 & I2C_SR1_RXNE)); // Esperar a que llegue un byte
buffer_bcd[i] = I2C1->DR; // Leer byte
}
// 4. LEER EL ÚLTIMO BYTE (AÑO) Y FINALIZAR
// Para el último byte, el Maestro debe enviar un NACK (No-Acknowledge)
// y generar la condición de STOP, para que el Esclavo suelte el bus.
I2C1->CR1 &= ~I2C_CR1_ACK; // Desactivar ACK (Envía NACK)
I2C1->CR1 |= I2C_CR1_STOP; // Programar STOP
while(!(I2C1->SR1 & I2C_SR1_RXNE));
buffer_bcd[6] = I2C1->DR;
// 5. TRADUCIR EL BÚFER A LENGUAJE HUMANO (Conversión BCD)
tiempo->segundos = BCD_A_DEC(buffer_bcd[0]);
tiempo->minutos = BCD_A_DEC(buffer_bcd[1]);
tiempo->horas = BCD_A_DEC(buffer_bcd[2] & 0x3F); // Máscara modo 24h
tiempo->dia_semana = buffer_bcd[3]; // 1 al 7
tiempo->dia_mes = BCD_A_DEC(buffer_bcd[4]);
tiempo->mes = BCD_A_DEC(buffer_bcd[5] & 0x1F); // Enmascarar bit de siglo
tiempo->año = BCD_A_DEC(buffer_bcd[6]);
}
int main(void) {
// inicializar_i2c1(); // Del Día 123
TiempoRTC hora_actual;
while(1) {
// Cada vez que ocurre una venta, tomamos una fotografía temporal
leer_tiempo_ds3231(&hora_actual);
// Ejemplo: Si son las 15:45:00, hora_actual.minutos contendrá '45' entero.
// Ahora podemos adjuntar esta estructura a nuestro guardado en EEPROM.
// delay_ms_nobloqueante(1000);
}
return 0;
}
Objetivo del día: Trazabilidad Criptográfica y Auditoría Operativa.
Cuando un camión repartidor o una estación de carburación vende Gas LP, las regulaciones comerciales no solo exigen la cantidad en litros; exigen un Registro Temporal Irrefutable (Timestamp). Si un operador roba gas a las 3:00 AM, la memoria EEPROM debe registrar que la transacción fue realizada fuera del horario laboral. Si tu microcontrolador pierde energía y se reinicia, no puede perder la noción del tiempo, de lo contrario todo el archivo de auditoría pierde valor legal.
Al implementar el DS3231 por I2C, has dotado a tu sistema de una base de tiempo independiente. Gracias a la conversión BCD Bare-Metal, el STM32 extrae la fecha y la inyecta al struct RegistroVenta (del Día 120) en fracciones de milisegundo. Ahora, cada gota de fluido despachado queda sellada en la memoria con un estampado de tiempo de precisión militar, garantizando la trazabilidad perfecta para las inspecciones de la PROFECO.
0x68.(Dirección << 1) | R/W.STOP, para obligar al chip esclavo a desconectarse y liberar el cable SDA.