Hi folks,
I’m new here and recently got back into working on my hobby projects. I decided to build an audio CD player using the hardware I already have.
Here’s what I’ve already have:
Atmega2560
VS1053b audio decoder module
A cheap yellow display
A USB host shield (unfortunately, a clone version—it’s all I could find in local hardware stores)
The idea is to connect my ASUS USB DVD drive to this setup and control it by sending SCSI commands to achieve basic functionality like ejecting the tray, starting the motor, and reading data from CDs.
What I’ve Achieved So Far:
Successfully tested the USB host shield using Bill Greiman's UsbFat library (GitHub link: GitHub - greiman/UsbFat: Arduino FAT32/FAT16 library for USB drives.) to read and write data on a USB flash drive. This confirms the shield is functioning correctly.
Retrieved the DVD drive descriptor and enumerated the device.
Obtained the maximum Logical Unit Number (LUN) of the drive.
Identified and set up the IN and OUT endpoints for communication.
Captured USB packets using Wireshark to analyze commands and responses.
Wireshark indicates the DVD drive has a max packet size of 512 bytes, whereas the USB host shield supports a maximum of 64 bytes. I believe this isn’t an issue since the data can likely be sent and received in chunks (e.g., commands are around 31 bytes).
Additionally, I’ve ensured the drive receives enough power using a Y-cable.
My Questions:
Is the USB host shield sufficient to communicate with the DVD drive, or do I need a more capable MCU like the Teensy 4.1? For reference, I found a complete example using the Teensy here: Teensy Example
Are there any specific limitations I should be aware of when using the USB host shield?
What I Want to Achieve:
At the very least, I’d like to:
Send basic SCSI commands (e.g., eject tray, start motor) and receive proper responses.
Read data from the DVD drive to eventually play audio CDs.
Any help, advice, or resources to guide me through this project would be greatly appreciated!
New members cannot upload files, so links have been added for convenience. You are free not to click them if you prefer.
Thanks in advance!
Main.cpp
#include <SPI.h>
#include <UsbFat.h>
#include <masstorage.h>
#include "EndpointParser.h"
// USB host objects.
USB usb;
BulkOnly bulk(&usb);
// File system.
UsbFat key(&bulk);
// Test file.
File file;
static EpInfo* gEpControl = nullptr; // For Endpoint 0 (Control)
static EpInfo* gEpIn = nullptr; // For first IN endpoint found
static EpInfo* gEpOut = nullptr; // For first OUT endpoint found
void getStringDescriptor(uint8_t addr, uint8_t index, uint16_t langID) {
uint8_t buffer[64]; // Buffer to hold the string descriptor
uint8_t result = usb.getStrDescr(addr, 0, sizeof(buffer), index, langID, buffer);
if (result) {
Serial.print(F("Failed to get string descriptor. Error code: "));
Serial.println(result, HEX);
return;
}
// Print the string descriptor
Serial.print(F("String Descriptor (Index "));
Serial.print(index);
Serial.print(F("): "));
for (uint8_t i = 2; i < buffer[0]; i += 2) { // Skip first 2 bytes (length and type)
Serial.print((char)buffer[i]); // Convert UTF-16 to ASCII (basic conversion)
}
Serial.println();
}
void enumerateDevices() {
AddressPool &addrPool = usb.GetAddressPool();
Serial.println(F("Enumerating devices..."));
for (uint8_t i = 1; i < USB_NUMDEVICES; i++) { // Address 0 is reserved
UsbDevice *device = addrPool.GetUsbDevicePtr(i);
if (device == NULL) {
continue; // No device at this address
}
// Print basic device information
Serial.print(F("Device Address: "));
Serial.println(device->address.devAddress);
Serial.print(F("Parent Address: "));
Serial.println(device->address.bmParent);
Serial.print(F("Is Hub: "));
Serial.println(device->address.bmHub ? "Yes" : "No");
Serial.print(F("Endpoint Count: "));
Serial.println(device->epcount);
Serial.print(F("Low Speed: "));
Serial.println(device->lowspeed ? "Yes" : "No");
}
}
bool sendGetDescriptor(uint8_t addr) {
uint8_t ep = 0; // Control endpoint
uint8_t bmReqType = 0x80; // Device-to-host, Standard, Device
uint8_t bRequest = 0x06; // GET_DESCRIPTOR
uint8_t wValLo = 0x00; // Descriptor Index
uint8_t wValHi = 0x01; // Descriptor Type (Device Descriptor)
uint16_t wInd = 0x0000; // Language ID or Index
uint16_t total = 0x0012; // Expected total bytes (18 bytes)
uint16_t nbytes = 0x0012; // Number of bytes to read
uint8_t data[18]; // Buffer to hold the response
USBReadParser *p = NULL; // No parser needed
// Send the control request
uint8_t result = usb.ctrlReq(addr, ep, bmReqType, bRequest, wValLo, wValHi, wInd, total, nbytes, data, p);
if (result) {
Serial.print(F("GET_DESCRIPTOR failed with error code: "));
Serial.println(result, HEX);
return false;
}
Serial.println(F("Device Descriptor Retrieved:"));
for (uint8_t i = 0; i < nbytes; i++) {
Serial.print(data[i], HEX);
Serial.print(" ");
}
Serial.println();
return true;
}
void getConfigurationDescriptor(uint8_t addr) {
uint8_t ep = 0; // Control endpoint
uint8_t bmReqType = 0x80; // Device-to-host, Standard, Device
uint8_t bRequest = 0x06; // GET_DESCRIPTOR
uint8_t wValLo = 0x00; // Descriptor Index
uint8_t wValHi = 0x02; // Descriptor Type (Configuration Descriptor)
uint16_t wInd = 0x0000; // Language ID or Index
uint16_t nbytes = 9; // Initial request for 9 bytes
uint8_t buffer[9]; // Buffer to hold the response
USBReadParser *p = NULL; // No parser needed
// Send the control request
uint8_t result = usb.ctrlReq(addr, ep, bmReqType, bRequest, wValLo, wValHi, wInd, nbytes, nbytes, buffer, p);
if (result) {
Serial.print(F("GET_DESCRIPTOR (Configuration) failed with error code: "));
Serial.println(result, HEX);
return;
}
Serial.println(F("Configuration Descriptor (Initial):"));
for (uint8_t i = 0; i < nbytes; i++) {
Serial.print(buffer[i], HEX);
Serial.print(" ");
}
Serial.println();
// Now retrieve the full descriptor length
uint16_t totalLength = (buffer[3] << 8) | buffer[2]; // Extract wTotalLength
if (totalLength > sizeof(buffer)) {
uint8_t fullBuffer[totalLength];
result = usb.ctrlReq(addr, ep, bmReqType, bRequest, wValLo, wValHi, wInd, totalLength, totalLength, fullBuffer, p);
if (result) {
Serial.print(F("GET_DESCRIPTOR (Full Configuration) failed with error code: "));
Serial.println(result, HEX);
return;
}
Serial.println(F("Configuration Descriptor (Full):"));
for (uint8_t i = 0; i < totalLength; i++) {
Serial.print(fullBuffer[i], HEX);
Serial.print(" ");
}
Serial.println();
}
}
uint8_t setConfiguration(uint8_t addr, uint8_t configValue) {
uint8_t result = usb.setConf(addr, 0, configValue);
if (result) {
Serial.print(F("Failed to set configuration. Error code: "));
Serial.println(result, HEX);
return result;
}
Serial.println(F("Configuration set successfully."));
return 0;
}
void getMaxLUN(uint8_t addr) {
uint8_t ep = 0; // Control endpoint
uint8_t bmReqType = 0xA1; // Device-to-host, Class, Interface
uint8_t bRequest = 0xFE; // GET_MAX_LUN request
uint8_t wValLo = 0x00; // Reserved, always 0
uint8_t wValHi = 0x00; // Reserved, always 0
uint16_t wInd = 0x0000; // Interface number (usually 0)
uint16_t nbytes = 1; // Length of the data phase
uint8_t lun = 0; // Buffer to store the maximum LUN
// Send the GET_MAX_LUN request
uint8_t result = usb.ctrlReq(addr, ep, bmReqType, bRequest, wValLo, wValHi, wInd, nbytes, nbytes, &lun, NULL);
if (result) {
Serial.print(F("GET_MAX_LUN failed with error code: "));
Serial.println(result, HEX);
return;
}
Serial.print(F("Maximum LUN: "));
Serial.println(lun);
}
void printEndpoints(uint8_t addr) {
AddressPool &addrPool = usb.GetAddressPool();
UsbDevice *device = addrPool.GetUsbDevicePtr(addr);
if (device == nullptr) {
Serial.println(F("Device not found."));
return;
}
EpInfo *epInfo = device->epinfo; // Endpoint information
if (epInfo == nullptr) {
Serial.println(F("Endpoint info not available."));
return;
}
Serial.println(F("Registered Endpoints:"));
// Reset globals to nullptr each time we run this
gEpControl = nullptr;
gEpIn = nullptr;
gEpOut = nullptr;
for (uint8_t i = 0; i < device->epcount; i++) {
uint8_t address = epInfo[i].epAddr;
Serial.print(F("Endpoint Index: "));
Serial.println(i);
Serial.print(F(" Address: 0x"));
Serial.println(address, HEX);
Serial.print(F(" Max Packet Size: "));
Serial.println(epInfo[i].maxPktSize);
// Check if this is the default Control endpoint (usually epAddr == 0)
if (address == 0) {
Serial.println(F(" Type: CONTROL"));
// If we haven't already saved a control endpoint, save it
if (gEpControl == nullptr) {
gEpControl = &epInfo[i];
}
} else if ((address & 0x80) != 0) {
// If bit 7 is set, it's an IN endpoint
Serial.println(F(" Type: IN"));
// If we haven't already saved an IN endpoint, save this one
if (gEpIn == nullptr) {
gEpIn = &epInfo[i];
}
} else {
// Otherwise, it's an OUT endpoint
Serial.println(F(" Type: OUT"));
// If we haven't already saved an OUT endpoint, save this one
if (gEpOut == nullptr) {
gEpOut = &epInfo[i];
}
}
Serial.println(); // extra blank line for readability
}
// At this point, gEpControl, gEpIn, and gEpOut point to the first matching
// endpoints we encountered, or remain nullptr if none was found.
}
void setupEndpoints(uint8_t addr) {
UsbDevice *device = usb.GetAddressPool().GetUsbDevicePtr(addr);
if (!device) {
Serial.println(F("Device not found."));
return;
}
// Create the endpoint parser
EndpointParser parser;
// Retrieve and parse the configuration descriptor
uint8_t result = usb.getConfDescr(addr, 0, 1, &parser); // Config index is 1
if (result == 0) {
Serial.print(F("Bulk IN Address: 0x"));
Serial.print(parser.bulkInEp, HEX);
Serial.print(F(", Max Packet Size: "));
Serial.println(parser.bulkInMaxPktSize);
Serial.print(F("Bulk OUT Address: 0x"));
Serial.print(parser.bulkOutEp, HEX);
Serial.print(F(", Max Packet Size: "));
Serial.println(parser.bulkOutMaxPktSize);
} else {
Serial.print(F("Failed to get Configuration Descriptor. Error code: "));
Serial.println(result, HEX);
}
// Check if endpoints were found
if (parser.bulkInEp == 0 || parser.bulkOutEp == 0) {
Serial.println(F("Failed to find Bulk IN or Bulk OUT endpoints."));
return;
}
// Set up endpoint info
static EpInfo epInfo[3];
// Control endpoint
epInfo[0].epAddr = 0x00; // Control endpoint
epInfo[0].maxPktSize = 64;
// Bulk IN endpoint
epInfo[1].epAddr = parser.bulkInEp;
epInfo[1].maxPktSize = parser.bulkInMaxPktSize;
// Bulk OUT endpoint
epInfo[2].epAddr = parser.bulkOutEp;
epInfo[2].maxPktSize = parser.bulkOutMaxPktSize;
// Assign endpoints to the device
device->epinfo = epInfo;
device->epcount = 3;
}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
Serial.print(F("FreeRam "));
Serial.println(FreeRam());
// Initialize the USB bus.
if (!initUSB(&usb)) {
Serial.println(F("initUSB failed"));
return;
}
// Send the GET_DESCRIPTOR request
if (sendGetDescriptor(1)) { // Replace 1 with your device's address
Serial.println(F("GET_DESCRIPTOR successful."));
} else {
Serial.println(F("GET_DESCRIPTOR failed."));
}
// Send GET_DESCRIPTOR request for configuration descriptor
//getConfigurationDescriptor(1); // Replace 1 with your device's address
// Retrieve and print string descriptors
getStringDescriptor(1, 1, 0x0409); // Manufacturer String
getStringDescriptor(1, 2, 0x0409); // Product String
getStringDescriptor(1, 3, 0x0409); // Serial Number String
enumerateDevices(); // List all connected devices
setConfiguration(1, 1); // Activate configuration
DELAY(200);
setupEndpoints(1); // Discover and configure endpoints
DELAY(200);
printEndpoints(1); // Verify endpoint configuration
uint8_t r = bulk.Init(0, 1, false); // Initialize device at port 1, not low speed
if (r) {
Serial.print("BulkOnly initialization failed. Error code: ");
Serial.println(r);
} else {
Serial.println("BulkOnly Mass Storage Device initialized.");
}
boolean isLunOk = bulk.CheckLUN(0);
Serial.print(F("isLunOk "));
Serial.println(isLunOk);
CDB6_t cdb = CDB6_t(SCSI_CMD_TEST_UNIT_READY, 0, (uint8_t)0, 0);
uint8_t isUnitReady = bulk.SCSITransaction6(&cdb, 0, NULL, (uint8_t)MASS_CMD_DIR_IN);
Serial.print(F("isUnitReady "));
Serial.println(isUnitReady);
// Init the USB key or USB hard drive.
if (!key.begin()) {
Serial.println(F("key.begin failed"));
return;
}
Serial.print(F("\r\nVolume Size: "));
// Avoid 32-bit overflow for large volumes.
Serial.print((key.volumeBlockCount()/1000)*512/1000);
Serial.println(F(" MB"));
// Print a line to a test file.
//file.open("test file.txt", O_CREAT | O_RDWR);
//file.println("Hello USB");
//file.close();
// List the files in the root directory.
//Serial.println();
//key.ls(LS_A | LS_DATE | LS_SIZE);
//Serial.println();
uint8_t mediaResult = bulk.MediaCTL(0, 0x02);
Serial.print(F("Media Query Result "));
Serial.println(mediaResult);
uint32_t diskSize = bulk.GetCapacity(0);
Serial.print(F("Disk Size "));
Serial.println(diskSize);
}
//------------------------------------------------------
void loop() {
usb.Task();
}
You can find latest project files in here if you want to see whole project. : Project Files
Terminal output ;
FreeRam 6990
MS ConfigureDevice
MS Init
Addr:01
NC:01
Device Descriptor Retrieved:
12 1 0 2 0 0 0 40 6B 1C 23 A2 0 0 1 2 3 1
GET_DESCRIPTOR successful.
String Descriptor (Index 1): USB2.0 External
String Descriptor (Index 2): Mass Storage Device
String Descriptor (Index 3): 97 436311501508
Enumerating devices...
Device Address: 1
Parent Address: 0
Is Hub: No
Endpoint Count: 1
Low Speed: No
Configuration set successfully.
Bulk IN Address: 0x81, Max Packet Size: 64
Bulk OUT Address: 0x2, Max Packet Size: 64
Registered Endpoints:
Endpoint Index: 0
Address: 0x0
Max Packet Size: 64
Type: CONTROL
Endpoint Index: 1
Address: 0x81
Max Packet Size: 64
Type: IN
Endpoint Index: 2
Address: 0x2
Max Packet Size: 64
Type: OUT
MS Init
setAddr:0D
BulkOnly initialization failed. Error code: 13
ReadCapacity
---------------
CBW.dCBWTag: 00000001
USB Error: 0D
Index: 02
============================ CBW: 11
ResetRecovery
-----------------
ResetRecovery
-----------------
Gen SCSI Err: 11
LUN: 00
isLunOk 0
CBW.dCBWTag: 00000002
USB Error: 0D
Index: 02
============================ CBW: 11
ResetRecovery
-----------------
ResetRecovery
-----------------
Gen SCSI Err: 11
LUN: 00
isUnitReady 17
sectorSize 0
key.begin failed