本帖最后由 赵玥玥 于 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/ |
赞
文章选题方向较好,文章结构合理,流程图清晰详细,实现效果也不错,整体实现较好。