Каков наилучший (самый быстрый и надежный) способ отправки сообщений между Python на ПК и Arduino через последовательный порт?
Я пытаюсь установить связь между ПК с Python, используя PySerial, и Arduino. Сам Arduino имеет щит CAN и отвечает за взаимодействие с двигателем. Моя цель состоит в том, чтобы ПК сконструировал нужный кадр CAN (8 байт), отправил его по последовательному порту на Arduino, который затем сам использует библиотеку CAN для связи с двигателем. Затем мотор отправляет кадр возврата того же размера, который Arduino копирует в массив, а также выводит в последовательный порт.
В некоторых случаях это работает, хотя я изо всех сил пытаюсь сделать его надежным. Например, я управляю двигателем в режиме управления крутящим моментом (команда 37 в это техническое описание). Для произвольного прямого крутящего момента «50» отправляющий кадр должен быть:
[161, 0, 0, 0, 50, 0, 0, 0]
А для команды '-50' кадр должен быть:
[161, 0, 0, 0, 206, 255, 255, 255]
Я не понимал, что это может быть проблемой, но я предполагаю, что, поскольку для этого на самом деле требуется больше символов, отправка занимает больше времени. Положительные команды крутящего момента отправляются без проблем, в то время как отрицательные вызывают пропуски или дрожание двигателя. Я вижу, что иногда Arduino на самом деле не получает правильную команду, см. ниже (слева — кадр, отправленный из Python, а справа — то, что Arduino получает в ответ).
Я пробовал:
- Очистка последовательных буферов до и после отправки сообщений на обоих устройствах
- Добавление задержек, чтобы попытаться замедлить связь.
- Изменение скорости передачи
- Попытка использовать Serial.write() вместо Serial.print(), поскольку я предполагал, что это может быть быстрее (?)
Я прикрепил свой код ниже, но в целом, как лучше всего и быстрее обмениваться данными между Python и Arduino по последовательному порту? Даже если мое текущее приложение будет проигнорировано, как могут выглядеть идеальные сценарии для каждого из них?
Питон:
def serial_begin(self, baud, com):
self.ser = serial.Serial(com, baud)
self.ser.flushInput()
self.ser.flushOutput()
print("Connected to Serial Port " + com)
t.sleep(1)
def send_cam_frame(self, frame):
self.send_frame = frame
string_to_send = "<" + str(int(frame[0])) + "," + \
str(int(frame[1])) + "," + \
str(int(frame[2])) + "," + \
str(int(frame[3])) + "," + \
str(int(frame[4])) + "," + \
str(int(frame[5])) + "," + \
str(int(frame[6])) + "," + \
str(int(frame[7])) + ">"
self.ser.write(string_to_send.encode('UTF-8'))
self.receive_can_frame()
def receive_can_frame(self):
get_data = self.ser.readline().decode('UTF-8', errors='ignore')[0:][:-2]
self.receive_frame = np.fromstring(get_data, dtype='int', count=8, sep=' ')
def set_torque(self, torque):
self.command = self.command_list["SET_TORQUE"]
torque = int(self.constrain(torque * 2000 / 32, -2000, 2000))
frame = [self.command, 0, 0, 0, torque & 0xFF, (torque >> 8) & 0xFF, (torque >> 16) & 0xFF,
(torque >> 24) & 0xFF]
self.send_cam_frame(frame)
def main():
arduino_port = "COM5" # Default COM port
baud_rate = 57600 # Default Baud Rate
rmd = Motor()
rmd.serial_begin(baud=baud_rate, com=arduino_port)
set_torque = 0
while True:
try:
if keyboard.is_pressed('w'):
set_torque += 0.001
if keyboard.is_pressed('s'):
set_torque -= 0.001
rmd.set_torque(set_torque)
except KeyboardInterrupt:
rmd.disable_motor()
print("Keyboard Interrupt")
Ардуино:
byte recvFrame[8] = {0, 0, 0, 0, 0, 0, 0, 0};
byte sendFrame[8] = {0, 0, 0, 0, 0, 0, 0, 0};
// Serial Read Parameters
const byte numChars = 32; // Length of received char array from Serial
char receivedChars[numChars]; // Received char array from Serial
char tempChars[numChars]; // Temporary array for use when parsing
bool newData = false; // Flag to check if newData has been received
void setup() {
Serial.begin(57600);
delay(1000);
}
void loop() {
CANMessage frame;
frame.id = 0x140 + 1;
frame.len = 8;
// Do not touch this section
recvWithStartEndMarkers(); // Check Serial and receive data if there is newData
if (newData == true) {
strcpy(tempChars, receivedChars); // Copy variables to prevent them being altered
parseData(); // Parse data (split where there are commas)
for (int i = 0; i < 8; i++){
frame.data[i] = sendFrame[i];
}
can.tryToSend(frame);
newData = false; // Set to false
}
if (can.available()){
can.receive(frame);
}
for (int i = 0; i < 8; i++){
recvFrame[i] = frame.data[i];
}
printFrame();
}
// Do not touch this function
void recvWithStartEndMarkers() {
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (recvInProgress == true) {
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
}
}
else if (rc == startMarker) {
recvInProgress = true;
}
}
}
// Only touch this function if more data is being sent from Python code
void parseData() { // split the data into its parts
char * strtokIndx; // this is used by strtok() as an index
strtokIndx = strtok(tempChars,",");
sendFrame[0] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[1] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[2] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[3] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[4] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[5] = atoi(strtokIndx);
strtokIndx = strtok(NULL,",");
sendFrame[6] = atoi(strtokIndx);
strtokIndx = strtok(NULL, ",");
sendFrame[7] = atoi(strtokIndx);
// How to add a new variable
// Currently, data is sent as <0, 0, 0, 0>
// If a fourth parameter was to be sent (<0, 0, 0, 0, 1>), the following lines need to be added
// strtokIndx = strtok(NULL, ", "); This reads the string, from where it was previously cut, up until the next comma
// newVariableName = atoi(strtokIndx); atoi is 'to integer'. If newVariable is a float, atof is needed etc
}
void printFrame(){
for (int i = 0; i < 8; i++){
Serial.print(sendFrame[i]);
Serial.print(" ");
}
Serial.println();
//Serial.write(sendFrame, 8);
//delay(3);
}
Примечание. Для ясности я удалил некоторые ненужные функции из обоих скриптов (например, остальную часть класса Motor для Python и настройку CAN Shield для Arduino).
@ITregear, 👍1
Обсуждение1 ответ
Лучший ответ:
Нет лучшего способа, но есть способ лучше (и более эффективный).
Для произвольного прямого крутящего момента '50' отправляющий кадр должен быть:
[161, 0, 0, 0, 50, 0, 0, 0]
А для команды '-50' кадр должен быть:
[161, 0, 0, 0, 206, 255, 255, 255]
Большинство программистов, которые плохо знакомы со встроенными программами, подготавливают свои данные для отправки по каналу связи в виде строки вместо отправки необработанных двоичных данных. Проблема заключается в том, что а) для отправки значения 215 вы отправляете 3 байта ASCII, в то время как данные могут фактически отправляться одним байтом. б) как целое число 256 (0x0100
), так и 32766 (0x7FFE
) занимают всего два байта, но когда вы кодируете его как ASCII, вы имеете дело с различной длиной от 3 символов до 5 символов. Это не только требует больше байтов для отправки данных, но и затрудняет анализ данных, если у вас нет разделителя для разделения данных, например ('256,32766`, разделенных запятой).
Глядя на ваш набор данных и таблицу данных, становится ясно, что каждая точка данных представлена 4-байтовыми подписанными данными с небольшим порядком байтов. (то есть младший байт отправляется первым), поэтому десятичный 50
равен 0x00000032
, он хранится в памяти как 0x32, 0x00, 0x00, 0x00
с обратным порядком байтов.
Отрицательное значение представлено дополнением до двух положительного значения, поэтому -50
в десятичном виде равен 0xFFFFFFCE
и сохраняется как 0xCE, 0xFF, 0xFF, 0xFF
.
В python библиотека struct позволяет выполнять преобразования между значениями Python и структурами C, представленными в виде Python. байты объектов.
form struct import *
torque = -50
data = pack('<i', torque) # pack torque as a 4-byte integer with little endian
ser.write(torque) # send over serial as bytes
На стороне Arduino считайте данные из Serial как byte
(т.е. uint8_t
) и сохраните их в массиве. Затем вы можете преобразовать его обратно в int32
.
// предположим, что вы прочитали эти 4 байта данных из Serial и добавили их в массив
uint8_t data[4]{0xCE, 0xFF, 0xFF, 0xFF};
// Преобразование полученных байтов данных в int32_t
int32_t torque = static_cast<int32_t> (data[3]<< 24 | data[2] << 16 | data[1] << 8 | data[0]);
// Это вернет результат -50
Serial.println(torque);
Это должно сократить объем кода и значительно повысить эффективность отправки данных. Однако код не повышает надежность, одно из преимуществ отправки данных в виде строки заключается в том, что вы можете определить конец потока данных, обнаружив терминатор \0
в конце строки. При отправке необработанных двоичных файлов в идеале вам нужен механизм для обозначения количества байтов данных байтов, которые вы собираетесь отправить.
- Serial Comm. проблема синхронизации между Arduino и Pyserial
- Как отправить несколько данных по последовательному каналу в одном байте?
- Протокол связи Arduino с python — помимо примера pyserial и Arduino
- `time.sleep` в скрипте python чтение последовательного вывода вызывает неустойчивое поведение
- Потеря данных при последовательном считывании с помощью Arduino Nano
- Последовательная связь между двумя Arduino для аналогового чтения
- Проблемы при использовании SoftwareSerial
- Как игнорировать значения мусора при последовательной связи между Arduino и Python
вопросы о надежном последовательном протоколе не относятся к Arduino, @jsotola
Просить «лучшее» решение — плохой подход к дизайну. Четко сформулируйте свои требования, и у кого-то может быть **достаточно хорошее** решение., @Elliot Alderson
Вы проверили передачу данных без кода CAN и двигателя и без каких-либо других подключений? И в настоящее время вы отправляете обратно уже проанализированный кадр. Пожалуйста, попробуйте вместо этого отправить буфер символов и сообщить о результатах. И я не вижу, где вы печатали отправленные и полученные данные в коде Python. Пожалуйста, включите фактический код, который создал этот вывод. Это может быть актуально для отладки кода, @chrisl
Вам может понравиться https://github.com/nanopb/nanopb, @chicks