Original address:
http://hengch.blog.163.com/blog/static/10780067200851283245935
A USB flash drive is one of the USB devices we use most often. This article continues to use DOSUSB as the driver, and tries to read your USB flash drive by reading sectors.
This article may involve quite a few protocols.
I. Understanding your USB flash drive
First, use the usbview.exe program introduced in the previous article to look at your USB flash drive. The USB flash drive I used for testing in this article is as follows:
Device Descriptor: (device descriptor)
USB Address: 1
Length: 18
Descriptor Type: 1
USB Specification nr.: 0x0110
Calss Code: Class code specified by interface
Subclass Code: 0x00
Protocol Code: 0x00
MAX Packet Size: 0x08
Vendor ID: 0x058f
Product ID: 0x9321
Device Code: 0x0100
Manufacture Index: 1
Product Index: 2
Serial Number Index: 0
Number of Configuration: 1
String Descriptor: (string descriptor)
Manufacturer: Alcor Micro
Product: Mass Storage Device
Configuration Descriptor: (configuration descriptor)
Length: 9
Descriptor Type: 2
Total Length: 32
Number of Interfaces: 1
Configuration Value: 1
Configuration Index: 0
Attributes: Bus Powered
Max Power: 50mA
Interface Descriptor: (interface descriptor)
Length: 9
Descriptor Type: 4
Interface Number: 0
Alternate Setting: 0
Number of Endpoints: 2
Interface Class: Mass Storage Device
Interface Sub Class: 6
Interface Protocol: 80
Interface Index: 0
Endpoint Descriptor: (endpoint descriptor)
Length: 7
Descriptor Type: 5
Endpoint Address: 1 OUT endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
Endpoint Descriptor: (endpoint descriptor)
Length: 7
Descriptor Type: 5
Endpoint Address: 2 IN endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
The meanings of the various descriptors were introduced in earlier articles, or you can look them up in the USB specification, so I won't say more about them here. Starting from the interface descriptor, let's explain a few key points.
First look at the interface descriptor. Interface Class = 8 indicates a Mass Storage Device; Sub Class = 6 indicates that it uses SCSI commands; Interface Protocol = 0x80 indicates support for Bulk transfer; in addition, Number of Endpoints = 2 indicates that there are two endpoints.
What needs attention in the two endpoint descriptors is that Endpoint Address = 1 is the OUT endpoint, and Endpoint Address = 2 is the IN endpoint; some may be different. Some USB flash drives may also have a third endpoint. For example, USB flash drives that support interrupt transfer will also have an Interrupt endpoint, but that does not matter.
I roughly checked the 5 USB flash drives I have on hand. They all support bulk transfer and SCSI commands, so this may be a fairly typical example, and we will use it as our example.
II. CBW(Command Block Wrapper) and CSW(Command Status Wrapper)
In "USB Series Part 1", we installed DOSUSB. In "USB Series Part 2", we used USBDOS to read all the descriptor tables. To master these contents, you only need to understand USB protocol 1.1 (USB Specification Revision 1.1), and of course you also need to understand USBDOS, though that part is relatively simple.
In Part 1 and Part 2, we already became somewhat familiar with a DOSUSB data structure called URB, which will also be used extensively in this article. We also came across a structure called device_request. This structure is defined in the USB protocol and is used to send commands (Request) to devices, and it will also be used in this article.
Unlike the previous two parts, which could target any USB device such as USB flash drives, cameras, printers, and so on, this article will deal only with the USB device we use most often ---- the USB flash drive. If you plan to try what is introduced in this article, please prepare a USB flash drive of any kind, or a USB card reader, but remember to insert a card into it. In fact, the example carried in this article was completed using a USB CF card reader. Don't worry about damaging the data on your USB flash drive. This article will not perform any write operations on the USB flash drive, only some read operations.
In this series we need to read more specifications related to USB flash drives, as follows:
Universal Serial Bus Mass Storage Class Specification Overview
download link: http://blog.hengch.com/specification/usb_msc_overview_1.2.pdf
Universal Serial Bus Mass Storage Class -- Bulk-Only Transport
download link: http://blog.hengch.com/specification/usbmassbulk_10.pdf
SCSI Primary Commands - 3(SPC-3)
download link: http://blog.hengch.com/specification/SCSI_Primary_Commands-3.pdf
SCSI Block Commands - 2(SBC-2)
download link: http://blog.hengch.com/specification/SCSI_Block_Command-2.pdf
Don't worry about the specifications. In fact, the first two are both very short. The first one is not very useful for actual programming, but it is still best to look through it. The second specification is only 22 pages including the table of contents, and the content before page 13 can be skipped over (a lot of it is the same as in the USB Specification). For the third specification, mainly look at Chapter 6; for the fourth specification, mainly look at Chapter 5. The latter two specifications need to be consulted often during programming, so that you can understand the exact format and parameters of the SCSI command you are implementing.
In this section we mainly introduce two new data structures, both of which are defined in the second specification.
The first data structure is called CBW(Command Block Wrapper)

This structure carries the specific device-related command to the device. This structure is divided into two parts. The first part is 15 bytes total, from byte--byte, and the second part is 16 bytes total, from byte--byte, making the whole data structure 31 bytes. The specification does not define the contents of the second part, because the specific command carried by the second part depends both on the command set (the SCSI command set) and on the specific command itself. We use the SCSI command set, so the contents of the latter 16 bytes are defined in the latter two specifications mentioned above.
For example, suppose we want to send the SCSI command INQUIRY to the device (for the moment, let's not worry about what the command means). The structure of this command is defined on page 142 of SPC-3, as follows:

For the SCSI INQUIRY command, the definition of the second part of the CBW is the above six bytes. Different commands have different definitions.
All right, let's go back to the structure of the CSW. According to the specification, the value of dCBWSignature must be 0X43425355, which is actually the letters USBC reversed. This is because the character order in the CBW is little endian (this was introduced in earlier articles on network programming), while the character order in our PC is big endian, so it has to be reversed. In short, writing dCBWSignature = 0X43425355 is OK. dCBWTag is only a marker, and you can fill in any value. Here I should first say something about the CSW (Command Status Wrapper). Every time we send out a command, the device returns a CSW (this will be introduced very shortly below) to indicate the execution status of the command. This structure also has the two fields Signature and Tag. The Tag field is the same as the Tag field in the CBW when the command was sent, so we can distinguish which CBW this CSW corresponds to. As for Signature, more on that below.
The next field is dCBWDataTransferLength, which indicates the number of bytes we expect the device to return after this command is sent, or the number of bytes we want to transfer to the device. This article only involves data returned from the device, and does not involve transferring data to the device. For example, if we send the INQIURY command to the device, according to the description on page 144 of SPC-3, the data returned by that command is at least 36 bytes, so this field should be filled with 36 at that time. Another example: if we read one sector from a USB flash drive, and the sector length is 512 bytes, then this field should be filled with 512.
Next is the bmCBWFlags field. In this field only bit 7 is meaningful. 0 means data is to be transferred to the device, and 1 means data is to be obtained from the device.
The bCBWLUN field is always filled with 0, because the vast majority of USB flash drives do not support multiple LUNs (Logical Unit Number). Since there is only one logical unit, naturally it is 0.
The bCBWCBLength field refers to the length of the second part of the CBW. For the INQUIRY command used in the example above, the length is 6 bytes, so this field should be filled with 6. Another example: the READ(10) command has a length of 10 bytes (defined on page 42 of SBC-2), so of course this field should be filled with 10.
The second data structure to talk about is CSW. After the host sends a CBW to the device, it can then receive data from the device (or send data to the device). After receiving the required data, it can obtain a CSW(Command Status Wrapper) from the device. The structure of the CSW is as follows:

As mentioned earlier, the value of dCBWSignature in the CBW is always: 0x43425355, and the value of dCSWSignature in the obtained CSW is: 0x53425355, with dCSWTag the same as dCBWTag.
In the obtained CSW, there are always 13 bytes, and the definition of bCSWStatus is as follows:

III. Sending commands and receiving data
We know that the USB protocol defines three transfer methods: control transfer, bulk transfer, interrupt transfer, and real-time transfer. In "USB Series Part 2" we were always using control transfer, which we should already be fairly familiar with. This article will involve bulk transfer.
When using control transfer, we set up the URB and start the transfer transaction, and the corresponding result is returned to the specified buffer. Bulk transfer is not that simple. Bulk transfer is divided into output transactions and input transactions. We should have noticed that when looking at the USB flash drive descriptor tables earlier, there were two endpoints at the endpoint level, one called the OUT endpoint and one called the IN endpoint. When we start an output transaction, it must be sent to the OUT endpoint; when we start an input transaction, it must be sent to the input endpoint. Below we will briefly describe how to start a bulk transfer transaction.
When using control transfer, we should have read the DOSUSB documentation and be fairly familiar with the URB structure. There is a field in the URB called transation_type. When this value is 0x2d, it is control transfer; when it is 0x69, it is a bulk transfer IN transaction; when it is 0xe1, it is a bulk transfer OUT transaction. When starting a transfer, we must set this value correctly.
Let us use a concrete example to explain how to start a transfer. We will use the SCSI INQUIRY command as our example. The definition of this command is described on pages 142--157 of SPC-3. It is quite long, but most of the space is used to explain the meaning of the returned data, which we can ignore for the time being. First we need to fill in the CBW structure. The first part of the CBW has already been explained clearly above. The definition of the second part is on page 142 of SPC-3 and consists of 6 bytes, which we should fill in according to the definition. In fact, only two fields need to be filled in: one is OPERATION CODE = 0X12, and the second is ALLOCATION CODE = 36, indicating that 36 bytes of data are to be returned. After filling in the CBW, we begin filling in the URB. First put the offset and segment address of the CBW into buffer_off and buffer_seg of the URB, set transation_type=0xe1 to indicate an output transaction, and be sure to set the end_point field to the address of the OUT endpoint. Judging from the descriptor table above, it should be 1 (2 is the address of the IN endpoint; your machine may be different). The way to fill in the other fields was already introduced in "USB Series Part 2". After that, call DOSUSB, and in this way an output transaction carrying the INQUIRY command is sent to the endpoint specified by the dev_add and end_point fields in the URB.
Next we need to receive the result returned by the device after executing the INQUIRY command. This requires starting an input transaction, which is relatively easier, since you only need to fill in the URB. Set transation_type=0x69, fill in end_point with the address of the OUT endpoint, which in this example is 2, and let buffer_off and buffer_seg point to the buffer. Fill both buffer_length and actual_length with 64, because the endpoint descriptor table above clearly states that the maximum packet length is 64. Fill in the other fields as usual, call DOSUSB, and the returned contents can be obtained in buffer. Then just follow the explanation of the returned contents in SPC-3 to understand some information about the device.
After receiving the data, don't forget to receive the CSW. The method is also to start an input transaction, exactly the same as receiving data, and then interpret its meaning according to the CSW structure. At this point one command has been executed.
IV. Example
In the example in this article, we implemented the following:
Implemented Bulk-Only Mass Storage Reset
Implemented Get Max LUN
Implemented SCSI INQUIRY Command
Implemented SCSI READ CAPACITY (10) Command
Implemented SCSI REQUEST SENSE Command
Implemented SCSI TEST UNIT READY Command
Implemented SCSI READ (10) Command
With the last command, I will read one sector from your USB flash drive.
For the first two commands, please refer to page 7 of "Universal Serial Bus Mass Storage Class - Bulk-Only Transport"; for the three commands INQUIRY, REQUEST SENSE, and TEST UNIT READY, please refer to pages 142, 221, and 232 of SPC-3; for the READ CAPACITY(10) and READ(10) commands, please refer to pages 42 and 44 of SBC-2.
Please download the source code from the following URL:
http://blog.hengch.com/source/reader.rar
The various concepts have already been introduced above. The program is nothing more than implementing these concepts. Almost all of the code revolves around filling in data structures and displaying the returned results, so the code itself is not difficult. What is more important is understanding the meaning of each field in the data structures, and that may require reading some specifications. I don't think I can explain them more rigorously or more completely than the specifications themselves. Note that the USB flash drive you use cannot be exactly the same as mine. Under normal circumstances, the values that may differ are: device address devAddr, output endpoint address outEndpoint, and input endpoint address inEndpoint. So before compiling the program, be sure to use the method in "USB Series Part 2" to carefully examine the various descriptor tables of your USB flash drive. If these values differ from those of my USB flash drive, please change these variables at the beginning of the main program. In addition, in the 6th step of the main program, scsiRead10(0), the parameter passed to scsiRead10 is 0, meaning that one sector is read starting from LBA (Logical Block Address) 0. If you want to read some other sector, you can change this value. Its maximum value was already read out when implementing READ CAPACITY, so you can refer to that. Also, note that the byte order of the CBW is little endian, so when filling in the LBA and reading the maximum LBA, we made the corresponding conversions.
All right, there should be nothing more!
Enjoy it.
http://hengch.blog.163.com/blog/static/10780067200851283245935
A USB flash drive is one of the USB devices we use most often. This article continues to use DOSUSB as the driver, and tries to read your USB flash drive by reading sectors.
This article may involve quite a few protocols.
I. Understanding your USB flash drive
First, use the usbview.exe program introduced in the previous article to look at your USB flash drive. The USB flash drive I used for testing in this article is as follows:
Device Descriptor: (device descriptor)
USB Address: 1
Length: 18
Descriptor Type: 1
USB Specification nr.: 0x0110
Calss Code: Class code specified by interface
Subclass Code: 0x00
Protocol Code: 0x00
MAX Packet Size: 0x08
Vendor ID: 0x058f
Product ID: 0x9321
Device Code: 0x0100
Manufacture Index: 1
Product Index: 2
Serial Number Index: 0
Number of Configuration: 1
String Descriptor: (string descriptor)
Manufacturer: Alcor Micro
Product: Mass Storage Device
Configuration Descriptor: (configuration descriptor)
Length: 9
Descriptor Type: 2
Total Length: 32
Number of Interfaces: 1
Configuration Value: 1
Configuration Index: 0
Attributes: Bus Powered
Max Power: 50mA
Interface Descriptor: (interface descriptor)
Length: 9
Descriptor Type: 4
Interface Number: 0
Alternate Setting: 0
Number of Endpoints: 2
Interface Class: Mass Storage Device
Interface Sub Class: 6
Interface Protocol: 80
Interface Index: 0
Endpoint Descriptor: (endpoint descriptor)
Length: 7
Descriptor Type: 5
Endpoint Address: 1 OUT endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
Endpoint Descriptor: (endpoint descriptor)
Length: 7
Descriptor Type: 5
Endpoint Address: 2 IN endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
The meanings of the various descriptors were introduced in earlier articles, or you can look them up in the USB specification, so I won't say more about them here. Starting from the interface descriptor, let's explain a few key points.
First look at the interface descriptor. Interface Class = 8 indicates a Mass Storage Device; Sub Class = 6 indicates that it uses SCSI commands; Interface Protocol = 0x80 indicates support for Bulk transfer; in addition, Number of Endpoints = 2 indicates that there are two endpoints.
What needs attention in the two endpoint descriptors is that Endpoint Address = 1 is the OUT endpoint, and Endpoint Address = 2 is the IN endpoint; some may be different. Some USB flash drives may also have a third endpoint. For example, USB flash drives that support interrupt transfer will also have an Interrupt endpoint, but that does not matter.
I roughly checked the 5 USB flash drives I have on hand. They all support bulk transfer and SCSI commands, so this may be a fairly typical example, and we will use it as our example.
II. CBW(Command Block Wrapper) and CSW(Command Status Wrapper)
In "USB Series Part 1", we installed DOSUSB. In "USB Series Part 2", we used USBDOS to read all the descriptor tables. To master these contents, you only need to understand USB protocol 1.1 (USB Specification Revision 1.1), and of course you also need to understand USBDOS, though that part is relatively simple.
In Part 1 and Part 2, we already became somewhat familiar with a DOSUSB data structure called URB, which will also be used extensively in this article. We also came across a structure called device_request. This structure is defined in the USB protocol and is used to send commands (Request) to devices, and it will also be used in this article.
Unlike the previous two parts, which could target any USB device such as USB flash drives, cameras, printers, and so on, this article will deal only with the USB device we use most often ---- the USB flash drive. If you plan to try what is introduced in this article, please prepare a USB flash drive of any kind, or a USB card reader, but remember to insert a card into it. In fact, the example carried in this article was completed using a USB CF card reader. Don't worry about damaging the data on your USB flash drive. This article will not perform any write operations on the USB flash drive, only some read operations.
In this series we need to read more specifications related to USB flash drives, as follows:
Universal Serial Bus Mass Storage Class Specification Overview
download link: http://blog.hengch.com/specification/usb_msc_overview_1.2.pdf
Universal Serial Bus Mass Storage Class -- Bulk-Only Transport
download link: http://blog.hengch.com/specification/usbmassbulk_10.pdf
SCSI Primary Commands - 3(SPC-3)
download link: http://blog.hengch.com/specification/SCSI_Primary_Commands-3.pdf
SCSI Block Commands - 2(SBC-2)
download link: http://blog.hengch.com/specification/SCSI_Block_Command-2.pdf
Don't worry about the specifications. In fact, the first two are both very short. The first one is not very useful for actual programming, but it is still best to look through it. The second specification is only 22 pages including the table of contents, and the content before page 13 can be skipped over (a lot of it is the same as in the USB Specification). For the third specification, mainly look at Chapter 6; for the fourth specification, mainly look at Chapter 5. The latter two specifications need to be consulted often during programming, so that you can understand the exact format and parameters of the SCSI command you are implementing.
In this section we mainly introduce two new data structures, both of which are defined in the second specification.
The first data structure is called CBW(Command Block Wrapper)

This structure carries the specific device-related command to the device. This structure is divided into two parts. The first part is 15 bytes total, from byte--byte, and the second part is 16 bytes total, from byte--byte, making the whole data structure 31 bytes. The specification does not define the contents of the second part, because the specific command carried by the second part depends both on the command set (the SCSI command set) and on the specific command itself. We use the SCSI command set, so the contents of the latter 16 bytes are defined in the latter two specifications mentioned above.
For example, suppose we want to send the SCSI command INQUIRY to the device (for the moment, let's not worry about what the command means). The structure of this command is defined on page 142 of SPC-3, as follows:

For the SCSI INQUIRY command, the definition of the second part of the CBW is the above six bytes. Different commands have different definitions.
All right, let's go back to the structure of the CSW. According to the specification, the value of dCBWSignature must be 0X43425355, which is actually the letters USBC reversed. This is because the character order in the CBW is little endian (this was introduced in earlier articles on network programming), while the character order in our PC is big endian, so it has to be reversed. In short, writing dCBWSignature = 0X43425355 is OK. dCBWTag is only a marker, and you can fill in any value. Here I should first say something about the CSW (Command Status Wrapper). Every time we send out a command, the device returns a CSW (this will be introduced very shortly below) to indicate the execution status of the command. This structure also has the two fields Signature and Tag. The Tag field is the same as the Tag field in the CBW when the command was sent, so we can distinguish which CBW this CSW corresponds to. As for Signature, more on that below.
The next field is dCBWDataTransferLength, which indicates the number of bytes we expect the device to return after this command is sent, or the number of bytes we want to transfer to the device. This article only involves data returned from the device, and does not involve transferring data to the device. For example, if we send the INQIURY command to the device, according to the description on page 144 of SPC-3, the data returned by that command is at least 36 bytes, so this field should be filled with 36 at that time. Another example: if we read one sector from a USB flash drive, and the sector length is 512 bytes, then this field should be filled with 512.
Next is the bmCBWFlags field. In this field only bit 7 is meaningful. 0 means data is to be transferred to the device, and 1 means data is to be obtained from the device.
The bCBWLUN field is always filled with 0, because the vast majority of USB flash drives do not support multiple LUNs (Logical Unit Number). Since there is only one logical unit, naturally it is 0.
The bCBWCBLength field refers to the length of the second part of the CBW. For the INQUIRY command used in the example above, the length is 6 bytes, so this field should be filled with 6. Another example: the READ(10) command has a length of 10 bytes (defined on page 42 of SBC-2), so of course this field should be filled with 10.
The second data structure to talk about is CSW. After the host sends a CBW to the device, it can then receive data from the device (or send data to the device). After receiving the required data, it can obtain a CSW(Command Status Wrapper) from the device. The structure of the CSW is as follows:

As mentioned earlier, the value of dCBWSignature in the CBW is always: 0x43425355, and the value of dCSWSignature in the obtained CSW is: 0x53425355, with dCSWTag the same as dCBWTag.
In the obtained CSW, there are always 13 bytes, and the definition of bCSWStatus is as follows:

III. Sending commands and receiving data
We know that the USB protocol defines three transfer methods: control transfer, bulk transfer, interrupt transfer, and real-time transfer. In "USB Series Part 2" we were always using control transfer, which we should already be fairly familiar with. This article will involve bulk transfer.
When using control transfer, we set up the URB and start the transfer transaction, and the corresponding result is returned to the specified buffer. Bulk transfer is not that simple. Bulk transfer is divided into output transactions and input transactions. We should have noticed that when looking at the USB flash drive descriptor tables earlier, there were two endpoints at the endpoint level, one called the OUT endpoint and one called the IN endpoint. When we start an output transaction, it must be sent to the OUT endpoint; when we start an input transaction, it must be sent to the input endpoint. Below we will briefly describe how to start a bulk transfer transaction.
When using control transfer, we should have read the DOSUSB documentation and be fairly familiar with the URB structure. There is a field in the URB called transation_type. When this value is 0x2d, it is control transfer; when it is 0x69, it is a bulk transfer IN transaction; when it is 0xe1, it is a bulk transfer OUT transaction. When starting a transfer, we must set this value correctly.
Let us use a concrete example to explain how to start a transfer. We will use the SCSI INQUIRY command as our example. The definition of this command is described on pages 142--157 of SPC-3. It is quite long, but most of the space is used to explain the meaning of the returned data, which we can ignore for the time being. First we need to fill in the CBW structure. The first part of the CBW has already been explained clearly above. The definition of the second part is on page 142 of SPC-3 and consists of 6 bytes, which we should fill in according to the definition. In fact, only two fields need to be filled in: one is OPERATION CODE = 0X12, and the second is ALLOCATION CODE = 36, indicating that 36 bytes of data are to be returned. After filling in the CBW, we begin filling in the URB. First put the offset and segment address of the CBW into buffer_off and buffer_seg of the URB, set transation_type=0xe1 to indicate an output transaction, and be sure to set the end_point field to the address of the OUT endpoint. Judging from the descriptor table above, it should be 1 (2 is the address of the IN endpoint; your machine may be different). The way to fill in the other fields was already introduced in "USB Series Part 2". After that, call DOSUSB, and in this way an output transaction carrying the INQUIRY command is sent to the endpoint specified by the dev_add and end_point fields in the URB.
Next we need to receive the result returned by the device after executing the INQUIRY command. This requires starting an input transaction, which is relatively easier, since you only need to fill in the URB. Set transation_type=0x69, fill in end_point with the address of the OUT endpoint, which in this example is 2, and let buffer_off and buffer_seg point to the buffer. Fill both buffer_length and actual_length with 64, because the endpoint descriptor table above clearly states that the maximum packet length is 64. Fill in the other fields as usual, call DOSUSB, and the returned contents can be obtained in buffer. Then just follow the explanation of the returned contents in SPC-3 to understand some information about the device.
After receiving the data, don't forget to receive the CSW. The method is also to start an input transaction, exactly the same as receiving data, and then interpret its meaning according to the CSW structure. At this point one command has been executed.
IV. Example
In the example in this article, we implemented the following:
Implemented Bulk-Only Mass Storage Reset
Implemented Get Max LUN
Implemented SCSI INQUIRY Command
Implemented SCSI READ CAPACITY (10) Command
Implemented SCSI REQUEST SENSE Command
Implemented SCSI TEST UNIT READY Command
Implemented SCSI READ (10) Command
With the last command, I will read one sector from your USB flash drive.
For the first two commands, please refer to page 7 of "Universal Serial Bus Mass Storage Class - Bulk-Only Transport"; for the three commands INQUIRY, REQUEST SENSE, and TEST UNIT READY, please refer to pages 142, 221, and 232 of SPC-3; for the READ CAPACITY(10) and READ(10) commands, please refer to pages 42 and 44 of SBC-2.
Please download the source code from the following URL:
http://blog.hengch.com/source/reader.rar
The various concepts have already been introduced above. The program is nothing more than implementing these concepts. Almost all of the code revolves around filling in data structures and displaying the returned results, so the code itself is not difficult. What is more important is understanding the meaning of each field in the data structures, and that may require reading some specifications. I don't think I can explain them more rigorously or more completely than the specifications themselves. Note that the USB flash drive you use cannot be exactly the same as mine. Under normal circumstances, the values that may differ are: device address devAddr, output endpoint address outEndpoint, and input endpoint address inEndpoint. So before compiling the program, be sure to use the method in "USB Series Part 2" to carefully examine the various descriptor tables of your USB flash drive. If these values differ from those of my USB flash drive, please change these variables at the beginning of the main program. In addition, in the 6th step of the main program, scsiRead10(0), the parameter passed to scsiRead10 is 0, meaning that one sector is read starting from LBA (Logical Block Address) 0. If you want to read some other sector, you can change this value. Its maximum value was already read out when implementing READ CAPACITY, so you can refer to that. Also, note that the byte order of the CBW is little endian, so when filling in the LBA and reading the maximum LBA, we made the corresponding conversions.
All right, there should be nothing more!
Enjoy it.

DigestI