Фиксированное точное время выполнения loop

Друзья, опять нужна ваша помощь.
Датчик должен передавать температуру на приемник (не ардуино, заводское устройство) с периодичность максимально близкой к постоянной, у родных передатчиков разница между двумя отправками в пределах 10 миллисекунд.
Хотелось бы добиться примерно такой же точности для корректного приема.
Подводит датчик температуры у которого очень разнится время чтения данных - от 60 до 120 миллисекунд из-за чего не получается “выровнять” время.
Пытался реализовать с помощью миллис, рассчитывая что отсчет время чтения датчика начнется одновременно с миллис и оно окажется как-бы “внутри” времени таймера миллис, но выводя данные в порт понял, что миллис работает не так. Перепробовал много разных схем с миллис, тут лишь один из “нерабочих” вариантов.
После отправки плата (lgt8f328p) уходит в сон на точно заданное время, с этим проблем нет.
Подскажите как реализовать точную задержку на чтение датчика и как следствие - точное время работы loop. Ох, надеюсь вы меня поняли.

#include "FineOffset.h"
#include <Wire.h>
#include <AHT20.h>
#define TX_PIN 10
#define DEVICE_ID 123

#include <LowPower.h>

AHT20 aht20;
unsigned long timer1;
unsigned long timer2;

void setup() {
  Serial.begin(9600);
  Wire.begin(); //Join I2C bus
  aht20.begin();
  timer1 = timer2 = millis();
}
FineOffset tx(TX_PIN);

void loop(void)	{
    if (millis() - timer1 > 50) {
    timer1 = millis();
    float temp = 20.0;
    tx.send(DEVICE_ID, temp);
    Serial.println(temp);
    //Serial.println(timer);
  }
  if (millis() - timer2 > 5000) {
   timer2 = millis();
   LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
  }
}

А зачем? Себе же отправка по времени нужна (как я понял), ну и отправляй раз в нужно время (например раз в 2 секунды) ранее считанное значение. И даде если оно не успело обновиться или обновилось 500 раз - какая разница?))

1 лайк

А где тут чтение AHT?

С какой периодичностью-то?
Главное, чтобы эта периодичность была заведомо больше времени испольнения loop(). Если это условие выполняется - то остальное абсолютно без проблем, точность легко получить не только 10мс, но и 1-2 мс

1 лайк

Так дело не пойдёт. У этого датчика время измерения 80мс.
Причём, скорее всего блокирующие 80мс (из библиотеки):

 if (getBusyBit(AHT10_USE_READ_DATA) != 0x00) 
delay(AHT10_MEASURMENT_DELAY); //measurement delay

А ещё расчёты с float, шина.
Так что, здесь , как по мне, не менее 100мс, а то и больше надо брать.

1 лайк

Так он по интерфейсу i2c. Надо делать примерно так:

  1. Включили, ждём >40мс(калибровка и инициализация)
  2. Отправили команду чтения и вышли в луп.
  3. Через >80мс по миллису читаем и снова даём команду на чтение и выходим в луп.

То есть мы не зависаем, ожидая пока датчик измерит. Я такой алгоритм применял с датчиком HC-SR04. Итоговое время общения с ним было порядка 10-20мкс, то есть почти ничто.
Главное ручками свою неблокирующую библиотечку написать.

1 лайк

Простите, может я что то не понимаю, но есть такая величина температурная постоянная - время за которое при максимальном потоке тепла температура объекта изменяется на единицу разряда измерения. Измерять быстрее не имеет никакого инженерного смысла. Кроме того есть такая же постоянная времени датчика. А ещё есть время оцифровки. Это как желание измерить до сотых температуры воды в кастрюле на огне, в которой разница температур в разных точках больше нескольких градусов. Такое желание говорит о невежественности желавшего непонятного.

1 лайк

Хм, сколько ответов и все про разное, значит не прозрачно разъяснил…
Неважно сколько времени периодичность (48 сек если хотите), с эти проблем нет, чтения ант в приложеном примере тоже нет, но это к делу не относится (вместо чтения тут просто стоит константа 20.0 для тестов), проблема в том как написать программу.
Установив задержку миллис в 1 сек я расчитывал, по неопытности, что чтение АНТ и отправка данных стартуют сразу с миллис и к окончанию этой секунды данные прочитаются и отправятся. В таком случае мне останется только уйти в сон на 47 секунд и ключик наш!
Но на деле чтение/отправка стартуют после отсчета миллис, итого получается: 1 сек + 60÷120мс + 47 сек, т.е. не точно 48 секунд.

Т.е. поместить чтение в сетап?
Но как там определить точное время на чтение, все равно будет гулять от 60 до 120мс…

бред какой-то
Код покажите
Чтение будет стартовать там, где вы его поместите в коде. Хотите до миллис, хотите после.
Но чтобы у вас были постоянные интервалы - чтение датчика нужно помещать ВНУТРЬ ИНТЕРВАЛА миллис

Поправлю чуть чуть. Не чтение. Запуск оцифровки температуры. А уж чтение по готовности после запуска оцифровки. Иначе можно прочитать данные равномерно, но при этом получить температуру давно лежащую в регистре датчика.

#include "FineOffset.h"
#include <Wire.h>
#include <AHT20.h>
#define TX_PIN 10
#define DEVICE_ID 123

#include <LowPower.h>

AHT20 aht20;
unsigned long timerb

void setup() {
  Serial.begin(9600);
  Wire.begin();
  aht20.begin();
  timer = millis();
}
FineOffset tx(TX_PIN);

void loop(void)	{
    if (millis() - timer1 > 1000) {
    timer = millis();
    float temp = getTemperatureb
    tx.send(DEVICE_ID, temp);
    LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
  }
}

Вот другой вариант кода. В лупе через 1 сек начинается чтение-отправка, потом сон на 8 сек. Итого имеем 1 сек + 60÷120мс + 8 сек, т.е. время не постоянное а меняется в пределах 9.060- 9.120 сек, мне нужно чтобы не менялось и было постоянным!

Вы совсем не догоняете, да?
Как у вас цикл миллис на 1 сек будет работать, если в нем сон на 8 сек?

1 лайк

Длительность цикла миллис должна быть больше, чем сумма всех событий в нем.
То есть сон 8 сек + чтение дачика и отправка 1.5 сек - то есть цикл не менее 9.5. А лучше 10, чтоб с запасом

Повторю ещё раз. Я не знаю есть ли в этой библиотеке методы работы отдельно команды измерения и отдельно получения данных. Если нет, то надо их самому написать.
Что происходит: дали команду и тупо ждём в цикле while пока датчик соизволит измерить. Это глубо.
Что надо: быстро получили данные предидущего измерения, отправили команду, занимаемся своими делами, по прошествии T быстро читаем, снова даём команду и выходим из миллиса. T должно быть больше 40 или 80 мс(в ДШ смотреть надо)

Примерно так делайте. Делэй - это упрощённо сон.

Спойлер
void setup() {
  Serial.begin(9600);
}


void loop(void)	{
 static uint32_t start_millis = 0;
 static uint32_t timer1 = 0;
 static bool start = true;

  if(start)
  {
  timer1 = millis();
  start = false;
  start_millis = millis();
  }

  if (millis() - timer1 > 1000 && !start) {
  //датчик читаем здесь
    delay(1500);// вместо делэя нужное время сна
    start = true;
    Serial.println(millis() - start_millis);
  }
}
Спойлер

P.S. Немного дописал комменарии

1 лайк

Да, delay у меня был в отдельном millis, этот вариант конечно не рабочий.

Надо во все это въехать, спасибо буду пробовать!

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

21:33:56.467 -> ID: 1235, Temp: 28.2 
21:34:45.025 -> ID: 1235, Temp: 28.4  //48.558
21:35:33.606 -> ID: 1235, Temp: 28.6  //48.581
21:36:22.198 -> ID: 1235, Temp: 28.8  //48.592
21:37:10.766 -> ID: 1235, Temp: 29.1  //48.568
#include "lgt_LowPower.h"
#include "FineOffset.h"
#include <Wire.h>
#include <AHTxx.h>
#define TX_PIN 2
#define DEVICE_ID 1235

//AHT20 aht20;
AHTxx aht20(AHTXX_ADDRESS_X38, AHT2x_SENSOR);
FineOffset tx(TX_PIN);
unsigned long timer;

void setup() {
  CLKPR = 1<<PMCE;
  CLKPR = 0b00000100;
  Wire.begin();
  aht20.begin();
  timer = millis();
}
void loop() {
  static uint32_t start_millis = 0;
  static uint32_t timer1 = 0;
  static bool start = true;
 if(start)
  {
  timer1 = millis();
  start = false;
  start_millis = millis();
  }

  if (millis() - timer1 > 1000 && !start) {
  //датчик читаем здесь
    float temp = aht20.readTemperature();
    tx.send(DEVICE_ID, temp);
    //delay(1500);// вместо делэя нужное время сна
  for (int i=0; i<2; i++) {
    LowPower.powerDown(SLEEP_16S, ADC_OFF, BOD_OFF);
  }
  for (int i=0; i<1; i++) {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  }
  for (int i=0; i<1; i++) {
    LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);
  }
  for (int i=0; i<1; i++) {
    LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
  }
  for (int i=0; i<1; i++) {
    LowPower.powerDown(SLEEP_128MS, ADC_OFF, BOD_OFF);
  }
    start = true;
    Serial.println(millis() - start_millis);
  }
}

static uint32_t start_millis = 0;

Эта переменная в принципе не нужна, только для наглядности.

Сейчас занят, проверю немного позже