Не удается обновить время в RTC через I2c

Я связываюсь с двумя Arduino, используя протокол I2C, и отправляю от ведомого устройства к ведущему целочисленное значение UTC для обновления времени в модуле RTC. Когда мастер получает значение UTC от I2C и пытается установить время командой RTC.set(), Arduino зависает и нуждается в перезапуске, однако, если я использую RTC.set() в методе loop(), он работает в совершенстве. Я не понимаю, почему система, когда она обновляется по I2c, не конфликтует?

Это код с loop() и Wire.onReceive():

#include <Thread.h>
#include <ThreadController.h>
#include <SoftwareSerial.h>
#include <LinkedList.h>
#include <SPI.h>
#include <TimeLib.h>
#include <DS3232RTC.h>
#include <Wire.h>

void setup() {
  Serial.begin(9600);  

  Wire.begin(1); 
  Wire.onReceive(receiveEvent);    

  while (!Serial) {
    ; // ждем подключения последовательного порта. Требуется только для родного порта USB
  }

  setSyncProvider(RTC.get);   
  if (timeStatus() != timeSet) 
     Serial.println("Unable to sync with the RTC");
  else
     Serial.println("RTC has set the system time");

}

void loop() {


  digitalClockDisplay();
  delay(2000);

  // только проверка
  /*time_t t = processUTCFromI2c("1560810413");

  if (t != 0) {
    RTC.set(t);   // это работает
    setTime(t);          
  }*/

}

// ================================ INIT ONRECEIVE ============== ==============================

void receiveEvent(int howMany) {
  char* vectorAction;
  char c;
  Serial.println(howMany);


  LinkedList<char> myLinkedList = LinkedList<char>();

  // while (1 < Wire.available()) { // перебираем все, кроме последнего
  for (int i = 0; i < howMany; i++){  
    c = Wire.read(); // получаем байт как символ
    myLinkedList.add(c);
  }

  vectorAction = new char[myLinkedList.size()+1];

  for(int i=0; i < myLinkedList.size(); i++) {
    vectorAction[i] = myLinkedList.get(i);
  }

  vectorAction[myLinkedList.size()] = '\0';

  getParamsAndValues(vectorAction);

}

void getParamsAndValues(char* i2cValueString) {

  char* getValue; 
  char* getParam;
  char* endValue; 

  getValue = strchr (i2cValueString, '=');
  getParam = strchr (i2cValueString, '@');
  endValue = strchr (i2cValueString, '#');

  const size_t maxBuffLength = 15;

  char valueBuffer[maxBuffLength+1]; // выделяем локальный буфер с местом для завершающего нулевого символа
  char paramBuffer[maxBuffLength+1];

  valueBuffer[0] = '\0';

  while(getValue && getParam && endValue && (getValue > getParam) && (endValue > getValue)) {
    Serial.println(i2cValueString);

    if (getValue && getParam && (getValue > getParam)) {
      size_t paramLength = getValue-getParam-1;
      valueBuffer[0] = '\0';  

      if (paramLength <= maxBuffLength) {
        strncpy(paramBuffer, getParam+1, paramLength);// http://www.cplusplus.com/reference/cstring/strncpy/
        paramBuffer[paramLength] = '\0'; // корректно завершаем c-строку
      }
    } else {
      // обработка неправильного ввода
    }

    if (getValue && endValue && (endValue > getValue)) {
      size_t valueLength = endValue-getValue-1;
      valueBuffer[0] = '\0';

      if (valueLength <= maxBuffLength) {
        strncpy(valueBuffer, getValue+1, valueLength);// http://www.cplusplus.com/reference/cstring/strncpy/
        valueBuffer[valueLength] = '\0'; // корректно завершаем c-строку
      } else {
        // буфер обработки слишком мал
      }
    } else {
      // обработка неправильного ввода
    }

    getValue = strchr (getValue+1, '=');
    getParam = strchr (getParam+1, '@');
    endValue = strchr (endValue+1, '#');

    i2cActionReceived(paramBuffer, valueBuffer);
  }

}

void i2cActionReceived(char* param, char* value) {
  if(strcmp(param, "setTime") == 0) {
    time_t t = processUTCFromI2c(value);

    if (t != 0) {
      RTC.set(t);   // Это не работает
      setTime(t);          
    }
  }
}

// ================================ КОНЕЦ ПРИ ПОЛУЧЕНИИ ============== ==============================


// ================================ CONVERT UTC char* TO long ========== ======================
unsigned long processUTCFromI2c(char* utc) {
  unsigned long auxTime = 0L;

  if (strlen(utc)>0) {
    auxTime = atol(utc);
  } 

  return auxTime;
}

Подчиненное устройство отправляет строку через I2C в формате @setTime=1560810413#.

В loop() this прокомментирован код, который работает onorata, а i2cActionReceived (параметр char*, значение char*) — это место, где обновление завершается сбоем из-за onReceive.

, 👍0

Обсуждение

вы не используете согласованные данные в своих усилиях по отладке... в первом случае вы используете "1560810413"... во втором случае вы используете параметр в функции, @jsotola


2 ответа


1

Симптом, который вы описываете, следующий: метод RTC.set() блокируется в обратном вызове onReceive библиотеки Wire, но не при использовании в функции loop().

Похоже на проблему прерывания. Функция обратного вызова onReceive вызывается из ISR (процедуры обслуживания прерываний) оборудования I2C. Бесполезно использовать какой-либо код, использующий прерывания, внутри ISR, пока вы не знаете, что делаете.

В данном случае функция RTC.set() (которую вы можете найти здесь в строке 152) вызовет метод RTC.write(), который будет выполнять несколько транзакций I2C (как отправку, так и получение в качестве мастера). Выполнение этих действий внутри ISR может уже заблокировать дальнейшее выполнение кода.

Но есть еще одна проблема. У вас есть 2 Arduino, подключенных по I2C. Ведущий Arduino общается только с ведомым Arduino, в то время как ведомый Arduino также является ведущим для RTC (вы инициализируете этот Arduino как ведомый, но он станет ведущим на время, когда вы попытаетесь выполнить ведущую передачу). Ваш ведомый код и библиотека RTC используют библиотеку Wire, то есть аппаратный порт I2C Uno. Итак, у вас есть 2 мастера на одном порту I2C.

Хотя стандарт I2C поддерживает настройку с несколькими ведущими через арбитраж шины, библиотека Wire этого не делает. В случае, когда оба мастера начнут передачу одновременно, он заблокирует шину (из-за некоторых циклов блокировки внутри библиотеки Wire, которые не могут обрабатывать арбитраж шины). Таким образом, настройка, которую вы используете, либо не будет работать вообще, либо будет очень рискованной, в зависимости от количества отправляемых данных (когда шина заполняется большим количеством данных, становится более вероятным, что происходит конфликт между мастерами) .

С I2C на Arduino вы должны придерживаться настроек с одним мастером. Например, вы можете установить первый Arduino в качестве ведомого, оставив второй Arduino в качестве единственного ведущего на шине, которая обрабатывает как получение UTC от первого Arduino, так и связь с RTC. На первом Arduino вы должны настроить обратный вызов onRequest, который заполняет буфер I2C текущим временем UTC, которое затем передается на мастер Arduino, чтобы он мог обновить время RTC с ним.


ИЗМЕНИТЬ:

I2C имеет фундаментальный принцип ведущий-ведомый. Мастер генерирует тактовый сигнал и инициирует/останавливает любые транзакции на шине. Слейв не может ничего отправить по шине, когда мастер не запрашивал у него данные, в том числе «вызов» мастера. Любое общение начинается с мастера.

Библиотека Wire дает вам возможность инициализировать Arduino как ведомое устройство, а затем выполнять основные транзакции. Для этого оборудование I2C настраивается как основное на время транзакций. Таким образом, вы можете инициализировать оба Arduino как ведомые с адресом и позволить им перейти в режим ведущего для любой основной транзакции, которая вам нужна. Это то, о чем вы говорите в своем комментарии и что вы используете в коде в своем вопросе.

НО возникают большие проблемы, когда обе платы Arduino пытаются отправлять данные одновременно. Это называется коллизией данных. Спецификация I2C имеет для этого арбитраж шины (в основном мастер, который первым отправляет 1, должен отказаться от транзакции), так что коллизии могут быть изящно разрешены. Библиотека Wire не реализует это, поэтому она будет блокироваться в случае коллизии. Затем вам нужно перезагрузить Arduino, чтобы он снова заработал.

Вы должны еще раз проанализировать свои требования и проверить, действительно ли вам нужен I2C для связи между Arduino, или вам действительно нужна индивидуальная двунаправленная связь. Часто это также выбор дизайна.

Если вам действительно нужна индивидуальная двухсторонняя связь между ардуино, я вижу 2 возможности:

  1. Вы можете сделать это хакерским способом и добавить дополнительную линию между двумя Arduino. Вы бы установили Arduino с RTC в качестве ведущего I2C, а другой Arduino в качестве ведомого. Ведомый Arduino может перевести дополнительную линию с цифрового выходного контакта в состояние LOW, чтобы мастер Arduino мог воспринять это как сигнал на одном из своих цифровых входных контактов (настроенном с внутренним подтягивающим резистором). Когда мастер Arduino обнаружит, что дополнительная линия переходит в НИЗКИЙ уровень, он может запросить данные у ведомого. Таким образом, вы используете дополнительную линию для сигнализации ведущему устройству о том, что новые данные доступны на ведомом устройстве.

  2. Если вы не привязаны к I2C, другие интерфейсы связи могут подойти лучше. UART (в среде Arduino называется Serial) реализует двунаправленную побайтовую связь. Нет отношения хозяин/раб. Это было бы намного проще.

,

Спасибо за объяснение и подсказку как решить проблему! Из-за спецификации проекта я не смогу установить один из Arduino в качестве главного, два должны быть подчиненными и обмениваться данными между собой. Чтобы решить эту проблему, я установил переменную time_t как глобальную, а другую логическую переменную также как глобальную, и когда I2C получает значение, я присваиваю значение time_t и true другой переменной и выполняю обновление внутри цикла()., @user2831852

Я не совсем понимаю спецификацию проекта. В связи I2C всегда есть 1 мастер во время передачи. В конфигурации с несколькими ведущими эта роль будет просто меняться между устройствами. И библиотека Wire не поддерживает мультимастер. Зачем нужен мультимастер? Что именно об этом говорится в спецификации проекта?, @chrisl

Я мало что знаю о библиотеке Wire, но, насколько я понимаю, ведомое устройство не может вызывать ведущего, потому что у него нет адреса, поэтому мой «ведущий» находится по адресу 1 (Wire. begin (1);) и ведомое устройство по адресу 2 (Wire.begin (2);), и таким образом один может вызывать другой. Есть ли способ для ведомого вызвать ведущего, поскольку у него нет адреса?, @user2831852

@user2831852 user2831852 Я добавил дополнительные пояснения в конце своего ответа., @chrisl


0

Как объяснил chrisl, из-за того, что ардуино взаимодействуют с I2c, а обновление RTC происходит по I2C, обновление завершается ошибкой и блокирует систему. Чтобы решить эту проблему, я объявляю глобальные переменные boolean updateRTC = false; и time_t setUTC;, а в методе, который обрабатывает UTC, я присваиваю значение true для updateRTC и числовое значение UTC для setUTC и выполните проверку в void loop().

// глобальные переменные
boolean updateRTC = false;
time_t setUTC;

// обновление RTC внутри цикла()
void loop() {
  digitalClockDisplay();
  delay(1000);

  if (setUTC > 0 && updateRTC == true) {
    updateRTC = false;
    RTC.set(setUTC);
    setTime(setUTC);
    setUTC = 0;
  }

}

// Метод, выполняемый в конце события onReceive()
void i2cActionReceived(char* param, char* value) {
  if(strcmp(param, "setTime") == 0) {
    setUTC = processUTCFromI2c(value);

    updateRTC = true;
  }
}

,