По мотивам Больших Часов на Светодиодной Ленте (БЧСЛ).
Тестировать буду на atmrga168, выводить данные в монитор порта. Просто так делать вывод не очень интересно. Поэтому 1-ое что сделаю добавлю в код свою любимую функцию подсчета кол-ва циклов loop , которые проходят за 1 секунду. Код получился вот такой:
Пустой код крутится 265 тысяч раз в секунду.
Выводить в монитор порта будем время. Что бы не городить огород просто переведем значение millis в часы , минуты и секунды, прошедшие с момента включения.
Все это оформим функцией , которая будет возвращать строку.
Функция вывода строки в монитор порта будет отдельной, нам с ней еще надо будет как то поработать:
В результате имеем офигительное замедление работы ( где то в 250 раз!) и много избыточной информации.
Первое, что следует сделать, это ограничить вывод информации , если она не изменилась. Тут можно пойти 2-мя путями
1) Повесить эту обязанность на displayTime примерно вот так:
Мы запоминаем строку, которую вывели в последний раз и новую строку сравниваем с тем, что запомнили, вывод только в случае если эти строки не равны.
И сразу стало в 2 раза быстрее. Но в 100 раз хуже чем было сначала.
Плюс этого варианта в том, что он универсальный и будет работать на любую строку , которую мы хотим вывести куда -либо.
Минус в том, что размер строки может быть очень большим и хранить все это придется в оперативной памяти. А ее всегда мало.
Да и работает весьма медленно.
2) Второй вариант ввести в программу флаг, который будет говорить о том, что что то на выходе поменялось.
А так же возводить его, когда секунды поменялись, в данном примере этого достаточно , в более сложном может потребоваться сравнивать и минуты.
И получим:
Увеличив интервал опроса до 500 мы почти не изменим скорость вывода т.к. через раз будет изменение секунд.
Теперь добавим новую функцию, вывод температуры.
Сам датчик подключаться не будет, просто будем брать температуру равной 25 градусам.
Сразу ограничим получение температуры 1-ой секундой:
String TemperToString()
{
static uint32_t tmr1=millis();
if (millis()-tmr1<1000) return "";
tmr1=millis();
hasChange=true;
return "25°";
}
И добавим еще пару таких заглушек, для уличной температуры и для давления:
Теперь надо как то сделать вывод всех этих данных по очереди с определенной длительностью
Для этого в голове (или константами) пронумеруем данные:
0 - часы, хотим выводить их 4 секунды
1 - температура дома, 2 сек
2 - температура улицы, 2 сек
3 - давление, 2 секунды
И сделаем под это дело массив:
и массив порядка вывода:
Введем переменную, которая будет говорить нам о том, что сейчас выводим:
Внесем некоторые правки в цикл loop, введем статическую переменную для хранения времени выполнения текущего режима, а так же выбор того, что надо выводить:
Такой код уже работает:
А т.к. давление и температура будет вряд ли меняться быстрее градуса в 2 секунды, то можно увеличить их интервал обновления, на примере темпрературы:
В начале функции изменена инициализация начального значения таймера, что бы при старте он сразу выполнил свою функцию, без нее 1-ый цикл будет сбойный.
Вот результат выполнения кода:
1000 на скажем 1100. Результат показывать не буду, но поверьте, так работает как надо.
Но увлекшись выводом я совсем забыл про orderDisplay. Он нам нужен для выставления желаемого нам порядка прохождения. Внесем сл. изменения в цикл loop:
1) выбор switch идет уже по данным из массива orderDisplay
2) текущую проверку интервала идет сложнее, по данным из массива из массива. Да, вот так.
3) Все таки сделал проверку режима на выход за пределы границы массива orderDisplay, т.к. концепция немного усложнилось. Лишний 0 из массива интревалов. убрал
Что это дает и для чего такие сложности? Изменив массив порядка вывода к примеру на такой:
Получим следующий вывод данных:
А если сделать
то внешняя температура вообще не будет отображаться.
Цикл loop крутиться 27 тысяч раз в секунду, что вполне хватит для стабильной работы Гайвер-Энкодера и Гайвер-Батона без прерываний.
И последний момент. Мигание точек.
Лично я бы их вывел вообще отдельным пунктом. Но можно и в функцию получения времени. Сейчас функция получения времени вызывается 2 раза в секунду, что нужно для "быстрого" мигания точек. Примерный алгоритм реализации:
Что бы иметь возможность управлять точками не только из функции получения времени заводим глобальную переменную bool showDot=false;
Добавляю 2-ую функцию (что бы не терять 1-ый вариант, а так совсем не обязательно):
И в результате вывод будет такой:
Материал выдан для обучения. Итоговый код в прикреплении.
UPD. Для небольшого разнообразия сделал температуру и давление случайными при каждом получении.
Тестировать буду на atmrga168, выводить данные в монитор порта. Просто так делать вывод не очень интересно. Поэтому 1-ое что сделаю добавлю в код свою любимую функцию подсчета кол-ва циклов loop , которые проходят за 1 секунду. Код получился вот такой:
C++:
void setup() {
Serial.begin(115200);
}
void loop() {
ShowFPS();
}
void ShowFPS()
{
static uint32_t tm_m = millis();
static uint32_t cnt_m = 0;
cnt_m++;
if ((millis() - tm_m) > 1000)
{
Serial.print("loop per sec: "); Serial.println(cnt_m);
cnt_m = 0;
tm_m = millis();
}
}
10:48:46.085 -> loop per sec: 265059
10:48:47.068 -> loop per sec: 265330
10:48:48.087 -> loop per sec: 265058
10:48:49.071 -> loop per sec: 265330
10:48:50.086 -> loop per sec: 265058
10:48:51.071 -> loop per sec: 265330
10:48:52.088 -> loop per sec: 265058
10:48:53.069 -> loop per sec: 265330
10:48:54.085 -> loop per sec: 265329
Пустой код крутится 265 тысяч раз в секунду.
Выводить в монитор порта будем время. Что бы не городить огород просто переведем значение millis в часы , минуты и секунды, прошедшие с момента включения.
Все это оформим функцией , которая будет возвращать строку.
Функция вывода строки в монитор порта будет отдельной, нам с ней еще надо будет как то поработать:
C++:
String timeToString()[CODE lang="cpp" ]
{
char temp[10];
uint32_t nt=millis()/1000; //получили секунды
int8_t s=nt%60;
int8_t m=nt/60%60;
int8_t h=nt/3600%24;
snprintf(temp, 10, "%02d:%02d:%02d", h,m,s);
return String(temp);
}
void displayTime(String s)
{
Serial.println(s);
}
void loop() {
displayTime(timeToString());
ShowFPS();
}
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> loop per sec: 1179
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.702 -> 00:05:40
11:06:50.735 -> 00:05:40
Первое, что следует сделать, это ограничить вывод информации , если она не изменилась. Тут можно пойти 2-мя путями
1) Повесить эту обязанность на displayTime примерно вот так:
C++:
void displayTime(String s)
{
static String old_s="";
if (old_s!=s)
{
Serial.println(s);
old_s=s;
}
}
И сразу стало в 2 раза быстрее. Но в 100 раз хуже чем было сначала.
Плюс этого варианта в том, что он универсальный и будет работать на любую строку , которую мы хотим вывести куда -либо.
Минус в том, что размер строки может быть очень большим и хранить все это придется в оперативной памяти. А ее всегда мало.
Да и работает весьма медленно.
2) Второй вариант ввести в программу флаг, который будет говорить о том, что что то на выходе поменялось.
А так же возводить его, когда секунды поменялись, в данном примере этого достаточно , в более сложном может потребоваться сравнивать и минуты.
C++:
bool hasChange=false;
...
String timeToString()
{
char temp[10];
uint32_t nt = millis() / 1000; //получили секунды
static int8_t old_s=255;
int8_t s = nt % 60;
if (s!=old_s)
{
old_s=s;
hasChange=true;
}
else return"";
int8_t m = nt / 60 % 60;
int8_t h = nt / 3600 % 24;
snprintf(temp, 10, "%02d:%02d:%02d", h, m, s);
return String(temp);
}
...
void displayTime(String s)
{
if (!hasChange) return;
Serial.println(s);
hasChange=false;
}
Результат еще лучше, но все же далек от начального. Теперь посмотрим функцию получения времени, мы обрабатываем ее почти 10 тысяч раз в секунду, а выводим с точностью 1 секунда. Значит период преобразование можно смело снизить скажем до 10 раз в секунду. Точность вывовда будет в районе 0.1 секунды, что нам выше крыши, можно и еще снизить. пробуем, добавив в функцию преобразования времени в число такие (стандартные для меня) строки:11:22:58.778 -> 00:00:04
11:22:58.778 -> loop per sec: 9794
11:22:59.757 -> 00:00:05
11:22:59.790 -> loop per sec: 9802
C++:
static uint32_t tmr1=millis();
if (millis()-tmr1<100) return "";
tmr1=millis();
Что уже не плохо, и дает достаточно времени на обработку остальных функций , энкодера , кнопок.11:29:23.065 -> loop per sec: 31995
11:29:24.047 -> 00:00:09
11:29:24.047 -> loop per sec: 31963
11:29:25.030 -> 00:00:10
Увеличив интервал опроса до 500 мы почти не изменим скорость вывода т.к. через раз будет изменение секунд.
Теперь добавим новую функцию, вывод температуры.
Сам датчик подключаться не будет, просто будем брать температуру равной 25 градусам.
Сразу ограничим получение температуры 1-ой секундой:
String TemperToString()
{
static uint32_t tmr1=millis();
if (millis()-tmr1<1000) return "";
tmr1=millis();
hasChange=true;
return "25°";
}
И добавим еще пару таких заглушек, для уличной температуры и для давления:
C++:
String TemperOutToString()
{
static uint32_t tmr1=millis();
if (millis()-tmr1<1000) return "";
tmr1=millis();
hasChange=true;
return "-5°";
}
String PressToString()
{
static uint32_t tmr1=millis();
if (millis()-tmr1<1000) return "";
tmr1=millis();
hasChange=true;
return "745mm";
}
Для этого в голове (или константами) пронумеруем данные:
0 - часы, хотим выводить их 4 секунды
1 - температура дома, 2 сек
2 - температура улицы, 2 сек
3 - давление, 2 секунды
И сделаем под это дело массив:
uint8_t periodDisplay[]={4,2,2,2};
и массив порядка вывода:
uint8_t orderDisplay[]={0,1,2,3};
Введем переменную, которая будет говорить нам о том, что сейчас выводим:
uint8_t mode=0;
Внесем некоторые правки в цикл loop, введем статическую переменную для хранения времени выполнения текущего режима, а так же выбор того, что надо выводить:
C++:
void loop() {
static uint32_t ch_tmr = millis();
if ((millis() - ch_tmr) < (periodDisplay[mode] * 1000))
{
switch (mode) {
case 0:
displayTime(timeToString());
break;
case 1:
displayTime(TemperToString());
break;
case 2:
displayTime(TemperOutToString());
break;
case 3:
displayTime(PressToString());
break;
default:
mode = 0;
break;
}
}
else
{
ch_tmr = millis();
mode++;
}
ShowFPS();
}
Но у него есть маленькая, досадная ошибка. В один из моментов времени переменная mode становиться равной 4 и periodDisplay[mode] берет данные с луны. Что может очень навредить лунатикам и жителям планеты ардуино. Решить это можно проверкой переполнения после строки mode++; а можно добавить всего один байт в массив periodDisplay:11:59:57.667 -> 00:03:03
11:59:57.837 -> loop per sec: 27434
11:59:58.648 -> 25°
11:59:58.852 -> loop per sec: 27481
11:59:59.667 -> 25°
11:59:59.837 -> loop per sec: 27610
12:00:00.650 -> -5°
12:00:00.853 -> loop per sec: 27574
12:00:01.633 -> -5°
12:00:01.836 -> loop per sec: 27278
12:00:02.649 -> 745mm
12:00:02.851 -> loop per sec: 27261
12:00:03.664 -> 745mm
12:00:03.832 -> loop per sec: 27046
12:00:04.643 -> 00:03:10
12:00:04.845 -> loop per sec: 27136
12:00:05.658 -> 00:03:11
12:00:05.828 -> loop per sec: 27405
12:00:06.639 -> 00:03:12
12:00:06.843 -> loop per sec: 27434
12:00:07.655 -> 00:03:13
uint8_t periodDisplay[] = {4, 2, 2, 2,0};
А т.к. давление и температура будет вряд ли меняться быстрее градуса в 2 секунды, то можно увеличить их интервал обновления, на примере темпрературы:
C++:
String TemperToString()
{
static uint32_t tmr1 = millis()-periodDisplay[1]*1000;
if (millis() - tmr1 < (periodDisplay[1]*1000)) return "";
tmr1 = millis();
hasChange = true;
return "25°";
}
Вот результат выполнения кода:
Как видно из него иногда проскакивают двойные значения температуры и давления, это связано с неким дребезгом миллис. Избавиться можно изменив период проверки, несколько увеличив его изменив в строке:12:12:48.856 -> 00:01:31
12:12:49.840 -> 00:01:32
12:12:50.857 -> 00:01:33
12:12:51.839 -> 25°
12:12:53.840 -> -5°
12:12:55.844 -> 745mm
12:12:57.847 -> 00:01:40
12:12:58.831 -> 00:01:41
12:12:59.850 -> 00:01:42
12:13:00.835 -> 00:01:43
12:13:01.820 -> 25°
12:13:03.817 -> -5°
12:13:05.813 -> -5°
12:13:05.813 -> 745mm
12:13:07.843 -> 745mm
12:13:07.843 -> 00:01:50
if (millis() - tmr1 < (periodDisplay[1]*1000)) return "";
1000 на скажем 1100. Результат показывать не буду, но поверьте, так работает как надо.
Но увлекшись выводом я совсем забыл про orderDisplay. Он нам нужен для выставления желаемого нам порядка прохождения. Внесем сл. изменения в цикл loop:
1) выбор switch идет уже по данным из массива orderDisplay
2) текущую проверку интервала идет сложнее, по данным из массива из массива. Да, вот так.
3) Все таки сделал проверку режима на выход за пределы границы массива orderDisplay, т.к. концепция немного усложнилось. Лишний 0 из массива интревалов. убрал
C++:
void loop() {
static uint32_t ch_tmr = millis();
if ((millis() - ch_tmr) < (periodDisplay[orderDisplay[mode]] * 1000)) //текущуая проверка интервала
{
switch (orderDisplay[mode]) { // выбор
case 0:
displayTime(timeToString());
break;
case 1:
displayTime(TemperToString());
break;
case 2:
displayTime(TemperOutToString());
break;
case 3:
displayTime(PressToString());
break;
}
}
else
{
ch_tmr = millis();
mode++;
if (sizeof(orderDisplay)==mode) mode=0;
}
// ShowFPS();
}
uint8_t orderDisplay[] = {0, 1, 0, 2, 0, 3};
Получим следующий вывод данных:
Как видите данные температур и давления чередуются с данными времени12:27:07.450 -> 00:03:31
12:27:08.436 -> 00:03:32
12:27:09.453 -> 00:03:33
12:27:10.434 -> 745mm
12:27:12.436 -> 00:03:36
12:27:13.421 -> 00:03:37
12:27:14.441 -> 00:03:38
12:27:15.425 -> 00:03:39
12:27:16.411 -> 25°
12:27:18.413 -> 00:03:42
12:27:19.431 -> 00:03:43
12:27:20.414 -> 00:03:44
12:27:21.434 -> 00:03:45
12:27:22.418 -> -5°
12:27:24.419 -> 00:03:48
12:27:25.434 -> 00:03:49
12:27:26.415 -> 00:03:50
12:27:27.433 -> 00:03:51
12:27:28.415 -> 745mm
12:27:30.410 -> 00:03:54
12:27:31.424 -> 00:03:55
12:27:32.405 -> 00:03:56
А если сделать
uint8_t orderDisplay[] = {0, 1, 0, 3};
то внешняя температура вообще не будет отображаться.
Цикл loop крутиться 27 тысяч раз в секунду, что вполне хватит для стабильной работы Гайвер-Энкодера и Гайвер-Батона без прерываний.
И последний момент. Мигание точек.
Лично я бы их вывел вообще отдельным пунктом. Но можно и в функцию получения времени. Сейчас функция получения времени вызывается 2 раза в секунду, что нужно для "быстрого" мигания точек. Примерный алгоритм реализации:
Что бы иметь возможность управлять точками не только из функции получения времени заводим глобальную переменную bool showDot=false;
Добавляю 2-ую функцию (что бы не терять 1-ый вариант, а так совсем не обязательно):
C++:
String timeToStringDots()
{
static uint32_t tmr1 = millis();
if (millis() - tmr1 < 500) return "";
tmr1 = millis();
char temp[10];
uint32_t nt = millis() / 1000; //получили секунды
static int8_t old_s = 255;
int8_t s = nt % 60;
showDot = !showDot;
hasChange = true;
int8_t m = nt / 60 % 60;
int8_t h = nt / 3600 % 24;
if (showDot)
snprintf(temp, 10, "%02d:%02d:%02d", h, m, s);
else
snprintf(temp, 10, "%02d %02d %02d", h, m, s);
return String(temp);
}
....
//кусок цикла loop
switch (orderDisplay[mode]) {
case 0:
displayTime(timeToStringDots());
break;
case 1:
// конец куска
На этом утреннюю писанину считаю законченной. Код можно оптимизировать как душе угодно, избавиться от String например. Использовать в своих целях (любых, как коммерческих так и нет) со ссылкой на автора разумеется.12:38:07.646 -> 00:00:00
12:38:08.120 -> 00 00 01
12:38:08.627 -> 00:00:01
12:38:09.135 -> 00 00 02
12:38:09.611 -> 00:00:02
12:38:10.118 -> 00 00 03
12:38:10.627 -> 00:00:03
12:38:11.338 -> 25°
12:38:13.133 -> 00 00 06
12:38:13.608 -> 00:00:06
12:38:14.116 -> 00 00 07
12:38:14.626 -> 00:00:07
12:38:15.135 -> 00 00 08
12:38:15.607 -> 00:00:08
12:38:16.115 -> 00 00 09
12:38:16.625 -> 00:00:09
12:38:17.303 -> -5°
Материал выдан для обучения. Итоговый код в прикреплении.
UPD. Для небольшого разнообразия сделал температуру и давление случайными при каждом получении.
Вложения
-
2.9 KB Просмотры: 12
Изменено: