问题
UPDATED CODE
I'm using this code to claim the use of the serial-2-USB device. It shows up and I can query the info about it, ie. "connected to USB2.0-Serial VID: 6790 PID: 29987" (CH34x from Qinheng). To be clear, I use the winUSB driver (downloaded with zadig-2.5.exe, Windows 10) and have uninstalled the original drivers.
I get the received data as dataview but when I do the decode it comes out gibberish. And I see that the array length pretty much corresponds to what I know my ESP8266 is posting over the serial port.
Example of decoded data: �(#���D"D�T�b�!A#7mP�R�N����#�m93aw9 ½�d-K��b��BF+3ѡ��kag1�R�#��#!!r����g�!d��a��谛oa��399�}��1D�#��'99�����9�����'99���'99����@@譛
I get the data as Uint8Arrays but even if I try to make my own parser the same gibberish is the end result. Do I need to care about USB stuff as in start bits, ack bits etc. etc.? All the example code out there just do a text decoder and that's it.
serial.js + index.html
//https://zadig.akeo.ie/ --- for windows, get supported driver "winusb"
//https://www.cypress.com/file/134171/download --- read up on the USB standard
//https://cscott.net/usb_dev/data/devclass/usbcdc11.pdf --- more here
//http://www.linux-usb.org/usb.ids --- list of ids
//https://developer.mozilla.org/en-US/docs/Web/API/USBDevice --- more info
// host --- data pipe ---> vendor/device TX (host) transmitting data
// host <-- control pipe --> vendor/device Endpoint 0 (this is the one we us to control data up (from device to host) and data down (from host to device)
// host <-- data pipe ---- vendor/device RX (host) receiving data
//It's possible to send interrupt, bulk ant isochronus data packages.... this flasher only use bulk (and control transfers on the control pipe)
//Typically the usb cable work/look as follows:
//red 5V, black 0V/GND, green TX(host), white RX(host)
//USB standard transaction:
// [TOKEN][DATA][HANDSHAKE]
//... the data package ....
//1: Packet ID (PID): 8 bits (4 type bits + 4 error check bits) this is where we till if the transfer is "in / out / setup / SOF (start of frame)
//2: Optional device address: 7 bits (max 127 devices in the bus)
//3: Optional endpoint address: 4 bits (max 16 endpoints in a device)
//4: Optional payload data (0 ... 1023 bytes)
//5: Optional CRC (cyclic redundancy checksum)
//this flasher uses 8-N-1 for the serial parameters (8 data bits, No parity and 1 stop bit)
//when we control the "device" we control the USB chip directly, when we control the "interface" we control the serial functionality (class: 0xFF, vendor specific driver)
window.addEventListener("load", initiate, false);
//The different hardware we support + their specific data/configs
const table = {
0x0403: {"FTDI": {
0x6001: "FT232R",
0x6010: "FT2232H",
0x6011: "FT4232H",
0x6014: "FT232H",
0x6015: "FT231X", // same ID for FT230X, FT231X, FT234XD
}},
0x1a86: {"Quinheng": {
0x7523: "CH340",
0x5523: "CH341A",
}},
0x10c4: {"Silicon Labs": {
0xea60: "CP210x", // same ID for CP2101, CP2103, CP2104, CP2109
0xea70: "CP2105",
0xea71: "CP2108",
}},
0x067b: {"Prolific": {
0x2303: "PL2303"
}}
}
const config = {
"DEBUG" : true,
"DEFAULT_BAUD_RATE" : 115200,
//CH34x --> https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c <-- we have used the linux driver and made into a webUSB driver
"CH340": {
"BAUD_RATES" : [600,1200,2400,4800,9600,14400,19200,38400,57600,76800,115200,230400], // highest is 300 0000 limited by the BAUD_RATE_MAX_BPS
"REQUEST_READ_VERSION" : 0x5F,
"REQUEST_READ_REGISTRY" : 0x95,
"REQUEST_WRITE_REGISTRY" : 0x9A,
"REQUEST_SERIAL_INITIATION" : 0xA1,
"REG_DIVISOR" : 0x13,
"REG_PRESCALER" : 0x12,
"REG_LCR_1" : 0x18,
"REG_LCR_2" : 0x25,
"REG_MODEM_CTRL" : 0xA4,
"REG_MODEM_VALUE_OFF" : 0xFF,
"REG_MODEM_VALUE_ON" : 0xDF,
"REG_MODEM_VALUE_CALL" : 0x9F, // what does it do??
//"BAUD_RATE_CHIP_CLOCK_FREQ" : 48000000, // which one is it? 12MHz is the official value for the 340...
"BAUD_RATE_CHIP_CLOCK_FREQ" : 12000000,
"BAUD_RATE_MAX_DIVISOR" : function (ps, fact) {
return (1 << (12 - 3 * (ps) - (fact)))
},
"BAUD_RATE_MIN" : function (ps) {
return (config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ / (config.CH340.BAUD_RATE_MAX_DIVISOR(ps, 1) * 512))
},
"BAUD_RATE_MIN_BPS" : function () { // 47 bps
return Math.ceil((config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ + (config.CH340.BAUD_RATE_MAX_DIVISOR(0, 0) * 256) - 1) / (config.CH340.BAUD_RATE_MAX_DIVISOR(0, 0) * 256)); //Linux DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
},
"BAUD_RATE_MAX_BPS" : function () { // 3000000 bps
return Math.floor(config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ / (config.CH340.BAUD_RATE_MAX_DIVISOR(3, 0) * 2));
},
"QUIRK_LIMITED_PRESCALER" : 0b0, //binary 0
"QUIRK_SIMULATE_BREAK" : 0b1, //binary 1
"QUIRKS" : 0,
"LCR_ENABLE_RX" : 0x80,
"LCR_ENABLE_TX" : 0x40,
"LCR_DATA_BITS_8N1" : 0x03,
"LCR" : function () {
return config.CH340.LCR_ENABLE_RX | config.CH340.LCR_ENABLE_TX | config.CH340.LCR_DATA_BITS_8N1;
},
"FLAG_DTR" : false,
"FLAG_RTS" : false,
"REG_MCR_DTR" : 0x20,
"REG_MCR_RTS" : 0x40,
"MCR" : function () {
return (config.CH340.FLAG_RTS ? config.CH340.REG_MCR_RTS : 0) | (config.CH340.FLAG_DTR ? config.CH340.REG_MCR_DTR : 0); // if false, set to 0, else set to value of the REG_MCR_<...>
},
}
}
const serial = {};
let device = {};
let port;
(function() {
'use strict';
serial.getPorts = function() {
return navigator.usb.getDevices().then(devices => {
return devices.map(device => new serial.Port(device));
});
};
serial.requestPort = function() {
let supportedHardware = [];
//This one create the filter of hardware based on the hardware table
Object.keys(table).map(vendorId => {
Object.keys(table[vendorId]).map(vendorName => {
Object.keys(table[vendorId][vendorName]).map(productId => {
supportedHardware.push({
"vendorId": vendorId,
"productId": productId
})
})
})});
//device contains the "device descriptor" (see USB standard), add as a new device to be able to control
return navigator.usb.requestDevice({ 'filters': supportedHardware }).then(
device => new serial.Port(device)
);
}
//set it to the active device..
serial.Port = function(device) {
this.device_ = device;
};
//here's the config + read loop is taking place....
serial.Port.prototype.connect = function() {
//this is the read loop on whatever port is currently used... it will repeat itself
let readLoop = () => {
this.device_.transferIn(this.endpointIn_, 64).then(result => {
this.onReceive(result.data);
readLoop();
}, error => {
this.onReceiveError(error);
});
};
return this.device_.open()
.then(() => {
//first we get some GUI stuff populated, we use "device" for that... serial and port are used for the configuration elsewhere
device.hostName = port.device_.productName;
device.vendorName = Object.keys(table[port.device_.vendorId])[0];
device.chip = table[port.device_.vendorId][device.vendorName][port.device_.productId];
device.serialNumber = port.device_.serialNumber;
device.manufacturerName = port.device_.manufacturerName;
//1: we set an configuration (configuration descriptor in the USB standard)
if (this.device_.configuration === null) {
return this.device_.selectConfiguration(1);
}
})
.then(() => {
//2: we set what endpoints for data we will use, we use only "bulk" transfer and thus we parse their addresses
let configInterfaces = this.device_.configuration.interfaces;
configInterfaces.forEach(element => {
element.alternates.forEach(elementalt => {
if (elementalt.interfaceClass === 0xff) {
this.interfaceNumber_ = element.interfaceNumber;
elementalt.endpoints.forEach(elementendpoint => {
//This part here get the bulk in and out endpoints programmatically
if (elementendpoint.direction === "out" && elementendpoint.type === "bulk") {
this.endpointOut_ = elementendpoint.endpointNumber;
this.endpointOutPacketSize_ = elementendpoint.packetSize;
}
if (elementendpoint.direction === "in" && elementendpoint.type === "bulk") {
this.endpointIn_ = elementendpoint.endpointNumber;
this.endpointInPacketSize_ = elementendpoint.packetSize;
}
})
}
})
})
})
//3: we claim this interface and select the alternative interface
.then(() => this.device_.claimInterface(this.interfaceNumber_))
.then(() => this.device_.selectAlternateInterface(this.interfaceNumber_, 0))
//4: we configure in and out transmissions, based on detected hardware
.then(() => serial[device.chip](this))
//5: we start the loop
.then(() => {
//console.log(this);
readLoop();
})
};
//upon disconnect, what to do
serial.Port.prototype.disconnect = async function() {
await serial[device.chip](this).DISCONNECT;
};
//send data, what to do
serial.Port.prototype.send = function(data) {
return this.device_.transferOut(this.endpointOut_, data);
};
serial.controlledTransfer = async function (object, direction, type, recipient, request, value = 0, data = new DataView(new ArrayBuffer(0)), index = object.interfaceNumber_) {
direction = direction.charAt(0).toUpperCase() + direction.slice(1);
type = type.toLowerCase();
recipient = recipient.toLowerCase();
if (data.byteLength === 0 && direction === "In") {
// we set how many bits we want back for an "in"
// so set data = 0....N in the call otherwise it will default to 0
data = 0;
}
return await object.device_["controlTransfer" + direction]({
'requestType': type,
'recipient': recipient,
'request': request,
'value': value,
'index': index
}, data)
.then(res => {
if (config.DEBUG) {
//debugger; // remove comment for extra debugging tools
console.log(res);
}
if (res.status !== "ok") {
let errorRequest = `
controlTransfer` + direction + `
'requestType': ` + type + `,
'recipient': ` + recipient + `,
'request': 0x` + request.toString(16) + `,
'value': 0x` + value.toString(16) + `,
'index': 0x` + index.toString(16) + `
}`;
console.warn("error!", errorRequest, data) // add more here
}
if (res.data !== undefined && res.data.buffer !== undefined) {
return res.data.buffer;
}
return null;
});
};
// you can really use any numerical value since JS treat them the same:
// dec = 15 // dec will be set to 15
// bin = 0b1111; // bin will be set to 15
// oct = 0o17; // oct will be set to 15
// oxx = 017; // oxx will be set to 15
// hex = 0xF; // hex will be set to 15
// note: bB oO xX are all valid
serial.hexToDataView = function (number) {
if (number === 0) {
let array = new Uint8Array([0]);
return new DataView(array.buffer)
}
let hexString = number.toString(16);
// split the string into pairs of octets
let pairs = hexString.match(/[\dA-F]{2}/gi);
// convert the octets to integers
let integers = pairs.map(function(s) {
return parseInt(s, 16);
});
let array = new Uint8Array(integers);
return new DataView(array.buffer);
}
// you can give this method a string like "00 AA F2 01 23" and it will turn it into a DataView for the webUSB API transfer data
serial.hexStringArrayToDataView = function (hexString) {
// remove the leading 0x (if any)
hexString = hexString.replace(/^0x/, '');
// split the string into pairs of octets
let pairs = hexString.split(/ /);
// convert the octets to integers
let integers = pairs.map(function(s) {
return parseInt(s, 16);
});
let array = new Uint8Array(integers);
return new DataView(array.buffer);
}
// these are the hardware specific initialization procedures...
serial["CH340"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
/*
direction request value
in 0xA1 0xC39C -
in 0x9A 0xF2C -
in 0xA4 0xDF - modem on? 0xFF modem off?
in 0xA4 0x9F - modem?
in 0x95 0x706 - got 2 bytes
in 0x9A 0x2727 - LCR wrong??
in 0x9A 0x1312 - baud rate seems ok
in 0x95 0x706 - got 2 bytes
in 0x9A 0x2727 -
*/
/* await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x706); //test
return;
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_SERIAL_INITIATION, 0xC39C); // test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, 0xF2C); // test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_ON); // test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_CALL); // test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x706); //test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, 0x2727); //test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, 0x1312); //test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x706); //test
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, 0x2727); //test
*/
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_VERSION); // we expect to get a OK with the response of [48, 00] 0x30 0x00 or 0x27 0x00.... you can look at the response by adding a "let r = await serial.controlledTrans....." and then console log the "r"
//return; // if I uncomment this and thus not gonna continue the script I get gibbrish.... but as far as I can tell I have followed the linux initialization ?
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_SERIAL_INITIATION);
await serial["CH340"].setBaudRate(obj, baudRate);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.MCR()); // handshake
// now what? all the control transfers came back "ok"?
}
serial["CH340"].setBaudRate = async function (obj, baudRate) {
let data = serial["CH340"].getDivisor(baudRate);
// CH34x buffers data until a full endpoint size packet (32 bytes) has been reached unless bit 7 is set
data |= (1 << 7); // data |= (1 << 6); //seems correct for the 7th bit??
data = serial.hexToDataView(data);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, (config.CH340.REG_DIVISOR << 8 | config.CH340.REG_PRESCALER), data);
data = serial.hexToDataView(config.CH340.LCR());
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, (config.CH340.REG_LCR_2 << 8 | config.CH340.REG_LCR_1), data);
}
serial["CH340"].getDivisor = function (baudRate) {
let forceFactor0 = false;
// make sure our baud rate is with the min max
let baudRateChecked = Math.min(Math.max(parseInt(baudRate), config.CH340.BAUD_RATE_MIN_BPS()), config.CH340.BAUD_RATE_MAX_BPS());
// start with highest possible base clock (factor = 1) that will give a divisor strictly less than 512.
let factor = 1;
let ps = 3;
for (ps; ps >= 0; ps--) {
if (baudRateChecked > config.CH340.BAUD_RATE_MIN(ps)) {
break;
}
}
// determine corresponding divisor, rounding down
let clockDivisor = Math.floor(config.CH340.BAUD_RATE_MAX_DIVISOR(ps, factor));
let divisor = config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ / (clockDivisor * baudRateChecked);
// some devices require a lower base clock if ps < 3
if (ps < 3 && (config.CH340.QUIRKS & config.CH340.QUIRK_LIMITED_PRESCALER)) {
forceFactor0 = true;
}
// if we force a factor = 0 or have divisors outside range, split the base clock divisor by 2 and make factor=0
if (divisor < 9 || divisor > 255 || forceFactor0) {
divisor /= 2;
clockDivisor *= 2;
factor = 0;
}
divisor = Math.ceil(divisor);
// pick next divisor if resulting rate is closer to the requested one, scale up (16x) to avoid rounding errors on low rates.
let compare1 = 16 * config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ / (clockDivisor * divisor) - 16 * baudRateChecked;
let compare2 = 16 * baudRateChecked - 16 * (config.CH340.BAUD_RATE_CHIP_CLOCK_FREQ / (clockDivisor * (divisor + 1)));
if (compare1 >= compare2) {
divisor++;
}
// prefer lower base clock (factor = 0) if even divisor "divisor % 2"... this makes the receiver more tolerant to errors
if (factor === 1 && (divisor % 2 === 0) ) {
divisor /= 2;
factor = 0;
}
return (0x100 - divisor) << 8 | factor << 2 | ps;
}
serial["CH340"].DISCONNECT = async function (obj) {
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_OFF);
}
serial["CP210x"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["CP2105"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["CP2108"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["PL2303"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT2232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT4232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT231X"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
})();
//GUI function "connect"
function connect() {
port.connect().then(() => {
document.getElementById('editor').value = "connected to: " + device.hostName + "\nvendor name: " + device.vendorName + "\nchip type: " + device.chip;
port.onReceive = data => {
console.log(data);
document.getElementById('output').value += new TextDecoder().decode(data);
}
port.onReceiveError = error => {
//console.error(error);
port.disconnect();
};
});
}
//GUI function "disconnect"
function disconnect() {
port.disconnect();
}
//GUI function "send"
function send(string) {
console.log("sending to serial:" + string.length);
if (string.length === 0)
return;
console.log("sending to serial: [" + string +"]\n");
let data = new TextEncoder('utf-8').encode(string);
console.log(data);
if (port) {
port.send(data);
}
}
//the init function which we have an event listener connected to
function initiate(){
serial.getPorts()
.then(ports => {
//these are devices already paired, let's try the first one...
if (ports.length > 0) {
port = ports[0];
connect();
}
});
document.querySelector("#connect").onclick = async function () {
await serial.requestPort().then(selectedPort => {
if (port === undefined || port.device_ !== selectedPort.device_) {
port = selectedPort;
connect();
} else {
// port already selected...
}
});
}
document.querySelector("#disconnect").onclick = function() {
disconnect()
}
document.querySelector("#submit").onclick = () => {
let source = document.querySelector("#editor").value;
send(source);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="serial.js"></script>
<title>Grovkillen test webusb</title>
</head>
<body>
<button id="connect">Connect</button>
<button id="disconnect">disConnect</button>
<label for="editor">TX</label><textarea id="editor">Flash Easy</textarea>
<button id="submit">Send</button>
<label for="output">RX</label><textarea id="output"></textarea>
</body>
POC running Chrome on Android 10
POC running Chrome on Windows 10
As a test, I used the great piece of software called Serial USB Terminal for Android. And that app could connect just fine since I could set the baud rate etc. within it... but look at what happened when I had divided screens and closed the connection on the terminal app and opened it on my web app... Clearly, the fault lies within how Chrome is initiating the connection.
回答1:
This isn't a flaw in WebUSB but with the script you are running. The serial.js script included in the Arduino examples repository is designed to work with Arduino devices where setting the baud rate in unnecessary because the port is virtual. In order to set the baud rate on a USB to serial adapter you need to send the SET_LINE_CODING control transfer. This should go before the SET_CONTROL_LINE_STATE command in your existing code. There is documentation for the structure of the control transfer here:
https://github.com/MarkDing/lufa-efm32#311-set-line-coding
回答2:
Thanks to this library I managed to get my CH340 to understand webUSB :)
https://github.com/felHR85/UsbSerial/blob/master/usbserial/src/main/java/com/felhr/usbserial/CH34xSerialDevice.java
Javascript below and it work!
window.addEventListener("load", initiate, false);
//The different hardware we support + their specific data/configs
const table = {
0x0403: {"FTDI": {
0x6001: "FT232R",
0x6010: "FT2232H",
0x6011: "FT4232H",
0x6014: "FT232H",
0x6015: "FT231X", // same ID for FT230X, FT231X, FT234XD
}},
0x1a86: {"Quinheng": {
0x7523: "CH340",
0x5523: "CH341A",
}},
0x10c4: {"Silicon Labs": {
0xea60: "CP210x", // same ID for CP2101, CP2103, CP2104, CP2109
0xea70: "CP2105",
0xea71: "CP2108",
}},
0x067b: {"Prolific": {
0x2303: "PL2303"
}}
}
const config = {
"DEBUG" : true,
"DEFAULT_BAUD_RATE" : 115200,
"BAUD_RATES" : [600,1200,2400,4800,9600,14400,19200,38400,57600,115200,230400], // highest is 300 0000 limited by the BAUD_RATE_MAX_BPS
//CH34x --> https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c <-- we have used the linux driver and made into a webUSB driver
// plus --> https://github.com/felHR85/UsbSerial/tree/master/usbserial/src/main/java/com/felhr/usbserial <--
"CH340": {
"REQUEST_READ_VERSION": 0x5F,
"REQUEST_READ_REGISTRY": 0x95,
"REQUEST_WRITE_REGISTRY": 0x9A,
"REQUEST_SERIAL_INITIATION": 0xA1,
"REG_SERIAL": 0xC29C,
"REG_MODEM_CTRL": 0xA4,
"REG_MODEM_VALUE_OFF": 0xFF,
"REG_MODEM_VALUE_ON": 0xDF,
"REG_MODEM_VALUE_CALL": 0x9F,
"REG_BAUD_FACTOR": 0x1312,
"REG_BAUD_OFFSET": 0x0F2C,
"REG_BAUD_LOW": 0x2518,
"REG_CONTROL_STATUS": 0x2727,
"BAUD_RATE": {
600: {"FACTOR": 0x6481, "OFFSET": 0x76},
1200: {"FACTOR": 0xB281, "OFFSET": 0x3B},
2400: {"FACTOR": 0xD981, "OFFSET": 0x1E},
4800: {"FACTOR": 0x6482, "OFFSET": 0x0F},
9600: {"FACTOR": 0xB282, "OFFSET": 0x08},
14400: {"FACTOR": 0xd980, "OFFSET": 0xEB},
19200: {"FACTOR": 0xD982, "OFFSET": 0x07},
38400: {"FACTOR": 0x6483, "OFFSET": null},
57600: {"FACTOR": 0x9883, "OFFSET": null},
115200: {"FACTOR": 0xCC83, "OFFSET": null},
230500: {"FACTOR": 0xE683, "OFFSET": null},
}
}
}
const serial = {};
let device = {};
let port;
(function() {
'use strict';
serial.getPorts = function() {
return navigator.usb.getDevices().then(devices => {
return devices.map(device => new serial.Port(device));
});
};
serial.requestPort = function() {
let supportedHardware = [];
//This one create the filter of hardware based on the hardware table
Object.keys(table).map(vendorId => {
Object.keys(table[vendorId]).map(vendorName => {
Object.keys(table[vendorId][vendorName]).map(productId => {
supportedHardware.push({
"vendorId": vendorId,
"productId": productId
})
})
})});
//device contains the "device descriptor" (see USB standard), add as a new device to be able to control
return navigator.usb.requestDevice({ 'filters': supportedHardware }).then(
device => new serial.Port(device)
);
}
//set it to the active device..
serial.Port = function(device) {
this.device_ = device;
};
//here's the config + read loop is taking place....
serial.Port.prototype.connect = function() {
//this is the read loop on whatever port is currently used... it will repeat itself
let readLoop = () => {
this.device_.transferIn(this.endpointIn_, 64).then(result => {
this.onReceive(result.data);
readLoop();
}, error => {
this.onReceiveError(error);
});
};
return this.device_.open()
.then(() => {
//first we get some GUI stuff populated, we use "device" for that... serial and port are used for the configuration elsewhere
device.hostName = port.device_.productName;
device.vendorName = Object.keys(table[port.device_.vendorId])[0];
device.chip = table[port.device_.vendorId][device.vendorName][port.device_.productId];
device.serialNumber = port.device_.serialNumber;
device.manufacturerName = port.device_.manufacturerName;
//1: we set an configuration (configuration descriptor in the USB standard)
if (this.device_.configuration === null) {
return this.device_.selectConfiguration(1);
}
})
.then(() => {
//2: we set what endpoints for data we will use, we use only "bulk" transfer and thus we parse their addresses
let configInterfaces = this.device_.configuration.interfaces;
configInterfaces.forEach(element => {
element.alternates.forEach(elementalt => {
if (elementalt.interfaceClass === 0xff) {
this.interfaceNumber_ = element.interfaceNumber;
elementalt.endpoints.forEach(elementendpoint => {
//This part here get the bulk in and out endpoints programmatically
if (elementendpoint.direction === "out" && elementendpoint.type === "bulk") {
this.endpointOut_ = elementendpoint.endpointNumber;
this.endpointOutPacketSize_ = elementendpoint.packetSize;
}
if (elementendpoint.direction === "in" && elementendpoint.type === "bulk") {
this.endpointIn_ = elementendpoint.endpointNumber;
this.endpointInPacketSize_ = elementendpoint.packetSize;
}
})
}
})
})
})
//3: we claim this interface and select the alternative interface
.then(() => this.device_.claimInterface(this.interfaceNumber_))
.then(() => this.device_.selectAlternateInterface(this.interfaceNumber_, 0))
//4: we configure in and out transmissions, based on detected hardware
.then(() => serial[device.chip](this))
//5: we start the loop
.then(() => {
//console.log(this);
readLoop();
})
};
//upon disconnect, what to do
serial.Port.prototype.disconnect = async function() {
await serial[device.chip](this).DISCONNECT;
};
//send data, what to do
serial.Port.prototype.send = function(data) {
return this.device_.transferOut(this.endpointOut_, data);
};
serial.controlledTransfer = async function (object, direction, type, recipient, request, value = 0, data = new DataView(new ArrayBuffer(0)), index = object.interfaceNumber_) {
direction = direction.charAt(0).toUpperCase() + direction.slice(1);
type = type.toLowerCase();
recipient = recipient.toLowerCase();
if (data.byteLength === 0 && direction === "In") {
// we set how many bits we want back for an "in"
// so set data = 0....N in the call otherwise it will default to 0
data = 0;
}
return await object.device_["controlTransfer" + direction]({
'requestType': type,
'recipient': recipient,
'request': request,
'value': value,
'index': index
}, data)
.then(res => {
if (config.DEBUG) {
//debugger; // remove comment for extra debugging tools
console.log(res);
}
if (res.status !== "ok") {
let errorRequest = `
controlTransfer` + direction + `
'requestType': ` + type + `,
'recipient': ` + recipient + `,
'request': 0x` + request.toString(16) + `,
'value': 0x` + value.toString(16) + `,
'index': 0x` + index.toString(16) + `
}`;
console.warn("error!", errorRequest, data) // add more here
}
if (res.data !== undefined && res.data.buffer !== undefined) {
return res.data.buffer;
}
return null;
});
};
// you can really use any numerical value since JS treat them the same:
// dec = 15 // dec will be set to 15
// bin = 0b1111; // bin will be set to 15
// oct = 0o17; // oct will be set to 15
// oxx = 017; // oxx will be set to 15
// hex = 0xF; // hex will be set to 15
// note: bB oO xX are all valid
serial.hexToDataView = function (number) {
if (number === 0) {
let array = new Uint8Array([0]);
return new DataView(array.buffer)
}
let hexString = number.toString(16);
// split the string into pairs of octets
let pairs = hexString.match(/[\dA-F]{2}/gi);
// convert the octets to integers
let integers = pairs.map(function(s) {
return parseInt(s, 16);
});
let array = new Uint8Array(integers);
return new DataView(array.buffer);
}
// you can give this method a string like "00 AA F2 01 23" or "0x00 0xAA 0xF2 0x01 0x23" and it will turn it into a DataView for the webUSB API transfer data
serial.hexStringArrayToDataView = function (hexString) {
// remove the leading 0x (if any)
hexString = hexString.replace(/^0x/, '');
// split the string into pairs of octets
let pairs = hexString.split(/ /);
// convert the octets to integers
let integers = pairs.map(function(s) {
return parseInt(s, 16);
});
let array = new Uint8Array(integers);
return new DataView(array.buffer);
}
serial.arrayBufferToHex = function (arrayBuffer) {
let hex = "0x0" + Array.prototype.map.call(new Uint8Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('');
return parseInt(hex);
}
// these are the hardware specific initialization procedures...
serial["CH340"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
let data = serial.hexToDataView(0); // null data
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_SERIAL_INITIATION, config.CH340.REG_SERIAL, data, 0xB2B9) // first request...
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_ON);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_CALL);
let r = await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x0706, 2);
r = serial.arrayBufferToHex(r);
if (r < 0) {
// we have an error
return;
}
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_FACTOR, data, 0xB282);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_OFFSET, data, 0x0008);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_LOW, data, 0x00C3);
r = await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REQUEST_READ_REGISTRY, 0x0706, 2);
r = serial.arrayBufferToHex(r);
if (r < 0) {
// we have an error
return;
}
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
await serial["CH340"].setBaudRate(obj, baudRate);
// now what? all the control transfers came back "ok"?
}
serial["CH340"].setBaudRate = async function (obj, baudRate) {
let data = serial.hexToDataView(0);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_FACTOR, data, config.CH340.BAUD_RATE[baudRate].FACTOR);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_BAUD_OFFSET, data, config.CH340.BAUD_RATE[baudRate].OFFSET);
await serial.controlledTransfer(obj,"out", "vendor", "device", config.CH340.REQUEST_WRITE_REGISTRY, config.CH340.REG_CONTROL_STATUS, data);
}
serial["CH340"].DISCONNECT = async function (obj) {
await serial.controlledTransfer(obj,"in", "vendor", "device", config.CH340.REG_MODEM_CTRL, config.CH340.REG_MODEM_VALUE_OFF);
}
serial["CP210x"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["CP2105"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["CP2108"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["PL2303"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT2232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT4232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT232H"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
serial["FT231X"] = async function (obj, baudRate = config.DEFAULT_BAUD_RATE) {
}
})();
//GUI function "connect"
function connect() {
port.connect().then(() => {
document.getElementById('editor').value = "connected to: " + device.hostName + "\nvendor name: " + device.vendorName + "\nchip type: " + device.chip;
port.onReceive = data => {
console.log(data);
document.getElementById('output').value += new TextDecoder().decode(data);
}
port.onReceiveError = error => {
//console.error(error);
port.disconnect();
};
});
}
//GUI function "disconnect"
function disconnect() {
port.disconnect();
}
//GUI function "send"
function send(string) {
console.log("sending to serial:" + string.length);
if (string.length === 0)
return;
console.log("sending to serial: [" + string +"]\n");
let data = new TextEncoder('utf-8').encode(string);
console.log(data);
if (port) {
port.send(data);
}
}
//the init function which we have an event listener connected to
function initiate(){
serial.getPorts()
.then(ports => {
//these are devices already paired, let's try the first one...
if (ports.length > 0) {
port = ports[0];
connect();
}
});
document.querySelector("#connect").onclick = async function () {
await serial.requestPort().then(selectedPort => {
if (port === undefined || port.device_ !== selectedPort.device_) {
port = selectedPort;
connect();
} else {
// port already selected...
}
});
}
document.querySelector("#disconnect").onclick = function() {
disconnect()
}
document.querySelector("#submit").onclick = () => {
let source = document.querySelector("#editor").value;
send(source);
}
}
Please observe that I have yet to implement the parity and stop bits etc.
来源:https://stackoverflow.com/questions/64929987/webusb-api-working-but-the-data-received-arent-decoded-properly