Lecture
Это окончание невероятной информации про usb.
...
break; case SET_INTERFACE: /* by default only the device is supported, a Stall is possible */ /* returned in the status stage of the request */ if (SetupPacket.wIndex == 0 && SetupPacket.wValue == 0) /* Interface Zero, Alternative Setting = 0 */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); else ErrorStallControlEndPoint(); break; case GET_INTERFACE: if (SetupPacket.wIndex == 0) { /* Interface Zero */ Buffer = 0; /* Alternative Setting */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 1); break; } /* otherwise fall through to RequestError */ //case CLEAR_FEATURE: //case SET_FEATURE: /* The interface has no defined features. Return RequestError */ default: ErrorStallControlEndPoint(); break; } break;No real functions are performed for Standard Interface Requests. The Get Status request must return a zero word – it is reserved for future use. The Set Interface and Get Interface requests are used with alternate interface descriptors. We have no alternate interface descriptors defined, so Get Interface returns 0, and any request to set an interface other than interface 0 with alternate setting 0 is handled with a Request Error.
case STANDARD_ENDPOINT_REQUEST:
printf("Standard Endpoint Request\n\r");
switch (SetupPacket.bRequest)
{
case CLEAR_FEATURE:
case SET_FEATURE:
/* the Halt (Stall) feature must be implemented on all Interrupt and */
/* Bulk endpoints. It is not required and not recommended on the
Default Pipe */
if (SetupPacket.wValue == ENDPOINT_HALT)
{
if (SetupPacket.bRequest == CLEAR_FEATURE)
Buffer = 0x00;
else
Buffer = 0x01;
switch (SetupPacket.wIndex & 0xFF)
{
case 0x01:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP1_OUT, Buffer, 1);
break;
case 0x81:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP1_IN, Buffer, 1);
break;
case 0x02:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP2_OUT, Buffer, 1);
break;
case 0x82:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP2_IN, Buffer, 1);
break;
case 0x03:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP3_OUT, Buffer, 1);
break;
case 0x83:
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \
D11_ENDPOINT_EP3_IN, Buffer, 1);
break;
default:
/* Invalid Endpoint - RequestError */
ErrorStallControlEndPoint();
break;
}
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);
}
else
{
/* There are no other features on the endpoint - request error */
ErrorStallControlEndPoint();
}
break;
The Set Feature and Clear Feature requests are used for endpoint-specific features. The standard defines one feature selector – ENDPOINT_HALT. Accordingly, we check the request for setting/clearing the STALL bit. The HALT feature is not required for the default endpoint (endpoint 0).
case GET_STATUS:
/* Get the Status Request for the endpoint that must be returned */
/* Halt Status in D0 for Interrupt and Bulk */
switch (SetupPacket.wIndex & 0xFF)
{
case 0x01:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP1_OUT, Buffer, 1);
break;
case 0x81:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP1_IN, Buffer, 1);
break;
case 0x02:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP2_OUT, Buffer, 1);
break;
case 0x82:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP2_IN, Buffer, 1);
break;
case 0x03:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP3_OUT, Buffer, 1);
break;
case 0x83:
D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \
D11_ENDPOINT_EP3_IN, Buffer, 1);
break;
default:
/* Invalid Endpoint - RequestError */
ErrorStallControlEndPoint();
break;
}
if (Buffer & 0x08)
Buffer = 0x01;
else
Buffer = 0x00;
Buffer = 0x00;
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);
break;
default:
/* Unsupported - Request Error - Stall */
ErrorStallControlEndPoint();
break;
}
break;
A Get Status request directed at an endpoint returns the status of the endpoint, i.e. whether it is halted or not. As with the Set/Clear feature ENDPOINT_HALT request, we only need to report the status of ordinary (nonzero) endpoints.
Any undefined Standard Endpoint Requests are handled with a USB Request Error.
case VENDOR_DEVICE_REQUEST:
case VENDOR_ENDPOINT_REQUEST:
printf("Vendor Device bRequest = 0x%X, wValue = 0x%X, wIndex = 0x%X\n\r", \
SetupPacket.bRequest, SetupPacket.wValue, SetupPacket.wIndex);
switch (SetupPacket.bRequest)
{
case VENDOR_GET_ANALOG_VALUE:
printf("Get Analog Value, Channel %x :",SetupPacket.wIndex & 0x07);
ADCON0 = 0xC1 | (SetupPacket.wIndex & 0x07) << 3;
/* Wait Acquistion time of Sample and Hold */
for (a = 0; a <= 255; a++);
ADGO = 1;
while(ADGO);
Buffer = ADRESL;
Buffer = ADRESH;
a = (Buffer << 8) + Buffer ;
a = (a * 500) / 1024;
printf(" Value = %d.%02d\n\r",(unsignedint)a/100,(unsigned int)a%100);
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);
break;
Now we have (finally!) come to the functional part of the USB device. Vendor Requests are invented by the developer. We came up with 2 requests, VENDOR_GET_ANALOG_VALUE and VENDOR_SET_RB_HIGH_NIBBLE. VENDOR_GET_ANALOG_VALUE reads the 10-bit ADC voltage value of channel x, specified in the wIndex field. We AND the value of x with the mask 0x07, which allows 8 possible channel numbers (which the larger PIC16F877 chip supports, if needed). The analog voltage value is returned in 2 bytes of the data packet (translator's note – the data phase could be dispensed with by passing these 2 bytes in wValue or wIndex. In that case wLength should be specified as 0, not 2).
case VENDOR_SET_RB_HIGH_NIBBLE:
printf("Write High Nibble of PORTB\n\r");
PORTB = (PORTB & 0x0F) | (SetupPacket.wIndex & 0xF0);
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);
break;
default:
ErrorStallControlEndPoint();
break;
}
break;
VENDOR_SET_RB_HIGH_NIBBLE can be used to set the bits of the high nibble of PORTB[3:7].
default:
printf("UnSupported Request Type 0x%X\n\r", SetupPacket.bmRequestType);
ErrorStallControlEndPoint();
break;
}
}
else
{
printf("Data Packet?\n\r");
/* This is a Data Packet */
}
}
Any unsupported request types, such as class device request, class interface request, etc., are handled with a USB Request Error.
void GetDescriptor(PUSB_SETUP_REQUEST SetupPacket)
{
switch((SetupPacket->wValue & 0xFF00) >> 8)
{
case TYPE_DEVICE_DESCRIPTOR:
printf("\n\rDevice Descriptor: Bytes Asked For %d, Size of Descriptor %d\n\r", \
SetupPacket->wLength,DeviceDescriptor.bLength);
pSendBuffer = (const unsigned char *)&DeviceDescriptor;
BytesToSend = DeviceDescriptor.bLength;
if (BytesToSend > SetupPacket->wLength)
BytesToSend = SetupPacket->wLength;
WriteBufferToEndPoint();
break;
case TYPE_CONFIGURATION_DESCRIPTOR:
printf("\n\rConfiguration Descriptor: Bytes Asked For %d, Size of Descriptor %d\n\r", \
SetupPacket->wLength,sizeof(ConfigurationDescriptor));
pSendBuffer = (const unsigned char*)&ConfigurationDescriptor;
BytesToSend = sizeof(ConfigurationDescriptor);
if (BytesToSend > SetupPacket->wLength)
BytesToSend = SetupPacket->wLength;
WriteBufferToEndPoint();
break;
Get Descriptor requests entail responses larger than the limit of 8 bytes on the maximum packet size for the endpoint. The response must therefore be split into 8-byte chunks. Both the Device and Configuration requests load the address of the corresponding descriptor into pSendBuffer and set BytesToSend to the length of the descriptor. The request also specifies the descriptor length in the wLength field, which gives the maximum amount of data to send. In any case, we check the actual value requested by the host and truncate the size as necessary. Then WriteBuffertoEndpoint is called, which loads the first 8 bytes into the endpoint buffer and advances the pointer so as to be ready for the next 8-byte packet.
case TYPE_STRING_DESCRIPTOR:
printf("\n\rString Descriptor: LANGID = 0x%04x, Index %d\n\r", \
SetupPacket->wIndex, SetupPacket->wValue & 0xFF);
switch (SetupPacket->wValue & 0xFF)
{
case 0:
pSendBuffer = (const unsigned char*)&LANGID_Descriptor;
BytesToSend = sizeof(LANGID_Descriptor);
break;
case 1:
pSendBuffer = (const unsigned char*)&Manufacturer_Descriptor;
BytesToSend = sizeof(Manufacturer_Descriptor);
break;
default:
pSendBuffer = NULL;
BytesToSend = 0;
}
if (BytesToSend > SetupPacket->wLength)
BytesToSend = SetupPacket->wLength;
WriteBufferToEndPoint();
break;
If any string descriptor is present, string descriptor 0 must be present, describing the languages that the device supports. Any requests for string descriptors with nonzero indexes carry a LanguageID in the wIndex field, which tells which language is supported. In our case we cheat a little and ignore the wIndex value (LANGID), returning the string regardless of which language was requested.
default:
ErrorStallControlEndPoint();
break;
}
}
void ErrorStallControlEndPoint(void)
{
unsigned char Buffer[] = { 0x01 };
/* 9.2.7 RequestError - return a STALL PID in response to the next DATA Stage Transaction */
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_IN, Buffer, 1);
/* or in the status stage of the message. */
D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_OUT, Buffer, 1);
}
When we encounter an incorrect request, a parameter, or a request that the device does not support, we must report a request error. This is defined in section 9.2.7 of the standard. A request error returns a STALL PID in response to the next data stage transaction or the status stage of the message. However, to prevent unwanted bus traffic, the message should be returned at the next data stage instead of waiting until the status stage.
unsigned char D11ReadEndpoint(unsigned char Endpoint, unsigned char*Buffer)
{
unsigned char D11Header ;
unsigned char BufferStatus = 0;
/* Select the endpoint */
D11CmdDataRead(Endpoint, &BufferStatus, 1);
/* Check whether the buffer is full */
if(BufferStatus & 0x01)
{
/* Read the dummy header - the pointer into the D11 buffer is incremented
on every read and is reset only by the Select Endpoint command */
D11CmdDataRead(D11_READ_BUFFER, D11Header, 2);
if(D11Header ) D11CmdDataRead(D11_READ_BUFFER, Buffer, D11Header );
/* Allow new packets to be received. */
D11CmdDataWrite(D11_CLEAR_BUFFER, NULL, 0);
}
return D11Header ;
}
void D11WriteEndpoint(unsigned char Endpoint, const unsigned char*Buffer, unsigned char Bytes)
{
unsigned char D11Header ;
unsigned char BufferStatus = 0;
D11Header = 0x00;
D11Header = Bytes;
/* Select the endpoint */
D11CmdDataRead(Endpoint, &BufferStatus, 1);
/* Write the header */
D11CmdDataWrite(D11_WRITE_BUFFER, D11Header, 2);
/* Write the packet */
if (Bytes) D11CmdDataWrite(D11_WRITE_BUFFER, Buffer, Bytes);
/* Validate the buffer */
D11CmdDataWrite(D11_VALIDATE_BUFFER, NULL, 0);
}
The D11ReadEndpoint and D11WriteEndpoint functions are specific to the PDIUSBD11 chip. The PDIUSBD11 has two dummy bytes that prefix any data read or write operation. The first byte is reserved, and the second byte indicates the number of bytes received or transmitted. These two functions take care of this header.
void WriteBufferToEndPoint(void)
{
if (BytesToSend == 0)
{
/* If BytesToSend is 0 and the routine was called again,
assume the buffer is smaller than the Setup Request size,
and mark the end by sending a zero-length packet
(Zero Length packet) */
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);
}
else if (BytesToSend >= 8)
{
/* Write another 8 bytes to the buffer and send them */
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, 8);
pSendBuffer += 8;
BytesToSend -= 8;
}
else
{
/* The buffer must have fewer than 8 bytes remaining. */
D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, BytesToSend);
BytesToSend = 0;
}
}
As we mentioned earlier, the WriteBufferToEndPoint function is responsible for loading data into the PDIUSBD11 chip in chunks of 8 bytes and adjusting the pointers to be ready for the next packet. The request handler calls the function once to load the first 8 bytes into the endpoint buffer. The host then sends an IN token and reads this data, and the PDIUSBD11 generates an interrupt. The EP0 IN endpoint handler then calls WriteBufferToEndpoint to load the next packet, ready for the next IN token from the host.
A transfer is considered complete when all requested bytes have been read, when a packet with a payload smaller than bMaxPacketSize is received, or when a zero-length packet is returned. Thus, if the BytesToSend counter reaches 0, we assume that the data was sent in several 8-byte chunks, and we send a zero-length packet, which indicates that this was the last of the data. However, if fewer than 8 bytes remain to be sent, we send only the remaining bytes. There is no need to pad the data with zeros here.
void loadfromcircularbuffer(void)
{
unsigned char Buffer[10];
unsigned char count;
// Read the buffer fill status
D11CmdDataRead(D11_ENDPOINT_EP1_IN, Buffer, 1);
if (Buffer == 0)
{
// The buffer is empty
if (inpointer != outpointer)
{
// there are bytes to send
count = 0;
do
{
Buffer[count++] = circularbuffer[outpointer++];
if (outpointer >= MAX_BUFFER_SIZE)
outpointer = 0;
if (outpointer == inpointer)
break; // no more data
}while (count < 8); // maximum buffer size
// Now load it into EP1_In
D11WriteEndpoint(D11_ENDPOINT_EP1_IN, Buffer, count);
}
}
}
The loadfromcircularbuffer() routine handles loading data into the EP1 IN endpoint buffer. It is normally called after an EP1 IN interrupt to reload the buffer and get ready for the next IN token on endpoint EP1. However, to send the first packet, we need to load data before an EP1 IN interrupt is received. Therefore, the routine is also called after data is received on the EP1 OUT endpoint.
By also calling the routine from the EP1 OUT handler, we could overwrite the data in the IN buffer regardless of whether the previous IN buffer data has already been sent. To prevent this, we check whether the EP1 IN buffer is empty before attempting to load new data into it.
void D11CmdDataWrite(unsigned char Command, const unsigned char*Buffer, unsigned char Count)
{
I2C_Write(D11_CMD_ADDR, &Command, 1);
if(Count)
I2C_Write(D11_DATA_ADDR_WRITE, Buffer, Count);
}
void D11CmdDataRead(unsigned char Command, unsigned char Buffer[], unsigned char Count)
{
I2C_Write(D11_CMD_ADDR, &Command, 1);
if(Count)
I2C_Read(D11_DATA_ADDR_READ, Buffer, Count);
}
D11CmdDataWrite and D11CmdDataRead are two functions specific to the PDIUSBD11 chip. They are responsible for working with the I2C interface: first sending the Address/Command bytes and then transferring the data over the I2C bus. Additional low-level functions are included in the source code but are not shown here, in order to focus on the specifics of the USB protocol.
This example can be used together with the bulkUSB.sys sample, which is part of the Windows DDK. To load the bulkUSB.sys driver, either change the code to identify itself with VID 0x045E and PID 0x930A, or change the bulkUSB.inf file for bulkUSB.sys to match the VID/PID used in this example.
You can now use the user-mode console program rwbulk.exe to send packets from the circular buffer. Use the following command to send 80-byte chunks from the PIC16F876:
rwbulk -r 80 -w 80 -c 1 -i 1 -o 0
Using a payload larger than 80 bytes will cause the PIC's circular buffer in BANK1 memory to overflow.
This example was coded for better readability at the expense of code size. It compiles to 3250 words of FLASH memory (39% of the PIC16F876 capacity).
Acknowledgments
Special thanks go to Michael DeVault of DeVaSys Embedded Systems . This example is based on code written by Michael. The code was readily developed on the DeVaSys USBLPT-PD11 USB development board before being ported to the PIC.
Source Code Download
Revision History
ADC Analog-to-Digital Converter.
bit stuffing insertion of bits into the serial bit stream on the line according to a special algorithm. It is used for synchronization or for reducing/eliminating the DC component of the signal.
bus powered devices USB devices that receive +5V power from the USB bus (the VBUS line). The supply voltage on the VBUS line is provided by the host.
daisy chained a term referring to a linear (sometimes ring) network topology. Such a topology is sometimes called a chain topology. The literal meaning of daisy chain is a chain of daisies, that is, a wreath of daisies.
downstream a downward connection. Such a connection exists for a host with respect to a USB device.
Endpoint, endpoint a concept specific to the USB standard, representing a source or sink of a data stream.
EPx EndPoint number x.
errata corrections and additions to the standard.
Feature some capability (characteristic) of a device.
Feature Selector a number that determines the choice of some capability (characteristic) of a device.
FLASH non-volatile memory for storing programs or data.
FrameWork a framework, a working environment.
handshaking a procedure for establishing communication.
Host the master device on the bus. Usually this is a computer.
ICD In Circuit Debug.
ISR Interrupt Service Routine.
Latency the waiting time for processing, the delay time.
LDO regulator, Low Dropout a regulator with a small voltage drop, sufficient for normal operation (regulation).
OTP ROM one-time programmable memory (usually for the program).
Padding filling with bytes up to the required count (usually with zero bytes).
payload the useful load, the data transmitted in the stream.
PCB printed circuit board.
peer-to-peer the ability to exchange data between equal devices.
Pipe(s) literally "a tube" ("tubes"). Refers to specially formed individual data streams through the USB interface. A pipe is sometimes called a channel.
plug the connector at the end of a cable or on the end of a USB device (for example, a flash drive). This connector is inserted into a socket, which is usually mounted on the body of a device (a computer or USB peripheral).
pull up, pull-up a load resistor for a signal line, connected between the signal line and the positive supply.
pull down, pull-down a load resistor for a signal line, connected between the signal line and ground.
self powered devices USB devices powered from a separate source (not from the USB bus).
socket a receptacle on the body of a computer or USB peripheral into which the plug (connector) is inserted.
status reporting obtaining information about the status of a device.
tiered star a multi-tier star topology.
token a symbol.
Token Packet a symbol showing what kind of data follows.
trade-off a compromise.
upstream an upward connection. Such a connection exists for a USB device with respect to the host.
Часть 1 All About USB: USB Interface Programming and Working with USB Peripherals
Часть 2 Communication Method in the USB Specification - All About USB:
Часть 3 Chapter 6: USB Requests - All About USB: USB Interface
Часть 4 Terms - All About USB: USB Interface Programming and Working
Comments