Частота мигания светодиодом не соответствует заданным временным параметрам

Друзья, столкнулся вот с чем:
Управляю Ардуиной от ПК через порт. Посылаю строку управления в виде “211C0P000V000#”, где первые цифры определяют частоту мигания светодиодом (2- разрешить мигание, 1-множитель для времени зажженного светодиода, 1-множитель для времени погасшего светодиода). Для перевода в миллисекунды в строке " int heater_multi=5; " (строчка скетча 49) указываю, на сколько нужно умножить множители (простите за тавтологию). Программа работает, но частота включения-выключения светодиода совершенно не совпадает с тем, что я указываю (значительно медленнее идет мигание).
Подскажите, где искать!
Заранее спасибо!

//UNO
//__________________БЛОК ИЗМЕРЕНИЯ ТЕМПЕРАТУР
#include <OneWire.h>    //Подключение библиотеки термометров
// Объявление пинов термометров ds18s20
int pin_sensor_col=A0;  //Пин термодатчика колонны
int pin_sensor_wat=A1;  //Пин термодатчика охлаждения
int pin_sensor_cub=A2;  //Пин термодатчика куба 
int pin_sensor_def=A3;  //Пин термодатчика дефлегматора
// Объявление пинов исполнения
int pin_heater=8;   //Синий - ТЭН
int pin_cooler=9; //Красный - Вентилятор
int pin_valve=10; //Зеленый - Клапан
int pin_pump=11;  //Белый - Насос (ШИМ)
// Объявление пинов считывания из сети
int pin_volt=A4; //Пин вольтметра
//int pin_zero=2; //Пин прерывания зеро-кросс

//Cоздаём объекты класса OneWire для 4 датчиков температуры
OneWire ds_col(pin_sensor_col);  // Датчик T° колонны на пине A0
OneWire ds_wat(pin_sensor_wat);  // Датчик T° охлаждения на пине A1
OneWire ds_cub(pin_sensor_cub);  // Датчик T° куба на пине A2
OneWire ds_def(pin_sensor_def);  // Датчик T° дефлегматора на пине A3
//________Объявляем переменные температур 
volatile int temp_col; // Температура ректификационной колонны
volatile int temp_wat; // Температура выходящей воды
volatile int temp_cub; // Температура куба 
volatile int temp_def; // Температура дефлегматора
//Переменная периода опроса термодатчиков 
long ds18b20_time;
//Переменная периода связи ПК и Ардуино
long string_time;
//Подвязывание рабочих переменных к библиотеке датчиков
byte i;
byte present = 0;
byte data[12];
byte addr_col[8];
byte addr_wat[8];
byte addr_cub[8];
byte addr_def[8];
//Строки приема и посыла в порт
String string_in;
String string_out;
//Переменные исполнения
//Нагрев
long heater_previous; //Время последнего переключения нагрева
long heater_time; //Интервал переключения нагрева
int heater_open=0; //Время зажженного нагрева
int heater_close=0; //Время погасшего нагрева
int heater_multi=5; //Множитель для установки миллисекунд
int heater_mode=0; //Режим нагрева (0 - отключен, 1-турбо, 2-рабочий нагрев)
int heater_status=HIGH; //Состояние нагрева

//Ветилятор охлаждения
int cool_status=0; //Состояние вентилятора (0 - выключен, 1-включен)
//Насос
int pump_value=0; //Производительность насоса 0-255 для ШИМ
//Клапан отбора 
long valve_time; //Время переключения клапана
int valve_open=0; //Время открытого клапана
int valve_close=0; //Время закрытого клапана
int valve_work; //Переменнове время включения-выключения клапана
int valve_status=0; //Состояние клапана (0 - закрыт, 1-открыт)
int valve_allow=0; //Разрешение на работу клапана (0 - запрет, 1-разрешение)
//Переменные вольметра
volatile unsigned long volt_summ=0; //Сумма
volatile long volt_meas=0;          //Измеренное напряжение
volatile long volt_aver=0;          //Среднее
String volt;                        //Посыляемое напряжение в символах
long time_volt_meas=0;              //Период измерения напряжения
int clc=0;                          //Число измерений 


void setup() 
{
  // Объявляем скорость порта
  Serial.begin(115200);
  //Инициализация пинов управления
  pinMode (pin_heater, OUTPUT);   //Синий - ТЭН
  pinMode (pin_cooler, OUTPUT); //фиолетовыйй - Вентилятор
  pinMode (pin_valve, OUTPUT);  //Серый - Клапан
  pinMode (pin_pump, OUTPUT);   //Белый - Насос (ШИМ)
  //Инициализация пинов термодатчиков
  pinMode(pin_sensor_col, INPUT);   //Термодатчик колонны
  pinMode(pin_sensor_cub, INPUT);   //Термодатчик куба
  pinMode(pin_sensor_wat, INPUT);   //Термодатчик охлаждения
  pinMode(pin_sensor_def, INPUT);   //Термодатчик дефлегматора
  //Инициализация пина вольтметра
  pinMode(pin_volt, INPUT);
  //Запрос термодатчикам на измерение температуры
  if ( !ds_col.search(addr_col)) {}
  if ( !ds_cub.search(addr_cub)) {}
  if ( !ds_wat.search(addr_wat)) {}
  if ( !ds_def.search(addr_def)) {}
  //Инициируем датчики температуры
  //Колонна
  ds_col.reset();
  ds_col.select(addr_col);
  ds_col.write(0x44);
  //Куб
  ds_cub.reset();
  ds_cub.select(addr_cub);
  ds_cub.write(0x44);
  //Охлаждение
  ds_wat.reset();
  ds_wat.select(addr_wat);
  ds_wat.write(0x44);
  //Дефлегматор
  ds_def.reset();
  ds_def.select(addr_def);
  ds_def.write(0x44);
}

void loop() 
{
  //Чтение строки из ПК
  if (millis()-string_time>heater_multi) 
  {
    //Формат строки
    //ТЭН  Вентилятор  Насос Клапан  Конец строки  
    //xxx  Cx          Pxxx  Vxxx    #
    String string_in = Serial.readStringUntil('#'); //Читаем строку из порта до знака окончания "#"
    int lenstr = string_in.length();                //Определяем длину строки
    if (lenstr == 13) //Проверка длины строки, если она=13, то:
    {
      //Вычленяем параметры мощности нагрева 
      heater_mode = string_in.substring (0,1).toInt();
      heater_open = string_in.substring (1,2).toInt();
      heater_open=heater_open*heater_multi;
      heater_close = string_in.substring (2,3).toInt(); 
      heater_close =  heater_close*heater_multi;
      //Вычленяем состояние вентилятора охлаждения
      cool_status = string_in.substring (4,5).toInt();
      //Вычленяем производитиельность насоса
      pump_value = string_in.substring (6,9).toInt();      
      //Вычленяем параметры работы клапана
      valve_allow = string_in.substring (12,13).toInt();
      valve_open = string_in.substring (10,11).toInt();
      valve_open=valve_open*1000;
      valve_close = string_in.substring (11,12).toInt();
      valve_close=valve_close*1000;
    }
    string_time=millis();
  }
  //Вольтметр 
  if (millis()-time_volt_meas>1)        //По прошествии 1 миллисекунды
  {
    //time_volt_meas=millis();          //Обнуляем таймер  
    volt_meas=analogRead(pin_volt);     //Измеряем напряжение в "0-1023"
    volt_meas=volt_meas*volt_meas;      //Возводим к вадрат
    volt_summ=volt_summ+volt_meas;      //Суммируем
    clc++;                              //Считаем количество измерений  
    if (clc==1003)                      //Если количество измерений = 1003 
    {
      volt_aver=volt_summ/1003;         //Делим сумму на количество измерений   
      volt_aver=sqrt(volt_aver);        //Извлекаем квадратный корень
      volt_aver=volt_aver/2.89;         //Переводим  "0-1023" в вольты
      if (volt_aver<10)  {volt="00" + String(volt_aver);}
      if (volt_aver<100 && volt_aver>9) {volt="0" + String(volt_aver);}
      if (volt_aver<1000 && volt_aver>99) {volt=String(volt_aver);}
      //volt="123";// + String(volt_aver);
      volt_summ=0;
      volt_meas=0;
      volt_aver=0;
      clc=0;
      time_volt_meas=millis();          //Обнуляем таймер
    } 
  }
  
  //Опрос показаний датчиков температуры 1 раз в 850 мсек
  if (millis()-ds18b20_time>850)
  {
    //Опрашиваем датчик колонны
    present = ds_col.reset();
    ds_col.select(addr_col);
    ds_col.write(0xBE);
    for ( i = 0; i < 9; i++)
    {
      data[i] = ds_col.read();
    }
    int16_t raw = (data[1] << 8) | data[0];
    temp_col=raw / 1.6;
    //Опрашиваем датчик куба
    present = ds_cub.reset();
    ds_cub.select(addr_cub);
    ds_cub.write(0xBE);
    for ( i = 0; i < 9; i++)  
    {
      data[i] = ds_cub.read();
    }
    raw = (data[1] << 8) | data[0];
    temp_cub=raw / 1.6;    
    //Опрашиваем датчик охлаждения
    present = ds_wat.reset();
    ds_wat.select(addr_wat);
    ds_wat.write(0xBE);
    for ( i = 0; i < 9; i++)  
    {
      data[i] = ds_wat.read();
    }
    raw = (data[1] << 8) | data[0];
    temp_wat=raw / 1.6;       
    //Опрашиваем датчик дефлегматора
    present = ds_def.reset();
    ds_def.select(addr_def);
    ds_def.write(0xBE);
    for ( i = 0; i < 9; i++)  
    {
      data[i] = ds_def.read();
    }
    raw = (data[1] << 8) | data[0];
    temp_def=raw / 1.6;    

    //Очередной запрос датчикам на измерение температуры
    if ( !ds_col.search(addr_col)) {}
    if ( !ds_cub.search(addr_cub)) {}
    if ( !ds_wat.search(addr_wat)) {}
    if ( !ds_def.search(addr_def)) {}
    //Инициируем датчики температуры
    //Колонна
    ds_col.reset();
    ds_col.select(addr_col);
    ds_col.write(0x44);
    //Куб
    ds_cub.reset();
    ds_cub.select(addr_cub);
    ds_cub.write(0x44);
    //Охлаждение
    ds_wat.reset();
    ds_wat.select(addr_wat);
    ds_wat.write(0x44);
    //Воздух
    ds_def.reset();
    ds_def.select(addr_def);
    ds_def.write(0x44);
    ds18b20_time=millis();
  
    //Посылаем строку температур и напряжения в порт
    //string_out=String(temp_col) + '-' + String(temp_wat) + '-' + String(temp_cub) + '-' + String(temp_def) + '-' + volt;
    string_out=String(heater_open) + '-' + String(heater_close);
    Serial.println (string_out);
  }    
  //Работа насоса
  analogWrite (pin_pump, pump_value);
  //Работа вентилятора охлаждения
  if (cool_status==1) {digitalWrite (pin_cooler, HIGH);} 
  else  {digitalWrite (pin_cooler, LOW);}
  //Работа клапана
  if (valve_allow==0) {digitalWrite (pin_valve, LOW);} 
  else
  {
    if (millis()-valve_time>valve_work)   
    {
      if (valve_status==0)
        {digitalWrite (pin_valve, HIGH);
        valve_work=valve_open;
        valve_status=1;
        }
      else
        {digitalWrite (pin_valve, LOW);
        valve_work=valve_close;
        valve_status=0;
        }
      valve_time=millis();
    }  
  }
  //Работа ТЭНа
  if(heater_mode==0) {digitalWrite (pin_heater, LOW);} //Нагрев запрещен
  if(heater_mode==1) {digitalWrite (pin_heater, HIGH);} //Турбонагрев
  if(heater_mode==2) //Рабочий нагрев
  {
     unsigned long heater_current = millis();
     if(heater_current - heater_previous > heater_time)  
     {
        heater_previous=heater_current;
        if (heater_status==LOW)
        {
           heater_status=HIGH;
           heater_time=heater_open;
        }   
        else
        {
           heater_status=LOW;
           heater_time=heater_close;
        }
        digitalWrite(pin_heater, heater_status);        
     }
  }
}

У вас в коде указан период 5 миллисекунд. Вы реально рассчитываете мигать диодом раз 5 миллисекунд с таким забитым функциями лупом? Да еще и успевать читать новые строчки из Сериал?
Поставьте множитель 1000 или хотя бы 100 - и посмотрите, какая будет частота.

“5” - это просто для примера. Ну, конечно, я ставил множители и другие. Все, что больше 100 и меньше 1500 дает одинаковое переключение с частотой 1 секунда. ЧУТЬ (!) больше или ЧУТЬ (!) меньше. При значениях 2000 и больше - ситуация получше, но заданным параметрам все равно не соответствует: 2-секундная заявка, например, мигает с частотой явно больше 2 секунд (2,5-3). И т.д. Вот я и не могу понять, что мешает?

Вот эта строчка

будет ждать прихода символа “#” до истечения таймаута, который по умолчанию 1 секунда. А вы пытаетесь читать из сериала с частотой, равной вашему “множителю”. Значит любой ваш период НЕ МОЖЕТ БЫТЬ МЕНЕЕ 1 секунды. А при неудачном стечении обстоятельств этих таймаутов в цикле может быть и более одного.

Вам надо менять логику и в первую очередь отказаться от функции Serial.readStringUntil. Вообще, любые функции сериал, связанные с накоплением символов - типа

Serial.readStringUntil

или

Serial.readBytes

лучше избегать в коде. Читайте сериал по одному байту и сами сравнивайте с “#” - так будет проще и надежнее.

==== добавка ====
Как минимум, читайте Сериал только тогда, когда там есть символы. Какой смысл в запуске функции

Serial.readStringUntil

если Serial.available() выдает нуль???

Видимо, Вы правы - когда я пробовал запускать функцию мигания в “голом виде”, она работала вполне прилично, а в приведенном скетче - описанные траблы.
Первый вопрос: действительно ли функция “String string_in = Serial.readStringUntil(‘#’);” в моем случае требует 1 секунду? То есть, если множитель поставить 1000, то должно работать адекватно и временные параметры совпадут хотя бы в первом приближении?
Второй вопрос: как построить функцию побайтного чтения сериала (я еще совсем слабый “программист”)? Если нетрудно, ткните, где об этом почитать понятно для ламера (нахально просить пример кода боюсь :grinning:)?

а вы сами попробуйте

Вот тут очень подробно с кучей примеров:

Спасибо, прочту, постараюсь усвоить. Пока дискуссию прервем, мне надо разобраться. Оставлю за собой возможность возобновить диалог, если не возражаете.

Думаю, если использовать для мигания не millis(), а прерывания таймера, то таких проблем быть не должно.

1 лайк

Конечно, но проблему с неправильным чтением Сериал это не снимет. Светодиод будет мигать правильно, а весь остальной ЛУП тормозить как раньше

Очень интересно, пороюсь… Спасибо.

Надо частоту в угол поставить на горох …

1 лайк

Изверг!!!

Вроде бы Serial всегда при чтении дает задержку. Так что как правильно его не используй - она будет присутствовать.

Если читать символы из буфера - никакой задержки нет.

Теоретически - да.
Но кого в данном случае волнует задержка в несколько микросекунд?

Друзья, я сейчас не у “рабочего ПК”. Отвечу после выходных: поиграюсь в свободное время.

Покопался в выходные, проблема решилась просто (как и предлагал b707, спасибо, кстати!).
Строка 116 заменена на “if (Serial.available() > 0)”
Строка 142 стёрта.
Всё. Мигаю теперь настолько часто, насколько хочу.
Всем спасибо! Тема исчерпана, думаю.