基于DWC2的USB驱动开发-高效灵活的IN端点发送实现

原创 嵌入式Lee 2024-01-26 12:30

前言

UVC,UAC等要求实时传输的设备中,使用的是ISOC同步传输。而UVC等应用一般发送数据都比较大,比如发送一帧未压缩图像,可能就有几MB, 对于这种大数据的发送我们可以充分利用DWC2Scatter/Gather DMA来实现。

我们的设计目标是考虑资源消耗和性能的平衡,灵活可配,资源不够时可以降低效率但是也要能工作,资源够时可以充分发挥其性能。

描述符

dwc2Scatter/Gather DMA模式是一种高效的操作方式,减轻了软件的负担,软件只需要设置好描述符,硬件DMA就会进行处理,我们这里即基于该模式来实现同步发送,其他传输方式也是类似。充分利用其特性实现灵活的发送处理,在资源充足要求高效时我们可以构造非常大的链表一次性发送非常大的数据,而资源紧张,性能要求没这么高时,可以构造小的链表,一次发送较少数据,发送完后继续发送剩余数据,直到发送完,后者会产生更多的中断。这样可以实现不同需求的灵活配置。

对于描述符我们参考《DesignWare Cores USB 2.0 Hi-Speed On-The Go (OTG) Programming Guide.pdf》或者前面的dwc2驱动系列文章。

对于同步传输IN的描述符设置如下,两个4字节的参数,第一个即Status,第2个即buffer指针。其中Buffer指针最好是4字节对齐(不对齐也是OK,但是猜测可能会影响Burst操作降低性能)Status则重点关注L SP的组合。BSTxSts硬件会更新状态,软件写BS指示状态,详见手册说明这里不再赘述。

IOC设置为1则本描述符处理完产生中断,一般设置链表的最后一个描述符处理完才产生中断,尽可能减少中断次数。

PID需要根据一个微帧发送几帧来设置(端点描述符中也要对应), DCTl.IgnrFrmNum=1模式Frame Num无需设置。

发送函数实现

架构如下

大数据可以分为多个链表发送,每个链表产生一次中断,中断之后构建下一个链表。

每个链表可以包含多个描述符,每个描述符可以发送1~3包数据。

可配参数:

1.包大小

2.一个描述符发送几包

3.一个链表包含几个描述符

即如下参数

static usbd_ep_info_st s_vs_in_ep={    .addr = 0x84,    .fifonum = 3,    .maxpacket = EP_SIZE,    .packets = EP_TRANS_NUM,    .type = USBD_EP_TYPE_ISO,    .dmabuffer = s_vs_in_ep_dma,    .dmanums = (sizeof(s_vs_in_ep_dma)/sizeof(s_vs_in_ep_dma[0]))/2, /* 一个描述符需要2个WORD所以需要/2 */    .buffer = 0,};

代码实现如下:

注意以下代码没有考虑一个描述符发送2或者3帧的情况,按需求添加描述符中PID配置即可。

/** * \fn  usbd_ep_write_0 * 端点发送数据 * \param[in] epnum 端点地址 * \param[in] buffer 发送缓冲区 * \param[in] size   发送长度 * \param[in] flag  1第一次发送 0后续发送 * \return 总是返回0*/static int usbd_ep_write_0(uint32_t epnum, void *buffer, uint32_t size, uint32_t flag){    uint32_t sendsize = 0;  /* 本次描述符链表发送的数据长度             */    usbd_ep_st* pep;        /* 端点指针                               */    uint32_t dmanums = 0;   /* 需要的描述符个数                        */    uint32_t last_len = 0;  /* 本描述符链表,最后一个描述符对应的发送长度 */    uint32_t sndbuffer = 0; /* 记录描述符对应的待发送数据缓存开始地址    */
    /* 获取端点信息 */    epnum &= USB_EPNO_MASK;    pep = &(s_usbd_dev.dep[EP_IN_OFS + epnum]);
    /* 如果是第一次启动传输则初始化变量,后续传输在此基础上继续 */    if(flag != 0)    {        pep->xfer_len = size;    /* 待发送的数据长度   */        pep->xfer_buff = buffer; /* 待发送数据缓存     */        pep->xfered_count = 0;   /* 已经传输的长度     */    }    else    {        /* 无需初始化变量,在原来基础上继续 */    }
    /* 计算本次链表待传输长度     * 一个链表有多个描述符,一个描述符可以传输pep->maxpacket * pep->packets     * 总共可以传输pep->maxpacket * pep->packets * pep->dma_nums     *      * 如果一次传输不完,则本次传输大小是pep->maxpacket * pep->packets * pep->dma_nums,     * 否则就是剩余待传输大小. 计算得到本次待传输大小sendsize.     *      * 如果一次传输不完则所有描述符都需要用上,即dmanums=pep->dma_nums     * 否则根据实际传输大小计算,注意要向上圆整      * dmanums = (size+pep->maxpacket*pep->packets-1)/(pep->maxpacket*pep->packets)     *      * 计算最后一个描述传输的大小last_len     * 如果一次传输不完则last_len是pep->maxpacket * pep->packets,     * 否则根据sendsize计算.     */    if(size <= pep->maxpacket * pep->packets * pep->dma_nums)    {        sendsize = size;   /* 一次可以发送完 */        dmanums = (size+pep->maxpacket*pep->packets-1)/(pep->maxpacket*pep->packets); /* 向上圆整 */
        last_len = sendsize % (pep->maxpacket * pep->packets);        if(last_len == 0)        {            /* 注意这里如果整除,则说明刚好是倍数,最后也是一个完整pep->maxpacket * pep->packets 大小 */            last_len = pep->maxpacket * pep->packets;          }    }    else    {        sendsize = pep->maxpacket * pep->packets * pep->dma_nums; /* 分多次发送完 */        dmanums = pep->dma_nums;
        last_len = pep->maxpacket * pep->packets;    }
    /* 更新本次传输长度,in中断中根据该值计算本次实际传输长度,以更新下一次需要传输时的缓存地址与剩余待传输长度 */    pep->xfer_count = sendsize;
    /* 填充描述符 */    //sndbuffer = (uint32_t)pep->xfer_buff;  /* 开始地址,填充完一个描述符后递增 */    sndbuffer = buffer; /* 用传进来的参数,pep->xfer_buff不变 */    for(uint32_t i=0; i    {        pep->dma_addr[2*i+1] = sndbuffer; /* 待发送地址 */        if(i==(dmanums-1))        {            /* 链表的最后一个描述符 设置IOC位 产生中断             * 最后一个描述符需要设置L位             * @todo 根据不同传输类型可能需要设置SP位              */            pep->dma_addr[2*i+0] = (uint32_t)(USBD_DESC_BIT_IOC | USBD_DESC_BIT_L | USBD_DESC_BIT_SP | last_len); /* 这是最后一笔 SP=1 */            /* 最后一次无需再计算sndbuffer */        }        else        {            pep->dma_addr[2*i+0] = (uint32_t)(0 | (pep->maxpacket * pep->packets)); /* 不是最后一笔 SP=0 */            sndbuffer += pep->maxpacket * pep->packets; /* 计算下一个描述符开始的地址 */        }    }         /* 记录本次待发送的大小 */    pep->xfer_count = sendsize;
    /* 设置DMA开始发送 注意EPENA_IN_MASK放在最后 */    REG_DIEP_DMA(epnum) = (uint32_t)(pep->dma_addr);    REG_DIEP_CTL(epnum) |= (EPENA_IN_MASK | CNAK_IN_MASK);    return 0;}

同时基于此实现两个接口

usbd_ep_write由用户调用启动传输,usbd_ep_write_continue在中断中调用继续发送。

区别是前者会初始化参数,后者无需。

int usbd_ep_write(uint32_t epnum, void *buffer, uint32_t size){    USBD_IN_LOG(("inep%02x start,buf:%x tosnd %d\n",epnum,buffer,size));    return usbd_ep_write_0(epnum,buffer,size, 1);}
/** * \fn  usbd_ep_write_continue * 中断中调用继续发送 * \param[in] epnum 端点地址 * \param[in] buffer 发送缓冲区 * \param[in] size   发送长度 * \return 参考usbd_ep_write_0返回地址*/static int usbd_ep_write_continue(uint32_t epnum,void *buffer, uint32_t size ){    USBD_IN_LOG(("inep%02x continue,buf:%x tosnd %d\n",epnum,buffer,size));    return usbd_ep_write_0(epnum,buffer,size, 0);}

IN中断处理

中断处理很简单,只需要判断发送完没有,没有发送完则计算当前发送到了的位置和剩余长度调用usbd_ep_write_continue继续发送,发送完则回调发送完接口

static void usbd_epn_in_intr(uint8_t epnum){    uint32_t intr;    uint8_t addr;    addr = epnum & USB_EPNO_MASK;    intr = REG_DIEP_INT(addr);
    if((intr & XFERCOMPL_IN_MASK) != 0)    {        usbd_ep_st* pep;        pep = &(s_usbd_dev.dep[addr + EP_IN_OFS]);        uint32_t send;        /* xfer_count本次待发送大小        * dma_addr是当前描述符处理完剩余未发送的长度        * 所以send本次实际发送的长度 = xfer_count待发送的长度 - dma_addr剩余未发送长度        * 这里理论上需要根据pep->xfer_count减去dma状态计算剩余未发送完的大小来确定已发哦是那个大小        * 为了简单假设全部发送完所以dma_addr中剩余未发送完为0        */        //send = pep->xfer_count - (pep->dma_addr[addr] & 0xffff); /* 本次实际发送的大小 */        send = pep->xfer_count;
        pep->xfer_len -= send; /* 计算剩余未发送大小, xfer_len不可能小于send */        pep->xfered_count += send;
        if(pep->xfer_len == 0)        {            /* 全部发送完 调用回调函数 */            USBD_IN_LOG(("in ep%d done\n",addr));            if(pep->event_cb !=0)            {                pep->event_cb(epnum,pep->xfer_buff,pep->xfered_count);            }        }        else        {            /* 继续发送 */            usbd_ep_write_continue(epnum,pep->xfer_buff+pep->xfered_count,pep->xfer_len);        }    }
    /*@todo 其他中断处理 */
    REG_DIEP_INT(addr) = intr; /* 写1清零标志 */}

测试

测试一次发送完,分配较大的描述符链表,足够一次发送完。

__attribute__((aligned(8))) uint32_t s_vs_in_ep_dma[((IMG_H_MAX*IMG_V_MAX*2*2)+(EP_SIZE*EP_TRANS_NUM))/(EP_SIZE*EP_TRANS_NUM*2)*2];//__attribute__((aligned(8))) uint32_t s_vs_in_ep_dma[8*2];  /* 8个描述符 */static usbd_ep_info_st s_vs_in_ep={    .addr = 0x84,    .fifonum = 3,    .maxpacket = EP_SIZE,    .packets = EP_TRANS_NUM,    .type = USBD_EP_TYPE_ISO,    .dmabuffer = s_vs_in_ep_dma,    .dmanums = (sizeof(s_vs_in_ep_dma)/sizeof(s_vs_in_ep_dma[0]))/2, /* 一个描述符需要2个WORD所以需要/2 */    .buffer = 0,};

对应打印如下

可以看到连续发送,没有间隔,效率高。

测试多次发送完,分配较小的描述符链表,一次不能发送完。

__attribute__((aligned(8))) uint32_t s_vs_in_ep_dma[8*2];  /* 8个描述符 */static usbd_ep_info_st s_vs_in_ep={    .addr = 0x84,    .fifonum = 3,    .maxpacket = EP_SIZE,    .packets = EP_TRANS_NUM,    .type = USBD_EP_TYPE_ISO,    .dmabuffer = s_vs_in_ep_dma,    .dmanums = (sizeof(s_vs_in_ep_dma)/sizeof(s_vs_in_ep_dma[0]))/2, /* 一个描述符需要2个WORD所以需要/2 */    .buffer = 0,};

可以看到如下3次发送完,一次发送8包,一个描述符发送一包,8个描述符。

由于中断处理,每次发送之间有间隔。效率比前者低。

总结

在嵌入式驱动开发中,性能和资源是矛盾体,但是驱动开发者需要兼顾彼此,提供给用户灵活的可选性,这也是嵌入式系统可定制的一个体现。也就是哪怕资源不够我们也应该要能工作只是效率低一点,资源够用我们则可以跑的嘎嘎飞快。这是嵌入式驱动开发设计的很重要的思想,好的驱动设计时刻需要考虑该条。


评论
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 65浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 125浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 141浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 103浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 61浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 167浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 38浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 77浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 113浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦