Проблема с передачей последовательных данных между Arduino и ATtiny туда и обратно

Я работаю над взаимодействием между Arduino и ATtiny13, и мне нужно, чтобы это взаимодействие работало. Есть Arduino Nano, питаемая по USB-кабелю от ПК, и ATtiny13, питаемая от Nano по линиям 3V3/GND. Контакты 2 Nano и Tiny соединены одним проводом длиной 20 см.

Nano отправляет один байт информации Tiny, а затем Tiny отправляет тот же байт обратно Nano. По крайней мере, идея в этом, но это не совсем работает.

Это скетч Arduino (своего рода мастер):

    #define DATA_PIN 2
    
    void setup() {
      pinMode(DATA_PIN, OUTPUT);
      digitalWrite(DATA_PIN, HIGH);
      Serial.begin(9600);  //
    }
    
    void loop() {
      byte dataToSend = 0x55;  // отправка некоторого байта
      byte receivedData;
    
      sendByte(dataToSend);
    
      receivedData = receiveByte();
      
      pinMode(DATA_PIN, OUTPUT);
      digitalWrite(DATA_PIN, HIGH);
      
      Serial.print("Sent: 0x");
      Serial.print(dataToSend, HEX);
      Serial.print(", Received: 0x");
      Serial.println(receivedData, HEX);
    
      delay(1000);  // небольшая пауза для удобства пользователя
    }
    
    void sendByte(byte data) {
      pinMode(DATA_PIN, OUTPUT);  // в режим вывода
      digitalWrite(DATA_PIN, LOW);
      delayMicroseconds(500);
      for (int i = 0; i < 8; i++) {
        digitalWrite(DATA_PIN, (data & (1 << i)) ? HIGH : LOW);  // отправить один бит
        delayMicroseconds(500);  // длительность одного бита
      }
      digitalWrite(DATA_PIN, HIGH);  // Состояние ожидания
    }
    
    byte receiveByte() {
      pinMode(DATA_PIN, INPUT);  // переключиться в режим ввода
      byte received = 0;
      while (digitalRead(DATA_PIN) == HIGH);  // ждем LOW (стартовый бит)
      delayMicroseconds(750);  // ждем до половины первого бита
      for (int i = 0; i < 8; i++) {
        if (digitalRead(DATA_PIN) == HIGH) {
          received |= (1 << i);  // прочитал один бит
        }
        delayMicroseconds(500);  // до конца бита
      }
      pinMode(DATA_PIN, OUTPUT);  // вернуться в режим вывода
      digitalWrite(DATA_PIN, HIGH);  // состояние ожидания
      return received;
    }

Это скетч ATtiny13 (это будет ведомое устройство):

    #define DATA_PIN 2
    
    void setup() {
      pinMode(DATA_PIN, INPUT);
    }
    
    void loop() {
      byte receivedData = receiveByte();  // ждем сообщение от ардуино
      sendByte(receivedData);  // отправить эти данные обратно
    }
    
    byte receiveByte() {
      pinMode(DATA_PIN, INPUT);  // переключиться в режим ввода
      byte received = 0;
      while (digitalRead(DATA_PIN) == HIGH);  // ждем LOW (стартовый бит)
      delayMicroseconds(750);  // ждем половину бита
      for (int i = 0; i < 8; i++) {
        if (digitalRead(DATA_PIN) == HIGH) {
          received |= (1 << i);  // вернем бит
        }
        delayMicroseconds(500);  // до конца бита
      }
      return received;
    }
    
    void sendByte(byte data) {
      pinMode(DATA_PIN, OUTPUT);  // в режим вывода
      digitalWrite(DATA_PIN, LOW);
      delayMicroseconds(500);
      for (int i = 0; i < 8; i++) {
        digitalWrite(DATA_PIN, (data & (1 << i)) ? HIGH : LOW);  // отправить один бит
        delayMicroseconds(500);  // длительность одного бита
      }
      pinMode(DATA_PIN, INPUT);  // обратно в режим ввода
    }

Всё, что я получаю, это это, снова и снова:

...
Sent: 0x55, Received: 0x0
Sent: 0x55, Received: 0x0
Sent: 0x55, Received: 0x0
...

Теперь, когда я отправляю некоторые жестко запрограммированные данные из Tiny в Arduino, они прекрасно отображаются на последовательном мониторе.

Проблема где-то при отправке данных с Arduino на Tiny, помогите, пожалуйста!

, 👍2

Обсуждение

9600 предназначен только для последовательного монитора, эта связь между микроконтроллерами имеет произвольную длину бита 500 микросекунд., @Milos

отладьте это, изменив скорость до 2 секунд на бит... мигание светодиода на обоих устройствах между передачей битов и при чтении битов... это покажет вам, считываются ли биты в правильное время, @jsotola

Justme: да, он был сгенерирован искусственным интеллектом, а позже исправлен, чтобы появился начальный бит... Я просто взял его из искусственного интеллекта, не проверяя..., @Milos

jstola: спасибо, это отличная идея, я так и сделаю!, @Milos

Разве для дуплексной «связи» не нужны три провода?, @Antonio51

"*да, это сгенерировано ИИ,...*" Это проблема. Модель ИИ обучалась как на неправильном, так и на правильном коде и не видит разницы. Код в вашем сообщении будет скопирован ИИ-ботами и добавлен в модель, способствуя «коллапсу модели»., @Transistor

ИИ полезен только в том случае, если у вас достаточно опыта, чтобы оценить результат. Подавляющее большинство пользователей, с которыми я сталкиваюсь, не обладают им. Поэтому общий совет — не использовать ИИ в программировании., @the busybee

Поскольку создается впечатление, что эскизы были отредактированы, но миграция удалила историю, пожалуйста, [отредактируйте] свой вопрос, чтобы уточнить: текущие эскизы по-прежнему ведут себя так, как показано?, @the busybee

Возможно, вам захочется добавить подтягивающий резистор, чтобы гарантировать наличие определённого уровня на проводе в случае, если ни один из микроконтроллеров им не управляет. Можно использовать внешний резистор или pinMode(DATA_PIN, INPUT_PULLUP);., @the busybee

ATtiny13 может пропускать полученные данные из-за неправильного времени ожидания., @liaifat85


2 ответа


0

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

void sendByte(byte data) {
  pinMode(DATA_PIN, OUTPUT);  // в режим вывода
  // Отправить стартовый бит
  digitalWrite(DATA_PIN, LOW);
  delayMicroseconds(500);
  // Отправить биты данных
  for (int i = 0; i < 8; i++) {
    digitalWrite(DATA_PIN, (data & (1 << i)) ? HIGH : LOW);  // отправить один бит
    delayMicroseconds(500);  // длительность одного бита
  }
  digitalWrite(DATA_PIN, HIGH);  // Состояние ожидания
  pinMode(DATA_PIN, INPUT);  // обратно в режим ввода
}    

byte receiveByte() {
  byte received = 0;

  // ждем LOW (стартовый бит)
  while (digitalRead(DATA_PIN) == HIGH); 
  delayMicroseconds(500)  
  for (int i = 0; i < 8; i++) {
    delayMicroseconds(250);  // ждем половину бита
    if (digitalRead(DATA_PIN) == HIGH) {
      received |= (1 << i);  // прочитать бит
    }
    delayMicroseconds(250);  // до конца бита
  }
  return received;
}

Кроме того, могут возникнуть проблемы с синхронизацией. При тестировании с ATtiny1614 вместо ATtiny13 было обнаружено, что delayMicroseconds даёт слишком короткие задержки, что приводит к получению неверного байта, слишком раннему началу ответа и неправильной синхронизации ответа. Эту проблему можно решить, включив

#define F_CPU 16000000UL
#include <avr/delay.h>

в верхней части файла ATtiny, устанавливаем частоту Attiny1614 на 10 МГц(!), и заменив delayMicroseconds() на _delay_us().

,

Спасибо, GPT дал мне какой-то дурацкий код, но он всё равно не работает., @Milos

Ваш код ничем не лучше оригинала. Вы просто по-другому распределили задержки. Но обе версии не передают стоповый бит, что создаёт проблемы при последовательной отправке байтов., @Dave Tweed

Стартовый бит — важная часть, стоповый бит здесь не так уж и нужен, так как приёмная программа ожидает только 8 бит после стартового бита и просто прекращает чтение после того, как весь байт будет прочитан. Я просто не могу найти проблему..., @Milos


0

ATtiny13 питается от 3V3/GND от Nano

Это проблема. Arduino Nano использует логику 5 В. Когда он устанавливает данные, на вывод OUTPUT HIGH, он будет подавать избыточный ток через Диод защиты от электростатического разряда ATtiny. Это может привести к повреждению выходного сигнала Arduino, Вход ATtiny, или оба. Более того, когда данные передаются в обратном направлении, В направлении, высокий уровень ATtiny (3,3 В) довольно близок к «Высокое» входное напряжение Arduino (VIH = 3 В). Если Arduino Выход 3,3 В немного слабый, логический уровень может упасть ниже VIH. Выходное напряжение 3,3 В для Arduino обеспечивается через USB/последовательный порт. мост, и он рассчитан всего на 30 мА.

Я предлагаю следующее:

  1. Убедитесь, что выход Arduino и вход ATtiny по-прежнему работают.

  2. Питайте ATtiny от контакта 5 В платы Arduino.

  3. Используйте отдельные провода для Arduino → ATtiny и ATtiny → Arduino. Направления коммуникации. Если это слишком непрактично, то поставьте последовательный резистор сопротивлением не менее 220 Ом, включенный последовательно между двумя контактами данных. Это должно предотвратить перегорание GPIO в случае, если оба контакта выйдут из строя. быть выходами одновременно (программное обеспечение должно предотвратить это) происходит, но ошибки в программном обеспечении случаются).

Если после того, как вы это исправили, оно все еще не работает, тогда возьмите зонд и посмотрите на сигналы, проходящие по проводу. Это должно быть По крайней мере, покажет, какая сторона слабеет. Что ещё стоит попробовать:

  • Каждый раз, когда у вас есть

    pinMode(DATA_PIN, OUTPUT);
    digitalWrite(DATA_PIN, HIGH);
    

    Поменяйте местами эти строки. Это должно предотвратить ошибку, когда строка может находиться в состоянии OUTPUT LOW в течение очень короткого времени, что приемник может интерпретировать как стартовый бит.

  • Замените все вхождения INPUT на INPUT_PULLUP. Это должно предотвратить плавающую линию, которая может привести к появлению ложных стоп-битов быть обнаруженным.

  • Заставьте отправителей выводить стоп-бит. Да, я знаю, что получатели этого не делают. Ожидайте стоп-бит. Однако стоп-бит даёт приемнику время получить сам готов принять следующий стартовый бит. Или, ещё лучше, добавить пять Стоп-биты. Это поможет вам легче различать кадры. отличаются по области применения.

  • Используйте прямое управление портами вместо digitalRead()/digitalWrite(). Эти функции из Arduino. ядра очень медленные, и они могут сбить ваши тайминги.

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

,

Вместо прямого управления портами я с большим успехом и тем же результатом использую библиотеку [digitalWriteFast](https://reference.arduino.cc/reference/en/libraries/digitalwritefast/). Её функции можно использовать где угодно, где пины являются константами, известными на этапе компиляции., @the busybee