6a9a010dfb1e5fd88dc8b1336b5b648d
前言
前面我们实现了UVC设备的枚举与打开关闭。现在我们来添加数据流实现UVC的显示。
先来实现NV12格式,stm32f7还支持硬件jpeg编解码,后面有时间再实现jpeg格式。
过程
usbd_video.h开头中定义#define USBD_UVC_FORMAT_UNCOMPRESSED
默认GUID为NV12
#define UVC_GUID_YUY2 0x32595559U
#define UVC_GUID_NV12 0x3231564EU
#ifndef UVC_UNCOMPRESSED_GUID
#define UVC_UNCOMPRESSED_GUID UVC_GUID_NV12
#endif /* UVC_UNCOMPRESSED_GUID */
/* These defines shall be updated in the usbd_conf.h file */
#ifndef UVC_WIDTH
#define UVC_WIDTH 240U
#endif /* UVC_WIDTH */
#ifndef UVC_HEIGHT
#define UVC_HEIGHT 240U
可以看到格式和大小都变过来了
usbd_conf.c的USBD_LL_Init
hpcd.Init.Sof_enable = 1;
因为需要在USBD_VIDEO_SOF回调中进行一次发送,触发发送完中断,然后在后面中断中进行图片数据发送,形成发送流。
hpcd.Init.dma_enable = 1;
生成RGB格式的NV12数据
uint8_t s_framebuffer[H_SIZE*V_SIZE*3/2];
//uint8_t s_framebuffer_out[H_SIZE*V_SIZE];
uint32_t color_table[] =
{
0xFF0000, /* */
0x00FF00, /* */
0x0000FF, /* */
};
/*
Y = 0.298R + 0.612G + 0.117B; [13,235]
U = -0.168R - 0.330G + 0.498B + 128; [16,239]
V = 0.449R - 0.435G - 0.083B + 128; [16,239]
*/
static void update_buffer(void)
{
uint8_t* p = (uint8_t*)s_framebuffer;
static unsigned int index = 0;
int y = 0;
int u = 0;
int v = 0;
uint8_t r = 0;
uint8_t g = 0;
uint8_t b = 0;
if(index >= sizeof(color_table)/sizeof(color_table[0]))
{
index = 0;
}
r = (color_table[index] >> 16) & 0xFF;
g = (color_table[index] >> 8) & 0xFF;
b = (color_table[index] >> 0) & 0xFF;
index++;
y = (0.298*r + 0.612*g + 0.117*b);
u = (-0.168*r - 0.330*g + 0.498*b + 128);
v = (0.449*r - 0.435*g - 0.083*b + 128);
if(y>235)
{
y=235;
}
if(y<16)
{
y=16;
}
if(u>239)
{
u=239;
}
if(u<16)
{
u=16;
}
if(v>239)
{
v=239;
}
if(v<16)
{
v=16;
}
for(int i=0;i<H_SIZE*V_SIZE;i++)
{
*p++ = y;
}
for(int i=0;i<H_SIZE*V_SIZE/4;i++)
{
*p++ = u;
*p++ = v;
}
}
解决BUG
- usbd_video_if_template.c中
static uint8_t packet_index = 0U; 索引值在UVC_PACKET_SIZE比较小,tImagesSizes[x]比较大时会溢出,该为static uint32_t packet_index = 0U;
- VIDEO_Itf_Data中是在中断中处理的,此时进行Delay会阻塞中断处理,并且也必须systick优先级大于USB中断优先级才能systick中断嵌套tick继续走行。我们直接注释掉即可。这里只显示一个图片,不控制图片间的间隔。
//USBD_Delay(USBD_VIDEO_IMAGE_LAPS);
优化-解决更新图片导致花屏的问题
修改为发送完一帧才能更新framebuffer,否则正在发送时修改会花屏。
USBD_VIDEO_SOF中注释掉发送,在主循环中触发发送流
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] USBD_VIDEO_SOF
* handle SOF event
* @param pdev: device instance
* @retval status
*/
static uint8_t USBD_VIDEO_SOF(USBD_HandleTypeDef *pdev)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit[pdev->classId];
uint8_t payload[2] = {0x02U, 0x00U};
/* Check if the Streaming has already been started by SetInterface AltSetting 1 */
if (hVIDEO->uvc_state == UVC_PLAY_STATUS_READY)
{
/* Transmit the first packet indicating that Streaming is starting */
//(void)USBD_LL_Transmit(pdev, VIDEOinEpAdd, (uint8_t *)payload, 2U);
/* Enable Streaming state */
hVIDEO->uvc_state = UVC_PLAY_STATUS_STREAMING;
hVIDEO->sending = 0;
}
/* Exit with no error code */
return (uint8_t)USBD_OK;
}
主函数中触发发送
while (1)
{
uart_debug_send(16);
shell_exec_shellcmd();
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->uvc_state) == UVC_PLAY_STATUS_STREAMING)
{
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->sending) == 0)
{
((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit[0]))->sending = 1;
framebuffer_update();
(void)USBD_LL_Transmit(&USBD_Device, 0x81, (uint8_t *)payload, 2U);
}
}
发送完一帧停止等待主函数中重新启动
发送完一帧不再发送
static uint8_t USBD_VIDEO_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit[pdev->classId];
static uint8_t packet[UVC_PACKET_SIZE + (UVC_HEADER_PACKET_CNT * 2U)] = {0x00U};
static uint8_t *Pcktdata = packet;
static uint16_t PcktIdx = 0U;
static uint16_t PcktSze = UVC_PACKET_SIZE;
static uint8_t payload_header[2] = {0x02U, 0x00U};
uint8_t i = 0U;
uint32_t RemainData, DataOffset = 0U;
/* Check if the Streaming has already been started */
if (hVIDEO->uvc_state == UVC_PLAY_STATUS_STREAMING)
{
/* Get the current packet buffer, index and size from the application layer */
((USBD_VIDEO_ItfTypeDef *)pdev->pUserData[pdev->classId])->Data(&Pcktdata, &PcktSze, &PcktIdx);
/* Check if end of current image has been reached */
if (PcktSze > 2U)
{
/* Check if this is the first packet in current image */
if (PcktIdx == 0U)
{
/* Set the packet start index */
payload_header[1] ^= 0x01U;
}
RemainData = PcktSze;
/* fill the Transmit buffer */
while (RemainData > 0U)
{
packet[((DataOffset + 0U) * i)] = payload_header[0];
packet[((DataOffset + 0U) * i) + 1U] = payload_header[1];
if (RemainData > pdev->ep_in[VIDEOinEpAdd & 0xFU].maxpacket)
{
DataOffset = pdev->ep_in[VIDEOinEpAdd & 0xFU].maxpacket;
(void)USBD_memcpy((packet + ((DataOffset + 0U) * i) + 2U),
Pcktdata + ((DataOffset - 2U) * i), (DataOffset - 2U));
RemainData -= DataOffset;
i++;
}
else
{
(void)USBD_memcpy((packet + ((DataOffset + 0U) * i) + 2U),
Pcktdata + ((DataOffset - 2U) * i), (RemainData - 2U));
RemainData = 0U;
}
}
/* Transmit the packet on Endpoint */
(void)USBD_LL_Transmit(pdev, (uint8_t)(epnum | 0x80U),
(uint8_t *)&packet, (uint32_t)PcktSze);
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"%d %d\r\n",PcktSze,PcktIdx);
}
else
{
/* Add the packet header */
packet[0] = payload_header[0];
packet[1] = payload_header[1];
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"done\r\n");
}
}
/* Exit with no error code */
return (uint8_t) USBD_OK;
}
测试
详见视频
总结
以上实现了UVC摄像头显示, 就可以玩转GUI了,后面移植GUI比如LVGL等就可以进行有意思的开发了。后一篇先玩个有意思的,移植NES模拟器,玩转NES游戏。