Для университетского задания я пишу Java-приложение, которое будет запускать некоторую игровую логику для интерактивного светодиодного стола. Сам стол управляется либо 2 Arduino Duemilanove, либо 1 Arduino Mega 2560.
Чтобы предоставить Arduino (-ам) информацию о том, какие светодиоды должны светиться и какого цвета, я отправляю данные через последовательный порт с Raspberry Pi 3b+ на Arduino. Поскольку таблица состоит из 14 светодиодных лент, по 14 светодиодов на каждую светодиодную ленту, и каждый светодиод имеет 3 значения цвета (RGB), я храню данные о таблице в массиве int[14][14][3].
Перед отправкой массива в Arduino я создаю его объект JSON (используя библиотеку Jackson), а затем отправляю массив в виде строки с помощью jSerialComm. В зависимости от того, какую настройку Arduino я использую, я также либо передаю весь массив в JSON, либо разбиваю его на два массива int[7][14][3] перед созданием объекта JSON.
Поскольку данные поступали в последовательный порт в неправильном порядке, когда я использовал 2 Arduino и jSerialComm, теперь я получил новый Arduino Mega 2560 (поскольку другие вопросы SO предполагали, что неправильный порядок данных может возникнуть из-за устаревшего модуля PL2303) и попробовал еще раз с тем же результатом. После некоторых дальнейших исследований я попытался использовать JSSC вместо jSerialComm, но все равно появляется тот же результат.
Класс Java, который я использую для отправки данных в Arduino, выглядит следующим образом (закомментированный код — это код, в котором я использовал jSerialComm/2 Arduinos):
package de.pimatrix.backend;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fazecast.jSerialComm.SerialPort;
import jssc.SerialPortException;
public class SerialThread implements Runnable {
public static SerialPort arduino1, arduino2;
private int[][][] matrix = new int[14][14][3];
private int[][][] matrixLeft = new int[7][14][3];
private int[][][] matrixRight = new int[7][14][3];
private Socket localHost;
private Matrix matrixData;
private ObjectInputStream in;
@Override
public void run() {
SerialJSONWriter writer = new SerialJSONWriter();
ServerSocket ss = null;
localHost = null;
matrixData = new Matrix(matrix);
try {
ss = new ServerSocket(62000); // erstellen eines lokalen Sockets auf Port 62000, um die zu übertragende
// Matrix vom ClientThread
} catch (IOException e) {
}
while (true) {
try {
localHost = ss.accept();
} catch (Exception e) {
e.printStackTrace();
}
initializeInputStream();
waitForMatrix();
splitMatrix();
try {
writer.tryWrite(matrixRight, matrixLeft);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void splitMatrix() {
for (int i = 0; i < 14; i++) {
for (int j = 0; j < 14; j++) {
if (i <= 6) {
matrixRight[i][j][0] = matrix[i][j][0];
matrixRight[i][j][1] = matrix[i][j][1];
matrixRight[i][j][2] = matrix[i][j][2];
} else {
matrixLeft[i - 7][j][0] = matrix[i][j][0];
matrixLeft[i - 7][j][1] = matrix[i][j][1];
matrixLeft[i - 7][j][2] = matrix[i][j][2];
}
}
}
}
private void initializeInputStream() {
try {
InputStream input = localHost.getInputStream();
in = new ObjectInputStream(input);
} catch (IOException e) {
e.printStackTrace();
}
}
public void waitForMatrix() {
System.out.println("Waiting for Matrix");
try {
matrixData = (Matrix) in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.matrix = matrixData.matrix;
}
class SerialJSONWriter implements AutoCloseable {
// Zuweisen der seriellen Ports
// private final SerialPort /*arduino1, arduino2,*/ arduinoMega;
private jssc.SerialPort arduinoMega;
public SerialJSONWriter() {
// arduino1 = SerialPort.getCommPort("COM5");
// arduino2 = SerialPort.getCommPort("COM6");
// arduinoMega = SerialPort.getCommPort("COM7");
arduinoMega = new jssc.SerialPort("COM7");
try {
arduinoMega.openPort();
arduinoMega.setParams(115200, 8, 1, jssc.SerialPort.PARITY_EVEN);
} catch (SerialPortException e) {
e.printStackTrace();
}
// arduinoMega.setBaudRate(115200);
// arduinoMega.setNumDataBits(8);
// arduinoMega.setNumStopBits(1);
// arduinoMega.setParity(0);
// setzen der Timeouts für die Kommunikation mit den Arduinos
// arduino1.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduino2.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduinoMega.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
// arduino1.setBaudRate(115200);
// arduino2.setBaudRate(115200);
// arduinoMega.setBaudRate(115200);
// arduino1.openPort();
// arduino2.openPort();
// arduinoMega.openPort();
// arduino1.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
// arduino2.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
// arduinoMega.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 0,
// 0);
}
public void write() {
}
private void tryWrite(Object dataRight, Object dataLeft) throws IOException {
String dataAsJSONRight = new ObjectMapper().writeValueAsString(dataRight) + "\n";
String dataAsJSONLeft = new ObjectMapper().writeValueAsString(dataLeft) + "\n";
try {
arduinoMega.writeString(dataAsJSONRight);
} catch (SerialPortException e) {
e.printStackTrace();
}
// for (int i = 0; i < dataAsJSONRight.length(); i++) {
//// arduino1.getOutputStream().write(dataAsJSONRight.getBytes()[i]);
// System.out.println(dataAsJSONRight);
// arduinoMega.getOutputStream().write(dataAsJSONRight.getBytes()[i]);
// }
// for (int i = 0; i < dataAsJSONLeft.length(); i++) {
//// arduino2.getOutputStream().write(dataAsJSONLeft.getBytes()[i]);
// arduinoMega.getOutputStream().write(dataAsJSONLeft.getBytes()[i]);
// }
}
@Override
public void close() throws Exception {
// arduino1.closePort();
// arduino2.closePort();
arduinoMega.closePort();
}
}
}
На Arduino (ах) обработка выглядит так:
#include <ArduinoJson.h>
#include <Adafruit_NeoPixel.h>
#define PINROW0 2
#define PINROW1 3
#define PINROW2 4
#define PINROW3 5
#define PINROW4 6
#define PINROW5 7
#define PINROW6 8
#define NUMPIXELS 14 //Amount of pixels per row
Adafruit_NeoPixel row[] = { //Intitialize the array, that contains the addressable LED strips in the Adafruit format
Adafruit_NeoPixel(NUMPIXELS, PINROW0, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW1, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW2, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW3, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW4, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW5, NEO_GRB + NEO_KHZ800),
Adafruit_NeoPixel(NUMPIXELS, PINROW6, NEO_GRB + NEO_KHZ800)
};
#define DELAY 1000 //set refresh cycle to 10 milliseconds
#define NUMSTRIPS 7/*(sizeof(row)/sizeof(row[0]))*/ //Amount of connected LED strips
int values[7][14][3];
int c = 0;
String matrixAsString = "";
void setup() {
/*Setup serial port on which the Pi connects to the Arduino*/
Serial.begin(115200); //set baudrate to 115200 Bit per second
Serial.setTimeout(1000);
Serial.println(100);
/*initialize NeoPixel Library*/
for (int i = 0; i < NUMSTRIPS; i++) {
row[i].begin();
row[i].show();
}
}
void process(String matrixAsString) {
StaticJsonDocument<4372> doc;
Serial.println(matrixAsString);
deserializeJson(doc, matrixAsString);
for (int i = 0; i < 7; i++) {
for (int j = 0; i < 14; j++) {
values[i][j][0] = values[i][j][1] = values[i][j][2] = (int) (doc[i][j][0]);
}
}
}
//infinite loop refreshing the matrix
void loop() {
while (Serial.available()) {
char c = Serial.read();
Serial.println(matrixAsString);
matrixAsString += c;
if (c == '\n') {
process(matrixAsString);
matrixAsString = "";
}
}
}
При отправке данных для половинной матрицы (например, int[7][14][3]):
[[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[255,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]],[[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0]]]
через последовательный монитор в Arduino IDE я получаю этот вывод от Arduino (начиная с Serial.println() в void loop
):
Как видно, первые значения RGB передаются корректно, однако после менее чем одной полной светодиодной ленты данные поступают в неправильном порядке и (как видно в конце картинки) в какой-то момент вообще перестают отображаться, что вероятно, указывает, что данные больше не считываются.
Я перепробовал множество вещей, таких как замена Arduino на случай, если PL2303 устарел или неисправен, а также пробовал разные библиотеки для последовательной связи, однако я не могу понять, что я делаю неправильно. Я потратил более 30 часов, пробуя разные подходы, но безрезультатно, так что все становится очень разочаровывающим для меня.
ОБНОВИТЬ
Как было предложено Б.Летц, я правильно настроил данные, стоповые биты и биты четности (теперь 8 битов данных, 1 стоповый и без битов четности). Прочитав отзывы Arduino, я все еще получил те же результаты, но после некоторой настройки я понял, что проблема, вероятно, заключалась в том, что мой Serial.print, по-видимому, приводил к значительному отставанию Arduino, поэтому он не мог правильно и вовремя обрабатывать все данные. После удаления первого вызова Serial.print перед выполнением обработки я теперь вижу, что первая передаваемая матрица правильно печатается Arduino. Однако почему-то для всех дальнейших передаваемых данных Arduino печатает null
. Я попытаюсь увеличить тайм-ауты на случай, если нулевой указатель возникнет из-за тайм-аута на стороне Arduino.
ОБНОВЛЕНИЕ 2
Вопреки моему предположению, перенастройка тайм-аутов не решила проблему. Я также выяснил, что после отправки первого объекта JSON Arduino выводит null
на консоль и отправляет мне первый объект JSON только после получения второго объекта JSON. Однако это единственный раз, когда я получаю какую-либо обратную связь от Arduino, кроме null
. Я также заметил, что когда я отправляю строку JSON через последовательный монитор, Arduino мгновенно печатает правильную строку НО, а также печатает пустую новую строку и не отвечает ни на какие новые данные.
@B.Letz Да, я пытался использовать разные скорости передачи данных. Даже при использовании скорости 9600 бод я не получил должных результатов. Если я правильно помню, это даже ухудшилось, поскольку случайным образом 0 превратился в 5 в какой-то момент моих тестовых значений. Я также попытался установить разные значения скорости передачи данных на сервере и Arduino (хотя я, очевидно, не ожидал, что это сработает).
Строка JSON верна или это тоже неправильно?
Я проанализировал свои данные на уровне приложения, распечатав их на консоли Eclipse, и проанализировал их структуру, вставив и структурировав их в текстовом редакторе, и это действительно правильное представление светодиодной матрицы. Образец int[7][14][3], который я разместил в вопросе, - это точные данные. Перед отправкой данных очевидно, что они в правильном порядке. Неправильный порядок появляется, как только я регистрирую его с помощью Arduino, и не имеет значения, передаю ли я строку через java-приложение или последовательный монитор Arduino IDE.
На Arduino мне кажется, что вы не установили 8 битов данных, 1 стоповый бит и четный бит четности. Или я это упускаю из виду?
Спасибо за этот совет. Я попробую, как только доберусь до проекта позже. Однако этому соображению может противодействовать тот факт, что первые байты данных принимаются правильно - если была неправильная конфигурация с данными, стоповыми битами и битами четности, я ожидал бы, что данные будут получены неправильно с самого начала. Хотя я был бы очень рад, если бы ошибся в этом вопросе.
Первым шагом для рабочего решения было удаление ненужного вызова Serial.print()
каждый раз, когда считывался новый символ. После удаления этой строки я мог подтвердить, что данные поступили правильно.
Смещенная обратная связь, как упоминалось в моем втором обновлении сообщения:
I also figured out that after the first JSON Object is sent the Arduino prints null to the console and only sends me the first JSON object after receiving the second JSON object. However this is the only time I get any feedback from the Arduino except null
произошло из-за того, что я недостаточно долго ждал поступления данных на стороне java-приложения перед вызовом функции read()
. После решения этого я всегда получал правильную строку.
Пробуя разные конфигурации с помощью DynamicJsonDocument
и StaticJsonDocument
, я в конечном итоге использовал DynamicJsonDocument
, однако здесь мог бы сработать и StaticJsonDocument
.
Довольно неприятная проблема заключалась в том, что во внутреннем цикле for в void process
я случайно сравнил переменную счетчика с переменной внешнего цикла for, хотя мне удалось получить правильные данные в этой точке вне цикла for.
Таким образом, проблема, заданная в вопросах это, решена, однако теперь возникла еще большая проблема, поскольку я не могу получить какие-либо данные из полученного объекта JSON, как только я начинаю реализовывать код для управления светодиодами и вызывать row[i].setPixelColor(j, row[i].Color(values[i][j][0], values[i][j][1], values[i][j][2]));
в любой момент в моем коде. Подводя итог, этот конкретный вызов является фактической причиной того, что код не работает должным образом.
Я открою новый вопрос для этой новой проблемы, поскольку он тематически не относится к этому вопросу, однако я добавлю ссылку на него здесь, как только он будет написан.
ОБНОВИТЬ
Новый вопрос, касающийся адресации более 7 светодиодных лент с использованием библиотеки Adafruit NeoPixel, можно найти здесь.
Вы пробовали более медленную скорость передачи данных? Скорость 115200 бод очень высока для последовательного соединения и рассчитана только на ~1,2 м (~4 фута) кабеля. Возможно, попробуйте 19200 в качестве скорости передачи и посмотрите, поможет ли это вообще.