This provides an API for interacting with the micro:bit over USB. It also includes a demo application to show how it works.
ubitwebusb.js
: The actual API (the only file needed for other applications)- Demo application (a web-based console that shows the different messages)
index.html
: HTML with in-line JavaScript for the application- View Live Version Only works in Chrome
style.css
- Requires
ubitwebusb.js
- Documentation files
- Upgrade Micro:bit to latest firmware
- Program the Micro:bit with one of the example programs that generates serial data
- Setup a Web Server & Open the project's page to run the sample application (
index.html
) (Live Example of it) (Can just open theindex.html
locally with current version)
This should only be needed one time for each micro:bit.
Upgrade the micro:bit firmware as describe at: Updating your micro:bit firmware
- Plug the Micro:bit into the computer
- Open https://makecode.microbit.org/_Pya288iq3eo2
- Select the Gear Menu in the upper right
- Select the
Pair Device
option - Select
Pair Device
- Select the Micro:bit device
- Download the code (Blue Download button at the bottom of the window)
- See what data looks like with Makecode's existing graphs via the
Console Simulator
orConsole Device
buttons in the simulator. - Unplug/re-plug Micro:bit (to Un-Pair device)
The micro:bit retains it's program until it is explicitly re-programmed or the firmware is upgraded. This programming process won't need to be repeated unless a different program is desired (which may be needed to demonstrate different the different ways to annotate graphs)
This is an alternative to the above. Either can be done, but there's no reason to do both.
The program below will send serial data and can be used for initial testing/debugging.
- Connect USB cable
- Open the MakeCode Editor
- Select JavaScript from the Blocks/JavaScript slider.
- Paste in the code above
serial.onDataReceived(serial.delimiters(Delimiters.NewLine), function () {
serial.writeLine("echo " + serial.readUntil(serial.delimiters(Delimiters.NewLine)))
})
basic.forever(function () {
serial.writeValue("x", Math.map(Math.randomRange(0, 100), 0, 100, -2.4, 18.2))
serial.writeValue("y data", Math.randomRange(0, 10))
serial.writeValue("graph2.a", Math.randomRange(-5, 10))
serial.writeValue("graph2.b data", Math.randomRange(-5, 10))
if (Math.randomRange(0, 10) == 5) {
serial.writeLine("x:\"Hi\"")
}
if (Math.randomRange(0, 50) == 5) {
serial.writeLine("console log")
}
basic.pause(500)
})
- Select the Gear Menu in the upper right
- Select the
Pair Device
option - Select
Pair Device
- Select the Micro:bit device
- Download the code (Blue Download button at the bottom of the window)
- See what data looks like with Makecode's existing graphs via the
Console Simulator
orConsole Device
buttons in the simulator. - Unplug/re-plug Micro:bit (to Un-Pair device)
- In browser go to file>open and browse to
index.html
or right-click onindex.html
and use file navigation to open in a browser.
- Install a local web server
- Python 3 / Python: Use
pip
orpip3
to install thehttp
package. (ex: Open a terminal window / command prompt and runpip install http
) - Run the
http.server
module in the directory containing the project:python -m http.server
orpython3 -m http.server
as appropriate.
- Python 3 / Python: Use
- Open browser to http://localhost:8000/ (Default page for Python Server)
- Make sure the micro:bit is connected via the USB cable
- Click on
Connect
- Select the micro:bit from the pop-up menu that appears
There are three main functions:
uBitConnectDevice(callback)
: Prompt user to connect to device and provide callback function for device events- See
uBitEventCallback
for callback format and argument descriptions.
- See
uBitSend(device)
: Send data (astring
) to a micro:bituBitDisconnect(device)
: Disconnect from the designatedmicro:bit
See index.html
for a complete example application.
Now uses DAP.js for DAPLink (serial) interactions and events.
Data may be shown in a combination of zero or more graphs that show a single series and zero or more graphs that show multiple series. There may also be "events" (descriptive items with a string)
The number of graphs and series will now be known in advance. It must be determined from the streaming data.
Format: NAME:NUMBER
NAME
is a string name of the graph (and the name of the series)NUMBER
is a numeric value to graph
Format: NAME:STRING
NAME
is a string name of the graphString
is a string for an event
Format: NAME.SERIES:NUMBER
NAME
is a string name of the graph (and the name of the series)SERIES
is a string name of the series for the dataNUMBER
is a numeric value to graph
Format: NAME.SERIES:STRING
NAME
is a string name of the graphSERIES
is a string name of the series for the dataString
is a string for an event
Any message that doesn't include a colon is a console message. Console messages should end with a newline (\n
), which will be sent implicitly with serial write line
(not serial write
). Ex: This is a message
sent via serial write line
.
The micro:bit console messages (serial write
blocks and serial.*
TypeScript commands) are directed over the USB interface. They actually go through ARM's CMSIS-DAP. DAP is described as:
CMSIS-DAP is a specification and a implementation of a Firmware that supports access to the CoreSight Debug Access Port (DAP).
DAP has several well defined commands as well as the ability to support custom vendor commands. Messages begin with a byte indicating the command type. The firmware in the micro:bit supports two custom messages that are used to configure the baud rate.
Current work uses DAPJS.
Get the DAPJS file:
mkdir temp
cd temp
npm install dapjs
cp node_modules/dapjs/dist/dap.umd.js ..
cp node_modules/dapjs/dist/dap.umd.js.map ..
cd ..
rm -Rf temp
jsdoc ubitwebusb.js -r jsdoc.md -d docs
Immediately after connection to micro:bit (this assumes the connected device is a micro:bit and all control transfers in/out go to endpoint 4, the CMSIS-DAP endpoint):
- Select device configuration 1
- Claim interface 4 (CMSIS-DAP)
- Control Transfer Out DAP Vendor Specific Command (
ID_DAP_Vendor2
or0x82
) to set the UART baud to 115,200bps- Bytes:
[[0x82, 0x00, 0xc2, 0x01, 0x00]
- Bytes:
This approach was based on the sequence sent from Makecode, but it doesn't appear that the majority of it is necessary.
- Select device configuration 1
- Claim interface 4 (CMSIS-DAP)
- Control Transfer Out
DAP_Connect
to default (connect the default device)- Bytes:
[2, 0]
- Bytes:
- Control Transfer Out
DAP_SWJ_Clock
to 10MHz- Bytes:
[0x11, 0x80, 0x96, 0x98, 0]
- Bytes:
- Control Transfer Out
DAP_SWD_Configure
to configure the software debug (1 clock turn around, no Wait/Fault phases)- Bytes:
[0x13, 0]]
- Bytes:
- Control Transfer Out DAP Vendor Specific Command (
ID_DAP_Vendor2
or0x82
) to set the UART baud to 115,200bps- Bytes:
[[0x82, 0x00, 0xc2, 0x01, 0x00]
- Bytes:
- Control Transfer Out DAP Vendor Specific Command (
ID_DAP_VEndor3
or0x83
) request UART data up to 64 bytes. - Control Transfer In of up to 64 bytes.
- 1st byte is repeat of command (
0x83
) - 2nd byte is length of string
- Remaining bytes (2..2+length) are utf-8 string
- 1st byte is repeat of command (
- Control Transfer Out DAP Vendor Specific Command (
ID_DAP_VEndor4
or0x84
) request UART data up to 64 bytes.- Bytes:
[0x84, string length, string[0], string[1], ..., string[length-1]]
- Bytes:
This can be done to identify all the messages being sent to a USB device from a WebUSB page, like MakeCode
- Open the page in question
- Open the JavaScript console (Inspect the page via developer tools; Right-click on page and select Insepct)
- Paste in the following code, which adds a debugging print message to all calls to most USB functions
var trackerLogOn = true;
function addTracker(methodName, object) {
var temp = object[methodName];
object[methodName] = function() {
if(trackerLogOn) {
console.log(methodName + ":");
console.dir(arguments);
console.log("----------------")
}
// this is a USBDevice object
return temp.apply(this, arguments)
}
}
addTracker("claimInterface",USBDevice.prototype)
addTracker("selectAlternateInterface", USBDevice.prototype)
addTracker("controlTransferIn", USBDevice.prototype)
addTracker("controlTransferOut", USBDevice.prototype)
addTracker("transferIn", USBDevice.prototype)
addTracker("transferOut", USBDevice.prototype)
addTracker("selectConfiguration", USBDevice.prototype)
addTracker("isochronousTransferIn", USBDevice.prototype)
addTracker("isochronousTransferOut", USBDevice.prototype)
- Use the page to trigger USB operations
- Enter
trackerLogOn = false
in the Console to stop collecting data, then scroll back and examine all messages/traffic.
Based on https://www.umpah.net/how-to-sniff-usb-traffic-reverse-engineer-usb-device-interactions/
- Enable virtual port for monitoring:
sudo ifconfig XHC20 up
- Open Wireshark for capture.
- Select
XHC20
device for capture - Filter based on device's location ID (get it from Apple Menu,
About this Mac...
,System Report
,USB
, select the device and look at the Location ID):usb.darwin.location_id == 0x14200000
- When done disable
XHC20
:sudo ifconfig XHC20 down
- USB Made Simple: Sections on Introduction, Data Flow and Protocol are helpful.
- Makecode/pxt's webusb.ts source
- 0: USBInterface USB Mass Storage
- interfaceClass: 8
- interfaceProtocol: 80
- interfaceSubclass: 6
- interfaceNumber: 0
- 1: USBInterface CDC Control
- interfaceClass: 2
- interfaceProtocol: 1
- interfaceSubclass: 2
- interfaceNumber: 1
- 2: USBInterface CDC - Data
- interfaceClass: 10
- interfaceProtocol: 0
- interfaceSubclass: 0
- interfaceNumber: 2
- 3: USBInterface
- interfaceNumber: 3
- 4: USBInterface CMSIS-DAP
- interfaceNumber: 4
- DAP_SWJ_CLOCK [17,128,150,152,0]
- DAP_Connect [2,0]
- DAP_TransferConfigure [4, 0, 80, 0, 0, 0]
- SWD Configure [19, 0]
- [18, 56, 255, 255, 255, 255, 255, 255, 255] // DAP_SWJ_Sequence
- [18, 16, 158, 231]
- [18, 8, 0]
- DAP_Transfer [5, ...]
- [130, 0, 194, 1, 0]// Set UART Config https://github.com/ARMmbed/DAPLink/blob/0711f11391de54b13dc8a628c80617ca5d25f070/source/daplink/cmsis-dap/DAP_vendor.c
- [0, 254] // DAP_Info / Get Packet Size
- [17, 128, 150, 152, 0] // DAP_SWJ_Clock
- [2, 0] // DAP_Connect Defaults
- [17, 128, 150, 152, 0] // DAP_SWJ_Clock
- [4, 0, 80, 0, 0, 0] // DAP_TransferConfigure
- [19, 0] // DAP_SWD_Configure default
- [18, 56, 255, 255, 255, 255, 255, 255, 255] // DAP_SWJ_Sequence
-
.then(() => device.selectConfiguration(1))
-
.then(() => device.claimInterface(5))
-
Transfer out 5, [5, 0, 4, 8, 0, 0, 0, 0, 1, 82, 0, 0, 35, 5, 240, 237, 0, 224, 13, 3, 0, 95, 160] ID_DAP_Transfer https://arm-software.github.io/CMSIS_5/DAP/html/group__DAP__Transfer.html index 0, transfer 4 8, 0, 0, 0, 0, 1, 82, 0, 0, 35, 5, 240, 237, 0, 224, 13, 3, 0, 95, 160 https://arm-software.github.io/CMSIS_5/DAP/html/group__DAP__Transfer.html
BYTE | BYTE *****| BYTE **********| BYTE *************| WORD *********|
0x05 | DAP Index | Transfer Count | Transfer Request | Transfer Data | 5, 0, 4,
8: 0, 0, 0, 0 //write A3 Register Address bit 3. ??? 1: 82, 0, 0, 35, write to access port 5: 240, 237, 0, 224 a2 register write to AP ??? 13:, 3, 0, 95, 160 a3 and a2
-
Transfer in 5, 64 ID_DAP_Vendor0 ???
-
Transfer out 5, [0,4]
- ID_DAP_Info / 0x04 = Get the CMSIS-DAP Protocol Version (string).
-
Transfer in 5, 64
-
Transfer out 5,[128] 0x80? 1.
-
Transfer in 5, 64
-
Transfer out 5,[130, 0, 194, 1, 0] 0x82
-
Transfer in 5, 64
-
Transfer out 5,[0, 254] // Get the maximum Packet Count (BYTE).
-
Transfer in 5, 64
-
Transfer out 5,[17, 128, 150, 152, 0] //MMM
-
Transfer in 5, 64
-
Transfer out 5,[2, 0]
-
Transfer in 5, 64
-
Transfer out 5,[17, 128, 150, 152, 0]
- ID_DAP_SWJ_Clock ... ???
-
Transfer in 5, 64
-
Transfer out 5,[4, 0, 80, 0, 0, 0]
- ID_DAP_TransferConfigure
- 0 idle cyclles
- 80, 0, wait retry
- 00 00 match retry
-
Transfer in 5, 64
-
Transfer out 5,[19, 0]
- DAP_SWD_Configure
-
Transfer in 5, 64
-
Transfer out 5,[18, 56, 255, 255, 255, 255, 255, 255, 255]
- ID_DAP_SWJ_Sequence
-
Transfer in 5, 64
-
Transfer out 5,[18, 16, 158, 231]
- ID_DAP_SWJ_Sequence
-
Transfer in 5, 64
-
Transfer out 5,[18, 56, 255, 255, 255, 255, 255, 255, 255]
-
Transfer in 5, 64
-
Transfer out 5,[18, 8, 0]
-
Transfer in 5, 64 Connected Transfer out 5, [5, 0, 1, 2] Transfer out 5, [5, 0, 4, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, 4, 0, 0, 0, 80, 6] Transfer out 5, [5, 0, 4, 4, 0, 15, 0, 80, 8, 0, 0, 0, 0, 8, 240, 0, 0, 0, 15] Transfer out 5, [5, 0, 4, 8, 0, 0, 0, 0, 1, 82, 0, 0, 35, 5, 0, 32, 0, 224, 15] Breakpoints messqge Transfer out 5, [5, 0, 4, 8, 0, 0, 0, 0, 1, 82, 0, 0, 35, 5, 0, 32, 0, 224, 13, 2, 0, 0, 0] Transfer out 5, [5, 0, 4, 8, 0, 0, 0, 0, 1, 82, 0, 0, 35, 5, 8, 32, 0, 224, 13, 0, 0, 0, 0] Transfer out 5, [] Transfer out 5, [] Transfer out 5, [] Transfer out 5, [] Transfer out 5, [] Transfer out 5, [] Transfer out 5, []
// product id 516
- Transfer out 5,[17, 128, 150, 152, 0]
- Transfer out 5,[2, 0]
- Transfer out 5,[19, 0]
- Transfer out 5,[130, 0, 194, 1, 0]
ok from here on
- [130, 0, 194, 1, 0] Connecting...
- [0,254]
- [17, 128, 150, 152, 0]
- [2,0]
- [17, 128, 150, 152, 0]
- [4, 0, 80, 0, 0, 0]
- [19, 0]
- [18, 56, 255, 255, 255, 255, 255, 255, 255]
- [18, 16, 158, 23]