Teensy 4.1 Camera Control

Status
Not open for further replies.

Garug

Well-known member
Modern cameras support USB PTP camera control. It looks like Teensy 4.1 has all the HW needed. Is there SW support for this, any existing projects that I just did not find?

Here is one project I did find for Canon cameras but it is a bit older one and using UNO + USB host shield.
https://chome.nerpa.tech/canon-eos-cameras-principles-of-interfacing-and-library-description/

The USB PTP https://en.wikipedia.org/wiki/Picture_Transfer_Protocol

"Device control[edit]
Many modern digital cameras from Canon and Nikon can be controlled via PTP from a USB host enabled computing device (Smartphone, PC or Arduino for example). As is the norm for PTP, the communication takes place over a USB connection. When interacting with the camera in this manner, it is expected that the USB endpoints are in (synchronous) Bulk Transfer Mode, for getting/setting virtually all the camera's features/properties (such as ISO, Aperture, Shutter speed and focus). Events raised by the camera, in response to specific actions performed by the host device, are sent back to the host via the USB asynchronous Interrupt endpoint.
In addition to changing the camera's settings and operating mode, it is possible to receive a through-the-lens view using "Live View". As described above, the storage objects of the camera's memory cards can be manipulated too.
By controlling a camera in this way it is possible to augment its capabilities. For example, if the controlling software was running on a Smartphone with GPS functionality, it would be possible to add the GPS coordinates to an image's Exif data, at the time of image capture - even if the camera itself had no GPS functionality."
 
Last edited:
The ISO 15740 standard cost almost 200 euro, so did not get it yet, but the PIMA 15740: 2000 is available for free.

it seems to be from time ISO 15740 was not ready yet.

"The technical content of this PIMA standard is closely related to ISO 15740, which is currently in the working draft stage while work on multiple transports is being completed."
 
Modern cameras support USB PTP camera control. It looks like Teensy 4.1 has all the HW needed. Is there SW support for this, any existing projects that I just did not find?
We have the MTP implemented, mostly for file transfer, but I cannot see why one could not add the PTP subset to it.
Event driven actions is a little bit more complicated, especially in absence examples and devices.
BTW, it is easy for WIKI to promise everything, but without 'open-source' implementations it is only potential functionality.
 
Thanks of pointing to MTP.

I do have Teensy 4.1 and have played with the USB Host somewhat but not MTP, I do have several cameras supporting the PTP, not sure but maybe even smart phones have that support? It is somewhat a challenge for me to make the PTP work, but will look into it now couple of days and hopefully found a way.

As WMXZ mentioned and found this, so maybe there is some hope. All help is appreciated.

"PTP stands for “Picture Transfer Protocol.” When Android uses this protocol, it appears to the computer as a digital camera. MTP is actually based on PTP, but adds more features, or “extensions.” PTP works similarly to MTP, and is commonly used by digital cameras."

Maybe the starting point is creating the endpoints: "The device shall contain at least four endpoints: default, Data-In, Data-Out, and an Interrupt endpoint."

Screenshot 2021-08-08 at 13.49.59.jpg
 

Attachments

  • Screenshot 2021-08-08 at 13.33.27.jpg
    Screenshot 2021-08-08 at 13.33.27.jpg
    109 KB · Views: 56
I thought I try the MTP first, but it seems I am badly lost with the very basics.

1. The Teensyuino 1.54 has the USB type MTP Disc (experimental), but are these selections not for the standard USB port (same as serial)? I tough the MTP uses the USB Host port?

2. The examples I found like https://github.com/WMXZ-EU/MTP_t4/blob/master/examples/mtp-test/mtp-test.ino
include #include "SD.h" Why is that, is it using that for reading USB disc, I thought the MTP disc is for USB disc or stick connected toi USB Host port?

3. The above example does not compile, only tried to compile the .ino, as thinking 1.54 has the libraries and everything set for MTP Disk but it does not seem to be so as it ends "fatal error: MTP.h: No such file or directory
Multiple libraries were found for "SD.h"
compilation terminated."

Is there an example that would work with Teensyuino 1.54 MTP Disc (experimental) selected, like just giving the disc info etc.

reading now the https://github.com/WMXZ-EU/MTP_t4 maybe indeed I have misunderstood the whole thing and MTP Disc is not to attach USB disc to Teensy USB Host port, but make the Teensy appear as USB disc for computer, is that it? but on that case I fail to understand how it would be helpful for the camera PTP as camera should be connected to the USB host.
 
Last edited:
@Garug
Indeed MTP disk is not useful for your application.

MTP disk is a MPT responder, that is, it makes the teensy to act as a MTP device and uses a PC as MTP initiator.
What you have is a camera as a MTP responder and you wanted the teensy to act as MTP initiator with camera specific functionality (FWIW MTP include PTP)
That is, you need to run USB Host on teensy and write SW extending USB Host that interacts with your camera.

In fact I said I used MTP as a responder, so it cannot be used for your application.
BUT, maybe the links I gave you on the work of felis could help you to get SW for your camera MTP/PTP application.
In fact, the link you gave in OP is related to same SW. So you have what you need to get started.
 
The problem was I did not understand what MTP responder meant, now I do.

I think if I could just request and get camera info using USB Host, from there I could make it using the PTP standard and device specific documents, my current challenge really is to make the first communication between camera and Teensy 4.1 USB Host.

So I think the current challenge is to create the endpoints and end to establish way to send and receive the PTP messages using USB Host.

Those examples are for different platform, they are so deep and complex, with programming style that is difficult for me and with license I do not agree. I am happy to publish simple example if I succeed to establish the PTP communication, but not the complete code.
 
The USB Host HIDDeviceInfo example looks promising starting point, this is what BlackMagic Pocket 4k camera reports when connected

Code:
USBDeviceInfo claim this=20005DE8

****************************************
** Device Level **
  vid=1EDB
  pid=BE16
  bDeviceClass = 239
  bDeviceSubClass = 2
  bDeviceProtocol = 1
08 0B 00 04 FF 03 00 02 09 04 00 00 00 FF 03 00 00 0A 21 01 00 00 00 00 00 00 00 09 04 01 00 02
FF 02 00 00 07 05 01 02 00 02 00 07 05 81 02 00 02 00 09 04 02 00 02 FF 05 FF 00 07 05 02 02 00
02 00 07 05 82 02 00 02 00 09 04 03 00 00 FE 01 01 00 09 21 08 E8 03 00 00 01 01 09 04 04 00 03
06 01 01 00 07 05 04 02 00 02 00 07 05 84 02 00 02 00 07 05 83 03 00 02 01 

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 00 00 00 FF 03 00 00 0A 21 01 00 00 00 00 00 00 00 09 04 01 00 02 FF 02 00 00 07 05 01 02
00 02 00 07 05 81 02 00 02 00 09 04 02 00 02 FF 05 FF 00 07 05 02 02 00 02 00 07 05 82 02 00 02
00 09 04 03 00 00 FE 01 01 00 09 21 08 E8 03 00 00 01 01 09 04 04 00 03 06 01 01 00 07 05 04 02
00 02 00 07 05 84 02 00 02 00 07 05 83 03 00 02 01 
 bInterfaceNumber = 0
 number end points = 0
 bInterfaceClass =    255
 bInterfaceSubClass = 3
 bInterfaceProtocol = 0

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 01 00 02 FF 02 00 00 07 05 01 02 00 02 00 07 05 81 02 00 02 00 09 04 02 00 02 FF 05 FF 00
07 05 02 02 00 02 00 07 05 82 02 00 02 00 09 04 03 00 00 FE 01 01 00 09 21 08 E8 03 00 00 01 01
09 04 04 00 03 06 01 01 00 07 05 04 02 00 02 00 07 05 84 02 00 02 00 07 05 83 03 00 02 01 
 bInterfaceNumber = 1
 number end points = 2
 bInterfaceClass =    255
 bInterfaceSubClass = 2
 bInterfaceProtocol = 0

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 02 00 02 FF 05 FF 00 07 05 02 02 00 02 00 07 05 82 02 00 02 00 09 04 03 00 00 FE 01 01 00
09 21 08 E8 03 00 00 01 01 09 04 04 00 03 06 01 01 00 07 05 04 02 00 02 00 07 05 84 02 00 02 00
07 05 83 03 00 02 01 
 bInterfaceNumber = 2
 number end points = 2
 bInterfaceClass =    255
 bInterfaceSubClass = 5
 bInterfaceProtocol = 255

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 03 00 00 FE 01 01 00 09 21 08 E8 03 00 00 01 01 09 04 04 00 03 06 01 01 00 07 05 04 02 00
02 00 07 05 84 02 00 02 00 07 05 83 03 00 02 01 
 bInterfaceNumber = 3
 number end points = 0
 bInterfaceClass =    254
 bInterfaceSubClass = 1
 bInterfaceProtocol = 1

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 04 00 03 06 01 01 00 07 05 04 02 00 02 00 07 05 84 02 00 02 00 07 05 83 03 00 02 01 
 bInterfaceNumber = 4
 number end points = 3
 bInterfaceClass =    6
 bInterfaceSubClass = 1
 bInterfaceProtocol = 1


and this is Canon EOS R

Code:
USBDeviceInfo claim this=20005DE8

****************************************
** Device Level **
  vid=4A9
  pid=32DA
  bDeviceClass = 0
  bDeviceSubClass = 0
  bDeviceProtocol = 0
09 04 00 00 03 06 01 01 00 07 05 82 02 00 02 00 07 05 01 02 00 02 00 07 05 83 03 40 00 0A 

USBDeviceInfo claim this=20005DE8

****************************************
** Interface Level **
09 04 00 00 03 06 01 01 00 07 05 82 02 00 02 00 07 05 01 02 00 02 00 07 05 83 03 40 00 0A 
 bInterfaceNumber = 0
 number end points = 3
 bInterfaceClass =    6
 bInterfaceSubClass = 1
 bInterfaceProtocol = 1
 
I have now been going trough the USBHost_t36

The ehci.cpp says

Code:
// All USB EHCI controller hardware access is done from this file's code.
// Hardware services are made available to the rest of this library by
// three structures:
//
//   Pipe_t: Every USB endpoint is accessed by a pipe.  new_Pipe()
//     sets up the EHCI to support the pipe/endpoint, and delete_Pipe()
//     removes this configuration.
//
//   Transfer_t: These are used for all communication.  Data transfers
//     are placed into work queues, to be executed by the EHCI in
//     the future.  Transfer_t only manages data.  The actual data
//     is stored in a separate buffer (usually from a device driver)
//     which is referenced from Transfer_t.  All data transfer is queued,
//     never done with blocking functions that wait.  When transfers
//     complete, a driver-supplied callback function is called to notify
//     the driver.
//
//   USBDriverTimer: Some drivers require timers.  These allow drivers
//     to share the hardware timer, with each USBDriverTimer object
//     able to schedule a callback function a configurable number of
//     microseconds in the future.
//
// In addition to these 3 services, the EHCI interrupt also responds
// to changes on the main port, creating and deleting the root device.
// See enumeration.cpp for all device-level code.

As suggested by USBHost_t36.h I have read the relevant parts of the usb20.pdf and have some theoretical understanding how it should work on very abstracted level

Code:
// Dear inquisitive reader, USB is a complex protocol defined with
// very specific terminology.  To have any chance of understand this
// source code, you absolutely must have solid knowledge of specific
// USB terms such as host, device, endpoint, pipe, enumeration....
// You really must also have at least a basic knowledge of the
// different USB transfers: control, bulk, interrupt, isochronous.
//
// The USB 2.0 specification explains these in chapter 4 (pages 15
// to 24), and provides more detail in the first part of chapter 5
// (pages 25 to 55).  The USB spec is published for free at
// www.usb.org.  Here is a convenient link to just the main PDF:
//
// https://www.pjrc.com/teensy/beta/usb20.pdf
//

And going trough the PIMA 15740: 2000 and many Arduino and other PTP Camera control examples gives some understanding, but still very much lost how I could get and use the pipes to send and receive data with USBHost_t36. Over all I am confused how the USBHost_t36 works.

Could someone shed some light on this.

Edit: All supported device types seem to have their own .cpp i am now looking the serial.ccp and tere can find some guidance how the pipe is used "rxpipe = new_Pipe(dev, 2, rx_ep & 15, 1, rx_size);" What would be the best device type to look at to better understand how to implement the communication with cameraPTP, MassStorageDriver.cpp maybe follows MTP? that is close to PTP
 
Last edited:
I am trying to figure out how the MassStorageDriver.cpp sends an inquiry, how it gets the address, endpoint pipe etc. but it is pretty hopeless for me. Edit: After writing this it seems maybe the answer is below, but requires plenty of digging still before it is clear.

It seems this is a Mass Storage Device Inquiry, this I just do not understand "msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t)"

Code:
// Do Mass Storage Device Inquiry
uint8_t msController::msDeviceInquiry(msInquiryResponse_t * const Inquiry)
{
#ifdef DBGprint
	println("msDeviceInquiry()");
#endif
	msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t)
	{
		.Signature          = CBW_SIGNATURE,
		.Tag                = ++CBWTag,
		.TransferLength     = sizeof(msInquiryResponse_t),
		.Flags              = CMD_DIR_DATA_IN,
		.LUN                = currentLUN,
		.CommandLength      = 6,
		.CommandData        = {CMD_INQUIRY,0x00,0x00,0x00,sizeof(msInquiryResponse_t),0x00}
	};
	return msDoCommand(&CommandBlockWrapper, Inquiry);

But it seems it is the return that triggers the actual Inquiry

Code:
//---------------------------------------------------------------------------
// Send SCSI Command
// Do a complete 3 stage transfer.
uint8_t msController::msDoCommand(msCommandBlockWrapper_t *CBW,	void *buffer)
{
	uint8_t CSWResult = 0;
	mscTransferComplete = false;
#ifdef DBGprint
	println("msDoCommand()");
#endif	
	if(CBWTag == 0xFFFFFFFF) CBWTag = 1;
	// digitalWriteFast(2, HIGH);
	queue_Data_Transfer(datapipeOut, CBW, sizeof(msCommandBlockWrapper_t), this); // Command stage.
	while(!msOutCompleted) yield();
	// digitalWriteFast(2, LOW);
	msOutCompleted = false;
	if((CBW->Flags == CMD_DIR_DATA_IN)) { // Data stage from device.
		queue_Data_Transfer(datapipeIn, buffer, CBW->TransferLength, this);
	while(!msInCompleted) yield();
	// digitalWriteFast(2, HIGH);
	msInCompleted = false;
	} else {							  // Data stage to device.
		queue_Data_Transfer(datapipeOut, buffer, CBW->TransferLength, this);
	while(!msOutCompleted) yield();
	// digitalWriteFast(2, LOW);
	msOutCompleted = false;
	}
	CSWResult = msGetCSW(); // Status stage.
	// All stages of this transfer have completed.
	//Check for special cases. 
	//If test for unit ready command is given then
	//  return the CSW status byte.
	//Bit 0 == 1 == not ready else
	//Bit 0 == 0 == ready.
	//And the Start/Stop Unit command as well.
	if((CBW->CommandData[0] == CMD_TEST_UNIT_READY) ||
	   (CBW->CommandData[0] == CMD_START_STOP_UNIT))
		return CSWResult;
	else // Process possible SCSI errors.
		return msProcessError(CSWResult);
}


And somehow the data ends here

Code:
// MSC Inquiry Command Reponse Struct
typedef struct
{
	unsigned DeviceType          : 5;
	unsigned PeripheralQualifier : 3;
	unsigned Reserved            : 7;
	unsigned Removable           : 1;
	uint8_t  Version;
	unsigned ResponseDataFormat  : 4;
	unsigned Reserved2           : 1;
	unsigned NormACA             : 1;
	unsigned TrmTsk              : 1;
	unsigned AERC                : 1;
	uint8_t  AdditionalLength;
	uint8_t  Reserved3[2];
	unsigned SoftReset           : 1;
	unsigned CmdQue              : 1;
	unsigned Reserved4           : 1;
	unsigned Linked              : 1;
	unsigned Sync                : 1;
	unsigned WideBus16Bit        : 1;
	unsigned WideBus32Bit        : 1;
	unsigned RelAddr             : 1;
	uint8_t  VendorID[8];
	uint8_t  ProductID[16];
	uint8_t  RevisionID[4];
}  __attribute__((packed)) msInquiryResponse_t;

the HIDDeviceInfo seems to be better starting point, maybe for the cameraPTP, as it already provides some basic information of the camera, but need to figure out how the PTP communication with the camera works using the USBHost_t36 library.
 
While I cannot comment on your problem, as I have at the moment no teensy with me, I would suggest to do the following:
Take the USB-host examples and try to generate first a basic MTP disk initiator.
Why, we have a (nearly) full functioning MTP disk responder, so you can follow what PC sends and what Teensy answers.
Once you understand how initiator and responder communicate, it should be easier to implement a Camera initiator.
 
The mscTesting.ino works fine, and I have now edited it so that PC sends nothing, just receives this on terminal. (teensy receives this from USB stick)

Code:
MSC TEST

Initializing USB Drive(s)
msDrive  connected
msDrive  connected/initilized
msDrive is NOT Mounted

   connected 1
   initialized 1
   USB Vendor ID: 0951
  USB Product ID: 16ae
      HUB Number: 0
        HUB Port: 0
  Device Address: 1
Removable Device: YES
      DeviceType: 00000
       Qualifier: 000
        VendorID: Kingston
       ProductID: DT microDuo 3C  
      RevisionID: PMAP
         Version: 6
    Sector Count: 60555263
     Sector size: 512
   Disk Capacity: 31004294656 Bytes

And to get that data it does inquiry, and above I tried to follow the code how it happens.

I think I will try to rewrite the inquiry routine tomorrow just to understand how everything works, and after try that routine with camera using the HIDDeviceInfo example as starting point for the cameraPTP experiments.
 
@Garug - I am not so sure this is the way to talk to the camera. But, I can give you the procedure for talking to a USB thumb drive. Gathering info for the device plugged in is done here:
Code:
// Initialize Mass Storage Device
uint8_t msController::mscInit(void) {
#ifdef DBGprint
	println("mscIint()");
#endif
	uint8_t msResult = MS_CBW_PASS;

	CBWTag = 0;
	uint32_t start = millis();
	// Check if device is connected.
	do {
		if((millis() - start) >= MSC_CONNECT_TIMEOUT) {
			return MS_NO_MEDIA_ERR;  // Not connected Error.
		}
		yield();
	} while(!available());
  
	msReset();
// delay(500); // Not needed any more.
	maxLUN = msGetMaxLun();

//	msResult = msReportLUNs(&maxLUN);
//println("maxLUN = ");
//println(maxLUN);
//	delay(150);
	//-------------------------------------------------------
	msResult = msStartStopUnit(1);
	msResult = WaitMediaReady();
	if(msResult)
		return msResult;
		
	// Retrieve drive information.
	msDriveInfo.initialized = true;
	msDriveInfo.hubNumber = getHubNumber();			// Which HUB.
	msDriveInfo.hubPort = getHubPort();				// Which HUB port.
	msDriveInfo.deviceAddress = getDeviceAddress();	// Device addreess.
	msDriveInfo.idVendor = getIDVendor();  			// USB Vendor ID.
	msDriveInfo.idProduct = getIDProduct();  		// USB Product ID.
[COLOR="#FF0000"]	msResult = msDeviceInquiry(&msInquiry);			// Config Info.[/COLOR]
	if(msResult)
		return msResult;
	msResult = msReadDeviceCapacity(&msCapacity);	// Size Info.
	if(msResult)
		return msResult;
	memcpy(&msDriveInfo.inquiry, &msInquiry, sizeof(msInquiryResponse_t));
	memcpy(&msDriveInfo.capacity, &msCapacity, sizeof(msSCSICapacity_t));
	return msResult;
}

This is where the inquiry command is given. The struct for that is shown in your previous post. I am not sure if this would even work with a camera unless it uses SCSI. You create a instance of the inquiry structure using:
Code:
msInquiryResponse_t myInquiry
Then call:
Code:
// Do Mass Storage Device Inquiry
uint8_t msController::msDeviceInquiry(msInquiryResponse_t * const Inquiry)
{
#ifdef DBGprint
	println("msDeviceInquiry()");
#endif
	msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t)
	{
		.Signature          = CBW_SIGNATURE,
		.Tag                = ++CBWTag,
		.TransferLength     = sizeof(msInquiryResponse_t),
		.Flags              = CMD_DIR_DATA_IN,
		.LUN                = currentLUN,
		.CommandLength      = 6,
		.CommandData        = {CMD_INQUIRY,0x00,0x00,0x00,sizeof(msInquiryResponse_t),0x00}
	};
	return msDoCommand(&CommandBlockWrapper, Inquiry);
}
Like as this example:
Code:
msDrive1.msDeviceInquiry(&myInquiry)

The way msDoCommand() works is in three stages. It creates a Command Block wrapper that has the SCSI command code for retrieving info about the drive.
There are three stages of this operation. First the command is sent with it's parameters including the address of the inquiry structure to receive the info about the drive. Then it sends a command based on whether we are looking to input data or output data from/to the device. CMD_DIR_DATA_IN or CMD_DIR_DATA_OUT. After either one we ask for a status from the last operation. IE. Read or Write. This is:
Code:
uint8_t msController::msGetCSW(void)
Which is processed for success or errors which will be processed with:
Code:
uint8_t msController::msProcessError(uint8_t msStatus)
Again not knowing anything about the camera I am not sure if this will work but at least you may see the function of access to SCSI.
 
Thanks, I look into it. The library comments say

"// Transfer_t: These are used for all communication. Data transfers
// are placed into work queues, to be executed by the EHCI in
// the future. Transfer_t only manages data. The actual data
// is stored in a separate buffer (usually from a device driver)
// which is referenced from Transfer_t. All data transfer is queued,
// never done with blocking functions that wait. When transfers
// complete, a driver-supplied callback function is called to notify
// the driver."

How is the Transfer_t referenced by the above? I am still very much lost with the USBHost_t36 architecture and how to use it for other than the already supported protocols; HID, MIDI, Serial, maybe partial MTP that should be related to PMP. Is the current Mass Storage Device support using MTP “Media Transfer Protocol.”

So what I really am looking for is how the Transfer_t is used for the communication, to implement the PTP communication and hope the Transfer_t supports PTP communication?

Just a note: USB Picture Transfer Protocol (PTP) this is what is discussed here. Somewhat confusingly the PTP is also Precision Time Protocol, that can work also over USB.
 
Last edited:
The MSC example seems to run happily with these files (all these files on the .ino directory and reference to library changed to USBHost_t36_.h so it should be using these files only for the USB host functionality)

Screenshot 2021-08-11 at 12.55.26.jpg

+ the on USBHost_t36_.h referred two extensions

#include "utility/imxrt_usbhs.h"
#include "utility/msc.h"

The hub.ccp is not needed as I have commented //USBHub hub1(myusb); and attaching USB stick directly to the USB port, but will keep it for future needs.

I am not sure if ehci.cpp uses enumeration.cpp or memory.cpp but they are needed to get it compiled.

Just thinking, maybe I should figure out how to use ehci.cpp functions directly form cameraPTP.ino

Are other functions than these needed for USB PTP communication over the USB Host

Code:
void USBHost::begin()
void USBHost::isr()
bool USBHost::queue_Control_Transfer(Device_t *dev, setup_t *setup, void *buf, USBDriver *driver) // Create a Control Transfer and queue it
bool USBHost::queue_Data_Transfer(Pipe_t *pipe, void *buffer, uint32_t len, USBDriver *driver) // Create a Bulk or Interrupt Transfer and queue it
bool USBHost::queue_Transfer(Pipe_t *pipe, Transfer_t *transfer)
bool USBHost::followup_Transfer(Transfer_t *transfer)
void USBHost::followup_Error(void)
bool USBHost::allocate_interrupt_pipe_bandwidth(Pipe_t *pipe, uint32_t maxlen, uint32_t interval)
void USBHost::add_qh_to_periodic_schedule(Pipe_t *pipe)
void USBHost::delete_Pipe(Pipe_t *pipe)

Is there any documentation how to use them other than the few comments on ehci.cpp?

A drawing how they are used with some implemented functionality, like keyboard communication would be helpful. i.e on what part of the communication sequence they get used.
 
Last edited:
This is giving me big headache

#define print USBHost::print_
#define println USBHost::println_

Why is it, what is it, where does it print and why can I not use the normal Seria.print

Does it print to the USB host interface? Because I do not see those prints on terminal, and would like to.

There is also the commented //USBHDBGSerial.print("remain = "); etc. but uncommenting them and problems will follow.

"
USBHDBGSerial.println(remain);
^
'USBHost' is not a base of 'usb_serial_class'
"

The USBHDBGSerial is defined #define USBHDBGSerial Serial
 
This is giving me big headache

#define print USBHost::print_
#define println USBHost::println_

Why is it, what is it, where does it print and why can I not use the normal Seria.print
AFAIK, these are macros defined in some include files for debugging for easy redirection of debugging info.
(different people have different programming styles)
 
But the problem is it prevents using the normal Serial.print as they have defined #define print USBHost::print_

Also the question is, where is this printing?

I have now changed these to

Code:
#define printB   USBHost::print_
#define printlnB USBHost::println_

And it still seems to work and can now use Serial.print... There was so many instances of the print and println that had to be changed manually.
 
Last edited:
And this is how the ehci.cpp and MassStorageDriver.cpp functions get called. Booting the teensy and removing USB stick after result provided.

Code:
MSC TEST

Initializing USB Drive(s)
USBHost::begin
msController::mscInit
USBHost::isr()
USBHost::isr()
USBHost::isr()
USBHost::isr()
USBHost::new_Pipe
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
msController::claim
msController::claim
USBHost::new_Pipe
USBHost::new_Pipe
msController::msReset
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
msController::control
msController::msGetMaxLun
USBHost::queue_Control_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
USBHost::followup_Transfer
USBHost::followup_Transfer
msController::control
msController::msStartStopUnit
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackOut
msController::new_dataOut
msController::msGetCSW
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::WaitMediaReady
msController::msTestReady
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackOut
msController::new_dataOut
msController::msGetCSW
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::msDeviceInquiry
msController::msDoCommand
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackOut
msController::new_dataOut
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::msGetCSW
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::msProcessError
msController::msReadDeviceCapacity
msController::msDoCommand
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackOut
msController::new_dataOut
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::msGetCSW
queue_Data_Transfer
USBHost::queue_Transfer
USBHost::isr()
USBHost::followup_Transfer
msController::callbackIn
msController::new_dataIn
msController::msProcessError
msDrive  connected
msController::checkConnectedInitialized
msDrive  connected/initilized
msDrive is NOT Mounted

   connected 1
   initialized 1
   USB Vendor ID: 0951
  USB Product ID: 16ae
      HUB Number: 0
        HUB Port: 0
  Device Address: 1
Removable Device: YES
      DeviceType: 00000
       Qualifier: 000
        VendorID: Kingston
       ProductID: DT microDuo 3C  
      RevisionID: PMAP
         Version: 6
    Sector Count: 60555263
     Sector size: 512
   Disk Capacity: 31004294656 Bytes

msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
USBHost::isr()
msController::disconnect()
USBHost::delete_Pipe
USBHost::delete_Pipe
USBHost::delete_Pipe
msController::checkConnectedInitialized
msDrive not connected: Code: 40

and just the MassStorageDriver.cpp functions

Code:
MSC TEST

Initializing USB Drive(s)
msController::mscInit
msController::claim
msController::claim
msController::msReset
msController::control
msController::msGetMaxLun
msController::control
msController::msStartStopUnit
msController::callbackOut
msController::new_dataOut
msController::msGetCSW
msController::callbackIn
msController::new_dataIn
msController::WaitMediaReady
msController::msTestReady
msController::callbackOut
msController::new_dataOut
msController::msGetCSW
msController::callbackIn
msController::new_dataIn
msController::msDeviceInquiry
msController::msDoCommand
msController::callbackOut
msController::new_dataOut
msController::callbackIn
msController::new_dataIn
msController::msGetCSW
msController::callbackIn
msController::new_dataIn
msController::msProcessError
msController::msReadDeviceCapacity
msController::msDoCommand
msController::callbackOut
msController::new_dataOut
msController::callbackIn
msController::new_dataIn
msController::msGetCSW
msController::callbackIn
msController::new_dataIn
msController::msProcessError
msDrive  connected
msController::checkConnectedInitialized
msDrive  connected/initilized
msDrive is NOT Mounted

   connected 1
   initialized 1
   USB Vendor ID: 0951
  USB Product ID: 16ae
      HUB Number: 0
        HUB Port: 0
  Device Address: 1
Removable Device: YES
      DeviceType: 00000
       Qualifier: 000
        VendorID: Kingston
       ProductID: DT microDuo 3C  
      RevisionID: PMAP
         Version: 6
    Sector Count: 60555263
     Sector size: 512
   Disk Capacity: 31004294656 Bytes

msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::checkConnectedInitialized
msController::disconnect()
msController::checkConnectedInitialized
msDrive not connected: Code: 40

msController::checkConnectedInitialized
 
Last edited:
After looking more the code maybe there would have been a very simple way to enable the debug printing, but I just do not understand it so got rid of it. The debug printing on enumeration.cpp is now rewritten and provides this for BlackMagic camera,

Code:
USBHost::disconnect_Device:  00200023bc
disconnect driver:           0020001640
removed Device_t from devlist
USBHost::disconnect_Device:  0000000000
USBHost::new_Device
  new_Device:    480 Mbit/sec
  dev:           00200023bc
  hub_addr:      0000000000
  hub_port:      0000000000
  control_pipe:  0020002400
USBHost::enum:   START
  transfer:      0020001a60
  buffer:        00200021a0
  length:        0000000008
  pipe_set_maxlen: 0000000064
USBHost::assign_address:      
  addres:        0000000002
0__USBHost::enumeration:         read 8 bytes of device desc
  enumbuf:       00200021a0
  enumsetup:     0000020500
USBHost::enum:   START
  transfer:      0020002540
  buffer:        0000000000
  length:        0000000000
1__USBHost::enumeration:         request device descriptor
  wValue:        0000000002
  pipe_set_addr: 0000000002
USBHost::enum:   START
  transfer:      0020001a60
  buffer:        00200021a0
  length:        0000000012
2__USBHost::enumeration:         parse 18 device desc bytes
  bDeviceClass:  00000000ef
  bD_SubClass:   0000000002
  bD_Protocol:   0000000001
  idVendor:      0000001edb
  idProduct:     000000be16
3__USBHost::enumeration:         request Language ID
USBHost::enum:   START
  transfer:      0020002500
  buffer:        00200021a4
  length:        00000001fc
4__USBHost::enumeration:         parse Language ID
  enumbuf:       00200021a0
5__USBHost::enumeration:         request Manufacturer string
  LanguageID:    0000000409
USBHost::enum:   START
  transfer:      0020002480
  buffer:        00200021a4
  length:        00000001fc
6__USBHost::enumeration:         parse Manufacturer string
  transfer:      0020002480
  descriptor:  Blackmagic
7__USBHost::enumeration:         request Product string
USBHost::enum:   START
  transfer:      00200024c0
  buffer:        00200021a4
  length:        00000001fc
8__USBHost::enumeration:         parse Product string
  transfer:      00200024c0
  descriptor:  Blackmagic
9__USBHost::enumeration:         request Serial Number string
USBHost::enum:   START
  transfer:      0020002540
  buffer:        00200021a4
  length:        00000001fc
10_USBHost::enumeration:         parse Serial Number string
  transfer:      0020002540
  descriptor:  Blackmagic
11_USBHost::enumeration:         request first 9 bytes of config desc
USBHost::enum:   START
  transfer:      0020001a60
  buffer:        00200021a0
  length:        0000000009
12_USBHost::enumeration:         read 9 bytes, request all of config desc
  enumbuf:       00200021a0
  lenght:        0000000082
USBHost::enum:   START
  transfer:      0020002500
  buffer:        00200021a0
  length:        0000000082
13_USBHost::enumeration:         read all config desc, send set config
  bmAttributes:  00000000c0
  bMaxPower:     0000000048
USBHost::enum:   START
  transfer:      0020002480
  buffer:        0000000000
  length:        0000000000
14_USBHost::enumeration:         device is now configured
USBHost::claim_drivers:         
  Descriptor:    000000000b = IAD
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000021 = HID
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000021 = HID
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT

A good starting point I think and I thought this happens without MassStorageDriver.cpp, but it does not seem to be so after all, but do not understand how it is involved.

This is the code the above is printed with.

Code:
#include "USBHost_t36_.h"

USBHost myusb;
//USBHub hub1(myusb);

msController msrive(myusb);

void setup() {
	while(!Serial);

	Serial.printf(F("\n\nMSC TEST\n\n"));
	Serial.printf(F("Initializing USB Drive(s)\n"));	
	myusb.begin();

}

void loop() {

}

why is the "msController msrive(myusb);" needed? msrive is not used for anything and the name could be what ever, but that kind of definition is needed or it stops on step 1. How is the MassStorageDriver.cpp still involved, via what mechanism, I would like to remove it and if needed write necessary parts on the main code.

This is the printout if I comment out the //msController msrive(myusb);

Code:
MSC TEST

Initializing USB Drive(s)
USBHost::disconnect_Device:  0000000000
USBHost::disconnect_Device:  0000000000
USBHost::new_Device
  new_Device:    480 Mbit/sec
  dev:           00200023bc
  hub_addr:      0000000000
  hub_port:      0000000000
  control_pipe:  0020002400
USBHost::enum:   START
  transfer:      00200024c0
  buffer:        00200021a0
  length:        0000000008
  pipe_set_maxlen: 0000000064
USBHost::assign_address:      
  addres:        0000000001
0__USBHost::enumeration:         read 8 bytes of device desc
  enumbuf:       00200021a0
  enumsetup:     0000010500
USBHost::enum:   START
  transfer:      0020002540
  buffer:        0000000000
  length:        0000000000
1__USBHost::enumeration:         request device descriptor
  wValue:        0000000001
  pipe_set_addr: 0000000001

Even though it stops on the "1__USBHost::enumeration: request device descriptor" the library runs still correctly, and detects removal and connecting a new device correctly.
 
Last edited:
After some experimenting, I can delete everything else from the MassStorageDriver.cpp but this and the "msController msrive(myusb);" must be on the main code.

Code:
#include <Arduino.h>
#include "USBHost_t36_.h"  // Read this header first for key info




void msController::init()
{
	//contribute_Pipes(mypipes, sizeof(mypipes)/sizeof(Pipe_t));
	contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t));
	//contribute_String_Buffers(mystring_bufs, sizeof(mystring_bufs)/sizeof(strbuf_t));
	//driver_ready_for_device(this);
}

bool msController::claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len)
{
 
}

void msController::disconnect()
{
 
}

void msController::control(const Transfer_t *transfer)
{
 

}

What do I need to write on the main code to get rid of the MassStorageDriver.cpp?

This is the current enumeration.cpp, only changes are the debug prints.

Code:
/* USB EHCI Host for Teensy 3.6
 * Copyright 2017 Paul Stoffregen (paul@pjrc.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <Arduino.h>
#include "USBHost_t36_.h"  // Read this header first for key info


// USB devices are managed from this file.


// List of all connected devices, regardless of their status.  If
// it's connected to the EHCI port or any port on any hub, it needs
// to be linked into this list.
static Device_t  *devlist=NULL;

// List of all inactive drivers.  At the end of enumeration, when
// drivers claim the device or its interfaces, they are removed
// from this list and linked into the list of active drivers on
// that device.  When devices disconnect, the drivers are returned
// to this list, making them again available for enumeration of new
// devices.
static USBDriver *available_drivers = NULL;

// Static buffers used during enumeration.  One a single USB device
// may enumerate at once, because USB address zero is used, and
// because this static buffer & state info can't be shared.
static uint8_t enumbuf[512] __attribute__ ((aligned(16)));
static setup_t enumsetup __attribute__ ((aligned(16)));
static uint16_t enumlen;

// True while any device is present but not yet fully configured.
// Only one USB device may be in this state at a time (responding
// to address zero) and using the enumeration static buffer.
volatile bool USBHost::enumeration_busy = false;



static void pipe_set_maxlen(Pipe_t *pipe, uint32_t maxlen);
static void pipe_set_addr(Pipe_t *pipe, uint32_t addr);

#define FPRINT 

// The main user function to cause internal state to update.  Since we do
// almost everything with DMA and interrupts, the only work to do here is
// call all the active driver Task() functions.
void USBHost::Task()
{
	for (Device_t *dev = devlist; dev; dev = dev->next) {
		for (USBDriver *driver = dev->drivers; driver; driver = driver->next) {
			(driver->Task)();
		}
	}
}

// Drivers call this after they've completed initialization, so get themselves
// added to the list of inactive drivers available for new devices during
// enumeraton.  Typically this is called from constructors, so hardware access
// or even printing debug messages should be avoided here.  Just initialize
// lists and return.
//
void USBHost::driver_ready_for_device(USBDriver *driver)
{
	driver->device = NULL;
	driver->next = NULL;
	if (available_drivers == NULL) {
		available_drivers = driver;
	} else {
		// append to end of list
		USBDriver *last = available_drivers;
		while (last->next) last = last->next;
		last->next = driver;
	}
  #ifdef FPRINT 
  Serial.print("USBHost::driver_ready_for_device"); Serial.printf(F("  driver:  %10.10x\n"),driver);
  #endif
}

// Create a new device and begin the enumeration process
//
Device_t * USBHost::new_Device(uint32_t speed, uint32_t hub_addr, uint32_t hub_port)
{
  #ifdef FPRINT 
  Serial.println("USBHost::new_Device"); 
  #endif
	Device_t *dev;
  
   #ifdef FPRINT 
    Serial.print("  new_Device:    ");
  switch (speed) {
    case 0: Serial.print("12"); break;
    case 1: Serial.print("1.5"); break;
    case 2: Serial.print("480"); break;
    default: Serial.print("??");
  }
  Serial.println(" Mbit/sec");
  #endif
  
	dev = allocate_Device();
	if (!dev) return NULL;
	memset(dev, 0, sizeof(Device_t));
	dev->speed = speed;
	dev->address = 0;
	dev->hub_address = hub_addr;
	dev->hub_port = hub_port;
	dev->control_pipe = new_Pipe(dev, 0, 0, 0, 8);
	if (!dev->control_pipe) {
		free_Device(dev);
		return NULL;
	}
	dev->strbuf = allocate_string_buffer();  // try to allocate a string buffer; 
	dev->control_pipe->callback_function = &enumeration;
	dev->control_pipe->direction = 1; // 1=IN
	// Here is where the enumeration process officially begins.
	// Only a single device can enumerate at a time.
	USBHost::enumeration_busy = true;
	mk_setup(enumsetup, 0x80, 6, 0x0100, 0, 8); // 6=GET_DESCRIPTOR
	queue_Control_Transfer(dev, &enumsetup, enumbuf, NULL);
	if (devlist == NULL) {
		devlist = dev;
	} else {
		Device_t *p;
		for (p = devlist; p->next; p = p->next) ; // walk devlist
		p->next = dev;
	}
  #ifdef FPRINT 
  Serial.printf(F("  dev:           %10.10x\n"),dev);
  Serial.printf(F("  hub_addr:      %10.10x\n"),hub_addr);
  Serial.printf(F("  hub_port:      %10.10x\n"),hub_port);
  Serial.printf(F("  control_pipe:  %10.10x\n"),dev->control_pipe);
  #endif
	return dev;
}


// Control transfer callback function.  ALL control transfers from all
// devices call this function when they complete.  When control transfers
// are created by drivers, the driver is called to handle the result.
// Otherwise, the control transfer is part of the enumeration process,
// which is implemented here.
//
void USBHost::enumeration(const Transfer_t *transfer)
{
      #ifdef FPRINT 
      Serial.println("USBHost::enum:   START");
      Serial.printf(F("  transfer:      %10.10x\n"),transfer);
      Serial.printf(F("  buffer:        %10.10x\n"),transfer->buffer);
      Serial.printf(F("  length:        %10.10x\n"),transfer->length);
      #endif
	Device_t *dev;
	uint32_t len;

	// If a driver created this control transfer, allow it to process the result
	if (transfer->driver) {
		transfer->driver->control(transfer);
		return;
	}


	//print_hexbytes(transfer->buffer, transfer->length);

	dev = transfer->pipe->device;

	while (1) {
  
		// Within this large switch/case, "break" means we've done
		// some work, but more remains to be done in a different
		// state.  Generally break is used after parsing received
		// data, but what happens next could be different states.
		// When completed, return is used.  Generally, return happens
		// only after a new control transfer is queued, or when
		// enumeration is complete and no more communication is needed.
		switch (dev->enum_state) {
		case 0: // read 8 bytes of device desc, set max packet, and send set address
			pipe_set_maxlen(dev->control_pipe, enumbuf[7]);
     
			mk_setup(enumsetup, 0, 5,assign_address() , 0, 0); // 5=SET_ADDRESS
			queue_Control_Transfer(dev, &enumsetup, NULL, NULL);
      #ifdef FPRINT 
      Serial.print("0__USBHost::enumeration: "); Serial.println("        read 8 bytes of device desc"); 
      Serial.printf(F("  enumbuf:       %10.10x\n"),enumbuf);
      Serial.printf(F("  enumsetup:     %10.10x\n"),enumsetup);
      #endif
      
			dev->enum_state = 1;
			return;
		case 1: // request 18 device device descriptor
      #ifdef FPRINT 
      Serial.print("1__USBHost::enumeration:         ");Serial.println("request device descriptor"); 
      Serial.printf(F("  wValue:        %10.10x\n"),enumsetup.wValue);
      #endif
			dev->address = enumsetup.wValue;
			pipe_set_addr(dev->control_pipe, enumsetup.wValue);
			mk_setup(enumsetup, 0x80, 6, 0x0100, 0, 18); // 6=GET_DESCRIPTOR
			queue_Control_Transfer(dev, &enumsetup, enumbuf, NULL);
			dev->enum_state = 2;
			return;
		case 2: // parse 18 device desc bytes

			print_device_descriptor(enumbuf);
			dev->bDeviceClass = enumbuf[4];
			dev->bDeviceSubClass = enumbuf[5];
			dev->bDeviceProtocol = enumbuf[6];
			dev->idVendor = enumbuf[8] | (enumbuf[9] << 8);
			dev->idProduct = enumbuf[10] | (enumbuf[11] << 8);
			enumbuf[0] = enumbuf[14];
			enumbuf[1] = enumbuf[15];
			enumbuf[2] = enumbuf[16];

      #ifdef FPRINT 
      Serial.print("2__USBHost::enumeration:         ");Serial.println("parse 18 device desc bytes"); 
      Serial.printf(F("  bDeviceClass:  %10.10x\n"),dev->bDeviceClass);
      Serial.printf(F("  bD_SubClass:   %10.10x\n"),dev->bDeviceSubClass);
      Serial.printf(F("  bD_Protocol:   %10.10x\n"),dev->bDeviceProtocol);
      Serial.printf(F("  idVendor:      %10.10x\n"),dev->idVendor);
      Serial.printf(F("  idProduct:     %10.10x\n"),dev->idProduct);
      #endif
			if ((enumbuf[0] | enumbuf[1] | enumbuf[2]) > 0) {
				dev->enum_state = 3;
			} else {
				dev->enum_state = 11;
			}
			break;
		case 3: // request Language ID
      #ifdef FPRINT 
      Serial.print("3__USBHost::enumeration:         "); Serial.println("request Language ID"); 
      #endif
			len = sizeof(enumbuf) - 4;
			mk_setup(enumsetup, 0x80, 6, 0x0300, 0, len); // 6=GET_DESCRIPTOR
			queue_Control_Transfer(dev, &enumsetup, enumbuf + 4, NULL);
			dev->enum_state = 4;
			return;
		case 4: // parse Language ID
      #ifdef FPRINT 
      Serial.print("4__USBHost::enumeration:         ");Serial.println("parse Language ID"); 
      Serial.printf(F("  enumbuf:       %10.10x\n"),enumbuf);
      #endif
			if (enumbuf[4] < 4 || enumbuf[5] != 3) {
				dev->enum_state = 11;
			} else {
				dev->LanguageID = enumbuf[6] | (enumbuf[7] << 8);
				if (enumbuf[0]) dev->enum_state = 5;
				else if (enumbuf[1]) dev->enum_state = 7;
				else if (enumbuf[2]) dev->enum_state = 9;
				else dev->enum_state = 11;
			}
			break;
		case 5: // request Manufacturer string

			len = sizeof(enumbuf) - 4;
			mk_setup(enumsetup, 0x80, 6, 0x0300 | enumbuf[0], dev->LanguageID, len);
			queue_Control_Transfer(dev, &enumsetup, enumbuf + 4, NULL);
      #ifdef FPRINT 
      Serial.print("5__USBHost::enumeration:         "); Serial.println("request Manufacturer string"); 
      Serial.printf(F("  LanguageID:    %10.10x\n"),dev->LanguageID);
      #endif
     
			dev->enum_state = 6;
			return;
		case 6: // parse Manufacturer string
      #ifdef FPRINT 
      Serial.print("6__USBHost::enumeration:         ");Serial.println("parse Manufacturer string");   
      Serial.printf(F("  transfer:      %10.10x\n"),transfer);
      #endif 
			print_string_descriptor("Manufacturer: ", enumbuf + 4);
			convertStringDescriptorToASCIIString(0, dev, transfer);
			// TODO: receive the string...
     
			if (enumbuf[1]) dev->enum_state = 7;
			else if (enumbuf[2]) dev->enum_state = 9;
			else dev->enum_state = 11;
			break;
		case 7: // request Product string
      #ifdef FPRINT 
      Serial.print("7__USBHost::enumeration:         ");Serial.println("request Product string"); 
      #endif
			len = sizeof(enumbuf) - 4;
			mk_setup(enumsetup, 0x80, 6, 0x0300 | enumbuf[1], dev->LanguageID, len);
			queue_Control_Transfer(dev, &enumsetup, enumbuf + 4, NULL);
			dev->enum_state = 8;
			return;
		case 8: // parse Product string
      #ifdef FPRINT 
      Serial.print("8__USBHost::enumeration:         ");Serial.println("parse Product string"); 
      Serial.printf(F("  transfer:      %10.10x\n"),transfer);
      #endif
			print_string_descriptor("Product: ", enumbuf + 4);
			convertStringDescriptorToASCIIString(1, dev, transfer);

			if (enumbuf[2]) dev->enum_state = 9;
			else dev->enum_state = 11;
			break;
		case 9: // request Serial Number string
      #ifdef FPRINT 
      Serial.print("9__USBHost::enumeration:         "); Serial.println("request Serial Number string"); 
      #endif
			len = sizeof(enumbuf) - 4;
			mk_setup(enumsetup, 0x80, 6, 0x0300 | enumbuf[2], dev->LanguageID, len);
			queue_Control_Transfer(dev, &enumsetup, enumbuf + 4, NULL);
			dev->enum_state = 10;
			return;
		case 10: // parse Serial Number string
      #ifdef FPRINT 
      Serial.print("10_USBHost::enumeration:         "); Serial.println("parse Serial Number string"); 
      Serial.printf(F("  transfer:      %10.10x\n"),transfer);
      #endif
			print_string_descriptor("Serial Number: ", enumbuf + 4);
			convertStringDescriptorToASCIIString(2, dev, transfer);

			dev->enum_state = 11;
			break;
		case 11: // request first 9 bytes of config desc
      #ifdef FPRINT 
      Serial.print("11_USBHost::enumeration:         "); Serial.println("request first 9 bytes of config desc"); 
      #endif
			mk_setup(enumsetup, 0x80, 6, 0x0200, 0, 9); // 6=GET_DESCRIPTOR
			queue_Control_Transfer(dev, &enumsetup, enumbuf, NULL);
			dev->enum_state = 12;
			return;
		case 12: // read 9 bytes, request all of config desc

			enumlen = enumbuf[2] | (enumbuf[3] << 8);
      #ifdef FPRINT 
      Serial.print("12_USBHost::enumeration:         "); Serial.println("read 9 bytes, request all of config desc"); 
      Serial.printf(F("  enumbuf:       %10.10x\n"),enumbuf);
      Serial.printf(F("  lenght:        %10.10x\n"),enumlen);
      #endif
			
			if (enumlen > sizeof(enumbuf)) {
				enumlen = sizeof(enumbuf);
				// TODO: how to handle device with too much config data
			}
			mk_setup(enumsetup, 0x80, 6, 0x0200, 0, enumlen); // 6=GET_DESCRIPTOR
			queue_Control_Transfer(dev, &enumsetup, enumbuf, NULL);
			dev->enum_state = 13;
			return;
		case 13: // read all config desc, send set config

			print_config_descriptor(enumbuf, sizeof(enumbuf));
			dev->bmAttributes = enumbuf[7];
			dev->bMaxPower = enumbuf[8];
			// TODO: actually do something with interface descriptor?
			mk_setup(enumsetup, 0, 9, enumbuf[5], 0, 0); // 9=SET_CONFIGURATION
			queue_Control_Transfer(dev, &enumsetup, NULL, NULL);
      #ifdef FPRINT 
      Serial.print("13_USBHost::enumeration:         "); Serial.println("read all config desc, send set config"); 
      Serial.printf(F("  bmAttributes:  %10.10x\n"),dev->bmAttributes);
      Serial.printf(F("  bMaxPower:     %10.10x\n"),dev->bMaxPower);
      #endif
			dev->enum_state = 14;
			return;
		case 14: // device is now configured
      #ifdef FPRINT 
      Serial.print("14_USBHost::enumeration:         "); Serial.println("device is now configured"); 
      #endif
			claim_drivers(dev);
			dev->enum_state = 15;
			// unlock exclusive access to enumeration process.  If any
			// more devices are waiting, the hub driver is responsible
			// for resetting their ports and starting their enumeration
			// when the port enables.
			USBHost::enumeration_busy = false;
			return;
		case 15: // control transfers for other stuff?
      #ifdef FPRINT 
      Serial.print("15_USBHost::enumeration:         "); Serial.println("control transfers for other stuff?"); 
      #endif
			// TODO: handle other standard control: set/clear feature, etc
		default:
			return;
		}
	}
}

void  USBHost::convertStringDescriptorToASCIIString(uint8_t string_index, Device_t *dev, const Transfer_t *transfer) {
	strbuf_t *strbuf = dev->strbuf; 
	if (!strbuf) return;	// don't have a buffer

	uint8_t *buffer = (uint8_t*)transfer->buffer;
	uint8_t buf_index = string_index? strbuf->iStrings[string_index]+1 : 0;

	// Try to verify - The first byte should be length and the 2nd byte should be 0x3
	if (!buffer || (buffer[1] != 0x3)) {
		return;	// No string so can simply return
	}

	strbuf->iStrings[string_index] = buf_index;	// remember our starting positio
	uint8_t count_bytes_returned = buffer[0];
	if ((buf_index + count_bytes_returned/2) >= DEVICE_STRUCT_STRING_BUF_SIZE)
		count_bytes_returned = (DEVICE_STRUCT_STRING_BUF_SIZE - buf_index) * 2;

	// Now copy into our storage buffer. 
	for (uint8_t i = 2; (i < count_bytes_returned) && (buf_index < (DEVICE_STRUCT_STRING_BUF_SIZE -1)); i += 2) {
		strbuf->buffer[buf_index++] = buffer[i];
	} 
	strbuf->buffer[buf_index] = 0;	// null terminate. 

	// Update other indexes to point to null character
	while (++string_index < 3) {
		strbuf->iStrings[string_index] = buf_index;	// point to trailing NULL character
	}
 Serial.printf(F("  descriptor:  %10.10s\n"),strbuf->buffer);
}


void USBHost::claim_drivers(Device_t *dev)
{
	USBDriver *driver, *prev=NULL;

	// first check if any driver wishes to claim the entire device
	for (driver=available_drivers; driver != NULL; driver = driver->next) {
		if (driver->device != NULL) continue;
		if (driver->claim(dev, 0, enumbuf + 9, enumlen - 9)) {
			if (prev) {
				prev->next = driver->next;
			} else {
				available_drivers = driver->next;
			}
			driver->device = dev;
			driver->next = NULL;
			dev->drivers = driver;
			return;
		}
		prev = driver;
	}
	// parse interfaces from config descriptor
	const uint8_t *p = enumbuf + 9;
	const uint8_t *end = enumbuf + enumlen;
	while (p < end) {
		uint8_t desclen = *p;
		uint8_t desctype = *(p+1);

      #ifdef FPRINT 
      Serial.println("USBHost::claim_drivers:         "); 
      Serial.printf(F("  Descriptor:    %10.10x"),desctype);
      Serial.print(" = ");
      if (desctype == 4) Serial.println("INTERFACE");
      else if (desctype == 5) Serial.println("ENDPOINT");
      else if (desctype == 6) Serial.println("DEV_QUALIFIER");
      else if (desctype == 7) Serial.println("OTHER_SPEED");
      else if (desctype == 11) Serial.println("IAD");
      else if (desctype == 33) Serial.println("HID");
      else Serial.println(" ???");
      
      #endif

		if (desctype == 11 && desclen == 8) {
			// TODO: parse IAD, ask drivers for claim
			// TODO: how to skip over all interfaces IAD represented
		}
		if (desctype == 4 && desclen == 9) {
			// found an interface, ask available drivers if they want it
			prev = NULL;
			for (driver=available_drivers; driver != NULL; driver = driver->next) {
				if (driver->device != NULL) continue;
				// TODO: should parse ahead and give claim()
				// an accurate length.  (end - p) is the rest
				// of ALL descriptors, likely more interfaces
				// this driver has no business parsing
				if (driver->claim(dev, 1, p, end - p)) {
					// this driver claims iface
					// remove it from available_drivers list
					if (prev) {
						prev->next = driver->next;
					} else {
						available_drivers = driver->next;
					}
					// add to list of drivers using this device
					driver->next = dev->drivers;
					dev->drivers = driver;
					driver->device = dev;
					// not done, may be more interface for more drivers
				}
				prev = driver;
			}
		}
		p += desclen;
	}

}

static bool address_in_use(uint32_t addr)
{
	for (Device_t *p = devlist; p; p = p->next) {
		if (p->address == addr) return true;
	}
	return false;
}

uint32_t USBHost::assign_address(void)
{
	static uint8_t last_assigned_address=0;
	uint32_t addr = last_assigned_address;
	while (1) {
		if (++addr > 127) addr = 1;
		if (!address_in_use(addr)) {
			last_assigned_address = addr;
      #ifdef FPRINT 
      Serial.println("USBHost::assign_address:      "); Serial.printf(F("  addres:        %10.10x\n"),addr);
      #endif
      
			return addr;
		}
	}
}

static void pipe_set_maxlen(Pipe_t *pipe, uint32_t maxlen)
{ 
      #ifdef FPRINT 
      Serial.printf(F("  pipe_set_maxlen: %10.10d\n"),maxlen);
      #endif
	pipe->qh.capabilities[0] = (pipe->qh.capabilities[0] & 0xF800FFFF) | (maxlen << 16);
}

static void pipe_set_addr(Pipe_t *pipe, uint32_t addr)
{
      #ifdef FPRINT 
      Serial.printf(F("  pipe_set_addr: %10.10d\n"),addr);
      #endif
	pipe->qh.capabilities[0] = (pipe->qh.capabilities[0] & 0xFFFFFF80) | addr;
}


void USBHost::disconnect_Device(Device_t *dev)
{
      #ifdef FPRINT 
      Serial.printf(F("USBHost::disconnect_Device:  %10.10x\n"),dev);
      #endif
	if (!dev) return;


	// Disconnect all drivers using this device.  If this device is
	// a hub, the hub driver is responsible for recursively calling
	// this function to disconnect its downstream devices.
	print_driverlist("available_drivers", available_drivers);
	print_driverlist("dev->drivers", dev->drivers);
	for (USBDriver *p = dev->drivers; p; ) {

    #ifdef FPRINT 
    Serial.printf(F("disconnect driver:           %10.10x\n"),p);
    #endif
		p->disconnect();
		p->device = NULL;
		USBDriver *next = p->next;
		p->next = available_drivers;
		available_drivers = p;
		p = next;
	}
	print_driverlist("available_drivers", available_drivers);

	// delete all the pipes
	for (Pipe_t *p = dev->data_pipes; p; ) {
		Pipe_t *next = p->next;
		delete_Pipe(p);
		p = next;
	}
	delete_Pipe(dev->control_pipe);

	// remove device from devlist and free its Device_t
	Device_t *prev_dev = NULL;
	for (Device_t *p = devlist; p; p = p->next) {
		if (p == dev) {
			if (prev_dev == NULL) {
				devlist = p->next;
			} else {
				prev_dev->next = p->next;
			}
     
      #ifdef FPRINT 
			Serial.println("removed Device_t from devlist");
      #endif
      
			if (p->strbuf != nullptr ) {
				free_string_buffer(p->strbuf);
			}
			free_Device(p);
			break;
		}
		prev_dev = p;
	}
}
 
I got it finally to the point that the USBHost_t36 does its thing, detects attachment or removal of USB device and gets basic information. This does not include yet any attempt to actually implement the PTP but will see that next.

Screenshot 2021-08-14 at 10.44.28.jpg

However this runs currently only with the local implementation and modified USBHost_t36_.h where I have removed everything else than needed.

Any Idea why this code does not compile with the actual USBHost_t36 library. (/CameraTesting2.ino: In member function 'virtual bool cameraPTP::claim(Device_t*, int, const uint8_t*, uint32_t)':)

The class is probably missing a lot of definitions to make the interface actually to work, It would be really helpful to have this kind of single .ino file with some basic functionality and comments for any interface, to see how things are done.

Code:
#include "USBHost_t36_.h"

USBHost myusb;
//USBHub hub1(myusb);

class cameraPTP : public USBDriver {
public:
  cameraPTP(USBHost &host) { init(); }
  cameraPTP(USBHost *host) { init(); }

protected:
  virtual bool claim(Device_t *device, int type, const uint8_t *descriptors, uint32_t len);
  virtual void control(const Transfer_t *transfer);
  virtual void disconnect();
  void init();

private:
  Transfer_t mytransfers[7] __attribute__ ((aligned(32)));
  strbuf_t mystring_bufs[1];
  Pipe_t mypipes[3] __attribute__ ((aligned(32)));
};

cameraPTP camera1(myusb);

void setup() {
	while(!Serial);
	myusb.begin();
}

void loop() {}

void cameraPTP::init(){ 
  contribute_Pipes(mypipes, sizeof(mypipes)/sizeof(Pipe_t));
  contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t)); 
  contribute_String_Buffers(mystring_bufs, sizeof(mystring_bufs)/sizeof(strbuf_t));
  driver_ready_for_device(this);
  }

bool cameraPTP::claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len){}

void cameraPTP::disconnect(){}

void cameraPTP::control(const Transfer_t *transfer){}

This is the USBHost_t36_.h, on the other files only debug printing has been edited.

Code:
/* USB EHCI Host for Teensy 3.6
 * Copyright 2017 Paul Stoffregen (paul@pjrc.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef USB_HOST_TEENSY36_
#define USB_HOST_TEENSY36_

#include <stdint.h>

#if !defined(__MK66FX1M0__) && !defined(__IMXRT1052__) && !defined(__IMXRT1062__)
#error "USBHost_t36 only works with Teensy 3.6 or Teensy 4.x.  Please select it in Tools > Boards"
#endif



// Dear inquisitive reader, USB is a complex protocol defined with
// very specific terminology.  To have any chance of understand this
// source code, you absolutely must have solid knowledge of specific
// USB terms such as host, device, endpoint, pipe, enumeration....
// You really must also have at least a basic knowledge of the
// different USB transfers: control, bulk, interrupt, isochronous.
//
// The USB 2.0 specification explains these in chapter 4 (pages 15
// to 24), and provides more detail in the first part of chapter 5
// (pages 25 to 55).  The USB spec is published for free at
// www.usb.org.  Here is a convenient link to just the main PDF:
//
// https://www.pjrc.com/teensy/beta/usb20.pdf
//
// This is a huge file, but chapter 4 is short and easy to read.
// If you're not familiar with the USB lingo, please do yourself
// a favor by reading at least chapter 4 to get up to speed on the
// meaning of these important USB concepts and terminology.
//
// If you wish to ask questions (which belong on the forum, not
// github issues) or discuss development of this library, you
// ABSOLUTELY MUST know the basic USB terminology from chapter 4.
// Please repect other people's valuable time & effort by making
// your best effort to read chapter 4 before asking USB questions!


// Uncomment this line to see lots of debugging info!
//#define USBHOST_PRINT_DEBUG


// This can let you control where to send the debugging messages
//#define USBHDBGSerial	Serial1
#ifndef USBHDBGSerial
#define USBHDBGSerial	Serial
#endif







#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
 
// Allow USB host code written for "USBHS" on Teensy 3.6 to compile for "USB2" on Teensy 4.0

#define IRQ_USBHS    IRQ_USB2

#define USBPHY_CTRL   USBPHY2_CTRL
#define USBPHY_CTRL_CLR   USBPHY2_CTRL_CLR
#define USBPHY_CTRL_SET   USBPHY2_CTRL_SET

#define USBHS_USBCMD    USB2_USBCMD
#define USBHS_USBSTS    USB2_USBSTS
#define USBHS_USBINTR   USB2_USBINTR
#define USBHS_FRINDEX   USB2_FRINDEX
#define USBHS_PERIODICLISTBASE  USB2_PERIODICLISTBASE
#define USBHS_ASYNCLISTADDR USB2_ASYNCLISTADDR
#define USBHS_PORTSC1   USB2_PORTSC1
#define USBHS_USBMODE   USB2_USBMODE
#define USBHS_GPTIMER0CTL USB2_GPTIMER0CTRL
#define USBHS_GPTIMER0LD  USB2_GPTIMER0LD
#define USBHS_GPTIMER1CTL USB2_GPTIMER1CTRL
#define USBHS_GPTIMER1LD  USB2_GPTIMER1LD

#define USBHS_USBCMD_ASE  USB_USBCMD_ASE
#define USBHS_USBCMD_IAA  USB_USBCMD_IAA
#define USBHS_USBCMD_RST  USB_USBCMD_RST
#define USBHS_USBCMD_ITC(n) USB_USBCMD_ITC(n)
#define USBHS_USBCMD_RS   USB_USBCMD_RS
#define USBHS_USBCMD_ASP(n) USB_USBCMD_ASP(n)
#define USBHS_USBCMD_ASPE USB_USBCMD_ASPE
#define USBHS_USBCMD_PSE  USB_USBCMD_PSE
#define USBHS_USBCMD_FS2  USB_USBCMD_FS_2
#define USBHS_USBCMD_FS(n)  USB_USBCMD_FS_1(n)    

#define USBHS_USBSTS_AAI  USB_USBSTS_AAI
#define USBHS_USBSTS_AS   USB_USBSTS_AS
// UAI & UPI bits are undocumented in IMXRT, K66 pg 1602, RT1050 pg 2374
#define USBHS_USBSTS_UAI  ((uint32_t)(1<<18))
#define USBHS_USBSTS_UPI  ((uint32_t)(1<<19))
#define USBHS_USBSTS_UEI  USB_USBSTS_UEI
#define USBHS_USBSTS_PCI  USB_USBSTS_PCI
#define USBHS_USBSTS_TI0  USB_USBSTS_TI0
#define USBHS_USBSTS_TI1  USB_USBSTS_TI1
#define USBHS_USBSTS_SEI  USB_USBSTS_SEI
#define USBHS_USBSTS_URI  USB_USBSTS_URI
#define USBHS_USBSTS_SLI  USB_USBSTS_SLI
#define USBHS_USBSTS_HCH  USB_USBSTS_HCH
#define USBHS_USBSTS_NAKI USB_USBSTS_NAKI

#define USBHS_USBINTR_PCE USB_USBINTR_PCE
#define USBHS_USBINTR_TIE0  USB_USBINTR_TIE0
#define USBHS_USBINTR_TIE1  USB_USBINTR_TIE1
#define USBHS_USBINTR_UEE USB_USBINTR_UEE
#define USBHS_USBINTR_SEE USB_USBINTR_SEE
#define USBHS_USBINTR_UPIE  USB_USBINTR_UPIE
#define USBHS_USBINTR_UAIE  USB_USBINTR_UAIE

#define USBHS_PORTSC_PFSC USB_PORTSC1_PFSC
#define USBHS_PORTSC_PP   USB_PORTSC1_PP
#define USBHS_PORTSC_OCC  USB_PORTSC1_OCC
#define USBHS_PORTSC_PEC  USB_PORTSC1_PEC
#define USBHS_PORTSC_CSC  USB_PORTSC1_CSC
#define USBHS_PORTSC_CCS  USB_PORTSC1_CCS
#define USBHS_PORTSC_PE   USB_PORTSC1_PE
#define USBHS_PORTSC_HSP  USB_PORTSC1_HSP
#define USBHS_PORTSC_FPR  USB_PORTSC1_FPR
#define USBHS_PORTSC_PR   USB_PORTSC1_PR

#define USBHS_GPTIMERCTL_RST  USB_GPTIMERCTRL_GPTRST
#define USBHS_GPTIMERCTL_RUN  USB_GPTIMERCTRL_GPTRUN

#define USBHS_USBMODE_CM(n) USB_USBMODE_CM(n)

// TODO: what is the best setting for this register on IMXRT ???
#define USBHS_USB_SBUSCFG USB2_SBUSCFG


#endif // __IMXRT1052__ or __IMXRT1062__




/************************************************/
/*  Data Types                                  */
/************************************************/

// These 6 types are the key to understanding how this USB Host
// library really works.

// USBHost is a static class controlling the hardware.
// All common USB functionality is implemented here.
class USBHost;

// These 3 structures represent the actual USB entities
// USBHost manipulates.  One Device_t is created for
// each active USB device.  One Pipe_t is create for
// each endpoint.  Transfer_t structures are created
// when any data transfer is added to the EHCI work
// queues, and then returned to the free pool after the
// data transfer completes and the driver has processed
// the results.
typedef struct Device_struct       Device_t;
typedef struct Pipe_struct         Pipe_t;
typedef struct Transfer_struct     Transfer_t;
typedef enum { CLAIM_NO=0, CLAIM_REPORT, CLAIM_INTERFACE} hidclaim_t;

// All USB device drivers inherit use these classes.
// Drivers build user-visible functionality on top
// of these classes, which receive USB events from
// USBHost.
class USBDriver;
class USBDriverTimer;
class USBHIDInput;





/************************************************/
/*  Data Structure Definitions                  */
/************************************************/

// setup_t holds the 8 byte USB SETUP packet data.
// These unions & structs allow convenient access to
// the setup fields.
typedef union {
 struct {
  union {
   struct {
        uint8_t bmRequestType;
        uint8_t bRequest;
   };
        uint16_t wRequestAndType;
  };
        uint16_t wValue;
        uint16_t wIndex;
        uint16_t wLength;
 };
 struct {
        uint32_t word1;
        uint32_t word2;
 };
} setup_t;

typedef struct {
	enum {STRING_BUF_SIZE=50};
	enum {STR_ID_MAN=0, STR_ID_PROD, STR_ID_SERIAL, STR_ID_CNT};
	uint8_t iStrings[STR_ID_CNT];	// Index into array for the three indexes
	uint8_t buffer[STRING_BUF_SIZE];
} strbuf_t;

#define DEVICE_STRUCT_STRING_BUF_SIZE 50

// Device_t holds all the information about a USB device
struct Device_struct {
	Pipe_t   *control_pipe;
	Pipe_t   *data_pipes;
	Device_t *next;
	USBDriver *drivers;
	strbuf_t *strbuf;
	uint8_t  speed; // 0=12, 1=1.5, 2=480 Mbit/sec
	uint8_t  address;
	uint8_t  hub_address;
	uint8_t  hub_port;
	uint8_t  enum_state;
	uint8_t  bDeviceClass;
	uint8_t  bDeviceSubClass;
	uint8_t  bDeviceProtocol;
	uint8_t  bmAttributes;
	uint8_t  bMaxPower;
	uint16_t idVendor;
	uint16_t idProduct;
	uint16_t LanguageID;
};

// Pipe_t holes all information about each USB endpoint/pipe
// The first half is an EHCI QH structure for the pipe.
struct Pipe_struct {
	// Queue Head (QH), EHCI page 46-50
	struct {  // must be aligned to 32 byte boundary
		volatile uint32_t horizontal_link;
		volatile uint32_t capabilities[2];
		volatile uint32_t current;
		volatile uint32_t next;
		volatile uint32_t alt_next;
		volatile uint32_t token;
		volatile uint32_t buffer[5];
	} qh;
	Device_t *device;
	uint8_t  type; // 0=control, 1=isochronous, 2=bulk, 3=interrupt
	uint8_t  direction; // 0=out, 1=in (changes for control, others fixed)
	uint8_t  start_mask;
	uint8_t  complete_mask;
	Pipe_t   *next;
	void     (*callback_function)(const Transfer_t *);
	uint16_t periodic_interval;
	uint16_t periodic_offset;
	uint16_t bandwidth_interval;
	uint16_t bandwidth_offset;
	uint16_t bandwidth_shift;
	uint8_t  bandwidth_stime;
	uint8_t  bandwidth_ctime;
	uint32_t unused1;
	uint32_t unused2;
	uint32_t unused3;
	uint32_t unused4;
	uint32_t unused5;
};

// Transfer_t represents a single transaction on the USB bus.
// The first portion is an EHCI qTD structure.  Transfer_t are
// allocated as-needed from a memory pool, loaded with pointers
// to the actual data buffers, linked into a followup list,
// and placed on ECHI Queue Heads.  When the ECHI interrupt
// occurs, the followup lists are used to find the Transfer_t
// in memory.  Callbacks are made, and then the Transfer_t are
// returned to the memory pool.
struct Transfer_struct {
	// Queue Element Transfer Descriptor (qTD), EHCI pg 40-45
	struct {  // must be aligned to 32 byte boundary
		volatile uint32_t next;
		volatile uint32_t alt_next;
		volatile uint32_t token;
		volatile uint32_t buffer[5];
	} qtd;
	// Linked list of queued, not-yet-completed transfers
	Transfer_t *next_followup;
	Transfer_t *prev_followup;
	Pipe_t     *pipe;
	// Data to be used by callback function.  When a group
	// of Transfer_t are created, these fields and the
	// interrupt-on-complete bit in the qTD token are only
	// set in the last Transfer_t of the list.
	void       *buffer;
	uint32_t   length;
	setup_t    setup;
	USBDriver  *driver;
};


/************************************************/
/*  Main USB EHCI Controller                    */
/************************************************/

class USBHost {
public:
	static void begin();
	static void Task();
	static void countFree(uint32_t &devices, uint32_t &pipes, uint32_t &trans, uint32_t &strs);
protected:
	static Pipe_t * new_Pipe(Device_t *dev, uint32_t type, uint32_t endpoint,
		uint32_t direction, uint32_t maxlen, uint32_t interval=0);
	static bool queue_Control_Transfer(Device_t *dev, setup_t *setup,
		void *buf, USBDriver *driver);
	static bool queue_Data_Transfer(Pipe_t *pipe, void *buffer,
		uint32_t len, USBDriver *driver);
	static Device_t * new_Device(uint32_t speed, uint32_t hub_addr, uint32_t hub_port);
	static void disconnect_Device(Device_t *dev);
	static void enumeration(const Transfer_t *transfer);
	static void driver_ready_for_device(USBDriver *driver);
	static volatile bool enumeration_busy;
public: // Maybe others may want/need to contribute memory example HID devices may want to add transfers.
	static void contribute_Devices(Device_t *devices, uint32_t num);
	static void contribute_Pipes(Pipe_t *pipes, uint32_t num);
	static void contribute_Transfers(Transfer_t *transfers, uint32_t num);
	static void contribute_String_Buffers(strbuf_t *strbuf, uint32_t num);
private:
	static void isr();
	static void convertStringDescriptorToASCIIString(uint8_t string_index, Device_t *dev, const Transfer_t *transfer);
	static void claim_drivers(Device_t *dev);
	static uint32_t assign_address(void);
	static bool queue_Transfer(Pipe_t *pipe, Transfer_t *transfer);
	static void init_Device_Pipe_Transfer_memory(void);
	static Device_t * allocate_Device(void);
	static void delete_Pipe(Pipe_t *pipe);
	static void free_Device(Device_t *q);
	static Pipe_t * allocate_Pipe(void);
	static void free_Pipe(Pipe_t *q);
	static Transfer_t * allocate_Transfer(void);
	static void free_Transfer(Transfer_t *q);
	static strbuf_t * allocate_string_buffer(void);
	static void free_string_buffer(strbuf_t *strbuf);
	static bool allocate_interrupt_pipe_bandwidth(Pipe_t *pipe,
		uint32_t maxlen, uint32_t interval);
	static void add_qh_to_periodic_schedule(Pipe_t *pipe);
	static bool followup_Transfer(Transfer_t *transfer);
	static void followup_Error(void);
protected:
#ifdef USBHOST_PRINT_DEBUG
	static void print_(const Transfer_t *transfer);
	static void print_(const Transfer_t *first, const Transfer_t *last);
	static void print_token(uint32_t token);
	static void print_(const Pipe_t *pipe);
	static void print_driverlist(const char *name, const USBDriver *driver);
	static void print_qh_list(const Pipe_t *list);
	static void print_device_descriptor(const uint8_t *p);
	static void print_config_descriptor(const uint8_t *p, uint32_t maxlen);
	static void print_string_descriptor(const char *name, const uint8_t *p);
	static void print_hexbytes(const void *ptr, uint32_t len);
	static void print_(const char *s)	{ USBHDBGSerial.print(s); }
	static void print_(int n)		{ USBHDBGSerial.print(n); }
	static void print_(unsigned int n)	{ USBHDBGSerial.print(n); }
	static void print_(long n)		{ USBHDBGSerial.print(n); }
	static void print_(unsigned long n)	{ USBHDBGSerial.print(n); }
	static void println_(const char *s)	{ USBHDBGSerial.println(s); }
	static void println_(int n)		{ USBHDBGSerial.println(n); }
	static void println_(unsigned int n)	{ USBHDBGSerial.println(n); }
	static void println_(long n)		{ USBHDBGSerial.println(n); }
	static void println_(unsigned long n)	{ USBHDBGSerial.println(n); }
	static void println_()			{ USBHDBGSerial.println(); }
	static void print_(uint32_t n, uint8_t b) { USBHDBGSerial.print(n, b); }
	static void println_(uint32_t n, uint8_t b) { USBHDBGSerial.println(n, b); }
	static void print_(const char *s, int n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.print(n, b); }
	static void print_(const char *s, unsigned int n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.print(n, b); }
	static void print_(const char *s, long n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.print(n, b); }
	static void print_(const char *s, unsigned long n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.print(n, b); }
	static void println_(const char *s, int n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.println(n, b); }
	static void println_(const char *s, unsigned int n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.println(n, b); }
	static void println_(const char *s, long n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.println(n, b); }
	static void println_(const char *s, unsigned long n, uint8_t b = DEC) {
		USBHDBGSerial.print(s); USBHDBGSerial.println(n, b); }
	friend class USBDriverTimer; // for access to print & println
#else
	static void print_(const Transfer_t *transfer) {}
	static void print_(const Transfer_t *first, const Transfer_t *last) {}
	static void print_token(uint32_t token) {}
	static void print_(const Pipe_t *pipe) {}
	static void print_driverlist(const char *name, const USBDriver *driver) {}
	static void print_qh_list(const Pipe_t *list) {}
	static void print_device_descriptor(const uint8_t *p) {}
	static void print_config_descriptor(const uint8_t *p, uint32_t maxlen) {}
	static void print_string_descriptor(const char *name, const uint8_t *p) {}
	static void print_hexbytes(const void *ptr, uint32_t len) {}
	static void print_(const char *s) {}
	static void print_(int n) {}
	static void print_(unsigned int n) {}
	static void print_(long n) {}
	static void print_(unsigned long n) {}
	static void println_(const char *s) {}
	static void println_(int n) {}
	static void println_(unsigned int n) {}
	static void println_(long n) {}
	static void println_(unsigned long n) {}
	static void println_() {}
	static void print_(uint32_t n, uint8_t b) {}
	static void println_(uint32_t n, uint8_t b) {}
	static void print_(const char *s, int n, uint8_t b = DEC) {}
	static void print_(const char *s, unsigned int n, uint8_t b = DEC) {}
	static void print_(const char *s, long n, uint8_t b = DEC) {}
	static void print_(const char *s, unsigned long n, uint8_t b = DEC) {}
	static void println_(const char *s, int n, uint8_t b = DEC) {}
	static void println_(const char *s, unsigned int n, uint8_t b = DEC) {}
	static void println_(const char *s, long n, uint8_t b = DEC) {}
	static void println_(const char *s, unsigned long n, uint8_t b = DEC) {}
#endif
	static void mk_setup(setup_t &s, uint32_t bmRequestType, uint32_t bRequest,
			uint32_t wValue, uint32_t wIndex, uint32_t wLength) {
		s.word1 = bmRequestType | (bRequest << 8) | (wValue << 16);
		s.word2 = wIndex | (wLength << 16);
	}
};


/************************************************/
/*  USB Device Driver Common Base Class         */
/************************************************/

// All USB device drivers inherit from this base class.
class USBDriver : public USBHost {
public:
	operator bool() {
		Device_t *dev = *(Device_t * volatile *)&device;
		return dev != nullptr;
	}
	uint16_t idVendor() {
		Device_t *dev = *(Device_t * volatile *)&device;
		return (dev != nullptr) ? dev->idVendor : 0;
	}
	uint16_t idProduct() {
		Device_t *dev = *(Device_t * volatile *)&device;
		return (dev != nullptr) ? dev->idProduct : 0;
	}
	const uint8_t *manufacturer() {
		Device_t *dev = *(Device_t * volatile *)&device;
		if (dev == nullptr || dev->strbuf == nullptr) return nullptr;
		return &dev->strbuf->buffer[dev->strbuf->iStrings[strbuf_t::STR_ID_MAN]];
	}
	const uint8_t *product() {
		Device_t *dev = *(Device_t * volatile *)&device;
		if (dev == nullptr || dev->strbuf == nullptr) return nullptr;
		return &dev->strbuf->buffer[dev->strbuf->iStrings[strbuf_t::STR_ID_PROD]];
	}
	const uint8_t *serialNumber() {
		Device_t *dev = *(Device_t * volatile *)&device;
		if (dev == nullptr || dev->strbuf == nullptr) return nullptr;
		return &dev->strbuf->buffer[dev->strbuf->iStrings[strbuf_t::STR_ID_SERIAL]];
	}
protected:
	USBDriver() : next(NULL), device(NULL) {}
	// Check if a driver wishes to claim a device or interface or group
	// of interfaces within a device.  When this function returns true,
	// the driver is considered bound or loaded for that device.  When
	// new devices are detected, enumeration.cpp calls this function on
	// all unbound driver objects, to give them an opportunity to bind
	// to the new device.
	//   device has its vid&pid, class/subclass fields initialized
	//   type is 0 for device level, 1 for interface level, 2 for IAD
	//   descriptors points to the specific descriptor data
	virtual bool claim(Device_t *device, int type, const uint8_t *descriptors, uint32_t len);

	// When an unknown (not chapter 9) control transfer completes, this
	// function is called for all drivers bound to the device.  Return
	// true means this driver originated this control transfer, so no
	// more drivers need to be offered an opportunity to process it.
	// This function is optional, only needed if the driver uses control
	// transfers and wishes to be notified when they complete.
	virtual void control(const Transfer_t *transfer) { }

	// When any of the USBDriverTimer objects a driver creates generates
	// a timer event, this function is called.
	virtual void timer_event(USBDriverTimer *whichTimer) { }

	// When the user calls USBHost::Task, this Task function for all
	// active drivers is called, so they may update state and/or call
	// any attached user callback functions.
	virtual void Task() { }

	// When a device disconnects from the USB, this function is called.
	// The driver must free all resources it allocated and update any
	// internal state necessary to deal with the possibility of user
	// code continuing to call its API.  However, pipes and transfers
	// are the handled by lower layers, so device drivers do not free
	// pipes they created or cancel transfers they had in progress.
	virtual void disconnect();

	// Drivers are managed by this single-linked list.  All inactive
	// (not bound to any device) drivers are linked from
	// available_drivers in enumeration.cpp.  When bound to a device,
	// drivers are linked from that Device_t drivers list.
	USBDriver *next;

	// The device this object instance is bound to.  In words, this
	// is the specific device this driver is using.  When not bound
	// to any device, this must be NULL.  Drivers may set this to
	// any non-NULL value if they are in a state where they do not
	// wish to claim any device or interface (eg, if getting data
	// from the HID parser).
	Device_t *device;
	friend class USBHost;
};

// Device drivers may create these timer objects to schedule a timer call
class USBDriverTimer {
public:
	USBDriverTimer() { }
	USBDriverTimer(USBDriver *d) : driver(d) { }
	USBDriverTimer(USBHIDInput *hd) : driver(nullptr), hidinput(hd) { }

	void init(USBDriver *d) { driver = d; };
	void start(uint32_t microseconds);
	void stop();
	void *pointer;
	uint32_t integer;
	uint32_t started_micros; // testing only
private:
	USBDriver      *driver;
	USBHIDInput    *hidinput;
	uint32_t       usec;
	USBDriverTimer *next;
	USBDriverTimer *prev;
	friend class USBHost;
};




/************************************************/
/*  USB Device Drivers                          */
/************************************************/

class USBHub : public USBDriver {
public:
	USBHub(USBHost &host) : debouncetimer(this), resettimer(this) { init(); }
	USBHub(USBHost *host) : debouncetimer(this), resettimer(this) { init(); }
	// Hubs with more more than 7 ports are built from two tiers of hubs
	// using 4 or 7 port hub chips.  While the USB spec seems to allow
	// hubs to have up to 255 ports, in practice all hub chips on the
	// market are only 2, 3, 4 or 7 ports.
	enum { MAXPORTS = 7 };
	typedef uint8_t portbitmask_t;
	enum {
		PORT_OFF =        0,
		PORT_DISCONNECT = 1,
		PORT_DEBOUNCE1 =  2,
		PORT_DEBOUNCE2 =  3,
		PORT_DEBOUNCE3 =  4,
		PORT_DEBOUNCE4 =  5,
		PORT_DEBOUNCE5 =  6,
		PORT_RESET =      7,
		PORT_RECOVERY =   8,
		PORT_ACTIVE =     9
	};
protected:
	virtual bool claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len);
	virtual void control(const Transfer_t *transfer);
	virtual void timer_event(USBDriverTimer *whichTimer);
	virtual void disconnect();
	void init();
	bool can_send_control_now();
	void send_poweron(uint32_t port);
	void send_getstatus(uint32_t port);
	void send_clearstatus_connect(uint32_t port);
	void send_clearstatus_enable(uint32_t port);
	void send_clearstatus_suspend(uint32_t port);
	void send_clearstatus_overcurrent(uint32_t port);
	void send_clearstatus_reset(uint32_t port);
	void send_setreset(uint32_t port);
	void send_setinterface();
	static void callback(const Transfer_t *transfer);
	void status_change(const Transfer_t *transfer);
	void new_port_status(uint32_t port, uint32_t status);
	void start_debounce_timer(uint32_t port);
	void stop_debounce_timer(uint32_t port);
private:
	Device_t mydevices[MAXPORTS];
	Pipe_t mypipes[2] __attribute__ ((aligned(32)));
	Transfer_t mytransfers[4] __attribute__ ((aligned(32)));
	strbuf_t mystring_bufs[1];
	USBDriverTimer debouncetimer;
	USBDriverTimer resettimer;
	setup_t setup;
	Pipe_t *changepipe;
	Device_t *devicelist[MAXPORTS];
	uint32_t changebits;
	uint32_t statusbits;
	uint8_t  hub_desc[16];
	uint8_t  interface_count;
	uint8_t  interface_number;
	uint8_t  altsetting;
	uint8_t  protocol;
	uint8_t  endpoint;
	uint8_t  interval;
	uint8_t  numports;
	uint8_t  characteristics;
	uint8_t  powertime;
	uint8_t  sending_control_transfer;
	uint8_t  port_doing_reset;
	uint8_t  port_doing_reset_speed;
	uint8_t  portstate[MAXPORTS];
	portbitmask_t send_pending_poweron;
	portbitmask_t send_pending_getstatus;
	portbitmask_t send_pending_clearstatus_connect;
	portbitmask_t send_pending_clearstatus_enable;
	portbitmask_t send_pending_clearstatus_suspend;
	portbitmask_t send_pending_clearstatus_overcurrent;
	portbitmask_t send_pending_clearstatus_reset;
	portbitmask_t send_pending_setreset;
	portbitmask_t debounce_in_use;
	static volatile bool reset_busy;
};

#endif

Just a note, I think it would be nice to have the USBHost_t36.h clean like this and just make an include for the Joystick, MIDI etc. parts.

Ps. older image, did not find how to remove it.
 

Attachments

  • Screenshot 2021-08-14 at 10.09.10.jpg
    Screenshot 2021-08-14 at 10.09.10.jpg
    121.2 KB · Views: 64
Last edited:
That looks much like the library I have been using, https://github.com/PaulStoffregen/USBHost_t36 confusing that there is many versions of the same, without much explanation what is the difference "forked from PaulStoffregen/USBHost_t36"

Ah, it took me a while to understand it has USBHost_t36/MTPDevice.cpp that is interesting.

What I have done anyway now is removed the MassStorageDriver.cpp but kept the class and functions that are necessary to initialise the USB connection and will now continue from there, probably build now back to the main code some request function for USB stick, to get its size etc. and after learned how that is done, see if get the communication working in deeper level with the camera, than just getting the basic information than the library provides.

This is the debug printout from enumeration.cc with the current code for BlackMagic Pocket 4k camera.

Code:
USBHost::disconnect_Device:  0000000000
USBHost::new_Device
  new_Device:    480 Mbit/sec
  dev:           00200023bc
  hub_addr:      0000000000
  hub_port:      0000000000
  control_pipe:  0020002400
  pipe_set_maxlen: 0000000064
USBHost::assign_address:      
  addres:        0000000002
0__USBHost::enumeration:         read 8 bytes of device desc
  enumbuf:       00200021a0
  enumsetup:     0000020500
1__USBHost::enumeration:         request device descriptor
  wValue:        0000000002
  pipe_set_addr: 0000000002
2__USBHost::enumeration:         parse 18 device desc bytes
  bDeviceClass:  00000000ef
  bD_SubClass:   0000000002
  bD_Protocol:   0000000001
  idVendor:      0000001edb
  idProduct:     000000be16
3__USBHost::enumeration:         request Language ID
4__USBHost::enumeration:         parse Language ID
  enumbuf:       00200021a0
5__USBHost::enumeration:         request Manufacturer string
  LanguageID:    0000000409
6__USBHost::enumeration:         parse Manufacturer string
  transfer:      0020002500
  descriptor:  Blackmagic
7__USBHost::enumeration:         request Product string
8__USBHost::enumeration:         parse Product string
  transfer:      00200024c0
  descriptor:  Blackmagic
9__USBHost::enumeration:         request Serial Number string
10_USBHost::enumeration:         parse Serial Number string
  transfer:      0020002540
  descriptor:  Blackmagic
11_USBHost::enumeration:         request first 9 bytes of config desc
12_USBHost::enumeration:         read 9 bytes, request all of config desc
  enumbuf:       00200021a0
  lenght:        0000000082
13_USBHost::enumeration:         read all config desc, send set config
  bmAttributes:  00000000c0
  bMaxPower:     0000000048
14_USBHost::enumeration:         device is now configured
USBHost::claim_drivers:         
  Descriptor:    000000000b = IAD
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000021 = HID
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000021 = HID
USBHost::claim_drivers:         
  Descriptor:    0000000004 = INTERFACE
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::claim_drivers:         
  Descriptor:    0000000005 = ENDPOINT
USBHost::disconnect_Device:  00200023bc
removed Device_t from devlist

My problem is that I am no coder, and now learning much more c++ that I ever would want to.
 
Last edited:
Status
Not open for further replies.
Back
Top