4 Bits Digital Tube LED Display Module Four Serial for Arduino 595 Driver-Y103 (4-хразрядный 7-сегментный индикатор на сдвиговом регистре 74hc595)

- Цена: $1.38 (Брал у другого продавца, но там кончились)
- Перейти в магазин
Каша из топора.
Бывает, что какая-то незначительная деталька служит причиной взяться за проект только ради того, чтобы пристроить эту детальку…
Достались мне такие экранчики по недоразумению. Искал семисегментный индикатор для поделки, в поиске были только такие и на ТМ1637. Но 1637 – были «часовые», с двоеточием между часами-минутами, а мне нужен был с десятичными точками. Вот и взял их.


Как видно, пайка средней паршивости – индикатор и гребенку припаивали явно руками, флюс местами просвечивает. Но — работает.
Немного физических параметров








Схему вызванивать не стал, нашел первое подходящее в инете:

Попробовав их подключать – разочаровался. Уж очень мало нашлось инструкций по работе с ними. Никаких библиотек, только редкие скетчи, которые не всегда толком повторить получалось. Повозился, пока кто-то не объяснил: динамическая индикация… Что это значит? Одновременно у индикатора можно зажечь только одну цифру. А значит – для отображения всех 4-х цифр нужно быстро-быстро зажигать цифры одну-за-другой. Это самое быстро-быстро – и есть недостаток этих индикаторов. Потому что это, во-первых, трата процессорного времени. Во-вторых – необходимость следить, чтобы другие процессы не занимали процессор на долгое время – иначе индикатор начнет «мигать».
Лежали у меня эти экранчики с нового года. Решил – надо их как-то утилизировать. Как всегда – самое сложное в ардуиностроении – придумать, что же такого сделать в этот раз? Частично ответ на этот вопрос может дать гугл

Но мне повезло. Родной ЖКХ, обрадовавшись моим платежам за капремонт, решил планово отключить горячее водоснабжение. Что повлекло необходимость включить водогрейку. Водогрейка старенькая, с одной крутилкой (ТЭНы пора бы проревизировать), и вот призадумался – а до какой температуры она греет? Ну вот – и задача: сделать термометр с показаниями температуры на этот индикатор.
Задействовать под это дело ардуину было жалко – это даже не «из пушки по воробьям»… Поэтому, в хозяйстве нашел такие штучки (другой продавец, у которого брал я — кончились):

Это микроконтроллер, ну прямо почти совсем микро. У него всего 8 ног. Учитывая. Что 2 – это питание и одна — ресет, остается совсем не густо. Ресет правда можно забрать, но вот если проект будет уличный – то придется отдать пару ног под внешний кварц… В общем – с ресурсами ног для большого проекта тут туго. Но нам для индикатора нужно всего 3 ноги. И еще 1 ногу под термодатчик. Да и по условиям ТЗ никакого уличного исполнения не требуется – так что вполне хватит (еще и остается).
Надо сказать – тинька в дип-корпусе, возможно, не лучший выбор. Для ее программирования потребуется программатор. Да, такой программатор делается из любимой ардуины просто подключением проводов. Но если у вас нет свободной ардуины – можно обратить внимание на такую штучку, как дигиспарк. Такие или такие. Это та же тинька, только на плате с гребенкой контактов, стабилизатором питания и, главное, интерфейсом УСБ для прошивки. На вид – очень удобная штука. Но я пока не пробовал, подробностей не расскажу.
Итак, процессор есть, индикатор есть, остался – датчик. За датчик возьмем любимый даллас, знакомый нам со времен метеостанции:

Ну и чтобы собрать все – кусочек макетной платы (посчитал что ЛУТ для этого напрягать не стоит…).
Ну еще понадобятся провода и один резистор на 4,7К

Схема получилась простая, даже для макетки.

Макетку закрепил прямо на выводы индикатора (пришлось их выпаять и удлинить чтобы иметь возможность шить тиньку при необходимости)
Это что касается железа. Но есть же и вторая сторона – софт. Надо сказать, что в среде разработки ардуино есть возможность «подключить» разные типы процессоров и компилировать прошивки под них. Само программирование при этом остается в довольно удобной среде – с возможностью компиляции и отладки. А еще – написанную программу можно скомпилировать под другой процессор просто переключив тип процессор в настройках. Таким образом – работа с термодатчиком сводилась к выбору наиболее удобного куска кода из примеров оболочки (или из тысяч готовых примеров в интернете). А вот индикатор заставил помучатся. В гугле нашлось несколько вариантов работы с индикатором. Но из-за динамической индикации (см выше) это все было несколько неудобно… Поковырявшись пару вечеров – сделал для себя пару подпрограмм, которые позволяли писать основной код относительно комфортно.
На что обратить внимание? Первое: подпрограмма «рисования» на индикаторе должна уметь нарисовать нужные нам символы. 7-сегментный индикатор позволяет кодировать символы одним байтом – 8 бит — это как раз 7 сегментов + десятичная точка. Берем «маску» для цифр из найденных примеров, дорисовываем нужные нам символы (при желании – можно реализовать бОльшую часть латинского алфавита). Готово.
Второе. Нам нужно вызывать эту подпрограмму постоянно. Но, в коде опроса датчика температуры, например, есть место с паузой – нужно ждать минимум 750миллисекунд. Можно попробовать вместо паузы засечь время и пропускать последующий код, пока не пройдет нужное время. А можно написать еще одну процедуру, в которую передавать время паузы. И пусть она сама дергает рефреш индикатора… Такой подход позволит использовать паузу в разных местах, не перегружая код (эта подпрограмма во второй части обзора).
Третье – работать с масками символов не очень удобно. Температуру то нам датчик вернет в виде десятичного значения, а не в виде маски… Значит – делаем еще одну процедуру (зачеркнуто) функцию, которая по значению цифры вернет ее код
Таким образом – работать с индикатором стало вполне удобно. Пишем небольшой код, отлаживаем, запускаем. Добавляем какие-ни-будь фишки, пьем пиво. За несколько вечеров проект запустился и отлично работал. Мигания индикатора (динамическая индикация) не было заметно (для такой маленькой задачи это не удивительно). Начал собирать все на макетку и думать, как встроить полученную конструкцию в водогрейку (блок питания нашелся в виде зарядки от старого мобильника).


Пока отлаживался на тиньку, пришлось научиться пользоваться логическим анализатором. Шикарная штука! У меня не получалось тинькой считать температуру. Анализатор, оказывается, «знает» протоколы, в том числе – 1wire.
Дополнительная информация

При считывании работы тиньки сразу показал, что не выдержаны временные интервалы…. Решилось прошивкой в тиньку загрузчика (все есть в ардуино ide) – видимо пришли от китайцев с загрузчиком на 1мгц, а я шил ее как 8…
В общем – все заработало!

Код получился такой:
// Экран 595 и термодатчик на Attyny85
// © Naevus 2016
#define SCLK 2 // пины Attyny85
#define RCLK 1
#define DIO 0
byte digitBuffer[4];
#include <OneWire.h>
OneWire oneWire(4); // пин термодатчика
float Temp;
OneWire ds(4);
long TMillis = 0;
long TPause = 5000; // интервал опроса датчика
long PMillis = 0;
long PPause = 800; // пауза на датчике для протокола (минимум 750)
int s = 0; //скринсейвер
long SMillis = 0;
long SPause = 20000; // интервал скринсейвера
const byte chr[4] = { // маска для разряда
0b00001000,
0b00000100,
0b00000010,
0b00000001
};
int T = 0; // сотни
int D = 2; // десятки
int U = 0; // единицы
void setup(void)
{
pinMode(RCLK, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(DIO, OUTPUT);
pinMode(3, OUTPUT);
}
void loop(void)
{
// скринсейвер
if (millis() > SMillis){
if (s == 1) {s = 0;} else {s = 1;};
if (T != 0 && T != 10 ) {s=1;};
SMillis = millis()+SPause;
}
// Если пауза больше, то читаем датчик
if(millis() > TMillis) {
digitalWrite(3,LOW);
byte i;
byte present = 0;
byte data[12];
byte addr[8];
ds.reset();
ds.skip();
// Start conversion
ds.write(0x44, 1);
// Тут по протоколу долна быть пауза 750 минимум
PMillis = millis()+PPause;
while (millis() < PMillis){
// Пока пауза - рефрешим дисплей
showDisplay(); // delay(1);
};
present = ds.reset();
ds.skip();
// Issue Read scratchpad command
ds.write(0xBE);
// Receive 9 bytes
for ( i = 0; i < 9; i++) { data[i] = ds.read(); }
// Calculate temperature value
Temp = ( (data[1] << 8) + data[0] )*0.0625;
// Сотни
T = Temp/100;
// Десятки
D = (Temp - (T*100)) / 10;
// Единицы
U = Temp - (T*100) - (D*10);
if (T == 0) {T = 10;} // пусто
if (s==0) { digitBuffer[3]= 10; }
else { digitBuffer[0]= T; };
digitBuffer[0+s]= D;
digitBuffer[1+s]= U;
digitBuffer[2+s]= 11;
digitalWrite(3,HIGH);
TMillis = millis()+TPause;
}
//595
showDisplay();
}
void showDisplay(){
const byte digit[18] = { // маска для 7 сигментного индикатора
0b11000000, // 0
0b11111001, // 1
0b10100100, // 2
0b10110000, // 3
0b10011001, // 4
0b10010010, // 5
0b10000010, // 6
0b11111000, // 7
0b10000000, // 8
0b10010000, // 9
0b11111111, // 10 Пусто
0b10011100, // 11 Градус
0b10001000, // 12 A
0b11000110, // 13 C
0b10100001, // 14 d
0b10000110, // 15 E
0b10101111, // 16 r
0b10001011, // 17 h
};
// отправляем в цикле по два байта в сдвиговые регистры
for(byte i = 0; i <= 3; i++){
digitalWrite(RCLK, LOW); // открываем защелку
shiftOut(DIO, SCLK, MSBFIRST, digit[digitBuffer[i]]); // отправляем байт с "числом"
shiftOut(DIO, SCLK, MSBFIRST, chr[i]); // включаем разряд
digitalWrite(RCLK, HIGH); // защелкиваем регистры
}
}
Похвастался конструкцией товарищу. Он тут же дал мне ссылку на сообщество ардуинщиков на драйве (сам он заядлый участник драйва). Зашел ради интереса. И в первых же строках увидел топик «Безнадега…» с фотографией обозреваемого индикатора. Человек просил помощи в подключении этого чуда. Я не мог оставить просьбу о помощи без внимания. Отписал… Завязалась переписка… В общем – на пару недель я занялся его проблемой. От него было четкое ТЗ (см начало обзора про проблемы с придумыванием), что служило стимулом к работе. Неудобно было делать все дистанционно, но я уже имел опыт «редактирования autoexec.bat по телефону с бухгалтером», так что боле мене мы справились. Из неудач – нашел удобную библиотеку под RTC, но она не заработала у «заказчика». Пришлось переделывать на библиотеку, которая у него запускалась. Код поэтому страшный – преобразование типов (как и их выбор) наверняка можно сделать проще. Но главное – работает.
С этой задачей я забросил свой водонагревательный проект на самом завершающем этапе. А потом уже и воду горячую дали…
… Поэтому, вторая часть марлезонского балета и второй пример использования обозреваемого индикатора.
Итак, задача была простая: выводить на индикатор время от rtc и периодически менять его на показания температуры. В процессе реализации проект немного оброс фичами. Постарался сделать их отключаемыми (доделываю потихоньку). Что получилось:
- Часы. Возможность корректировки показаний двумя кнопками. «Ход» часов обеспечивает модуль RTC с отдельным питанием.
- Термометр. Сменяет показания часов по кнопке или по времени (настраивается перед прошивкой)
- Вольтметр. Меняет показания по кнопке. Может контролировать выход напряжения за заданный предел и мигать индикатором
- Управление реле по напряжению – пределы включения и выключения задаются отдельно.
Что для этого понадобилось? Ну кроме индикатора и термодатчика из первой половины обзора:
- Ардуина. Да, Тиньки тут уже мало – по количеству ног не вписывается. Зато на ардуине можно в дальнейшем наращивать фичи. Уже есть парочка в планах.
Подойдет любая ардуина. Я использую нано,
Дополнительная информация
она подходящих размеров и имеет порт УСБ, что позволяет шить ее непосредственно компьютером. (ардуино мини, например, меньше размером, но без порта – шить ее можно другой ардуиной или программатором) - Модуль часов RTC. У меня под рукой был такой (другой продавец).
Дополнительная информация
А у «заказчика» – такой (другой продавец, вернее, я вообще такие не заказывал еще). Это добавило головняков с несовместимостью библиотек и отладкой. - Пара резисторов. Неплохо бы подобрать резисторы с минимальным температурным коэффициентом. Ну или хотя-бы максимально одинаковых. Предлагаю взять 4 одинаковых резистора (5-10килоом) и соединить три из них последовательно. Получится делитель 4:1, что позволит измерять напряжение до 20вольт. Если у вас в хламе не найдется 4 резисторов – в ближайшем магазине они стоят копейки.
- Кнопки. Я для тестирования использовал первые попавшиеся. У «заказчика» кнопки были в подготовленном корпусе для устройства.
- Реле. На один канал (другой продавец)
Дополнительная информация
или на два (для будущих фич) (другой продавец)
Дополнительная информация
- Питание. До реализации в авто я не дошел (да и не собирался пока), поэтому вопрос питания оставил на усмотрение «заказчика». Простое решение – старый автомобильный зарядник для телефона.
Итак, собираем все в кучу и допиливаем код. Постарался комментировать в коде по максимуму. В принципе там основа – та же процедура что была и в термометре для водогрейки. Остальное – просто использует ее в нужный момент.
Код
// Термометр, часы 1307 и вольтметр на 7-сегментном дисплее на 595 (динамическая индикация)
// © Naevus 2016
/// Настройки. Все насройки, которые слдует просмотреть/изменить под себя помечены тремя косыми чертами ///
/// Блоки условного компилирования. Если какое то оборудование не используется - отключите его и прошивка
/// скомпилируется без этого функционала (и можно не настраивать соответствующий блок в настройках
#define D7SEG 1 /// Если подключен 7 сегментный дисплей, то 1 иначе 0.
#define VOLT 1 /// Если подключен вольтметр... (мигать экраном при выходе напряжения за пределы)
#define DS1820 1 /// Если подключен термодатчик...
#define RTCDS 1 /// Если подключен модуль RTC...
#define BUTTN 1 /// Если подключены кнопки...
#if D7SEG == 1
/// Экран на 595 сдвиговом регистре
#define SCLK 12 /// пины для подключения экрана
#define RCLK 11 ///
#define DIO 10 ///
byte digit[4]; // Буфер значений знакомест. В буфер помещается маска выводимых символов для отправки на дисплей
#endif
#if VOLT == 1
/// Вольтметр
//
// Измеряемое
// напряжение ─┐
// ┌┴┐
// │ │R2
// └┬┘
// ├───── Аналоговый вход Arduino
// ┌┴┐
// │ │R1
// └┬┘
// ═╧═
//
// Rmax должно быть минимум в 3 раза больше Rmin. (позволяет измерять напряжение до 20в).
// Например 30 и 10 КОм соответственно. Лучше взять 4 одинаковых резистора, один R1 и три последовательно на R2
#define R1 7.3 /// Резистор в нижнем плече делителя. У меня 7,3К
#define R2 29.5 /// Резистор в верхнем плече делителя. У меня 29,5К.
#define VIN A0 /// аналоговый вход для вольтметра
#define VCORR 0.912 /// коэффициент корректировки
/// Для расчета коэффициента:
/// 1. выставляем коэффициент = 1
/// 2. Выставляем vmin = 100
/// 3. Компилируем, запускаем.
/// 4. Подключаем источник напряжения (АКБ 12В)
/// 5. Записываем показания индикатора (например 13,5)
/// 6. Замеряем это же напряжение образцовым вольтметром. Записываем (например 12,3)
/// 7. Делим замеренное на показаное. Т.е. 12,3/13,5=0,911
/// 8. Это и есть коэффициент для вашего экземпляра ардуины. Прописываем его в vcorr
/// 9. Не забываем вернуть обратно vmin
/// Если не подключен или не используется - задайте VMIN = 0 а VMAX = 1000.
#define VMIN 0 /// минимальное напряжение для аларма (если ниже - будет мигать)
#define VMAX 15.1 /// максимальное напряжение для аларма (если выше - будет мигать)
#define VRPIN 13 /// Пин для подключения реле
#define VRMIN 11.0 /// Реле выключается если напряжение меньше VRMIN
#define VRMAX 12.5 /// Реле включается если напряжение больше VRMAX
float vvalue; // Напряжение
#endif
#if DS1820 == 1
/// Термодатчик
#define TPIN 9 /// Пин термодатчика. Тип термодатчика DS18x20 определяется автоматически
#include <OneWire.h> // Библиотека термодатчика
OneWire ds(TPIN);
byte type_s = 0; // Тип датчика (после автоматического определения)
#endif
/// Часы
#define POINTTIME 500 /// Полупериод мигания точкой - полсекунды. Этот же период берется за основу для мигания прочих элементов.
#define SETPAUSE 500 /// Пауза для быстрой смены показаний при настройке часов при удержании кнопки set
#include <Time.h>
#include <DS1307RTC.h>
#include <Wire.h>
long pointmillis; // Время для мигания точкой
int point = HIGH; // состояние точки в данный момент
int H; // часы
int M; // минуты
long setmillis=0; //Время для паузы корректировки показаний длинного нажатия
#if BUTTN == 1
/// Кнопки. Кнопки без резисторов, замыкание на землю.
// "Режим" - длинное нажатие переводит в режим установки часов. Выход из режима так же или через 5сек
// Короткое нажатие - меняет режим установки часов на минут и обратно.
// В режиме часов короткое нажатие показывает напряжение
// "Установка" - в режиме часов показывает термометр.
// В режиме корректировки меняет значение часов или минут.
#define MPIN 8 /// пин кнопки "Режим"
#define SPIN 7 /// пин кнопки "Установка"
#define BPAUSE 2000 /// пауза для длинного нажатия 2 сек
#define AD 5 /// пауза для антидребезга. подбирать для конкретных кнопок экспериментально
#define TOPAUSE 5000/// пауза для автоматического выхода из режима уствновки часов-минут
int mstate = 0; // состояние кнопки mode
int sstate = 0; // состояние кнопки set
// 0 - не нажата
// 1 - нажата, не отпущена (короткое нажатие)
// 2 - нажата, отпущена (короткое нажатие)
// 3 - нажата не отпущено (длинное нажатие)
// 4 - нажата, отпущена (длинное нажатие)
long mmillis; // время для запоминания нажатия кнопки mode
long smillis; // время для запоминания нажатия кнопки set
#include "Bounce2.h"; // Библиотека с антидребезгом
Bounce mbutton = Bounce(); // Антидребезговые кнопки
Bounce sbutton = Bounce();
#endif
/// Общие переменные и константы
#define CPAUSE 20000 /// Время отображения часов - 20 сек
#define TPAUSE 3500 /// Время отображения градусника (или напряжения) - 3.5 сек
long ctmillis = 0; // Время для счетчика переключателя часы-температура
int state; // Режим работы:
// 0 - Часы. Отображает часы, мигает точкой раз в секунду. Каждые (задано) сек переключается в режим температуты
// 1 - Термометр. Запрашивает температуру, отображает заданое время, по окончании - возврат в часы.
// 2 - Установка часов. Вход а режим - длительное нажатие кнопки mode. Выход так же или через (задано) сек неактивности.
// 3 - Установка минут. Вход а режим - короткое нажатие кнопки mode из режима установки часов. Выход так же или через (задано) сек неактивности.
// 4 - Аларм по напряжению. Мигает напряжением если оно вышло за пределы
// 5 - Напряжение. время отображения - как у термометра
// Из 2 в 3 и обратно - короткое нажатие mode. В 2 и 3 нажатие set меняет показания часов/минут.
// Если состояние требует различать вход и повторный вход, то для повторного к состоянию добавляем 10.
// Т.е. 0 - часы первый запуск, 10 - часы последующие запуски
char dbuffer [6]; // буффер для строки с запятой
void setup(void)
{
// Инициализация пинов экрана
pinMode(RCLK, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(DIO, OUTPUT);
#if BUTTN == 1
// Инициализация пинов кнопок.
pinMode(MPIN, INPUT_PULLUP);
pinMode(SPIN, INPUT_PULLUP);
// инициализация антидребезга
mbutton.attach(MPIN);
sbutton.attach(SPIN);
mbutton.interval(AD);
sbutton.interval(AD);
#endif
#if VOLT == 1
// Инициализация пина вольтметра
pinMode(VIN, INPUT);
pinMode (VRPIN, OUTPUT);
#endif
#if DS1820 == 1
//Поиск и распознавание термодатчика
byte addr[8];
if ( !ds.search(addr)) {ds.reset_search(); type_s = 99;} // датчик не найден
else {switch (addr[0]) { // Первый байт - тип датчика
case 0x10: type_s = 1; break; //DS18S20 или старый DS1820
case 0x28: type_s = 0; break; //DS18B20
case 0x22: type_s = 0; break; //DS1822
default: type_s = 99; //ошибка определения типа датчика
}
}
#endif
// Инициализация часов
setSyncProvider(RTC.get); // получаем время с RTC
///установим вручную - один раз установили и хватит
///TimeElements te;
///te.Second = 0; //секунды
///te.Minute = 53; //минуты
///te.Hour = 12; //часы
///te.Day = 16; //день
///te.Month = 2; // месяц
///te.Year = 2016 - 1970; //год в библиотеке отсчитывается с 1970
///time_t timeVal = makeTime(te);
///RTC.set(timeVal);
///setTime(timeVal);
state = 0; // По умолчанию - часы
}
void loop(void)
{
switch(state){
case 0 : rclock(); break; // отрабатываем часы (первый раз)
case 10 : rclock(); break; // отрабатываем часы
#if DS1820 == 1
case 1 : rtemp() ; break; // отрабатываем термометр (первый раз)
case 11 : rtemp() ; break; // отрабатываем термометр
#endif
#if BUTTN == 1
case 2 : rhour (); break; // установка часов
case 12 : rhour (); break; // установка часов
case 3 : rmin (); break; // установка минут
case 13 : rmin (); break; // установка минут
#endif
#if VOLT == 1
case 4 : rvalarm(); break; // отрабатываем аларим по напряжению (первый раз)
case 14 : rvalarm(); break; // отрабатываем аларим по напряжению
case 5 : rvolt() ; break; // отрабатываем напряжение (первый раз)
case 15 : rvolt() ; break; // отрабатываем напряжение
#endif
default : rclock();
}
#if VOLT == 1
// Вольтметр
float vread = analogRead(VIN); // Читаем АЦП
vvalue = vread * ((5*VCORR*(R1+R2)/R1)/1024); // Преобразуем к напряжению
if (vvalue < VRMIN){digitalWrite(VRPIN,LOW);} // Выкл реле если напряжение меньше
if (vvalue > VRMAX){digitalWrite(VRPIN,HIGH);} // Вкл реле если напряжение больше
if (vvalue > VMAX || vvalue < VMIN) {state = 4;};// аларм вольтметра
#endif
#if BUTTN == 1
rbutton(); // опрашиваем кнопки
#endif
showDisplay(); // Рефрешим дисплей
}
#if BUTTN == 1
void rbutton (void) { // Кнопки
mbutton.update(); // Mode
switch (mstate){
case 0: if (!mbutton.read()){mstate = 1; mmillis = millis() + BPAUSE;}// нажали кнопку
break;
case 1: if ( mbutton.read()){mstate = 2;} // отжали кнопку
else {if (millis()>mmillis){mstate = 3;}} // длинное нажатие
break;
case 2: mstate = 0; break;
case 3: if ( mbutton.read()){mstate = 4;} // отжали длинное нажатие
break;
case 4: mstate = 0; break;
default: mstate = 0;
}
sbutton.update(); // Set
switch (sstate){
case 0: if (!sbutton.read()){sstate = 1; smillis = millis() + BPAUSE;}// нажали кнопку
break;
case 1: if ( sbutton.read()){sstate = 2;} // отжали кнопку
else {if (millis()>smillis){sstate = 3;}} // длинное нажатие
break;
case 2: sstate = 0; break;
case 3: if ( sbutton.read()){sstate = 4;} // отжали длинное нажатие
break;
case 4: sstate = 0; break;
default: sstate = 0;
}
}
#endif
#if VOLT == 1
void rvolt(void){ // Отображение напряжения
if (state == 15) { // Повторный вход. просто проверяем время и выходим
if (millis() > ctmillis){state = 0;} // Если время отображения термометра кончилось, переключаемся на часы
return;
} else {
dtostrf(vvalue, 4, 1, dbuffer); // Преобразуем напряжение в набор символов
// пытаемся вставить знак U в нужное место (проверяем, есть ли в наборе символов точка или запятая)
if (dbuffer[1]==44 || dbuffer[1]==46 ||
dbuffer[2]==44 || dbuffer[2]==46){dbuffer[4] = 'U';}
else {dbuffer[3] = 'U';}
led7print(dbuffer); // Функция для записи символов в буфер индикатора
ctmillis = millis()+TPAUSE; // время для переключения обратно на часы
state = 15; // меняем состояние на повторное
}
}
#endif
#if VOLT == 1
void rvalarm(void){ // Аларм напряжения (мигает)
if (vvalue>VMIN && vvalue<VMAX) {state = 0; return;} //Если напряжение нормальное - выходим в часы
if (millis()>=pointmillis) {
point = !point; //раз в pointtime мигаем напряжением
pointmillis = pointmillis + POINTTIME;
if (point) {
dtostrf(vvalue, 4, 1, dbuffer); // Преобразуем напряжение в набор символов
// пытаемся вставить знак U в нужное место (проверяем, есть ли в наборе символов точка или запятая)
if (dbuffer[1]==44 || dbuffer[1]==46 ||
dbuffer[2]==44 || dbuffer[2]==46){dbuffer[4] = 'U';}
else {dbuffer[3] = 'U';}
}
else {
dbuffer[0] = ' ';
dbuffer[1] = ' ';
dbuffer[2] = ' ';
dbuffer[3] = ' ';
} // мигаем напряжением
led7print(dbuffer); // Функция для записи символов в буфер индикатора
}
}
#endif
void rclock(void) { // Отображение часов
char cbuff[6]; //Буфер для часов
if (state == 10) { // Повторный вход. просто проверяем время
if (millis() > ctmillis){state = 1; return;} // Если время отображения часов кончилось, переключаемся на термометр
#if BUTTN == 1
// Проверяем кнопки
#if VOLT == 1
if (mstate == 2) {state = 5; return;} // если нажата Mode то переходим к вольтметру
#endif
#if DS1820 == 1
if (sstate == 2) {state = 1; return;} // если нажата Set то переходим к термометру
#endif
if (mstate == 3) {state = 2; return;} // если долго нажата Mode то переходим к корректировке часов
#endif
}
else{
#if BUTTN == 1
if (mstate != 4) { //если длинная кнопка отпущена
state = 10; ctmillis = millis()+CPAUSE;} // если первый вход - запоминаем время
#else
state = 10; ctmillis = millis()+CPAUSE;
#endif
}
// мигание точки в часах
if (millis()>=pointmillis) {
point = !point; //раз в pointtime меняем точку на не точку
pointmillis = pointmillis + POINTTIME;
H=hour();
M=minute();
if (point) {
cbuff[0]=H/10;
cbuff[1]=H-(cbuff[0]*10);
cbuff[0]=cbuff[0]+48; // +48, чтобы из значения получить ascii код цифры
cbuff[1]=cbuff[1]+48;
cbuff[2]=',';
cbuff[3]=M/10;
cbuff[4]=M-(cbuff[3]*10);
cbuff[3]=cbuff[3]+48;
cbuff[4]=cbuff[4]+48;
}
else {
cbuff[0]=H/10;
cbuff[1]=H-(cbuff[0]*10);
cbuff[2]=M/10;
cbuff[3]=M-(cbuff[2]*10);
cbuff[0]=cbuff[0]+48;
cbuff[1]=cbuff[1]+48;
cbuff[2]=cbuff[2]+48;
cbuff[3]=cbuff[3]+48;
}
led7print(cbuff); // выводим время и мигаем точкой
}
}
#if BUTTN == 1
void rhour(void) { // Установка часов
char cbuff[6]; //Буфер для часов
M=minute();
if (state == 2) { // Первый вход. Ждем когда отпустят кнопку mode
if (mstate != 3){ //Если кнопку отпустили
state = 12; ctmillis = millis()+TOPAUSE; // Запоминаем время
H=hour();}
}
if (state == 12){
if (millis()>ctmillis){state = 0;}
// Проверяем кнопки
if (mstate == 2) {state = 3; return;} // если была нажата Mode то переходим к установке минут
if (mstate == 3) {state = 0; return;} // если долго нажата Mode то переходим к часам
if (sstate == 2){ // если нажата Set
H = H+1; if(H > 23) {H = 0;}
ctmillis = millis()+TOPAUSE; // и обнуляем таймаут для выхода
}
if (sstate == 3) { // если долго нажата Set
if (millis() > setmillis) {
H++; if(H > 23) {H = 0;}
setmillis = millis() + SETPAUSE; // пауза чтобы показания менялись не слишком быстро
}
ctmillis = millis()+TOPAUSE; // и обнуляем таймаут для выхода
}
if (sstate == 2 || sstate == 4) { //кнопку отпустили - толкаем все в rtc
setTime(H, M, 0, day(), month(), year());
RTC.set(now());
}
}
cbuff[0]=H/10;
cbuff[1]=H-(cbuff[0]*10);
cbuff[0]=cbuff[0]+48; // +48, чтобы из значения получить ascii код цифры
cbuff[1]=cbuff[1]+48;
cbuff[2]=',';
cbuff[3]=M/10;
cbuff[4]=M-(cbuff[3]*10);
cbuff[3]=cbuff[3]+48;
cbuff[4]=cbuff[4]+48;
// мигание часов
if (millis()>=pointmillis) {
point = !point; //раз в pointtime меняем точку на не точку
pointmillis = pointmillis + POINTTIME;
if (point && sstate != 3) { //мигаем если не нажата set
cbuff[0]=' ';
cbuff[1]=' ';
}
led7print(cbuff); // выводим время и мигаем
}
}
#endif
#if BUTTN == 1
void rmin(void) { // Установка минут
char cbuff[6]; //Буфер для часов
H=hour();
if (state == 3) { // Первый вход.
state = 13; ctmillis = millis()+TOPAUSE; // Запоминаем время
M=minute();
}
if (state == 13){
if (millis()>ctmillis){state = 0;}
// Проверяем кнопки
if (mstate == 2) {state = 2; return;} // если была нажата Mode то переходим к установке часов
if (mstate == 3) {state = 0; return;} // если долго нажата Mode то переходим к часам
if (sstate == 2) { // если нажата Set то прибавляем часы
M = M+1; if(M > 59) {M = 0;}
ctmillis = millis()+TOPAUSE; // и обнуляем таймаут для выхода
}
if (sstate == 3) { // если нажата Set то прибавляем часы
if (millis() > setmillis) {
M = M+1; if(M > 59) {M = 0;}
setmillis = millis() + SETPAUSE; // пауза чтобы показания менялись не слишком быстро
}
ctmillis = millis()+TOPAUSE; // и обнуляем таймаут для выхода
}
if (sstate == 2 || sstate == 4) {
/// тут как то надо затолкать часы в RTC
setTime(H, M, 0, day(), month(), year());
RTC.set(now());
}
}
cbuff[0]=H/10;
cbuff[1]=H-(cbuff[0]*10);
cbuff[0]=cbuff[0]+48; // +48, чтобы из значения получить ascii код цифры
cbuff[1]=cbuff[1]+48;
cbuff[2]=',';
cbuff[3]=M/10;
cbuff[4]=M-(cbuff[3]*10);
cbuff[3]=cbuff[3]+48;
cbuff[4]=cbuff[4]+48;
// мигание часов
if (millis()>=pointmillis) {
point = !point; //раз в pointtime меняем точку на не точку
pointmillis = pointmillis + POINTTIME;
if (point && sstate != 3) { //мигаем если не нажата set
cbuff[3]=' ';
cbuff[4]=' ';
}
led7print(cbuff); // выводим время и мигаем
}
}
#endif
#if DS1820 == 1
void rtemp (void) { // Отображение температуры
float temp; // Температура
byte present = 0;// Наличие датчика
byte data[12]; // Сюда читаем данные с датчика
if (type_s == 99 ) {state = 0; return;} // если датчик не найден, то сразу возвращаемся в часы
if (state == 11) { // Повторный вход. просто проверяем время и выходим
if (millis() > ctmillis){state = 0;} // Если время отображения термометра кончилось, переключаемся на часы
return;
}
else { // Иначе - запрашиваем температуру
byte i;
ds.reset();
ds.skip();
ds.write(0x44, 1);
delays(800); //по протоколу пауза перед считыванием температуры. Минимум 750мс
present = ds.reset();
ds.skip();
ds.write(0xBE);
for ( i = 0; i < 9; i++) { data[i] = ds.read(); }
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
// default is 12 bit resolution, 750 ms conversion time
}
temp = (float)raw / 16.0; // Получаем температуру
dtostrf(temp, 4, 1, dbuffer); // Преобразуем температуру в набор символов
// пытаемся вставить знак градуса в нужное место (проверяем, есть ли в наборе символов точка или запятая)
if (dbuffer[1]==44 || dbuffer[1]==46 ||
dbuffer[2]==44 || dbuffer[2]==46){
dbuffer[4] = '"';}
else {dbuffer[3] = '"';}
led7print(dbuffer); // Функция для записи символов в буфер индикатора
ctmillis = millis()+TPAUSE; // время для переключения обратно на часы
state = 11; // меняем состояние на повторное
}
}
#endif
void delays (long pause){ // Замена штатной функции delay на собственную - с рефрешем индикатора
long pmillis = millis()+pause; // запоминаем время паузы
while (millis() < pmillis){showDisplay();}; // Ждем наступления времени паузы, попутно рефрешим дисплей
}
void led7print (char dstr[6]){ // Процедура разбора строки и заполнение буфера для индикатора
byte c = 0; //счетчик для входного буфера
byte d = 0; //счетчик для ВЫходного буфера
// Разбираем буфер на символы (ищем запятую)
while (d < 4) {
switch (dstr[c]) {
case 44: c++;bitClear(digit[d-1],7);break; // запятая
case 46: c++;bitClear(digit[d-1],7);break; // точка
case 0 : for (byte i=d; i<4; i++) {digit[i]=22;}; d=4; break; //конец строки, заполняем все остальное пробелами
default: digit[d]=ascii2mask(dstr[c]); c++; d++; // по умолчанию просто переносим символ
}
}
}
void showDisplay(void){ // процедура отображения экрана
const byte chr[4] = {0b00001000,0b00000100,0b00000010,0b00000001}; // маска для разряда знакоместа
for(byte i = 0; i <= 3; i++){ // отправляем в цикле по два байта в сдвиговые регистры
digitalWrite(RCLK, LOW); // открываем защелку
shiftOut(DIO, SCLK, MSBFIRST, digit[i]); // отправляем байт с "числом"
shiftOut(DIO, SCLK, MSBFIRST, chr[i]); // включаем разряд
digitalWrite(RCLK, HIGH); // защелкиваем регистры
//delay(1); // ждем немного перед отправкой следующего "числа"... если не ждать то мигания меньше.
}
}
byte ascii2mask (int ascii){ // Функция перевода кода символа в его маску
// Маски символов
const byte mask[27] = {
0b11000000, // 0
0b11111001, // 1
0b10100100, // 2
0b10110000, // 3
0b10011001, // 4
0b10010010, // 5
0b10000010, // 6
0b11111000, // 7
0b10000000, // 8
0b10010000, // 9
0b10001000, // 10 A
0b10000011, // 11 b
0b10100111, // 12 c
0b10100001, // 13 d
0b10000110, // 14 E
0b11111111, // 15 F
0b10001011, // 16 h
0b11000111, // 17 L
0b10100011, // 18 o
0b10001100, // 19 P
0b10101111, // 20 r
0b10000111, // 21 t
0b11100011, // 22 u
0b11111111, // 23 Пусто
0b10011100, // 24 Градус (")
0b10111111, // 25 -
0b11110111, // 26 _
};
if (ascii > 47 && ascii < 58) {return mask[ascii-48];} // цифры
if (ascii > 64 && ascii < 71) {return mask[ascii-55];} // A-F
if (ascii > 96 && ascii < 103){return mask[ascii-87];} // a-f
switch(ascii){
case 32 :return mask[23]; // пробел
case 34:return mask[24]; // градус
case 45 :return mask[25]; // -
case 95 :return mask[26]; // _
case 72 :return mask[16]; // H
case 104:return mask[16]; // h
case 76 :return mask[17]; // L
case 108:return mask[17]; // l
case 79 :return mask[18]; // O
case 111:return mask[18]; // o
case 80 :return mask[19]; // P
case 112:return mask[19]; // p
case 82 :return mask[20]; // R
case 114:return mask[20]; // r
case 83 :return mask[5]; // S
case 115:return mask[5]; // s
case 84 :return mask[21]; // T
case 116:return mask[21]; // t
case 85 :return mask[22]; // U
case 117:return mask[22]; // u
}
return mask[26]; // если символ не из списка, то отображаем подчеркивание
}
Ну и результат:
Время

Температура

Напряжение (для отладки замеряю напряжение стабилизатора 3,3 ардуины)

Фотовспышка засвечивает индикатор до невидимости, вот фото без вспышки (но с шевеленкой, сорри)



Итог. Неплохой в принципе индикатор при условии наличия избытка вычислительных мощностей. Но если у вас основа поделки – часы, лучше взять индикатор на tm1637 (вроде бы видел такие комбинированные – с десятичными точками и двоеточием одновременно. Но сейчас навскидку не нашел :(, только индикаторы отдельно).
Рабочие моменты




Самые обсуждаемые обзоры
+15 |
1821
55
|
+54 |
1601
38
|
Но обзор понравился
Спасибо
точки — разрядность, и цена тоже хорошая.
https://aliexpress.com/item/item/New-MAX7219-LED-Dot-matrix-8-Digit-Digital-Display-Control-Module-for-Arduino-Wholesale/32605671192.html,searchweb201602_2_10039_10057_10048_10047_10056_10055_10037_10049_10059_10033_10046_10058_10032_10045_10017_405_404_407_10040,searchweb201603_4&btsid=56d70f2d-1af7-433e-9b08-ae87c2634a54
Спасибо тебе,Naevus, за помощь в освоении этого индикатора, уверен это пригодится еще многим.
P.S.Кот прикольный.
ps О, оттуда можно выковырять маски на алфавит.
arduino-kit.ru/catalog/id/modul-klaviaturyi-i-svetodiodnoy-indikatsii-tm1638-led_key
отличное описание и пример и библиотека.
samopal.pro/esp8266-hcontroller2/
С платы 3231 нужно выпаять мелкий красный диод справа от м/с часов. Иначе батарейка быстро выйдет из строя от перезаряда.
В общем — мне оно больше нравилось… Про диод — у китайцев в эти модули видимо ставятся аккумуляторы литиевые, и диод — это цепь подзарядки. Не представляю — зачем (если и на батарейке они проживут несколько лет), так что спасибо что напомнили — диод удалять.
Маленький вопрос: а в тиньку 13 первый код поместится?
Можно убрать ненужные символы, переменные перевести в постоянные, отказаться от «скринсейвера»… но, боюсь что в 3 раза этим код не ужмешь. Более того — думаю, что основной объем дает библиотека 1wire. А как от нее избавиться — я не знаю :(
ide 1,0,6 храню на всякий случай, хотя по слухам в ней тоже есть несовместимости с 1,0,5
З.Ы. Сейчас вот идея возникла: может вообще вместо энкодера аналоговый джойстик влепить? И программировать проще, и у кого ещё духовка с джойстиком есть? ;)
С джойстиком прикольно :)
:)
ps Энкодеры заказал, будет на чем поиграться через месяц