Отправка данных по ай-ту-си

5N62V
Offline
Зарегистрирован: 25.02.2016

Решил прикрутить к системе управлениея чем-то там   логгер.  Но чтобы не сильно расходовать и без того скромные ресурсы, решил отправлять по i2c состояние системы ( 64 байта) на контроллер SD карты, который это дело будет парсить и записывать.

Сначала уткнуся в то, что максимальный буфер в библиотеке Wire  - 32 байта. Изменения в библиотеке счастья не принесли, т.к. резко подскочил расход памяти.  Пришлось отправлять в два этапа по 32 байта.

Дописал простенькую функцию:

void tx_data_toLogger() {
  unsigned long time = millis();
  Wire.beginTransmission(35);
 Wire.write(tx_buffer,32);
  Wire.endTransmission();

  Wire.beginTransmission(35);
  for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);
  Wire.endTransmission();
  Serial.print("Data sent  "); Serial.println(millis() - time);
}

в процессе выполнения получил:

Data sent  132
Data sent  134
Data sent  132
Data sent  132
Data sent  132
 
Чему был жестко опечален:   132 мс  для меня непозволительная роскошь. Буду благодарен на совет что делать, чтобы минимизировать это время. 
sadman41
Offline
Зарегистрирован: 19.10.2016

Как минимум - можно начать вычислять время выполнения до вызова Serial.print() ;) Пока оно там на 9600 выплюнет в UART...

Потом, наверное, можно еще немножко сэкономить, вызывая не TwoWire::write(uint8_t data) в своем цикле, а сразу TwoWire::write(const uint8_t *data, size_t quantity)...

Или плюнуть на SD и перейти на NVRAM.

 
5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Как минимум - можно начать вычислять время выполнения до вызова Serial.print() ;) Пока оно там на 9600 выплюнет в UART...

Да, выкроил 1 мс.

sadman41 пишет:
Потом, наверное, можно еще немножко сэкономить, вызывая не TwoWire::write(uint8_t data) в своем цикле, а сразу TwoWire::write(const uint8_t *data, size_t quantity)...

первая половина так  и сделана. А ранее стоял тоже цикл - увы, разница видимо менее 1мс, ибо не видна. 

sadman41 пишет:
Или плюнуть на SD и перейти на NVRAM.

Пойду покурю что есть NVRAM. Спс! :)

sadman41
Offline
Зарегистрирован: 19.10.2016

Еще есть FRAM: https://habrahabr.ru/post/319336/

А вот тут есть кой-какие цифры характеризующие скорости, которых можно достичь на SD https://forum.arduino.cc/index.php?topic=98898.0

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Вообще-то SD принято подключать по SPI, а не по I2C.

И причина этого проста: I2C - 100кГц при наличии служебной информации, а SPI - не менее 4 МГц без дополнительной служебной.

Но вообще, 132 мс - это жесть.

Во-первых, Вы неправильно измеряете. В частности, во время измерение включается время передачи Serial.

Если правильно организовать передачу данных по I2C, то 1024 байта можно передать примерно за 45 мс.

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:
Во-первых, Вы неправильно измеряете. В частности, во время измерение включается время передачи Serial.

Да, об этом уже говорилось. Разница составила 1мс, что , увы, погоды не делает.

andriano пишет:
Если правильно организовать передачу данных по I2C, то 1024 байта можно передать примерно за 45 мс.

В этом и смысл поста. Буду искренне признателен, если Вы подскажете мне как минимизировать время передачи.  

b707
Offline
Зарегистрирован: 26.05.2017

5N62V пишет:

andriano пишет:
Во-первых, Вы неправильно измеряете. В частности, во время измерение включается время передачи Serial.

Да, об этом уже говорилось. Разница составила 1мс, что , увы, погоды не делает.

Вы явно продолжаете неправильно измеряить время, так как за счет удаления Serial.print выигрыш должен был составить минимум 10мс. Приведите правленный скетч

5N62V
Offline
Зарегистрирован: 25.02.2016

b707 пишет:

Вы явно продолжаете неправильно измеряить время, так как за счет удаления Serial.print выигрыш должен был составить минимум 10мс. Приведите правленный скетч

вот, пожалуйста:

void tx_data_toLogger() {
  unsigned long time = millis();
  Wire.beginTransmission(35);
 Wire.write(tx_buffer,32);
  Wire.endTransmission();

  Wire.beginTransmission(35);
  for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);
  Wire.endTransmission();
  unsigned long time1 = millis();
  Serial.print("Data sent  "); Serial.println(time1 - time);
}

Я конечно умею сильно ошибаться, у меня просто талант к этому! :) Неужели и тут умудрился? 

можно было и так записать, чтобы не использовать дополнительную переменную : time = millis()-time;   но это не делает никакой заметной разницы.

Но, кстати, и 10мс мне погоду не делают. 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Вот это зачем:


  for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

Если можно:

  Wire.write(&(tx_buffer[31]),32);

И кстати - если у вас буфер в 64 символа, то вот здесь

for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

вы пошлёте 31 символ ;)

5N62V
Offline
Зарегистрирован: 25.02.2016

DIYMan пишет:

Вот это зачем:


  for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

Если можно:

  Wire.write(&(tx_buffer[31]),32);

И кстати - если у вас буфер в 64 символа, то вот здесь

for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

вы пошлёте 31 символ ;)

Вы меня не путайте, я и сам запутаюсь. :)

Во-первых, я на противоположной стороне контролирую данные, там все в порядке: приходит 32+32 байта в правильном порядке. Я дал значения ячейкам массива от 0 до 63.

Во-вторых, предлагаемая Вами строка должна выглядеть так: 

Wire.write(&(tx_buffer[32]),32);

Но за подсказку спасибо!

В-третьих, это вообще не решает мою проблему - те же 132мс.

Увы :(

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

5N62V пишет:

DIYMan пишет:

Вот это зачем:


  for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

Если можно:

  Wire.write(&(tx_buffer[31]),32);

И кстати - если у вас буфер в 64 символа, то вот здесь

for (byte i = 32; i < 64; i++)Wire.write(tx_buffer[i]);

вы пошлёте 31 символ ;)

Вы меня не путайте, я и сам запутаюсь. :)

Во-первых, я на противоположной стороне контролирую данные, там все в порядке: приходит 32+32 байта в правильном порядке. Я дал значения ячейкам массива от 0 до 63.

Да, туплю сегодня чего-то видимо :) С индексами всё норм, вместо 31 должно быть 32 в моём примере, жёсткий у мну тупняк, осень, все дела :)

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:

Вообще-то SD принято подключать по SPI, а не по I2C.

И причина этого проста: I2C - 100кГц при наличии служебной информации, 

А я че-то думал, что ай2си работает на 400кГц.

в ардуино действительно ограничено 100кгц, причем ограничение в самой библиотеке Wire.

Вот древняя статья по этому вопросу: http://forum.arduino.cc/index.php?topic=16793.0

Вдохновленный я попробовал это воплотить, прописать 400000L в файле twi.h   , правда никаких twi.o     wire.o   я не обнаружил. Но вроде изменения случились. Но, сука, это не то, на что я надеялся.   Вместо 132мс я получил 127мс.  Печаль!

плз хелп!

sadman41
Offline
Зарегистрирован: 19.10.2016

Так это... на SPI-то пробовали? Может дело не в бобине...

5N62V
Offline
Зарегистрирован: 25.02.2016

SPI не пробовал т.к. нет пинов свободных, а i2c уже есть и реализована в том устройстве, куда я хочу еще и логгер прикрутить. 

Logik
Offline
Зарегистрирован: 05.08.2014

мрак. Длительность чего Вы меряете? Первые 32 байта отправляются блоком а вторые 32 циклом и длительность суммарная. Сделайте блоком обе отправки и померяйте. Подозреваю  что  потери производительности у вас гдето не на отправке. На 100КГц 32байта передаются за ~3мсек. Если хуже в разы - то чегото не так. А прерывания сильно нагружены? 

Logik
Offline
Зарегистрирован: 05.08.2014

5N62V пишет:

SPI не пробовал т.к. нет пинов свободных, а i2c уже есть и реализована в том устройстве, куда я хочу еще и логгер прикрутить. 

Может чего на i2c висит и занимает её. Переходить на spi - безсмысленная возня.

5N62V
Offline
Зарегистрирован: 25.02.2016

Logik пишет:

 

Может чего на i2c висит и занимает её.

Я пока на макетке меряю, ничего не висит пока. 

5N62V
Offline
Зарегистрирован: 25.02.2016

Logik пишет:

мрак. Длительность чего Вы меряете? Первые 32 байта отправляются блоком а вторые 32 циклом и длительность суммарная. Сделайте блоком обе отправки и померяйте. Подозреваю  что  потери производительности у вас гдето не на отправке. На 100КГц 32байта передаются за ~3мсек. Если хуже в разы - то чегото не так. А прерывания сильно нагружены? 

Вы просто не дочитали,  при отправке блоком обоих пачек время передачи не меняется. Что-то явно не так. Что именно - не пойму.  Прерывания не нагружены аапще. 

sadman41
Offline
Зарегистрирован: 19.10.2016

Ну, а скажем, замерять не общее, а поэтапное время - пробовали? Сколь конкретно beginTransmission идет, передача и пр.

Имея ваши 10 строчек кода остается только пальцами в небо тыкать.

Прерывания могут неявно дергаться, как вон, например с SoftSerial-ом.

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Имея ваши 10 строчек кода остается только пальцами в небо тыкать.

Прерывания могут неявно дергаться, как вон, например с SoftSerial-ом.

мне просто показалось, что там не на что смотреть.


#include <Wire.h>

#define MAX_POINTS_NUMBER 20  //максимальное кол-во точек
#define SLAVE_ADD 35

unsigned int res[MAX_POINTS_NUMBER * 3]; // массив хранения координат точек
byte points = 0;                         //количество точек в программе
byte event_point = 0;
byte *ptr_res = (byte*)(&res[0]);
byte tx_buffer[64];                      //массив данных, передаваемых в логгер


void setup() {
  Serial.begin(9600);
  Wire.begin();
  delay(1000);
  while (digitalRead(5));               //ждем, пока SD ридер(он же логгер)  прочтет с карточки программу и будет готов передать на мастер
  Wire.requestFrom(SLAVE_ADD, 2);       //запрашиваем количество точек, и точку где должен произойти ивент
  delay(1);
  points = Wire.read();
  event_point = Wire.read();
  Serial.println(" Got points number! - "); Serial.println(points);
  delay(1);
  byte i;
  for (  i = 0; i < points; i++) {
    byte calc_check_sum = 0;
    Wire.requestFrom(SLAVE_ADD, 7);     //запрашиваем координаты одной точки: X,Y,Z и байт контрольной суммы

    Serial.print("Available:  "); Serial.println(Wire.available());
    if (!Wire.available()) {
      Serial.print(" Wire Timeout! ");
      while (1);
    }
    for (byte  j = 0; j < 6; j++) {

      *(ptr_res + (i * 6) + j) = Wire.read();  //считываем X,Y,Z
      calc_check_sum ^= *(ptr_res + (i * 6) + j);  //вычисляем контрольную сумму

    }
    byte check_sum = Wire.read(); //считываем контрольную сумму

    if (calc_check_sum != check_sum) {
      Serial.print("Check sum is wrong in the point # ");      Serial.print(i);      while (1);
    }
    Serial.println();

  }



  for (byte i = 0; i < points; i++) {
    Serial.print(i); Serial.print(" "); Serial.print(res[i * 3]); Serial.print(" "); Serial.print(res[i * 3 + 1]); Serial.print(" "); Serial.println(res[i * 3 + 2]);
  }
  Serial.print("Event  point =  "); Serial.println(event_point);
  for (byte i = 0; i < 64; i++)tx_buffer[i] = i;   //инициируем буффер для логгера
}



void loop() {

  delay(900);
  tx_data_toLogger(); //пишем лог
}

void tx_data_toLogger() {
  unsigned long time = millis();
  Wire.beginTransmission(SLAVE_ADD);
  Wire.write(tx_buffer, 32);
  Wire.endTransmission();

  Wire.beginTransmission(SLAVE_ADD);
  Wire.write(&(tx_buffer[32]), 32);
  Wire.endTransmission();
  time = millis() - time;
  Serial.print("Data sent  "); Serial.println(time);
}

 

С этим кодом имеем:

Data sent  128
Data sent  127
Data sent  127
Data sent  128
Data sent  128
 
5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Ну, а скажем, замерять не общее, а поэтапное время - пробовали? Сколь конкретно beginTransmission идет, передача и пр.

Это интересно!  перемещаем  time = millis() - time;  построчно

void tx_data_toLogger() {
  unsigned long time = millis();
   Wire.beginTransmission(SLAVE_ADD);  //0ms
  Wire.write(tx_buffer, 32);           //0ms
  Wire.endTransmission();             //1...2ms
  Wire.beginTransmission(SLAVE_ADD);   //1...2ms
  Wire.write(&(tx_buffer[32]), 32);    //1...2ms
  Wire.endTransmission();             //127...128ms
}

 

Logik
Offline
Зарегистрирован: 05.08.2014

Перед тем, как пихонуть вторые 32 байта полезно проверить отправлены ли первые и освободился ли буфер. Может из за этого и бошку ссносит. А данные вобще передаются корректно?

sadman41
Offline
Зарегистрирован: 19.10.2016

Вообще, если позволите субъективный взгляд, у меня есть чувство, что писалка не успевает скидывать данные и шину держит. Я не сильно I2C увлекался, но глянул одним глазом исходник Wire - там endTransmission() вызывает twi_writeTo() с параметром wait = true. Т.е., как я понимаю, пока прием не будет подтвержден, twi_writeTo() будет тупо крутить while(wait && (TWI_MTX == twi_state)) .

P.S. Писалка - это штука с SD-картой.

5N62V
Offline
Зарегистрирован: 25.02.2016

Logik пишет:

Перед тем, как пихонуть вторые 32 байта полезно проверить отправлены ли первые и освободился ли буфер. Может из за этого и бошку ссносит. А данные вобще передаются корректно?

Данные передаются корректно. Факт налицо: пока приемник не примет, передатчик новую порцию не отправит.

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Вообще, если позволите субъективный взгляд, у меня есть чувство, что писалка не успевает скидывать данные и шину держит.

Такое имхо, что пока читалка не считает, новая порция не уйдет. 

sadman41
Offline
Зарегистрирован: 19.10.2016

А на стороне логгера что - микросхема, вторая ардуина? Пишет на SD не завершая транзакцию по I2C и не кэширует оперативные данные ?

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

А на стороне логгера что - микросхема, вторая ардуина? Пишет на SD не завершая транзакцию по I2C и не кэширует оперативные данные ?

На второй стороне ардуина. Пока ничего никуда не пишет, просто принимает.

void get_data(int howMany){
  static unsigned long time = 0;
  static bool rx_event = 0;
  
  delay(1);
  if(Wire.available()!=32){while(Wire.available()) Wire.read(); Serial.print(Wire.available()); Serial.println("Wrong data");return;}
  (millis()-time>20)?rx_event = 0:rx_event = 1;
  for (byte i = 0; i<32; i++) rx_buffer[i+32*rx_event] =  Wire.read();
  for (byte i = 0; i<64; i++) {Serial.print(rx_buffer[i]);Serial.print(" ");}
  Serial.println();
  time = millis();
  
}

Если что, то rx_event просто для определения это первые 32 байта или вторые.

sadman41
Offline
Зарегистрирован: 19.10.2016

Не понимаю в деталях, что у вас там там за логика, но если взять обычный экзэмпл из Arduino IDE -> Wire -> slave_reciever и сериал из него выкинуть, то как со скоростью будет?  Мои идеи по простой диагностике исчерпываются. Дальше только аппаратный логический анализатор.

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Не понимаю в деталях, что у вас там там за логика, 

Да ладно! :)



void get_data(int howMany){
  static unsigned long time = 0;
  static bool rx_event = 0;  
  delay(1);
 
  if(Wire.available()!=32){
while(Wire.available()) Wire.read();
Serial.print(Wire.available());
Serial.println("Wrong data");
return;}//если пришедшая пачка не равно 32, то считываем все нах и выходим из функции
 
  (millis()-time>20)?rx_event = 0:rx_event = 1;
//если между вызовами ф-ции прошло меньше 20мс, то это вторые 32 байта информации
  for (byte i = 0; i<32; i++) rx_buffer[i+32*rx_event] =  Wire.read();
//считываем соответствующие 32 байта, либо первые, либо вторые
  for (byte i = 0; i<64; i++) {Serial.print(rx_buffer[i]);Serial.print(" ");}
  Serial.println();
  time = millis();
  
}

 

 
То есть нет ничего, что могло бы тормозить принятие 32ух байт.
ой, кроме вывода в сериал :)
5N62V
Offline
Зарегистрирован: 25.02.2016

Заработало!  3-4 мс.   Блииииииин ! :)))))))))))

16ю строчку прописал как 

if(rx_event) for (byte i = 0; i<64; i++) {Serial.print(rx_buffer[i]);Serial.print(" ");}

 

и все пошло как надо.  Ну я ж говорил, что я мастер спорта по затыкам :))))

 

надо было просто запретить выводить в сериал после приема первых 32 байт, а только после вторых. Ну ё-маё! 

Спасибо всем за участие! Прелесть форума в том, что обговаривая проблему , тебя подталкивают смотреть на эту проблему под разными углами. И таким образом есть шанс выйти из тупика, даже без прямых указаний. 

Короче, всем добра! :)

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Осталось ощущения и интуитивные действия перевести в научное объяснение. 

Logik
Offline
Зарегистрирован: 05.08.2014

Приемная сторона тормозит передающую. Это хороше, либка правильная значить.

b707
Offline
Зарегистрирован: 26.05.2017

sadman41 пишет:

Осталось ощущения и интуитивные действия перевести в научное объяснение. 

все вполне научно - 32 байта в строковом представлении, включая пробелы - это 100 - 150 символов. что при скорости 9600 бод (1200 символов в сек) - как раз и дает порядка 100 -120 мс задержки

5N62V
Offline
Зарегистрирован: 25.02.2016

sadman41 пишет:

Осталось ощущения и интуитивные действия перевести в научное объяснение. 

Logic уже сформулировал предельно точно и лаконично: тормозила приемная часть. Теперь буду очень внимательным, ибо Serial.print в функции которая привязана к Wire.onReceive в слейве - зло. Как и во всех других, где быстродействие является критичным моментом. 

Код передающей части был верным, я не там рылся.   На скорость работы то, как организована передача данных, побайтно ли с помощью цикла, или одним паком. не влияет.  По крайней мере на таких небольших пачках.

Ну, еще можно сделать последний, самый глобальный и столь же бесполезный вывод: хороший программер сразу напишет правильный код.  :)

Всем бобра!