Help Needed: Building an Audio CD Player with USB DVD Drive

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

Read the pinned post re 'How to get the most from the forum'. Somewhere in there it will tell you we don't chase links.

New members cannot upload files, so links have been added for convenience. You are free not to click them if you prefer.

Restrictions on new posters:-

So new members can post images given the correct criteria.

I don’t really get it. Do you want me to screenshot my solution codes and upload them here? I provided the google drive link in case someone wants to download and test the current code. Isn’t that much easier than other methods?

Also, I have updated my post. Add the main.cpp file in case anyone is still interested in helping me.

What makes you think that?

It says a new user can post one image, and posting code as an image is one of the most useless things you can do on this forum. You can post code but please use the proper code tags as described in the link How to get the best out of this forum. You should read this.

You gave me unnecessary information, wasting both your time and mine.
Why would I need to upload images for this topic?
I shared a google drive link because I couldn't upload the entire project directly due to the specific libraries I use and the platformio configurations.
@sonofcy mentioned that you guys don't open links and thats ok.
I updated my post and added the main.cpp file.
I specifically mentioned that I provided the project link to make it easier to set up the current code.
Are you guys actually willing to help?

What kind of support is this, seriously?

We download code in code tags many times a day.

codes tells me all I need to know.

Ok. What do my codes tell you?

Mostly unpaid volunteers.

It's okay, I know they don't have to answer me, but giving unrelated answers is really disappointing.

Code is code, there is NO plural. It tells me you have no background, training, or education in software.

Only 1 day on the forum and look at all the friends you have made.

Sorry, I didn't know this forum was facebook. :rofl: What a toxic environment. :yawning_face:

Every new guy thinks they know better, especially the ones who do not read the agreed-upon rules. You just clicked-through the rules that you agreed when you got your account, and now call everyone toxic for expecting you to follow those rules. Typical 4chan fanboy. (pout)

To show your schematic, or your actually physical layout.

OK sorry for wasting your time, but I thought you were after a solution to your problem. But it seems I was wrong. I wonder why you ever posted a question if you do not want to cooperate and abide by the rules here. We are not getting past first base here. So I will put this post on mute. That means I will not see any more posts from this thread. As we say in these parts.

Best of luck with your project but I am out of here.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.