Как программно обнаружить последовательный порт Arduino на разных платформах?

Я пытаюсь написать небольшую программу, которая должна автоматически обнаруживать порты Arduino. Я перепробовал много методов, но не нашел ни одного надежного способа.

Я вижу, что Arduino IDE может обнаружить его, показав /dev/cu.xyz (Arduino Uno) или COM6 (Arduino Uno) и т. Д.

Как я могу определить, является ли устройство, подключенное к последовательному порту, Arduino или нет? Желательно без предварительной перепрошивки Arduino с определенной прошивкой.

Я нашел эту версию Python, которая использует serial.tools.list_ports.comports() здесь, но она не работала, когда я тестировал ее на Windows и macOS.

Лучшее, что я могу получить,-это использовать USBManufacturer go-serial, я могу получить "Arduino (www.arduino.cc).

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

, 👍7

Обсуждение

Когда я подключаю Arduino Uno к своей коробке Linux, lsusb говорит: “ID 2341: 0043 Arduino SA Uno R3 (CDC ACM)”. Однако я не знаю, как это сделать на других ОС., @Edgar Bonet

Я согласен с Эдгаром Бонетом - если вы можете перечислить USB-порты, вы можете определить, на каком из них есть Arduino, как это делает IDE., @Nick Gammon

Да, может быть, информация о usb-это все, что мы можем получить, так как последовательные порты больше не имеют никакой информации. Можно использовать порты Description и USBManufacturer. Спасибо вам, ребята., @leetom

Было бы полезно получить немного больше информации о том, что вы пытаетесь сделать., @mwwalk


4 ответа


0

Вы можете запустить Arduino IDE до тех пор, пока не будет обнаружен порт, который хранится на жестком диске arduino IDE;выйти из IDE; затем прочитать этот файл с помощью вашей пользовательской Windows (или любой другой ОС, которую вы используете), чтобы получить ответ. Это ответ на ваш вопрос, который вы написали выше.

,

Это неприменимо, так как программа должна работать на машинах без Arduino IDE, и процесс обнаружения должен быть АВТОМАТИЧЕСКИМ., @leetom

Это не было ясно для меня из вашего вопроса-вот почему я написал: "Это ответ на ваш вопрос, который вы написали выше"., @Dat Han Bag

Arduino IDE имеет открытый исходный код, поэтому вы можете получить часть кода, которая делает это обнаружение COM-это потребует некоторой работы с вашей стороны-и использовать его в своей собственной программе, @Dat Han Bag

Так что, насколько я понимаю, это действительно ответ на ваш вопрос, @Dat Han Bag

Хорошо, я пытаюсь найти соответствующую часть из исходного кода. Все равно спасибо., @leetom


1

Вот решение:

В Windows Arduino IDE поставляется с инструментом под названием listComPorts.exe, в котором будут перечислены все COM-порты и их USB-информация, такая как VID & PID. Существует также файл с именем arduino.inf в папке драйверов, содержащий все известные типы устройств и их VID&PID и т.д., И мы можем искать подключенное устройство.

В *nix IDE использует libusb и не имеет такого инструмента, и легко найти инструмент для перечисления информации о usb на этих платформах.

Этот пакет go, упомянутый в вопросе, является хорошим выбором, который также использует libusb и может быть построен на всех платформах. (В Windows вы должны установить mingw для сборки кода C, cygwin и msys не работают, я попробовал TDM-GCC, и он работает. Вы также должны добавить LDFLAGS: -lsetupapi (в serial.go), чтобы успешно связать код.) После перечисления всех последовательных портов вы можете найти "Arduino", чтобы определить порт. "CH340" также является ключевым словом для поиска, так как многие модифицированные платы используют этот чипсет.

Новая версия IDE не имеет listComPorts.exe все они используют libusb. Я нахожу этот инструмент начиная с версии 1.6.5.

,

2

Довольно поздно для вечеринки! Просто оставьте его здесь для тех, кто ищет решение в Windows.

Правильный способ - прочитать список устройств из реестра.

Первое место-это

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM

Лучшее место находится здесь

HKLM\SYSTEM\CurrentControlSet\Services\usbser\Enum

HKLM\SYSTEM\CurrentControlSet\Services\usbser\Enum

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

Чтобы получить порт, просто скопируйте первое устройство (индекс 0) и подключите его в это место

HKLM\SYSTEM\CurrentControlSet\Enum

Итак, это так

HKLM\SYSTEM\CurrentControlSet\Enum\USB\VID_2341&PID_8036&MI_00\9&88cb209&0&0000

HKLM\SYSTEM\CurrentControlSet\Enum\USB\VID_2341&PID_8036&MI_00

HKLM\SYSTEM\CurrentControlSet\Enum\USB\VID_2341&PID_8036&MI_00\ --- Device parameters

Таким образом, у вас есть дружественное имя и номер порта последовательного устройства. Затем просто напишите какой-нибудь код для чтения этих местоположений реестра.

В C

#include <windows.h>
#include <wchar.h>
#define MAX_VALUE_LENGTH 255

void getUSBSerialList()
{

    HKEY hKey;
    //проверьте, можем ли мы читать реестр
    INT dwRegOPenKey = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\usbser\\Enum"), 0, KEY_READ, &hKey);
    if(dwRegOPenKey != ERROR_SUCCESS) return 0;


    DWORD type=REG_DWORD, size=0, USBser_count = 0; // место для записи RegQueryValueEx
    DWORD dwRes;

    //количество считываний USB-устройств
    dwRes = RegQueryValueExW(hKey, _T("Count"), NULL, (LPDWORD) &type, (LPBYTE) &USBser_count, &size);
    if (dwRes) return 0;

    if (USBser_count) 
    {
        for (INT i=0, dwRes = ERROR_SUCCESS; i<USBser_count; i++) 
        { 
            WCHAR achValue[MAX_VALUE_LENGTH]; 
            WCHAR buffer[MAX_VALUE_LENGTH];
            ZeroMemory(buffer, sizeof WCHAR  * MAX_VALUE_LENGTH);

            swprintf(achValue, L"%d",i); // прочитать имя значения" 0", "1"...
            dwRes = RegQueryValueExW(hKey, achValue, NULL, (LPDWORD) &type, (LPBYTE) &buffer, &size);
            
            // Вставьте это значение в "SYSTEM\\CurrentControlSet\\Enum\\".
            HKEY hKey2;
            swprintf(achValue, L"SYSTEM\\CurrentControlSet\\Enum\\%s",buffer);
                
            INT dwRegOPenKey2 = RegOpenKeyExW(HKEY_LOCAL_MACHINE, achValue, 0, KEY_READ, &hKey2);
            if (dwRegOPenKey2) return 0; // проверьте, существует ли этот ключ
            
            // Отсюда читайте дружественное имя
            dwRes = RegQueryValueExW(hKey2, _T("FriendlyName"), NULL, (LPDWORD) &type, (LPBYTE) &buffer, &size);
            if (dwRes) return 0;
            
            // или читать COM-порт
            swprintf(achValue, L"%s\\Device Parameters", achValue);
            dwRegOPenKey2 = RegOpenKeyExW(HKEY_LOCAL_MACHINE, achValue, 0, KEY_READ, &hKey2);
            if (dwRegOPenKey2) return 0;

            dwRes = RegQueryValueExW(hKey2, _T("PortName"), NULL, (LPDWORD) &type, (LPBYTE) &buffer, &size);
            if (dwRes) return 0;
        }
    }
}

В C# для этого просто используйте объект реестра

int usbser_count = (int)Registry.GetValue("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\usbser\\Enum", "Count", -1);

Я не делал этого в C#, но я думаю, что это проще, чем в C.

Что делать, если ваш клон arduino использует CH340 вместо atmega16u2, он не появится в указанном выше расположении реестра. Вместо этого он появится здесь:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CH341SER_A64\Enum

Вы можете сделать то же самое для других типов USB-последовательных устройств, таких как CP210x (на некоторых платах ESP32 dev).

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\silabser\Enum

Или даже получить список устройств WinUSB (например, ST-Link v2).

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WINUSB\Enum

,

0

Вот фрагмент кода C#, основанный на ответе SimonVu14. Он просматривает именованные ключи и получает порты как для чипов Atmega, так и для CH340. Если вы ожидаете, что к компьютеру будет подключен только один Arduino, вы можете просто пойти с первым найденным. В противном случае вам нужно будет что-то придумать, чтобы выяснить, какой из них ваш (что обычно включает в себя открытие порта, отправку некоторого предопределенного запроса и ожидание некоторого предопределенного ответа), или дать пользователю возможность выбрать через пользовательский интерфейс. Sample

    private readonly string[] _arduinoKeys = {
        @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\usbser\Enum",
        @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CH341SER_A64\Enum"
    };

    /// <summary>
    /// See https://arduino.stackexchange.com/questions/30808/how-to-detect-arduino-serial-port-programmatically-on-different-platforms/80887
    /// </summary>
    private IEnumerable<(string name, string port)> EnumerateArduinos()
    {
        foreach (var arduinoKey in _arduinoKeys)
        {
            var countObject  = Registry.GetValue(arduinoKey, "Count", null);
            if (countObject is not int count)
            {
                continue;
            }

            for (int i = 0; i < count; i++)
            {
                var enumObject = Registry.GetValue(arduinoKey, i.ToString(), null);
                if (enumObject is not string enumKey)
                {
                    continue;
                }

                var friendlyName = Registry.GetValue($@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\{enumKey}", "FriendlyName", null);
                var portName = Registry.GetValue($@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\{enumKey}\Device Parameters", "PortName", null);
                if (portName is string port && friendlyName is string name)
                {
                    yield return (name, port);
                }
            }
        }
    }
,