📜 ⬆️ ⬇️

We work with a USB stack nRF24LU1 +. Part 2

Continued, the first part is here .
Simplified USB structure. It can be seen that there are only two interrupts USBIRQ and USBWU


USB initialization


Nothing complicated, register descriptions, see datashite. Allow endpoint 0-2, enable interrupts, specify buffers for data storage. (Endpoint 2 and 3 are allowed to work with double buffer when sending data to the host)
USB initialization
void usb_init(void) { // Setup state information usb_state = DEFAULT; usb_bm_state = 0; // Setconfig configuration information usb_current_config = 0; usb_current_alt_interface = 0; // Disconnect from USB-bus since we are in this routine from a power on and not a soft reset: usbcs |= 0x08; delay_ms(50); usbcs &= ~0x08; /*intterrupt enable uresie,suspie,sutokie,sudavie */ usbien = 0x1d; /*Endpoint 0 to 5 IN interrupt enables (in_ien)*/ in_ien = 0x01; /*Endpoints 0 to 5 IN interrupt request register (in_irq) - clear interrupt*/ in_irq = 0x1f; /*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/ out_ien = 0x01; /*Endpoints 0 to 5 OUT interrupt request register (out_irq) - clear in interrupt*/ out_irq = 0x1f; // Setup the USB RAM with some OK default values: bout1addr = MAX_PACKET_SIZE_EP0/2; bout2addr = MAX_PACKET_SIZE_EP0/2 + USB_EP1_SIZE/2; bout3addr = MAX_PACKET_SIZE_EP0/2 + 2*USB_EP1_SIZE/2; bout4addr = MAX_PACKET_SIZE_EP0/2 + 3*USB_EP1_SIZE/2; bout5addr = MAX_PACKET_SIZE_EP0/2 + 4*USB_EP1_SIZE/2; binstaddr = 0xc0; bin1addr = MAX_PACKET_SIZE_EP0/2; bin2addr = MAX_PACKET_SIZE_EP0/2 + USB_EP1_SIZE/2; bin3addr = MAX_PACKET_SIZE_EP0/2 + 2*USB_EP1_SIZE/2; bin4addr = MAX_PACKET_SIZE_EP0/2 + 3*USB_EP1_SIZE/2; bin5addr = MAX_PACKET_SIZE_EP0/2 + 4*USB_EP1_SIZE/2; // Set all endpoints to not valid (except EP0IN and EP0OUT) /*Endpoints 0 to 5 IN valid bits (Inbulkval)*/ inbulkval = 0x01; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ outbulkval = 0x01; /*Isochronous IN endpoint valid bits (inisoval)*/ inisoval = 0x00; /*Isochronous OUT endpoint valid bits (outisoval)*/ outisoval = 0x00; /* Switch ON Endpoint 1 */ /*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */ in_ien |= 0x02; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ inbulkval |= 0x02; /*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/ out_ien |= 0x02; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ outbulkval |= 0x02; /* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/ out1bc = 0xff; /* Switch ON Endpoint 2 */ /*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */ in_ien |= 0x04; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ inbulkval |= 0x04; /*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/ out_ien |= 0x04; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ outbulkval |= 0x04; /* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/ out2bc = 0xff; /* Switch ON Endpoint 3 */ /*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */ in_ien |= 0x08; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ inbulkval |= 0x08; /*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/ out_ien |= 0x08; /*Endpoints 0 to 5 OUT valid bits (outbulkval)*/ outbulkval |= 0x08; /* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/ out3bc = 0xff; } 


Then we enable overlapping of USBIRQ (further we need to analyze the flags) and enable interrupts globally:
 USB = 1; // USBIRQ is mapped to IEN1.4 EA = 1; // enable global interrupt 

Actually this is the whole initialization. At each interruption, a handler will be called which looks like this:
 /* USB interrupt request */ void usb_irq_handler(void) interrupt INTERRUPT_USB_INT { usb_irq(); } 


USB interrupt handler


This is the basis of our system. All the action will be spinning here. What event occurred we will understand after analyzing the register IVEC.
 void usb_irq(void) { uint8_t i; uint8_t temp_irq; if (ivec == INT_USBRESET) { /*The USB interrupt request register (usbirq) - clear USB reset interrupt request*/ usbirq = 0x10; usb_state = DEFAULT; usb_current_config = 0; usb_current_alt_interface = 0; usb_bm_state = 0; } else { switch(ivec) { case INT_SUDAV: /*Setup data valid interrupt*/ usbirq = 0x01; isr_sudav(); break; case INT_SOF: /*Start of frame interrupt (sofir)*/ usbirq = 0x02; break; case INT_SUTOK: /*Setup token interrupt*/ usbirq = 0x04; packetizer_data_ptr = NULL; packetizer_data_size = 0; packetizer_pkt_size = 0; break; case INT_SUSPEND: /*Suspend interrupt (suspir)*/ usbirq = 0x08; break; case INT_EP0IN: in_irq = 0x01; packetizer_isr_ep0_in(); break; case INT_EP0OUT: out_irq = 0x01; packetizer_data_size = 0; USB_EP0_HSNAK(); break; case INT_EP1IN: in_irq = 0x02; int_ep1in_handler(); break; case INT_EP1OUT: out_irq = 0x02; out1bc = 0xff; break; case INT_EP2IN: in_irq = 0x04; break; default: break; } } } 


Enumeration


Perhaps, use the quote from the wonderful guide USB in a NutShell - guide to the standard USB
Enumeration is the process of determining the fact that a device is actually connected to the USB bus and what parameters it requires - power consumption,
the number and type of the end point (or points), the device class, etc. In the enumeration process, the host assigns an address to the device and allows configuration,
allowing the device to transfer data over the bus. [...]
The general enumeration process under the Windows operating system includes the following steps:
')
1. The host or hub detects the connection of a new device using pull-up resistors, which the device connects to a pair of data signal wires (D + and D-). The host makes a delay of at least 100 ms, which allows you to insert the connector completely and stabilize the device power supply.
2. The host issues a reset to the bus, which puts the device in the default state. The device can now respond to the default zero address.
3. The MS Windows host requests the first 64 bytes of the device descriptor (Device Descriptor).
4. After receiving the first 8 bytes of the device descriptor, the host immediately issues a new bus reset.
5. Now the host issues the Set Address command, which puts the device into addressable state.
6. The host requests all 18 bytes of the device descriptor.
7. It then requests 9 bytes of the Configuration Descriptor to determine its full size.
8. The host requests 255 bytes of the configuration descriptor.
9. The host requests all string descriptors (String Descriptors), if any.

To support WinUSB, an additional procedure is required:

I recommend this manual for understanding the process.

For analysis and debugging, the minimum set is the UART on the controller side and the USB software analyzer on the host. I recommend the free Microsoft Message Analyzer .

So, all enumeration (and subsequent vendor requset and other requests, if needed) will be processed in the function isr_sudav () (Setup data valid interrupt).
isr_sudav ()
 static void isr_sudav() { bmRequestType = setupbuf[0]; /* Host-to-device standart request */ if((bmRequestType & 0x60 ) == 0x00) { switch(setupbuf[1]) { case USB_REQ_GET_DESCRIPTOR: usb_process_get_descriptor(); break; case USB_REQ_GET_STATUS: usb_process_get_status(); break; case USB_REQ_SET_ADDRESS: usb_state = ADDRESSED; usb_current_config = 0x00; break; case USB_REQ_GET_CONFIGURATION: switch(usb_state) { case ADDRESSED: in0buf[0] = 0x00; in0bc = 0x01; break; case CONFIGURED: in0buf[0] = usb_current_config; in0bc = 0x01; break; case ATTACHED: case POWERED: case SUSPENDED: case DEFAULT: default: USB_EP0_STALL(); break; } break; case USB_REQ_SET_CONFIGURATION: switch(setupbuf[2]) { case 0x00: usb_state = ADDRESSED; usb_current_config = 0x00; USB_EP0_HSNAK(); break; case 0x01: usb_state = CONFIGURED; usb_bm_state |= USB_BM_STATE_CONFIGURED; usb_current_config = 0x01; USB_EP0_HSNAK(); break; default: USB_EP0_STALL(); break; } break; case USB_REQ_GET_INTERFACE: // GET_INTERFACE in0buf[0] = usb_current_alt_interface; in0bc = 0x01; break; case USB_REQ_SET_DESCRIPTOR: case USB_REQ_SET_INTERFACE: // SET_INTERFACE case USB_REQ_SYNCH_FRAME: // SYNCH_FRAME default: USB_EP0_STALL(); break; } } // bmRequestType = 0 01 xxxxx : Data transfer direction: Host-to-device, Type: Class else if((bmRequestType & 0x60 ) == 0x20) // Class request { if(setupbuf[6] != 0 && ((bmRequestType & 0x80) == 0x00)) { // If there is a OUT-transaction associated with the Control-Transfer-Write we call the callback // when the OUT-transaction is finished. Note that this function do not handle several out transactions. out0bc = 0xff; } else { USB_EP0_HSNAK(); } } /* Extended Compat ID OS Descriptor setupbuf[1] (bRequest) is equal to MS_VendorCode (0xAA is current program)*/ else if(bmRequestType == 0xC0 && setupbuf[1] == MS_VENDORCODE) { packetizer_pkt_size = MAX_PACKET_SIZE_EP0; //xprintf("Extended Compat ID\r"); packetizer_data_ptr = g_usb_extended_compat_id; packetizer_data_size = MIN(setupbuf[6], packetizer_data_ptr[0]); packetizer_isr_ep0_in(); } /* Extended Properties OS Descriptor */ else if(bmRequestType == 0xC1 && setupbuf[1] == MS_VENDORCODE) { packetizer_pkt_size = MAX_PACKET_SIZE_EP0; //xprintf("Extended Properties ID\r"); packetizer_data_ptr = g_usb_extended_proper_os; packetizer_data_size = MIN(setupbuf[6], packetizer_data_ptr[0]); packetizer_isr_ep0_in(); } else // Unknown request type { USB_EP0_STALL(); } } 


The penultimate two branches in this function are responsible for Extended Compat ID OS Descriptor and Extended Properties OS Descriptor requests.
At this stage, the enumeration is completed, you can proceed to receive / transmit data.

Bulk tranfer


In tranfer


The host wants to accept data. Sends In token. If the inxbsy bit (x is the endpoint number) is set, the USB controller sends data to the host. In order to set this bit itself, we must first load the data into the inxbuf [] buffer and tell how much we have written to the inxbc register, i.e. data must be preloaded before interruption.
 void int_ep1in_handler(void) { uint8_t i; for(i=0;i<64;i++){ in1buf[i]=i; } in1bc = 64; } 

After successfully sending the data - received an ACK - the INT_EP1IN interrupt occurs (usb_irq function) and the next piece of data can be loaded into it.

Out tranfer


The host wants to send data. Sends out token. Next to the token are data. After the data is received, the INT_EP1OUT interrupt is triggered. Data is read from the outxbuf [] buffer. The size of the data is outxbc. After the data have been processed, it is necessary to write any value to the outxbc register, thereby letting the USB controller know that we are ready to receive the next piece of data.
 void int_ep1out_handler(void) { do_anything(out1buf,in1bc); in1bc = 0xFF; //dummy write } 


Double buffering

If the task is to send or receive data with maximum speed, then you need to use double buffering. While the controller is busy receiving or sending data, its buffers (inxbuf and outxbuf) are not available. The idea is that an additional buffer appears, where we write when the controller is busy, and then just switch them.
For this mode is responsible register usbpair. For example, writing 0x01 there we combine 2 and 3 in endpoint. The endpoint 3 buffer will be the second buffer.

However, the documentation on this mode is extremely incomprehensible and sometimes mutually exclusive. Through long experiments, I got this service code buffers for transmission to the host:
 while(1) { while(in2cs != 0); if(i%2){ //first buffer for(i=0;i<64;i++) in2buf[i]=0xAA; in2bc = 64; } else { //second buffer for(i=0;i<64;i++) in3buf[i]=0xBB; in2bc = 64; } i++; } 

We wait until IN 2 is free and in turn we pour the data then in in2buf then in3buf, then all the while we update in2bc (!!!). The code is not specifically tested, use with caution.

We program Host


All code was written in Visual Studio 2013. Immediately install a Windows Driver Kit (WDK). It adds USB project templates. I advise you to study a couple of manuals from Microsoft, everything is detailed there:

The program is simple to impossible: open the device, get a descriptor, read and write in the pipe. Everything.

Speed ​​measurement


Here are the results achieved:
Data transfer to host in single buffer360 kb / s
Data transfer to host in double buffer510 kb / s
Receive data from single buffer host230 kb / s
Receive data from host double buffernot tested

Conclusion


I hope the article will benefit beginners and not only. The source is not very combed, do not kick much, I post it on the githab, use it for health.
github.com/covsh/workingtitle/tree/master/nRF24LU1P

Source: https://habr.com/ru/post/367955/


All Articles