Невозможно отобразить строку chr с помощью Wire.read() и u8g2.drawStr().

Я все еще знакомлюсь с C++, поэтому я ожидаю, что это очень простая проблема, с которой я сталкиваюсь. У меня есть OLED-экран, подключенный к Teensy LC, и я могу отлично отображать на нем текст. Teensy получает байты через I2C, и я хочу собрать эти байты в строки и отобразить их на OLED.

Мой соответствующий код:

#define I2C_ADDRESS 4
char buf[16];

void setup(void) {
  Serial.begin(9600);
  Wire.begin(I2C_ADDRESS);
  Wire.onReceive(receiveData);
  u8g2.begin();
}
void loop(void) {
  // U8G2 обрезан установочный код
  // Эта строка ничего не выводит на экран:
  u8g2.drawStr(0, 0, buf);
  // Эта строка делает:
  u8g2.drawStr(0, 20, "Foobar!");
  u8g2.sendBuffer();
  delay(1000);
}

void receiveData(int byteCount){
  Serial.print("Data received: ");
  int i;
  for (i=0; i<byteCount; i++)
  {
    buf[i] = Wire.read();
    Serial.println(buf[i]);
  }
  buf[i] = '\0';
  Serial.println(buf);
}

В функции receiveData () Serial.println () внутри цикла for выводит каждую букву полезной нагрузки по мере ее получения, одну за другой, но Serial.println () ниже цикла for, который, по моей оценке, должен распечатать все сообщение, выводит пустая строка. Я предполагаю, что это связано с тем, что полученная строка не отображается на OLED-дисплее.

, 👍0

Обсуждение

Добавьте Serial.print(ByteCount); в качестве первой строки receiveData. Возможно, это дает намек., @DataFiddler


2 ответа


2

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

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

Использование Serial.print(), .println(), .write()и многих других вещей, включая несерийные вещи, такие как delay() внутри службы прерываний, обычно является no-no . На ядре AVR Arduino, если исходящий последовательный буфер заполнится, ваш код будет заблокирован базовой функцией .write(), ожидающей, пока в исходящем последовательном буфере останется место, и прерыванием последовательной передачи, ожидающим, когда функция .write() завершит его ожидание. Я не знаю, как сконфигурированы Teensy LC и его MCU, определяет ли он приоритеты прерываний и т. Д., Но вполне вероятно, что вы все еще не должны вызывать такие функции внутри контекста прерывания.

На самом деле вы не так уж много должны делать в ISR, кроме:

  • связь с оборудованием (что делается для вас здесь)
  • управляйте энергонезависимыми переменными, принадлежащими самому ISR.
  • установите глобальные изменчивые квалифицированные переменные для связи с основной строкой выполнения.
  • вызывайте функции, которые не имеют общего состояния с основной линией выполнения.
  • вызывайте функции, которые имеют общее состояние, но тщательно разработаны для вызова как из контекстов ISR, так и из основной строки выполнения.

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

Так как у вас сейчас есть receiveData, он может переполнить buf. Все, что для этого требуется, - это чтобы отправляющая сторона отправила более 15 байт. Любое количество странных вещей может произойти, если вам удастся переполниться. Это как-то связано? Может быть.

Для связи между полученными данными, запущенными в контексте ISR, и основной строкой выполнения в циклеbuf должен иметь квалификацию volatile. Без этого основная линия исполнения может вести себя так, как будто buf никогда не меняется после того, как он впервые увиден. Из-за того, как вы его используете и знакомы с компилятором, я предполагаю, что это не проявляется как ошибка, но я этого не знаю; это может быть связано.

Поэтому я бы рекомендовал обновить что-то вроде вашего объявления buf до volatile qualified и удалить вызовы Serial print из функции receiveData, а также предотвратить переполнение:

volatile char buf[16];

/// ...

void receiveData(int byteCount){
  static const size_t room_for_non_null_chars = sizeof buf - 1;
  const size_t read_amount = min(byteCount, room_for_non_null_chars);

  int i;
  for (i = 0; i < read_amount; ++i) {
    buf[i] = Wire.read();
  }

  buf[i] = '\0';
}

Если бы не тот факт, что нам нужно прочитать что-то изменчивое, приведенный выше цикл действительно мог бы быть просто вызовом read, принимающим буфер и размер. Вы можете видеть, что это обновлено, чтобы предотвратить переполнение.

Тогда часть цикла станет чем-то вроде:

void loop() {
  char non_volatile_buf[sizeof buf];
  size_t write_index = 0;

  noInterrupts();
  for (char c: buf) {
    non_volatile_buf[write_index++] = c;
  }
  interrupts();

  // U8G2 обрезан установочный код
  // Эта строка ничего не выводит на экран:
  u8g2.drawStr(0, 0, non_volatile_buf);

  // ...

Мы отключаем прерывания при копировании данных в локальный буфер, чтобы обработчик onReceive не мог прервать использование buf, обновляя его, пока вы используете его в drawStr.

Если вы заботитесь о потере сообщений, вам нужно сделать еще кое-что. Что делать, если у вас есть вызов receiveData, в то время как сообщение находится в buf. То, как написано выше, просто отбрасывает все, что там есть, независимо от того, было ли это замечено loop() или нет.

,

0

Прошлой ночью я плохо спал, но наконец-то понял это благодаря сообщениюtimemage. Я обеспокоен тем, что мое решение гиперспецифично, но я бы предпочел поделиться им, чтобы избавить кого-то от неприятностей в будущем.

  1. timemage был прав в том, что буфер действительно должен быть летучим, и что в том, что я теперь понимаю, является ISR, должно происходить как можно меньше обработки. Я не мог заставить их код работать, но в конце концов наткнулся на i2c_t3, улучшенную библиотеку I2C специально для Teensy 3.0/3.1/LC. Глядя на код библиотеки и особенно на ее базовый периферийный пример, похоже, что она реализует методы, аналогичные рекомендованным timemage.
  2. Еще одна вещь, которую я не учел, - это то, как общался контроллер. Я использовал библиотеку Python smsbus2, и в какой-то момент я изменил смещение адреса с 0x00 на 0x01. Это таинственным образом заставляло код Teensy работать, но я не знаю почему!
data = [1, 2, 3, 4, 5, 6, 7, 8]
bus.write_i2c_block_data(0x21, 0x01, data)
  1. Наконец, читая сообщения на форуме о сохранении данных в переменные из I2C, похоже, что много довольно простого кода, который работает на других устройствах Arduino, не работает на Teensy.1 Однако мне нужно было бы получить устройство Arduino, чтобы проверить эту теорию.

Поскольку я плохой ученый, я не проверял, работает ли какой-либо из моего исходного кода или улучшенного кода timemage, когда я смещаю адрес в коде smbus2. Но как бы то ни было, это то, что я сделал, чтобы решить свою проблему!


  1. См., например: https://forum.arduino.cc/index.php?topic=570597.0; https://forum.arduino.cc/index.php?topic=619902.0.
,