使用 Ch32V307 实现一个 USB YUV 摄像头

这里介绍如何使用 Ch32V307 实现一个 USB YUV 摄像头。这次的实验是基于WCH官方Ch32v307开发板进行的。实现一个 160*120 分辨率的YUV 摄像头,使用 307 的 USBFS 来实现。

  1. 参考的对象是之前的一个使用 32U4 制作的摄像头【参考1】,因此描述符部分是完全照抄的。实现描述符之后插入设备就能看到设备管理器中“长出来”我们自定义的摄像头了,但是还无法工作。
  2. 项目是根据WCH官方的 307 USB FS 键盘鼠标例程修改而来。设定串口输出波特率是 6 000 000 bps。如有需要,可以直接和原版对比。

       USART_Printf_Init( 6000000 );

USB FS 是下图4的接口,串口输出在下图7位置的 TXD

3.实现下面的命令应答之后,设备即可发送数据

GET_INFO SU_INPUT_SELECT_CONTROL

GET_INFO PU_BRIGHTNESS_CONTROL

GET_MIN ProcessingUnit

GET_MAX ProcessingUnit

GET_RES processingUnit

GET_DEF ProcessingUnit

GET_CUR ProcessingUnit

SET_CUR ProcessingUnit (主机对设备发数据)

GET_CUR Video_Streaming (主机打开摄像头软件之后才有)

GET_MAX VideoStreaming

GET_MIN VideoStreaming

SET_CUR VideoStreaming

4.接下来构建数据进行发送。需要特别注意的是:原版使用 Endpoint1 进行发送,但是307 的 USBFS EndPoint1 最大只能发送64 Bytes.设置为 256 会使得 EndPoint 的 Length 重置为 0。设计上307 的 FS 只有 EndPoint3 支持最大为 1023 Bytes的ISO包。因此,我们修改代码,改为EndPoint3,每一个包的长度仍然是 256Bytes.

5.发送的数据实际上是有一个头的,比如: 02 给出了头的长度是 2 Bytes, 0x80 是一个切换值,用于表示数据结束。比如,这一帧图像数据开头都是 02 80 ,那么下一帧的数据开头都是 02 81, 下下一帧就会是 02 80。接收端用这个跳变来判断一帧是否结束。

6.因为有这个头的存在,所以有效的数据长度是EndPoint 的长度是  256-2=254 字节。我们一帧的数据量计算方式如下:

以最常用的 ‌YUV420‌ 格式(如YV12)为例:

  • Y分量:160×120 = 19,200 字节
  • U分量:160×120×1/4 = 4,800 字节(色度下采样)
  • V分量:160×120×1/4 = 4,800 字节
  • 总大小 = 19,200 + 4,800 + 4,800 = 28,800 字节(约28.1KB

那么需要发送 28800/254=113个 余98Bytes。

简单起见,我们设定 Y=U=V=从0-255固定值,循环变化。发送 113次 256Bytes(带2Bytes头),再多发送一次 98Bytes。具体是在下面准备好每一个USB 包的数据进行发送。

int counter = 0;
uint8_t color=0;
void USBFS_Endp_ZSend (void) {
    if (USBFS_Endp_Busy[3] == 0) {  // 可以发送


        if (counter == 113) {
            USBFSD_UEP_TLEN (3) = 100;
        } else USBFSD_UEP_TLEN (3) = 256;

        if (counter == 0) {
            for (int i=2;i<256;i++) {
                USBFS_EP3_Buf[i]=color;
            }
            color++;

              if (USBFS_EP3_Buf[1] == 0x80) {
                 USBFS_EP3_Buf[1] = 0x81;
              } else {
                USBFS_EP3_Buf[1] = 0x80;
              }
        }
        
        printf ("%d %x %x %x\r\n", counter, USBFSD_UEP_TLEN (3), USBFS_EP3_Buf[1], USBFS_EP3_Buf[2]);
        USBFSD_UEP_TX_CTRL (3) = (USBFSD_UEP_TX_CTRL (3) & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_NONE;
        USBFS_Endp_Busy[3] = 1;
        if (counter == 113) {
            counter=0;
        } else counter++;
    }
}

工作的测试视频:

这次只是一个简单的测试,相比 MJPEG 摄像头,YUV 的分辨率不会太高(没有压缩数据量太大),但是YUV 的显示内容完全可以通过简单的计算来得到。后面我会探索更多的玩法。

完整的代码:

参考:

1. https://www.lab-z.com/adca/ Arduino Leonardo 自带的“显示屏

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注