Đồng hồ thời gian thực sử dụng OLED NodeMCU, DS3231 và SSD1306
Dự án NodeMCU này cho thấy cách tạo đồng hồ thời gian thực với board phát triển này, chip DS3231 RTC và màn hình OLED SSD1306 (128 × 64 Pixel).
DS3231 RTC có cảm biến nhiệt độ tích hợp, chúng ta có thể sử dụng cảm biến này để phát hiện và đọc nhiệt độ của chip, màn hình SSD1306 cũng sẽ hiển thị nhiệt độ này.
DS3231 sử dụng giao diện I2C để giao tiếp với thiết bị chính, trong trường hợp này là board ESP8266 NodeMCU. Điều đó có nghĩa là màn hình OLED DS3231 và SSD1306 có chung bus I2C. Ngay cả khi chúng chia sẻ cùng một bus nhưng bất cứ lúc nào vi điều khiển giao tiếp với 1 thiết bị chỉ phụ thuộc vào địa chỉ được gửi. Địa chỉ DS3231 RTC là 0x68 và địa chỉ OLED SSD1306 là 0x3D.
Trong mạch có hai nút ấn để cài đặt thời gian và ngày của đồng hồ thời gian thực.
SSD SSD1306 được sử dụng trong dự án này được cấu hình để hoạt động ở chế độ I2C, một số board OLED SSD1306 có thể yêu cầu sửa đổi phần cứng (để chọn giữa chế độ SPI hoặc chế độ I2C) như hàn, nối dây.
Yêu cầu phần cứng:
- Board phát triển ESP8266 NodeMCU
- Màn hình OLED SSD1306 với độ phân giải 128 × 64 Pixel
- Board DS3231 – Board dữ liệu DS3231 RTC
- 2 x Nút ấn
- Pin di động 3V
- cáp micro USB (để lập trình và cấp nguồn cho mạch)
- Breadboard
- Dây dẫn
Mạch OLED NodeMCU + DS3231 RTC + SSD1306:
Hình ảnh sau đây cho thấy sơ đồ mạch dự án.
và cái thứ hai cho thấy mạch fritzing :
Các chân SDA và SCL của bus I2C nối với GPIO4 (D2) và GPIO0 (D3) của board NodeMCU (tương ứng), chúng được nối với các chân SDA và SCL (SCK) của mô-đun hiển thị SSD1306. Ngoài ra, chúng được nối với các chân SDA và SCL của mô-đun DS3231.
RESET (RES) của mô-đun hiển thị được nối với GPIO5 (D1) của board phát triển NodeMCU.
Hai nút ấn B1 và B2 là để cài đặt thời gian và ngày. Nút B1 được kết nối với chân GPIO12 (D6) và B2 được nối với chân GPIO13 (D7) của NodeMCU. Pull-up nội bộ cho hai chân đầu vào được kích hoạt trong code.
Mô-đun hiển thị SSD1306 và board DS3231 được cấp nguồn 3,3V từ board phát triển NodeMCU.
CODE dự án:
SSD1306 OLED và DS3231 RTC chia sẻ cùng một bus I2C và ESP8266 chỉ giao tiếp với 1 thiết bị tại một thời điểm tùy thuộc vào địa chỉ (được gửi bởi ESP8266), địa chỉ SSD1306 là 0x3D 0x68.
Code Arduino IDE bên dưới sử dụng trình điều khiển OLED Adafruit SSD1306 và thư viện Adafruit GFX. Nó không sử dụng bất kỳ thư viện nào cho DS3231 RTC. (Link tải các thư viện có trong các dự án trước)
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>#define OLED_RESET 5 // define SSD1306 OLED reset at ESP8266 GPIO5 (NodeMCU D1)Adafruit_SSD1306 display(OLED_RESET);const int button1 = 12; // button B1 is connected to ESP8266 GPIO12 (NodeMCU D6)const int button2 = 13; // button B2 is connected to ESP8266 GPIO13 (NodeMCU D7)void setup(void){pinMode(button1, INPUT_PULLUP);pinMode(button2, INPUT_PULLUP);delay(1000);Wire.begin(4, 0); // set I2C pins [SDA = GPIO4 (D2), SCL = GPIO0 (D3)], default clock is 100kHz// by default, we’ll generate the high voltage from the 3.3v line internally! (neat!)display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128×64)// init doneWire.setClock(400000L); // set I2C clock to 400kHz// Clear the display buffer.display.clearDisplay();display.display();display.setTextSize(1);display.setTextColor(WHITE, BLACK);display.drawRect(117, 56, 3, 3, WHITE); // Put degree symbol ( ° )draw_text(0, 56, “TEMPERATURE =”, 1);draw_text(122, 56, “C”, 1);}char Time[] = ” : : “;char Calendar[] = ” / /20 “;char temperature[] = ” 00.00″;char temperature_msb;byte i, second, minute, hour, day, date, month, year, temperature_lsb;void loop(){if(!digitalRead(button1)) // If button B1 is pressedif(debounce(button1, LOW)){i = 0;while(debounce(button1, HIGH) == 0); // Wait for button B1 to be releasedwhile(true){while(!digitalRead(button2)){ // While button B2 pressedday++; // Increment dayif(day > 7) day = 1;display_day(); // Call display_day functiondelay(200); // Wait 200 ms}draw_text(40, 0, ” “, 1);blink_parameter(); // Call blink_parameter functiondisplay_day(); // Call display_day functionblink_parameter(); // Call blink_parameter functionif(!digitalRead(button1)) // If button B1 is pressedif(debounce(button1, LOW))break;}date = edit(4, 14, date); // Edit datemonth = edit(40, 14, month); // Edit monthyear = edit(100, 14, year); // Edit yearhour = edit(16, 35, hour); // Edit hoursminute = edit(52, 35, minute); // Edit minuteswhile(debounce(button1, HIGH) == 0); // Wait for button B1 to be released// Convert decimal to BCDminute = ((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 data to DS3231 RTCWire.beginTransmission(0x68); // Start I2C protocol with DS3231 addressWire.write(0); // Send register addressWire.write(0); // Reset sesonds and start oscillatorWire.write(minute); // Write minuteWire.write(hour); // Write hourWire.write(day); // Write dayWire.write(date); // Write dateWire.write(month); // Write monthWire.write(year); // Write yearWire.endTransmission(); // Stop transmission and release the I2C busdelay(200); // Wait 200ms}Wire.beginTransmission(0x68); // Start I2C protocol with DS3231 addressWire.write(0); // Send register addressWire.endTransmission(false); // I2C restartWire.requestFrom(0x68, 7); // Request 7 bytes from DS3231 and release I2C bus at end of readingsecond = Wire.read(); // Read seconds from register 0minute = Wire.read(); // Read minuts from register 1hour = Wire.read(); // Read hour from register 2day = Wire.read(); // Read day from register 3date = Wire.read(); // Read date from register 4month = Wire.read(); // Read month from register 5year = Wire.read(); // Read year from register 6Wire.beginTransmission(0x68); // Start I2C protocol with DS3231 addressWire.write(0x11); // Send register addressWire.endTransmission(false); // I2C restartWire.requestFrom(0x68, 2); // Request 2 bytes from DS3231 and release I2C bus at end of readingtemperature_msb = Wire.read(); // Read temperature MSBtemperature_lsb = Wire.read(); // Read temperature LSBdisplay_day();DS3231_display(); // Diaplay time & calendardelay(50); // Wait 50ms}bool debounce(int button, bool s){int count = 0;for(int i = 0; i < 5; i++) {if (digitalRead(button) == s)count++;delay(10);}if(count > 2) return 1;else return 0;}void display_day(){switch(day){case 1: draw_text(40, 0, ” SUNDAY “, 1); break;case 2: draw_text(40, 0, ” MONDAY “, 1); break;case 3: draw_text(40, 0, ” TUESDAY “, 1); break;case 4: draw_text(40, 0, “WEDNESDAY”, 1); break;case 5: draw_text(40, 0, “THURSDAY “, 1); break;case 6: draw_text(40, 0, ” FRIDAY “, 1); break;default: draw_text(40, 0, “SATURDAY “, 1);}}void DS3231_display(){// Convert BCD to decimalsecond = (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 conversionTime[7] = second % 10 + ‘0’;Time[6] = second / 10 + ‘0’;Time[4] = minute % 10 + ‘0’;Time[3] = minute / 10 + ‘0’;Time[1] = hour % 10 + ‘0’;Time[0] = hour / 10 + ‘0’;Calendar[9] = year % 10 + ‘0’;Calendar[8] = year / 10 + ‘0’;Calendar[4] = month % 10 + ‘0’;Calendar[3] = month / 10 + ‘0’;Calendar[1] = date % 10 + ‘0’;Calendar[0] = date / 10 + ‘0’;if(temperature_msb < 0){temperature_msb = abs(temperature_msb);temperature[0] = ‘-‘;}elsetemperature[0] = ‘ ‘;temperature_lsb >>= 6;temperature[2] = temperature_msb % 10 + ‘0’;temperature[1] = temperature_msb / 10 + ‘0’;if(temperature_lsb == 0 || temperature_lsb == 2){temperature[5] = ‘0’;if(temperature_lsb == 0) temperature[4] = ‘0’;else temperature[4] = ‘5’;}if(temperature_lsb == 1 || temperature_lsb == 3){temperature[5] = ‘5’;if(temperature_lsb == 1) temperature[4] = ‘2’;else temperature[4] = ‘7’;}draw_text(4, 14, Calendar, 2); // Display the date (format: dd/mm/yyyy)draw_text(16, 35, Time, 2); // Display the timedraw_text(80, 56, temperature, 1); // Display the temperature}void blink_parameter(){byte j = 0;while(j < 10 && digitalRead(button1) && digitalRead(button2)){j++;delay(25);}}byte edit(byte x_pos, byte y_pos, byte parameter){char text[3];sprintf(text,“%02u”, parameter);while(debounce(button1, HIGH) == 0); // wait for button B1 to be releasedwhile(true){while(!digitalRead(button2)){ // If button B2 is pressedparameter++;if(i == 0 && parameter > 31) // If date > 31 ==> date = 1parameter = 1;if(i == 1 && parameter > 12) // If month > 12 ==> month = 1parameter = 1;if(i == 2 && parameter > 99) // If year > 99 ==> year = 0parameter = 0;if(i == 3 && parameter > 23) // If hours > 23 ==> hours = 0parameter = 0;if(i == 4 && parameter > 59) // If minutes > 59 ==> minutes = 0parameter = 0;sprintf(text,“%02u”, parameter);draw_text(x_pos, y_pos, text, 2);delay(200); // Wait 200ms}draw_text(x_pos, y_pos, ” “, 2);blink_parameter();draw_text(x_pos, y_pos, text, 2);blink_parameter();if(!digitalRead(button1)) // If button B1 is pressedif(debounce(button1, LOW)){i++; // Increament ‘i’ for the next parameterreturn parameter; // Return parameter value and exit}}}void draw_text(byte x_pos, byte y_pos, char *text, byte text_size){display.setCursor(x_pos, y_pos);display.setTextSize(text_size);display.print(text);display.display();}// End of code.
Video demo dự án: