В этой статье рассмотрим совместную работу ESP32 и модуля из 4 матриц 8×8 MAX7219 плюс фоторезистор.
Схема подключения всех компонентов выглядит следующим образом:

Прежде чем приступить к материалу, я Вас попрошу, если нравится то, что я делаю и хотите следить за моей деятельностью, то рекомендую подписаться на мой телеграмм канал: https://t.me/ypavla
Там я публикую новости о вышедших видео, статьях и разные устройства для умного дома и не только показываю.
Спасибо за внимание, теперь продолжим.
За основу был взят скетч с этой статьи, но значительно переделан и доработан. Автор данного Скетча Дамир Салахов из г. Самара. Почта “Damir@salakhov.info”
В данном скетче добавил:
- Восстановление соединения с Wi-Fi
- Нули заменены на букву “O” для удобочитаемости.
- Нарисован знак “градуса”.
- Добавлена стартовая строка для приветствия.
- Кнопка Включения и выключения строки с погодой, чтоб оставить только часы.
- Изменение яркости от фоторезистора
- %% облачности перевел на русский язык.
- Раздвинул цифры на часах для того чтобы не сливались из далека.
С данным человеком я переписывался буквально несколько дней. Но за эти дни я его стал очень сильно уважать. Человек уже в возрасте. Последнюю программу написал 30 лет назад и делает такие вещи, которые в наше время далеко ни каждый увлекающийся этим человек сможет сделать! В том числе и я)
По просьбе Дамира и выкладываю данную программу. Надеюсь кому-то пригодится мне то уж точно!
Ну что-ж приступим к самому скетчу:
/*
Подключения:
esp32 -> Matrix
GPIO23 -> DIN
GPIO18 -> Clk
GPIO5 -> CS
Фоторезистор пин 36. Делитель с пина 3.3в!!!
*/
#include <WiFi.h>
#include <ESP32WebServer.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Max72xxPanel.h>
#include <ArduinoJson.h>
// =======================================================================
// Конфигурация устройства:
// =======================================================================
const char* ssid = "ds"; // SSID
const char* password = ""; // пароль
String APIKEY = ""; // Чтобы получить API ключ, перейдите по ссылке http://openweathermap.org/api
String weatherLang = "&lang=ru";
String cityID = "499099"; //Samara
// =======================================================================
#define BUTTON_PIN 15 // кнопка включения/отключения бегущей строки с погодой
boolean weatherOn = "false";
WiFiClient client;
int photocellPin = 36; // фоторезистор
int brightness = 0;
boolean buttonWasUp = true; // была ли кнопка отпущена кнопка включения/отключения бегущей строки с погодой
String weatherMain = "";
String weatherDescription = "";
String weatherLocation = "";
String country;
int humidity;
int pressure;
float temp;
float tempMin, tempMax;
int clouds;
float windSpeed;
String date;
String currencyRates;
String weatherString;
int windDeg;
String windDegString;
String cloudsString;
String firstString;
int cntFailedWeather = 0; // Счетчик отсутстия соединения с сервером
int cntFailedTime = 0; // Счетчик отсутстия соединения с сервером
boolean start = true;
long period;
int offset=1,refresh=0;
int pinCS = 5; // Подключение пина CS
int numberOfHorizontalDisplays = 4; // Количество светодиодных матриц по Горизонтали
int numberOfVerticalDisplays = 1; // Количество светодиодных матриц по Вертикали
String decodedMsg;
Max72xxPanel matrix = Max72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
//matrix.cp437(true);
int wait = 20; // скорость бегущей строки
int spacer = 2;
int width = 5 + spacer; // Регулируем расстояние между символами
void setup(void) {
matrix.setIntensity(brightness); // Яркость матрицы от 0 до 15
pinMode(BUTTON_PIN, INPUT_PULLUP);
// начальные координаты матриц 8*8
matrix.setRotation(0, 1); // 1 матрица
matrix.setRotation(1, 1); // 2 матрица
matrix.setRotation(2, 1); // 3 матрица
matrix.setRotation(3, 1); // 4 матрица
Serial.begin(115200); // Дебаг
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); // Подключаемся к WIFI
while (WiFi.status() != WL_CONNECTED) { // Ждем до посинения
delay(500);
ScrollText(utf8rus("."));
Serial.print(".");
}
}
// =======================================================================
int updCnt = 0;
int dots = 0;
long dotTime = 0;
long clkTime = 0;
//int dx=0;
//int dy=0;
byte del=0;
int h,m,s;
// =======================================================================
void loop(void) {
setbrightness();
if(updCnt<=0) { // каждые 10 циклов получаем данные времени и погоды
updCnt = 10;
Serial.println("Getting data ...");
getWeatherData();
getTime();
Serial.println("Data loaded");
clkTime = millis();
}
if(millis()-clkTime > 15000 && !del && dots) { //каждые 15 секунд запускаем бегущую строку
if (cntFailedWeather != 0 || cntFailedTime != 0) {
ScrollText(utf8rus("Потеряно соединение с интернетом! Восстанавливается."));
WiFi.begin(ssid, password); // Подключаемся к WIFI
while (WiFi.status() != WL_CONNECTED) { // Ждем до посинения
delay(500);
ScrollText(utf8rus("."));
Serial.print(".");
}
cntFailedWeather =0 ;
cntFailedTime = 0;
}
if ( start ) { // Строка после включенмя один раз
firstString = (" Текущие время и погода в " + weatherLocation);
ScrollText(utf8rus(firstString));
start = ! start;
}
if (weatherOn) { // включение/отключение бегущей строки с погодой
ScrollText(utf8rus(weatherString));
}
updCnt--;
clkTime = millis();
}
DisplayTime();
if(millis()-dotTime > 500) {
dotTime = millis();
dots = !dots;
}
}
// =======================================================================
void DisplayTime(){
updateTime();
matrix.fillScreen(LOW);
int y = (matrix.height() - 8) / 2; // Центрируем текст по Вертикали
setbrightness();
scrollWeatherOn();
if(s & 1){matrix.drawChar(14, y-1, (String(":"))[0], HIGH, LOW, 1);} //каждую четную секунду печатаем двоеточие по центру (чтобы мигало)
else{matrix.drawChar(14, y-1, (String(" "))[0], HIGH, LOW, 1);}
scrollWeatherOn(); // кнопка включения/отключения бегущей строки с погодой
String hour1 = String (h/10);
String hour2 = String (h%10);
String min1 = String (m/10);
String min2 = String (m%10);
String sec1 = String (s/10);
String sec2 = String (s%10);
int xh = 0;
int xm = 20;
// Везде меняем нули на О
if (hour1[0] == "0"[0]) {
matrix.drawChar(xh, y, "O"[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(xh, y, hour1[0], HIGH, LOW, 1);
}
if (hour2[0] == "0"[0]) {
matrix.drawChar(xh+7, y, "O"[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(xh+7, y, hour2[0], HIGH, LOW, 1);
}
if (min1[0] == "0"[0]) {
matrix.drawChar(xm, y, "O"[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(xm, y, min1[0], HIGH, LOW, 1);
}
if (min2[0] == "0"[0]) {
matrix.drawChar(xm+7, y, "O"[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(xm+7, y, min2[0], HIGH, LOW, 1);
}
matrix.write(); // Вывод на дисплей
}
// =======================================================================
void DisplayText(String text){
matrix.fillScreen(LOW);
for (int i=0; i<text.length(); i++){
int letter =(matrix.width())- i * (width-1);
int x = (matrix.width() +1) -letter;
int y = (matrix.height() - 8) / 2; // Центрируем текст по Вертикали
if (String(text[i]) == "0") {
matrix.drawChar(x, y, (String("O"))[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(x, y, text[i], HIGH, LOW, 1);
}
matrix.write(); // Вывод на дисплей
}
}
// =======================================================================
void ScrollText (String text){
for ( int i = 0 ; i < width * text.length() + matrix.width() - 1 - spacer; i++ ) {
if (refresh==1) i=0;
refresh=0;
matrix.fillScreen(LOW);
int letter = i / width;
int x = (matrix.width() - 1) - i % width;
int y = (matrix.height() - 8) / 2; // Центрируем текст по Вертикали
while ( x + width - spacer >= 0 && letter >= 0 ) {
if ( letter < text.length() ) {
if (String( text[letter]) != "'" ) { // Рисуем знак градуса вместо апострофа
if (String(text[letter]) == "0") { // меняем нули на О
matrix.drawChar(x, y, "O"[0], HIGH, LOW, 1);
} else
{
matrix.drawChar(x, y, text[letter], HIGH, LOW, 1);
}
} else {
matrix.drawPixel ( x+1,y,HIGH); // Рисуем знак градуса
matrix.drawPixel ( x+2,y,HIGH);
matrix.drawPixel ( x,y+1,HIGH);
matrix.drawPixel ( x+3,y+1,HIGH);
matrix.drawPixel ( x,y+2,HIGH);
matrix.drawPixel ( x+3,y+2,HIGH);
matrix.drawPixel ( x+1,y+3,HIGH);
matrix.drawPixel ( x+2,y+3,HIGH);
}
}
letter--;
x -= width;
}
matrix.write(); // Вывод на дисплей
setbrightness();
scrollWeatherOn(); // кнопка включения/отключения бегущей строки с погодой
delay(wait);
}
}
// =======================================================================
// Берем погоду с сайта openweathermap.org
// =======================================================================
const char *weatherHost = "api.openweathermap.org";
void getWeatherData() //client function to send/receive GET request data.
{
String result ="";
WiFiClient client;
const int httpPort = 80;
if (!client.connect(weatherHost, httpPort)) {
Serial.println("connection to openweather failed");
cntFailedWeather++;
return;
}
else {
Serial.println("connection to openweather ok");
cntFailedWeather = 0;
}
// We now create a URI for the request
String url = "/data/2.5/weather?id="+cityID+"&units=metric&cnt=1&APPID="+APIKEY+ weatherLang;
// This will send the request to the server
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + weatherHost + "\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
client.stop();
return;
}
}
// Read all the lines of the reply from server
while(client.available()) {
result = client.readStringUntil('\r');
}
result.replace('[', ' ');
result.replace(']', ' ');
char jsonArray [result.length()+1];
result.toCharArray(jsonArray,sizeof(jsonArray));
jsonArray[result.length() + 1] = '\0';
StaticJsonBuffer<1024> json_buf;
JsonObject &root = json_buf.parseObject(jsonArray);
if (!root.success())
{
Serial.println("parseObject() failed");
}
weatherMain = root["weather"]["main"].as<String>();
weatherDescription = root["weather"]["description"].as<String>();
weatherDescription.toLowerCase();
weatherLocation = root["name"].as<String>();
country = root["sys"]["country"].as<String>();
temp = root["main"]["temp"];
humidity = root["main"]["humidity"];
pressure = root["main"]["pressure"];
windSpeed = root["wind"]["speed"];
clouds = root["main"]["clouds"]["all"];
String deg = String(char('~'+25));
weatherString = " Температура " + String(temp,1) + "'C ";
weatherString += " Влажность " + String(humidity) + "% ";
weatherString += " Давление " + String(int(pressure/1.3332239)) + "ммРтСт ";
weatherString += " Ветер " + String(windSpeed,1) + "м/с ";
if (clouds <= 10) cloudsString = " Ясно";
if (clouds > 10 && clouds <= 30) cloudsString = " Малооблачно";
if (clouds > 30 && clouds <= 70) cloudsString = " Средняя облачность";
if (clouds > 70 && clouds <= 95) cloudsString = " Большая облачность";
if (clouds > 95) cloudsString = " Пасмурно";
weatherString += cloudsString;
Serial.println(weatherString);
}
// =======================================================================
// Берем время у GOOGLE
// =======================================================================
float utcOffset = 4; //поправка часового пояса
long localEpoc = 0;
long localMillisAtUpdate = 0;
void getTime()
{
WiFiClient client;
if (!client.connect("www.google.com", 80)) {
Serial.println("connection to google failed");
cntFailedTime++;
return;
}
Serial.println("connection to google ok"); cntFailedTime = 0;
client.print(String("GET / HTTP/1.1\r\n") +
String("Host: www.google.com\r\n") +
String("Connection: close\r\n\r\n"));
int repeatCounter = 0;
while (!client.available() && repeatCounter < 10) {
delay(500);
//Serial.println(".");
repeatCounter++;
}
String line;
client.setNoDelay(false);
while(client.connected() && client.available()) {
line = client.readStringUntil('\n');
line.toUpperCase();
if (line.startsWith("DATE: ")) {
date = " "+line.substring(6, 22);
h = line.substring(23, 25).toInt();
m = line.substring(26, 28).toInt();
s = line.substring(29, 31).toInt();
localMillisAtUpdate = millis();
localEpoc = (h * 60 * 60 + m * 60 + s);
}
}
client.stop();
}
// =======================================================================r
void updateTime()
{
long curEpoch = localEpoc + ((millis() - localMillisAtUpdate) / 1000);
long epoch = round(curEpoch + 3600 * utcOffset + 86400L) % 86400L;
h = ((epoch % 86400L) / 3600) % 24;
m = (epoch % 3600) / 60;
s = epoch % 60;
}
// =======================================================================
String utf8rus(String source)
{
int i,k;
String target;
unsigned char n;
char m[2] = { '0', '\0' };
k = source.length(); i = 0;
while (i < k) {
n = source[i]; i++;
if (n >= 0xC0) {
switch (n) {
case 0xD0: {
n = source[i]; i++;
if (n == 0x81) { n = 0xA8; break; }
if (n >= 0x90 && n <= 0xBF) n = n + 0x30-1;
break;
}
case 0xD1: {
n = source[i]; i++;
if (n == 0x91) { n = 0xB8; break; }
if (n >= 0x80 && n <= 0x8F) n = n + 0x70-1;
break;
}
}
}
m[0] = n; target = target + String(m);
}
return target;
}
void setbrightness()
{
int photocellReading = analogRead(photocellPin);
// мы должны инвертировать считываемые значения от 0-4095 к 4095-0
photocellReading = 4095 - photocellReading;
// теперь мы должны преобразовать диапазон 0-4095 в 0-15 (экспериментально)
brightness = map(photocellReading, 0, 3500, 2, 15);
if (brightness > 15 ) {brightness = 15;}
matrix.setIntensity(brightness); // Яркость матрицы от 0 до 15
}
void scrollWeatherOn()
{
// сначала понимаем, отпущена ли кнопка прямо сейчас...
boolean buttonIsUp = digitalRead(BUTTON_PIN);
// ...если «кнопка была отпущена и (&&) не отпущена сейчас»...
if (buttonWasUp && !buttonIsUp) {
// ...может это «клик», а может и ложный сигнал (дребезг),
// возникающий в момент замыкания/размыкания пластин кнопки,
// поэтому даём кнопке полностью «успокоиться»...
delay(10);
// ...и считываем сигнал снова
buttonIsUp = digitalRead(BUTTON_PIN);
if (!buttonIsUp) { // если она всё ещё нажата...
// ...это клик! Переворачиваем сигнал светодиода
weatherOn = !weatherOn;
Serial.println(weatherOn);
}
}
// запоминаем последнее состояние кнопки для новой итерации
buttonWasUp = buttonIsUp;
}
Ссылка на скетч : https://yadi.sk/d/XpUlyTIU790vLw
В принципе тут и добавить нечего. Все функции описаны. Все понятно, что и откуда брать. Единственное для некоторых может быть непонятно где взять (String cityID = “499099”). Это id города которого хотите видеть погоду. Он берется из адресной строки браузера. Например вы ввели на сайте http://openweathermap.org город Mosow и вам открылась погода города Москва. Адрес данной страницы выглядит таким образом: https://openweathermap.org/city/524901 . Где цифры это и есть id нашего города.
Ну думаю на этом все. У кого будут вопросы обязательно пишите Дамиру и он обязательно Вам поможет.
P.S. Спасибо еще раз Дамиру Салахову за проделанный труд. Побольше бы таких как Вы. Спасибо.