7

I am trying to write a little program that needs to detect Arduino ports automatically. I have tried many methods but haven't found any reliable way.

I can see that the Arduino IDE can detect it, by showing /dev/cu.xyz (Arduino Uno) or COM6 (Arduino Uno) etc.

How could I detect if the device connected to the serial port is an Arduino or not? Preferably without pre-flashing the Arduino with specific firmware.

I found this Python version, which uses serial.tools.list_ports.comports() here, but it didn't work when I tested it on Windows and MacOS.

The best I can get is by using go-serial's USBManufacturer, I can get 'Arduino (www.arduino.cc)'.

I also found this post, about the same question, but without any solution.

4
  • When I connect an Arduino Uno to my Linux box, lsusb says “ID 2341:0043 Arduino SA Uno R3 (CDC ACM)”. I don't know how to do that on other OSes though. Nov 3, 2016 at 20:07
  • I agree with Edgar Bonet - if you can enumerate the USB ports you can work out which one has an Arduino on it, just like the IDE does.
    – Nick Gammon
    Nov 4, 2016 at 9:32
  • Yeah, maybe usb info is all we can get since serial ports don't have any more info. The ports' Description and USBManufacturer could be used. Thank you guys.
    – leetom
    Nov 5, 2016 at 10:26
  • A little more information about what you are trying to do would be helpful.
    – mwwalk
    Nov 10, 2016 at 3:04

4 Answers 4

2

Quite late for the party! Just leave it here for anyone looking for a solution on windows.

The proper way is to read the device list from registry.

First location is

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM

HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM

A better location is here

HKLM\SYSTEM\CurrentControlSet\Services\usbser\Enum

HKLM\SYSTEM\CurrentControlSet\Services\usbser\Enum

You have the list of Arduino devices there. Mine is Arduino Leonardo. Genuine Arduino UNO uses the similar chip and it should be in there as well.

To get the port, just copy the first device (index 0) and plug into this location

HKLM\SYSTEM\CurrentControlSet\Enum

So it is like this

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

So you have friendly name and port number of the serial device. Then just write some code to read these registry locations.

In C

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

void getUSBSerialList()
{

    HKEY hKey;
    //check if we can read registry
    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; // a place for RegQueryValueEx to write back to
    DWORD dwRes;

    //read count of usb devices
    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); // read value name "0", "1"...
            dwRes = RegQueryValueExW(hKey, achValue, NULL, (LPDWORD) &type, (LPBYTE) &buffer, &size);
            
            // Plug that value into "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; // check if this key exists
            
            // From here read friendly name
            dwRes = RegQueryValueExW(hKey2, _T("FriendlyName"), NULL, (LPDWORD) &type, (LPBYTE) &buffer, &size);
            if (dwRes) return 0;
            
            // or read COM port
            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;
        }
    }
}

In C#, just use Registry object to do so

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

I haven't done it in C# but I think it's easier than in C.

What if yours is arduino clone that use CH340 instead of atmega16u2, it won't appear in the registry location above. It will show up here instead:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CH341SER_A64\Enum

You can do the same for other type of USB serial devices like CP210x (on some ESP32 dev boards)

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\silabser\Enum

Or even get a list of WinUSB devices (such as ST-Link v2)

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WINUSB\Enum

1

Here is a solution:

On Windows, Arduino IDE comes with a tool named listComPorts.exe, which will list all COM ports and their USB info like VID & PID. There is also a file named arduino.inf in drivers folder contains all known device types and their VID&PID etc, and we can look for the connected device.

On *nix, the IDE take advantage of libusb and no such a tool, and it's easy to find a tool to list usb info on these platforms.

This go package mentioned in the question is a good choice, which also uses libusb and could build on all platforms. (On windows, you have to install mingw to build the C code, cygwin & msys don't work, I tried TDM-GCC and it works. You also have to add LDFLAGS: -lsetupapi (in serial.go) to successfully link the code.) After list all serial ports, you can search for 'Arduino' to determine the port. 'CH340' is also a keyword to search as many modified boards use this chipset.

New version of the IDE don't have listComPorts.exe, they all uses libusb. I find this tool from version 1.6.5.

0

You can start the Arduino IDE wait unit the port is detected-which are stored on the hard drive by the arduino IDE;exit the IDE; then read through that file with your custom Windows (or whatever OS you're using -to get your answer. This answers your question you wrote above.

5
  • 1
    This is not applicable as the program is to run on machines without Arduino IDE, and the detecting process should be AUTOMATICALLY.
    – leetom
    Nov 3, 2016 at 17:04
  • That wasnt clear to me from your question -which is why I wrote "This answers your question you wrote above." Nov 3, 2016 at 17:05
  • 2
    The arduino IDE is open source so you can get the part of the code that does that COM detection-this will require some work on your part-and use it in your own program Nov 3, 2016 at 17:10
  • So as far as I can understand that does answer your question Nov 3, 2016 at 17:15
  • OK, I am trying to find the related part from the source code. Thank you anyway.
    – leetom
    Nov 3, 2016 at 17:32
0

Here is snippet of C# code based on answer by SimonVu14. It looks through the named keys and gets ports for both Atmega and CH340 chips. If you expect to have just one Arduino connected to the computer, you can just go with the first one found. Otherwise you'll need to invent something to figure out which one is yours (which usually involves opening port, sending some predefined request and expecting some predefined response), or give user option to select via UI. 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);
                }
            }
        }
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.