DS3231 실시간 시계를 사용하여 Arduino를 중단시키는 방법에 대해 배운 요령
Arduino 하드웨어의 타이머와 카운터를 사용하지 않고 Arduino가 스케치의 특수 코드 세그먼트를 실행할 정확한 시간을 결정하려고 합니다. Arduino IDE를 사용하여 코드를 작성하는 데 어느 정도 기술과 경험이 있으며, delay()
와 같은 코드 문을 사용하지 않으려고 합니다.
대신, 매우 정밀한 DS3231 실시간 클럭 모듈을 외부 인터럽트 소스로 연결하려고 합니다. Arduino의 전원이 일시적으로 끊기더라도 DS3231이 배터리를 사용하여 정확한 시간을 유지할 수 있다는 것이 중요할 수 있습니다.
마지막으로 Arduino 온라인 참조(https://www.arduino.cc/reference/en/libraries/ds3231/)에서 참조되는 Andrew Wickert의 DS3231.h 라이브러리를 사용하는 방법을 배우고 싶습니다. 여기에는 숙달하려는 노력의 가치가 충분히 입증될 수 있는 몇 가지 요령이 포함되어 있습니다. 라이브러리 관리자(도구 > 라이브러리 관리...)를 사용하여 이 라이브러리를 Arduino IDE로 가져올 수 있습니다.
단계별로 진행하세요. 이 튜토리얼에서는 다음 단계를 보여줍니다.
특수 코드를 지정한 간격으로 두 번 이상 실행하려면 코드에서 3단계를 다시 수행하고 새 알람을 설정할 수 있습니다. 이 튜토리얼과 함께 제공되는 예제 스케치는 Arduino를 10초 간격으로 반복적으로 중단합니다.
이 튜토리얼은 다음을 포함한 "공식" 참고 자료를 바탕으로 작성되었습니다.
attachInterrupt()
함수에 대한 Arduino 참조: https://www.arduino.cc/reference/en/언어/functions/external-interrupts/attachinterrupt/. 이 예의 특수 코드는 간단합니다. DS3231에서 현재 시간만 인쇄합니다. 실제 사례에서는 식물에 물을 주거나 온도 측정값을 기록하는 등 유용한 작업을 수행할 수 있습니다. 작업이 무엇이든 코드는 DS3231 경보가 Arduino를 중단할 때만 실행되도록 자체 특수 블록에 들어가야 합니다.
나는 코드를 짧은 함수로 나누는 것이 좋은 습관이라고 생각합니다. 여기서 각 함수는 하나의 작업 또는 하나의 관련 작업 세트만 처리합니다. 함수의 이름은 무엇이든 될 수 있습니다. 함수가 무엇을 하는지 설명하도록 만들어 보는 것은 어떨까요? 다음은 이 예제에서 특수 코드를 실행하는 함수의 일부입니다.
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
}
다섯 쌍의 핀 사이에 와이어를 연결합니다. 각 쌍은 하나의 전기적 목적을 수행하며 Arduino의 핀을 DS3231의 해당 핀과 일치시킵니다. 천천히 진행하여 각 쌍을 연결한 다음 양쪽 끝을 확인하여 각 와이어가 올바른 위치에 있는지 확인하십시오. 표에는 Arduino Uno에서 DS3231에 왼쪽에서 오른쪽으로 연결되는 순서대로 쌍이 나열되어 있습니다.
목적 | DS3231 핀 | 아두이노 핀 |
---|---|---|
경보 | SQW | 3* |
SCL | SCL | SCL** |
SDA | SDA | SDA** |
5볼트 전력 | VCC | 5V |
지면 | 접지 | 접지 |
내가 말했듯이 이러한 연결을 만드는 데 시간을 투자하십시오. 느리고 확실한 것이 어떤 일이든 올바르게 완료하는 가장 빠른 방법인 경우가 많습니다.
이름이 적힌 일종의 소프트웨어 도구 상자인 DS3231 "객체"를 통해 DS3231 모듈과 통신하겠습니다. DS3231 라이브러리는 상자 내부에 도구라고 생각되는 많은 기능을 정의합니다. 함수를 사용하려면 도구 상자 이름, 점, 도구 이름 순으로 적습니다. 예제 스케치에서는 이 목적을 위해 "시계" 변수를 생성합니다. 그런 다음 스케치는 "시계" 상자의 도구에 액세스할 수 있습니다. 모든 도구는 위에서 언급한 DS3231.h 파일에 선언되어 있습니다.
#include <DS3231.h>
DS3231 clock;
Serial.println(clock.getMinute()); // the current minute, 0..59
알람을 설정하기 위해 "시계" 개체의 도구를 사용하겠습니다. 하지만 먼저 알람 시간을 계산해야 합니다.
이 단계에서는 이전에 DS3231에 실제 시간을 설정했다고 가정합니다. 예제 스케치에는 필요한 경우 시계의 시간을 설정하는 데 사용할 수 있는 코드가 포함되어 있습니다. 주변에 있는 주석 구분 기호 /* 및 */를 제거하기만 하면 됩니다.
이 튜토리얼의 예제 스케치는 현재 시간에 간격(초)을 추가하여 미래의 알람 시간을 계산합니다. 이 예에서는 10초를 추가합니다. 1분에 60초가 추가됩니다. 한 시간에 3,600초가 추가됩니다. 하루, 86,400초. 등등.
DS3231 라이브러리에는 시간을 초 단위로 쉽게 추가할 수 있는 숨겨진 "트릭"이 있습니다. DS3231 라이브러리의 README 페이지에 있는 사용 가능한 기능 중에서 이 트릭을 찾을 수 없습니다. DS3231.h 파일을 봐도 완전히 명확하지는 않습니다. 일부 세부 정보는 DS3231.cpp 코드 파일에서 찾을 수 있습니다. 계산을 수행하는 단계는 다음과 같습니다.
now()
함수와 동일하게 설정하십시오.4단계와 5단계를 결합할 수 있습니다.
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
AlarmTime 객체는 초 단위로 생성되지만 연, 월, 일, 시, 분, 초를 표현할 수 있는 도구를 도구 상자에 제공합니다. 다음에 설명된 대로, 필요한 값의 소스로 AlarmTime 개체를 사용하여 DS3231에 알람 시간을 설정합니다.
예를 들어 DS3231 모듈이 보고한 currentTime이 2021년 10월 27일 수요일 오전 10시 42분에서 7초였다고 가정해 보겠습니다. 위에서 계산된 AlarmTime은 같은 날 10초 후 10시 42분 17초가 됩니다.
DS3231은 알람 #1(A1)과 알람 #2(A2)라는 두 가지 다른 알람을 제공합니다. 두 알람 모두 요일과 시간, 최대 분까지 지정할 수 있습니다. 차이점은 A1을 초 단위까지 추가로 지정할 수 있다는 것입니다. 각 알람에는 DS3231 라이브러리에 알람 시간을 설정하고 해당 시간을 읽기 위한 자체 기능 쌍이 있습니다. 기능은 모두 DS3231 개체(예: "시계"라고 명명한 개체)를 통해 액세스됩니다.
clock.setA1Time(), clock.getA1Time(), clock.setA2Time() 및 clock.getA2Time()
setA1Time() 함수는 DS3231.h 파일의 인용문에 나열된 대로 8개의 매개변수를 사용합니다.
void setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM);
DS3231.h 헤더 파일을 자세히 읽으면 매개변수를 설명할 수 있습니다. 다음은 그것들을 내 자신의 말로 다시 설명하려는 나의 시도입니다. 독자가 내 버전과 헤더 파일 사이에 불일치를 발견하면 헤더가 정확하다고 가정하십시오.
처음 5개 매개변수는 "바이트" 유형입니다. cppreference 웹 사이트는 https://en.cppreference.com/w/cpp/types/byte 방식으로 바이트 유형을 정의합니다.
std::byte는 C++ 언어 정의에 지정된 대로 바이트 개념을 구현하는 고유한 유형입니다.
char 및 unsigned char과 마찬가지로 다른 객체(객체 표현)가 차지하는 원시 메모리에 액세스하는 데 사용할 수 있지만 이러한 유형과 달리 문자 유형도 아니고 산술 유형도 아닙니다. 바이트는 비트의 모음일 뿐이며 이에 대해 정의된 유일한 연산자는 비트 연산자입니다.
이 특정 상황에서는 날짜와 시간에 대한 바이트 유형 변수를 부호 없는 정수인 것처럼 생각할 수 있습니다. 0에서 255 사이의 정수 값을 보유할 수 있습니다 . 주의: 코드 작성자는 무의미한 값을 피해야 합니다. 예를 들어, 값 102는 이러한 매개변수에 대해 의미가 없습니다. 합리적인 가치를 제공하는 것이 코드 작성자의 임무입니다.
이전 단계에서 생성된 알람 시간(월 27일, 오전 10시 42분 17초)을 계속 진행해 보겠습니다. 아래 목록은 해당 값을 함수에 제공하는 방법을 보여줍니다. 사람이 더 쉽게 읽을 수 있도록 하고 설명을 위한 공간을 확보하기 위해 각 매개변수를 한 줄에 나열했습니다. 여기의 예는 불완전합니다. 날짜 및 시간에 대한 바이트 유형 값만 보여줍니다. 이 함수에는 아래 설명과 같이 더 많은 매개변수가 필요하며 여기에 표시된 형식으로는 실행되지 않습니다.
그런데 "clock" 및 "alarmTime" 변수는 개체, 즉 소프트웨어 도구 상자라는 점에 유의하세요. 보시다시피, 우리는 각 도구 상자 내부의 도구를 사용하여 개체에 포함된 정보에 액세스합니다.
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
);
AlarmBits라는 다음 바이트 유형 매개변수는 실제로 비트 모음일 뿐입니다. 비트에는 DS3231 데이터시트(11페이지)에 정의된 이름이 있습니다.
비트 7 | 비트 6 | 비트 5 | 비트 4 | 비트 3 | 비트 2 | 비트 1 | 비트 0 |
---|---|---|---|---|---|---|---|
-- | -- | -- | DyDt | A1M4 | A1M3 | A1M2 | A1M1 |
비트는 함께 "마스크" 또는 패턴을 형성하여 DS3231에 경보 신호를 보내는 시기와 빈도를 알려줍니다. 데이터시트 12페이지의 표에는 다양한 비트 모음에 대한 의미가 나와 있습니다. 해당 테이블을 기반으로 이 튜토리얼의 예제 스케치에서는 다음 비트 컬렉션을 사용합니다.
-- | -- | -- | DyDt | A1M4 | A1M3 | A1M2 | A1M1 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
이러한 비트 배열은 0x00001110
코드로 명시적으로 표현될 수 있습니다. 이는 "초가 일치"할 때마다, 즉 알람 설정의 "초" 값이 현재 시간의 "초" 값과 일치할 때마다 경보 신호를 보내도록 DS3231에 지시합니다.
setA1Time()
함수의 마지막 세 매개변수는 부울 또는 true/false 값입니다. 이는 알람 설정을 평가하는 방법에 대한 추가 정보를 DS3231에 알려줍니다. 다음 코드 세그먼트는 위에서 시작한 예제를 계속하여 setA1Time()에 대한 완료된 호출을 보여줍니다.
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.
);
10초마다 중단되도록 알람을 설정한 예제 스케치에서는 A1Second 및 AlarmBits 매개변수만 중요합니다. 그러나 setA1Time()
함수를 호출할 때 이를 모두 제공해야 합니다. 올바른 가치는 정크 가치보다 제공하기가 더 이상 어렵지 않습니다. 우리는 그들을 조심하는 것이 나을 수도 있습니다.
setA2Time()
함수도 유사하게 작동하지만 몇 초 동안 매개변수가 없습니다. 시간을 내어 라이브러리에 있는 DS3231.h 파일의 119~145행과 데이터시트의 11~12페이지를 검토하세요. 알람 시간을 설정하는 데 필요한 정보를 찾을 때까지 이러한 참고 자료를 참을성 있게 기다리십시오.
시간을 설정한 후 스케치는 DS3231에서 알람을 활성화하기 위해 추가 조치를 취해야 합니다. 나는 독자들이 어떤 이유로 어떤 단계가 어느 시점에 덜 필요해 보일지라도 지속적으로 3단계 순서를 따르도록 권장합니다. 코드에 오류가 발생해야 한다면 확실성을 염두에 두고 오류를 발생시키도록 하세요.
알람 A1의 경우 DS3231 라이브러리의 지침은 다음과 같습니다.
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
알람 A2의 경우 매개변수를 2로 변경하면 됩니다. 예: checkIfAlarm(2); // clear A2 flag bit in register 0Fh
.
@flowmeter가 이 저장소에 설명한 문제는 DS3231이 경보 신호를 보내기 전에 두 경보 플래그를 모두 지워야 한다는 점을 강조합니다. 확실하게 하려면 알람 중 하나만 사용하는 경우에도 각 알람에 대해 한 번씩 checkIfAlarm()을 두 번 호출하는 것이 좋습니다 .
checkIfAlarm(1);
checkIfAlarm(2);
코드 작성자가 현재 신호를 보내지 않는다고 생각되는 경보를 "확인"하는 이유는 무엇입니까? 그 이유는 checkIfAlarm()
함수에 명백하지 않은 부작용이 있기 때문입니다. 알람 플래그 비트를 삭제합니다. DS3231 라이브러리에서 필요한 작업을 수행하는 유일한 함수이기 때문에 checkIfAlarm()
함수를 사용합니다.
생각해 보세요. 아래에 설명할 이유 때문에 Arduino 인터럽트 감지 하드웨어는 알람이 발생하기 전에 DS3231의 SQW 핀 전압이 HIGH여야 합니다. 알람 이벤트는 DS3231 내부에서 두 가지 사항을 변경합니다.
SQW 핀은 해당 알람 플래그 비트 중 하나가 설정된 상태로 유지되는 한 LOW로 유지됩니다. DS3231 내부의 알람 플래그 비트가 SQW 핀을 LOW로 유지하는 한 Arduino는 더 이상 알람을 감지할 수 없습니다. DS3231이 SQW 알람 핀에서 HIGH 전압을 복원하려면 알람 플래그 비트를 모두 지워야 합니다. DS3231 데이터시트 14페이지의 "상태 레지스터(0Fh)"에서 비트 1과 0에 대한 설명을 참조하세요.
각 경보에는 DS3231 내부에 자체 경보 플래그 비트가 있습니다. 알람 플래그 비트 중 하나는 SQW 핀을 LOW로 유지할 수 있습니다. DS3231은 자체적으로 알람 플래그 비트를 지우지 않습니다. 알람이 발생한 후 알람 플래그 비트를 지우는 것은 코드 작성자의 임무입니다 .
메인 루프에서는 시간을 측정할 필요가 없습니다. 알람이 발생했는지 여부를 확인하려면 플래그만 확인하면 됩니다. 예제 스케치에서 이 플래그는 "alarmEventFlag"라는 부울 변수입니다.
if (alarmEventFlag == true) {
// run the special code
}
대부분의 경우 플래그는 false 이며 루프는 특수 코드를 건너뜁니다. 스케치는 깃발을 어떻게 설정합니까? 세 단계:
bool alarmEventFlag = false;
void rtcISR() {alarmEventFlag = true;}
attachInterrupt()
함수는 이 모든 것을 하나로 통합합니다. 다음 예에서는 Arduino 하드웨어가 지정된 디지털 핀에서 "FALLING" 신호를 감지할 때마다 즉시 rtcISR()
기능을 실행하도록 지시합니다.attachInterrupt(digitalPinToInterrupt(dataPin), rtcISR, FALLING);
심오하고 신비스러운 이유로 인터럽트에 대한 핀 번호를 지정할 때 항상 특수 함수 digitalPinToInterrupt()
를 사용하십시오. 나는 독자들이 왜 그 기능이 필요한지 발견할 수 있는 연습으로 남겨 둡니다.
FALLING 신호란 무엇입니까? 이는 Arduino의 디지털 핀에 의해 감지된 대로 전압이 HIGH에서 LOW로 변경됨을 의미합니다. 전압 변화는 어디에서 오는가? 이는 DS3231 모듈의 알람 핀에서 발생합니다. 해당 핀은 SQW라는 라벨이 붙어 있으며 대부분의 경우 VCC 공급 레벨(즉, Uno의 경우 5V) 근처에서 높은 전압을 방출합니다. 알람이 발생하면 DS3231이 SQW 핀의 전압을 LOW로 변경합니다. Arduino는 SQW 핀에서 나오는 전압을 감지하고 변화를 알아차립니다. 해당 이벤트는 알람당 한 번만 발생하는 반면 LOW 레벨은 지속되고 Arduino가 많은 인터럽트를 트리거하도록 혼동할 수 있기 때문에 Arduino에게 FALLING을 알리도록 지시합니다.
전압의 떨어지는 변화를 감지할 수 있는 핀은 무엇입니까? Unos의 경우 핀 2 또는 3 중 하나를 선택할 수 있습니다. Leonardo의 경우 핀 0, 1 또는 7 중 하나일 수 있습니다. (예, Leonardo는 핀 2 및 3에서도 인터럽트를 감지합니다. 그러나 이는 Leonardo의 I2C 핀은 DS3231 모듈이 이를 사용한다는 것을 의미합니다. Leonardo의 인터럽트를 위해 핀 7부터 시작합니다.) 예제 스케치는 dataPin 변수를 정의하고 해당 값을 3으로 초기화합니다. Uno에서 이런 식으로 실행하려면 :
int dataPin = 3;
주기를 반복하려는 경우 특수 코드를 사용하여 새로운 알람 시간을 설정할 수도 있습니다. 3단계에 설명된 대로 새 알람 시간을 계산하는 것부터 시작하고 거기부터 일련의 단계를 따르세요.
이 튜토리얼에서 설명한 대로 예제 스케치가 DS3231 모듈에 올바르게 연결된 Arduino Uno에서 실행될 때 아래 그림과 유사한 출력을 생성할 것으로 기대합니다. 표시된 시간이 다르더라도 놀라지 마세요. 어쨌든 당신은 자신의 시간에 맞춰 일해야 합니다.