USB on registers: isochronous endpoint using the example of an Audio device

image<picture with board and headphones>

Even lower level (avr-vusb): habr.com/ru/post/460815

USB on registers: STM32L1 / STM32F1

USB on registers: bulk endpoint using the example of Mass Storage

USB on registers: interrupt endpoint on HID example



Today, let's look at the last type of endpoint, isochronous. It is designed to transfer data critical to delivery time, but does not guarantee its success. The most classic example is audio devices: speakers, microphones.



Oddly enough, this type of endpoint turned out to be the most brain-pumping (and this is after everything I've seen with stm'ki!). Nevertheless, today we will make an audio device and at the same time slightly finish the core of the USB library. As usual, the source codes are available:

github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1

github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1



Kernel refinement



It is necessary to refine the kernel because STM can only have isochronous points with double buffering, that is, roughly speaking, it is impossible to make 0x01 isochronous, and 0x81 control. That is, it is, of course, possible to write this in the USB descriptor, but this will not change the inside of the controller, and the real address of the point will simply differ from the one visible from the outside. Which, of course, will increase the risk of mistakes, so we will not be perverted in this direction.



It should be noted that double buffering occurs not only for isochronous points, but also for bulk, and it turns on and works differently. If isochronous points enable buffering automatically, simply because they cannot do otherwise, then for the corresponding bulk setting, you have to use the special USB_EP_KIND bit, which must be set along with the actual point type setting.



By itself, buffering means that if before one point corresponded to one buffer for transmission and one for reception, now both buffers will work either for transmission or reception, and they will only work together. As a result, the setting of a buffered point is very different from the usual one, because you need to set up not one buffer, but two. Therefore, we will not sculpt unnecessary conditions into the usual initialization, but create a separate function usb_ep_init_double () based on it.



Reception and transmission of packets does not differ so much, although it took much more time first to try to understand how it should work according to the ST logic, then to adjust the spell from the Internet to make it work. As mentioned earlier, if for an ordinary point two buffers are independent and differ in the direction of exchange, then for a buffered one they are the same and differ only in offset. So let's change the usb_ep_write and usb_ep_read functions a little so that they accept not a point number, but an offset number. That is, if earlier these functions assumed the existence of eight double points, now - 16 single ones. Accordingly, the number of the new "half-line" for writing is only equal to the number of the usual one, multiplied by two, and for usb_ep_read one must also add one (see the allocation of buffers in the PMA). Actually,this is done by the inline functions usb_ep_write and usb_ep_read for regular points. But let's take a closer look at the buffered logic.



According to the documentation, one buffer of such a point is available for hardware, the second for software. Then they switch and again do not interfere with each other. For the OUT point, the flag on the hardware side is the USB_EP_DTOG_RX bit, which needs to be read in order to understand which of the buffers has just finished writing and from where the software can read, respectively. When he read his buffer, you need to jerk the USB_EP_DTOG_TX bit, which actually switches the buffers. Not sure if this is what is meant, but at least it works.



A symmetrical situation should have been with IN points. But in practice, it turned out that you need to check and pull USB_EP_DTOG_RX. Why not TX I still do not understand ... Thanks to the user kuzulis for the link to github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c



Due to the function inline, no special overhead was added, apart from initialization. But you can, if you wish, throw it out with the linker flags. Or you don't need to throw it out: it doesn't take up so much space, and it is called only during initialization. This is not a HAL for you, where functions are not only heavy, but also call each other all the time.



As a result, the endpoints have learned to work in buffered mode ... if you don't breathe too hard on them.



For the user, the difference is small: instead of usb_ep_init, use usb_ep_init_double, and instead of usb_ep_write and usb_ep_read, use usb_ep_write_double and usb_ep_read_double, respectively.



AudioDevice device



And now, when we figured out the technical rake, let's move on to the most interesting thing - setting up an audio device.



According to the USB standard, an audio device is a set of entities connected to each other in a certain topology, through which the audio signal passes. Each entity has its own unique number (bTerminalID, aka UnitID), by which other entities or endpoints can connect to it, the host also uses it if it wants to change some parameters. And he is considered the only way out of this entity. But there may be no inputs at all (if it is an input terminal), or there may be more than one (bSourceID). Actually, by writing the numbers of the entities from which the current one receives an audio signal to the bSourceID array, we describe the entire topology, which as a result can turn out to be very quick. As an example, I will give the topology of a purchased USB sound card (the numbers show bTerminalID / UnitID):



lsusb and its decryption
Bus 001 Device 014: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller

#   
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0d8c C-Media Electronics, Inc.
  idProduct          0x013c CM108 Audio Controller
  bcdDevice            1.00
  iManufacturer           1 
  iProduct                2 
  iSerial                 0 
  bNumConfigurations      1
  
#  
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x00fd
    bNumInterfaces          4  #   
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    
# 0 -  
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      1 Control Device
      bInterfaceProtocol      0 
      iInterface              0 
      AudioControl Interface Descriptor:
        bLength                10
        bDescriptorType        36
        bDescriptorSubtype      1 (HEADER)
        bcdADC               1.00
        wTotalLength       0x0064
        bInCollection           2  # !    (2)
        baInterfaceNr(0)        1  #   
        baInterfaceNr(1)        2  # 
 
#####  #####
# 1 InputTerminal (USB,  ) 
      AudioControl Interface Descriptor:
        bLength                12
        bDescriptorType        36
        bDescriptorSubtype      2 (INPUT_TERMINAL)
        bTerminalID             1  #    
        wTerminalType      0x0101 USB Streaming
        bAssocTerminal          0
        bNrChannels             2  #    
        wChannelConfig     0x0003  #   -    
          Left Front (L)
          Right Front (R)
        iChannelNames           0 
        iTerminal               0 
        
# 2 InputTerminal ()
      AudioControl Interface Descriptor:
        bLength                12
        bDescriptorType        36
        bDescriptorSubtype      2 (INPUT_TERMINAL)
        bTerminalID             2
        wTerminalType      0x0201 Microphone
        bAssocTerminal          0
        bNrChannels             1
        wChannelConfig     0x0001
          Left Front (L)
        iChannelNames           0 
        iTerminal               0 
        
# 6 OutputTerminal (),     9
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (OUTPUT_TERMINAL)
        bTerminalID             6
        wTerminalType      0x0301 Speaker
        bAssocTerminal          0
        bSourceID               9  #    
        iTerminal               0 
        
# 7 OutputTerminal (USB),     8
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (OUTPUT_TERMINAL)
        bTerminalID             7
        wTerminalType      0x0101 USB Streaming
        bAssocTerminal          0
        bSourceID               8
        iTerminal               0 
        
# 8 Selector,      10
      AudioControl Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      5 (SELECTOR_UNIT)
        bUnitID                 8
        bNrInPins               1  #        
        baSourceID(0)          10  #   
        iSelector               0 
        
# 9 Feature,     15
      AudioControl Interface Descriptor:
        bLength                10
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                 9
        bSourceID              15
        bControlSize            1
        bmaControls(0)       0x01
          Mute Control
        bmaControls(1)       0x02
          Volume Control
        bmaControls(2)       0x02
          Volume Control
        iFeature                0 
        
# 10 Feature,     2
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                10
        bSourceID               2
        bControlSize            1
        bmaControls(0)       0x43
          Mute Control
          Volume Control
          Automatic Gain Control
        bmaControls(1)       0x00
        iFeature                0 
        
# 13 Feature,     2
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                13
        bSourceID               2
        bControlSize            1
        bmaControls(0)       0x03
          Mute Control
          Volume Control
        bmaControls(1)       0x00
        iFeature                0 
        
# 15 Mixer,     1  13
      AudioControl Interface Descriptor:
        bLength                13
        bDescriptorType        36
        bDescriptorSubtype      4 (MIXER_UNIT)
        bUnitID                15
        bNrInPins               2  #   
        baSourceID(0)           1  #   
        baSourceID(1)          13
        bNrChannels             2
        wChannelConfig     0x0003
          Left Front (L)
          Right Front (R)
        iChannelNames           0 
        bmControls(0)        0x00
        iMixer                  0 
#####   #####

#  1 () -    
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      
#  1 () -     
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       1
      bNumEndpoints           1
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      AudioStreaming Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      1 (AS_GENERAL)
        bTerminalLink           1
        bDelay                  1 frames
        wFormatTag         0x0001 PCM
      AudioStreaming Interface Descriptor:
        bLength                14
        bDescriptorType        36
        bDescriptorSubtype      2 (FORMAT_TYPE)
        bFormatType             1 (FORMAT_TYPE_I)
        bNrChannels             2
        bSubframeSize           2
        bBitResolution         16
        bSamFreqType            2 Discrete
        tSamFreq[ 0]        48000
        tSamFreq[ 1]        44100
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            9
          Transfer Type            Isochronous
          Synch Type               Adaptive
          Usage Type               Data
        wMaxPacketSize     0x00c8  1x 200 bytes
        bInterval               1
        bRefresh                0
        bSynchAddress           0
        AudioStreaming Endpoint Descriptor:
          bLength                 7
          bDescriptorType        37
          bDescriptorSubtype      1 (EP_GENERAL)
          bmAttributes         0x01
            Sampling Frequency
          bLockDelayUnits         1 Milliseconds
          wLockDelay         0x0001
          
#  2 () - 
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      
#  2 ()
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       1
      bNumEndpoints           1
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      AudioStreaming Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      1 (AS_GENERAL)
        bTerminalLink           7
        bDelay                  1 frames
        wFormatTag         0x0001 PCM
      AudioStreaming Interface Descriptor:
        bLength                14
        bDescriptorType        36
        bDescriptorSubtype      2 (FORMAT_TYPE)
        bFormatType             1 (FORMAT_TYPE_I)
        bNrChannels             1
        bSubframeSize           2
        bBitResolution         16
        bSamFreqType            2 Discrete
        tSamFreq[ 0]        48000
        tSamFreq[ 1]        44100
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            9
          Transfer Type            Isochronous
          Synch Type               Adaptive
          Usage Type               Data
        wMaxPacketSize     0x0064  1x 100 bytes
        bInterval               1
        bRefresh                0
        bSynchAddress           0
        AudioStreaming Endpoint Descriptor:
          bLength                 7
          bDescriptorType        37
          bDescriptorSubtype      1 (EP_GENERAL)
          bmAttributes         0x01
            Sampling Frequency
          bLockDelayUnits         0 Undefined
          wLockDelay         0x0000
#####    #####

#  3 "    " ( )
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        3
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.00
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      60
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x87  EP 7 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0004  1x 4 bytes
        bInterval               2

      
      









image



We are going to do something simpler (I took the blank from here ):



image



Here you can see two independent signal propagation branches: either from USB through a “feature” to a “speaker”, or from a “microphone” through another “feature” to USB. The microphone and speaker are not just put in quotes: they are not on my debug board, so instead of the sound itself, we will use buttons and LEDs. However, nothing new. "Features" in my case do nothing and are added more for beauty.



It should be clarified right away that the signal in this model is considered to be composed of one or more logical channels. That is, if, for example, I change a mono speaker to a stereo one, the topology itself will remain unchanged, only the signal format will change.



I have not dug deep into the differences between the kinds of "features" and other entities, but I will not disdain to retell a piece of documentation.



1. Input Terminal

As the name suggests, it is through it that the audio signal enters the audio device. It can be USB, it can be an ordinary microphone, a headset microphone, even a microphone array.



2. Output Terminal

It is also quite obvious - that through which the sound leaves our device. It can be the same USB, it can be a speaker, a headset, a speaker in the monitor, speakers of various frequencies, and a bunch of other devices.



3. Mixer Unit

It takes several input signals, amplifies each one by a predetermined amount and adds the result to the output channel. If desired, you can set the gain to zero, which will reduce it to the next entity.



4. Selector Unit

Takes multiple input signals and redirects one of them to the output.



5. Filter (Feature Unit)

Takes a single input signal, changes the sound parameters (volume, tone, etc.) and outputs it to the output. Naturally, all these parameters are applied to the entire signal in the same way, without the interaction of logical channels within it



6. Processing Unit

But this thing already allows you to manipulate individual logical channels within each input. Moreover, it allows you to make the number of logical channels in the output not equal to the number in the input.



7. Extension Unit

The whole set of non-standard entities, so that the sick fantasy of equipment manufacturers was free. Accordingly, both behavior and settings will depend on this very fantasy.



Some entities have parameters like gain or channel number, which can be influenced by the host using setFeature / getFeature queries on the entity number. But here, to be honest, I don't really understand how to check it at all. Probably, you need some kind of special software, which I do not have. Well, okay, anyway I got into it just to check all types of points ... on my head ...



Rake in the descriptor



Unlike previous USB devices, the descriptor here is complex, layered and tends to scare Windows into BSOD. As we saw above, the topology of an autologous device can be quite complex and spreading. An entire interface stands out for its description. Obviously, it will not contain endpoints, but it will contain a list of entity descriptors and descriptions of what their inputs are connected to. I don't see much sense here, it's easier to look at the code and documentation. I will only note the main rake: here it is described which interfaces with the corresponding endpoints refer specifically to this device. For example, if you want to change my configuration and remove the speaker from there, you will not only have to delete half of the entities (thank the macros, at least there will be no problem with calculating the length of the descriptor), but also reduce the bInCollection field to 1,then remove the number of the extra interface from the array bInterfaceNr following it.



Further there are interfaces responsible for data exchange. In my case, the 1st interface is responsible for the microphone, and the 2nd for the speaker. It is worth paying attention here, first of all, to two variants of each of these interfaces. One with bAlternateSetting equal to 0, the second with 1. They differ in the presence of an endpoint. That is, if our device is not currently in use, the host simply switches to the alternative interface that is not equipped with the endpoint, and no longer wastes the bus bandwidth on it.



The second feature of data interfaces is the audio signal format. The corresponding descriptor specifies the encoding type, number of channels, resolution and sampling rate (which is specified by a 24-bit number). There are quite a few coding options, but we will use the simplest one - PCM. In fact, it is just a sequence of values ​​of the instantaneous value of the signal without any coding, and the value is considered a signed integer . The signal resolution is set in two places (it's not clear why): the bSubFrameSize field specifies the number of bytes , and bBitResolution specifies the number of bits... It can probably be pointed out that the range of our sound card does not go up to the full range of the data type, say int16_t and is only 10 bits.



And finally, the descriptor of the actual endpoint. It also differs slightly from the usual ones, since it provides, firstly, several synchronization options, and secondly, the number of the entity with which this point is associated (bTerminalLink) . Synchronization options are written in high-order bits directly into the endpoint type (which is why the isochronous point is moved to the default branch in the initialization function), but I haven’t dealt with their details, so I can’t tell you anything interesting. Instead of synchronization, we will use a regular controller timer, which will generate interrupts at approximately the desired frequency.



Oh yeah, I almost forgot to mention another bunch of BSODs when testing the wrong descriptors. Let me remind you once again: the number of data interfaces must correspond to the number of bInCollection, and their numbers must correspond to the array following it!

Hidden text
, , . --.





The logic of the device



As I already said, for tests it makes no sense to fence hinged components on the debug board, so all testing will be carried out with what has already been installed - buttons and LEDs. However, in this case, this does not constitute a problem: the "microphone" can simply generate a sinusoid with a frequency of, say, 1 kHz, and the "speaker" turn on the LED when the sound threshold value is exceeded (say, above 10,000: with the specified 16 bits of resolution, which corresponds range -32768 ... +32767, this is about a third).



But with testing, there was a small problem: I did not find an easy way to redirect the signal from the microphone to the stdin of some program. It seems that earlier this was done simply by reading / dev / dsp, but now something has broken. However, nothing critical, because there are all sorts of libraries for interaction with multimedia - SDL, SFLM and others. Actually in SFML I wrote a simple utility for reading from a microphone and, if necessary, visualizing the signal.



I will pay special attention to the limitations of our audio device: as far as I understand, an isochronous IN request is sent once per millisecond (but there can be many OUTs), which limits the sampling rate. Let's say the size of the endpoint is 64 bytes (taking into account buffering, it takes 128 bytes in memory, but the host does not know about it), the resolution is 16 bits, that is, 32 samples can be sent at a time. Given a 1 ms interval, we get a theoretical limit of 32 kHz for one channel. The easiest way to get around this is to increase the size of the endpoint. But here we must remember that the size of the total PMA buffer is only 512 bytes. Minus the point distribution table, minus ep0, we get a maximum of 440 bytes, that is, 220 bytes per single point, taking into account buffering. And this is the theoretical limit.



But the fact that the host can send several OUT requests in one frame suggests that the device can do the same. It remains to understand how. Perhaps this is solved by a competent synchronization setting. But for me this question is no longer of interest: the isochronous points work, the buffered points work, the audio device works - the task is completed.



Conclusion (common for the cycle)



Well, we got acquainted with the USB device in STM32F103 and STM32L151 controllers (and others with a similar implementation), were surprised at the logic of some architectural solutions (I was especially impressed by the USB_EPnR register, however, double buffering is also not lagging behind), examined all types of endpoints and checked them, by building the appropriate devices. So we can say that this series of articles has come to a logical conclusion. Although this, of course, does not mean that I will abandon controllers or USB: in the distant plans, I still have to deal with composite devices (so far it looks easy, but after all, isochronous points also did not bode well) and USB on controllers of other families.



All Articles