【ST多款开发板返场测评】STM32F767 Nucleo-144 实现基于USB-UVC的摄像头(显示)
<div class='showpostmsg'><h1>e219daa6f8eb914b85ae25465e184ecb<br /> </h1>
<h1><b>前言</b></h1>
<p > 前面我们实现了UVC设备的枚举与打开关闭。现在我们来添加数据流实现UVC的显示。</p>
<p >先来实现NV12格式,stm32f7还支持硬件jpeg编解码,后面有时间再实现jpeg格式。</p>
<p > </p>
<h1 ><b>过程</b></h1>
<ul>
<li>修改配置描述符支持NV12格式</li>
</ul>
<p >usbd_video.h开头中定义#define USBD_UVC_FORMAT_UNCOMPRESSED</p>
<ul>
<li>usbd_video.h中</li>
</ul>
<p >默认GUID为NV12</p>
<pre>
<code class="language-cpp">#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 */</code></pre>
<p > </p>
<ul>
<li>修改图片大小</li>
</ul>
<pre>
<code class="language-cpp">/* 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</code></pre>
<p > </p>
<p >可以看到格式和大小都变过来了</p>
<p > </p>
<p > </p>
<ul>
<li>使能sof中断</li>
</ul>
<p >usbd_conf.c的USBD_LL_Init</p>
<p > hpcd.Init.Sof_enable = 1;</p>
<p >因为需要在USBD_VIDEO_SOF回调中进行一次发送,触发发送完中断,然后在后面中断中进行图片数据发送,形成发送流。</p>
<p > hpcd.Init.dma_enable = 1;</p>
<ul>
<li>添加测试数据源头</li>
</ul>
<p >生成RGB格式的NV12数据</p>
<pre>
<code class="language-cpp">uint8_t s_framebuffer;
//uint8_t s_framebuffer_out;
uint32_t color_table[] =
{
0xFF0000, /* */
0x00FF00, /* */
0x0000FF, /* */
};
/*
Y = 0.298R + 0.612G + 0.117B;
U = -0.168R - 0.330G + 0.498B + 128;
V = 0.449R - 0.435G - 0.083B + 128;
*/
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))
{
index = 0;
}
r = (color_table >> 16) & 0xFF;
g = (color_table >> 8) & 0xFF;
b = (color_table >> 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;
}
}</code></pre>
<p > </p>
<h1 ><b>解决BUG</b></h1>
<ul>
<li>usbd_video_if_template.c中</li>
</ul>
<p > static uint8_t packet_index = 0U; 索引值在UVC_PACKET_SIZE比较小,tImagesSizes比较大时会溢出,该为static uint32_t packet_index = 0U;</p>
<p > </p>
<ul>
<li>VIDEO_Itf_Data中是在中断中处理的,此时进行Delay会阻塞中断处理,并且也必须systick优先级大于USB中断优先级才能systick中断嵌套tick继续走行。我们直接注释掉即可。这里只显示一个图片,不控制图片间的间隔。</li>
</ul>
<p >//USBD_Delay(USBD_VIDEO_IMAGE_LAPS);</p>
<p > </p>
<p > </p>
<h1 ><b>优化-解决更新图片导致花屏的问题</b></h1>
<p >修改为发送完一帧才能更新framebuffer,否则正在发送时修改会花屏。 </p>
<p >USBD_VIDEO_SOF中注释掉发送,在主循环中触发发送流</p>
<p></p>
<p > </p>
<pre>
<code class="language-cpp">/**
* @brief USBD_VIDEO_SOF
* handle SOF event
* @parampdev: device instance
* @retval status
*/
static uint8_tUSBD_VIDEO_SOF(USBD_HandleTypeDef *pdev)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit;
uint8_t payload = {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;
}</code></pre>
<p >主函数中触发发送</p>
<p > </p>
<p > </p>
<pre>
<code class="language-cpp"> while (1)
{
uart_debug_send(16);
shell_exec_shellcmd();
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))->uvc_state) == UVC_PLAY_STATUS_STREAMING)
{
if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))->sending) == 0)
{
((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))->sending = 1;
framebuffer_update();
(void)USBD_LL_Transmit(&USBD_Device, 0x81, (uint8_t *)payload, 2U);
}
}</code></pre>
<p >发送完一帧停止等待主函数中重新启动</p>
<p >发送完一帧不再发送</p>
<pre>
<code class="language-cpp">static uint8_tUSBD_VIDEO_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_VIDEO_HandleTypeDef *hVIDEO = (USBD_VIDEO_HandleTypeDef *) pdev->pClassDataCmsit;
static uint8_tpacket = {0x00U};
static uint8_t *Pcktdata = packet;
static uint16_t PcktIdx = 0U;
static uint16_t PcktSze = UVC_PACKET_SIZE;
static uint8_tpayload_header = {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)->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 ^= 0x01U;
}
RemainData = PcktSze;
/* fill the Transmit buffer */
while (RemainData > 0U)
{
packet[((DataOffset + 0U) * i)] = payload_header;
packet[((DataOffset + 0U) * i) + 1U] = payload_header;
if (RemainData > pdev->ep_in.maxpacket)
{
DataOffset = pdev->ep_in.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 = payload_header;
packet = payload_header;
do_debug(DEBUG_TAG_SETUP,DEBUG_LEVEL_INFO,"done\r\n");
}
}
/* Exit with no error code */
return (uint8_t) USBD_OK;
}</code></pre>
<p > </p>
<p > </p>
<h1 ><b>测试</b></h1>
<p >详见视频</p>
<p > </p>
<h1 ><b>总结</b></h1>
<p >以上实现了UVC摄像头显示, 就可以玩转GUI了,后面移植GUI比如LVGL等就可以进行有意思的开发了。后一篇先玩个有意思的,移植NES模拟器,玩转NES游戏。</p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页:
[1]