Arduino.ru
Всем привет! Имеется скетч, функция которой «Автоматический звонок».
Решил написать все самостоятельно с использованием модуля GPS, RTC, IC2 LCD и силового реле. Звонок не имеет динамики.
Проблема в том, что либо функция loop() работает долго либо где то в говн* коде есть самая вонючая часть, которая работает медленно( Помогите пожалуйста!
P.s Признаюсь! Код реально ужасен.
#include #include #include #include LiquidCrystal_I2C lcd(0x27, 16, 2); iarduino_RTC time(RTC_DS3231); SoftwareSerial GPS(9, 10); //Переменные для работы с GPS unsigned char buffer[64]; // Буффер int count = 0; // Счетчик для работы с буфером. String GPS_time; // Переменная которая хранит в себе время //когда необходимо получить время с GPS String tmp_data; // Временная переменная полученных данных String tmp_substring; // Переменная для работы со строками полученных из GPS String tmp_full = ""; // Переменная для конкатетации строк из GPS byte gps_hour; byte gps_minute; byte gps_second; // Контакты const PROGMEM byte RELAY_PIN = 8; // Выход для управления РЕЛЕ // Переменные для управления кнопками const PROGMEM byte btn1 = 2; const PROGMEM byte btn2 = 3; const PROGMEM byte btn3 = 4; const PROGMEM byte btn4 = 5; const PROGMEM byte btn5 = 6; byte btn1_clicked = 1; byte btn2_clicked = 1; byte btn3_clicked = 1; byte btn4_clicked = 1; byte btn5_clicked = 1; byte btn_num = 0; // Номер экрана short lcd_num = 0; // Переменная для переключения между экранами //tmp времени byte tmp_t = 0; // Переменная для сохранения изменяемых параметров даты и времени //tmp расписания(Переменные для передачи в функцию РАСПИСАНИЕ для дальнейшей проверки) String shedule_week; // День недели для расписания String shedule_time; // Время для расписания byte shedule_type = 1; // Тип для расписания, которая меняет между 30 минутным //и 45 минутным расписанием bool was_clicked = false; // Нужно чтобы если был выбран 45 минутное //расписание вручную, то во вторник 40 минутное расписание отменяется void setup() < // Инициализация для контактов pinMode(RELAY_PIN, OUTPUT); // Переключаем PIN реле в режим для выхода //Инициализация дисплея lcd.init(); lcd.backlight(); // Включаем подсветку дисплея startArduino(); // Включаем экран приветствия на 1 - секунду lcd.clear(); // Очищаем экран для дальнейшего пользования //Инициализация модулей time.begin(); Wire.begin(); GPS.begin(9600); //Serial.begin(9600); //Отладочное изменение времени time.settime(55, 24, 10, -1, -1, -1, -1); //конфигурация входа контроллера и подключение внутреннего подтягивающего резистора pinMode(btn1, INPUT_PULLUP); pinMode(btn2, INPUT_PULLUP); pinMode(btn3, INPUT_PULLUP); pinMode(btn4, INPUT_PULLUP); pinMode(btn5, INPUT_PULLUP); >void loop() < // Экраны if (lcd_num == 0) < // Экран с таймером tmp_t = 0; if (millis() % 200 == 0) < shedule_time = time.gettime("H:i:s"); // Получение ВРЕМЕНИ GPS_time = shedule_time.substring(0, 5); String date_send = time.gettime("d-m-Y"); // Получение ДАТЫ shedule_week = time.gettime("D"); // Получение ДНЯ недели lcd.setCursor(0, 0); lcd.print(shedule_time); lcd.print(", "); lcd.setCursor(11, 0); lcd.print(shedule_week); lcd.setCursor(0, 1); lcd.print(date_send); lcd.setCursor(12, 1); if (shedule_type == 2 || shedule_week.equals(F("Tue"))) < // Если ТИП для расписания 2(40м) или ДЕНЬ недели ЧТ lcd.setCursor(12, 1); lcd.print(F("40m")); // То выводим оповещение о том,что включени ТИП 40 минутного расписания >if (shedule_type == 1 && (shedule_week.equals(F("Tue")) == false)) < // Если ТИП для расписания 1(45м) и ДЕНЬ недели не ЧТ lcd.setCursor(12, 1); lcd.print(F("45m")); // То выводим оповещение о том, что включен ТИП 45 минутного расписания >if (shedule_type == 3 && (shedule_week.equals(F("Tue")) == false)) < // Если ТИП для расписания 3(35м) и ДЕНЬ недели не ЧТ lcd.setCursor(12, 1); lcd.print(F("35m")); // То выводим оповещение о том, что включен ТИП 35 минутного расписания >if (shedule_type == 4 && (shedule_week.equals(F("Tue")) == false)) < // Если ТИП для расписания 4(30м) и ДЕНЬ недели не ЧТ lcd.setCursor(12, 1); lcd.print(F("30m")); // То выводим оповещение о том, что включен ТИП 30 минутного расписания >> > if (lcd_num == -1) < // Экран выбора 45 минутного расписания lcd.setCursor(0, 0); lcd.print(F("45 minute")); lcd.setCursor(0, 1); lcd.print(F("SEL - choose")); >if (lcd_num == -2) < // Экран выбора 40 минутного расписания lcd.setCursor(0, 0); lcd.print(F("40 minute")); lcd.setCursor(0, 1); lcd.print(F("SEL - choose")); >if (lcd_num == -3) < // Экран выбора 35 минутного расписания lcd.setCursor(0, 0); lcd.print(F("35 minute")); lcd.setCursor(0, 1); lcd.print(F("SEL - choose")); >if (lcd_num == -4) < // Экран выбора 30 минутного расписания lcd.setCursor(0, 0); lcd.print(F("30 minute")); lcd.setCursor(0, 1); lcd.print(F("SEL - choose")); >if (lcd_num == 1) < // Экран изменения ЧАСОВ lcd.setCursor(0, 0); lcd.print(F("Hour edit:")); lcd.setCursor(0, 1); lcd.print(tmp_t); lcd.print(time.gettime(":i:s, D")); >if (lcd_num == 2) < // Экран изменения МИНУТ lcd.setCursor(0, 0); lcd.print(F("Minute edit:")); lcd.setCursor(0, 1); lcd.print(time.gettime("H:")); lcd.print(tmp_t); lcd.print(time.gettime(":s, D")); >if (lcd_num == 3) < // Экран изменения СЕКУНД lcd.setCursor(0, 0); lcd.print(F("Second edit:")); lcd.setCursor(0, 1); lcd.print(time.gettime("H:i:")); lcd.print(tmp_t); lcd.print(time.gettime(", D")); >if (lcd_num == 4) < // Экран изменения дня НЕДЕЛИ lcd.setCursor(0, 0); lcd.print(F("Week edit:")); lcd.setCursor(0, 1); lcd.print(F("NOW:")); lcd.print(time.gettime("D")); lcd.setCursor(9, 1); lcd.print(F("NUM:")); lcd.setCursor(13, 1); lcd.print(tmp_t); >if (lcd_num == 5) < // Изменение ДНЯ lcd.setCursor(0, 0); lcd.print(F("Day edit:")); lcd.setCursor(0, 1); lcd.print(F("NOW:")); lcd.setCursor(5, 1); lcd.print(time.gettime("d")); lcd.setCursor(8, 1); lcd.print(F("NUM:")); lcd.print(tmp_t); >if (lcd_num == 6) < // Изменение МЕСЯЦА lcd.setCursor(0, 0); lcd.print(F("Month edit:")); lcd.setCursor(0, 1); lcd.print(F("NOW:")); lcd.setCursor(5, 1); lcd.print(time.gettime("m")); lcd.setCursor(8, 1); lcd.print(F("NUM:")); lcd.print(tmp_t); >if (lcd_num < -4) < lcd_num = 6; >else if (lcd_num > 6) < lcd_num = -4; >if ((shedule_week.equals(F("Sat")) || shedule_week.equals(F("Sun"))) && GPS_time.equals(F("10:25"))) < if (GPS.available()) < while(GPS.available()) < buffer[count++]=GPS.read(); if(count == 64) break; >tmp_data = buffer, count; tmp_data.replace("\n", "|"); tmp_full = tmp_full + tmp_data; //Serial.println(tmp_full); int index_str = tmp_full.indexOf(F("$GNGGA")); if (index_str != -1) < String time_UTC = tmp_full.substring(index_str+ 7, index_str+13); if (time_UTC == "" || time_UTC.length() < 6) < >else < gps_hour = time_UTC.substring(0, 2).toInt() + 5; gps_minute = time_UTC.substring(2, 4).toInt(); gps_second = time_UTC.substring(4, 6).toInt(); time.settime(gps_second, gps_minute, gps_hour, -1, -1, -1, -1); >> clrBuff(); count = 0; > > // Номера нажатых кнопок if (digitalRead(btn1) == LOW && btn1_clicked == 1) < // Перейти на следующий экран (Кнопка NEXT) btn_num = 0; if (lcd_num == 1) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ ЧАС tmp_t++; if (tmp_t >24) < tmp_t = 0; >> if (lcd_num == 2) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ МИНУТА tmp_t++; if (tmp_t >59) < tmp_t = 0; >> if (lcd_num == 3) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ СЕКУНД tmp_t++; if (tmp_t >59) < tmp_t = 0; >> if (lcd_num == 4) < // ИЗМЕНЕНИЕ ДНЯ НЕДЕЛИ tmp_t++; if (tmp_t >6) < tmp_t = 0; >> if (lcd_num == 5) < // ИЗМЕНЕНИЕ ДНЯ tmp_t++; if (tmp_t >31) < tmp_t = 1; >> if (lcd_num == 6) < // ИЗМЕНЕНИЕ МЕСЯЦА tmp_t++; if (tmp_t >12) < tmp_t = 1; >> > if (digitalRead(btn2) == LOW && btn2_clicked == 1) < // Собитие при нажатии на кнопку BTN2 btn_num = 0; lcd_num++; tmp_t = 0; lcd.clear(); >if (digitalRead(btn3) == LOW && btn3_clicked == 1) < btn_num = 0; if (lcd_num == 1) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ ЧАС tmp_t--; if (tmp_t >24) < tmp_t = 0; >> if (lcd_num == 2) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ МИНУТА tmp_t--; if (tmp_t >59) < tmp_t = 0; >> if (lcd_num == 3) < // ИЗМЕНЕНИЕ ЗНАЧЕНИЙ СЕКУНД tmp_t--; if (tmp_t >59) < tmp_t = 0; >> if (lcd_num == 4) < // ИЗМЕНЕНИЕ ДНЯ НЕДЕЛИ tmp_t--; if (tmp_t < 1) < tmp_t = 1; >> if (lcd_num == 5) < // ИЗМЕНЕНИЕ ДНЯ tmp_t--; if (tmp_t < 1) < tmp_t = 1; >> if (lcd_num == 6) < // ИЗМЕНЕНИЕ МЕСЯЦА tmp_t--; if (tmp_t < 1) < tmp_t = 1; >> > if (digitalRead(btn4) == LOW && btn4_clicked == 1) < btn_num = 0; lcd_num--; tmp_t = 0; lcd.clear(); >if (digitalRead(btn5) == LOW && btn5_clicked == 1) < btn_num = 0; if (lcd_num == 0) < // Внеплановый звонок relaySwitch(); >if (lcd_num == 1) < // Установить часы time.settime(-1,-1,tmp_t,-1,-1,-1,-1); tmp_t = 0; >if (lcd_num == 2) < // Установить минуты time.settime(-1,tmp_t,-1,-1,-1,-1,-1); tmp_t = 0; >if (lcd_num == 3) < // Установить секунды time.settime(tmp_t,-1,-1,-1,-1,-1,-1); tmp_t = 0; >if (lcd_num == 4) < // Установить дня недели time.settime(-1,-1,-1,-1,-1,-1,tmp_t); tmp_t = 0; >if (lcd_num == 5) < // Установить день time.settime(-1,-1,-1,tmp_t,-1,-1,-1); tmp_t = 0; >if (lcd_num == 6) < // Установить месяц time.settime(-1,-1,-1,-1,tmp_t,-1,-1); tmp_t = 0; >if (lcd_num == -1) < // Расписание - 45 минут shedule_type = 1; was_clicked = true; lcd.clear(); lcd_num = 0; >if (lcd_num == -2) < // Расписание - 40 минут shedule_type = 2; lcd.clear(); lcd_num = 0; >if (lcd_num == -3) < // Расписание - 35 минут shedule_type = 3; lcd.clear(); lcd_num = 0; >if (lcd_num == -4) < // Расписание - 30 минут shedule_type = 4; lcd.clear(); lcd_num = 0; >> btn1_clicked = digitalRead(btn1); btn2_clicked = digitalRead(btn2); btn3_clicked = digitalRead(btn3); btn4_clicked = digitalRead(btn4); btn5_clicked = digitalRead(btn5); if (shedule_week.equals(F("Tue")) && shedule_type == 1 && was_clicked == false) < sheduleFunc(); >else < if (shedule_week.equals(F("Sat")) || shedule_week.equals(F("Sun"))) < //Ничего не делаем >else < sheduleFunc(); >> > // Функции void startArduino() < lcd.setCursor(0, 0); lcd.print(F("LCD - ready")); lcd.setCursor(0, 1); lcd.print(F("Arduino UNO")); >void relaySwitch() < //Переключение реле true - вкл, false - выкл //Отладочный код(Для проверки работы всех расписаний) //--------------------------- //Serial.print("Сигнал подан: "); //Serial.print(signal_num); //Serial.println(); //--------------------------- digitalWrite(RELAY_PIN, HIGH); //Serial.print("On"); delay(3000); //Serial.print("Off"); digitalWrite(RELAY_PIN, LOW); shedule_time = time.gettime("H:i:s"); >void clrBuff() < for (int i=0; i> void sheduleFunc() < // Расписания if (shedule_type == 4) < // Уроки по 30 минут //Звонок на 1 урок if (shedule_time.equals(F("10:25:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:27:00"))) < // Перемена relaySwitch(); >//Звонок на 2 урок if (shedule_time.equals(F("10:29:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:32:00"))) < // Перемена relaySwitch(); >//Звонок на 3 урок if (shedule_time.equals(F("10:35:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:38:00"))) < // Перемена relaySwitch(); >//Звонок на 4 урок if (shedule_time.equals(F("10:41:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:44:00"))) < // Перемена relaySwitch(); >//Звонок на 5 урок if (shedule_time.equals(F("10:47:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:50:00"))) < // Перемена relaySwitch(); >//Звонок на 6 урок if (shedule_time.equals(F("10:53:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:56:00"))) < // Перемена relaySwitch(); >//Звонок на 7 урок if (shedule_time.equals(F("10:59:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:02:00"))) < // Перемена relaySwitch(); >//Звонок на 8 урок if (shedule_time.equals(F("11:05:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:08:00"))) < // Последний звонок дня 15:30:00 relaySwitch(); shedule_type = 0; >> if (shedule_type == 3) < // Уроки по 35 минут //Звонок на 1 урок if (shedule_time.equals(F("10:25:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:27:00"))) < // Перемена relaySwitch(); >//Звонок на 2 урок if (shedule_time.equals(F("10:29:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:32:00"))) < // Перемена relaySwitch(); >//Звонок на 3 урок if (shedule_time.equals(F("10:35:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:38:00"))) < // Перемена relaySwitch(); >//Звонок на 4 урок if (shedule_time.equals(F("10:41:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:44:00"))) < // Перемена relaySwitch(); >//Звонок на 5 урок if (shedule_time.equals(F("10:47:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:50:00"))) < // Перемена relaySwitch(); >//Звонок на 6 урок if (shedule_time.equals(F("10:53:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:56:00"))) < // Перемена relaySwitch(); >//Звонок на 7 урок if (shedule_time.equals(F("10:59:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:02:00"))) < // Перемена relaySwitch(); >//Звонок на 8 урок if (shedule_time.equals(F("11:05:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:08:00"))) < // Последний звонок дня 15:30:00 relaySwitch(); shedule_type = 0; >> if (shedule_type == 2 || shedule_week.equals("Tue")) < // Уроки по 40 минут //Звонок на 1 урок if (shedule_time.equals(F("10:25:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:26:00"))) < // Перемена relaySwitch(); >//Звонок на 2 урок if (shedule_time.equals(F("10:27:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:38:00"))) < // Перемена relaySwitch(); >//Звонок на 3 урок if (shedule_time.equals(F("10:39:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:38:00"))) < // Перемена relaySwitch(); >//Звонок на 4 урок if (shedule_time.equals(F("10:41:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:44:00"))) < // Перемена relaySwitch(); >//Звонок на 5 урок if (shedule_time.equals(F("10:47:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:50:00"))) < // Перемена relaySwitch(); >//Звонок на 6 урок if (shedule_time.equals(F("10:53:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:56:00"))) < // Перемена relaySwitch(); >//Звонок на 7 урок if (shedule_time.equals(F("10:59:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:02:00"))) < // Перемена relaySwitch(); >//Звонок на 8 урок if (shedule_time.equals(F("11:05:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:08:00"))) < // Последний звонок дня 15:30:00 relaySwitch(); shedule_type = 0; >> if (shedule_type == 1) < // Уроки по 45 минут //Звонок на 1 урок if (shedule_time.equals(F("08:00:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("08:45:00"))) < // Перемена relaySwitch(); >//Звонок на 2 урок if (shedule_time.equals(F("08:50:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("09:35:00"))) < // Перемена relaySwitch(); >//Звонок на 3 урок if (shedule_time.equals(F("09:40:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:15:00"))) < // Перемена relaySwitch(); >//Звонок на 4 урок if (shedule_time.equals(F("10:20:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:15:00"))) < // Перемена relaySwitch(); >//Звонок на 5 урок if (shedule_time.equals(F("10:47:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:50:00"))) < // Перемена relaySwitch(); >//Звонок на 6 урок if (shedule_time.equals(F("10:53:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("10:56:00"))) < // Перемена relaySwitch(); >//Звонок на 7 урок if (shedule_time.equals(F("10:59:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:02:00"))) < // Перемена relaySwitch(); >//Звонок на 8 урок if (shedule_time.equals(F("11:05:00"))) < // На урок relaySwitch(); >if (shedule_time.equals(F("11:08:00"))) < // Последний звонок дня 15:30:00 relaySwitch(); >> >
- Войдите на сайт для отправки комментариев
Arduino.ru
У меня есть клавиатура 4 на 4. Мне бы хотелось сделать что-нибуть вроде домофона.
Проблема в том, что я не могу получаемые значения соеденить в одну строку с паролем.
Вот код:
есть ли способы убрать задержку при включении digispark
Втр, 08/05/2018 — 14:07 | by Andry Smart
подозреваю, что при включении питания пауза в 6 секунд изза загрузчика. реально ли как то ускорить старт.
нужно плавное зажигание светодиода при старте.
набросал скетч но светоди од начинает зажигаться лишь секунд через 6.
думал дело в скетче. сделал включение (не плавное) всеравно включается только через 5 секунд.
Частота аналогового сигнала
Втр, 08/05/2018 — 11:04 | by Roman_khv
Требуется рассчитать частоту аналогового сигнала приходящего с осей акселерометра. Сейчас это делается с помощью fft в LabView (в комп порт посылаются данные с осей акса, в лаб вью они расскладываются по частоте). Подскажите как это можно сделать с помощью кода и сразу в ком порт слать вычислинную частоту? Данные по радио улетат если что
проблема с меандрой
Пнд, 07/05/2018 — 22:51 | by fsdb
Суть: Крутится шаговый двигатель и в момент считывания температуры (я так думаю), происходят проблемы с генерацией меандры. слышу кратковременный щелчек шагового. я точно знаю что возникает проблема с меандрой для шд и он кратковременно останавливается.
Не смог понять почему так происходит и сделал другую схему- https://yadi.sk/i/Pn2MqYM63VVmDr так все работает, но все таки прошу помочь разобраться в проблеме и обойтись одной ардуиной.
Ниже код первой ардуинки — подскажите где и что поправить — возможные источники проблем с меандрой.
- 12 комментариев
- Читать далее
Два регулятора с одним потенциометром
Пнд, 07/05/2018 — 21:02 | by macsim25m
Добрый день, коллеги! Вопрос такого характера, имею ардуино, потенциометр и два регулятора оборотов подключенных к бессколекторным двигателям.
Суть вопроса, как реализовать в коде, чтобы один из моторов, при вращении потенциометра, вращался в 2,3 или 4 раза медленнее, по отношению ко второму. Код предоставляю.
- 15 комментариев
- Читать далее
Ошибка при загрузке скетча // Недостаточно памяти, программа может работать нестабильно.
Пнд, 07/05/2018 — 18:55 | by wizaller
Помогите пожалуйста решить проблему. При загрузке скетча пишет:
Скетч использует 10894 байт (75%) памяти устройства. Всего доступно 14336 байт.Глобальные переменные используют 1000 байт (97%) динамической памяти, оставляя 24 байт для локальных переменных. Максимум: 1024 байт.
Arduino Mini пытаюсь поженить с графическим WIN25664 на SSD1322.
Пнд, 07/05/2018 — 15:18 | by 5N62V
Возникла у меня задача запустить WIN25664 на про мини. Подключение по 4 WIRE SPI.
Ну у меня без приключений не бывает.Началось все с того, что два дня он не хотел работать, ни с одной библиотекой. Ни с u8glib , ни с хваленой (мною) u8g2. Ни один пример не зажег даже пикселя.
- Войдите на сайт для отправки комментариев
- Читать далее
Компиляция двух заголовков в скетче
Пнд, 07/05/2018 — 15:18 | by Stochfard
Задача стоит написать скетч, который по команде поступающей через usb будет производить действия с мышью/клавиатурой.
У меня Digispark USB-A на базе Attiny85. Насколько я понял у этого микроконтроллера нет функционала работать через COM порт. Поэтому для взаимодействия с Usb и клавиатурой пытаюсь использовать DigiUSB.h и DigiKeyboard.h соответственно.
По отдельности подключение заголовков идет отлично и все компилируется, но когда пытаюсь вместе выдает ошибку копиляции.
Arduino DUE USART — настройка.
Пнд, 07/05/2018 — 14:12 | by AntonULN
Всем добрый день.
Прошу помочь c Arduino DUE… Требуется передавать данные на комп через serial (пока через стандартные «programming» , но в дальнейшем через отдельный UART-USB)
Помогите с настройками прерывания UART…есть пример для MEGA, хотелось бы, что то подобное для Arduino DUE
void USART_Init(int baudrate ) //Функция инициализации USART < /* Set baud rate */ UBRR1H = baudrate>>8; UBRR1L = baudrate;
- Войдите на сайт для отправки комментариев
- Читать далее
Помогите с lcd.cursor
Вс, 06/05/2018 — 11:28 | by lineyka
объясните, кто знает, почему при установке курсора таким способом lcd.setCursor (8, d); cd.cursor(); он еле заметно бегает по всему 16х2 экрану, а не подсвечивается в в указанном lcd.setCursor (8, d); месте ?
- 16 комментариев
- Читать далее
Оптимизируем digitalWrite на Arduino

Сегодня я протестирую фактическую скорость работы функции digitalWrite на своей Arduino Mega2560 и расскажу как ускорить работу программы в 50 раз! В основе отладочной платы Arduino Mega2560 лежит микроконтроллер AT2560, работающий с тактовой частотой 16 Мгц. Если перевести эти 16 миллионов колебаний во временной интервал, то получим достаточно небольшой период, равный 62.5 нс. Это быстро, но действительно ли Arduino выполняет операции с такой же скоростью? Давайте посмотрим.
Команды, которые мы пишем на языке Wiring, в процессе компиляции преобразуются в более простые команды, так называемый, машинный код, которые микроконтроллер уже непосредственно выполняет. Некоторые команды микроконтроллера выполняются за один такт работы микроконтроллера, на некоторые требуются больше тактов, соответственно и времени выполнения. Поэтому одна, на наш взгляд, простая команда или функция будет выполняться микроконтроллером за несколько, может даже несколько десятков или сотен тактов. К тому же, помимо вычислений, нам требуются операции чтения/записи в память, поэтому, средняя частота обработки команд будет значительно отличаться от тактовой частоты микроконтроллера. Вопрос только на сколько.
Попробуем выяснить сколько же выполняется простая команда изменения значения на цифровом выходе Arduino — digitalWrite. Проведем несколько экспериментов. В ходе первого эксперимента выполним следующий код.
void setup() < pinMode(13, OUTPUT); >void loop()
Этот код мигает светодиодом, расположенном на плате и подключенным к 13 выводу. Мы просто поочередно меняем состояние этого выхода. Если запустить этот код, то мы увидим, что светодиод будет светится непрерывно, но, на самом деле это не так. Светодиод включается и выключается, просто наш глаз не может воспринимать колебания частотой свыше 25 Гц и такие колебания мы видим как постоянно горящий светодиод с яркостью, определяемой скважностью подаваемого сигнала.
Для того, чтобы посмотреть, что же на самом деле происходит на 13 выводе, я воспользуюсь осциллографом. Осциллограмма сигнала на 13 пине выглядит так:

Похоже, что команда digitalWrite (13, HIGH) выполняется за 6.6 мкс, а digitalWrite (13, LOW) за 7.2 мкс. Итого 13.8 мкс. Это намного дольше, чем 62.5 нс, в действительности, в 220 раз дольше. Также, можно заметить, что нахождение с состоянии LOW (7.2 мкс) занимает больше времени, чем нахождение в состоянии HIGH (6.6 мкс).
Проведем следующий эксперимент.
void setup() < pinMode(13, OUTPUT); >void loop()
Теперь мы переводим дважды 13 пин в состояние HIGH, а затем один раз в LOW и цикл повторятся заново. Я ожидал увидеть значение времени для состояния HIGH равное 6.6 × 2 = 13.2 мкс и для LOW равное по прежнему 7.2 мкс. Посмотрим на фактическую осциллограмму сигнала, полученного в результате выполнения второго скетча.

По факту, две инструкции, переводящие вывод в состояние HIGH дважды занимают 19.4 мкс, или, в среднем, по 9.7 мкс на одну команду, на нахождение в состоянии LOW, по прежнему, уходит 7.2 мкс.
Попробуем реализовать теперь еще одну последовательность состояний: HIGH→LOW→LOW.
void setup() < pinMode(13, OUTPUT); >void loop()
В результате выполнения этого кода, осциллограмма сигнала на 13 пине выглядит так:

Единственная инструкция HIGH занимает 6.8 мкс — примерно так же как и ожидалось (6.6 мкс). Две подряд команды, переводящие вывод в состояние LOW занимают 13.8 мкс — это чуть меньше, чем ожидаемые 14.4 мкс (7.2 × 2 ).
Что получатеся? В цикле loop (), используя функцию digitalWrite, мы можем менять состояние пина с частотой максимум 72 кГц, а в отдельных случаях, эта частота может быть и ниже, например, как во втором случае — около 37 кГц. Такая частота значительно меньше тактовых 16 Мгц, но если использовать прерывания по таймеру, то мы можем значительно увеличить этот показатель.
Реализация функции digitalWrite в языке Wiring является, мягко говоря, не оптимальной. Если посмотреть на ее реализацию на Ассемблере, то можно обнаружить несколько проверок, в частности, проверятся не нужно ли выключить таймер ШИМ после предыдущей функции analogWrite (). Наиболее быстрая реализация, но, в то же время и наиболее затратная по времени ее написания, могла бы быть на языке Ассемблер. Но написание кода на Ассемлере — то еще насилие над собой. Я уже не говорю про отладку ассемблерного кода. Одним из компромиссных решений может быть использование оптимизированных библиотек, реализующих различные функции ввода/вывода. В дальнейших своих публикациях я рассмотрю некоторые из таких библиотек.
Еще одной причиной задержки является сама функция loop (). Когда все команды внутри loop () выполнены, то неявно для нас выполняется еще код, который производит возврат к повторному выполнению команд внутри этого цикла. Именно поэтому время последовательности из двух одинаковых состояний в конце loop () не равно сумме длительностей одиночного переключения состояния — здесь микроконтроллером еще выполняются инструкции, производящие возврат к началу цикла.
Для того, чтобы сократить время изменения состояния какого-либо вывода можно использовать команды прямой записи в регистр порта. Для изменения значений пинов с 8 по 13 используется регистр PORTB в который необходимо записать слово данных (два младших бита этого слова данных, то есть 1 и 2 биты не используются, а оставшиеся шесть — как раз и устанавливают состояния цифровых портов с 8 по 13). Для изменения состояний цифровых пинов с 0 по 7 используется PORTD.
void setup() < DDRB = B10000000; // устанавливаем 13 пин как OUTPUT >void loop() < // создаем свой бесконечный цикл while (1) < PORTB = B10000000; // устанавливаем состояние pin13 как HIGH PORTB = B00000000; // устанавливаем состояние pin13 как LOW >>
И вновь смотрим на осциллограму получившигося сигнала на 13 выводе:

Мы получили заветные 62 нс! Наша программа выполняется за 4 рабочих такта микроконтроллера. Фантастика! Режим работы 13 пина в качестве цифрового выхода мы устанавливаем всего один раз и сколько уходит на это времени — нас мало интересует. Один рабочий такт микроконтроллера уходит на перевод состояния 13 вывода в HIGH, еще один — для установки состояния этого пина в LOW и за два такта, с помощью бесконечного цикла while (1) <. >мы циклически возвращаемся вновь к установке состояния в HIGH. Зачем же здесь while (1), когда loop () делает фактически то же самое — циклически выполняет код, можете спросить вы? Изменим скетч, просто убрав из loop вложенный бесконечный цикл:
void setup() < DDRB = B10000000; // устанавливаем 13 пин как OUTPUT >void loop() < PORTB = B10000000; // устанавливаем состояние pin13 как HIGH PORTB = B00000000; // устанавливаем состояние pin13 как LOW >
И вновь обратимся к помощи осциллографа:

Как можно видеть, частота снизилась с 4 МГц до 1 Мгц. Время нахождения в состоянии HIGH не изменилось и по-прежнему равно 62.5 нс (на осцилограмме просто показано, Wid (1)). Следовательно, остальное время уходит на возврат к началу, и на это уходит в восемь раз больше времени, то есть 8 тактов. Вывод: функция loop не столь уж и эффективна.
В этом примере мы получили четырехкратное увеличение скорости, но это предельный случай. На самом деле, мы экономим несколько тактов работы и при увеличении времени выполнения программного кода внутри loop эффект сокращения времени работы программы значительно снизится.
Итак, подведу итог:
- Использование регистров портов вместо функции digitalWrite позволяет значительно увеличить скорость выполнения программы.
- Использованием вложенного в loop () бесконечного цикла while (1)мы можем сэкономить несколько тактов работы процессора на каждом витке работы цикла.
Полезные алгоритмы Arduino. Обновляемая статья!

На этой странице буду публиковать некоторые полезные алгоритмы и примеры для ваших проектов, которые накопились у меня за годы разработки собственных. Статья обновляется по мере моей ленивости, так что иногда заходите, проверяйте =)
НЕСКОЛЬКО ТРЮКОВ
- Автоформатирование – Arduino IDE умеет автоматически приводить ваш код в порядок (имеются в виду отступы, переносы строк и пробелы). Для автоматического форматирования используйте комбинацию CTRL+T на клавиатуре, либо Инструменты/АвтоФорматирование в окне IDE. Используйте чаще, чтобы сделать код красивым (каноничным, классическим) и более читаемым для других!
- Скрытие частей кода – сворачивайте длинные функции и прочие куски кода для экономии места и времени на скроллинг. Включается здесь: Файл/Настройки/Включить сворачивание кода
- Не используйте мышку! Чем выше становится ваш навык в программировании, тем меньше вы будете использовать мышку (да-да, как в фильмах про хакеров). Используйте обе руки для написания кода и перемещения по нему, вот вам несколько полезных комбинаций и хаков, которыми я пользуюсь ПОСТОЯННО:
- Ctrl+← , Ctrl+→ – переместить курсор влево/вправо НА ОДНО СЛОВО
- Home , End – переместить курсор в начало/конец строки
- Shift+← , Shift+→ – выделить символ слева/справа от курсора
- Shift+Ctrl+← , Shift+Ctrl+→ – выделить слово слева/справа от курсора
- Shift+Home , Shift+End – выделить все символы от текущего положения курсора до начала/конца строки
- Ctrl+Z – отменить последнее действие
- Ctrl+Y – повторить отменённое действие
- Ctrl+C – копировать выделенный текст
- Ctrl+X – вырезать выделенный текст
- Ctrl+V – вставить текст из буфера обмена
- Ctrl+U – загрузить прошивку в Arduino
- Ctrl+R – скомпилировать (проверить)
- Ctrl+Shift+M – открыть монитор порта
Также для отодвигания комментариев в правую часть кода используйте TAB, а не ПРОБЕЛ. Нажатие TAB перемещает курсор по некоторой таблице, из-за чего ваши комментарии будут установлены красиво на одном расстоянии за вдвое меньшее количество нажатий!
Питание от пинов – во время разработки прототипов без брэдборда всегда не хватает пинов для питания датчиков и модулей. Так вот, слабые (с потреблением тока менее 40 мА) 5 Вольтовые датчики можно питать от любых пинов! Достаточно сформировать пин как выход, и подать на него нужный сигнал (HIGH – 5 Вольт, LOW – GND).
Пример: подключаем трёхпиновый датчик звука, не используя пины 5V и GND
#define SENSOR_VCC 2 // пин VCC сенсора на D2 #define SENSOR_GND 3 // пин GND сенсора на D3 #define SENSOR_OUT 4 // сигнальный пин на D4 void setup() < // настройка пинов pinMode(SENSOR_VCC, OUTPUT); pinMode(SENSOR_GND, OUTPUT); // подаём сигналы digitalWrite(SENSOR_VCC, HIGH); digitalWrite(SENSOR_GND, LOW); >void loop() < // в качестве примера считываем сигнал boolean sound_signal = digitalRead(SENSOR_OUT); >
Питание от штекера для программатора. Вы наверняка задавались вопросом, а зачем на Arduino NANO на краю платы расположены 6 пинов? Это порт для подключения ISP программатора. Что он делает в списке лайфхаков? Вот вам фото распиновки, используйте!

- Использовать библиотеку энергосбережения GyverPower, есть подробный урок
- В паре с библиотекой сделать несколько модификаций: отключить светодиод питания и отрезать левую ногу регулятора напряжения. ВНИМАНИЕ! Резать ногу регулятору можно только в том случае, если плата питается от источника 3-5 Вольт в пины 5V и GND.

Arduino Pro Mini бывает двух типов: с кварцем на 16 МГц и 8 МГц. Китайцы обычно не подписывают плату, и есть риск перепутать разные платы, если у вас есть и те и те. На средних по цене Pro Mini стоит качественный полноразмерный кварц в овальном металлическом корпусе, на нём крупно написана цифра, обозначающая частоту в Мгц:

На недорогих платах стоит крошечный дешёвый кварц в SMD корпусе, вот он:

Берём лупу и смотрим: 16 МГц кварц маркируется примерно как “A1” or “A’N”, 8 МГц кварц маркируется “80’0” или что-то в этом стиле. Ну вот, теперь вы не перепутаете свои Pro Mini!
РАБОТА С ПЕРИФЕРИЕЙ (ДЛЯ ATMEGA328)
Или облегчённые и ускоренные куски ядра Arduino и не только
void pinModeFast(uint8_t pin, uint8_t mode) < switch (mode) < case INPUT: if (pin < 8) < bitClear(DDRD, pin); bitClear(PORTD, pin); >else if (pin < 14) < bitClear(DDRB, (pin - 8)); bitClear(PORTB, (pin - 8)); >else if (pin < 20) < bitClear(DDRC, (pin - 14)); // Mode: INPUT bitClear(PORTC, (pin - 14)); // State: LOW >return; case OUTPUT: if (pin < 8) < bitSet(DDRD, pin); bitClear(PORTD, pin); >else if (pin < 14) < bitSet(DDRB, (pin - 8)); bitClear(PORTB, (pin - 8)); >else if (pin < 20) < bitSet(DDRC, (pin - 14)); // Mode: OUTPUT bitClear(PORTC, (pin - 14)); // State: LOW >return; case INPUT_PULLUP: if (pin < 8) < bitClear(DDRD, pin); bitSet(PORTD, pin); >else if (pin < 14) < bitClear(DDRB, (pin - 8)); bitSet(PORTB, (pin - 8)); >else if (pin < 20) < bitClear(DDRC, (pin - 14)); // Mode: OUTPUT bitSet(PORTC, (pin - 14)); // State: HIGH >return; > >
void digitalWriteFast(uint8_t pin, bool x) < // раскомментируй, чтобы отключать таймер
/*switch (pin) < case 3: bitClear(TCCR2A, COM2B1); break; case 5: bitClear(TCCR0A, COM0B1); break; case 6: bitClear(TCCR0A, COM0A1); break; case 9: bitClear(TCCR1A, COM1A1); break; case 10: bitClear(TCCR1A, COM1B1); break; case 11: bitClear(TCCR2A, COM2A1); // PWM disable break; >*/ if (pin < 8) < bitWrite(PORTD, pin, x); >else if (pin < 14) < bitWrite(PORTB, (pin - 8), x); >else if (pin < 20) < bitWrite(PORTC, (pin - 14), x); // Set pin to HIGH / LOW >>// быстро инвертирует состояние пина void digitalToggleFast(uint8_t pin) < if (pin < 8) < bitSet(PIND, pin); >else if (pin < 14) < bitSet(PINB, (pin - 8)); >else if (pin < 20) < bitSet(PINC, (pin - 14)); // Toggle pin state (for 'tone()') >>
bool digitalReadFast(uint8_t pin) < if (pin < 8) < return bitRead(PIND, pin); >else if (pin < 14) < return bitRead(PINB, pin - 8); >else if (pin < 20) < return bitRead(PINC, pin - 14); // Return pin state >>
void analogWriteFast(uint8_t pin, uint16_t duty) < if (!duty) < // If duty = 0 digitalWrite(pin, LOW); // Disable PWM and set pin to LOW return; // Skip next code >switch (pin) < case 5: bitSet(TCCR0A, COM0B1); // Enable hardware timer output OCR0B = duty; // Load duty to compare register return; case 6: bitSet(TCCR0A, COM0A1); OCR0A = duty; return; case 10: bitSet(TCCR1A, COM1B1); OCR1B = duty; return; case 9: bitSet(TCCR1A, COM1A1); OCR1A = duty; return; case 3: bitSet(TCCR2A, COM2B1); OCR2B = duty; return; case 11: bitSet(TCCR2A, COM2A1); OCR2A = duty; return; >>
// ВНИМАНИЕ! Нужное опорное установлено DEFAULT, можно изменить на своё uint16_t analogReadFast(uint8_t pin) < pin = ((pin < 8) ? pin : pin - 14); // analogRead(2) = analogRead(A2) ADMUX = (DEFAULT
// установка делителя АЦП. Доступны 2,4,8,16,32,64,128 void analogPrescaler(uint8_t prescaler) < switch (prescaler) < case 2: ADCSRA = (ADCSRA & 0xF8) | 0x01; break; case 4: ADCSRA = (ADCSRA & 0xF8) | 0x02; break; case 8: ADCSRA = (ADCSRA & 0xF8) | 0x03; break; case 16: ADCSRA = (ADCSRA & 0xF8) | 0x04; break; case 32: ADCSRA = (ADCSRA & 0xF8) | 0x05; break; case 64: ADCSRA = (ADCSRA & 0xF8) | 0x06; break; case 128: ADCSRA = (ADCSRA & 0xF8) | 0x07; break; >>
// пример использования PCINT - прерывания на любом пине // прерывание вызывается при переключении состояния любого пина из группы // наши обработчики прерываний ISR(PCINT0_vect) < // пины 8-13 >ISR(PCINT1_vect) < // пины A0-A5 >ISR(PCINT2_vect) < // пины 0-7 >// функция для настройки PCINT uint8_t attachPCINT(uint8_t pin) < if (pin < 8) < // D0-D7 (PCINT2) PCICR |= (1 else if (pin > 13) < //A0-A5 (PCINT1) PCICR |= (1 else < // D8-D13 (PCINT0) PCICR |= (1 > // быстрый digitalRead для опроса внутри ISR // пригодится для проверки конкретного пина bool pinRead(uint8_t pin) < if (pin < 8) < return bitRead(PIND, pin); >else if (pin < 14) < return bitRead(PINB, pin - 8); >else if (pin < 20) < return bitRead(PINC, pin - 14); >>
// прицепляем аппаратные прерывания напрямую (пин, тип) void attachInterruptFast(uint8_t num, uint8_t type) < switch (num) < case 0: EICRA = (EICRA & 0x0C) | type; // Setup interrupt type bitSet(EIMSK, INT0); // Enable external interrupt return; case 1: EICRA = (EICRA & 0x03) | (type > void detachInterruptFast(uint8_t num) < bitClear(EIMSK, num); // Disable external interrupt >// векторы. В них будет прыгать прерывание ISR(INT0_vect) < >ISR(INT1_vect)
// пример работы с юартом // UART_begin(бод) - запустить // UART_write(byte) - отправить байт // UART_available() - проверка на входящий // UART_read() - прочитать байт // UART_end() - выключить void setup() < UART_begin(9600); UART_write(40); // отправить байт 40 UART_write(40); >void loop() < if (UART_available()) < // если есть что на приём byte data = UART_read(); // прочитать UART_write(data); // отправить обратно >> // собственно функции void UART_begin(uint32_t baudrate) < uint16_t speed = (F_CPU / (8L * baudrate)) - 1; UBRR0H = highByte(speed); UBRR0L = lowByte(speed); UCSR0A = (1 void UART_write(byte data) < while (!(UCSR0A & (1 bool UART_available() < return (UCSR0A & (1 byte UART_read() < byte data = UDR0; return data; >void UART_end()
// используем минимальный набор для Serial // стандартный читаемый вывод делает встроенный в ядро Print.h // ==== класс ==== #include "Print.h" class Uart : public Print < public: void begin(uint32_t baudrate) < uint16_t speed = (F_CPU / (8L * baudrate)) - 1; UBRR0 = speed; UCSR0A = (1 void end() < UCSR0B = 0; >size_t write(uint8_t data) < while (!(UCSR0A & (1 bool available() < return (UCSR0A & (1 char read() < byte data = UDR0; return data; >private: >; // ==== конец класса ==== Uart uart; void setup() < uart.begin(9600); uart.println("Hello "); uart.println(123456); // также есть // uart.end(); // uart.write(); // uart.available(); // uart.read(); >void loop()
// ВНИМАНИЕ! Это просто сон, без отключения АЦП и прочих жрущих блоков // для полноценного комфортного сна используйте GyverPower void setup() < pinMode(2, 2); // внешняя подтяжка лучше длоя энергосбережения! Serial.begin(9600); Serial.println("hello!"); delay(500); Serial.println("go to sleep"); delay(500); attachInterrupt(0, wakeUp, LOW); // вкл прерывание пробуждения goToSleep(); // отправка в сон delay(1000); // после сна Serial.println("im back in business"); // продолжили работу >void wakeUp() < detachInterrupt(0); // откл прерывание пробуждения SMCR &= ~ (1 void goToSleep() < SMCR |= (1 void loop()// полные аналоги стандартным функциям времени // пригодится, если работать без Arduino.h // доступные функции // необходимо вызвать uptime0Init() при запуске, чтобы всё завелось void uptime0Init(); unsigned long millis0(); unsigned long micros0(); void delay0(unsigned long ms); void delayMicroseconds0(unsigned int us); // ==================== РЕАЛИЗАЦИЯ ================== #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256)) #define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000) #define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3) #define FRACT_MAX (1000 >> 3) #define MICROS_MULT (64 / clockCyclesPerMicrosecond()) volatile unsigned long timer0_overflow_count = 0; volatile unsigned long timer0_millis = 0; static unsigned char timer0_fract = 0; void uptime0Init() < sei(); TCCR0A = (1 ISR(TIMER0_OVF_vect) < timer0_millis += MILLIS_INC; timer0_fract += FRACT_INC; if (timer0_fract >= FRACT_MAX) < timer0_fract -= FRACT_MAX; timer0_millis++; >timer0_overflow_count++; > unsigned long millis0() < uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer0_millis; // перехватить значение SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return m; // вернуть миллисекунды >unsigned long micros0() < uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer0_overflow_count; // счет переполнений uint8_t t = TCNT0; // считать содержимое счетного регистра if ((TIFR0 & _BV(TOV0)) && (t < 255)) // инкремент по переполнению m++; SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return (long)(((m void delay0(unsigned long ms) < uint32_t start = micros0(); while (ms >0) < // ведем отсчет while ( ms >0 && (micros0() - start) >= 1000) < ms--; start += 1000; >> > void delayMicroseconds0(unsigned int us) < #if F_CPU >= 24000000L us *= 6; // x6 us, = 7 cycles us -= 5; //=2 cycles #elif F_CPU >= 20000000L __asm__ __volatile__ ( "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop"); //just waiting 4 cycles if (us = 16000000L if (us = 12000000L if (us = 8000000L if (us >= 2; // us div 4, = 4 cycles #endif __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); >
// полные аналоги стандартным функциям времени // пригодится, если работать без Arduino.h // доступные функции // необходимо вызвать uptime2Init() при запуске, чтобы всё завелось void uptime2Init(); unsigned long millis2(); unsigned long micros2(); void delay2(unsigned long ms); void delayMicroseconds2(unsigned int us); // =================== РЕАЛИЗАЦИЯ ================== #define MICROSECONDS_PER_TIMER2_OVERFLOW (clockCyclesToMicroseconds(64 * 256)) #define MILLIS2_INC (MICROSECONDS_PER_TIMER2_OVERFLOW / 1000) #define FRACT2_INC ((MICROSECONDS_PER_TIMER2_OVERFLOW % 1000) >> 3) #define FRACT2_MAX (1000 >> 3) #define MICROS2_MULT (64 / clockCyclesPerMicrosecond()) volatile unsigned long timer2_overflow_count = 0; volatile unsigned long timer2_millis = 0; static unsigned char timer2_fract = 0; void uptime2Init() < sei(); TCCR2A = (1 ISR(TIMER2_OVF_vect) < timer2_millis += MILLIS2_INC; timer2_fract += FRACT2_INC; if (timer2_fract >= FRACT2_MAX) < timer2_fract -= FRACT2_MAX; timer2_millis++; >timer2_overflow_count++; > unsigned long millis2() < uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer2_millis; // перехватить значение SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return m; // вернуть миллисекунды >unsigned long micros2() < uint8_t oldSREG = SREG; // запомнинаем были ли включены прерывания cli(); // выключаем прерывания unsigned long m = timer2_overflow_count; // счет переполнений uint8_t t = TCNT2; // считать содержимое счетного регистра if ((TIFR2 & _BV(TOV2)) && (t < 255)) // инкремент по переполнению m++; SREG = oldSREG; // если прерывания не были включены - не включаем и наоборот return (long)(((m void delay2(unsigned long ms) < uint32_t start = micros2(); while (ms >0) < // ведем отсчет while ( ms >0 && (micros2() - start) >= 1000) < ms--; start += 1000; >> > void delayMicroseconds2(unsigned int us) < #if F_CPU >= 24000000L us *= 6; // x6 us, = 7 cycles us -= 5; //=2 cycles #elif F_CPU >= 20000000L __asm__ __volatile__ ( "nop" "\n\t" "nop" "\n\t" "nop" "\n\t" "nop"); //just waiting 4 cycles if (us = 16000000L if (us = 12000000L if (us = 8000000L if (us >= 2; // us div 4, = 4 cycles #endif __asm__ __volatile__ ( "1: sbiw %0,1" "\n\t" // 2 cycles "brne 1b" : "=w" (us) : "0" (us) // 2 cycles ); >
// пример чтения установленных фьюз-байтов #define LOW_FUSE (0x0000) #define LOCK (0x0001) #define EXTENDED_FUSE (0x0002) #define HIGH_FUSE (0x0003) void setup() < Serial.begin(9600); Serial.print("Low: 0x"); Serial.println(fuse_get(LOW_FUSE_BYTE), HEX); Serial.print("High: 0x"); Serial.println(fuse_get(HIGH_FUSE_BYTE), HEX); Serial.print("Extended: 0x"); Serial.println(fuse_get(EXTENDED_FUSE_BYTE), HEX); Serial.print("Lock: 0x"); Serial.println(fuse_get(LOCK_BYTE), HEX); >void loop() < >uint8_t fuse_get(uint16_t address)
СТРУКТУРА ПРОГРАММЫ
/* пример "чистого" и удобного для работы цикла loop() работать так гораздо удобнее, и труднее запутаться Пример: 1: получение показаний с датчика, фильтрация 2: отработка нажатий кнопок 3: отрисовка на дисплей 4: отправка команд на управляющие устройства и так далее */ void setup() < >void loop() < task_1(); task_2(); task_3(); task_4(); // . >void task_1() < // какие-то действия, ведущие к одной цели >void task_2() < // какие-то действия, ведущие к одной цели >void task_3() < // какие-то действия, ведущие к одной цели >void task_4() < // какие-то действия, ведущие к одной цели >
/* Данный код демонстрирует переключение режимов работы при помощи кнопки Для удобства используется библиотека отработки нажатий кнопки */ #define PIN 3 // кнопка подключена сюда (PIN — КНОПКА — GND) #define MODE_AM 5 // количество режимов (от 0 до указанного) #include «GyverButton.h» // моя библиотека для более удобной работы с кнопкой // скачать мождно здесь https://github.com/AlexGyver/GyverLibs GButton butt1(PIN); // создаём нашу «кнопку» byte mode = 0; // переменная режима void setup() < Serial.begin(9600); >void loop() < butt1.tick(); // обязательная функция отработки. Должна постоянно опрашиваться if (butt1.isPress()) < // правильная отработка нажатия с защитой от дребезга // увеличиваем переменную номера режима. Если вышла за количество режимов - обнуляем if (++mode >= MODE_AM) mode = 0; > // всё переключение в итоге сводится к оператору switch switch (mode) < case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; >> // наши задачи, внутри функций понятное дело может быть всё что угодно void task_0() < Serial.println("Task 0"); >void task_1() < Serial.println("Task 1"); >void task_2() < Serial.println("Task 2"); >void task_3() < Serial.println("Task 3"); >void task_4()
/* Данный код демонстрирует переключение режимов работы при помощи кнопки Для удобства используется библиотека отработки нажатий кнопки В этом варианте примера функции «режимов» вызываются только один раз */ #define PIN 3 // кнопка подключена сюда (PIN — КНОПКА — GND) #define MODE_AM 5 // количество режимов (от 0 до указанного) #include «GyverButton.h» // моя библиотека для более удобной работы с кнопкой // скачать мождно здесь https://github.com/AlexGyver/GyverLibs GButton butt1(PIN); // создаём нашу «кнопку» byte mode = 0; // переменная режима void setup() < Serial.begin(9600); >void loop() < butt1.tick(); // обязательная функция отработки. Должна постоянно опрашиваться if (butt1.isPress()) < // правильная отработка нажатия с защитой от дребезга // увеличиваем переменную номера режма. Если вышла за количество режимов - обнуляем if (++mode >= MODE_AM) mode = 0; // всё переключение в итоге сводится к оператору switch // переключение и вызов происходит только при нажатии. switch (mode) < case 0: task_0(); break; case 1: task_1(); break; case 2: task_2(); break; case 3: task_3(); break; case 4: task_4(); break; >> > // наши задачи, внутри функций понятное дело может быть всё что угодно void task_0() < Serial.println("Task 0"); >void task_1() < Serial.println("Task 1"); >void task_2() < Serial.println("Task 2"); >void task_3() < Serial.println("Task 3"); >void task_4()Допустим есть у нас задача: переключать режимы по одному и “по кругу”, в простейшем варианте это реализуется вот так:
#define MODE_AMOUNT 5 byte mode = 0; void nextMode() < mode++; // увеличиваем переменную номера режима if (mode >= MODE_AMOUNT) mode = 0; // закольцовываем > // Время выполнения 0.5 мкс
Есть ещё парочка интересных вариантов. Результат не отличается, но сам механизм знать будет полезно:
// второй вариант. Время выполнения 0.5 мкс if (++mode >= MODE_AMOUNT) mode = 0; // тут инкремент внесён в условие, получаем более короткую запись // третий вариант. Время выполнения 5.5 мкс. НЕ ИСПОЛЬЗУЙТЕ ЕГО! mode = ++mode % MODE_AMOUNT; // очень интересный вариант, без использования условия! Работает остаток от деления
Очень часто в примерах используется delay(), и это несомненно очень плохо: построить на основе такого примера серьёзный проект практически невозможно. Одним из частых и критичных моментов являются циклы с delay(), это может касаться каких то эффектов со светодиодами, адресными светодиодными лентами, и прочими действиями со счётчиком:
for (int i = 0; i < 30; i++) < // например, зажигаем i-ый светодиод delay(100); >
Как переписать такой цикл, чтобы он не блокировал выполнение кода? Очень просто: нужно избавиться и от цикла, и от delay. Введём таймер на millis(), и будем работать по нему:
int counter = 0; // замена i uint32_t timer = 0; // переменная таймера #define T_PERIOD 100 // период переключения void loop() < if (millis() - timer >= T_PERIOD) < // таймер на millis() timer = millis(); // сброс // действие с counter - наш i-ый светодиод например counter++; // прибавляем счётчик if (counter >30) counter = 0; // закольцовываем изменение > >
Вот собственно и всё! Вместо переменной цикла i у нас теперь свой глобальный счётчик counter, который бегает от 0 до 30 (в этом примере) с периодом 100 мс.
ПОЛЕЗНЫЕ МАКРОСЫ
Часто приходится писать один и тот же цикл for, можно заменить его макросом:
#define FOR_i(from, to) for(int i = (from); i < (to); i++)
Макрос создаст цикл for от from до to и счётчиком i внутри. Пример использования:
// выведет числа от 0 до 9 FOR_i(0, 10)
Нужны вложенные циклы? Можно сделать макрос с выбором имени переменной#define FOR(x, from, to) for (int (x) = (from); (x) < (to); (x)++)
И пример с ним:
// работа с двумерным массивом FOR(i, 0, 10) < FOR(j, 0, 3) < someArray[i][j] = someValue; >>
//=========================== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr += (x);\ if (flag) //===========================
Данный макрос заменяет “таймер на миллис” одной строчкой, без использования библиотек и создания классов! Пользоваться очень просто:
EVERY_MS(100) < // . // данный код будет выполняться каждые 100 мс >
Единственное ограничение: нельзя вызывать макрос больше одного раза в одном и том же блоке кода, это приведёт к ошибке =) То есть вот так нельзя:
void loop() < EVERY_MS(100) < // ваш код >EVERY_MS(500) < // ваш код >>
Если очень нужна такая конструкция – помещаем каждый вызов в свой блок кода:
void loop() < < EVERY_MS(100) < // ваш код >> < EVERY_MS(500) < // ваш код >> >
Либо используем блоки кода по условиям или как функцию:
void loop() < if (someCondition) < EVERY_MS(100) < // ваш код >> myAction(); > void myAction() < EVERY_MS(500) < // ваш код >>
При разработке проекта важна отладка, мы делаем её средствами Serial.println(). Чтобы после окончания разработки не убирать из кода все вызовы Serial и не нагружать код условными конструкциями #ifdef DEBUG…. #endif, можно сделать так:
#ifdef DEBUG_ENABLE #define DEBUG(x) Serial.println(x) #else #define DEBUG(x) #endif
Если DEBUG_ENABLE задефайнен – все вызовы DEBUG() в коде будут заменены на вывод в порт. Если не задефайнен – они будут заменены НИЧЕМ, то есть просто “вырежутся” из кода! Также по DEBUG_ENABLE можно запустить сериал и получить полный контроль над отладкой: если она не нужна – убрали DEBUG_ENABLE и из кода убрался запуск порта и все выводы, что резко сокращает объём занимаемой памяти:
// раздефайнить или задефайнить для использования //#define DEBUG_ENABLE #ifdef DEBUG_ENABLE #define DEBUG(x) Serial.println(x) #else #define DEBUG(x) #endif void setup() < #ifdef DEBUG_ENABLE Serial.begin(9600); #endif >void loop()
Чтобы полностью остановить программу в каком-то месте, обычно используют замкнутые циклы. Это нужно при отработке каких-то критических ошибок и в других ситуациях. Обычно это делается так:
Можно обернуть в более понятный макрос:
#define FOREVER for(;;)
Теперь в коде на строке FOREVER; код “зависнет”:
// . if (criticalError) FOREVER; // .
ВРЕМЯ, ТАЙМЕРЫ
// Данный код выполняет действия периодически за указанный период // Нам нужно задать период таймера В МИЛЛИСЕКУНДАХ // дней*(24 часов в сутках)*(60 минут в часе)*(60 секунд в минуте)*(1000 миллисекунд в секунде) // (long) обязательно для больших чисел, иначе не посчитает // можно посчитать на калькуляторе, но какбэ ардуино и есть калькулятор, пусть считает. unsigned long period_time = (long)5*24*60*60*1000; // переменная таймера, максимально большой целочисленный тип (он же uint32_t) unsigned long my_timer; void setup() < my_timer = millis(); // "сбросить" таймер >void loop() < if ((long)millis() - my_timer >period_time) < my_timer = millis(); // "сбросить" таймер // набор функций, который хотим выполнить один раз за период // бла бла бла // . >>
// Данный код выполняет действие с периодом PERIOD на время WORK_TIME, эдакий свернизкочастотный ШИМ // Банально автополив // Нам нужно задать период таймера В МИЛЛИСЕКУНДАХ // дней*(24 часов в сутках)*(60 минут в часе)*(60 секунд в минуте)*(1000 миллисекунд в секунде) // (long) обязательно для больших чисел, иначе не посчитает // можно посчитать на калькуляторе, но какбэ ардуино и есть калькулятор, пусть считает. unsigned long period_time = (long)5*24*60*60*1000; unsigned long work_time = 10000; // время, на которое ну скажем включится лампочка #define TIMER_START 0 // 1 - отсчёт периода с момента ВЫКЛЮЧЕНИЯ лампочки, 0 - с ВКЛЮЧЕНИЯ // переменная таймера, максимально большой целочисленный тип (он же uint32_t) unsigned long period_timer, work_timer; boolean work_flag; void setup() < period_timer = millis(); // "сбросить" таймер >void loop() < if ((long)millis() - period_timer >period_time) < period_timer = millis(); // "сбросить" таймер периода work_timer = millis(); // сбросить таймер выполнения work_flag = true; // начали выполнение // включить лампу, помпу, реле, что угодно // банально digitalWrite(пин, HIGH) >if ( ((long)millis() - work_timer > work_time) && work_flag) < work_flag = false; // сброс флага на выполнение // можно сбросить таймер периода ПОСЛЕ выполнения задачи. Подумайте над этим! if (TIMER_START) period_timer = millis(); // выключить лампу, помпу, реле, что угодно // банально digitalWrite(пин, LOW) >if (work_flag) < // а вот этот блок кода выполняется всегда, пока мы находимся по времени "внутри" WORK_TIME >>
Таймер, который после срабатывания переключает период на другой, например включаем реле на 10 секунд каждые 60 минут
uint32_t tmr; bool flag; #define period1 10*1000L #define period2 60*60*1000L void setup() < >void loop() < if (millis() - tmr >= (flag ? period1 : period2)) < tmr = millis(); flag = !flag; // тут можно сделать digitalWrite(pin, flag); // для переключения реле >>
/* Делаем "параллельное" выполнение нескольких задач с разным периодом выполнения */ #define PERIOD_1 100 // период первой задачи #define PERIOD_2 2000 // период второй задачи #define PERIOD_3 666 // . unsigned long timer_1, timer_2, timer_3; void setup() < >void loop() < if (millis() - timer_1 >PERIOD_1) < // условие таймера timer_1 = millis(); // сброс таймера // выполняем блок №1 каждые PERIOD_1 миллисекунд >if (millis() - timer_2 > PERIOD_2) < timer_2 = millis(); // выполняем блок №2 каждые PERIOD_2 миллисекунд >if (millis() - timer_3 > PERIOD_3) < timer_3 = millis(); // выполняем блок №3 каждые PERIOD_3 миллисекунд >>
/* Пример параллельного выполнения нескольких задач по таймеру. Библиотеку GyverTimer можно скачать здесь https://github.com/AlexGyver/GyverLibs */ #include "GyverTimer.h" // создать миллисекундный таймер, в скобках период в миллисекундах GTimer myTimer1(MS, 500); GTimer myTimer2(MS, 600); GTimer myTimer3(MS, 1000); void setup() < Serial.begin(9600); >void loop()
//==== MILLISTIMER MACRO ==== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr += (x);\ if (flag) //===========================
Данный макрос заменяет “таймер на миллис” одной строчкой, без использования библиотек и создания классов! Пользоваться очень просто: добавьте указанный выше макрос в самое начало кода и вызывайте его как функцию
EVERY_MS(100) < // . // данный код будет выполняться каждые 100 мс >
Единственное ограничение: нельзя вызывать макрос больше одного раза в одном и том же блоке кода, это приведёт к ошибке =) То есть вот так нельзя:
void loop() < EVERY_MS(100) < // ваш код >EVERY_MS(500) < // ваш код >>
Если очень нужна такая конструкция – помещаем каждый вызов в свой блок кода:
void loop() < < EVERY_MS(100) < // ваш код >> < EVERY_MS(500) < // ваш код >> >
Либо используем блоки кода по условиям или как отдельную функцию, которая “оборачивает” макрос:
//=========================== #define EVERY_MS(x) \ static uint32_t tmr;\ bool flag = millis() - tmr >= (x);\ if (flag) tmr = millis();\ if (flag) //=========================== void setup() <> void loop() < myAction1(); myAction2(); >void myAction1() < EVERY_MS(1000) < // ваш код >> void myAction2() < EVERY_MS(500) < // ваш код >>
uint32_t now = millis(); while (millis () - now < 5000) < // тут в течение 5000 миллисекунд вертится код // удобно использовать для всяких калибровок >
В первом пункте мы разобрали “классический” таймер на millis(), давайте посмотрим ещё один, иногда встречающийся в скетчах:
#define PERIOD_1 2000 void loop() < if ( (millis() % PERIOD_1) == 0) < delay(1); // ваше действие >>
Чем он хорош и чем плох? Хорош тем, что не нужна отдельная переменная типа uint32_t, а также данный таймер не сбивается и без проблем проходит через переполнение millis(). Минусы весьма существенные: операция % выполняется очень долго, также внутри таймера нужен delay(1), иначе таймер может сработать несколько раз в течение одной миллисекунды (пока миллис кратен периоду). Не используйте этот таймер, но знайте, что такой есть.
Недавно я задался вопросом: а можно ли сделать таймер на миллис, который будет корректно обходить переполнение millis() и не сбивать период? Можно, сделал:
#define PERIOD 500 uint32_t timer = 0; void loop() < if (millis() - timer >= PERIOD) < // ваше действие do < timer += PERIOD; if (timer < PERIOD) break; // переполнение uint32_t >while (timer < millis() - PERIOD); // защита от пропуска шага >>
Данный таймер имеет механику классического таймера с хранением переменной таймера, а его период всегда кратен PERIOD и не сбивается. Эту конструкцию можно упростить до
#define PERIOD 500 uint32_t timer = 0; void loop() < if (millis() - timer >= PERIOD) < // ваше действие timer += PERIOD; >>
В этом случае алгоритм получается короче, кратность периодов сохраняется, но теряется защита от пропуска вызова и переполнения millis(). Мои библиотеки GyverTimer и timerMinim были обновлены до этого алгоритма, можете работать с ними.
// получаем из миллиса часы, минуты и секунды работы программы (часы не ограничены, т.е. аптайм) uint32_t sec = millis() / 1000ul; int timeHours = (sec / 3600ul); int timeMins = (sec % 3600ul) / 60ul; int timeSecs = (sec % 3600ul) % 60ul;
// получаем из миллиса часы, минуты и секунды работы программы. Часы ограничиваем до 23, т.е. режим часов uint32_t sec = millis() / 1000ul; int timeHours = (sec / 3600ul) % 24; int timeMins = (sec % 3600ul) / 60ul; int timeSecs = (sec % 3600ul) % 60ul;
// Полезные функции для получения номера дня по дате (день по счёту с 01.01.2000) // Можно использовать в качестве "миллиса" при подключенном RTC // Вторая функция получает дату из дня (день по счёту с 01.01.2000) void setup() < Serial.begin(9600); // смотрим каким днём (по счёту с 01.01.2000) будет 20 июня 2066 года Serial.println(daySince2000(20, 6, 2066)); // временные переменные для работы dayToDate() byte day; byte month; int year; // смотрим, в какую дату попадает день 24278 (по счёту с 01.01.2000) // данная функция запишет результат в указанные переменные! dayToDate(24278, day, month, year); Serial.print(day); Serial.print('.'); Serial.print(month); Serial.print('.'); Serial.println(year); >void loop() < >// ============== САМИ ФУНКЦИИ ============ // возвращает количество дней с 01.01.2000 (день 1-30/31, месяц 1-12, год 2000-. ) int daySince2000(byte day, byte month, int year) < int days = day-1; // + день текущего месяца for (int i = 0; i < month - 1; i++) days += (i<7)?((i==1)?28:((i&1)?30:31)):((i&1)?31:30); if (month >2 && (year & 3) == 0) days++; // + високосный days += (year - 2000) * 365; // + предыдущие года days += (year - 2000 + 3) / 4; // + предыдущие високосные года return days; > // записывает дату дня с номером day2000 в переменные по ссылкам void dayToDate(int day2000, byte &day, byte &month, int &year) < day2000++; int countDays = day2000; year = 0; while (countDays >0) < year++; countDays -= 365; if ((year & 3) == 0) countDays--; >year--; day2000 -= year * 365; day2000 -= (year + 3) / 4; int days = 0; for (int i = 0; i < 12; i++) < int daysm = (i<7)?((i==1)?((year&3)==0?29:28):((i&1)?30:31)):((i&1)?31:30); if (day2000 <= days + daysm) < month = i + 1; day = day2000 - days; break; >days += daysm; > >РАБОТА С SERIAL
Парсинг переехал в отдельный урок
ФИЛЬТРЫ ЗНАЧЕНИЙ
/* Простейший фильтр: запаздывающий, бегущее среднее, "цифровой фильтр", фильтр низких частот - это всё про него любимого Имеет две настройки: постоянную времени FILTER_STEP (миллисекунды), и коэффициент "плавности" FILTER_COEF Данный фильтр абсолютно универсален, подходит для сглаживания любого потока данных При маленьком значении FILTER_COEF фильтрованное значение будет меняться очень медленно вслед за реальным Чем больше FILTER_STEP, тем меньше частота опроса фильтра Сгладит любую "гребёнку", шум, ложные срабатывания, резкие вспышки и прочее говно. Пользуюсь им постоянно */ #define FILTER_STEP 5 #define FILTER_COEF 0.05 int val; float val_f = 0.0; unsigned long filter_timer; void setup() < Serial.begin(9600); >void loop() < if (millis() - filter_timer >FILTER_STEP) < filter_timer = millis(); // просто таймер // читаем значение (не обязательно с аналога, это может быть ЛЮБОЙ датчик) val = analogRead(0); // основной алгоритм фильтрации. Внимательно прокрутите его в голове, чтобы понять, как он работает val_f = val * FILTER_COEF + val_f * (1 - FILTER_COEF); // для примера выведем в порт Serial.println(val_f); >>
/* "Удобный" фильтр бегущее среднее (низких частот) Библиотеку GyverHacks можно скачать здесь https://github.com/AlexGyver/GyverLibs */ #include "GyverHacks.h" GFilterRA analog0; // фильтр назовём analog0 void setup() < Serial.begin(9600); // установка коэффициента фильтрации (0.0. 1.0). Чем меньше, тем плавнее фильтр analog0.setCoef(0.01); // установка шага фильтрации (мс). Чем меньше, тем резче фильтр analog0.setStep(10); >void loop()
Классический вариант бегущего среднего выглядит так:// filtered_val - фильтрованное значение // val - новое значение (с датчика) // k - коэффициент фильтрации 0.. 1. Обычно около 0.01-0.1 (то бишь float) filtered_val = filtered_val * (1 - k) + val * k;
Но если раскрыть скобки и “причесать” выражение, получится очень красивая короткая запись. Время выполнения одной операции фильтрации составляет 35 мкс.
filtered_val += (val - filtered_val) * k;
У фильтра “бегущее среднее” не один настраиваемый параметр, как может показаться на первый взгляд. Помимо коэффициента фильтрации k очень большую роль играет время итерации, то есть период вызова фильтра. В реальном коде фильтр вызывается с определённым промежутком времени, чтобы фильтровать шумы. Я обычно настраиваю фильтр вручную по графику, который строится средствами Arduino IDE или программой Serial Port Plotter. Задаюсь периодом итерации и подгоняю k, пока не станет “хорошо”. Но есть и аналитический способ расчёта коэффициента фильтрации (или времени итерации). При выборе значения коэффициента k необходимо отталкиваться от того, какие изменения сигнала нам интересны, а какие мы будем считать за шум. Сделать это можно с помощью следующего выражения: t = dt * (1 / k – 1) где t — период времени, который отделяет слишком быстрые изменения от требуемых; dt — время итерации (период вызова фильтра). Например, если в нашем случае с потенциометром, k = 0,1, а время между двумя измерениями dt = 20 мс, то время t = (1-0.1) * 0,02 / 0.1 = 0,18 сек. То есть все изменения сигнала, которые длятся меньше 0,18 секунд будут подавляться. Во втором случае (при k = 0,3), мы получим t = 0,047 сек. Вникнув в эту связь, можно настроить фильтр, всего лишь глянув на график сырого значения!
/* Элементарная реализация среднего арифметического. Сложили NUM_READINGS измерений, затем разделили сумму на NUM_READINGS и всё! Является "частным случаем" предыдущего фильтра Время выполнения примерно равно: 10 значений 50 мкс, 50 значений 92 мкс, 100 значений 146 мкс */ #define NUM_READINGS 500 int average; void setup() < Serial.begin(9600); >void loop() < long sum = 0; // локальная переменная sum for (int i = 0; i < NUM_READINGS; i++) < // согласно количеству усреднений sum += analogRead(0); // суммируем значения с любого датчика в переменную sum >average = sum / NUM_READINGS; // находим среднее арифметическое, разделив сумму на число измерений Serial.println(average); // для примера выводим в порт >
/* Готовая функция для вычисления среднего арифметического Принимает новые значения, суммирует их в своём массиве */ #define NUM_AVER 10 // выборка (из скольки усредняем) int middleArifm(int newVal) < // принимает новое значение static byte idx = 0; // индекс static int valArray[NUM_AVER]; // массив valArray[idx] = newVal; // пишем каждый раз в новую ячейку if (++idx >= NUM_AVER) idx = 0; // перезаписывая самое старое значение long average = 0; // обнуляем среднее for (int i = 0; i < NUM_AVER; i++) < average += valArray[i]; // суммируем >average /= NUM_AVER; // делим return average; // возвращаем >
/* Готовая функция для вычисления среднего арифметического Принимает новые значения, суммирует их в своём массиве */ // оптимизированный вариант без суммирования массива при каждом вызове // значения хранятся и отнимаются из переменной суммы #define NUM_AVER 10 // выборка (из скольки усредняем) int aver(int val) < static int t = 0; static int vals[NUM_AVER]; static int average = 0; if (++t >= NUM_AVER) t = 0; // перемотка t average -= vals[t]; // вычитаем старое average += val; // прибавляем новое vals[t] = val; // запоминаем в массив return (average / NUM_AVER); >
/* Медианный фильтр — довольно простая и интересная штука. Берёт значения и выбирает из них среднее. Не усредняет, а именно ВЫБИРАЕТ, отбрасывает все сильно отличющиеся. Время выполнения близко к нулю мкс Простой пример, чем отличается медианный фильтр от среднего арифметического: Возьмём числа 3, 4, 50. Среднее арифметическое даст нам 19. Целью медианного фильтра является фильтрация резких скачков, и после фильтрации он даст нам 4, как среднее между 3 и 50, а 50 будет отброшено как скачок. В данном скетче реализована фильтрация по трём значениям. Если интересен вариант с фильтрацией более трёх значений, то добро пожаловать в исходную статью. Осторожно, жесть. http://tqfp.org/programming/mediannyy-filtr-na-sluzhbe-razrabotchika.html */ int val[3]; int val_filter; byte index; void setup() < Serial.begin(9600); >void loop() < if (++index >2) index = 0; // переключаем индекс с 0 до 2 (0, 1, 2, 0, 1, 2…) val[index] = analogRead(0); // записываем значение с датчика в массив // фильтровать медианным фильтром из 3ёх ПОСЛЕДНИХ измерений val_filter = middle_of_3(val[0], val[1], val[2]); Serial.println(val_filter); // для примера выводим в порт > // медианный фильтр из 3ёх значений float middle_of_3(float a, float b, float c) < int middle; if ((a else < if ((b else < middle = (a > return middle; >
ЧИСЛА, МАТЕМАТИКА
В этом примере покажу, как разбить число на цифры и поместить в массив:
void setup() < Serial.begin(9600); long data = 1234567; // число, которое нужно разбить int8_t bytes[10]; // буфер byte amount; // количество цифр в числе for (byte i = 0; i < 10; i++) < //>bytes[i] = data % 10; // записываем остаток в буфер data /= 10; // "сдвигаем" число if (data == 0) < // если число закончилось amount = i; // запомнили, сколько знаков break; >> // массив bytes хранит цифры числа data в обратном порядке! for (int8_t i = amount; i >= 0; i--) < //>Serial.println(bytes[i]); // выводим > > void loop() <>
Обратная задача: собрать число из цифр, например – из массива. Удобно при парсинге по одному символу
void setup() < Serial.begin(9600); byte digits[] = ; long number = 0; for (byte i = 0; i < sizeof(digits) / sizeof(digits[0]); i++) < number += digits[i]; // пишем следующую цифру number *= 10; // "сдвигаем" число >number /= 10; // убираем лишнее умножение на 10 Serial.println(number); >
По умолчанию в С++ целочисленное деление производится с отбрасыванием дробной части, т.е. 3/4==0 или 5/4==1 . Иногда бывает нужно получить округление вверх, для этого можно использовать функцию ceil() , но она работает с float и является очень громоздким решением. Целочисленная операция деления числа a на число b с округлением вверх делается вот так: (a + b - 1) / b .
int val = 1234, val2; byte b1, b2; // разбиваем val на байты b1 = val >> 8; // старший байт b2 = val & 0xFF; // младший байт val2 = b2 | (b1
Функция принимает HEX число в виде текста в формате ABC123 , 0xABC123 , #ABC123 с буквами в верхнем и нижнем регистре ( abc123 , 0xabc123 , #abc123 ) и преобразовывает в целочисленный uint32_t . Версии для String и char*:
uint32_t StringHEX(String hex) < uint8_t s = 0; if (hex[0] == '#') s = 1; if (hex[1] == 'x') s = 2; uint8_t len = hex.length() - s; uint32_t val = 0; for (int i = 0; i < len; i++) < val return val; >
uint32_t cstringHEX(char* hex) < uint8_t s = 0; if (hex[0] == '#') s = 1; if (hex[1] == 'x') s = 2; uint8_t len = strlen(hex) - s; uint32_t val = 0; for (int i = 0; i < len; i++) < val return val; >
СТРОКИ
const char _qwerty_ru[] PROGMEM = "FSM\">Zf,dult;pbqrkvyjghcnea[wxio]sm'.z~`"; String ru_to_qw(const String& ru) < String qw; uint8_t prev = 0; for (int i = 0; i < ru.length(); i++) < uint8_t cur = ru[i]; if (cur >127) < uint8_t thiscur = cur; if (cur >191) cur = 0; else if (prev == 209 && cur == 145) cur = 193; // ё else if (prev == 208 && cur == 129) cur = 192; // Ё prev = thiscur; > if (!cur) continue; if (cur else if (cur return qw; >
Serial.println(ru_to_qw("123abcПривет")); // 123abcGhbdtnconst char _qwerty_ru[] PROGMEM = "FSM\">Zf,dult;pbqrkvyjghcnea[wxio]sm'.z~`"; uint16_t ru_to_qw(const char* ru, char* qw) < uint16_t len = strlen(ru); uint16_t idx = 0; uint8_t prev = 0; for (int i = 0; i < len; i++) < uint8_t cur = ru[i]; if (cur >127) < uint8_t thiscur = cur; if (cur >191) cur = 0; else if (prev == 209 && cur == 145) cur = 193; // ё else if (prev == 208 && cur == 129) cur = 192; // Ё prev = thiscur; > if (!cur) continue; if (cur else if (cur qw[idx] = 0; return idx; >
Использовать так:
char* ru = "Привет"; char qw[strlen(ru)]; ru_to_qw(ru, qw); Serial.println(qw); // Ghbdtn
int strlen_ru(const char* data) < int i = 0; int count = 0; while (data[i]) < if ((data[i] & 0xc0) != 0x80) count++; i++; >return count; >
String u_encode(uint32_t c) < char b1 = 0, b2 = 0, b3 = 0, b4 = 0; if (c < 0x80) < b1 = c & 0x7F | 0x00; >else if (c < 0x0800) < b1 = c >> 6 & 0x1F | 0xC0; b2 = c >> 0 & 0x3F | 0x80; > else if (c < 0x010000) < b1 = c >> 12 & 0x0F | 0xE0; b2 = c >> 6 & 0x3F | 0x80; b3 = c >> 0 & 0x3F | 0x80; > else if (c < 0x110000) < b1 = c >> 18 & 0x07 | 0xF0; b2 = c >> 12 & 0x3F | 0x80; b3 = c >> 6 & 0x3F | 0x80; b4 = c >> 0 & 0x3F | 0x80; > String s; s.reserve(4); s += b1; s += b2; s += b3; s += b4; return s; >
Использовать так:
Serial.println(u_encode(0x2605)); // ★
wchar_t str[] = ; char* p = (char*)str; for (int i = 0; i
МАССИВЫ, БУФЕРЫ
void setup() < Serial.begin(9600); byte bytes[] = ; // выводим в порт for (byte i = 0; i < 8; i++) < Serial.print(bytes[i]); Serial.print(' '); >Serial.println(); // копируем массив в буфер byte buf[8]; for (byte i = 0; i < 8; i++) < buf[i] = bytes[i]; >// переписываем наоборот for (byte i = 0; i < 8; i++) < bytes[i] = buf[7 - i]; >// выводим для проверки for (byte i = 0; i < 8; i++) < Serial.print(bytes[i]); Serial.print(' '); >>Допустим, нам нужно сравнить каждый элемент массива со всеми остальными (как например в видео про симуляцию эпидемии https://www.youtube.com/watch?v=y3Ei3d7wQv8 ). Можно оптимизировать процесс при помощи диагонального цикла:
// objAmount - размер массива for (int i = 0; i < objAmount-1; i++) < for (int j = i+1; j < objAmount; j++) < // сравниваем [i] и [j] >>
Рассмотрим, как хранить в массиве например 5 последних значений с датчика для дальнейшего усреднения. Будем работать с линейным буфером, перед записью нового элемента все предыдущие сдвигаются влево, стирая самый первый элемент, и освобождая место для нового.
#define ARRAY_SIZE 5 byte bytes[ARRAY_SIZE]; void setup() < Serial.begin(9600); // 7 раз "задвинем" в массив случайное число // и выведем в порт for (byte i = 0; i < 7; i++) < updateArray(random(0, 100)); printArray(); >/* Вывод: 0 0 0 0 7 0 0 0 7 49 0 0 7 49 73 0 7 49 73 58 7 49 73 58 30 49 73 58 30 72 73 58 30 72 44 */ > void updateArray(int newVal) < for (byte i = 0; i < ARRAY_SIZE - 1; i++) < // сдвигаем члены влево bytes[i] = bytes[i + 1]; >// пишем новое значение в последний элемент bytes[ARRAY_SIZE - 1] = newVal; > void printArray() < // выводим в порт for (byte i = 0; i < ARRAY_SIZE; i++) < Serial.print(bytes[i]); Serial.print('\t'); >Serial.println(); > void loop() <>Рассмотрим, как хранить в массиве например 5 последних значений с датчика для дальнейшего усреднения. Будем работать с циклическим буфером: придётся помнить номер последнего нового элемента. Данный алгоритм лучше, т.к. не приходится перематывать массив.
#define ARRAY_SIZE 5 byte bytes[ARRAY_SIZE]; byte arrayCounter = 0; // номер ячейки void setup() < Serial.begin(9600); // 7 раз "задвинем" в массив случайное число // и выведем в порт for (byte i = 0; i < 7; i++) < //>updateArray(random(0, 100)); printArray(); > /* Вывод: 7 0 0 0 7 49 0 0 0 7 49 73 0 0 7 49 73 58 0 7 49 73 58 30 72 49 73 58 30 72 44 73 58 30 */ > void updateArray(int newVal) < // пишем новое значение в элемент номер arrayCounter bytes[arrayCounter] = newVal; arrayCounter++; // прибавляем // и зацикливаем if (arrayCounter >ARRAY_SIZE - 1) arrayCounter = 0; > void printArray() < // выводим в порт for (byte i = 0; i < ARRAY_SIZE; i++) < Serial.print(bytes[i]); Serial.print('\t'); >Serial.println(); > void loop() <>Самый продвинутый вариант буфера – кольцевой. Данный буфер позволяет хранить набор значений, получать самое крайнее, знать, сколько значений осталось непрочитанными, и “добавлять” новые значения в очередь. Суть состоит в том, что мы запоминаем ячейки начала и конца последовательности данных, и можем обращаться к самому “крайнему” значению, в то же время зная, сколько непрочитанных значений осталось. Такой буфер работает быстрее линейного буфера за счёт отсутствия “перемотки” данных на ячейку назад – здесь все данные сидят в своих ячейках, меняется только их “адрес” – начало и конец буфера, голова и хвост. Такой буфер обычно используется для работы с интерфейсами передачи данных, где всё время что-то читается и добавляется. Пример с готовыми функциями по работе с буфером:
// пример кольцевого буфера для хранения набора данных #define buffer_SIZE 32 // размер буфера int buffer[buffer_SIZE]; // сам буфер (массив) uint8_t buffer_head; // "голова" буфера uint8_t buffer_tail; // "хвост" буфера void setup() <> void loop() <> // запись в буфер void bufferWrite(int newVal) < // положение нового значения в буфере uint8_t i = (buffer_head + 1) % buffer_SIZE; // если есть местечко if (i != buffer_tail) < buffer[buffer_head] = newVal; // пишем в буфер buffer_head = i; // двигаем голову >> // чтение из буфера int bufferRead() < if (buffer_head == buffer_tail) return -1; // буфер пуст int thisVal = buffer[buffer_tail]; // берём с хвоста buffer_tail = (buffer_tail + 1) % buffer_SIZE; // хвост двигаем return thisVal; // возвращаем значение >// возвращает крайнее значение без удаления из буфера // если буфер пуст, вернёт -1 int bufferPeek() < return buffer_head != buffer_tail ? buffer[buffer_tail] : -1; >// вернёт количество непрочитанных элементов // если буфер пуст, вернёт -1 int bufferAmount() < return ((unsigned int)(buffer_SIZE + buffer_head - buffer_tail)) % buffer_SIZE; >// "очистка" буфера void bufferClear()
Данный вариант отличается от предыдущего более быстрым выполнением (остаток от деления заменён условием)
// пример кольцевого буфера для хранения набора данных #define buffer_SIZE 32 // размер буфера int buffer[buffer_SIZE]; // сам буфер (массив) uint8_t buffer_head; // "голова" буфера uint8_t buffer_tail; // "хвост" буфера void setup() <> void loop() <> // запись в буфер void bufferWrite(int newVal) < // положение нового значения в буфере uint8_t i = (buffer_head + 1 >= buffer_SIZE) ? 0 : buffer_head + 1; // если есть местечко if (i != buffer_tail) < buffer[buffer_head] = newVal; // пишем в буфер buffer_head = i; // двигаем голову >> // чтение из буфера int bufferRead() < if (buffer_head == buffer_tail) return -1; // буфер пуст int thisVal = buffer[buffer_tail]; // берём с хвоста if (++buffer_tail >= buffer_SIZE) buffer_tail = 0; // хвост двигаем return thisVal; // возвращаем значение > // возвращает крайнее значение без удаления из буфера // если буфер пуст, вернёт -1 int bufferPeek() < return (buffer_head != buffer_tail) ? buffer[buffer_tail] : -1; >// вернёт количество непрочитанных элементов // если буфер пуст, вернёт -1 int bufferAmount() < return ((unsigned int)(buffer_SIZE + buffer_head - buffer_tail)) % buffer_SIZE; >// "очистка" буфера void bufferClear()
Быстрый алгоритм сортировки, но потребляет много оперативной памяти для вызова (рекурсия):void quickSort(int arr[], int low, int high) < if (low < high) < int pivot = arr[high]; int idx = low; for (int i = low; i > idx--; quickSort(arr, low, idx - 1); quickSort(arr, idx + 1, high); > >
Первоначальный вызов:
int arr[] = ; quickSort(arr, 0, sizeof(arr)/sizeof(arr[0]) - 1); // quickSort(массив, 0, длина - 1)