Trik yang saya pelajari tentang menggunakan Jam Waktu Nyata DS3231 untuk menginterupsi Arduino
Anda ingin menentukan waktu yang tepat kapan Arduino akan menjalankan segmen kode khusus dalam sketsa, tanpa menggunakan timer dan counter di perangkat keras Arduino. Anda memiliki keahlian dan pengalaman menulis kode dengan Arduino IDE, dan Anda ingin menghindari penggunaan pernyataan kode seperti delay()
.
Sebagai gantinya, Anda ingin menghubungkan modul Jam Waktu Nyata DS3231 yang sangat tepat sebagai sumber interupsi eksternal. Mungkin penting bagi Anda bahwa DS3231 dapat menggunakan baterai untuk menjaga waktu yang akurat meskipun Arduino kehilangan daya untuk sementara.
Terakhir, Anda ingin mempelajari cara menggunakan perpustakaan DS3231.h oleh Andrew Wickert yang dirujuk dalam referensi online Arduino: https://www.arduino.cc/reference/en/libraries/ds3231/. Ini berisi beberapa trik yang terbukti layak untuk dikuasai. Anda dapat mengimpor perpustakaan ini ke Arduino IDE Anda dengan menggunakan Manajer Perpustakaan (Alat > Kelola Perpustakaan...).
Lakukan selangkah demi selangkah. Tutorial ini menunjukkan langkah-langkah berikut:
Jika Anda ingin kode khusus dijalankan lebih dari sekali, pada interval yang Anda tentukan, kode Anda dapat melakukan Langkah 3 lagi dan menyetel alarm baru. Contoh sketsa yang menyertai tutorial ini menginterupsi Arduino berulang kali, dengan interval 10 detik.
Tutorial ini diambil dari referensi “resmi”, antara lain:
attachInterrupt()
: https://www.arduino.cc/reference/en/bahasa/functions/external-interrupts/attachinterrupt/. Kode khusus dalam contoh ini sepele: hanya mencetak waktu saat ini dari DS3231. Contoh kehidupan nyata mungkin dapat melakukan sesuatu yang berguna seperti menyiram tanaman atau mencatat pengukuran suhu. Apa pun tugasnya, kode tersebut harus masuk ke blok khusus miliknya sendiri untuk dijalankan hanya ketika alarm DS3231 mengganggu Arduino.
Saya percaya ini adalah praktik yang baik untuk memecah kode menjadi fungsi-fungsi pendek, di mana setiap fungsi hanya menangani satu tugas atau satu set tugas terkait. Nama suatu fungsi bisa berupa apa saja; mengapa tidak menjelaskan fungsinya? Inilah bagian dari fungsi saya yang menjalankan kode khusus dalam contoh ini.
void runTheSpecialCode() {
// get the current time
// using the DateTime and RTClib classes
// defined in DS3231.h
DateTime dt = RTClib::now();
// print the current time
Serial.print(dt.hour()); Serial.print(":");
if (dt.minute() < 10) Serial.print("0");
Serial.print(dt.minute()); Serial.print(":");
if (dt.second() < 10) Serial.print("0");
Serial.println(dt.second());
// There will be more to do here, as you will see.
// This is enough, for now, to illustrate the idea:
// put special code in its own, special function
}
Anda akan memasang kabel di antara lima pasang pin. Setiap pasangan melakukan satu tujuan kelistrikan dan mencocokkan pin di Arduino dengan pin yang sesuai di DS3231. Ambillah secara perlahan, sambungkan masing-masing pasangan, lalu periksa kedua ujungnya untuk memastikan setiap kabel berada di tempat yang seharusnya. Tabel mencantumkan pasangan secara berurutan saat dipasang, dari kiri ke kanan, ke DS3231 dari Arduino Uno.
Tujuan | DS3231 Pin | Pin Arduino |
---|---|---|
Alarm | persegi | 3* |
SCL | SCL | SCL** |
SDA | SDA | SDA** |
daya 5volt | VCC | 5V |
Tanah | GND | GND |
Seperti saya katakan, luangkan waktu Anda untuk membuat hubungan ini. Lambat dan pasti seringkali merupakan cara tercepat untuk menyelesaikan sesuatu dengan benar.
Kita akan berbicara dengan modul DS3231 melalui "objek" DS3231, semacam kotak peralatan perangkat lunak dengan nama di atasnya. Pustaka DS3231 mendefinisikan banyak fungsi -- anggap saja sebagai alat -- di dalam kotaknya. Ketika kita ingin menggunakan suatu fungsi, kita tuliskan nama toolboxnya, diikuti titik, lalu nama toolnya. Contoh sketsa membuat variabel "jam" untuk tujuan ini. Kemudian sketsa dapat mengakses alat-alat di kotak "jam". Semua alat dideklarasikan dalam file DS3231.h yang disebutkan di atas.
#include <DS3231.h>
DS3231 clock;
Serial.println(clock.getMinute()); // the current minute, 0..59
Kita akan menggunakan alat di objek "jam" kita untuk menyetel alarm. Tapi pertama-tama kita perlu menghitung waktu alarm.
Langkah ini mengasumsikan bahwa Anda sebelumnya telah menyetel waktu sebenarnya pada DS3231. Contoh sketsa berisi kode yang dapat Anda gunakan untuk mengatur waktu pada jam, jika Anda membutuhkannya. Cukup hapus pembatas komentar, /* dan */, yang mengelilinginya.
Contoh sketsa dalam tutorial ini menghitung waktu alarm di masa depan dengan menambahkan interval, dalam jumlah detik, ke waktu saat ini. Contohnya menambahkan sepuluh detik. Satu menit akan menambah 60 detik. Satu jam akan menambah 3.600 detik. Sehari, 86.400 detik. Dan sebagainya.
Pustaka DS3231 memiliki "trik" tersembunyi yang memudahkan untuk menambahkan waktu dalam beberapa detik. Anda tidak akan menemukan trik ini tercantum di antara fungsi-fungsi yang tersedia di halaman README perpustakaan DS3231. Hal ini juga tidak sepenuhnya jelas dengan melihat file DS3231.h. Beberapa detailnya menunggu untuk ditemukan di file kode DS3231.cpp. Berikut langkah-langkah melakukan perhitungannya.
now()
, yang perlu diakses dengan cara khusus.Langkah 4 dan 5 dapat digabungkan.
const Uint32_t interval = 10; // number of seconds to add
DateTime currentTime; // default declaration
currentTime = RTClib::now(); // RTClib is defined in DS3231.h
uint32_t currentSeconds = currentTime.unixtime(); // express the date in seconds
DateTime alarmTime(currentSeconds + interval); // add 10 seconds and create a new date
Meskipun objek alarmTime dibuat berdasarkan jumlah detik, ia menyediakan alat di kotak peralatannya untuk menyatakan tahun, bulan, hari, jam, menit, dan detik. Kita mengatur waktu alarm pada DS3231, seperti dijelaskan selanjutnya, menggunakan objek alarmTime sebagai sumber nilai yang kita perlukan.
Misalkan, misalnya, Waktu saat ini yang dilaporkan oleh modul DS3231 adalah pukul 10:42 lewat 7 detik pada hari Rabu, 27 Oktober 2021. Waktu alarm yang dihitung di atas adalah pukul 10:42:17 pada hari yang sama, sepuluh detik kemudian.
DS3231 menyediakan dua alarm berbeda: Alarm #1 (A1) dan Alarm #2 (A2). Kedua alarm dapat ditentukan menjadi hari dan waktu, hingga satu menit. Perbedaannya adalah A1 dapat dispesifikasikan lebih lanjut hingga satu detik. Setiap alarm memiliki sepasang fungsi tersendiri di perpustakaan DS3231 untuk mengatur waktu alarm dan membaca waktu tersebut. Semua fungsi diakses melalui objek DS3231, misalnya, yang kami beri nama, "jam":
jam.setA1Time(), jam.getA1Time(), jam.setA2Time(), dan jam.getA2Time()
Fungsi setA1Time() mengambil delapan parameter, seperti yang tercantum dalam kutipan dari file DS3231.h ini:
void setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM);
Pembacaan dekat file header DS3231.h dapat menjelaskan parameternya. Berikut ini adalah upaya saya untuk menjelaskannya kembali kepada diri saya sendiri dengan kata-kata saya sendiri. Jika pembaca menemukan perbedaan antara versi saya dan file header, asumsikan header tersebut benar.
Lima parameter pertama bertipe "byte". Situs web cppreference mendefinisikan tipe byte dengan cara ini https://en.cppreference.com/w/cpp/types/byte:
std::byte adalah tipe berbeda yang mengimplementasikan konsep byte seperti yang ditentukan dalam definisi bahasa C++.
Seperti char dan unsigned char, ini dapat digunakan untuk mengakses memori mentah yang ditempati oleh objek lain (representasi objek), tetapi tidak seperti tipe tersebut, ini bukan tipe karakter dan bukan tipe aritmatika. Satu byte hanyalah kumpulan bit, dan satu-satunya operator yang ditentukan untuknya adalah operator bitwise.
Kita dapat membiarkan diri kita memikirkan variabel tipe byte untuk hari dan waktu seolah-olah mereka adalah bilangan bulat yang tidak ditandatangani, dalam situasi khusus ini. Mereka dapat menyimpan nilai integer antara 0 dan 255. PERHATIAN: penulis kode harus menghindari nilai yang tidak masuk akal. Misalnya, nilai 102 tidak masuk akal untuk parameter mana pun. Tugas Anda sebagai penulis kode adalah memberikan nilai yang masuk akal.
Mari kita lanjutkan dengan alarmTime yang dibuat pada langkah sebelumnya: hari ke 27 setiap bulan, pukul 17 lewat 10:42 pagi. Daftar di bawah ini menunjukkan bagaimana Anda dapat memasukkan nilai-nilai tersebut ke dalam fungsi. Saya mencantumkan setiap parameter pada barisnya sendiri, agar lebih mudah dibaca oleh manusia dan memberikan ruang untuk komentar. Contoh di sini tidak lengkap; itu hanya menunjukkan nilai tipe byte untuk tanggal dan waktu. Fungsi ini memerlukan lebih banyak parameter, seperti dijelaskan di bawah, dan tidak akan berjalan dalam bentuk yang ditunjukkan di sini.
Ngomong-ngomong, perhatikan bahwa variabel "jam" dan "waktu alarm" adalah objek, yaitu kotak peralatan perangkat lunak. Seperti yang Anda lihat, kami menggunakan alat dari dalam kotak alat masing-masing untuk mengakses informasi yang terdapat dalam objek.
clock.setA1Time(
alarmTime.day(), // the day of the month: 27
alarmTime.hour(), // the hour of the day: 10
alarmTime.minute(), // the minute of the hour: 42
alarmTime.second(), // the second of the minute: 17
// ... the remaining parameters are explained below
);
Parameter tipe byte berikutnya, bernama AlarmBits, sebenarnya hanyalah kumpulan bit. Bit memiliki nama seperti yang ditentukan dalam lembar data DS3231 (di halaman 11).
Bagian 7 | Bagian 6 | Bagian 5 | Bagian 4 | Bagian 3 | Bagian 2 | Bagian 1 | Sedikit 0 |
---|---|---|---|---|---|---|---|
-- | -- | -- | DyDt | A1M4 | A1M3 | A1M2 | A1M1 |
Bersama-sama, bit-bit tersebut membentuk "topeng", atau pola, yang memberitahu DS3231 kapan dan seberapa sering memberi sinyal alarm. Tabel di halaman 12 lembar data memberikan arti untuk kumpulan bit yang berbeda. Berdasarkan tabel tersebut, contoh sketsa pada tutorial ini menggunakan kumpulan bit berikut:
-- | -- | -- | DyDt | A1M4 | A1M3 | A1M2 | A1M1 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
Susunan bit ini dapat dinyatakan secara eksplisit dalam kode: 0x00001110
. Ini memberitahu DS3231 untuk memberi sinyal alarm setiap kali "detiknya cocok", yaitu ketika nilai "detik" dari pengaturan alarm cocok dengan nilai "detik" dari waktu saat ini.
Tiga parameter terakhir dari fungsi setA1Time()
adalah nilai boolean, atau nilai benar/salah. Mereka memberi tahu DS3231 informasi lebih lanjut tentang cara mengevaluasi pengaturan alarm. Segmen kode berikut menunjukkan panggilan yang telah selesai ke setA1Time(), melanjutkan contoh yang dimulai di atas:
clock.setA1Time(
alarmTime.day(), // the day of the month: 27
alarmTime.hour(), // the hour of the day: 10
alarmTime.minute(), // the minute of the hour: 42
alarmTime.second(), // the second of the minute: 17
0x00001110, // AlarmBits = signal when the seconds match
false, // A1Dy false = A1Day means the date in the month;
// true = A1Day means the day of the week
false, // A1h12 false = A1Hour in range 0..23;
// true = A1Hour in range 1..12 AM or PM
false // A1PM false = A1Hour is a.m.;
// true = A1Hour is p.m.
);
Dalam contoh sketsa, di mana kita mengatur alarm untuk interupsi setiap 10 detik, hanya parameter A1Second dan AlarmBits yang penting. Namun, kita perlu menyediakan semuanya saat kita memanggil fungsi ke setA1Time()
. Nilai-nilai yang benar tidak lebih sulit untuk diberikan dibandingkan dengan nilai-nilai sampah; sebaiknya kita berhati-hati dengan mereka.
Fungsi setA2Time()
bekerja dengan cara yang sama, tetapi tanpa parameter selama beberapa detik. Luangkan waktu untuk meninjau baris 119 hingga 145 dari file DS3231.h di perpustakaan dan halaman 11-12 di lembar data. Duduklah dengan sabar dengan referensi ini sampai Anda menemukan di dalamnya informasi yang Anda perlukan untuk menyetel waktu alarm.
Setelah mengatur waktu, sketsa harus mengambil tindakan tambahan untuk mengaktifkan alarm di DS3231. Saya mendorong pembaca secara konsisten untuk mengikuti urutan tiga langkah, meskipun beberapa langkah mungkin tampak kurang diperlukan pada suatu waktu karena alasan tertentu. Jika kode Anda pasti salah, biarkan saja yang salah.
Untuk alarm A1, instruksi di perpustakaan DS3231 adalah:
turnOffAlarm(1); // clear the A1 enable bit in register 0Eh
checkIfAlarm(1); // clear the A1 alarm flag bit in register 0Fh
turnOnAlarm(1); // set the A1 enable bit in register 0Eh
Untuk alarm A2 cukup ubah parameternya menjadi 2. Contoh: checkIfAlarm(2); // clear A2 flag bit in register 0Fh
.
Masalah yang dijelaskan dalam repo oleh @flowmeter ini menekankan bahwa kedua tanda alarm harus dihapus sebelum DS3231 dapat memberi sinyal alarm. Yang pasti, pertimbangkan untuk memanggil checkIfAlarm() dua kali, satu kali untuk setiap alarm, meskipun Anda hanya menggunakan salah satu alarm :
checkIfAlarm(1);
checkIfAlarm(2);
Mengapa penulis kode memilih untuk "memeriksa" alarm yang mereka yakini saat ini tidak mengirimkan sinyal? Alasannya adalah fungsi checkIfAlarm()
memiliki efek samping yang tidak jelas. Ini menghapus sedikit tanda alarm. Kami menggunakan fungsi checkIfAlarm()
, karena ini adalah satu-satunya fungsi di perpustakaan DS3231 yang melakukan operasi yang diperlukan.
Pikirkan tentang hal ini. Untuk alasan yang akan dijelaskan di bawah, perangkat keras penginderaan interupsi Arduino memerlukan tegangan pada pin SQW DS3231 menjadi TINGGI sebelum alarm berbunyi. Peristiwa alarm mengubah dua hal di dalam DS3231:
Pin SQW akan tetap RENDAH selama salah satu bit tanda alarm tersebut tetap disetel. Selama bit tanda alarm di dalam DS3231 menahan pin SQW RENDAH, Arduino tidak dapat merasakan alarm lagi. Bit tanda alarm keduanya harus dihapus agar DS3231 mengembalikan tegangan TINGGI pada pin alarm SQW-nya. Lihat pembahasan bit 1 dan 0 pada "Status Register (0Fh)", di halaman 14 datasheet DS3231.
Setiap alarm memiliki bit tanda alarmnya sendiri di dalam DS3231. Salah satu bit tanda alarm dapat menahan pin SQW LOW. DS3231 tidak akan menghapus sedikit pun tanda alarm atas inisiatifnya sendiri. Tugas penulis kode adalah menghapus bit tanda alarm setelah alarm terjadi .
Loop utama Anda tidak perlu mengukur waktu. Ia hanya perlu memeriksa sebuah bendera untuk melihat apakah alarm telah terjadi. Dalam contoh sketsa, flag ini adalah variabel boolean bernama "alarmEventFlag":
if (alarmEventFlag == true) {
// run the special code
}
Seringkali, flagnya adalah false , dan loop akan melewati kode khusus. Bagaimana sketsa mengatur benderanya? Tiga langkah:
bool alarmEventFlag = false;
void rtcISR() {alarmEventFlag = true;}
attachInterrupt()
yang disediakan oleh Arduino IDE menyatukan semuanya. Contoh berikut memberitahu perangkat keras Arduino untuk segera menjalankan fungsi rtcISR()
setiap kali mendeteksi sinyal "JATUH" pada pin digital yang ditentukan.attachInterrupt(digitalPinToInterrupt(dataPin), rtcISR, FALLING);
Untuk alasan mendalam dan gaib, selalu gunakan fungsi khusus, digitalPinToInterrupt()
, saat menentukan nomor pin untuk interupsi. Saya biarkan ini sebagai latihan bagi pembaca untuk mengetahui mengapa kita memerlukan fungsi tersebut.
Apa itu sinyal JATUH? Artinya terjadi perubahan tegangan menjadi LOW dari HIGH yang dideteksi oleh pin digital Arduino. Dari manakah perubahan tegangan itu berasal? Itu berasal dari pin alarm modul DS3231. Pin tersebut diberi label, SQW, dan sering kali memancarkan tegangan TINGGI, mendekati tingkat pasokan VCC (yaitu, 5 volt pada Uno). Alarm menyebabkan DS3231 mengubah tegangan pada pin SQW menjadi LOW. Arduino merasakan tegangan yang datang dari pin SQW dan memperhatikan perubahannya. Kami memberi tahu Arduino untuk memperhatikan JATUH karena peristiwa itu hanya terjadi sekali per alarm, sedangkan level RENDAH dapat bertahan dan membingungkan Arduino sehingga memicu banyak interupsi.
Pin mana yang dapat merasakan perubahan tegangan yang JATUH? Untuk Unos, Anda dapat memilih salah satu dari pin 2 atau 3. Untuk Leonardo, dapat berupa salah satu dari pin 0, 1, atau 7. (Ya, saya tahu, Leonardo juga merasakan interupsi pada pin 2 dan 3. Namun, itu adalah Pin I2C Leonardo, yang berarti modul DS3231 akan menggunakannya. Saya mulai dengan pin 7 untuk interupsi pada Leonardo.) Contoh sketsa mendefinisikan variabel dataPin dan menginisialisasi nilainya menjadi 3 untuk menjalankan Uno dengan cara ini:
int dataPin = 3;
Kode khusus juga dapat mengatur waktu alarm baru, jika Anda ingin mengulangi siklus tersebut. Mulailah dengan menghitung waktu alarm baru, seperti yang dijelaskan pada Langkah 3, dan ikuti urutan langkah dari sana.
Saya mengharapkan contoh sketsa menghasilkan keluaran yang mirip dengan ilustrasi di bawah ini, ketika dijalankan pada Arduino Uno yang terhubung dengan benar ke modul DS3231, seperti yang saya jelaskan dalam tutorial ini. Jangan heran jika waktu yang ditampilkan berbeda. Bagaimanapun, Anda harus mengatur waktu Anda sendiri.