{“payload”:{“allShortcutsEnabled”:false,”fileTree”:{“”:{“items”:[{“name”:”.idea”,”path”:”.idea”,”contentType”:”directory”},{“name”:”README.assets”,”path”:”README.assets”,”contentType”:”directory”},{“name”:”examples”,”path”:”examples”,”contentType”:”directory”},{“name”:”hardwarelibrary”,”path”:”hardwarelibrary”,”contentType”:”directory”},{“name”:”.gitattributes”,”path”:”.gitattributes”,”contentType”:”file”},{“name”:”.gitignore”,”path”:”.gitignore”,”contentType”:”file”},{“name”:”README-1-USB.md”,”path”:”README-1-USB.md”,”contentType”:”file”},{“name”:”README-2-RS232.md”,”path”:”README-2-RS232.md”,”contentType”:”file”},{“name”:”README-3-USB-Cameras.md”,”path”:”README-3-USB-Cameras.md”,”contentType”:”file”},{“name”:”README-4-New-device-coding-example.md”,”path”:”README-4-New-device-coding-example.md”,”contentType”:”file”},{“name”:”README-5-Communication-ports.md”,”path”:”README-5-Communication-ports.md”,”contentType”:”file”},{“name”:”README-7-Sutter-ROE-200.md”,”path”:”README-7-Sutter-ROE-200.md”,”contentType”:”file”},{“name”:”README.md”,”path”:”README.md”,”contentType”:”file”},{“name”:”SCPI.md”,”path”:”SCPI.md”,”contentType”:”file”},{“name”:”setup.py”,”path”:”setup.py”,”contentType”:”file”},{“name”:”setupEnvironment.sh”,”path”:”setupEnvironment.sh”,”contentType”:”file”},{“name”:”test.py”,”path”:”test.py”,”contentType”:”file”}],”totalCount”:17}},”fileTreeProcessingTime”:3.9735250000000004,”foldersToFetch”:[],”reducedMotionEnabled”:null,”repo”:{“id”:141367028,”defaultBranch”:”master”,”name”:”PyHardwareLibrary”,”ownerLogin”:”DCC-Lab”,”currentUserCanPush”:false,”isFork”:false,”isEmpty”:false,”createdAt”:”2018-07-18T01:55:24.000Z”,”ownerAvatar”:”https://avatars.githubusercontent.com/u/33441621?v=4″,”public”:true,”private”:false,”isOrgOwned”:true},”symbolsExpanded”:false,”treeExpanded”:true,”refInfo”:{“name”:”master”,”listCacheKey”:”v0:1694542313.0″,”canEdit”:false,”refType”:”branch”,”currentOid”:”939ffca7c8b3b214b77acadae2d76d5029dd0660″},”path”:”README-1-USB.md”,”currentUser”:null,”blob”:{“rawLines”:null,”stylingDirectives”:null,”csv”:null,”csvError”:null,”dependabotInfo”:{“showConfigurationBanner”:false,”configFilePath”:null,”networkDependabotPath”:”/DCC-Lab/PyHardwareLibrary/network/updates”,”dismissConfigurationNoticePath”:”/settings/dismiss-notice/dependabot_configuration_notice”,”configurationNoticeDismissed”:null,”repoAlertsPath”:”/DCC-Lab/PyHardwareLibrary/security/dependabot”,”repoSecurityAndAnalysisPath”:”/DCC-Lab/PyHardwareLibrary/settings/security_analysis”,”repoOwnerIsOrg”:true,”currentUserCanAdminRepo”:false},”displayName”:”README-1-USB.md”,”displayUrl”:”https://github.com/DCC-Lab/PyHardwareLibrary/blob/master/README-1-USB.md?raw=true”,”headerInfo”:{“blobSize”:”30 KB”,”deleteInfo”:{“deleteTooltip”:”You must be signed in to make or propose changes”},”editInfo”:{“editTooltip”:”You must be signed in to make or propose changes”},”ghDesktopPath”:”https://desktop.github.com”,”gitLfsPath”:null,”onBranch”:true,”shortPath”:”08b6eec”,”siteNavLoginPath”:”/login?return_to=https%3A%2F%2Fgithub.com%2FDCC-Lab%2FPyHardwareLibrary%2Fblob%2Fmaster%2FREADME-1-USB.md”,”isCSV”:false,”isRichtext”:true,”toc”:[{“level”:1,”text”:”Understanding the Universal Serial Bus (USB)”,”anchor”:”understanding-the-universal-serial-bus-usb”,”htmlText”:”Understanding the Universal Serial Bus (USB)”},{“level”:2,”text”:”Inspecting USB devices”,”anchor”:”inspecting-usb-devices”,”htmlText”:”Inspecting USB devices”},{“level”:3,”text”:”Installing PyUSB and libusb”,”anchor”:”installing-pyusb-and-libusb”,”htmlText”:”Installing PyUSB and libusb”},{“level”:3,”text”:”First steps”,”anchor”:”first-steps”,”htmlText”:”First steps”},{“level”:3,”text”:”Configuring the USB device”,”anchor”:”configuring-the-usb-device”,”htmlText”:”Configuring the USB device”},{“level”:3,”text”:”Choosing a USB interface”,”anchor”:”choosing-a-usb-interface”,”htmlText”:”Choosing a USB interface”},{“level”:3,”text”:”Viewing input and output endpoints”,”anchor”:”viewing-input-and-output-endpoints”,”htmlText”:”Viewing input and output endpoints”},{“level”:3,”text”:”Final words”,”anchor”:”final-words”,”htmlText”:”Final words”},{“level”:2,”text”:”Communicating with USB devices”,”anchor”:”communicating-with-usb-devices”,”htmlText”:”Communicating with USB devices”},{“level”:3,”text”:”Encoding the information”,”anchor”:”encoding-the-information”,”htmlText”:”Encoding the information”},{“level”:1,”text”:”References”,”anchor”:”references”,”htmlText”:”References”}],”lineInfo”:{“truncatedLoc”:”344″,”truncatedSloc”:”268″},”mode”:”file”},”image”:false,”isCodeownersFile”:null,”isPlain”:false,”isValidLegacyIssueTemplate”:false,”issueTemplateHelpUrl”:”https://docs.github.com/articles/about-issue-and-pull-request-templates”,”issueTemplate”:null,”discussionTemplate”:null,”language”:”Markdown”,”languageID”:222,”large”:false,”loggedIn”:false,”newDiscussionPath”:”/DCC-Lab/PyHardwareLibrary/discussions/new”,”newIssuePath”:”/DCC-Lab/PyHardwareLibrary/issues/new”,”planSupportInfo”:{“repoIsFork”:null,”repoOwnedByCurrentUser”:null,”requestFullPath”:”/DCC-Lab/PyHardwareLibrary/blob/master/README-1-USB.md”,”showFreeOrgGatedFeatureMessage”:null,”showPlanSupportBanner”:null,”upgradeDataAttributes”:null,”upgradePath”:null},”publishBannersInfo”:{“dismissActionNoticePath”:”/settings/dismiss-notice/publish_action_from_dockerfile”,”dismissStackNoticePath”:”/settings/dismiss-notice/publish_stack_from_file”,”releasePath”:”/DCC-Lab/PyHardwareLibrary/releases/new?marketplace=true”,”showPublishActionBanner”:false,”showPublishStackBanner”:false},”renderImageOrRaw”:false,”richText”:”
[TOC]
nn
by Prof. Daniel Côté, Ph.D., P. Eng., dccote@cervo.ulaval.ca, http://www.dcclab.ca
n
You are here because you have an interest in programming hardware devices, and the communication with many of them is through the Universal Serial Bus, or USB. The USB standard is daunting to non-expert for many reasons: because it is so general (universal), it needs to provide a solution to many, many different types of devices from mouse pointers to all-in-one printers, USB hubs, ethernet adaptors, etc. In addition, it was created to solve problems related to the original serial port RS-232, and if you have not worked with old serial ports, some of the problems USB solves will not be apparent to you or may not even appear necessary. Therefore, when you are just trying to understand serial communications for your problem (“I just want to send a command to my XYZ stage!“), all this complexity becomes paralyzing. Of course, you don’t always need to program everything from scratch, like we will do here: very often, manufacturers will provide a Python Software Development Kit (or SDK) with all the work done for you. If it exists, use it. However, we assume here that either such an SDK is not available or you simply want to learn how they are made. We could always sweep everything under the rug, but you are currently reading this document, so it is assumed you want to understand the details. I hope to help you understand better from the perspective of a non-expert.
n
Inspecting USB devices
n
Let’s start by exploring with Python and PyUSB to see what these devices are telling us. We will not communicate with them directly yet, we will simply inspect them.
n
Installing PyUSB and libusb
n
I wish we could dive right in. I really do. If you just want to see what I do, skip and go to the exploration part (First Steps). But if you want to explore on your computer too, then you need to install PyUSB and libusb.
n
The first part is simple. Install the PyUSB module with pip
:
nn
But then, you need to install libusb
, which is the actual engine that talks to the USB ports on your computer, and PyUSB needs to know where it is. Libusb is an open source library that has been adopted by many developers because it is complete, bug-free and cross-platform, and without it, PyUSB will not work. Doing pip install libusb
is not a solution, it is a different module and keeps the libusb “for itself”. It also does not ship with the macOS libusb. You can use Zadig on Windows to install it or brew on macOS. It may also already be installed on your computer (if you see /usr/local/lib/libusb-1.0.0.dylib
on your computer, it should work). On macOS and Linux, install libusb with these two lines in the terminal:
n
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"nbrew install libusb
n
On Windows, get Zadig and keep for fingers crossed. Worse comes to worst, the simplest solution is to download it and keep libusb-1.0.x.dll
in the directory where you expect to keep your Python scripts (for now). Don’t get me started on DLLs on Windows. If everything really does not work for one reason or another, you can use USBView or USBTreeView to at least look at the USB descriptors.
n
First steps
n
Your computer has a USB bus, that is, a main entry point through which all USB devices that you connect will communicate. When you plug in a device, it will automatically provide basic information to identify itself. That information is hardcoded into the device and informs the computer what it is and what it can do. So the first thing we want is to list all devices currently connected to your computer through your USB bus (or busses, some computers have more than one):
n
import usb.corenimport usb.utilnnfor bus in usb.busses():n for device in bus.devices:n if device != None:n usbDevice = usb.core.find(idVendor=device.idVendor, n idProduct=device.idProduct)n print(usbDevice)
n
I connected a Kensington Wireless presenter and Laser pointer. I get the following USB Device Descriptor, which I commented for clarity:
n
DEVICE ID 047d:2012 on Bus 020 Address 001 =================n bLength : 0x12 (18 bytes) # The length in bytes of this descriptionn bDescriptorType : 0x1 Device # This is a USB Device Descriptorn bcdUSB : 0x200 USB 2.0 # What USB standard it complies ton bDeviceClass : 0x0 Specified at interface # A class of device (here, not known yet)n bDeviceSubClass : 0x0 # A subclass of device (here, not known yet)n bDeviceProtocol : 0x0 # A device protocol (here, not known yet)n bMaxPacketSize0 : 0x8 (8 bytes) # The size of packets that will be transmittedn idVendor : 0x047d # The USB Vendor ID. This is Kensington.n idProduct : 0x2012 # The USB Product ID. This pointer device.n bcdDevice : 0x6 Device 0.06 n iManufacturer : 0x1 Kensington # The text description of the manufacturern iProduct : 0x3 Wireless Presenter with Laser Pointer # Text name of productn iSerialNumber : 0x0 # The text description of the serial number n bNumConfigurations : 0x1 # The number of USB configurationsn
n
Let’s highlight what is important:
n
- n
- First: numbers written 0x12, 0x01 etc… are hexadecimal numbers, or numbers in base 16. Each “digit” can take one of 16 values: 0-9, then a to f representing 10 to 15. Therefore, 0x12 is 1 x 16 + 2 = 18 dec. Lowercase or uppercase are irrelevant. Up to 9, decimal and hexadecimal are the same.
- The vendor id is unique to the vendor of this device. This value is registered with the USB consortium for a small cost. In the present case, we can use this later to get “all devices from Kensington”.
- The product id is unique to a product, from Kensington. The vendor manages their product ids as they wish.
- The bNumConfigurations is the number of possible configurations this USB device can take. It will generally be 1 with scientific equipment, but it can be more than that. We will simply assume it is always 1 for the rest of the present discussion, this is not a vital part for us.
- Don’t worry about the letters (
b
,i
,bcd
) in front of the descriptors: they simply indicate without ambiguity how they are stored in the USB descriptor: ab
represents a byte value, ani
represents a 2-byte integer, and abcd
is also a 2-byte integer, but interpreted in decimal (binary-coded-decimal).bcdUSB
= 0x200 means 2.0.0. - You may be wondering how a string can be represented by an integer (say
iManufacturer
)? This is because it is not the string itself, but a string pointer to a list of strings at the end of the descriptor. PyUSB automatically fetches the corresponding string when showing show the descriptor.
n
n
n
n
n
n
n
Right after connecting, the device is in an “unconfigured mode”. Someone, somewhere, needs to take responsibility for this device and do something. Again, we need to separate general user devices (mouse, printers etc…) from scientific hardware. With user devices, the device class, subclass, vendor id, product id, and protocol are sufficient for the computer to determine whether or not it can manage it. If the computer has the driver for this device, it will “own” it and manage it. For instance, the printer is recognized and then the operating system can do what it needs to do with it (this is discussed below). However, scientific equipement will often appear as “General Device”, and the computer will likely not know what to do. This is when we come in.
n
Configuring the USB device
n
We need to set the device into one of its configurations. As described before, this is most likely just setting the device in its only possible configuration. So we get the USB device using the usb.core.find
function of PyUSB, which will just relay the request to libusb and return any device that matches the parameters we want. In our case, we specify the idVendor and the idProduct, so the only device that can match is the one we want.
n
import usb.corenimport usb.utilnndevice = usb.core.find(idVendor=0x047d, idProduct=0x2012) # For a Kensington pointer. Choose yours.nif device is None:ntraise IOError("Can't find device")nndevice.set_configuration() # Use the first configurationnconfiguration = device.get_active_configuration() # Then get a reference to itnprint(configuration)
n
This will print the following USB Configuration Descriptor, which I am also commenting:
n
CONFIGURATION 1: 100 mA =========================