Большие часы на адресных светодиодах WS2812B

Большие часы на адресных светодиодах WS2812B
Всем привет, хочу рассказать о том как я попробовал повторить проект, но столкнулся с некоторыми проблемами.

Как-то раз на просторах зарубежного интернета я нашел вот такой проект часов на базе светодиодной ленты WS2812B: Big, Auto Dim, Room Clock, и мне захотелось его повторить. Часы устроены как семи сегментный циферблат и работают на RTC модуле DS3231 . Часы умеют показывать температуру, имеют кнопки для настройки и перевода на зимнее время и обратно, позже я добавил датчик влажности DHT11. Температура уже вместе с влажностью считывалась с этого датчика и выводилась раз в минуту. Хоть и в модуле часов есть температурный датчик, но похоже он показывал температуру не корректно из-за самонагрева (ИМХО).




Untitled Sketch_МП.png
И главное нанести много горячих соплей

IMG_20180907_120755.jpg


Когда все было собрано, повозившись с библиотеками, все заработало. И тут возникло одно большое НО: положения яркости в только в двух позициях меня не устраивало. Тем более на границе значений происходило мерцание. Установка яркости на статичном значении меня тоже не очень устраивала ибо днем при установленном тусклом свете ничего не было видно, а ночью даже со значениями близким к минимуму можно было осветить всю Москву. Поинтересовавшись как в других проектах с этим справляется Alex было решено заменить такой код настройки яркости.

C:
void BrightnessCheck(){
  const byte sensorPin = BRI_PIN; // light sensor pin
  const byte brightnessLow = 1; // Low brightness value
  const byte brightnessHigh = 50; // High brightness value
  int sensorValue = analogRead(sensorPin); // Read sensor
  Serial.print("Sensor is: ");Serial.println(sensorValue);
  sensorValue = map(sensorValue, 0, 255, 1, 100);
  LEDS.setBrightness(sensorValue);
  };
вот такими строчками:
C:
void BrightnessCheck() {

  if (auto_bright) {                         // если включена адаптивная яркость
    if (millis() - bright_timer > 100) {     // каждые 100 мс
      bright_timer = millis();               // сброить таймер
      new_bright = map(analogRead(BRI_PIN), 0, bright_constant, min_bright, max_bright);   // считать показания с фоторезистора, перевести диапазон
      new_bright = constrain(new_bright, min_bright, max_bright);
      new_bright_f = new_bright_f * coef + new_bright * (1 - coef);
      LEDS.setBrightness(new_bright_f);      // установить новую яркость
    }
  }
};

Тут стоит упомянуть что программистом я не являюсь и большинство проблем я пытаюсь решить методом тыка и перебора.

И тут все заработало, моей радости не было придела, пока не наступила ночь. Появилась проблема с отключением ленты при полной темноте. Иногда это было только на минуту, а иногда на целую ночь. Еще есть проблема со слабим мерцанием светодиодов если освещения в комнате было мало (например когда работал телик), но это было редко и длилось это одну минуту.

Что касается кода вот сылка на GitHub. Кое-что я перевел для лучшего понимания.

Давно хотел поделиться готовым и работоспособным проектом в кругах аудитории Алекса, но вышло так что собственно нормальной работоспособностью тут не пахнет. Буду благодарени, если у кто сможет помочь.
 
Изменено:

Комментарии

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
Получается что key всегда увеличивается и FastLED.show(); происходит 4 раза за цикл ?
да, после вызова функции идет ее показ, теоретически можно попробовать вынести ее в сами функции, а не в loop
Посмотри отметки времени
хорошо, попробую завтра
которая в промежуток между 0-5 секундой вызывается опять же с бешеной частотой.
в мониторе не часто выводит, вроде 2 раза, проверю
а есть ли разница? я думаю нет?
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
а есть ли разница? я думаю нет?
Разбери этот код, что бы было понятно все, сначала подумай что будет выведено, потом сравни с тем, что выведет монитор порта.
C++:
void setup() {
  Serial.begin(115200);
  bool f = false;
  int key = 0;
  if (f)
    TimeToArray(); Serial.println("-1111-"); key++;
  if (f)
  {
    TimeToArray(); Serial.println("-2222-"); key++;
  }
  f = true;
  if (f)
    TimeToArray(); Serial.println("-3333-"); key++;
  if (f)
  {
    TimeToArray(); Serial.println("-4444-"); key++;
  }
  Serial.println(key);
}

void loop() {

}

void TimeToArray()
{
  Serial.println("TimeToArray");
}
 

bort707

★★★★★★✩
21 Сен 2020
3,046
909
Разбери этот код, что бы было понятно все, сначала подумай что будет выведено, потом сравни с тем, что выведет монитор порта.
а, ну да :) не заметил :)
Я ж говорю, когда мы писали вместе, все было правильно , сравни :)
https://community.alexgyver.ru/thre...yx-svetodiodax-ws2812b.233/page-11#post-63021
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
точки включили - засекли миллис.
снова миллисы, ок)
я на правки, что туда новички вносят - уже смотреть не могу
ну это не мой косяк, это было в чьем-то готовом скетче) если с delay, то все показывает исправно, но есть минусы)
с millis этих минусов нет, но надо баги править, которые возникают из-за них)
Сделай внутри проверку на то, что бы прошло какое то время с последней проверки.
добавил, буду разбираться с loop...
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
Вообще мне цикл loop не нравится совсем.
Как сделал бы я, примерно:
C++:
void setup() {
  Serial.begin(115200);
}

void loop() {
uint32_t tmrall=millis()%14000/1000;
String out_str;
static String old_out_str;
switch (tmrall)
  {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    {
      out_str="Время:"+String(tmrall);
    }
    break;
    case 6:
    case 7:
    {
      out_str="Температура:"+String(tmrall);
    }
    break;
    case 8:
    case 9:
    {
      out_str="Улица:"+String(tmrall);
    }
    break;

    case 10:
    case 11:
    {
      out_str="Давление:"+String(tmrall);
    }
    break;
    case 12:
    case 13:
    {
      out_str="Что то еще:"+String(tmrall);
    }
    break;
   
  }

if (old_out_str!=out_str)
  {
    Serial.println(out_str);
    old_out_str=out_str;
  }


}
Тут я убрал условия if, но можно сделать и с ним, не проблема. Я захотел использовать switch, т.к. на мой взгляд это более наглядно и менее запутано
Далее. Я сделал изменение в 1 секунду ("/1000" в коде) , можно делить на 2000, тогда дискретность будет 2 секунды и меньше case писать. Тут не принципиально. если бы случаев было больше, я бы делал с if

Второй момент. Вывод на экран. я храню последнее выведенное значение, и вывожу только если оно изменилось. В случае с часами это будет затруднительно. Что можно сделать:
1) ни в коем случае не использовать фастлед.шоу внутри switch.
2) Перерисовывать матрицу с какой то частотой. Например 20 раз в секунду независимо от всего. как монитор, который всегда гоняет луч. Для этого в конце добавить что то типа:
if (millis()-last_show>50)
{
Fastled.show();
last_show=millis();
}
Этого вполне должно хватить для корректного вывода изображения на ленту. Можно сделать и 50 раз в сек, тогда 50 сменить на 20. Но чаще уже нет смысла.

Или все же завести переменную, которая будет отвечать за то, что состояние матрицы изменилось. Тогда так:
if (matrix_change)
{
Fastled.show();
matrix_change=false;
}

Второй способ более трудоёмкий, но позволит быстрее реагировать на изменения. Быстрее на 50 мс, для 20 кадров в сек.
Внутри после case следует делать соотв. вычисления времени, давления, температуры, прыщей и вообще чего требуется. и занести нужное в массив ленты. Опять же, температуру и давление можно вычислить только 1 раз в начале вывода, а далее ее только выводить. Вряд ли за 2 секунды она существенно изменится, но тут хозяин-барин.

В целом идея такая: ограничить частоту вывода матрицы до минимально необходимой, а свободное время потратить на что то полезное.
 
Изменено:
  • Лойс +1
Реакции: bort707 и ASM

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
Переписал код с точками, останется отладить)
C++:
void Dots_on()  {
  leds[56] = ledColor;
  leds[57] = ledColor;
     static uint32_t last_dot = millis();
    if ((millis() - last_dot) > 1000);
    last_dot = millis();
    Dots_off();
}

void Dots_off()  {
  leds[56] = 0x000000;
  leds[57] = 0x000000;
}
Я захотел использовать switch, т.к. на мой взгляд это более наглядно и менее запутано
мне вообще не понравились эти switch и case, у bort вроде код на них, такая каша, как по мне и весь код в loop)
поэтому я и отказался от этого варианта
если подскажете хорошее описание, может разберусь и переделаю)

Разбери этот код
почему-то не выводит TimeToArray в первый раз)

появилась еще такая мысль, что, если millis преобразовать в целые секунды и уже проверять секунды, может корректнее будет обрабатываться?
C++:
uint32_t sec = millis() / 1000ul;
  int timeSecs = (sec % 3600ul) % 60ul;
вот такой вариант придумал, работает, но с точками бывают недочеты, как на видео)
В мониторе часы выводятся 3 раза, на старте 4 (из-за неточности в millis, это понятно), температуры по 2 раза и давление 1 раз, похоже надо в этом направлении и решать, т.к. тут ничего не меняется
C++:
Dot = second % 2; //из GetTime()

boolean Dots = true;
  if (Dot == 0) Dots = false; else Dots = true;
     if (Dots){leds[56] = ledColor;
               leds[57] = ledColor;}
         else {leds[56] = 0x000000;
               leds[57] = 0x000000;}
 
Изменено:

bort707

★★★★★★✩
21 Сен 2020
3,046
909
мне вообще не понравились эти switch и case, у bort вроде код на них, такая каша,
:)))
каша в коде не моя, я уже после руку приложил :)
моя задача была только убрать зависимость от числа диодов в сегменте. До меня тут все сегменты были прямо в тексте числами закодированы. А остальное я не трогал, общую логуику оригинального кода не менял.
Как говорится... "авторская орфорграфия и пунктуация сохранена"
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
@bort707,если переделать на switch, то все функции с датчиками просто перенести в case?
Хотя можно же просто сделать ссылки на них добавив millis?)

поразобрался немного с синтаксисом, только пока непонятно, как будет работать вывод на экран)

на данный момент сделал так)
C++:
 byte mode;

    switch (mode){
        case 0:{
        uint32_t Timer_Time;
        Timer_Time = millis();
        if (millis() - Timer_Time <=5000)
        Timer_Time = millis();
        TimeToArray();
        break;
        }
        case 1:{
        uint32_t Timer_Temp;
        Timer_Temp = millis();
        if (millis() - Timer_Temp <=2000)
        TempToArray();
        break;
        }
        case 2:{
        uint32_t Timer_TempS;
        Timer_TempS = millis();
        if (millis() - Timer_TempS <=2000)
        TempStreetToArray();
        break;
        }
        case 3:{
        uint32_t Timer_Press;
        Timer_Press = millis();
        if (millis() - Timer_Press <=2000)
        PressToArray();
        break;
        }
        }
      FastLED.show();
точки работают идеально, но монитор забит функцией TimeToArray(); другие не включаются)

куда запихнуть?)
C++:
for (mode = 0; mode <=3; mode++)
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
У тебя тут несколько другой подход, чем предлагал я. Но тоже имеет право налево.
Но ошибка прежняя!
if (millis() - Timer_Time <=5000)
Timer_Time = millis(); <- это выполниться по условию!
TimeToArray(); <- это выполниться всегда!
а т.к. в начале стоит Timer_Time = millis(); то условие тут вообще не играет ни какой роли
 

kym13

★★✩✩✩✩✩
14 Ноя 2020
472
70
min и max меняешь местами, посмотрел код, закомментировано немного не то)

в конце видео отчетливо заметны точки, которые передержались)

может тогда запихнуть вызов в функцию с датчиками или часами?
ASM
Спасибо
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
@Старик Похабыч, так лучше?)
C++:
  byte mode;
  uint32_t Timer_Time,Timer_Temp,Timer_TempS,Timer_Press;
  for (mode = 0; mode <= 3; mode++) {          //выводим по порядку
    switch (mode) {
      case 0:
          Timer_Time = millis();               //сбросим счетчик
          if (millis() - Timer_Time <= 5000) { //выводим в течении 5 секунд
            TimeToArray();                     //показания часов
          }                
          break;
      case 1:
          Timer_Temp = millis();               //сбросим счетчик
          if (millis() - Timer_Temp <= 2000) { //выводим в течении 2 секунд
            TempToArray();                     //показания датчика
          }              
          break;
      case 2:
          Timer_TempS = millis();               //сбросим счетчик
          if (millis() - Timer_TempS <= 2000) { //выводим в течении 2 секунд
            TempStreetToArray();                //показания датчика
          }          
          break;
      case 3:
          Timer_Press = millis();               //сбросим счетчик
          if (millis() - Timer_Press <= 2000) { //выводим в течении 2 секунд
            PressToArray();                     //показания датчика
          }                
          break;
    }
  }
  FastLED.show();
@kym13, лучше просто лайкнуть мой ответ, чем так отвечать)
на прошлое сообщение, просто спасибо на форумах не принято отвечать, модеры ругаются)
 
Изменено:
  • Лойс +1
Реакции: kym13

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
Нет! Так ничем не лучше. Ты сперва берешь
Timer_Time = millis();
а потом его же с миллис сравнваешь. Какой смысл ? понятно что там разница будет всегда одна и та же. И меньше 5 сек.
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
@Старик Похабыч,видно я что-то не понимаю. Я же комментарии написал, как я вижу) сбрасываю счетчик, т.к. он уже далеко за 5 секунд, потом пока не прошло 5 секунд показывают часы, если больше идем дальше... прошли все функции и вернулись в начало, снова сбросили и по циклу)
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
На пальцах
Сейчас время 0 часов 0 мину 10 секунд
Timer_Time = millis(); // в Timer_Time 0 0 10
Следующая строка:
if (millis() - Timer_Time <= 5000) // миллис изменится 2-3-4 мс. И условие всегда, понимаешь, ВСЕГДА будет выполняться.
Цикл for там вообще не нужен. Ты очень сильно усложняешь код, его можно довести до ума в таком виде. но потребуется тонна ...земли и палок.

Поэтому.. ЗАБЫЛИ !

Берем за основу код из п. 332. и адаптируем его к твоим значениям. 6 секунд, 2, 22, и вырезаем то, что тебе НЕ надо
будет вот что:

C++:
void setup() {
  Serial.begin(115200);
}

void loop() {
  uint32_t tmrall = millis() % 12000 / 1000;
  static uint32_t tmrall_old = 10000;
  if (tmrall_old != tmrall)
  {
    tmrall_old = tmrall;

    if (tmrall < 6)
    {
      Serial.println("Заносим  время в матрицу");
      //TimeToArray();
    } else if (tmrall < 8)
    {
      Serial.println("Заносим  темепературу в матрицу");
      //TempToArray();
    }
    else if (tmrall < 10)
    {
      Serial.println("Заносим  уличную темепературу в матрицу");
      //TempStreetToArray();
    }
    else
    {
      Serial.println("Заносим  давление в матрицу");
      //PressToArray();
    }
  };

  /*
    if (tmrall_old != tmrall)
    {
      tmrall_old = tmrall;
      switch (tmrall)
      {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
          {
            Serial.println("Заносим  время в матрицу");
            //TimeToArray();
          }
          break;
        case 6:
        case 7:
          {
            Serial.println("Заносим  темепературу в матрицу");
            //TempToArray();
          }
          break;
        case 8:
        case 9:
          {
            Serial.println("Заносим  уличную темепературу в матрицу");
            //TempStreetToArray();
          }
          break;

        case 10:
        case 11:
          {
            Serial.println("Заносим  давление в матрицу");
            //PressToArray();
          }
          break;

      }

      Serial.println("Обновление!");
      // тут надо фастледшоу раз в секунду
    }*/
}
Тут 2 варианта и с if и с switch, какой хочешь такой и используй.
Тут все данные получаются и заносятся в матрицу раз в секунду, по единому таймеру. И соотв. отрисовываются они тоже раз в секунду.
А точки у тебя дурили видимо потому что время ты считал до 13 секунд, а реально использовал до 11, вот 2 секунды точки и не знали что делать.

Как всегда есть НО! Это только вывод на матрицу. А надо еще корректировать часы. Раз в секунду этого мало. Поэтому подумай как бы это сделать.
Подсказка. Вспони. что я говорил про 20 кадров в секунду. Для этого надо вывести фастлед.шоу из условия и наложить свое отдельное.
 
  • Лойс +1
Реакции: ASM

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
@Старик Похабыч, сделал по первому варианту
C++:
  uint32_t tmrall = millis() % 12000 / 1000;
  static uint32_t tmrall_old = 10000;
  if (tmrall_old != tmrall)
  {
    tmrall_old = tmrall;
    if (tmrall < 6)
    {
      TimeToArray(); FastLED.show();
    }
    else if (tmrall < 8)
    {
      TempToArray(); FastLED.show();
    }
    else if (tmrall < 10)
    {
      TempStreetToArray(); FastLED.show();
    }
    else
    {
      PressToArray(); FastLED.show();
    }
  };
вроде все работает, на часах с точкой все норм, только лог интересный)
Код:
13:08:53.676 -> 13:9:28
13:08:54.660 -> 13:9:29
13:08:55.692 -> 13:9:30
13:08:56.675 -> 13:9:31
13:08:57.658 -> 13:9:32
13:08:58.690 -> 13:9:33
13:09:00.174 -> 23
13:09:01.206 -> 23
13:09:01.672 -> 2
13:09:02.696 -> 2
13:09:04.198 -> 744
13:09:05.183 -> 744
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
А что в нем такого интересного ?
Все как положено. Т.к. каждое из значений запрашивается раз в секунду то и лог показывает эти значения раз в секунду. Не чаще. Время, температура внутри, уличная и давление. Далее по кругу должно быть.
Или еще что то странное и непонятное есть ?
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
Или еще что то странное и непонятное есть ?
нет, спасибо, все понятно, на этом участке кода не так заметно, что хотел показать, я про мс интервалы.
пока потестирую как будет работать, место под скетч код также занимает)
А надо еще корректировать часы
это про что? вроде все исправно...
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,251
1,297
Москва
@ASM, Кнопки то есть какие ? Я думал время надо по кнопкам выставлять.. или не надо ? Я не во все вникал.

А интервалы можно сделать и чаще.
Вот смотри
uint32_t tmrall = millis() % 12000 / 1000;
тут 12000 это полный цикл в секундах. При получении остатка от него у нас будут числа от 0 до 11999. Далее делю на 1000 и получаю целое число в диапазоне от 0 до 11.
Тогда если делить на 2000 то то же будет целое число , но уже в другом диапазоне, от 0 до 5, но интервал будет 2 секунды на одно изменение числа.
Можно сделать чаще, делить на 500. Тогда будут числа от 0 до 23, изменение раз в секунду.
Далее если добавиь для температур и давлений для каждого свой флаг, сделать вместо
{
TempToArray(); FastLED.show();
}
что то типа:
C++:
void loop ()
{
static bool f1=true; // флаг для температуры в помещении
static bool f2=true;// флаг для температуры на улице
...
if (tmrall < 6)
    {
      TimeToArray(); [S]FastLED.show();[/S]
f1=true;
f2=true;
    }

...
 else if (tmrall < 8)
{
     if (f1)
    {
      TempToArray(); 
f1=false;
 }
...
    }
[CODE]

То частота запросов времени будет 2 раза в секунду, а запросы температур  будут только при 1-ом выводе и все. И давления туда же.

ДА! И только что заметил! Уберите на FastLED.show(); из каждого if  и поставьте в одном месте, в куске кода между 21 и 22 строкой в посте 342
 
  • Лойс +1
Реакции: ASM

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
Кнопки то есть какие ?
кнопки есть, но корректировка времени происходит раз в несколько месяцев, добавляю не более 2 минут)
если код без delay, думаю можно будет в любой момент добавить, а не раз за цикл, как раньше)
Вывод на экран вынес, все работает.
С флагами подумаю...
 

bort707

★★★★★★✩
21 Сен 2020
3,046
909
Вот смотри
uint32_t tmrall = millis() % 12000 / 1000;
тут 12000 это полный цикл в секундах. При получении остатка от него у нас будут числа от 0 до 11999. Далее делю на 1000 и получаю целое число в диапазоне от 0 до 11.
развивая эту идею - можно по этому целому числу выбирать что показывать с помощью switch()
 

ASM

★★★★★✩✩
26 Окт 2018
1,599
311
Теперь при отвале датчика уличной температуры показывает нуль, ране было '-27 (-127)', надо будет заменить на черточки или может кто предложит иной вариант символов?) Будет время, доберусь)

Библиотека была другая, сейчас на microDS18B20, возможно и из-за этого разный ответ получился)

Как реализовать данный момент и возможно ли?
if (!sensors.requestTemp()) Serial.println("No signal"); //если нет ответа с датчика, обрыв провода
 
Изменено: