Arduino

Real Time Clock Module DS3231

Laatst bijgewerkt: 30/07/2020

Inleiding

DS3231 is een goedkope, extreem nauwkeurige I2C real time klok (RTC) met een geïntegreerde temperatuur gecompenseerde kristaloscillator (TCXO) en kristal. Het apparaat bevat een batterij die zorgt voor een nauwkeurige tijdregistratie wanneer de stroomtoevoer naar het apparaat wordt onderbroken. De module gebruikt een CR2032 batterij als backup die ongeveer 3 4 jaar zou moeten meegaan.

De DS3231 module bevat in feite de hoofdchip die de DS3231 is, twee pull up weerstanden van 4,7K voor de SCL en SDA pinnen, 2 pinnen voor INT/SQW. Er is ook een 24C32 EEPROM chip en enkele andere weerstanden die we niet gebruikt in dit project.

De DS3231 kan uren, minuten en seconden bijhouden, evenals dag, maand en jaarinformatie. Ook heeft het automatische compensatie voor schrikkeljaren en voor maanden met minder dan 31 dagen.

In deze module laten we zien wat de DS3231 RTC module is, hoe deze werkt en hoe je deze kunt gebruiken om een eenvoudig project te maken met een Arduino Uno.

Hardware

De RTC DS3231 Module

Specificatie

Datasheet

Hoe werkt het?

De realtime klok module DS3231 houdt de tijd bij, zelfs wanneer de module niet van stroom wordt voorzien. Het heeft een ingebouwde 3V batterij die de tijd continu bijhoudt. We zullen de tijd en datum van de RTCmodule verkrijgen met behulp van de bibliotheekfuncties en dan zullen we deze tijd vergelijken met de alarmtijd die we in de code hebben ingesteld.

De meeste RTC's maken gebruik van een extern 32kHz timingskristal dat wordt gebruikt om de tijd bij te houden met een laag stroom verbruik. En dat is allemaal goed en wel, maar die kristallen hebben een kleine afwijking, vooral als de temperatuur verandert (de temperatuur verandert de oscillatiefrequentie heel erg, maar het klopt wel!) Bij deze RTC zit het kristal in de behuizing van de chip! En direct naast het geïntegreerde kristal bevindt zich een temperatuursensor. Die sensor compenseert de frequentiewijzigingen door kloktikken toe te voegen of te verwijderen, zodat de tijdfunctie op schema blijft.

Aansluitpinnen

Voeding pinnen

I2C pinnen

Andere pinnen

4 Voorbeelden

Datum en tijd weergeven op de seriële monitor
Real time klok module, LCD scherm en controller voor de tijd
Real time klok met 2 alarmen en temperatuurmonitor van de DS3231
EEPROM Module AT24C32

Datum en tijd weergeven op de seriële monitor

Pinout van de module

DS3231 Uno / Nano Mego Leonardo
SCL A5 21 21
SDA A4 20 20
VCC 5V 5V 5V
GND Gnd Gnd Gnd

Programma code

Nadat bovenstaande bewerkingen zijn voltooid, verbindt u de Arduino met uw computer via de USB kabel. Open de Arduino IDE en kies het overeenkomstige bordtype en poorttype voor uw project.

Werken met de RTC vereist twee belangrijke stappen:

Stel de huidige tijd in de Real Time Clock in

Voor het instellen van de huidige tijd moet u de code van de schets wijzigen.

De parameters voor de functie zijn rood gemarkeerd: seconden, minuten, uren, dag van de week, datum, maand en jaar (in deze volgorde). Zondag is dag 1 van de week en zaterdag is 7.

Nadat u de huidige tijd hebt ingesteld, kunt u de opgegeven code uploaden met de vereiste wijzigingen.

De meegeleverde code is geschreven door John Boxall van tronixstuff. Je kunt zijn tutorial hier lezen.

#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68 // Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val){
 return( (val/10*16) + (val%10) );
}

byte bcdToDec(byte val){	// Convert binary coded decimal to normal decimal numbers
 return( (val/16*10) + (val%16) );
}
void setup(){
 Wire.begin();
 Serial.begin(9600);
 // set the initial time here:
 // DS3231 seconds, minutes, hours, day, date, month, year
 setDS3231time(30,42,16,5,13,10,16);
}
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year){
 // sets time and date data to DS3231
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.write(0); // set next input to start at the seconds register
 Wire.write(decToBcd(second)); // set seconds
 Wire.write(decToBcd(minute)); // set minutes
 Wire.write(decToBcd(hour)); // set hours
 Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
 Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
 Wire.write(decToBcd(month)); // set month
 Wire.write(decToBcd(year)); // set year (0 to 99)
 Wire.endTransmission();
}
void readDS3231time(byte *second,
 byte *minute,
 byte *hour,
 byte *dayOfWeek,
 byte *dayOfMonth,
 byte *month,
 byte *year){
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.write(0); // set DS3231 register pointer to 00h
 Wire.endTransmission();
 Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
 // request seven bytes of data from DS3231 starting from register 00h
 *second = bcdToDec(Wire.read() & 0x7f);
 *minute = bcdToDec(Wire.read());
 *hour = bcdToDec(Wire.read() & 0x3f);
 *dayOfWeek = bcdToDec(Wire.read());
 *dayOfMonth = bcdToDec(Wire.read());
 *month = bcdToDec(Wire.read());
 *year = bcdToDec(Wire.read());
}
void displayTime(){
 byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
 // retrieve data from DS3231
 readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
 &year);
 // send it to the serial monitor
 Serial.print(hour, DEC);
 // convert the byte variable to a decimal number when displayed
 Serial.print(":");
 if (minute<10){
 Serial.print("0");
 }
 Serial.print(minute, DEC);
 Serial.print(":");
 if (second<10){
 Serial.print("0");
 }
 Serial.print(second, DEC);
 Serial.print(" ");
 Serial.print(dayOfMonth, DEC);
 Serial.print("/");
 Serial.print(month, DEC);
 Serial.print("/");
 Serial.print(year, DEC);
 Serial.print(" Day of week: ");
 switch(dayOfWeek){
 case 1:
 Serial.println("Sunday");
 break;
 case 2:
 Serial.println("Monday");
 break;
 case 3:
 Serial.println("Tuesday");
 break;
 case 4:
 Serial.println("Wednesday");
 break;
 case 5:
 Serial.println("Thursday");
 break;
 case 6:
 Serial.println("Friday");
 break;
 case 7:
 Serial.println("Saturday");
 break;
 }
}
void loop(){
 displayTime(); // display the real-time clock data on the Serial Monitor,
 delay(1000); // every second
}

Bewaar de tijd in de Real Time Clock

Als u de tijd niet opnieuw wilt instellen telkens wanneer de RTC is uitgeschakeld, moet u het volgende doen:

Dit is een zeer belangrijke stap om de tijd in uw RTC in te stellen. Als u dit niet doet, wordt telkens wanneer uw RTC wordt gereset, de tijd weergegeven die u eerder hebt ingesteld en niet de huidige tijd.

Demonstratie

Open de seriële monitor met een baudrate van 9600 en u ziet de resultaten.

Hier is de seriële monitor met de huidige datum en tijd.

Top

Real time klok module, LCD scherm en controller voor de tijd

Dit voorbeeld laat zien hoe u de huidige tijd kunt ophalen van de controller en deze kunt plaatsen op een Real Time Clock (RTC) module die is aangesloten op uw Arduino. We hebben ook een LCD-scherm bevestigd met tijd en temperatuur [afkomstig van de interne temperatuursensor in de RTC-module].

In dit voorbeeld worden de externe bibliotheken van TimeLib, DS3232RTC en LiquidCrystal_I2C gebruikt, u kan ze hier vinden.

Installeer ze en start de Arduino IDE opnieuw voordat u probeert te compileren.

Pinout van de module

DS3231 Uno / Nano LCD Display
SCL A5 SCL
SDA A4 SDA
VCC 5V 5V
GND Gnd Gnd

Programma code

Nadat bovenstaande bewerkingen zijn voltooid, verbindt u de Arduino met uw computer via de USB kabel. Open de Arduino IDE en kies het overeenkomstige bordtype en poorttype voor uw project.

Kopieer onderstaande code naar je Arduino IDE, upload het dan naar je Arduino Uno.

#include <Wire.h> // For the i2c devices
#include <LiquidCrystal_I2C.h> // For the LCD
LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
#define DS3231_I2C_ADDRESS 104 // RTC is connected, address is Hex68 (Decimal 104)

// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.
//
byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];
byte tMSB, tLSB;
float my_temp;
char my_array[100]; // Character array for printing something.

void setup()
{
 Wire.begin();
 Serial.begin(9600);
 lcd.init(); // initialize the lcd 
}

void loop()
{
 watchConsole(); 
 get3231Date();
 Serial.print(weekDay); 
 Serial.print(", "); 
 Serial.print(date, DEC); 
 Serial.print("/"); 
 Serial.print(month, DEC);
 Serial.print("/"); 
 Serial.print(year, DEC); 
 Serial.print(" - ");
 Serial.print(hours, DEC);
 Serial.print(":"); 
 Serial.print(minutes, DEC);
 Serial.print(":"); 
 Serial.print(seconds, DEC);

 my_temp = (float)get3231Temp();
 
 Serial.print(" - Temp: "); 
 Serial.println(my_temp);
 
 // NOTE: Arduino does NOT implement printing floats to a string.
 // If you use the std C function : sprintf(my_array, "Temp: %4.2f", my_temp), It will NOT CONVERT.
 // So I abandoned this, since I don't need to print the float to the LCD anyway.
 
 sprintf(my_array, "Time: %d:%d:%d", hours, minutes, seconds);

// Print a message to the LCD.
 lcd.backlight();
 lcd.setCursor(0,0);
 lcd.print(weekDay); 
 lcd.print(", ");
 lcd.print(date, DEC); 
 lcd.print("/"); 
 lcd.print(month, DEC);
 lcd.print("/"); 
 lcd.print(year, DEC);
 lcd.setCursor(0,1);
 lcd.print(my_array);
 delay(1000);
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
 return ( (val/10*16) + (val%10) );
}

void watchConsole()
{
 if (Serial.available()) { // Look for char in serial queue and process if found
 if (Serial.read() == 84) { //If command = "T" Set Date
 set3231Date();
 get3231Date();
 Serial.println(" ");
 }
 }
}
 
void set3231Date()
{
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
// T1124154091014
 seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result. 
 minutes = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
 hours = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
 day = (byte) (Serial.read() - 48);
 date = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
 month = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
 year = (byte) ((Serial.read() - 48) *10 + (Serial.read() - 48));
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.write(0x00);
 Wire.write(decToBcd(seconds));
 Wire.write(decToBcd(minutes));
 Wire.write(decToBcd(hours));
 Wire.write(decToBcd(day));
 Wire.write(decToBcd(date));
 Wire.write(decToBcd(month));
 Wire.write(decToBcd(year));
 Wire.endTransmission();
}


void get3231Date()
{
 // send request to receive data starting at register 0
 Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
 Wire.write(0x00); // start at register 0
 Wire.endTransmission();
 Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

 if(Wire.available()) {
 seconds = Wire.read(); // get seconds
 minutes = Wire.read(); // get minutes
 hours = Wire.read(); // get hours
 day = Wire.read();
 date = Wire.read();
 month = Wire.read(); //temp month
 year = Wire.read();
 
 seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
 minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
 hours = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
 day = (day & B00000111); // 1-7
 date = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
 month = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
 year = (((year & B11110000)>>4)*10 + (year & B00001111));
 }
 else {
 //oh noes, no data!
 }
 
switch (day) {
 case 1:
 strcpy(weekDay, "Sun");
 break;
 case 2:
 strcpy(weekDay, "Mon");
 break;
 case 3:
 strcpy(weekDay, "Tue");
 break;
 case 4:
 strcpy(weekDay, "Wed");
 break;
 case 5:
 strcpy(weekDay, "Thu");
 break;
 case 6:
 strcpy(weekDay, "Fri");
 break;
 case 7:
 strcpy(weekDay, "Sat");
 break;
 }
}

float get3231Temp()
{
 float temp3231;
 
 //temp registers (11h-12h) get updated automatically every 64s
 Wire.beginTransmission(DS3231_I2C_ADDRESS);
 Wire.write(0x11);
 Wire.endTransmission();
 Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
 
if(Wire.available()) {
 tMSB = Wire.read(); //2's complement int portion
 tLSB = Wire.read(); //fraction portion
 
 temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
 temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
 }
 else {
 //oh noes, no data!
 }
 return temp3231;
}

Het resultaat

Enkele seconden nadat de upload is voltooid, kunnen we het resultaat zien zoals hieronder

Open de seriële monitor, u kunt ook hier de uitvoer zien:

Real time klok met 2 alarmen en temperatuurmonitor van de DS3231

De DS3231 RTC heeft 2 ingebouwde alarmfuncties en een temperatuursensor met een resolutie van 0,25 en een nauwkeurigheid van ± 3 ° C, wat dit project eenvoudiger maakt.

Hardware vereist

Pinout van de module

DS3231 Uno / Nano LCD Display
SCL A5 SCL
SDA A4 SDA
VCC 5V 5V
GND Gnd Gnd
INT pin 2  
  pin3 RS
  pin 4 E
  pin 5 D4
  pin 6 D5
  pin 7 D6
  pin 8 D7

Het DS3231 module wordt gevoed met 5V net als het LCD, deze 5V komt van de Arduino Uno, er zijn 3 Verbindingsdraden aangesloten tussen de module en de Arduino. De SCL pin is verbonden met pen A5, SDA is verbonden met analoge pen 4 en INT lijn is verbonden met digitale pin 2, de externe interrupt pin van de Arduino. De DS3231 onderbreekt de microcontroller wanneer er een alarm is (alarm1 of alarm2).

In het circuit zitten 3 drukknoppen: B1, B2 en B3. Deze knoppen worden gebruikt om tijd, kalender en alarmen in te stellen. Tijd en kalender kunnen worden aangepast met B1 en B2, knop B1 selecteert tijd- of datumparameter (tijdparameters: uren en minuten; kalenderparameters: dag van de week, datum, maand en jaar) en B2 verhoogt de geselecteerde parameter. De knoppen B3 en B2 passen alarm1 en alarm2 parameters (uren, minuten en AAN / UIT) aan, knop B3 selecteert de parameter en B2 verhoogt de geselecteerde parameter.

Er is ook een LED aangesloten op Arduino pin 12, deze LED wordt gebruikt als een alarmindicator (alarm1 of alarm2), dus als er een alarm is, trekt de DS3231 de INT pin naar beneden die de microcontroller onderbreekt en de microcontroller zet de LED AAN , hier zet knop B2 zowel de LED als het opgetreden alarm UIT.

Programma code

De Arduino code hieronder gebruikt geen enkele bibliotheek voor de DS3231.

Door het datasheet van de DS3231 RTC te lezen, zal de code eenvoudiger zijn!

Programmeer hints

De DS3231 werkt alleen met BCD indeling (behalve de temperatuur) en om de BCD naar decimaal te converteren en vice versa heb ik de volgende opdrachten gebruikt (voorbeeld voor minutenvariabele)

minute = (minute >> 4) * 10 + (minute  & 0x0F) // Convert BCD to decimal
minute = ((minute / 10) << 4) + (minute %  10);    // Convert  decimal to BCD

Code functions

void DS3231_read()

Deze functie leest tijd en kalendergegevens van de DS3231 seconden, minuten, uren, dag, datum, maand en jaar.

void DS3231_display()

Toont tijd en kalendergegevens, voordat de tijd en kalendergegevens worden weergegeven, worden deze geconverteerd van BCD indeling naar decimale indeling.

Deze functie geeft de kalender weer door een functie met de naam void calendar_display () aan te roepen

void alarms_read_display()

In principe leest deze functie alarm1 en alarm2 minuten en uren. Het leest ook het DS3231 besturingsregister, statusregister en temperatuurregisters (2 registers).

De andere taak van deze functie is om alarmgegevens (uren, minuten en status) en de temperatuurwaarde weer te geven. De alarmstatus wordt uit het controleregister gehaald.

byte edit(byte x, byte y, byte parameter)

Ik heb deze functie gebruikt om tijd, kalender en alarmparameters behalve de dag te bewerken. Ik heb een variabele met de naam i gebruikt om onderscheid te maken tussen de parameters:
i = 0, 1: uren en minuten
i = 2, 3, 4: kalenderdatum, maand en jaar
i = 5, 6: alarmen uren en minuten
i = 7: alarmstatus (AAN of UIT)
Na het bewerken van tijd / kalender / alarmen moeten de gegevens worden geconverteerd naar het BCDformaat en worden geschreven naar de DS3231.

De Arduino schakelt de LED AAN wanneer deze wordt onderbroken door de DS3231, de DS3231 zendt het interruptsignaal (trekt de INT-lijn naar beneden) wanneer er een alarm is geweest. Knop B2 reset en zet het alarm UIT. Als beide alarmen actief zijn, wordt knop B2 opnieuw ingesteld en wordt alleen het geregistreerde alarm uitgeschakeld en blijft de andere ongewijzigd. Om dat te doen, moeten we detecteren welk alarm is opgetreden, wat eenvoudig kan worden gedaan door het statusregister van de DS3231 (A1IF- en A2IF-vlagbits) te lezen. Het in- of uitschakelen van een alarm wordt gedaan door te schrijven naar het besturingsregister (bits: INTCN, A1IE en A2IE). Het INTCN-bit moet altijd 1. zijn. Ik heb de volgende regel gebruikt om 1 naar de INTCN-bit te schrijven en om het voorval alarm uit te schakelen:

Wire.write(4 | (!bit_test(status_reg, 0) &  alarm1_status) | ((!bit_test(status_reg, 1) & alarm2_status) << 1));

Alarm1_status en alarm2_status zijn booleaanse variabelen (kan waar of onwaar zijn), bijvoorbeeld als alarm1_status 1 == > alarm1 is AAN en als alarm1_status 0 == > alarm1 is UIT. Hetzelfde voor alarm2.

De volledige Arduino code staat hieronder.

/* Arduino real time clock and calendar with 2 alarm functions and  temperature monitor using DS3231
   Read DS3231 RTC datasheet to  understand the code
   Time & date parameters can  be set using two push buttons connected to pins 9 (B1) & 10 (B2).
   Alarm1 and alarm2 can be set  using two push buttons connected to 11 (B3) & 10 (B2).
   Pin 12 becomes high when alarm  occurred and button B2 returns it to low and
   turns the occurred alarm OFF.
   DS3231 interrupt pin is connected  to Arduino external interrupt pin 2.
*/
#include <LiquidCrystal.h>     // include LCD library code
#include <Wire.h>              // include Wire library code (needed for I2C protocol devices)
LiquidCrystal lcd(3, 4, 5, 6, 7, 8); // LCD module connections (RS, E, D4, D5, D6, D7)
const int button1 = 9;               // button1 pin number
const int button2 = 10;              // button1 pin number
const int button3 = 11;              // button1 pin number
const int alarm_pin = 12;            // Alarms pin number

void setup() {
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, OUTPUT);
  digitalWrite(alarm_pin, LOW);
  // set up the LCD's number of  columns and rows
  lcd.begin(20, 4);
  Wire.begin(); // Join i2c  bus
  attachInterrupt(digitalPinToInterrupt(2), Alarm, FALLING);
}
// Variables declaration
bool alarm1_status, alarm2_status;
char Time[] = " :  : ",
              calendar[] = " /  /20 ",
                           alarm1[] = "A1:  : :00", alarm2[] = "A2:  : :00",
                                      temperature[] = "T: .  C";
byte i, second, minute, hour,  day, date, month, year,
     alarm1_minute, alarm1_hour,  alarm2_minute, alarm2_hour,
     status_reg;

void Alarm() {
  digitalWrite(alarm_pin, HIGH);
}

void DS3231_read() { // Function to  read time & calendar data
  Wire.beginTransmission(0x68); // Start I2C protocol with  DS3231 address
  Wire.write(0); // Send  register address
  Wire.endTransmission(false); // I2C restart
  Wire.requestFrom(0x68, 7); // Request 7 bytes from  DS3231 and release I2C bus at end of reading
  second = Wire.read(); // Read seconds from  register 0
  minute = Wire.read(); // Read minuts from  register 1
  hour = Wire.read(); // Read hour from  register 2
  day = Wire.read(); // Read day from  register 3
  date = Wire.read(); // Read date from  register 4
  month = Wire.read(); // Read month from  register 5
  year = Wire.read(); // Read year from register 6
}

void alarms_read_display() { // Function to read and  display alarm1, alarm2 and temperature data
  byte control_reg,  temperature_lsb;
  char temperature_msb;
  Wire.beginTransmission(0x68); // Start I2C protocol with DS3231  address
  Wire.write(0x08); // Send register  address
  Wire.endTransmission(false); // I2C restart
  Wire.requestFrom(0x68, 11); // Request 11 bytes from  DS3231 and release I2C bus at end of reading
  alarm1_minute =  Wire.read(); // Read  alarm1 minutes
  alarm1_hour = Wire.read(); // Read alarm1 hours
  Wire.read(); // Skip  alarm1 day/date register
  alarm2_minute =  Wire.read(); // Read  alarm2 minutes
  alarm2_hour = Wire.read(); // Read alarm2 hours
  Wire.read(); // Skip  alarm2 day/date register
  control_reg = Wire.read(); // Read the DS3231 control  register
  status_reg = Wire.read(); // Read the DS3231 status  register
  Wire.read(); // Skip aging  offset register
  temperature_msb = Wire.read(); // Read temperature MSB
  temperature_lsb =  Wire.read(); // Read  temperature LSB
  // Convert BCD to decimal
  alarm1_minute = (alarm1_minute  >> 4) * 10 + (alarm1_minute & 0x0F);
  alarm1_hour = (alarm1_hour >> 4) * 10 + (alarm1_hour & 0x0F);
  alarm2_minute = (alarm2_minute  >> 4) * 10 + (alarm2_minute & 0x0F);
  alarm2_hour = (alarm2_hour >> 4) * 10 + (alarm2_hour & 0x0F);
  // End conversion
  alarm1[8] = alarm1_minute % 10 + 48;
  alarm1[7] = alarm1_minute / 10 + 48;
  alarm1[5] = alarm1_hour % 10  + 48;
  alarm1[4] = alarm1_hour / 10  + 48;
  alarm2[8] = alarm2_minute % 10 + 48;
  alarm2[7] = alarm2_minute / 10 + 48;
  alarm2[5] = alarm2_hour % 10  + 48;
  alarm2[4] = alarm2_hour / 10  + 48;
  alarm1_status =  bitRead(control_reg, 0); // Read  alarm1 interrupt enable bit (A1IE) from DS3231 control register
  alarm2_status =  bitRead(control_reg, 1); // Read  alarm2 interrupt enable bit (A2IE) from DS3231 control register
  if (temperature_msb < 0) {
    temperature_msb =  abs(temperature_msb);
    temperature[2] = '-';
  }
  else
    temperature[2] = ' ';
  temperature_lsb >>= 6;
  temperature[4] = temperature_msb  % 10 + 48;
  temperature[3] = temperature_msb  / 10 + 48;
  if (temperature_lsb == 0 ||  temperature_lsb == 2) {
    temperature[7] = '0';
    if (temperature_lsb == 0)  temperature[6] = '0';
    else temperature[6] = '5';
  }
  if (temperature_lsb == 1 ||  temperature_lsb == 3) {
    temperature[7] = '5';
    if (temperature_lsb == 1)  temperature[6] = '2';
    else temperature[6] = '7';
  }
  temperature[8] = 223; // Put the degree  symbol
  lcd.setCursor(10, 0);
  lcd.print(temperature); // Display temperature
  lcd.setCursor(0, 2);
  lcd.print(alarm1); // Display alarm1
  lcd.setCursor(17, 2);
  if (alarm1_status) lcd.print("ON "); // If A1IE = 1 print 'ON'
  else lcd.print("OFF"); // If A1IE = 0 print 'OFF'
  lcd.setCursor(0, 3);
  lcd.print(alarm2); // Display alarm2
  lcd.setCursor(17, 3);
  if (alarm2_status) lcd.print("ON "); // If A2IE = 1 print 'ON'
  else lcd.print("OFF"); // If A2IE = 0 print 'OFF'
}

void calendar_display() { // Function to display  calendar
  switch (day) {
    case 1: strcpy(calendar, "Sun /  /20 "); break;
    case 2: strcpy(calendar, "Mon /  /20 "); break;
    case 3: strcpy(calendar, "Tue /  /20 "); break;
    case 4: strcpy(calendar, "Wed /  /20 "); break;
    case 5: strcpy(calendar, "Thu /  /20 "); break;
    case 6: strcpy(calendar, "Fri /  /20 "); break;
    case 7: strcpy(calendar, "Sat /  /20 "); break;
    default: strcpy(calendar,  "Sat / /20  ");
  }
  calendar[13] = year % 10 + 48;
  calendar[12] = year / 10 + 48;
  calendar[8] = month % 10 + 48;
  calendar[7] = month / 10 + 48;
  calendar[5] = date  % 10 + 48;
  calendar[4] = date  / 10 + 48;
  lcd.setCursor(0, 1);
  lcd.print(calendar); // Display calendar
}

void DS3231_display() {
  // Convert BCD to decimal
  second = (second >> 4) *  10 + (second & 0x0F);
  minute = (minute >> 4) *  10 + (minute & 0x0F);
  hour = (hour >> 4) * 10 +  (hour & 0x0F);
  date = (date >> 4) * 10 +  (date & 0x0F);
  month = (month >> 4) * 10  + (month & 0x0F);
  year = (year >> 4) * 10 +  (year & 0x0F);
  // End conversion
  Time[7] = second % 10 + 48;
  Time[6] = second / 10 + 48;
  Time[4] = minute % 10 + 48;
  Time[3] =  minute / 10 + 48;
  Time[1] = hour  % 10 + 48;
  Time[0] = hour  / 10 + 48;
  calendar_display(); // Call calendar  display function
  lcd.setCursor(0, 0);
  lcd.print(Time); // Display time
}

void Blink() {
  byte j = 0;
  while (j < 10 &&  (digitalRead(button1) || i >= 5) && digitalRead(button2) &&  (digitalRead(button3) || i < 5)) {
    j++;
    delay(25);
  }
}

byte edit(byte x, byte y, byte parameter) {
  char text[3];
  while (!digitalRead(button1) ||  !digitalRead(button3)); // Wait until  button B1 is released
  while (true) {
    while (!digitalRead(button2)) { // If button B2 is  pressed
      parameter++;
      if (((i == 0) || (i == 5))  && parameter > 23) //  If hours > 23 ==> hours = 0
        parameter = 0;
      if (((i == 1) || (i == 6))  && parameter > 59) //  If minutes > 59 ==> minutes = 0
        parameter = 0;
      if (i == 2 &&  parameter > 31) // If date > 31 ==> date = 1
        parameter = 1;
      if (i == 3 &&  parameter > 12) // If month > 12 ==> month = 1
        parameter = 1;
      if (i == 4 &&  parameter > 99) // If year > 99 ==> year = 0
        parameter = 0;
      if (i == 7 &&  parameter > 1) // For alarms ON or OFF (1: alarm ON, 0: alarm OFF)
        parameter = 0;
      lcd.setCursor(x, y);
      if (i == 7) { //  For alarms ON & OFF
        if (parameter == 1) lcd.print("ON ");
        else lcd.print("OFF");
      }
      else {
        sprintf(text, "%02u", parameter);
        lcd.print(text);
      }
      if (i >= 5) {
        DS3231_read(); // Read data from  DS3231
        DS3231_display(); // Display DS3231 time  and calendar
      }
      delay(200); // Wait 200ms
    }
    lcd.setCursor(x, y);
    lcd.print(" "); // Print two spaces
    if (i == 7) lcd.print("  "); // Print space  (for alarms ON & OFF)
    Blink(); // Call  Blink function
    lcd.setCursor(x, y);
    if (i == 7) { // For alarms  ON & OFF
      if (parameter == 1) lcd.print("ON ");
      else lcd.print("OFF");
    }
    else {
      sprintf(text, "%02u", parameter);
      lcd.print(text);
    }
    Blink();
    if (i >= 5) {
      DS3231_read();
      DS3231_display();
    }
    if ((!digitalRead(button1)  && i < 5) || (!digitalRead(button3) && i >= 5)) {
      i++; //  Increment 'i' for the next parameter
      return parameter; // Return parameter  value and exit
    }
  }
}

void loop() {
  if (!digitalRead(button1)) { // If B1 button is pressed
    i = 0;
    hour = edit(0, 0, hour);
    minute = edit(3, 0, minute);
    while (!digitalRead(button1)); // Wait until button B1 released
    while (true) {
      while (!digitalRead(button2)) { // If button B2 button is pressed
        day++; // Increment day
        if (day > 7) day = 1;
        calendar_display(); // Call display_calendar  function
        lcd.setCursor(0, 1);
        lcd.print(calendar);  // Display calendar
        delay(200);
      }
      lcd.setCursor(0, 1);
      lcd.print(" "); // Print 3 spaces
      Blink();
      lcd.setCursor(0, 1);
      lcd.print(calendar); // Print calendar
      Blink(); // Call Blink function
      if (!digitalRead(button1)) // If button B1 is pressed
        break;
    }
    date = edit(4, 1,  date); // Edit date
    month = edit(7, 1,  month); // Edit month
    year = edit(12, 1,  year); // Edit year
    // Convert decimal to BCD
    minute = ((minute / 10)  << 4) + (minute % 10);
    hour = ((hour / 10) <<  4) + (hour % 10);
    date = ((date / 10) <<  4) + (date % 10);
    month = ((month / 10)  << 4) + (month % 10);
    year = ((year / 10) <<  4) + (year % 10);
    // End conversion
    // Write time & calendar  data to DS3231 RTC
    Wire.beginTransmission(0x68); // Start I2C protocol with DS3231  address
    Wire.write(0); // Send register  address
    Wire.write(0); // Reset sesonds  and start oscillator
    Wire.write(minute); // Write minute
    Wire.write(hour); // Write hour
    Wire.write(day); // Write day
    Wire.write(date); // Write date
    Wire.write(month); // Write month
    Wire.write(year); // Write year
    Wire.endTransmission(); // Stop transmission and  release the I2C bus
    delay(200);
  }
  if (!digitalRead(button3)) { // If B3 button is pressed
    while (!digitalRead(button3)); // Wait until button B3 released
    i = 5;
    alarm1_hour = edit(4,  2, alarm1_hour);
    alarm1_minute = edit(7, 2, alarm1_minute);
    alarm1_status = edit(17, 2,  alarm1_status);
    i = 5;
    alarm2_hour = edit(4,  3, alarm2_hour);
    alarm2_minute = edit(7, 3, alarm2_minute);
    alarm2_status = edit(17, 3,  alarm2_status);
    alarm1_minute =  ((alarm1_minute / 10) << 4) + (alarm1_minute % 10);
    alarm1_hour = ((alarm1_hour / 10) << 4) + (alarm1_hour % 10);
    alarm2_minute =  ((alarm2_minute / 10) << 4) + (alarm2_minute % 10);
    alarm2_hour = ((alarm2_hour / 10) << 4) + (alarm2_hour % 10);
    // Write alarms data to  DS3231
    Wire.beginTransmission(0x68); // Start I2C protocol with  DS3231 address
    Wire.write(7); // Send register  address (alarm1 seconds)
    Wire.write(0); // Write 0 to  alarm1 seconds
    Wire.write(alarm1_minute); // Write alarm1 minutes value  to DS3231
    Wire.write(alarm1_hour); // Write alarm1 hours value  to DS3231
    Wire.write(0x80); // Alarm1 when  hours, minutes, and seconds match
    Wire.write(alarm2_minute); // Write alarm2 minutes value  to DS3231
    Wire.write(alarm2_hour); // Write alarm2 hours value  to DS3231
    Wire.write(0x80); // Alarm2 when hours  and minutes match
    Wire.write(4 | alarm1_status  | (alarm2_status << 1)); //  Write data to DS3231 control register (enable interrupt when alarm)
    Wire.write(0); // Clear alarm  flag bits
    Wire.endTransmission(); // Stop transmission and  release the I2C bus
    delay(200); // Wait 200ms
  }
  if (!digitalRead(button2)  && digitalRead(alarm_pin)) { // When button B2 pressed with alarm (Reset and turn OFF the alarm)
    digitalWrite(alarm_pin,  LOW); // Turn OFF the alarm  indicator
    Wire.beginTransmission(0x68); // Start I2C protocol with  DS3231 address
    Wire.write(0x0E); // Send register  address (control register)
    // Write data to control  register (Turn OFF the occurred alarm and keep the other as it is)
    Wire.write(4 |  (!bitRead(status_reg, 0) & alarm1_status) | ((!bitRead(status_reg, 1) &  alarm2_status) << 1));
    Wire.write(0); // Clear alarm  flag bits
    Wire.endTransmission(); // Stop transmission and  release the I2C bus
  }
  DS3231_read(); // Read time  and calendar parameters from DS3231 RTC
  alarms_read_display(); // Read and display  alarms parameters
  DS3231_display(); // Display time  & calendar
  delay(50); // Wait  50ms
}// End of code

EEPROM Module AT24C32

Op de module is er ook een EEPROM AT24C32 chip van 32K om uw gegevens op te bewaren.

Kenmerken

Inleiding

EEPROM, of Electrically Erasable Programmable Read Only Memory, is een apparaat dat u in staat stelt om kleine hoeveelheden gegevens op te slaan en later op te halen, zelfs als het apparaat is uitgezet. Veel moderne microcontrollers zoals de ATmega328 bevatten een aantal ingebouwde EEPROM, maar dat betekent niet dat je er niet meer aan kunt toevoegen!

Seriële EEPROM apparaten zoals de Microchip 24 serie EEPROM stellen u in staat meer geheugen toe te voegen aan elk apparaat dat via I²C kan verbinden. Vandaag gaan we leren hoe we seriële EEPROM apparaten met behulp van Arduino kunnen lezen en schrijven.

Er zijn twee belangrijke nadelen aan EEPROM als een methode voor gegevensopslag. In de meeste toepassingen wegen de voordelen op tegen de nadelen, maar u moet op de hoogte zijn voordat u de EEPROM in uw volgende ontwerp verwerkt.

Allereerst beperkt de technologie die EEPROM laat werken ook het aantal keren dat het kan worden herschreven. Dit heeft te maken met het vastraken van elektronen in de transistors die de ROM vormen en opbouwen totdat het ladingsverschil tussen een "1" en een "0" onherkenbaar is. Maar maak je geen zorgen, de meeste EEPROM's hebben een maximaal herschrijfnummer van 1 miljoen of meer.

Zolang u niet voortdurend naar de EERPROM schrijft, is het onwaarschijnlijk dat u dit maximum haalt.

Ten tweede wordt EEPROM niet gewist als u de voeding ervan uitschakelt, maar het houdt uw gegevens niet voor onbepaalde tijd vast. Elektronen kunnen uit de transistors en door de isolator drijven, waardoor de EEPROM na verloop van tijd effectief wordt gewist.

Dat gezegd hebbende, gebeurt dit meestal in de loop van jaren (hoewel het kan worden versneld door hitte). De meeste fabrikanten zeggen dat uw gegevens veilig zijn op EEPROM gedurende 10 jaar of langer bij kamertemperatuur. En er is nog iets dat u in gedachten moet houden bij het selecteren van een EEPROM apparaat voor uw project. De EEPROM capaciteit wordt gemeten in bits en niet in bytes. Een 32K EEPROM kan 32Kbits aan gegevens bevatten, met andere woorden slechts 4KB.

Pinout van de module

Oké, nu we weten wat EEPROM is, laten we er een aansluiten en zien wat het kan doen! We gebruiken de opstelling van het eerste voorbeeld.

DS3231 Uno / Nano Mego Leonardo
SCL A5 21 21
SDA A4 20 20
VCC 5V 5V 5V
GND Gnd Gnd Gnd

AT24C32 device addressing

In tegenstelling tot de RTC die een bedraad adres heeft, heeft de AT24C32 3 pinnen waarmee het adres door de gebruiker kan worden ingesteld, zodat meerdere AT24C32 / 64's op de I2C bus kunnen worden geïnstalleerd. Omdat er 3 adresingangen (A0, A1 en A2) zijn, die 2 toestanden kunnen aannemen, ofwel een hoge of een lage, zijn er daarom 8 verschillende combinaties van de toestandsingangen, dus 8 verschillende adressen kunnen worden gebruikt . Het standaard adres is 0x57

Geheugen Adressen

Als je je alle bytes in een 32 Kbit EEPROM voorstelt die in een lijn van 0 tot 4096 staan - omdat er 8 bits nodig zijn voor een byte en je dus 4096 bytes op een 32 Kbit EEPROM kunt passen - dan is een geheugenadres de plaats in regel waar je een bepaalde byte zou vinden. We moeten dat adres naar de EEPROM verzenden, zodat het weet waar de byte die we verzenden kan worden geplaatst.

Most Significant(MSB) en Least Significant(LSB) Bytes

Omdat er 4096 mogelijke plaatsen in een 32 Kbit EEPROM zijn en omdat 255 het grootste aantal is dat je in één byte kunt coderen moeten we dit adres in twee bytes verzenden. Eerst sturen we de Most Significant Byte (MSB) de eerste 8 bits in dit geval. Vervolgens sturen we de minst significante byte (LSB) de tweede 8 bits. Waarom? Omdat dit is hoe het apparaat ze verwacht te ontvangen, dat is alles.

Gegevens lezen en schrijven

Als u naar de AT24C32 wilt schrijven, moet u alleen het adres selecteren en een byte verzenden. Het adres is in twee bytes (niet alle 16 bits worden gebruikt, zie 12-bits registeradressen) omdat er 4096 mogelijke adressen zijn, wat meer is dan één byte (255) kan bevatten. Het converteren van een numerieke waarde naar deze twee bytes is een beetje moeilijk en ik zal het in het volgende voorbeeld uitleggen. Voorlopig schrijven we alleen naar het eerste mogelijke adres.

#include <Wire.h>
#define address 0x57

int val = 100;
byte data;
void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Wire.beginTransmission(address);
 Wire.write(0x00);      //First Word Address
 Wire.write(0x00);      //Second Word Address

 Wire.write(0x41);      //Write an 'A'

 delay(10);

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Wire.beginTransmission(address);
 Wire.write(0x00);      //First Word Address
 Wire.write(0x00);      //Second Word Address
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(address, 1);
 delay(10);
 data = Wire.read();
 Serial.write(data);      //Read the data and print to Serial port
 Serial.println();
 delay(10);
}

void loop()
{
}

Sequentieel lezen en semi-sequentieel schrijven

Zowel lezen als schrijven naar de EEPROM zijn sequentiel. Dit betekend dat u bytes uit het register kunt lezen zonder de adreswijzer te hoeven resetten, maar schrijven is slechts sequentieel tot de limiet van 32 bytes (een pagina-schrijven genoemd). De volgende schets schrijft het alfabet naar de eerste 26 posities van de EEPROM.

#include <Wire.h>
#define address 0x57

const int val = 26;
byte data;

void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Serial.println("Writing to EEPROM:");
 Wire.beginTransmission(address);
 Wire.write(0x00);
 Wire.write(0x00);

 for(byte i=0; i<val; i++)      //Write 26 data bytes
 {
   Wire.write(i+65);    
   Serial.write(i+65);
   Serial.println();
 }
 delay(10);
 Serial.println();

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Serial.println("Reading from EEPROM:");
 Wire.beginTransmission(address);
 Wire.write(0x00);
 Wire.write(0x00);
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(address, val);
 delay(10);

 for(byte i=0; i<val; i++)      //Read 26 data bytes
 {
   data = Wire.read();
   Serial.write(data);
   Serial.println();
 }
 delay(10);
}

void loop()
{
}

Probeer de waarde van 'val' aan het begin van de schets te veranderen, en je zult zien dat je alleen 30 bytes in de chip kunt schrijven (de andere 2 bytes zijn adressen) zonder dat er afval (ÿ) terugkomt als je het leest.

12 bit register adressen

Aangezien er 4096 geheugenlocaties zijn, zijn er 12 bits adressen en kunt u niet simpelweg 1 byte als een adres opgeven, maar moet u er twee verzenden, waarbij het adres wordt gesplitst over de 4 minst significante bits van het 1e woordadres en alle 8 bits van het 2e woord adres. Om complicaties te voorkomen, begon ik alleen met lezen / schrijven naar het eerste mogelijke adres (0) en eventuele volgende door sequentiële lees- / schrijfbewerkingen. Ik kreeg toen het probleem van het converteren van een int-adres naar een 12-bits nummer.

We hebben een adres, elk nummer tussen 0 en 4095 inclusief. In binair getal is dit (0000) abcd efgh ijkl. Om het eerste woordadres te krijgen, verplaatsen we het hele getal naar rechts 8 bits, dus wordt het 0000 abcd. Dan, bitverschuiving van het nummer terug 8 bits wordt het (0000) abcd 0000 0000. Als we dan dit afgeleide nummer (dat is gewoon het 1e woord adres met 8 0's geplakt op) aftrekken van het originele adres ((0000) abcd efgh ijkl ), kunnen we de onderste byte van het adres halen, (oooo oooo) efgh ijkl. Om het geheel begrijpelijker te maken, heb ik nog een voorbeeldtekening gemaakt om de functionaliteit hiervan te laten zien.

void setup() 
{
  Serial.begin(9600);
  word address = 4096;
  byte BYTE_1 = address >> 8;
  byte BYTE_2 = address - (BYTE_1 << 8);

  Serial.print("Address is: ");
  Serial.println(address);
  Serial.print("1st word is: ");
  Serial.println(BYTE_1);
  Serial.print("2nd word is: ");
  Serial.println(BYTE_2);

}

void loop() 
{

}

Ik heb functies gemaakt om schetsen te vereenvoudigen die deze moeten gebruiken:

byte highAddressByte(word address)
{
  byte BYTE_1;
  BYTE_1 = address >> 8;
  return BYTE_1;
}

byte lowAddressByte(word address)
{
  byte BYTE_1;
  byte BYTE_2;
  BYTE_1 = address >> 8;
  BYTE_2 = address - (BYTE_1 << 8);
  return BYTE_2;
}

Deze kunnen eenvoudig in de eerste schets worden geïntegreerd

#include <Wire.h>
#define AT24C32 0x50

byte data;
word a = 0;

byte highAddressByte(word address)
{
  byte BYTE_1;
  BYTE_1 = address >> 8;
  return BYTE_1;
}

byte lowAddressByte(word address)
{
  byte BYTE_1;
  byte BYTE_2;
  BYTE_1 = address >> 8;
  BYTE_2 = address - (BYTE_1 << 8);
  return BYTE_2;
}

void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Wire.beginTransmission(AT24C32);

 Wire.write(highAddressByte(a));      //First Word Address
 Wire.write(lowAddressByte(a));      //Second Word Address

 Wire.write(0x41);      //Write an 'A'

 delay(10);

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Wire.beginTransmission(AT24C32);
 Wire.write(highAddressByte(a));      //First Word Address
 Wire.write(lowAddressByte(a));      //Second Word Address
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(AT24C32, 1);
 delay(10);
 data = Wire.read();
 Serial.write(data);      //Read the data and print to Serial port
 Serial.println();
 delay(10);
}

void loop()
{
}

U kunt deze functies ook verder integreren in andere functies, bijvoorbeeld. AT24C32.Schrijf / lees (adres, locatie, gegevens)