Laatst bijgewerkt: 30/07/2020
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.
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.
| DS3231 | Uno / Nano | Mego | Leonardo |
|---|---|---|---|
| SCL | A5 | 21 | 21 |
| SDA | A4 | 20 | 20 |
| VCC | 5V | 5V | 5V |
| GND | Gnd | Gnd | Gnd |
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:
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
}
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.
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
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.
| DS3231 | Uno / Nano | LCD Display |
|---|---|---|
| SCL | A5 | SCL |
| SDA | A4 | SDA |
| VCC | 5V | 5V |
| GND | Gnd | Gnd |
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;
}
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:
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
| 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.
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!
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
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. p>
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
Op de module is er ook een EEPROM AT24C32 chip van 32K om uw gegevens op te bewaren.
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.
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 |
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
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.
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.
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()
{
}
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.
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)