пятница, 11 августа 2023 г.

Разное поведение плат Arduino при работе через последовательный порт

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

Чтобы отладить работу с протоколом я взял свободную Arduino Leonardo и набросал для нее скетч, который эмулирует поведение электронной нагрузки. После отладки стал переносить код протокола в основной скетч уже моей электронной нагрузки (которая сделана на базе Freeduino 2009, аналога Arduino Duemilanove) и столкнулся с проблемой, что программа на компьютере больше не определяет что подключена электронная нагрузка.

ASRL/dev/ttyUSB0::INSTR
SerialInstrument at ASRL/dev/ttyUSB0::INSTR
probe
<class 'pyvisa.errors.VisaIOError'>
('VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.',)
VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
error reading bytes
no answer
ko
No instruments found

Был уже поздний вечер и хотелось увидеть реальные показания и потому сразу попробовал поменять местами Freeduino и Arduino Leonardo - не помогло. Разные распиновки для I2C и не инициализируется периферия. Залил скетч эмулятора в обе платы и стал сравнивать их поведение - на Leonardo все просто работает, а на Freeduino - ошибка. Обратил внимание что отличаются названия портов - у Leonardo это /dev/ttyACM0, а у Freeduino это /dev/ttyUSB0, но почему на одном работает, а на втором - нет я не понял и пошел спать.

Сегодня расчехлил логический анализатор и прицепился к RX/TX, нескольким цифровым пинам и RESET. По коду набросал отдадочных "дерганий" пинами, которые подключены к анализатору. В начале setup() делается несколько "дрыганий" одним из пинов, а все остальные ставятся в низкий уровень. В конце setup() этот пин ставится в низкий уровень и поднимается пин для loop() и так далее. Так я могу понять когда начинается и завершается выполнение каждой секции кода и как они соотносятся с передачей данных через UART.

В результате получилось что при открывании порта выполнялся сброс (RESET в низком уровне) и сразу же передавались данные от программы на компьютере, но в этот момент еще даже не начиналось выполнение функции setup() в которой настраивается последовательный порт и данные уходили в /dev/null. Если добавить задержку 1.5 секунды между открыванием порта и отправкой данных, то все начинало работать.

Добрые люди подсказали что дело в сигнале DTR, который управляет сбросом на платах Arduino, а пауза перед setup() это загрузчик ожидает прошивку. Чтобы это не происходило нужно изменить программу и не использовать DTR при открывании порта. Вариант до которого я додумался сам - прицепить отдельный адаптер USB-UART в котором будут подключены только пины RX/TX и GND и это помогло, но пришлось перебрать три USB-UART адаптера (FT232, CH340 и PL2303). В отношении первых двух у меня сомнения относительно их оригинальности, а PL2303 покупался очень давно и скорее всего микросхема там оригинальная - вот с ним работает стабильно.

Далее были эксперименты с Pyserial чтобы работало без дополнительного адаптера. Я попробовал отключать DTR в настройках порта, но это не дало результата (DTR дергало несмотря на ser.dsrdtr = False).

Результат принес поиск в интернете - вначале я нашел упоминание команды stty -F /dev/ttyUSB0 -hupcl и после этого уже можно запускать программу и она нормально работает. А тут нашелся пример кода который делает аналогичное изменение в Python. В итоге тестовый код, который открывает порт и запрашивает значение напряжения:

import serial
import sys
import termios

cmd_get_voltage = b'\xb1\xb2\x11\x00\x00\xb6'

if len(sys.argv) > 1:
    port = sys.argv[1]
else:
    port = '/dev/ttyUSB0'

f = open(port)
attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close()

ser = serial.Serial()

ser.port = port
ser.baudrate = 9600
ser.bytesize = serial.EIGHTBITS
ser.parity   = serial.PARITY_NONE
ser.stopbits = serial.STOPBITS_ONE
ser.trscts   = False
ser.dsrdtr   = False
ser.timeout  = None

ser.open()

ser.write(cmd_get_voltage)
resp = ser.read(7)
print(resp)

ser.close()

В итоге написал скприт-обертку, который подключает virtualenv с зависимостями и выполняет нужную команду.

Комментариев нет:

Отправить комментарий