dmitrystu / libusb_stm32

Lightweight USB device Stack for STM32 microcontrollers
Apache License 2.0
696 stars 160 forks source link

Howto use this lib in a real project? #108

Closed EmbeddedMagic closed 2 years ago

EmbeddedMagic commented 2 years ago

Hi, I was successfully trying out the cdc_loopback(), works really nice. I enabled interrupt mode. My USB knowledge is very limited, therefore I would like to explain what I want to achieve:

  1. If something is received, I want that an application callback is called that will read out the data into a buffer (I use the registered callback cdc_rxonly from the example)
    static void cdc_rxonly (usbd_device *dev, uint8_t event, uint8_t ep) {
    myRxCallback();
    }
    void myRxCallback() {
    uint8 len = readUsb(&rxBuffer[0], sizeof(rxBuffer));
    // my loopback
    writeUsb(&rxBuffer[0], len);
    }
    readUsb(uint8 rxBuffer[], uint8 sizeof(rxBuffer)) {
    return usbd_ep_read(&udev, CDC_RXD_EP, &rxBuffer[0], maxLen);
    }
  2. I want to transmit data by using in the application
    
    txLen = writeUsb(&txBuffer[0], len);

int writeUsb(uint8 txBuffer[], uint8 len) { return usbd_ep_write(&udev, CDC_TXD_EP, &txBuffer[0], len); }

This doesn't work. Where is my wrong thinking? Are there anywhere more examples?

In your cdc_txonly(...) there is

uint8_t _t = dev->driver->frame_no(); memset(fifo, _t, CDC_DATA_SZ);


I have no idea what that is doing.
Thanks for your support
GrantMTG commented 2 years ago

Hi,

Despite the limited help and example code, this is a very good USB solution that is small. Some of the other so called small USB stacks are completely out of control in size and complexity.

I never used CDC but perhaps I should play with it. I went immediately to "raw" HID, but maybe some of this will be helpful?

For reception I take the data from OUT endpoint and enqueue it in a buffer that is later examined by the main application. My protocol (packet) has a length byte at position [0] and then the rest of the packet contains that amount of data (0 to endpointsize-1). This is the job of UnLoadEp1OutFifo(). It does nothing else. After the ISR is complete, the host may again send new data.

For transmission, when the host has taken the data we get the interrupt again and I set the flag. The endpoint will NAK until I give it more data. See the second code block.

/* 
.------------------------------------------------------------------------------
| void USBD_Ep1EventCb(usbd_device *dev, uint8_t event, uint8_t ep)
|
| Endpoint1 IN and/or OUT callback (endpoint transfer complete).
|
| Generic USB device event callback for events and endpoints processing.
| Endpoints with SAME indexes i.e. 0x01 and 0x81 share the SAME callback.
| (invoked in function usbd_process_evt() in usbd_core.c, line 367.
|
| Registration of dev->endpoint[] callback:   (except endpoint 0)
|   usbd_reg_endpoint(dev, HID_RPT_EP, USBD_Ep1EventCb);
`------------------------------------------------------------------------------
*/
void USBD_Ep1EventCb(usbd_device *dev, uint8_t event, uint8_t ep)
{
switch(event)
   {
   case usbd_evt_sof:                  // We never hit this with a breakpoint.
        break;                         // Not implemented by the core.

   case usbd_evt_eptx:                 // 4 = Data packet transmitted.
        if(ep == IN_EP1)
           {
           if(bLoadEp1 == 0)           // Tell main code that we are ready for
              bLoadEp1 = 1;            // more In_Packet[] data.
           }
        break;

   case usbd_evt_eprx:                 // 5 = Data packet received.
        if(ep == OUT_EP1)
           {
           usbd_ep_read(dev, ep, Out_Packet, EP1_PACKET_SIZE);
           if(Out_Packet[0])
              UnLoadEp1OutFifo();
           }
        break;

   default:                            // We never hit this with a breakpoint.
        break;
   }
}

Main level code for sending

// Load up our data for the host.
if(bLoadEp1)
   { 
   // Returns 0 if data is placed into the In_Packet[]. If we have no data then returns 1.
   if(LoadEp1InFifo() == 0)
      {
      // If there is data to move, clear the flag, else try again next pass.
      bLoadEp1 = 0;
      usbd_ep_write(&udev, BL_IN_EP, In_Packet, EP1_PACKET_SIZE);
      }
   }

The other difficulty is making sure the endpoints are started correctly.

/*
.------------------------------------------------------------------------------
| usbd_respond USBD_SetConfigCb(usbd_device *dev, uint8_t cfg)
|
| Set Configuration callback.
|
| Callback for SET_CONFIG control request 
| (invoked in function usbd_configure() in usbd_core.c, line 66.
|
| Registration of dev->config_callback: 
|   usbd_reg_config(&udev, USBD_SetConfigCb);  // was cdc_setconf
`------------------------------------------------------------------------------
*/
usbd_respond USBD_SetConfigCb(usbd_device *dev, uint8_t cfg)
{
UsbDevConfig = cfg;                    // dev->status.device_state 

switch(cfg)
   {
   case 0: /* deconfiguring device */
           usbd_ep_deconfig(dev, BL_IN_EP);
           usbd_ep_deconfig(dev, BL_OUT_EP);
           usbd_reg_endpoint(dev, BL_IN_EP,  0);   // IN/OUT on same index share one callback.
           usbd_reg_endpoint(dev, BL_OUT_EP, 0);   // IN/OUT on same index share one callback.
           return usbd_ack;

   case 1: /* configuring device */
           usbd_ep_config(dev, BL_IN_EP,  USB_EPTYPE_INTERRUPT, EP1_PACKET_SIZE);
           usbd_ep_config(dev, BL_OUT_EP, USB_EPTYPE_INTERRUPT, EP2_PACKET_SIZE);
           usbd_reg_endpoint(dev, BL_IN_EP,  USBD_Ep1EventCb);   // IN/OUT on same index share one callback.
           usbd_reg_endpoint(dev, BL_OUT_EP, USBD_Ep1EventCb);
           usbd_ep_write(dev, BL_IN_EP, 0, 0);  // An ACK with no data.  <---Do ONLY ONE of these!---.
//           bLoadEp1 = 1;                      // Arm ability to load regular tx data. <------------'
           return usbd_ack;                     // Then will be NAKing until data.

   default:
        return usbd_fail;
    }
}
EmbeddedMagic commented 2 years ago

Thanks GrantMTG, this was eye opening! It also answersmy other questions how to get informed about other events like wakeup, suspend, ...didn't see that. Hope they are implemented, but I will find out. Yes this library is well made and works out of the box in Windows, too. A tiny API description would be anyway for the impatient very nice :-) I am not able to do that right now...maybe later.

dmitrystu commented 2 years ago

Hi, guys. Sorry for the late reply. Some sort of hell goes around me. Anyway, I will try to explain to you how it works. void usbd_poll(usbd_device *dev) is the main entry point of this stack. It can be used both inside the main loop and in the USB interrupt handler. Depending on the internal state it will issue callbacks. Most of the common things described in the CH9 are implemented in the core. There are two types of common typedef void (*usbd_evt_callback)(usbd_device *dev, uint8_t event, uint8_t ep) callbacks: general events (reset, suspend, SOF, TX completed, RX completed) and endpoint events (TX completed, RX completed, setup packet received). Some of them may be issued twice as general and as an endpoint. Also, there are special callbacks issued by the core, usbd_respond (*usbd_ctl_callback)(usbd_device *dev, usbd_ctlreq *req, usbd_rqc_callback *callback); will be issued when the control packet will be completely received by EP0 (mostly used for the class-specific control requests) If the return code from this callback is usbd_fail the core continue processing it in a standard way, otherwise answer or data will be sent to host without any processing and after that usbd_rqc_callback will be issued. The usbd_dsc_callback and usbd_cfg_callback was designed to help to serve GET_DESCRIPTOR and SET_CONFIGURATION control requests. Of course, you can read or write endpoint outside callbacks but make sure that the device is in the configured state. There are some other limitations.

  1. HW endpoint number must meet a virtual endpoint number.
  2. The main control endpoint must be EP0, You can handle EP0 callback by yourself but the core will be disabled in this case.
  3. Depending on hardware limitations and endpoint configuration it's possible to have IN and OUT endpoint with the same virtual number (i.e. 0x01 and 0x81) but it will share the same endpoint callback.

PS. The core is thread-safe and can serve as many USB controllers as you have.