The goal we strive for is an alarm clock that, instead of whipping, can switch on a device, a radio or the stereo system via the infrared remote control. We have already done the first part of the way Read RC (Remote Control) and one own IR station to realize. The possibility a PS/2 keyboard to the ESP32 It allows the RC5 control to be read without having to be connected.
The ESP32 now has an RTC (Real Time Clock), which has two shortcomings. First, the accuracy is not convincing. Loud Micropython documentation For the ESP32, second, the controller should be equipped with an alarm method. In fact, Micropython does not offer such an alarm function on the ESP32, but it is currently crucial for an alarm clock.
X
>>> From machine import RTC
>>> RTC
<class 'RTC'>
>>> RTC=RTC()
>>> RTC.alarm(0,1000)
Traceback (custody recent call load):
File "" , line 1, in <modules>
Attribute: 'RTC' object has NO attributes 'alarm'
As a result, we have to fix this defect. This happens in this episode from the series
Micropython on the ESP32 and ESP8266
today
The ESP32 and the RTC module with the DS3231
The previous building instructions, as well as the program parts and modules, are included in the articles linked above (1, 2, 3).
What I found out about the accuracy of the ESP internal RTCs, I have in one earlier contribution already passed on. In this post, in addition to the RTC properties of an ESP8266 D1 mini, it is also about using an external RTC in the form of a DS1302 module. This chip exceeds the ESP-owned systems in accuracy, but unfortunately does not offer alarm mode. A use of this module would therefore mean that the controller would have to take on this function itself. Because that's too complex for me, I chose a DS3231 module. This also exceeds the DS1302 in terms of accuracy and even has two alarm timers, which suits me quite well in the stuff for this project. The DS3231 also offers the advantage that it can be addressed via the I2C bus. For the DS1302, you would have to provide three more bus lines. The battery buffering is also crucial for the choice of the external module. Even in the event of a power failure, the clock of the DS3231 continues to tick reliably, even temperature stabilized. In this article we will take a close look at this building block and its programming.
The resulting micropython module ds3231.py Let's discuss later, now we take a look at the hardware that has previously accumulated and what is new.
Hardware
1 |
|
1 |
|
1 |
|
1 |
0.91 inch OLED I2C display 128 x 32 pixels |
1 |
|
1 |
|
various |
|
1 |
|
2 |
NPN transistor BC337 or similar |
1 |
Resistance 1.0 kΩ |
1 |
Resistance 10 kΩ |
1 |
Resistance 330 Ω |
1 |
Resistance 47Ω |
1 |
Resistance 560Ω |
1 |
LED (color at will) |
1 |
Adapter PS/2 according to USB or PS/2 socket |
1 |
|
1 |
PS/2 keyboard |
You are right, only the DS3231 is new. First of all, let's consider the storage -based inner life of the DS3231. In the Data sheet from the manufacturer Maxim Integrated (before the Dallas) we find a table that lists the existing register. A number of registers contain the time data (0x00 to 0x06), others the alarm data (0x07 to 0x0d) and two more serve the administration (0x0e, 0x0f). The 0x11 and 0x12 tabs contain high and low-byte of internal temperature measurement.
Table 1: DS3231 Register map
The time and alarm data are BCD-coded (binary coded decimal). In addition, BIT 7 contains the information in which intervals an alarm should be triggered, every second, minute, hour, or on what day.
The circuit
In addition to the I2C bus, the DS3231 module has a separate output, SQW. Two signals can be used on this PIN. Either the connection serves to issue a second time, or as a IRQ-Line. We will use the latter way to notify the ESP32 if there is an alarm event. Until then, the controller can do its own stuff. This line is usually on high levels (3.3V) and goes to low (0V) if the alarm time and time match and other conditions are met. We come back to it during the programming.
The ISR Then, in addition to triggering the corresponding action, must then put the level on the line back on high. There is a suitable method for this (DS3231.Clearalarm ()) in the class DS3231.
Figure 1: A more precise clock with the DS3231 - battery buffert
The software
For flashes and the programming of the ESP32:
Thonny or
Saleae – Logic analyzer software (64 bit) For Windows 8, 10, 11
Used firmware for an ESP32:
Used firmware for an ESP8266:
The micropython programs for the project:
ds3231.py Driver module
oled.py: OLED-API
SSD1306.PY: OLED hardware driver
sync_it.py: Demo program for testing
second alarm.py: Demo program for a watch
Micropython - Language - Modules and Programs
To install Thonny you will find one here detailed instructions (English version). There is also a description of how that Micropython firmware (As of 05.02.2022) on the ESP chip burned becomes.
Micropython is an interpreter language. The main difference to the Arduino IDE, where you always flash entire programs, is that you only have to flash the Micropython firmware once on the ESP32 so that the controller understands micropython instructions. You can use Thonny, µpycraft or ESPTOOL.PY. For Thonny I have the process here described.
As soon as the firmware has flashed, you can easily talk to your controller in a dialogue, test individual commands and see the answer immediately without having to compile and transmit an entire program beforehand. That is exactly what bothers me on the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware to trying out and refining functions and entire program parts via the command line before knitting a program from it. For this purpose, I always like to create small test programs. As a kind of macro, they summarize recurring commands. Whole applications then develop from such program fragments.
Autostart
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blank tile. Save this file under boot.py in WorkSpace and upload it to the ESP chip. The program starts automatically the next time the reset or switching on.
Test programs
Programs from the current editor window in the Thonny-IDE are started manually via the F5 button. This can be done faster than the mouse click on the start button, or via the menu run. Only the modules used in the program must be in the flash of the ESP32.
In between, Arduino id again?
Should you later use the controller together with the Arduino IDE, just flash the program in the usual way. However, the ESP32/ESP8266 then forgot that it has ever spoken Micropython. Conversely, any espressif chip that contains a compiled program from the Arduino IDE or AT-Firmware or Lua or ... can be easily provided with the micropython firmware. The process is always like here described.
The class DS3231
In connection with the DS3231-RTC module, it is worth noting that an EEPROM with 4KB storage space also lives on the BOB (Break Out Board) in addition to the RTC chip. This is also the reason why the board on the i2C bus reports with two device addresses.
X
>>> From machine import Pin code, Softi2c
>>> I2C=Softi2c(scl=Pin code(22),sda=Pin code(23),freq=100000)
>>> I2C.scan()
[60, 87, 104]
60 = 0x3c: OLED display
87 = 0x57: EEPROM
104 = 0x68: DS3231
The EEPROM can be used in the event of power failures, for example to use configuration data. Maybe that could be a list of switching times.
The module ds3231.py this feature takes into account in the form of the class AT24C32, but I do not go down here because it is not used in this episode.
So let's turn to the class DS3231 to. With the declaration of the class, the register addresses of the DS3231 are assigned to the corresponding class attributes. The identifiers result in a more readable program for humans.
XXXXXXXXXX
class DS3231():
hwad = const(0x68) # 7-bit address
Rank = const(0x00)
Regmin = const(0x01)
Reghor = const(0x02)
Regdow = const(0x03)
RegDay = const(0x04)
Regmon = const(0x05)
Steryea = const(0x06)
Rega1sec = const(0x07)
Rega1min = const(0x08)
Rega1hor = const(0x09)
Rega1Day = const(0x0a)
Rega2min = const(0x0b)
Rega2hor = const(0x0c)
Rega2day = const(0x0d)
Regctl = const(0x0e)
Rainsta = const(0x0f)
Regagoff = const(0x10)
Ragemp = const(0x11)
Other constants name the intervals in which an alarm takes place.
XXXXXXXXXX
every second = const(1) # A1 every second
every minute = const(2) # A2 every min. To the 0th second
Secondary alarm = const(3) # A1 the seconds fit
Minute alarm = const(4) # Minutes and seconds p.
Hourly alarm = const(5) # Hor, min and sec fit
Date alarm = const(6) # Monthly day and time fit
Weekday alarm = const(7) # Weekday and time fit
mask = [0xfe,0xFD]
The list dow Contains the plain text names of the days of the week. Because the DS3231 counts from Monday = 1 to Sunday = 7, the first list element is occupied by None. This means that the weekday value of the DS3231 can be taken directly into the list of daily names as index.
XXXXXXXXXX
dow=[None,"Monday","Tuesday","Wednesday","Thursday",\
"Friday","Saturday","Sunday"]
The constructor DS3231 () In the form of the method __init__() takes an I2C object that must be declared in the calling program. With 0x4c = 0B01001100 we switch the output SQW As an interrupt output, however, no alarm at the moment. I later explain the importance of the individual bits. The value is in the control register Regctl written.
XXXXXXXXXX
def __init__(self, I2C):
self.I2C = I2C
self.writereg(Regctl, 0x4c)
print("DS3231 ready")
#Bbsqw + RS1 + Intcn ->>> IRQ on SQW -PIN
A number of similarly constructed methods follow that either access or set the corresponding time element. We look at the method as an example Second() at. If the call is called, no argument for the parameter second handed over, this receives the default value None. Then the register will Rank = 0x00 read out. The method bcd2dec () Forms the BCD code into a decimal value that we return.
XXXXXXXXXX
def Second(self, second = None):
IF second == None:
return self.BCD2dec(self.readreg(Rank))
Else:
self.writereg(Rank, \
self.Dec2bcd(second)&0x7f)
A hand -made decimal value is through Dec2bcd () Translated into the BCD format and with 0x7f unduly. This ensures that Bit7 is deleted before we write the result in the register of the DS3231.
The methods for minute, hour, weekday, month and year, month and year, work analogously, whereby there are still a few special cases to consider.
The hour register 0x02 determines with Bit6 whether it is counted every 12 or 24-hour rhythm. Because we work in 24-hour mode, Bit6 is set to 0. Bit5 is the "20"-Bit of the time.
BIT7 of the monthly register is googled when the annual count changes from 99 to 0.
The method Date() delivers or sets, analogously to the methods Year(), Month() and Day(), a daily date by calling these methods. The four-digit annual number handed over is shortened by Modulo 100 on tens and one digits. The month is narrowed down to 1… 12, the day to 1… 31. A safer plausibility control could of course also be made via a range construct, but the whole thing flows unnecessarily.
XXXXXXXXXX
IF dat[1] in range(1,13):
self.Month(dat[1])
Time() works analogously in the areas of hour, minute and second.
With the method DateTime () a list of date and current values is queried if no argument is handed over. The return takes place in a list of the form [Year, month, day, weekday, hour, minute, second, second]. Otherwise, the method expects a 7-series tupel, or a list of the same structure. The individual fields of the tupel transmit the corresponding methods to the DS3231.
XXXXXXXXXX
def DateTime(self, dat = None):
IF dat == None:
return self.Date() + [self.Weekday()] + \
self.Time()
Else:
self.Year(dat[0])
self.Month(dat[1])
self.Day(dat[2])
self.Weekday(dat[3])
self.House(dat[4])
self.minute(dat[5])
self.Second(dat[6])
To a Duration the form [Hour, minute, second] to a time To add, we use the method Adddelay2time(). The time interval is a list or tupel on the parameter delay hand over. The addition takes place in 60s and 24-series. The time particles always remain in the valid area 0 ... 59 and 0 ... 23. A transfer to day, month and year does not take place here, but would generally be possible - with greatly increased effort. Think of the different monthly lengths, switching customers and leaps.
XXXXXXXXXX
def Adddelay2time(self,delay): # delay = [Hor, min, sec]
time=self.Time()
S=delay[2]+time[2]
time[2]=S % 60
M= S // 60 + delay[1] + time[1]
time[1]=M % 60
H= M // 60 + delay[0] + time[0]
time[0] = H % 24
D= H // 24
return time
The second clock on the pin SQW Can we with Second clock() switch on or off by handing over 1 or 0. This makes Bit2 Intcn deleted or set in the Control register. We make the extinguishing through with the mask 0xFB = 0B11111011. Through Ornament With 0x04 we use the bits and thus switch off the second time and the interrupt function. Bit6 set is a prerequisite for both functions (BBSQW) in Regctl. The constructor of a DS3231 object has already done this. The condition of BBSQW is not changed by using the masks.
XXXXXXXXXX
def Second clock(self, onoff): # 1S clock to -In/SQW
CTRL = self.readreg(Regctl)
IF onoff == 1:
CTRL &= 0xFB
Else:
CTRL |= 0x04
self.writereg(Regctl, CTRL)
We can use the current alarm period Getalamtime() experience. The number of the alarm timer is in the parameter alarm hand over. Depending on the corresponding register area, the corresponding register area is read out and as list returned. An invalid timer number leads to the return of [-1,-1,-1].
XXXXXXXXXX
def Getalamtime(self, alarm):
IF alarm == 1:
sec=self.BCD2dec(self.readreg(Rega1sec)&0x7f)
with=self.BCD2dec(self.readreg(Rega1min)&0x7f)
horel=self.BCD2dec(self.readreg(Rega1hor)&0x3f)
elif alarm == 2:
sec=0
with=self.BCD2dec(self.readreg(Rega2min)&0x7f)
horel=self.BCD2dec(self.readreg(Rega2hor)&0x3f)
Else:
sec,with,horel=0,0,0
return [horel,with,sec]
The name of the method SetNewalarmime() is the program. The routine takes for Alarm1 A tupel or a list of form (hour, minute, second) and the timer number 1. The second register is read to preserve the current Bit7, the remaining bits are deleted. The number of seconds passed is translated into BCD code and Bit7 is deleted. Through Ornament with Z The new register value that is sent to the DS3231 is created. The minute register runs analogously. When reading the hourly alarm register, the two top bits are masked and noticed. As a precaution, these bits are hidden in the BCD value before Oder. With Alarm2 there is no second setting. Otherwise it runs like Alarm1.
XXXXXXXXXX
def SetNewalarmime(self,time,alarm):
IF alarm==1:
Z=(self.readreg(Rega1sec)&0x80)
self.writereg(Rega1sec,\
Z|(self.Dec2bcd(time[2])&0x7f))
Z=(self.readreg(Rega1min)&0x80)
self.writereg(Rega1min,\
Z|(self.Dec2bcd(time[1])&0x7f))
Z=(self.readreg(Rega1hor)&0xc0)
self.writereg(Rega1hor,\
Z|(self.Dec2bcd(time[0])&0x3f))
elif alarm==2:
Z=(self.readreg(Rega2min)&0x80)
self.writereg(Rega2min,\
Z|(self.Dec2bcd(time[1])&0x7f))
Z=(self.readreg(Rega2hor)&0xc0)
self.writereg(Rega2hor,\
Z|(self.Dec2bcd(time[0])&0x3f))
The interrupt-enable bits A1ie (Bit0) and A2ie (Bit1) in the Control Register Regctl = 0x0e allow the appearance of alarms if they are set to 1. To do this, Bit2 (Intcn) in Regctl be set. If the comparison between the time and alarm registers is positive, the SQW-Output of the DS3231 on low and the corresponding status bit in Rainsta = 0x0f set.
The method Alarm() Puts the interrupt-enable bit of the alarm to 0, the number of which is handed over to the routine and thus fundamentally prevents the occurrence of the interrupt. Only when number 1 or 2 is handed over will the corresponding Aie-bit be reset, otherwise the routine will not do anything. We read the tab of Regctl as usual and undoubtedly with the respective mask-byte from the list mask.
XXXXXXXXXX
mask = [0xfe,0xFD]
XXXXXXXXXX
def Alarm(self,alarm):
try:
mask=mask[alarm-1]
except:
return
CTRL = self.readreg(Regctl)
self.writereg(Regctl, CTRL & mask)
Alarm1 is approved when we Alarm1() Give the values for day, hour, minute and second. The parameter Fashion receives one of the constant values defined at the beginning. The value decides which mask bits (BIT7) are set or deleted in the alarm period registers. These mask bits determine the time interval for the appearance of interrupts. More on that, before that a look at the routine Alarm1() himself.
XXXXXXXXXX
every second = const(1) # A1 every second
every minute = const(2) # A2 every min. To the 0th second
Secondary alarm = const(3) # A1 the seconds fit
Minute alarm = const(4) # Minutes and seconds p.
Hourly alarm = const(5) # Hor, min and sec fit
Date alarm = const(6) # Monthly day and time fit
Weekday alarm = const(7) # Weekday and time fit
XXXXXXXXXX
def Alarm1(self, day, house, minute, second, Fashion):
CTRL = self.readreg(Regctl)
CTRL |= 0x45 # Set BBSQW + Intcn + A1ie
self.writereg(Regctl, CTRL)
To activate the Alarm1, we first set the right tax bits. BBSQW Must together with Intcn stand on 1, so by setting A1ie An interrupt on SQW-output is approved.
Figure 2: Configuration for Alarm1
We read Regctl = 0x0e one, or on it our config-byte 0b01000101 = 0x45 and send the result to the DS3231. Then we put all four mask bits on 1. The fact that this can happen in one line ensures a little syntactic sugar (Syntactic Suggar). The four bytes on the right of the assignment operator "=" are packed transparently in the background and unpacked on the four variables. What is behind it could be interpreted as follows:
XXXXXXXXXX
>>> tup=0x80,0x80,0x80,0x80
>>> tup
(128, 128, 128, 128)
>>> m1,m2,m3,m4=tup
>>> print(m1,m2,m3,m4)
128 128 128 128
Now we ask the fashion parameter one after the other and adjust the mask-bytes accordingly. We see the connection from Table 2, which I data sheet have taken.
XXXXXXXXXX
M1,M2,M3,M4 = 0x80,0x80,0x80,0x80 # Alarm every second
Dt = 0 # Date must fit, not the day of the week
IF Fashion == DS3231.every second:
passport
elif Fashion == DS3231.Secondary alarm: # Sec. Fit (minute!)
M1 = 0
elif Fashion == DS3231.Minute alarm:# Min. + Sec. fit (hourly)
M1,M2 = 0,0
elif Fashion == DS3231.Hourly alarm:# HOR+MIN+SEK p. (daily)
M1,M2,M3 = 0,0,0
Else: # Monthly day + time fit
M1,M2,M3,M4 = 0,0,0,0
IF Fashion == DS3231.Weekday alarm:
Dt = 0x40
When specifying the daily, we have to differentiate between a monthly date (dy/-dt = 0) and a weekday (dy/-dt = 1). Then the daily and current values passed are sent to the DS3231.
XXXXXXXXXX
self.writereg(Rega1sec,\
(self.Dec2bcd(second)&0x7f)|M1)
self.writereg(Rega1min,\
(self.Dec2bcd(minute)&0x7f)|M2)
self.writereg(Rega1hor,\
(self.Dec2bcd(house)&0x3f)|M3)
self.writereg(Rega1Day,\
(self.Dec2bcd(day)&0x3f)|M4|Dt)
If the seconds of the time and alarm unit agree (1.1,1,0), this happens once every minute, the minute alarm is triggered. Every day, hours and seconds go together (1.0.0.0) and once a year the date also matches if dy/-dt = 0. The alarm is triggered once a week when dy/-dt = 1 and the day of the week fits.
Alarm2 () works in exactly the same way, only that there are no seconds to check here.
Please note that an alarm only by calling the corresponding routine alarm1 or alarm2 activated can be. This sets the time and mode and approved the alarm. Only then can with SetNewalarmime() A new alarm period can be set while maintaining the previously set mode.
To defuse a triggered alarm, we usually press the button on the alarm clock. This happens here by the method Clear alarm() call. A mask is generated with the handed over the alarm number, 0xfe for alarm1 and 0xfD for alarm2. We read the status register Rainsta = 0x0f and delete the status bit in it Fierce With the mask.
Please note that an alarm can only be triggered again if the status bit is reset after activation. As a result, the line SQW from 0V goes back to 3.3V.
Figure 3: End alarm1
Many electronic alarms know repeated alarming alarming, after a certain time interval. This function can be included Adddelay2Alarm() realize flexibly. The routine works analogously Adddelay2time(), only that instead of the time register the alarm period register read and re-described here. The new time will also be returned.
The calculation takes place in the ring 24h, 60m, 60s, i.e. the time calculation takes place with transmission to minutes and hours, but without considering the transfer to the new day and is therefore only in alarm1 for modes 3 to 5 and in alarm2 only for The modes 4 and 5 usable.
XXXXXXXXXX
def Adddelay2Alarm(self,delay,alarm): # delay = [Hor, min, sec]
time=self.Getalamtime(alarm)
S=delay[2]+time[2]
time[2]=S % 60
M= S // 60 + delay[1] + time[1]
time[1]=M % 60
H= M // 60 + delay[0] + time[0]
time[0] = H % 24
# D = H // 24
IF alarm == 1:
self.SetNewalarmime(time, alarm)
elif alarm == 2:
self.SetNewalarmime(time[0:2], alarm)
return time
The status bits can be queried Tell alarm status() take place. The byte value of the two bits, 0, 1, 2, or 3.
XXXXXXXXXX
def Tell alarm status(self):
status=self.readreg(Rainsta) & 0x03
return status
Works similarly Tellalarmenbled(). The routine reports to us which alarm is generally sharpened.
XXXXXXXXXX
def Tellalarmenbled(self):
status=self.readreg(Regctl) & 0x03
return status
Was an alarm with Alarm() deactivated, then it can, while maintaining time and mode Enablealar() are allowed again. The alarm number passed is checked for validity. Then the corresponding bit is set in the read status byte and the whole thing is returned.
XXXXXXXXXX
def Enablealar(self,number):
IF number in range (1,3):
CTRL = self.readreg(Regctl)
CTRL |= number
self.writereg(Regctl, CTRL)
The DS3231 module also contains a temperature measuring unit that provides a value to the time unit every 64 seconds. This trimmed the oscillator and thus compensates for its temperature drift. The measurement can also be used by us. To do this, we only have to read out the two temperature register and put together the temperature in the chip. The method does that Temperature().
XXXXXXXXXX
def Temperature(self):
t1 = self.readreg(Ragemp)
t2 = self.readreg(Ragemp + 1)
IF t1 & 0x80:
return (-256 + t1) - (t2 >> 6)/4)
Else:
return t1 + (t2 >> 6)/4
The MSB (Ragemp = 0x11) contains the full -fashioned proportion of the temperature value and the LSB (0x12) in the upper two bits. If the MSB (MOST Significant byte) is set in the MSB (MOST Significant Bit = Bit7), the value in the two -way complement presentation represents a negative measured value.
Four service routines close the class DS3231 away. We meet here Dec2bcd(). The method transforms a decimal value from 0 to 99 including the BCD format. From the argument in dat we determine the resort of modulo 10, the one. The dialing number provides the integer division of DAT by 10. By multiplication at 16, we move the number of time into the upper four bits = high nibble. The one number remains in the low nibble. Adding the two nibble values results in the value of the BCD bytes.
XXXXXXXXXX
>>> dat=25
>>> dat % 10
5
>>> dat // 10
2
>>> hex((dat // 10)*16)
'0x20'
>>> hex((dat // 10)*16 + dat % 10)
'0x25'
XXXXXXXXXX
def Dec2bcd(self, dat):
dat=dat%100
return (dat//10) * 16 + (dat%10)
The opposite way goes BCD2dec(). The total number division of the value in dat The Zehnerziffer results from 16. You can just as well dat Slide to the right by four positions. We get the one -digit resort to modulo 16 or by unanimous with 0x0f.
XXXXXXXXXX
def BCD2dec(self, dat):
return (dat//16) * 10 + (dat%16)
or
XXXXXXXXXX
def BCD2dec(self, dat):
return (dat >> 4) * 10 + (dat & 0x0f)
The method is done by the writing order in a register writereg(). We hand over the register address and the data byte. From this we collect a nameless byte array through a nameless list, which together with the device address of the DS3231 of the routine I2C.Writeto() is handed over. This procedure is justified by the fact that writeto() An argument is expected to follow the Buffer protocol. This does not fulfill the integer format, as a result we need the array.
There is a similar restriction I2C.Readfrom(). First the register address must be sent, then the register content is received as a bytes object that also supports the Buffer protocol. In order for a numerical value to be returned, we address the first element with the index 0 in the Bytes object and receive the byte value.
XXXXXXXXXX
def readreg(self, Reg):
self.I2C.writeto(hwad, bytearar([Reg]))
return self.I2C.readfom(hwad, 1)[0]
A test program
Let us build a small test program that demonstrates the skills and use of the class DS3231. First of all, it is important to synchronize the RTC. That makes a call from sync_it.py out of the editor window. As usual, we start with some imports.
XXXXXXXXXX
# sync_it.py
From DS3231 import DS3231
From machine import Pin code,Softi2c
From OLED import OLED
From sys import exit
An I2C object is instantiated and on the constructor of the OLED object D hand over.
XXXXXXXXXX
I2C=Softi2c(scl=Pin code(22),sda=Pin code(23),freq=100000)
D=OLED(I2C,Heightw=32)
DS=DS3231(I2C)
Now the entry is waiting for a date and time in our known format. We enter a time in the immediate future and shortly before a comparison clock shows this time, we press the Enter key.
XXXXXXXXXX
Daytime=input("JJ, mm, TT, Dow, HH, MM, 0 \ n-> Synchronize with Enter:")
The next line is again syntactic sugar from the micropython-trick box, a so-called List comprehension. It creates an unnamed list of partial strings by dividing the input string. This is through the comprehension of iteratively, with an integer to the target list from each string of the source list dt is produced. This is elegant, efficient and corresponds to the following procedure.
XXXXXXXXXX
partial list=Daytime.split(",")
dt=[]
for X in partial list:
dt.append(intimately(X))
XXXXXXXXXX
dt=[intimately(X) for X in Daytime.split(",")]
We hand over the list to the DS3231. This synchronized the RTC, as the subsequent manual inputs and replies show.
XXXXXXXXXX
DS.DateTime(dt)
XXXXXXXXXX
>>> %run -C $Editor_content
This is the constructor of OLED class
Size:128X32
DS3231 ready
JJ,Mm,TT,dow,hh,mm,0
-> synchronize with Enter: 23,8,20,7,18,18,55,0
>>> DS.DateTime()
[2023, 8, 20, 7, 18, 19, 10]
>>> DS.dow[DS.DateTime()[3]]
'Sunday'
The next example, second alarm.py, shows the use of the second alarm of the alarm timer 1.
XXXXXXXXXX
# second alarm.py
From DS3231 import DS3231
From machine import Pin code,Softi2c, timer
From OLED import OLED
From sys import exit
From ESP32 import RMT
I2C=Softi2c(scl=Pin code(22),sda=Pin code(23),freq=100000)
alarm trigger=False
button=Pin code(13,Pin code.IN,Pin code.Pull_up)
D=OLED(I2C,Heightw=32)
RTC=DS3231(I2C)
The function alarm call() is called when the seconds of time and alarm timer matches. The function needs an obligatory parameter pin codethat in our case is assigned to the system 32. The routine can recognize from which PIN has a level change. Since an ISR (interrupt service routine) cannot return a value to a calling program (to whom?) The flag must alarm trigger global be declared, otherwise the main loop will not get any feedback. Nothing else happens here than that this flag True is set. An ISR should be kept as short as possible, it can no longer be shorter.
XXXXXXXXXX
def alarm call(pin code):
global alarm trigger
alarm trigger=True
Then we create the GPIO32 as an IRQ input that is connected to SQW of the DS3231. GPIO32 is declared an interruptable input that listens to falling flanks and then the routine when one alarm call() calls.
XXXXXXXXXX
rtcirq=Pin code(32, Pin code.IN)
rtcirq.IRQ(handler=alarm call, trigger=Pin code.Irq_falling)
We explain that at the minute distance, and the second 0, An IRQ should take place, snap out alarm2 and delete both status flags so that the SQW line goes to 3.3V = logical 1.
XXXXXXXXXX
RTC.Alarm1(0,0,0,0,DS3231.Secondary alarm) # every minute once
RTC.Alarm(2)
RTC.Clear alarm(1)
RTC.Clear alarm(2)
Some time can pass until the first full minute, which depends on the start time of the program. So that we do not think the ESP32 in the nirvana, we let ourselves be reported on the display.
XXXXXXXXXX
D.clearall(False)
D.writer("RTC Running",0,0)
The flag is in the main loop alarm trigger queried. If it is on True Stand must be traded. We read the status register of the DS3231 and put on the flag False. Then we check whether the alarm1 has set the trigger.
If this is the case, we delete the flag in the DS3231 and read a time recording. We spread the fields on the three strings dow, date and time. The integers and the format instructions can be generated very easily for the output.
The format string "{: 02d}. {: 02d}. "{: 02d}: {: 02d}".
Then we delete the display and also spend the day and date hidden. Covered means that the data is not sent to the OLED immediately, but only in its buffer memory on the ESP32. Only after the time string has ended up there is the entire buffer transferred to the display via I2C. This suppresses disturbing flickers and saves time, because otherwise with everyone writer() the entire buffer would be transferred.
XXXXXXXXXX
while 1:
IF alarm trigger:
status=RTC.Tell alarm status()
alarm trigger=False
IF status & 0x01:
RTC.Clear alarm(1)
dt=RTC.DateTime()
dow=RTC.dow[dt[3]]
date="{: 02d}. {: 02d}. {: 02d}".\
format(dt[2],dt[1],dt[0])
time= "{: 02d}: {: 02d}".format(dt[4],dt[5])
D.clearall(False)
D.writer(dow,0,0,False)
D.writer(date,0,1,False)
D.writer(time,0,2)
Now the display on the display is updated with every full minute.
Figure 4: circuit detail with DS3231
outlook
Of course, the OLED display for a real watch is "slightly" unsuitable. We want to change that in the next episode. We use a dimmable LED 7 segment display with 4 digits. Of course there is also a driver module for this.
Bye and stay tuned!