Hello Paul. Thank you very much for your comprehensive answer.
Sorry for the delay in responding. Real life interfered. Lol.
I should have mentioned that RawHID has been my only focus. The first thing I did when I first lit up the Arduino development system was set Tools / USB Type to HID and it's been there ever since.
One mistake I made was not knowing about the RawHID function calls. With the mode set to RawHID in the development system, I figured (wrongly) that I should still use the Arduino style Serial calls -- and they mostly worked. But 64 byte binary buffers up and down is all I want. Bandwidth is not an issue for anything I foresee and transactions are capped at 10 per second at the host. Reality is more like 2 or 3 per minute.
Background: I've used USB for many years but never really learned it. Projects were always on a deadline and when I looked at USB documentation, I didn't have time to learn something that complex. So I cheated and used an FTDI chip.
This project is not a rush, so I decided to finally learn USB. I'm no expert, but now I understand the basics of how it works and why it was designed as it is. I settled on hidapi.h as a decent solution and built a library that does the things I want. I enumerate all USB devices and put that in a table. Then I search for what I want. I discovered the Teensy enumerates as two devices as follows:
Device Found
type: 16c0 0486
path: 0002:0006:00
serial_number: 5527490
Manufacturer: Teensyduino
Product: Teensyduino RawHID
Release: 275
Interface: 0
Device Found
type: 16c0 0486
path: 0002:0006:01
serial_number: 5527490
Manufacturer: Teensyduino
Product: Teensyduino RawHID
Release: 275
Interface: 1
At first this puzzled me, but then I realized how it works. Connecting by means of Manufacturer/Product ID always connects to Device 0 and, as you point out, didn't work. To connect to Device 1, I had to change to using the full USB path. That worked.
However, I was in raw HID mode at both ends of the link but was using the Serial calls in the Teensy. This worked until I ran into the NULL problem.
Now, I've modified the Teensy code to use RawHID.send, etc., which was easy because what I wanted all along is 64 byte binary packets and I'll worry about what's in the packet.
I expected it to work straight off, but it doesn't. Haha. I'm getting nothing at present.
I'm confused on a couple of things:
The RawHID example in the Arduino dev sys uses RawHID.send() and RawHID.recv() and no USB initialization calls. That's what I went with.
However, the documentation on the web site talks about different functions like:
void usb_init(void);
uint8_t usb_configured(void);
int8_t usb_rawhid_recv(uint8_t *buffer, uint8_t timeout);
Also, I assume that since I was able to establish communications before simply by connecting to the USB full path, parameters like these:
#define RAWHID_USAGE_PAGE 0xFFAB // recommended: 0xFF00 to 0xFFFF
#define RAWHID_USAGE 0x0200 // recommended: 0x0100 to 0xFFFF
...are not necessary. In fact, I don't even know what I would do with those. hidapi.h doesn't require them.
So I enumerate USB devices. The Teensy reports two devices, 0 and 1. I connect with hid_open_path(path), which returns a handle and no errors. But then I can't write to it. hid_write() returns -1 (error). And it didn't give errors before, when I was using the Serial calls in the Teensy. So I'm wondering if there's some initialization step at the Teensy that I need to be doing. Is it possible that the first call to a Serial function like Serial.println() triggers some additional needed initialization?
The code now looks like this:
Code:
// Teensy 3.2
// USBtoLED.c
#include <OctoWS2811.h>
// Machine instructions to restart the processor
// Execute with CPU_RESTART
// Not needed in this application
#define CPU_RESTART_ADDR (uint32_t *)0xE000ED0C
#define CPU_RESTART_VAL 0x5FA0004
#define CPU_RESTART (*CPU_RESTART_ADDR = CPU_RESTART_VAL);
// Program declarations
byte usbBuf[65];
int tik, interval30secs;
boolean ledOn;
unsigned long secInterval;
// LED Library Declarations
const int ledsPerStrip = 50; // (LEDs per channel)
DMAMEM int displayMemory[ledsPerStrip*6];
int drawingMemory[ledsPerStrip*6];
const int config = WS2811_GRB | WS2811_800kHz;
OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);
// Once
void setup() {
pinMode(13, OUTPUT); //set LED pin as output
Serial.begin(9600); //USB is always 12 Mbit/sec
tik = 0;
interval30secs = 2; //startup at just 2 seconds, then 30
ledOn = false;
secInterval = millis() + 1000;
leds.begin();
leds.show();
}
// Main loop -- loops forever
void loop() {
int count;
//First we run the schedulers
if( millis() > secInterval ) {
//these tasks run once per second
tik++;
if( ledOn ) {
digitalWrite(13, LOW); // set the LED off
ledOn = false;
}
else {
digitalWrite(13, HIGH); // set the LED on
ledOn = true;
}
if( tik > interval30secs ) {
//these tasks execute every 30 seconds
interval30secs = tik+30;
//send heartbeat
usbBuf[0] = 'H';
usbBuf[1] = 'B';
RawHID.send(usbBuf,70);
}
secInterval += 1000;
}
if( (count = RawHID.recv(usbBuf,0)) ) {
processCmd(count);
}
}
// Process command. Returns "##" as handshake to acknowledge that
// command(s) are processed and another packet can be sent.
// ## does not indicate that there were no errors, just that
// the packet was received and processed. "##" is also the heartbeat.
//
// USB packets consist of 64 bytes. The first byte specifies the command,
// 0 to 255. The remaining 63 bytes are utilized (or not) depending on
// the command.
//
// Command 0 is not used.
//
// Command 1 is the heartbeat command. Single byte command.
//
// Command 2 sets the color in the memory buffer for up to 15 LEDs, but
// does not trigger an update of the LEDs. (see Command 3)
// Byte 0: 2
// Byte 1: Channel number, 0 to 7.
// Byte 2: Number of LED elements in this command, 1 to 15.
// An element consists of four bytes. The first byte is the LED number,
// 0 to 255. Then a three byte RGB, GBR, BRG, etc. color triplet.
// Byte 3: not used
// Bytes 4 to 63: as above
//
// Command 3 triggers an update of all LEDs. Single byte command.
boolean processCmd(int count) {
int cmdNum;
int channelNum;
int numElems;
int ledNum;
unsigned int color;
int i;
byte *cp;
cmdNum = usbBuf[0];
switch( cmdNum ) {
case 0: //not used
return(false);
case 1: //ping command (send cmd ack only)
break;
case 2:
channelNum = usbBuf[1];
numElems = usbBuf[2];
if( channelNum < 0 || channelNum > 7 ) return(false); //error
if( numElems < 1 || numElems > 15 ) return(false); //error
cp = usbBuf+4;
for( i=0; i<numElems; i++ ) {
ledNum = (int)cp[0];
//The following code must be adjusted for RGB, GRB, BRG
color = (unsigned int)cp[1] << 16;
color |= (unsigned int)cp[2] << 8;
color |= (unsigned int)cp[3];
leds.setPixel((channelNum * ledsPerStrip) + ledNum, color);
cp += 4;
}
break;
case 3:
leds.show();
break;
default:
return(false); //error
}
// echo most of the received buffer contents (first 50 bytes)
// back to the host for examination.
// shift buffer contents up a ways to make room for cmd ack
memcpy(usbBuf,usbBuf+7,50);
memcpy(usbBuf,"##cnt",5);
usbBuf[5] = lowByte(count);
usbBuf[6] = '#';
i = RawHID.send(usbBuf,70);
if( i ) return(true);
return(false);
}