const SS = D10;
const USB_NAK_LIMIT = 100;
const USB_RETRY_LIMIT = 3;
const STATE = {
DETACHED: 1,
ATTACHED: 2,
};
const REGISTERS = {
RCVFIFO: 0x08,
SNDFIFO: 0x10,
SUDFIFO: 0x20,
RCVBC: 0x30,
SNDBC: 0x38,
USB_CONTROL: 0x78,
CPU_CONTROL: 0x80,
PIN_CONTROL: 0x88,
USB_IRQ: 0x68,
IRQ: 0xc8,
MODE: 0xd8,
HIEN: 0xd0,
PERADDR: 0xe0,
HCTL: 0xe8,
HXFR: 0xf0,
HRSL: 0xf8,
};
const COMMANDS = {
CHIP_RESET: 0x20,
};
const OPTIONS = {
FULL_DUPLEX: 0x10,
LEVEL_INTERRUPT: 0x08,
};
const REQUEST_TYPE = {
vendor: 0x40,
standard: 0x00,
class: 0xa1
};
const TOKENS = {
SETUP: 0x10,
IN: 0x00,
OUT: 0x20,
INHS: 0x80,
OUTHS: 0xA0,
ISOIN: 0x40,
ISOOUT: 0x60
};
const HCTL = {
BUS_RESET: 0x01,
FRMRST: 0x02,
SAMPLE_BUS: 0x04,
SIGRSM: 0x08,
RCVTOG0: 0x10,
RCVTOG1: 0x20,
SNDTOG0: 0x40,
SNDTOG1: 0x80
};
const HRSL = {
SUCCESS: 0x00,
BUSY: 0x01,
BADREQ: 0x02,
UNDEF: 0x03,
NAK: 0x04,
STALL: 0x05,
TOGERR: 0x06,
WRONGPID: 0x07,
BADBC: 0x08,
PIDERR: 0x09,
PKTERR: 0x0A,
CRCERR: 0x0B,
KERR: 0x0C,
JERR: 0x0D,
TIMEOUT: 0x0E,
BABBLE: 0x0F,
};
const IRQS = {
BUS_EVENT: 0x01,
RWU: 0x02,
RECEIVED_DATA_AVAILABLE: 0x04,
SEND_BUFFER_AVAILABLE: 0x08,
SUS_DONE: 0x10,
CONNECTION_DETECT: 0x20,
FRAME: 0x40,
HXFR_DONE: 0x80
};
let currentAddress = 0;
const storeShort = function(v, b, st) {
b[st] = v & 0xFF;
b[st + 1] = (v >> 8) & 0xFF;
};
const extractShort = function(b, st) {
return ((b[st+1] << 8) + b[st]);
};
const bytes2hex = function(bytes, noGaps) {
var message = '';
for(var i in bytes) {
var hex = bytes[i].toString(16).toUpperCase();
if(hex.length === 1) {
message += '0';
}
message += hex;
if(!noGaps) {
message += ' ';
}
}
return message;
};
class USB {
constructor() {
Terminal.println('Setting up SPI');
SPI1.setup({mosi:D11, miso:D12, sck:D13, order:'msb', baud: 26000000});
Terminal.println('Revision ' + this.readRegister(0x90));
// TODO: get endpoints from config descriptor
this.endpoints = {
0 : { // control endpoint
receiveToggle: HCTL.RCVTOG1,
sendToggle: HCTL.SNDTOG0
},
1 : {
receiveToggle: HCTL.RCVTOG0,
sendToggle: HCTL.SNDTOG0
}
};
}
setRegister(register, command) {
SPI1.write([register | 0x02, command], SS);
}
sendBytes(register, bytes) {
SPI1.write(E.toUint8Array(register | 0x02, bytes), SS);
}
readBytes(register, length) {
let bytesToRead = new Uint8Array(length);
const result = SPI1.send([register, bytesToRead], SS);
console.log('Got:', bytes2hex(result));
return result.slice(1);
}
readRegister(register) {
const result = SPI1.send([register, 0x00], SS);
return result[1];
}
init(cb) {
console.log('Setting full-duplex');
this.setRegister(REGISTERS.PIN_CONTROL, OPTIONS.FULL_DUPLEX | OPTIONS.LEVEL_INTERRUPT);
if (this.reset() === 0)
console.log('Oscillator did not settle in time');
console.log('Setting host mode and pulldown resistors');
this.setRegister(REGISTERS.MODE, 0xc1); // set host mode and pulldown resistors
this.setRegister(REGISTERS.HIEN, IRQS.CONNECTION_DETECT | IRQS.FRAME);
// check if device is connected
this.setRegister(REGISTERS.HCTL, HCTL.SAMPLE_BUS);
let sampled = 0;
while(!sampled) {
// wait for USB sample to finish
const res = this.readRegister(REGISTERS.HCTL);
sampled = res & HCTL.SAMPLE_BUS;
}
this.busprobe();
// clear interrupt for connection detect
this.setRegister(REGISTERS.IRQ, IRQS.CONNECTION_DETECT);
this.setRegister(REGISTERS.CPU_CONTROL, 0x01); // enable interrupt pin
// wait for device to settle and then reset
setTimeout( () => {
console.log('Resetting device bus');
return this.resetDevice(cb);
}, 200);
}
resetDevice(cb) {
this.setRegister(REGISTERS.HCTL, HCTL.BUS_RESET);
while((this.readRegister(REGISTERS.HCTL) & HCTL.BUS_RESET) !== 0) { }
console.log('Start SOF');
const result = this.readRegister(REGISTERS.MODE) | HCTL.SOFKAENAB;
this.setRegister(REGISTERS.MODE, result);
setTimeout( () => {
if (this.readRegister(REGISTERS.IRQ) & IRQS.FRAME) {
// first SOF received
console.log('SOF received');
return cb();
}
}, 20); // USB spec says wait 20ms after reset
}
reset() {
this.setRegister(REGISTERS.USB_CONTROL, COMMANDS.CHIP_RESET);
this.setRegister(REGISTERS.USB_CONTROL, 0x00);
let i = 0;
while((this.readRegister(REGISTERS.USB_IRQ) & 0x01) === 0) {
i++;
}
console.log(i, 'attempts to settle');
return i;
}
busprobe() {
const JSTATUS = 0x80;
const KSTATUS = 0x40;
const LOW_SPEED = 0x02;
let busSample = this.readRegister(REGISTERS.HRSL);
busSample &= (JSTATUS | KSTATUS);
if ((busSample === KSTATUS) || (busSample === JSTATUS)) {
const isFullSpeed = (this.readRegister(REGISTERS.MODE) & LOW_SPEED) === 0;
if (isFullSpeed) {
console.log('Full-speed host');
this.setRegister(REGISTERS.MODE, 0xC9);
} else {
console.log('Low-speed host');
this.setRegister(REGISTERS.MODE, 0xcb);
}
} else if(busSample === 0xc0) {
console.log('Illegal state');
} else if (busSample === 0) {
console.log('Disconnected');
}
}
controlTransfer(direction, setup, length) {
let ep = 0;
let setupPacket = new Uint8Array(8);
if (setup.recipient === 'endpoint') {
setupPacket[0] = 0x02;
} else if (direction === 'in' && setup.requestType === 'standard') {
setupPacket[0] = 0x80;
} else {
setupPacket[0] = REQUEST_TYPE[setup.requestType];
}
setupPacket[1] = setup.request;
storeShort(setup.value, setupPacket, 2);
storeShort(setup.index, setupPacket, 4);
setupPacket[6] = length;
console.log('Setup packet:', bytes2hex(setupPacket));
this.sendBytes(REGISTERS.SUDFIFO, setupPacket);
let err = this.dispatchPacket(TOKENS.SETUP, ep);
if (err) {
console.log('Setup packet error:', err);
return err;
}
return 'ok';
}
controlTransferIn(setup, length) {
this.controlTransfer('in', setup, length);
const res = this.transferIn(0, length);
err = this.dispatchPacket(TOKENS.OUTHS, 0);
if (err) {
console.log('IN handshake error:', err);
}
return res;
}
controlTransferOut(setup, data, cb) {
let res;
let err;
if (data) {
console.log('Data length:', data.byteLength);
this.controlTransfer('out', setup, data.byteLength);
this.transferOut(0, data, (res) => {
return cb(res);
});
} else {
res = this.controlTransfer('out', setup, 0);
this.transferIn(0, 0);
return res;
}
}
transferIn(ep, length) {
const RCVTOGRD = 0x10;
let transferLength = 0;
let data = new Uint8Array(length);
while (1) {
this.setRegister(REGISTERS.HCTL, this.endpoints[ep].receiveToggle);
console.log('Dispatching packet');
let err = this.dispatchPacket(TOKENS.IN, ep);
if (err) {
console.log('Error:', err);
return(err);
}
console.log('Waiting for data..');
if (this.readRegister(REGISTERS.IRQ) & IRQS.RECEIVED_DATA_AVAILABLE === 0) {
return(new Error('Receive error'));
}
const packetSize = this.readRegister(REGISTERS.RCVBC);
console.log('Packet size:', packetSize);
data.set(this.readBytes(REGISTERS.RCVFIFO, packetSize), transferLength);
this.setRegister(REGISTERS.IRQ, IRQS.RECEIVED_DATA_AVAILABLE); // clear interrupt
transferLength += packetSize;
// TODO: use MAX_PACKET_SIZE
if ((packetSize < 8) || (transferLength >= length)) {
this.endpoints[ep].receiveToggle = this.readRegister(REGISTERS.HRSL) & RCVTOGRD ? HCTL.RCVTOG0 : HCTL.RCVTOG1;
return { data: data };
}
}
}
transferOut(ep, bytes, cb) {
const SNDTOGRD = 0x20;
let packetSize = 8; // TODO: use max packet size from descriptor
let bytesLeft = bytes.byteLength;
let bytesWritten = 0;
console.log('Sending..');
setTimeout( () => {
// Wait until send buffer is available
while (this.readRegister(REGISTERS.IRQ) & IRQS.SEND_BUFFER_AVAILABLE === 0) { }
while (bytesLeft) {
this.setRegister(REGISTERS.HCTL, this.endpoints[ep].sendToggle);
const nrBytes = (bytesLeft >= packetSize) ? packetSize : bytesLeft;
const toSend = bytes.slice(bytesWritten, bytesWritten + nrBytes);
this.sendBytes(REGISTERS.SNDFIFO, toSend);
console.log('Sending', nrBytes, 'bytes:', bytes2hex(toSend));
this.setRegister(REGISTERS.SNDBC, nrBytes); // set number of bytes
const err = this.dispatchPacket(TOKENS.OUT, ep);
if (err) {
console.log('Transfer out error:', err); // TODO: enumerate errors
return cb({status: err, bytesWritten: bytesWritten});
}
bytesLeft -= nrBytes;
bytesWritten = bytes.byteLength - bytesLeft;
this.endpoints[ep].sendToggle = this.readRegister(REGISTERS.HRSL) & SNDTOGRD ? HCTL.SNDTOG0 : HCTL.SNDTOG1;
}
return cb({status: 'ok', bytesWritten: bytesWritten});
}, 300); // wait at least 200ms according to USB spec
}
dispatchPacket(token, ep) {
let nakCount = 0;
let retryCount = 0;
let transferResult = null;
const abortTimer = setTimeout( () => {
console.log('Timeout error');
return(new Error('Timeout error'));
}, 5000);
while(1) {
this.setRegister(REGISTERS.HXFR, token | ep);
while (this.readRegister(REGISTERS.IRQ) & IRQS.HXFR_DONE === 0) { }
this.setRegister(REGISTERS.IRQ, IRQS.HXFR_DONE);
transferResult = this.readRegister(REGISTERS.HRSL) & 0x0f;
if (transferResult === HRSL.NAK) {
nakCount++;
if (nakCount > USB_NAK_LIMIT) {
clearTimeout(abortTimer);
return(new Error('NAK error'));
}
} else if (transferResult === HRSL.TIMEOUT) {
console.log('Timeout, retrying..');
retryCount++;
if (retryCount > USB_RETRY_LIMIT) {
clearTimeout(abortTimer);
return(new Error('Timeout error'));
}
} else if (transferResult === 0) {
// clear interrupt
clearTimeout(abortTimer);
break;
} else {
return transferResult;
}
}
}
selectConfiguration(configuration) {
const conf = {
requestType: 'standard',
recipient: 'device',
request: 0x09, // USB_REQUEST_SET_CONFIGURATION
value: configuration,
index: 0x0000
};
let results = usb.controlTransferOut(conf);
console.log('select configuration:', results);
}
getConfiguration() {
const conf = {
requestType: 'standard',
recipient: 'device',
request: 0x08, // USB_REQUEST_GET_CONFIGURATION
value: 0x0000,
index: 0x0000
};
let results = usb.controlTransferIn(conf, 1);
console.log('get configuration:', bytes2hex(results.data));
return results.data;
}
setAddress(address) {
const setAddress = {
requestType: 'standard',
recipient: 'device',
request: 0x05, // USB_REQUEST_SET_ADDRESS
value: address,
index: 0x0000
};
let results = usb.controlTransferOut(setAddress);
console.log('set address:', results);
}
claimInterface(number) {
const conf = {
requestType: 'standard',
recipient: 'device',
request: 0x0B, // USB_REQUEST_SET_INTERFACE
value: 0x0000,
index: number
};
let results = usb.controlTransferOut(conf);
console.log('set interface:', results);
}
clearHalt(endpoint) {
const conf = {
requestType: 'standard',
recipient: 'endpoint',
request: 0x01, // USB_REQUEST_CLEAR_FEATURE
value: 0x0000, // endpoint halt
index: endpoint
};
let results = usb.controlTransferOut(conf);
console.log('clear halt', endpoint, ':', results);
}
}
class CP2102 {
constructor() {
this.setPortConfig = {
requestType: 'vendor',
recipient: 'device',
request: 0x05,
value: 0x00,
index: 0x03
};
this.openPort = {
requestType: 'vendor',
recipient: 'device',
request: 0x06,
value: 0x80,
index: 0x03
};
this.startPort = {
requestType: 'vendor',
recipient: 'device',
request: 0x08,
value: 0x00,
index: 0x03
};
this.closePort = {
requestType: 'vendor',
recipient: 'device',
request: 0x07,
value: 0x00,
index: 0x03
};
this.timeoutID = null;
this.intervalTimer = null;
}
init() {
console.log('Initialising CP2102');
usb.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: 0x00,
index: 0x00,
value: 0x01,
});
usb.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: 0x07,
index: 0x00,
value: 0x03 | 0x0100 | 0x0200,
});
usb.controlTransferOut({
requestType: 'vendor',
recipient: 'device',
request: 0x01,
index: 0x00,
value: 0x384000 / 38400, // TODO: change baud rate here
});
console.log('Baud rate set to 38400');
}
close() {
//TODO: release interface and close device
}
send(data, cb) {
usb.transferOut(0x01, new Uint8Array(data), (result) => {
console.log('send result:', result);
return cb();
});
this.timeoutID = setTimeout(() => {
console.log('Device not connected');
if(this.intervalTimer) {
clearInterval(this.intervalTimer);
}
this.close();
}, 1000);
}
receive(length, end, cb) {
console.log('Receiving...');
//this.intervalTimer = setInterval(() => {
let allData = [];
let incoming = usb.transferIn(0x01, length);
if (incoming.data && incoming.data.byteLength > 0) {
const data = E.toString(incoming.data);
if (this.timeoutID) {
clearTimeout(this.timeoutID);
this.timeoutID = null;
}
console.log('Received:', data);
allData.concat(data);
if (data.indexOf(end) !== -1) {
clearInterval(intervalTimer);
return cb(allData);
}
}
//}, 50);
}
}
const usb = new USB();
usb.init(() => {
const getDeviceDescriptor = {
requestType: 'standard',
recipient: 'device',
request: 0x06, // USB_REQUEST_GET_DESCRIPTOR
value: 0x0100, // conf = 0x00, USB_DESCRIPTOR_DEVICE = 0x01
index: 0x0000
};
let results = usb.controlTransferIn(getDeviceDescriptor, 18);
console.log('Get device descriptor:', bytes2hex(results.data));
Terminal.println('Vendor ID: 0x' + bytes2hex([results.data[9], results.data[8]], true));
Terminal.println('Product ID: 0x' + bytes2hex([results.data[11], results.data[10]], true));
console.log('getConfiguration');
if (usb.getConfiguration() === 0) {
console.log('selectConfiguration');
usb.selectConfiguration(1);
}
usb.claimInterface(0);
const getConfigDescriptor = {
requestType: 'standard',
recipient: 'device',
request: 0x06, // USB_REQUEST_GET_DESCRIPTOR
value: 0x0202, // conf = 0x02, USB_DESCRIPTOR_CONFIGURATION = 0x02
index: 0x0000
};
// Get config descriptor length
results = usb.controlTransferIn(getConfigDescriptor, 4);
const configDescriptorLength = extractShort(results.data, 2);
console.log('Config descriptor length:', configDescriptorLength);
results = usb.controlTransferIn(getConfigDescriptor, configDescriptorLength);
console.log('Data:', bytes2hex(results.data));
const cp2102 = new CP2102();
cp2102.init();
cp2102.send([0x02, 0x08, 0x00, 0x04, 0x06, 0x03, 0x78, 0xC1], () => {
console.log('Sent message');
cp2102.receive(34, 'END', (data) => {
cp2102.close();
});
});
});
Comments