شاید تاکنون در انجام پروژههایتان به چالشی با عنوان سنجش زمان، تاریخ و دما برخورد کرده باشید، به طور مثال زمانسنجی مقدار عملکرد دستگاه، انجام یک عملکرد خاص طبق زمانبندی تعیین شده، چک کردن دما و گزارش هفتگی، ماهانه و سالیانه عملکرد دستگاه و…
اما مطمئنا اگر در پروژه هایتان با موارد بالا برخورد کرده باشید. حتما با سوالاتی همچون موارد زیر برخورد کرده اید.
- از چه آیسی ای باید استفاده کنیم؟ که قابلیت دریافت همزمان تاریخ، زمان و دما را داشته باشد؟
- چطور باید آن را راه اندازی کنیم؟
در این مقاله میخواهیم به این سوالات پاسخ دهیم و در نهایت ماژول معرفی شده را توسط برد آردوینو راهاندازی کنیم، در واقع یکی از بزرگترین ضعف های مهندسان الکترونیک، انتخاب قطعه مناسب برای انجام کار مورد نیاز است، که در این بخش میخواهیم با توجه به پروژه ای که تعریف کردهایم، قطعه موردنظر خود را انتخاب کنیم.
خب اگر دقت کنید در مقدمه مقاله گفتیم که نیاز به سنجش زمان، تاریخ و همینطور دما را داریم. برای این منظور از قطعه ای با نام DS3231 آشنا استفاده می کنیم. البته دقت کنید که آیسی DS1307 هم موجود میباشد که این آیسی دقت کمتری نسبت به DS3231 را دارد، پس از بررسی دیتاشیت ماژول DS3231، مواردی که مدنظر ما در این پروژه بود را از جمله موارد زیر را دارا میباشد.
- تنظیم سال
- تنظیم ماه
- تنظیم روز
- تنظیم روزهای هفته
- تنظیم ساعت
- تنظیم دقیقه
- تنظیم ثانیه
- داشتن یک سنسور داخلی که قابلیت اندازه گیری دما از -40 تا 85 درجه سانتیگراد را دارد.
خب پس از اینکه موفق به انتخاب آیسی موردنظر شدیم. باید آنرا راهاندازی کنیم.
نحوه راهاندازی ماژول DS3231
خب در این بخش قبل از راهاندازی نیاز به یک آشنایی جزئی با این ماژول را داریم. که این آشنایی جز با مراجعه به دیتاشیت بدست نخواهد آمد.
نحوه صحیح دیتاشیت خوانی چیست؟
یکی دیگر از مواردی که مهندسان الکترونیک در آن ضعف دارند، خواندن صحیح دیتاشیت قطعه موردنظر است که در این بخش ابتدا میخواهیم به صورت کاملا مختصر نحوه صحیح خواندن دیتاشیت را توضیح دهیم. و نکته ای که وجود دارد خیلی از دوستان فکر میکنند که حتما باید تسلط کامل به زبان انگلیسی داشته باشند تا بتوانند تسلط کافی را به دیتاشیت داشته باشند و در نهایت بتوانند قطعه موردنظر را راهاندازی کنند. خیر، برای راهاندازی قطعه موردنظر باید نکات کلیدی که نیاز است را از دیتاشیت جمعآوری کرد و در نهایت از آن نکات استفاده کرد البته از این نکته هم غافل نباشیم که تسلط به زبان انگلیسی بسیار میتواند در این روند کمک کننده محسوب شود. خب فرض کنید ما تا به حال حتی اسم این قطعه را هم نشنیده ایم و فقط تنها برنامهنویسی را میدانیم، حال باید برای راهاندازی، چه کاری را انجام دهیم. اولین کاری که باید انجام دهیم، این است که بدانیم آیسی موردنظر از چه پروتکلی پشتیبانی میکند. خب اگر به عکسی که در ادامه قرار دارد دقت کنید. این آیسی از پروتکل I2C جهت برقراری با ماژول استفاده میکند.
دومین مرحله یادگیری پروتکل I2C است که دقیقا چه عملیاتی را باید جهت برقراری ارتباط با میکروکنترلر انجام داد.
پروتکل I2C چیست؟
اگر به صورت کاملا مختصر توضیح دهیم، یک راه ارتباطی جهت انتقال دیتا بین میکروکنترلر و آیسی موردنظر است که نحوه عملکرد آن به صورت شکل زیر میباشد.
پروتکل I2C را با پین های زیر میشناسند.
- SDA (Serial Data) برای ارسال و دریافت داده بین master و slave
- SCL (Serial Clock) خطی که حامل سیگنال کلاک می باشد.
نحوه عملکرد پروتکل I2C
در I2C داده ها به صورت پیام هایی فرستاده میشود که خود این پیام ها خود به چند بخش تقسیم می شوند. هر پیام شامل یک بخش آدرس است، که همان آدرس باینری مربوط به slave موردنظر است. یک یا دو بخش مربوط به دیتا هم در پیام های ارسالی نیز وجود دارد. پیام همچنین شامل شرایط اولیه و پایانی(دستور شروع و پایان پروتکل)، بیت های read/write و بیت های ACK / NACK بین هربخش از پیام است.
حال پس از اینکه یک دید کلی نسبت به پروتکل پیدا کردیم نیاز است که دستورات ماژول DS3231 را مطالعه کنیم و ببینیم که هر دستور دقیقا چه کاربردی دارد و دقیقا کجا مورد استفاده قرار میگیرد.
خب پس از اینکه این دستورات را مورد مطالعه قرار دادیم نوبت به انتقال همین دستورات و انتقال دیتا توسط پروتکل I2C است. در واقع روندی که توضیح داده شد یک حالت کاملا کلی از خواندن دیتاشیت بود که در ادامه نحوه کدنویسی ماژول DS3231 را مورد مطالعه قرار میدهیم.
کدنویسی ماژول DS3231
در این بخش میخواهیم علاوه بر کد آردوینو ماژول، کتابخانه ای هم که از آن استفاده میکنیم مورد مطالعه قرار دهیم و از عملکرد آن مطلع شویم که اگر خودمان نیاز به نوشتن یک کتابخانه به صورت اصولی داشتیم باید چه کاری را انجام دهیم. و صرفا از کتابخانه های آماده استفاده نکنیم. در واقع یکی از مزیت هایی که میتواند بررسی کتابخانه ها داشته باشد. نحوه اصولی کدنویسی است که خود میتواند بسیار در کدنویسی موثر واقع شود. خب حالا نوبت به استفاده از این کتابخانه میرسد البته با این دید که میدانیم از چه توابعی استفاده شده و چطور مورد استفاده قرار گرفته شده است.
راهاندازی ماژول DS3231 با آردوینو
خب این بخش به دو قسمت شماتیک و کدنویسی تقسیم میشود.
شماتیک راهاندازی ماژول DS3231 با آردوینو
کدنویسی ماژول DS3231 با آردوینو
ابتدا باید کتابخانه مربوطه را نصب کنیم، که برای اینکار باید طبق شکلهایی که در ادامه میگذاریم عمل نمایید.
حال پس از نصب کتابخانه نوبت به نوشتن کد موردنظر برای راهاندازی ماژول موردنظر است که در این قسمت میخواهیم از مثال های آماده این کتابخانه استفاده کنیم. برای این کار باید به صورت زیر عمل کنیم.
خب همونطوری که میبینید این کتابخانه دارای 4 نمونه کد راهانداز است که میخواهیم کد NOW را امتحان و در نهایت روی سریال مانیتور اطلاعات را نمایش دهیم، در نهایت اگر تمامی مراحلی که توضیح دادیم به درستی انجام شده باشد باید خروجی سریال مانیتور مطابق اطلاعاتی که در ادامه قرار میدهیم، باشد.
DS3231 Arduino library
#include <Wire.h>
#include <avr/pgmspace.h>
#include "Sodaq_DS3231.h"
#include "Arduino.h"
#define EPOCH_TIME_OFF 946684800 // This is 2000-jan-01 00:00:00 in epoch time
#define SECONDS_PER_DAY 86400L
این خط ارسال دستور اماده باش به ماژول است که با دستور 0x68 شناخته میشود
#define DS3231_ADDRESS 0x68 //I2C Slave address
تعریف یک سری دستورات ماکرو که دقیقا از دیتاشیت آیسی انتخاب شده و کاملا مطابق
با دستورات دیتاشیت میباشد. که کاملا هم خوانا نوشته شده است.
نکته اول: در نوشتن کتابخانه همیشه منظم و خوانا عمل کنید.
#define DS3231_SEC_REG 0x00
#define DS3231_MIN_REG 0x01
#define DS3231_HOUR_REG 0x02
#define DS3231_WDAY_REG 0x03
#define DS3231_MDAY_REG 0x04
#define DS3231_MONTH_REG 0x05
#define DS3231_YEAR_REG 0x06
#define DS3231_AL1SEC_REG 0x07
#define DS3231_AL1MIN_REG 0x08
#define DS3231_AL1HOUR_REG 0x09
#define DS3231_AL1WDAY_REG 0x0A
#define DS3231_AL2MIN_REG 0x0B
#define DS3231_AL2HOUR_REG 0x0C
#define DS3231_AL2WDAY_REG 0x0D
#define DS3231_CONTROL_REG 0x0E
#define DS3231_STATUS_REG 0x0F
#define DS3231_AGING_OFFSET_REG 0x10
#define DS3231_TMP_UP_REG 0x11
#define DS3231_TMP_LOW_REG 0x12
////////////////////////////////////////////////////////////////////////////////
// utility code, some of this could be exposed in the DateTime API if needed
در این بخش هم به نحوه راهاندازی دستورات ماژول و همینطور نحوه محاسبه زمان و تاریخ و
همینطور محاسبه دما پرداخته شده است.
static const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 };
// number of days since 2000/01/01, valid for 2001..2099
static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) {
if (y >= 2000)
y -= 2000;
uint16_t days = d;
for (uint8_t i = 1; i < m; ++i)
days += pgm_read_byte(daysInMonth + i - 1);
if (m > 2 && y % 4 == 0)
++days;
return days + 365 * y + (y + 3) / 4 - 1;
}
static uint32_t time2long(uint16_t days, uint8_t h, uint8_t m, uint8_t s) {
return ((days * 24L + h) * 60 + m) * 60 + s;
}
static uint8_t conv2d(const char* p) {
uint8_t v = 0;
if ('0' <= *p && *p <= '9')
v = *p - '0';
return 10 * v + *++p - '0';
}
////////////////////////////////////////////////////////////////////////////////
// DateTime implementation - ignores time zones and DST changes
// NOTE: also ignores leap seconds, see http://en.wikipedia.org/wiki/Leap_second
DateTime::DateTime (long t) {
ss = t % 60;
t /= 60;
mm = t % 60;
t /= 60;
hh = t % 24;
uint16_t days = t / 24;
uint8_t leap;
for (yOff = 0; ; ++yOff) {
leap = yOff % 4 == 0;
if (days < 365 + leap)
break;
days -= 365 + leap;
}
for (m = 1; ; ++m) {
uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1);
if (leap && m == 2)
++daysPerMonth;
if (days < daysPerMonth)
break;
days -= daysPerMonth;
}
d = days + 1;
wday = 0; // FIXME This is not properly initialized
}
DateTime::DateTime (uint16_t year, uint8_t month, uint8_t date, uint8_t hour, uint8_t min, uint8_t sec, uint8_t wd) {
if (year >= 2000)
year -= 2000;
yOff = year;
m = month;
d = date;
hh = hour;
mm = min;
ss = sec;
wday = wd;
}
// A convenient constructor for using "the compiler's time":
// DateTime now (__DATE__, __TIME__);
// NOTE: using PSTR would further reduce the RAM footprint
DateTime::DateTime (const char* date, const char* time) {
// sample input: date = "Dec 26 2009", time = "12:34:56"
yOff = conv2d(date + 9);
// Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
switch (date[0]) {
case 'J': m = date[1] == 'a' ? 1 : date[2] == 'n' ? 6 : 7; break;
case 'F': m = 2; break;
case 'A': m = date[2] == 'r' ? 4 : 8; break;
case 'M': m = date[2] == 'r' ? 3 : 5; break;
case 'S': m = 9; break;
case 'O': m = 10; break;
case 'N': m = 11; break;
case 'D': m = 12; break;
}
d = conv2d(date + 4);
hh = conv2d(time);
mm = conv2d(time + 3);
ss = conv2d(time + 6);
wday = 0; // FIXME This is not properly initialized
}
uint32_t DateTime::get() const {
uint16_t days = date2days(yOff, m, d);
return time2long(days, hh, mm, ss);
}
uint32_t DateTime::getEpoch() const
{
return get() + EPOCH_TIME_OFF;
}
/*
* Format an integer as %0*d
*
* Arduino formatting sucks.
*/
static void add0Nd(String &str, uint16_t val, size_t width)
{
if (width >= 5 && val < 1000) {
str += '0';
}
if (width >= 4 && val < 100) {
str += '0';
}
if (width >= 3 && val < 100) {
str += '0';
}
if (width >= 2 && val < 10) {
str += '0';
}
str += val;
}
static inline void add04d(String &str, uint16_t val) { add0Nd(str, val, 4); }
static inline void add02d(String &str, uint16_t val) { add0Nd(str, val, 2); }
void DateTime::addToString(String & str) const
{
add04d(str, year());
str += '-';
add02d(str, month());
str += '-';
add02d(str, date());
str += ' ';
add02d(str, hour());
str += ':';
add02d(str, minute());
str += ':';
add02d(str, second());
}
static uint8_t bcd2bin (uint8_t val) { return val - 6 * (val >> 4); }
static uint8_t bin2bcd (uint8_t val) { return val + 6 * (val / 10); }
////////////////////////////////////////////////////////////////////////////////
// RTC DS3231 implementation
uint8_t Sodaq_DS3231::readRegister(uint8_t regaddress)
{
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write((byte)regaddress);
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDRESS, 1);
return Wire.read();
}
void Sodaq_DS3231::writeRegister(uint8_t regaddress,uint8_t value)
{
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write((byte)regaddress);
Wire.write((byte)value);
Wire.endTransmission();
}
uint8_t Sodaq_DS3231::begin(void) {
unsigned char ctReg=0;
Wire.begin();
ctReg |= 0b00011100;
writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address
delay(10);
// set the clock to 24hr format
uint8_t hrReg = readRegister(DS3231_HOUR_REG);
hrReg &= 0b10111111;
writeRegister(DS3231_HOUR_REG, hrReg);
delay(10);
return 1;
}
//set the time-date specified in DateTime format
//writing any non-existent time-data may interfere with normal operation of the RTC
void Sodaq_DS3231::setDateTime(const DateTime& dt) {
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write((byte)DS3231_SEC_REG); //beginning from SEC Register address
Wire.write((byte)bin2bcd(dt.second()));
Wire.write((byte)bin2bcd(dt.minute()));
Wire.write((byte)bin2bcd((dt.hour()) & 0b10111111)); //Make sure clock is still 24 Hour
Wire.write((byte)dt.dayOfWeek());
Wire.write((byte)bin2bcd(dt.date()));
Wire.write((byte)bin2bcd(dt.month()));
Wire.write((byte)bin2bcd(dt.year() - 2000));
Wire.endTransmission();
}
DateTime Sodaq_DS3231::makeDateTime(unsigned long t)
{
if (t < EPOCH_TIME_OFF)
return DateTime(0);
return DateTime(t - EPOCH_TIME_OFF);
}
// Set the RTC using timestamp (seconds since epoch)
void Sodaq_DS3231::setEpoch(uint32_t ts)
{
setDateTime(makeDateTime(ts));
}
//Read the current time-date and return it in DateTime format
DateTime Sodaq_DS3231::now() {
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write((byte)0x00);
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDRESS, 8);
uint8_t ss = bcd2bin(Wire.read());
uint8_t mm = bcd2bin(Wire.read());
uint8_t hrreg = Wire.read();
uint8_t hh = bcd2bin((hrreg & ~0b11000000)); //Ignore 24 Hour bit
uint8_t wd = Wire.read();
uint8_t d = bcd2bin(Wire.read());
uint8_t m = bcd2bin(Wire.read());
uint16_t y = bcd2bin(Wire.read()) + 2000;
return DateTime (y, m, d, hh, mm, ss, wd);
}
//Enable periodic interrupt at /INT pin. Supports only the level interrupt
//for consistency with other /INT interrupts. All interrupts works like single-shot counter
//Use refreshINTA() to re-enable interrupt.
void Sodaq_DS3231::enableInterrupts(uint8_t periodicity)
{
unsigned char ctReg=0;
ctReg |= 0b00011101;
writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address
switch(periodicity)
{
case EverySecond:
writeRegister(DS3231_AL1SEC_REG, 0b10000000 ); //set AM1
writeRegister(DS3231_AL1MIN_REG, 0b10000000 ); //set AM2
writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //set AM3
writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4
break;
case EveryMinute:
writeRegister(DS3231_AL1SEC_REG, 0b00000000 ); //Clr AM1
writeRegister(DS3231_AL1MIN_REG, 0b10000000 ); //set AM2
writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //set AM3
writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4
break;
case EveryHour:
writeRegister(DS3231_AL1SEC_REG, 0b00000000 ); //Clr AM1
writeRegister(DS3231_AL1MIN_REG, 0b00000000 ); //Clr AM2
writeRegister(DS3231_AL1HOUR_REG, 0b10000000 ); //Set AM3
writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4
break;
}
}
//Enable HH/MM/SS interrupt on /INTA pin. All interrupts works like single-shot counter
void Sodaq_DS3231::enableInterrupts(uint8_t hh24, uint8_t mm, uint8_t ss)
{
unsigned char ctReg=0;
ctReg |= 0b00011101;
writeRegister(DS3231_CONTROL_REG, ctReg); //CONTROL Register Address
writeRegister(DS3231_AL1SEC_REG, 0b00000000 | bin2bcd(ss) ); //Clr AM1
writeRegister(DS3231_AL1MIN_REG, 0b00000000 | bin2bcd(mm)); //Clr AM2
writeRegister(DS3231_AL1HOUR_REG, (0b00000000 | (bin2bcd(hh24) & 0b10111111))); //Clr AM3
writeRegister(DS3231_AL1WDAY_REG, 0b10000000 ); //set AM4
}
//Disable Interrupts. This is equivalent to begin() method.
void Sodaq_DS3231::disableInterrupts()
{
begin(); //Restore to initial value.
}
//Clears the interrrupt flag in status register.
//This is equivalent to preparing the DS3231 /INT pin to high for MCU to get ready for recognizing the next INT0 interrupt
void Sodaq_DS3231::clearINTStatus()
{
// Clear interrupt flag
uint8_t statusReg = readRegister(DS3231_STATUS_REG);
statusReg &= 0b11111110;
writeRegister(DS3231_STATUS_REG, statusReg);
}
//force temperature sampling and converting to registers. If this function is not used the temperature is sampled once 64 Sec.
void Sodaq_DS3231::convertTemperature()
{
// Set CONV
uint8_t ctReg = readRegister(DS3231_CONTROL_REG);
ctReg |= 0b00100000;
writeRegister(DS3231_CONTROL_REG,ctReg);
//wait until CONV is cleared. Indicates new temperature value is available in register.
do
{
//do nothing
} while ((readRegister(DS3231_CONTROL_REG) & 0b00100000) == 0b00100000 );
}
//Read the temperature value from the register and convert it into float (deg C)
float Sodaq_DS3231::getTemperature()
{
float fTemperatureCelsius;
uint8_t tUBYTE = readRegister(DS3231_TMP_UP_REG); //Two's complement form
uint8_t tLRBYTE = readRegister(DS3231_TMP_LOW_REG); //Fractional part
if(tUBYTE & 0b10000000) //check if -ve number
{
tUBYTE ^= 0b11111111;
tUBYTE += 0x1;
fTemperatureCelsius = tUBYTE + ((tLRBYTE >> 6) * 0.25);
fTemperatureCelsius = fTemperatureCelsius * -1;
}
else
{
fTemperatureCelsius = tUBYTE + ((tLRBYTE >> 6) * 0.25);
}
return (fTemperatureCelsius);
}
Sodaq_DS3231 rtc;
Sample Code
// Date and time functions using RX8025 RTC connected via I2C and Wire lib
#include <Wire.h>
#include "Sodaq_DS3231.h"
char weekDay[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
void setup ()
{
Serial.begin(57600);
Wire.begin();
rtc.begin();
}
uint32_t old_ts;
void loop ()
{
DateTime now = rtc.now(); //get the current date-time
uint32_t ts = now.getEpoch();
if (old_ts == 0 || old_ts != ts) {
old_ts = ts;
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.date(), DEC);
Serial.print(' ');
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.print(' ');
Serial.print(weekDay[now.dayOfWeek()]);
Serial.println();
Serial.print("Seconds since Unix Epoch: ");
Serial.print(ts, DEC);
Serial.println();
}
delay(1000);
}
Project Output
2002/2/5 7:12:58 Wed
Seconds since Unix Epoch: 1012893178
2002/2/5 7:12:59 Wed
Seconds since Unix Epoch: 1012893179
2002/2/5 7:13:0 Wed
Seconds since Unix Epoch: 1012893180
2002/2/5 7:13:1 Wed
Seconds since Unix Epoch: 1012893181
2002/2/5 7:13:2 Wed
Seconds since Unix Epoch: 1012893182
2002/2/5 7:13:3 Wed
Seconds since Unix Epoch: 1012893183
2002/2/5 7:13:4 Wed
Seconds since Unix Epoch: 1012893184
2002/2/5 7:13:5 Wed
Seconds since Unix Epoch: 1012893185
2002/2/5 7:13:6 Wed
Seconds since Unix Epoch: 1012893186
2002/2/5 7:13:7 Wed
Seconds since Unix Epoch: 1012893187
2002/2/5 7:13:8 Wed
Seconds since Unix Epoch: 1012893188
2002/2/5 7:13:9 Wed
Seconds since Unix Epoch: 1012893189
2002/2/5 7:13:10 Wed
Seconds since Unix Epoch: 1012893190
2002/2/5 7:13:11 Wed
Seconds since Unix Epoch: 1012893191
2002/2/5 7:13:12 Wed
منبع: وبسایت بعثت الکترونیک