Nano отправить 4 значения банка в Uno пожалуйста помогите с кодом

С Наступающим Новым Годом!

Итак, я сделал Nano отправить 4 значения pot в Arduino Uno по последовательному каналу, но у меня есть проблема: я не знаю, как разделить эти значения. Вот код:

Мастер:

    int Pot = A0;
    int Pot2 = A1;
    int Pot3 = A2;
    int Pot4 = A3;
    
    void setup() {
      Serial.begin(115200);
    }
    
    void loop() {
      int Value = analogRead(Pot);
      int Value2 = analogRead(Pot2);
      int Value3 = analogRead(Pot3);
      int Value4 = analogRead(Pot4);
      Serial.write(Value); //Write the serial data
      Serial.write(Value2); //Write the serial data
      Serial.write(Value3); //Write the serial data
      Serial.write(Value4); //Write the serial data
      delay(150);
    }

Рабыня:

    int incomingByte; // для входящих последовательных данных
    
    void setup() {
      Serial.begin(115200); // открывает последовательный порт, устанавливает скорость передачи данных на 9600 бит/с
    }
    
    void loop() {
      // отправлять данные только при получении данных:
      if (Serial.available() > 0) {
        // считывание входящего байта:
        incomingByte = Serial.read();
    
        // скажи, что у тебя есть:
        Serial.print("I received: ");
        Serial.println(incomingByte, DEC);
      }
    }

И немного из серийных журналов:

    Я получил: 255
    Я получил: 247
    Я получил: 11
    Я получил: 0

Он обновляет и отправляет эти четыре значения для четырех горшков, он может читать их, но они вместе, и иногда он также немного отстает, если я могу так сказать; иногда обновление занимает больше времени, но затем оно снова становится нормальным.

Спасибо!

, 👍0


3 ответа


Лучший ответ:

1

На самом деле это огромная тема, о которой я мог бы писать тома. Однако я постараюсь быть кратким. Но это настолько распространенный вопрос, что пришло время, наконец, написать окончательный ответ.

Передача данных между устройствами, будь то компьютеры, Arduino или что-то подобное, очень похожа на разговор с кем-то.

Очень часто можно сделать то же, что и вы, и просто "отправить" данные. Однако это, как вы правильно заметили, очень похоже на отправку кому-то сообщения, написанного заглавными буквами, без пробелов и знаков препинания. Вот так:

ЧТО ТАКОЕ ЧАСТЬ РАБОТЫ-МУЖЧИНА, КАК БЛАГОРОДНАЯ ПРИЧИНА, КАК БЕСКОНЕЧНАЯ ИНФОРМАЦИЯ, ДВИЖЕНИЕ, КАК ВЫРАЖЕННОЕ И ВОСХИТИТЕЛЬНОЕ ДЕЙСТВИЕ, КАК EANANGELINAPPREHENSION, КАК БОГ, КРАСОТА МИРА, PARAGONO, ЖИВОТНЫЕ, И ЕЩЕ ВМЕСТЕ, ЧТО ЭТО КВИНТОВАЯ СУТЬ ПЫЛИ, УДОВЛЕТВОРЕНИЯ, НЕ ИСПОЛЬЗУЙТЕ НИКАКИХ МУЖЧИН.

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

Итак, в языке мы вводим "мета" информация — то есть информация в сообщении, которая сообщает вам не о содержании сообщения, а о самом сообщении. Самый простой — ввести пробелы, чтобы указать, где слова начинаются и заканчиваются. Таким образом, это сообщение теперь выглядит так:

КАК ПРОИЗВЕДЕНИЕ ЧЕЛОВЕК КАК БЛАГОРОДЕН ПО РАЗУМУ КАК БЕСКОНЕЧЕН ПО СПОСОБНОСТИ ПО ФОРМЕ И ДВИГАЕТСЯ КАК ВЫРАЖЕН И ВОСХИТИТЕЛЬЕН В ДЕЙСТВИИ КАК ПОДОБЕН АНГЕЛУ В ПОНИМАНИИ КАК БОГ КРАСОТА МИРА ОБРАЗЕЦ ЖИВОТНЫХ И ВСЕ ЕЩЕ ДО МЕНЯ ЧТО ЭТО ЗА КВИНТЕСССА ПЫЛИ МУЖЧИНА НЕ УДОВОЛЬСТВУЕТ НИ МЕНЯ, НИ ЖЕНЩИНА, ХОТЯ СВОЕЙ УЛЫБКОЙ ТЫ ТАК СКАЖЕШЬ

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

Что за работа человек! Как благородны в разуме, как бесконечны в способностях! Как выразительны и восхитительны по форме и движениям! В действии как ангел! в опасении, как бог! Красота мира! Образец животных! И все же, что для меня эта квинтэссенция пыли? Мужчина не доставляет мне удовольствия; нет, ни Женщина ни; хотя по вашей улыбке вы, кажется, говорите об этом.

И теперь сообщение совершенно простое. Здесь мы ввели символы, которые не являются частью сообщения, но формируют его структуру. Эти символы отличаются от сообщения тем, что относятся к другому типу символов. Не буквы, а символы. Вы можете добавить любой символ, который вы можете себе представить, и придать ему значение, чтобы передать любую информацию о сообщении, которое вам нравится. Ограничений нет.

Но в этом отличие компьютерного взаимодействия.

При отправке данных через последовательный порт у вас есть ограниченный диапазон значений, которые вы можете отправить. У вас есть один байт для отправки информации, что означает, что вы можете отправить только один из 256 возможных "символов".

Когда вы отправляете двоичные данные, вы используете все 256 символов для своих данных. Для любых "пунктуационных знаков" не осталось символов. в вашем сообщении. Становится невозможным перейти даже от первого примера ко второму выше, не говоря уже о третьем.

Так что же можно сделать?

Ну, есть два подхода:

  1. Перекодируйте данные, чтобы использовать меньше "символов" оставляя часть доступной для метаинформации
  2. Назначьте некоторые символы, используемые для данных, которые также будут использоваться для метаинформации, и придумайте способ отличать их от реальных данных.

Первый способ, вероятно, самый простой: измените кодировку данных, чтобы использовать меньший диапазон символов. Самый очевидный способ — использовать кодировку ASCII — использовать всего 10 символов для представления числовых данных, но использовать их намного больше. Я уверен, что вы знаете символы: "0123456789". Затем у вас есть такие стандартные символы, как "Новая строка" (или \n), чтобы отметить завершение сообщения, и запятые для разделения разных частей сообщения.

Это легко закодировать и передать, но сложнее получить. Отправить так же просто, как:

Serial.print(Value);
Serial.print(F(","));
Serial.print(Value2);
Serial.print(F(","));
Serial.print(Value3);
Serial.print(F(","));
Serial.println(Value4);

Получить эти данные и осмыслить их гораздо сложнее. Вы должны прочитать все до запятой (или до конца строки и разделить ее на запятую), а затем преобразовать текст ASCII обратно в числа. Это можно сделать на лету посимвольно (прочитайте символ, если это число, затем умножьте текущее значение на 10 и добавьте к нему новое число, если это запятая, затем перейдите к следующему номер, или если это конец строки, то снова сбрасывайте на первое число) или как целую порцию данных (прочитайте всю партию в массив символов, пока не достигнете \n, а затем разделите его с strtok() через запятую и преобразовать каждый фрагмент с помощью atoi()).

Этот метод неэффективен. Для представления всего лишь небольшого числа требуется много байтов (например, число 100 занимает 3 байта, тогда как значение 100 умещается в один байт как необработанное значение), а последующая обработка этого числа является медленной и громоздкой.

Способ 2 из приведенного выше списка намного эффективнее, но требует более глубоких знаний и размышлений.

Шаг первый – разработка строгого формата сообщения. Вам нужно каким-то образом сказать "Это начало сообщения" и "Это конец сообщения" и все, что между ними, считается данными для сообщения. Поскольку вы работаете с переменными фиксированного размера, вам не нужно ничего внутри этих данных для разделения значений.

У вас есть 4 целочисленных значения. Всего 8 байт (каждое целое число занимает 2 байта для 16 бит). Вы можете упаковать их вместе в struct для упрощения обработки:

struct data {
    int value1;
    int value2;
    int value3;
    int value4;
};

struct data values;

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

int values[4];

Теперь в наборе символов ASCII есть набор управляющих символов, предназначенных для использования при построении подобных сообщений. Например, символ 2 — это «STX». или "Начало текста". Символ 4 — «EOT». или "Конец передачи". Таким образом, вы можете начать свое сообщение с символа 2, затем отправить свои 8 байтов и следовать за ним с символом 4. Затем вы просто ищете 2, читаете 8 байтов и проверяете, что за ним следует 4. Это сработает, правильно ?

Ну, нет. Не совсем. Что, если вы отправляете значение 2 или 4 как часть ваших данных? Как бы он отличал их от начальных/конечных символов? Вы не можете.

Пришло время представить новую концепцию: выход из канала передачи данных. Здесь вы назначаете еще одного специального символа. Этот говорит "Следующий символ - данные. Относитесь к этому как к таковому». DLE — это 16-й символ в ASCII, поэтому вы можете использовать его. Иногда также используется 27 или 255. Вы можете использовать что угодно, но мне нравится 16 (DLE).

Идея заключается в том, что если вы отправляете байт, который может быть либо управляющим символом, либо символом данных (например, 2 или 4), то вы сначала отправляете символ DLE, если и только если этот символ следует интерпретировать как данные. Поэтому, если вы отправляете начальный байт, вы отправляете только 2. Если вы отправляете значение 2 в своих данных, вы отправляете 16, а затем 2.

Но что, спросите вы, если вы отправляете значение 16 в своих данных? Просто: вы просто отправляете его дважды. Первый раз - это символ DLE, который говорит, что следующий байт - это данные. Следующий интерпретируется как данные независимо от того, что он следует за символом DLE.

Звучит сложно, да? Ну не совсем. Давайте визуализируем это. Возьмите наше исходное текстовое сообщение. Обозначим А как начало сообщения, а Б как конец сообщения. Мы даже добавим C в качестве пробела. D будет нашим символом DLE. Наше сообщение теперь будет выглядеть так (все не буду, слишком утомительно...):

AWHDATCDACPIEDCECOFCWORKCISCMDANB

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

  • A — начало сообщения
  • W - буква W
  • H - буква Н
  • D — Следующий символ — буква
  • А - буква А
  • Т - буква Т
  • C – пробел

... и т.д...

То же самое мы делаем с вашими двоичными данными. Допустим, мы отправляем значения 23, 2, 16 и 1928 как целые числа. В прямом порядке (я расскажу об этом позже) это представлено как значения

23,0,2,0,16,0,136,7

поэтому с нашими начальными и конечными символами и добавлением нашего символа DLE 16 мы получаем

2,23,0,16,2,0,16,16,0,136,7,4

Вы можете написать небольшую функцию для отправки данных в этом формате:


int values[4];

// Некоторые символы для удобства
#define STX 2
#define EOT 4
#define DLE 16

void sendPacket(const uint8_t *data, int len) {
    Serial.write(STX);
    for (int i = 0; i < len; i++) {
        // Экранирование специальных символов
        if ((data[i] == STX) || (data[i] == EOT) || (data[i] == DLE)) {
            Serial.write(DLE);
        }
        Serial.write(data[i]);
    }
    Serial.write(EOT);
}

// затем используем его:

sendPacket((uint8_t *)values, sizeof(values));

Теперь прием может выполняться по одному байту за раз и передаваться в ваш массив в виде необработанных данных. Что-то вроде этого:


int values[4];

// Некоторые символы для удобства
#define STX 2
#define EOT 4
#define DLE 16

bool rxData(uint8_t *data, int len) {
    static int pos = 0; // Где в данных мы сейчас находимся?
    static bool isDLE = false; // Мы в режиме DLE?
    static bool messageStarted = false; // Мы уже получаем?

    if (Serial.available()) {
        int b = Serial.read(); // Читаем один байт

        if (!messageStarted) { // Мы еще не получаем - стоит ли начинать?
            if (b == STX) { // STX получен, начинаем прием.
                messageStarted = true;
            }
            // Все остальное игнорируется.
            return false; // Сообщение еще не завершено
        }

        // Если мы здесь, значит, мы на полпути к получению.

        // Если мы получили DLE в прошлый раз, мы всегда рассматриваем его как данные
        if (isDLE) {
            data[pos++] = b;
            isDLE = false; // Больше не в режиме DLE
            if (pos > len) { // Слишком много байт в сообщении, что-то случилось
                // ПРЕРЫВАНИЕ
                pos = 0;
                messageStarted = false;
            }
            return false; // Сообщение еще не завершено
        } 


        // Если у нас есть STX и мы не в режиме DLE, то мы должны быть
        // не синхронизировано. Сбросить счетчик и флаги и начать новое сообщение
        if (b == STX) {
            pos = 0;
            isDLE = false;
            return false; // Сообщение еще не завершено
        } 

        // Мы в конце сообщения?
        if (b == EOT) {
            if (pos == len) { // Мы получили правильное количество байтов?
                messageStarted = false;
                return true; // Сообщение завершено
            }
            // Должно быть, случилось что-то плохое. Не нужное количество
            // байты, полученные для действительного сообщения. ПРЕРЫВАТЬ.
            messageStarted = false;
            pos = 0;
            isDLE = false;
            return false; // Сообщение еще не завершено
        }

        // Проверяем символ DLE
        if (b == DLE) {
            isDLE = true;
            return false; // Сообщение еще не завершено
        }

        // Все остальное является байтом данных.
        data[pos++] = b;
        if (pos > len) { // Слишком много байт в сообщении, что-то случилось
            // ПРЕРЫВАНИЕ
            pos = 0;
            messageStarted = false;
        }
    }
    return false; // Сообщение еще не завершено
}

// Тогда используйте его:

if (rxData((uint8_t *)values, sizeof(values)) {
    Serial.print("You got: ");
    Serial.print(values[0]); 
    Serial.print(", ");
    Serial.print(values[1]); 
    Serial.print(", ");
    Serial.print(values[2]); 
    Serial.print(", ");
    Serial.println(values[3]); 
}

Я не тестировал этот код, так что отнеситесь к этому с долей скептицизма.

Теперь несколько слов об порядке байтов. Целое число имеет размер 16 бит. Для представления требуется два байта (2 x 8 = 16). "порядок байтов"; системы — это порядок, в котором эти два байта хранятся в памяти. В системе с прямым порядком байтов, такой как Arduino, значение 1928 хранится как байты 136,7. Но в системе с обратным порядком байтов он хранится как 7136. "самый большой" (т. е. значение старшего значащего байта) идет первым. При отправке таких двоичных данных очень важно убедиться, что оба конца ссылки согласны с порядком байтов. И тогда есть размер слова, чтобы рассмотреть. 8-битные системы используют 16-битное слово для целых чисел. 32-битные системы используют 32-битное слово. Таким образом, отправка int от Arduino к Pi, например, вызовет путаницу — Arduino отправляет 2 байта (для 16 бит), а Pi ожидает 4 байта (для 32 бит).

Для борьбы с последним хорошо использовать типы переменных фиксированной ширины, которые точно определяют, сколько битов используется. Вместо int используйте uint16_t (для Unsigned INTболее 16 бит). Таким образом, вы однозначно определите, какой это размер.

Для решения первой проблемы (при необходимости) существует стандартный набор функций, которые изменяют значения с исходных на "сетевые". порядок байтов. Сетевой порядок следования байтов — это стандарт, используемый для IP-коммуникаций, и он совпадает с прямым порядком байтов. Если вы собираетесь обмениваться данными только между Arduino с прямым порядком байтов, то в этом нет реальной необходимости. Но если это не обязательно, то использование htons() для "Host to Network Short" и ntohs() для "Сокращение сети к хосту" («short» — это 16-битное слово, например int16_t) может быть полезным. Лучше всего использовать их при сохранении или извлечении значений из массива values[]:

values[0] = htons(analogRead(0));

и:

Serial.print(ntohs(values[0]);

Возможно, вам потребуется включить в код заголовок machine/endian.h в зависимости от используемой системы.

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

,

Спасибо. Я никогда не читал такого ответа. Пожалуйста, напишите книгу, я куплю ее во что бы то ни стало., @ridgy


3

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

Например, у вас есть:

I received: 255
I received: 247
I received: 11
I received: 0

Хотя это может быть проанализировано вашим Uno, будет намного проще, если вы отправите данные что-то вроде:

255|247|11|0

Теперь вы можете использовать функцию синтаксического анализа строк "C", такую как strtok(), для анализа символа "|". Конечно, есть много других способов сделать это, но это одно из возможных решений вашего вопроса.

,

0

Вот так? А также какие последовательные скорости я использую? @Majenko Это отправитель:

    int pot1 = A0;
    int pot2 = A1;
    int pot3 = A2;
    int pot4 = A3;
    
    
    
    // Некоторые символы для удобства использования
    #define STX 2
    #define EOT 4
    #define DLE 16
    
    
    void setup() {
    Serial.begin(9600);
    }
    
    
    void sendPacket(const uint8_t *data, int len) {
      Serial.write(STX);
      for (int i = 0; i < len; i++) {
        // Экранирование специальных символов
        if ((data[i] == STX) || (data[i] == EOT) || (data[i] == DLE)) {
          Serial.write(DLE);
        }
        Serial.write(data[i]);
      }
      Serial.write(EOT);
    }
    
    
    void loop() {
    
      int values[4] = {analogRead(pot1), analogRead(pot2), analogRead(pot3), analogRead(pot4)};
      sendPacket((uint8_t *)values, sizeof(values));
      
    
    }

А это приемник:

int values[4];

// Некоторые символы для удобства использования
#define STX 2
#define EOT 4
#define DLE 16


void setup() {
  // поместите свой установочный код здесь, чтобы запустить один раз:
  Serial.begin(9600);
}


bool rxData(uint8_t *data, int len) {
  static int pos = 0; // Где мы сейчас находимся в данных?
  static bool isDLE = false; // Мы в режиме DLE?
  static bool messageStarted = false; // Мы уже получаем?

  if (Serial.available()) {
    int b = Serial.read(); // Чтение одного байта

    if (!messageStarted) { // Мы еще не получаем - должны ли мы начать?
      if (b == STX) { // STX received , начните прием.
        messageStarted = true;
      }
      // Anything else is ignored.
      return false; // Сообщение еще не завершено
    }

    // Если мы здесь, то должны быть на полпути к получению.

    // Если мы получили DLE в прошлый раз, то всегда рассматриваем его как данные
    if (isDLE) {
      data[pos++] = b;
      isDLE = false; // Больше не в режиме DLE
      if (pos > len) { // Слишком много байтов в сообщении, случилось что-то плохое
        // ПРЕРВАТЬ
        pos = 0;
        messageStarted = false;
      }
      return false; // Сообщение еще не завершено
    }


    // Если у нас есть STX и мы не находимся в режиме DLE, то мы должны быть
    // не синхронизированы. Сбросьте счетчик и флаги и начните новое сообщение
    if (b == STX) {
      pos = 0;
      isDLE = false;
      return false; // Сообщение еще не завершено
    }

    // Мы подошли к концу сообщения?
    if (b == EOT) {
      if (pos == len) { // Получили ли мы нужное количество байтов?
        messageStarted = false;
        return true; // Сообщение завершено
      }
      // Something bad must have happened. Not the right amount of
      //, полученных для допустимого сообщения. ОТБОЙ.
      messageStarted = false;
      pos = 0;
      isDLE = false;
      return false; // Сообщение еще не завершено
    }

    // Проверка наличия символа DLE
    if (b == DLE) {
      isDLE = true;
      return false; // Сообщение еще не завершено
    }

    // Все остальное - это байт данных.
    data[pos++] = b;
    if (pos > len) { // Слишком много байтов в сообщении, случилось что-то плохое
      // ПРЕРВАТЬ
      pos = 0;
      messageStarted = false;
    }
  }
  return false; // Сообщение еще не завершено
}


void loop() {
  // поместите свой основной код здесь, чтобы запускать его повторно:
  if (rxData((uint8_t *)values, sizeof(values))) {
  Serial.print("You got: ");
    Serial.print(values[0]);
    Serial.print(", ");
    Serial.print(values[1]);
    Serial.print(", ");
    Serial.print(values[2]);
    Serial.print(", ");
    Serial.println(values[3]);
  }
}
,

Посмотрите в этом, пожалуйста @Majenko, @Imanoobdotcom