Skip to content

Contributed by bzhou830, curated by Ai-Thinker

1. Recap

I've written several posts about USB before, but some examples in the early SDK couldn't run. Not long ago, Bouffalo updated the SDK, so I tried again to see whether the USB protocol stack had been updated. This update is quite good: it added a lot of new content and fixed many bugs. It's well worth learning. This post continues the USB journey to study the USB UVC example.

First, here are the previous USB posts.

Exploring USB 01. USB protocol basics - Xiao An Pai S1 & M61 tutorial collection - IoT Developer Community - Ai-Thinker Forum

Exploring USB 02. Device connection and enumeration - Xiao An Pai S1 & M61 tutorial collection - IoT Developer Community - Ai-Thinker Forum

Peripheral porting: USB mouse + M61 board - Ai-M61-Kit peripheral porting tutorials - IoT Developer Community - Ai-Thinker Forum

Peripheral porting: USB keyboard + M61 board - Ai-M61-Kit peripheral porting tutorials - IoT Developer Community - Ai-Thinker Forum

Peripheral porting: USB MIDI + M61 board - Ai-M61-Kit peripheral porting tutorials - IoT Developer Community - Ai-Thinker Forum

The UVC example studied this time is from the following SDK path:

bouffalo_sdk/examples/peripherals/usbdev/usbd_static_video/video_static_mjpeg_template.c

2. Code Analysis

The code structure of the UVC example is shown above. The USB protocol stack uses CherryUSB, and the video_static_mjpeg_template.c file calls the interfaces provided by CherryUSB to enumerate the USB device and perform other operations.

cpp
void video_init()
{
    usbd_desc_register(video_descriptor);

    usbd_add_interface(usbd_video_init_intf(&intf0, INTERVAL, MAX_FRAME_SIZE,
                                            MAX_PAYLOAD_SIZE));

    usbd_add_interface(usbd_video_init_intf(&intf1, INTERVAL, MAX_FRAME_SIZE,
                                            MAX_PAYLOAD_SIZE));

    usbd_add_endpoint(&video_in_ep);

    usbd_initialize();
}

In the main file, it simply calls the device initialization and then enters the main loop to transfer JPEG data. The UVC device data is filled using the usbd_video_mjpeg_payload_fill((uint8_t *)jpeg_data1, sizeof(jpeg_data1), packet_buffer, &out_len); function implemented by CherryUSB.

Where does the mentioned jpeg data come from? To keep it simple, the example directly uses a JPEG binary array. From the JPEG format encoding, we can verify and compare the content of this data.

3. Compile and Run

Compile and flash directly.

Plug the USB interface into the computer. In Device Manager, you'll find that a USB UVC device has been successfully enumerated.

Now we can use this device just like a USB camera. Use the playcap tool to view the UVC camera image data.

Since it's a fixed array, it displays a static image. Let's slightly modify the code to cycle between two images.

This way, two images alternate.

4. Think Bigger

The example uses arrays. If a camera is connected, directly reading the camera data and encoding it for output would give a USB camera device.

If we can generate some displayable content ourselves, can we use this UVC device as a monitor? For example, JPEG-encode the game frames generated by an NES emulator and write them out — that gives a game console device that transmits game frames via UVC. Thinking about it, it's actually quite interesting 😎😀.

Coincidentally, the M61 also has hardware support for JPEG encoding/decoding. Worth playing with!

Finally, playcap was used above to display the camera image. Actually, the powerful PotPlayer can also directly open and display a camera, and it supports setting the blending mode.

Have questions?

For other questions, please visit the unified discussion area: Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23