本帖最后由 赵玥玥 于 2023-2-26 12:08 编辑
#申请原创# @21小跑堂 @21小跑堂 @21小跑堂
1.项目介绍 现在各种屏幕共享的软件都已经开始收费了,而且局域网下的无线共享设备市面上也很少,为了发挥CH32V307的优秀外设USBHS和千兆以太网,特此设计了一款远程共享摄像头项目。
2.设计 首先需要对整体框架进行设计,一共分为3个部分。 ①、摄像头服务器,该部分的功能主要是获取摄像头数据,并通过USB HS往CH32V307发送摄像头数据,同时接收CH32V307发送上来的温湿度数据。 ②、CH32V307中继器,该部分的功能是通过总线获取DHT11的温湿度数据,并通过USB HS发送给摄像头服务器。同时接收USB HS的摄像头数据+温湿度数据,并通过以太网以UDP协议的方式发送出去。 ③、摄像头客户端,该部分的功能就是解析从UDP接收下来的数据,并判断接收的是否为一个完整的帧数据,如果是则显示到界面上。该部分可以拥有多台设备。 系统整体框图如下图1所示: 图1 系统整体框图 首先是摄像头服务器的设计,该部分采用的是QT框架进行设计,首先需要从摄像头中获取帧数据,然后将帧数据RGB888格式转换成JPG压缩格式,减少USB和以太网传输数据的压力。同时将视频帧数据进行拆包发送。 摄像头服务器的工作流程如下图2所示: 图2 摄像头服务器工作流程图 摄像头服务器主要代码如下所示: - void USB_CAMERA::cameraImageCaptured(int, QImage image)
- {
- QBuffer jpgBuffer;
- QPixmap tempPixmap = QPixmap::fromImage(image);
- unsigned char buf2[512 + 1];
- int res;
- // 获取当前时间戳
- qDebug()<< "start-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
- jpgBuffer.open(QBuffer::WriteOnly);
- ui->labelCamera->setPixmap(tempPixmap);
- image.save(&jpgBuffer, "JPG");
- qDebug() << "add after:" << jpgBuffer.size();
- if(jpgBuffer.size() % 508 != 0)
- {
- char data1[512] = {0};
- jpgBuffer.write(data1, 508 - (jpgBuffer.size() % 508));
- }
- jpgBuffer.close();
- jpgBuffer.open(QBuffer::ReadOnly);
- buf2[1] = jpgBuffer.size() / 508;
- buf2[3] = tempValue;
- buf2[4] = humiValue;
- for(int i = 0;i < jpgBuffer.size() / 508;i++)
- {
- buf2[2] = i + 1;
- jpgBuffer.seek(508 * i);
- jpgBuffer.read((char *)&buf2[5], 508);
- res = hid_write(handle, buf2, 513); //第一个字节发不出去
- }
- qDebug()<< "end-----" << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
- ui->labelTemp->setText("Temp:" + QString::number(tempValue, 10) + "C");
- ui->labelHumi->setText("Humi:" + QString::number(humiValue, 10) + "%");
- }
之后就是CH32V307中继器的设计,该部分设计主要分为3个部分,第一个就是接收USB HS的数据,该部分需要采用DMA方式接收,这样能够给MCU更多的机会处理以太网的数据。将从USB HS接收到的数据存在存在缓存中,之后使用以太网以UDP的协议把数据发送出去。在每间隔一段时间获取DHT11温湿度传感器的数据,将数据使用USB HS发送给上位机,其实这里会有人会好奇为什么不直接把温湿度数据从UDP发送出去?因为设计将温湿度数据夹杂在视频帧数据中,这样可以让通信协议变得更简单。 CH32V307中继器的工作流程如下图3所示: 图3 CH32V307中继器工作流程图 CH32V307中继器主要代码如下所示: - void UART2_Rx_Service( void )
- {
- if ( USBHS_Endp_Busy[ 2 ] == 0x00)
- {
- /* Upload packet via USB. */
- USBHS_EP2_Tx_Buf[0] = temperature;
- USBHS_EP2_Tx_Buf[1] = humidity;
- USBHSD->UEP2_TX_DMA = (uint32_t)(uint8_t *)USBHS_EP2_Tx_Buf;
- USBHSD->UEP2_TX_LEN = 512;
- USBHS_Endp_Busy[ 2 ] |= DEF_UEP_BUSY;
- USBHSD->UEP2_TX_CTRL = ((USBHSD->UEP2_TX_CTRL) & ~USBHS_UEP_T_RES_MASK) | USBHS_UEP_T_RES_ACK;
- UART2_Rx_RemainLen -= 2;
- UART2_Rx_Deal_Ptr += 2;
- if (UART2_Rx_Deal_Ptr >= DEF_UART2_BUF_SIZE)
- {
- UART2_Rx_Deal_Ptr = 0x00;
- }
- }
- }
- void UART2_Tx_Service(int *dhtTimer)
- {
- uint16_t pkg_len = 0;
- uint8_t *pbuf;
- /* Pass-through USB-HID data to uart2 */
- if(RingBuffer_Comm.RemainPack)
- {
- *dhtTimer += 500;
-
- pbuf = &Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE];
- printf("send uart \n");
- camera_udp_send_data((u8 *)&Data_Buffer[(RingBuffer_Comm.DealPtr) * DEF_USBD_HS_PACK_SIZE], 512);
- NVIC_DisableIRQ(USBHS_IRQn); // Disable USB interrupts
- RingBuffer_Comm.RemainPack--;
- RingBuffer_Comm.DealPtr++;
- if(RingBuffer_Comm.DealPtr == DEF_Ring_Buffer_Max_Blks)
- {
- RingBuffer_Comm.DealPtr = 0;
- }
- NVIC_EnableIRQ(USBHS_IRQn); // Enable USB interrupts
- }
- /* Monitor whether the remaining space is available for further downloads */
- if(RingBuffer_Comm.RemainPack < (DEF_Ring_Buffer_Max_Blks - DEF_RING_BUFFER_RESTART))
- {
- if(RingBuffer_Comm.StopFlag)
- {
- RingBuffer_Comm.StopFlag = 0;
- USBHSD->UEP1_RX_CTRL = (USBHSD->UEP1_RX_CTRL & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_ACK;
- }
- }
- }
- int main(void)
- {
- Delay_Init();
- USART_Printf_Init(115200);
- printf( "SystemClk:%d\r\n",SystemCoreClock) ;
- printf( "Compatibility HID Running On USBHS Controller\n" );
- /* Variables init */
- Var_Init();
- /* UART2 init */
- UART2_Init();
- UART2_DMA_Init();
- /* USB20 device init */
- USBHS_RCC_Init( );
- USBHS_Device_Init( ENABLE );
- camera_udp_init();
- /* Timer init */
- TIM2_Init();
- printf("start usbh\n");
- if(DHT11_Init())
- {
- printf("DTH11 error\n");
- }
- else
- {
- DHT11_Read_Data(&temperature, &humidity);
- printf("Temp:%d,Humi:%d\n", temperature, humidity);
- DHT11_flag = 1;
- printf("DTH11 success\n");
- }
- while(1)
- {
- dhtTimer++;
- if(dhtTimer >= 500000)
- {
- dhtTimer = 0;
- if(DHT11_Read_Data(&temperature, &humidity) == 0 && DHT11_flag == 1)
- {
- printf("Temp:%d,Humi:%d\n", temperature, humidity);
- UART2_Rx_Service();
- }
- }
- camera_udp_task();
- if (USBHS_DevEnumStatus)
- {
- UART2_Tx_Service(&dhtTimer);
- HID_Set_Report_Deal();
- }
- }
- }
最后就是摄像头客户端的设计,该部分主要是接收UDP的数据,熟悉UDP的应该知道UDP是没有握手机制的,所以UDP丢包的情况很常见,那么我们要如何处理丢包的情况呢?首先在每一包数据中都包含视频帧的总包数量,同时包含当前包是视频帧拆包的第几包,只有接收到总包数量的包数时,说明没有丢包,此时就可以将接收到的视频帧显示在软件上。 摄像头客户端的工作流程如下图3所示: 图4 摄像头客户端工作流程图 摄像头客户端主要代码如下所示: - //服务器接收
- private void SocketReceive()
- {
- //进入接收循环
- while (true)
- {
- //对data清零
- recvData = new byte[512];
- //获取客户端,获取客户端数据,用引用给客户端赋值
- recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
- string recStr;
- recStr = "From Server Len: " + recvLen + "\r\n";
- System.Console.WriteLine(recStr);
- if (recvData[1] == 1 || recImageCnt > recvData[0])
- {
- recImageCnt = 0;
- }
- Array.ConstrainedCopy(recvData, 4, recvImageData, 508 * recImageCnt, 508);
- recImageCnt++;
- if (recvData[1] == recvData[0])
- {
- if (recImageCnt == recvData[0])
- {
- labelTemp.Text = "Temp:" + recvData[2].ToString() + "C";
- labelHumi.Text = "Humi:" + recvData[3].ToString() + "%";
- img = ByteToImage(recvImageData);
- pictureBoxCamera.Image = img;
- }
- recImageCnt = 0;
- }
- }
- }
3.总结 一开始设计本来准备采用CH32V307的DVP获取摄像头数据的,但是后面发现帧率太低了,会影响到摄像头展示的效果,之后才采用上面的方案。再次我还测试了USB HS和USB FS的区别,USB HS一包最多可以发送1024个字节,而USB FS一包最多发送64个字节,相差16倍,开发这个项目也算体验了一下高速USB的快乐。在有线网络的环境下UDP丢包的情况其实挺低的,大家可以看演示视频,可以发现另一台电脑界面中的摄像头其实也挺流畅的。沁恒不愧是USB起家的,USB通信做的杠杠的!
演示视频:https://www.bilibili.com/video/BV1s54y1w7mf/
|