qinyunti 发表于 2023-11-1 11:52

【ST多款开发板返场测评】STM32F767 Nucleo-144 实现基于USB-UVC的摄像头(显示)

<div class='showpostmsg'><h1>e219daa6f8eb914b85ae25465e184ecb<br />
&nbsp;</h1>

<h1><b>前言</b></h1>

<p >&nbsp; &nbsp; &nbsp; 前面我们实现了UVC设备的枚举与打开关闭。现在我们来添加数据流实现UVC的显示。</p>

<p >先来实现NV12格式,stm32f7还支持硬件jpeg编解码,后面有时间再实现jpeg格式。</p>

<p >&nbsp;</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 >&nbsp;</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 >&nbsp;</p>

<p >可以看到格式和大小都变过来了</p>

<p > &nbsp;</p>

<p >&nbsp;</p>

<ul>
        <li>使能sof中断</li>
</ul>

<p >usbd_conf.c的USBD_LL_Init</p>

<p >&nbsp;&nbsp;hpcd.Init.Sof_enable = 1;</p>

<p >因为需要在USBD_VIDEO_SOF回调中进行一次发送,触发发送完中断,然后在后面中断中进行图片数据发送,形成发送流。</p>

<p >&nbsp;&nbsp;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 &gt;= sizeof(color_table)/sizeof(color_table))

    {

        index = 0;

    }

    r = (color_table &gt;&gt; 16) &amp; 0xFF;

    g = (color_table &gt;&gt; 8) &amp; 0xFF;

    b = (color_table &gt;&gt; 0) &amp; 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&gt;235)

    {

        y=235;

    }

    if(y&lt;16)

    {

        y=16;

    }

    if(u&gt;239)

    {

        u=239;

    }

    if(u&lt;16)

    {

        u=16;

    }

    if(v&gt;239)

    {

        v=239;

    }

    if(v&lt;16)

    {

        v=16;

    }

    for(int i=0;i&lt;H_SIZE*V_SIZE;i++)

    {

        *p++ = y;

    }

    for(int i=0;i&lt;H_SIZE*V_SIZE/4;i++)

    {

        *p++ = u;

        *p++ = v;

    }

}</code></pre>

<p >&nbsp;</p>

<h1 ><b>解决BUG</b></h1>

<ul>
        <li>usbd_video_if_template.c中</li>
</ul>

<p >&nbsp;&nbsp;static uint8_t packet_index = 0U;&nbsp;索引值在UVC_PACKET_SIZE比较小,tImagesSizes比较大时会溢出,该为static uint32_t packet_index = 0U;</p>

<p >&nbsp;</p>

<ul>
        <li>VIDEO_Itf_Data中是在中断中处理的,此时进行Delay会阻塞中断处理,并且也必须systick优先级大于USB中断优先级才能systick中断嵌套tick继续走行。我们直接注释掉即可。这里只显示一个图片,不控制图片间的间隔。</li>
</ul>

<p >//USBD_Delay(USBD_VIDEO_IMAGE_LAPS);</p>

<p >&nbsp;</p>

<p >&nbsp;</p>

<h1 ><b>优化-解决更新图片导致花屏的问题</b></h1>

<p >修改为发送完一帧才能更新framebuffer,否则正在发送时修改会花屏。 &nbsp;</p>

<p >USBD_VIDEO_SOF中注释掉发送,在主循环中触发发送流</p>

<p></p>

<p >&nbsp;</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-&gt;pClassDataCmsit;
uint8_t payload = {0x02U, 0x00U};

/* Check if the Streaming has already been started by SetInterface AltSetting 1 */
if (hVIDEO-&gt;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-&gt;uvc_state = UVC_PLAY_STATUS_STREAMING;
                hVIDEO-&gt;sending = 0;
}

/* Exit with no error code */
return (uint8_t)USBD_OK;
}</code></pre>

<p >主函数中触发发送</p>

<p > &nbsp;</p>

<p >&nbsp;&nbsp;</p>

<pre>
<code class="language-cpp"> while (1)
{
                uart_debug_send(16);
                shell_exec_shellcmd();
                if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))-&gt;uvc_state) == UVC_PLAY_STATUS_STREAMING)
    {
                        if((((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))-&gt;sending) == 0)
      {
                               ((USBD_VIDEO_HandleTypeDef *)(USBD_Device.pClassDataCmsit))-&gt;sending = 1;
                                        framebuffer_update();
                                  (void)USBD_LL_Transmit(&amp;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-&gt;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-&gt;uvc_state == UVC_PLAY_STATUS_STREAMING)
{
    /* Get the current packet buffer, index and size from the application layer */
    ((USBD_VIDEO_ItfTypeDef *)pdev-&gt;pUserData)-&gt;Data(&amp;Pcktdata, &amp;PcktSze, &amp;PcktIdx);

    /* Check if end of current image has been reached */
    if (PcktSze &gt; 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 &gt; 0U)
      {
      packet[((DataOffset + 0U) * i)] = payload_header;
      packet[((DataOffset + 0U) * i) + 1U] = payload_header;

      if (RemainData &gt; pdev-&gt;ep_in.maxpacket)
      {
          DataOffset = pdev-&gt;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 *)&amp;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 > &nbsp;</p>

<p >&nbsp;</p>

<h1 ><b>测试</b></h1>

<p >详见视频</p>

<p >&nbsp;</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]
查看完整版本: 【ST多款开发板返场测评】STM32F767 Nucleo-144 实现基于USB-UVC的摄像头(显示)