USB学习【13】STM32+USB接收数据过程详解

目录

  • 1.官方的描述
  • 2.HAL的流程
    • 把接收到的数据从PMA拷贝到用户自己定义的空间中
  • 3.处理接收到的数据
  • 4.最后再次开启准备接收工作

1.官方的描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.HAL的流程

以上的官方说法我们暂时按下不表。
如果接收到数据,会激活中断进入到USB_LP_CAN1_RX0_IRQHandler()->HAL_PCD_IRQHandler()
因为所有的中断事件共用一个中断函数,所以第一步要进行中断类型检测,中断的信息都写在了ISTR寄存器中,判断代码如下:

  if (__HAL_PCD_GET_FLAG(hpcd, USB_ISTR_CTR)){/* servicing of the endpoint correct transfer interrupt *//* clear of the CTR flag into the sub */(void)PCD_EP_ISR_Handler(hpcd);}

下一步自然而然的就是执行这个函数:

 (void)PCD_EP_ISR_Handler(hpcd);

既然进入中断了,我们还是要验证一下ISTR的正确传输中断有没有置位

 while ((hpcd->Instance->ISTR & USB_ISTR_CTR) != 0U)

在这里插入图片描述
技术手册提示:可以通过DIR和EPID来判断是哪个端点,接下来的代码也是符合这个步骤的,下面的步骤是判断了哪个端点,暂时没有判断方向,一些逻辑执行完毕后,就会判断方向了,详细看后面的代码。

wIstr = hpcd->Instance->ISTR;/* extract highest priority endpoint number */
epindex = (uint8_t)(wIstr & USB_ISTR_EP_ID);if (epindex == 0U)
{
}else if (epindex == 1U){//我们真正使用的端点}

下一步拿到端点寄存器的数据,看看CTR_RX有没有接收到数据
如果接收到第一步就是把标志位清除掉

下面的图片,发现正确接收标志位置1,CTRM置位,就会产生中断,CTRM位说,如果中断状态寄存器置1就会产生中断,这里面就说明任何端点正确接收置1后,中断状态寄存器也置1,这是一个联动反应

  wEPVal = PCD_GET_ENDPOINT(hpcd->Instance, epindex);if ((wEPVal & USB_EP_CTR_RX) != 0U){/* clear int flag */PCD_CLEAR_RX_EP_CTR(hpcd->Instance, epindex);

在这里插入图片描述
紧接着,HAL库把OUT_ep结构体拿过来,这里面记录了所有规定的端点信息,在初始化阶段把这部分信息预先储存到结构体里面的。

ep = &hpcd->OUT_ep[epindex];/* OUT Single Buffering */if (ep->doublebuffer == 0U){count = (uint16_t)PCD_GET_EP_RX_CNT(hpcd->Instance, ep->num);if (count != 0U){USB_ReadPMA(hpcd->Instance, ep->xfer_buff, ep->pmaadress, count);}}

其中 count = (uint16_t)PCD_GET_EP_RX_CNT(hpcd->Instance, ep->num);这条语句,

//先拿到对应端点缓冲表USB_COUNTn_RX的数据
#define PCD_EP_RX_CNT(USBx, bEpNum) ((uint16_t *)((((uint32_t)(USBx)->BTABLE\+ ((uint32_t)(bEpNum) * 8U) + 6U) * PMA_ACCESS) + ((uint32_t)(USBx) + 0x400U)))
//把USB_COUNTn_RX中的高于9位的信息删除掉,只保留COUNTn_RX(实际接收到的数据)
#define PCD_GET_EP_RX_CNT(USBx, bEpNum)        ((uint32_t)(*PCD_EP_RX_CNT((USBx), (bEpNum))) & 0x3ffU)

在这里插入图片描述
到了对关键的一步,就数据从缓冲区里面,拷贝到用户的自定义区

if (count != 0U){USB_ReadPMA(hpcd->Instance, ep->xfer_buff, ep->pmaadress, count);}

注意一下参数:从上面看,最后接收到的数据,其实是放在对应端点结构体的xfter_buff里面的,现在就是只要拿到对应端点结构体的句柄,就能找到接收到的数据
(这个在接收数据完成之后定义的连接,需要结合后面代码看)
在这里插入图片描述

把接收到的数据从PMA拷贝到用户自己定义的空间中

/*** @brief Copy data from packet memory area (PMA) to user memory buffer* @param   USBx USB peripheral instance register address.* @param   pbUsrBuf pointer to user memory area.* @param   wPMABufAddr address into PMA.* @param   wNBytes no. of bytes to be copied.* @retval None*/
/*** @brief Copy data from packet memory area (PMA) to user memory buffer* @param   USBx USB peripheral instance register address.* @param   pbUsrBuf pointer to user memory area.* @param   wPMABufAddr address into PMA.* @param   wNBytes no. of bytes to be copied.* @retval None*/
void USB_ReadPMA(USB_TypeDef *USBx, uint8_t *pbUsrBuf, uint16_t wPMABufAddr, uint16_t wNBytes)
{// 计算需要处理的16位数据单元的数量(每个单元包含2个字节),比如接收了64字节,>>1,变成了32字节uint32_t n = (uint32_t)wNBytes >> 1;// 计算USB外设的基地址uint32_t BaseAddr = (uint32_t)USBx;// 用于循环计数和临时存储数据的变量uint32_t i, temp;// 定义一个指向PMA中16位数据的指针__IO uint16_t *pdwVal;// 定义一个指向用户缓冲区的指针uint8_t *pBuf = pbUsrBuf;// 计算PMA中目标数据的起始地址pdwVal = (__IO uint16_t *)(BaseAddr + 0x400U + ((uint32_t)wPMABufAddr * PMA_ACCESS));// 循环处理每个16位数据单元for (i = n; i != 0U; i--){// 从PMA中读取一个16位数据temp = *(__IO uint16_t *)pdwVal;pdwVal++;// 将16位数据的低8位存储到用户缓冲区*pBuf = (uint8_t)((temp >> 0) & 0xFFU);pBuf++;// 将16位数据的高8位存储到用户缓冲区*pBuf = (uint8_t)((temp >> 8) & 0xFFU);pBuf++;// 如果PMA访问的步长大于1,则跳过一个额外的单元(可能是因为硬件设计)
#if PMA_ACCESS > 1UpdwVal++;
#endif}// 如果剩余的字节数不是2的倍数(即还有1个字节未处理)if ((wNBytes % 2U) != 0U){// 从PMA中读取最后一个字节temp = *pdwVal;*pBuf = (uint8_t)((temp >> 0) & 0xFFU);}
}
#endif /* defined (USB) */

关于接收完成标志
可以看到这里有一个判断条件if ((ep->xfer_len == 0U) || (count <= ep->maxpacket)),其中ep->xfer_len == 0U)是给控制传输用的,他需要严格的控制包数,count <= ep->maxpacket是给中断传输用的,只要不大于最大包,就是传输完成(短包)

   /* multi-packet on the NON control OUT endpoint */ep->xfer_count += count;ep->xfer_buff += count;if ((ep->xfer_len == 0U) || (count <= ep->maxpacket)){/* RX COMPLETE */

3.处理接收到的数据

在这里插入图片描述

在这里插入图片描述

	USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev,uint8_t epnum, uint8_t *pdata)
{USBD_EndpointTypeDef *pep;if (epnum == 0U){//一些实现代码}else if ((pdev->pClass->DataOut != NULL) &&(pdev->dev_state == USBD_STATE_CONFIGURED)){pdev->pClass->DataOut(pdev, epnum);}else{/* should never be in this condition */return USBD_FAIL;}return USBD_OK;
}
USBD_ClassTypeDef  USBD_CUSTOM_HID =
{USBD_CUSTOM_HID_Init,USBD_CUSTOM_HID_DeInit,USBD_CUSTOM_HID_Setup,NULL, /*EP0_TxSent*/USBD_CUSTOM_HID_EP0_RxReady, /*EP0_RxReady*/ /* STATUS STAGE IN */USBD_CUSTOM_HID_DataIn, /*DataIn*/USBD_CUSTOM_HID_DataOut,NULL, /*SOF */NULL,NULL,USBD_CUSTOM_HID_GetHSCfgDesc,USBD_CUSTOM_HID_GetFSCfgDesc,USBD_CUSTOM_HID_GetOtherSpeedCfgDesc,USBD_CUSTOM_HID_GetDeviceQualifierDesc,
};

阅读上面代码,发现最终执行了pdev->pClass->DataOut(pdev, epnum);这个函数指针。
下图是在初始化阶段就绑定好的函数指针,可以找到真正执行的函数名字是
USBD_CUSTOM_HID_DataOut->

/**
/*** @brief  USBD_CUSTOM_HID_DataOut*         handle data OUT Stage* @param  pdev: device instance* @param  epnum: endpoint index* @retval status*/
static uint8_t  USBD_CUSTOM_HID_DataOut(USBD_HandleTypeDef *pdev,uint8_t epnum)
{USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(epnum,hhid->Report_buf[0],hhid->Report_buf[1]);USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);return USBD_OK;
}

下一步执行这个函数指针,实际是CUSTOM_HID_OutEvent_FS(uint8_t epnum, uint8_t event_idx, uint8_t state)这个函数

/*** @brief  Manage the CUSTOM HID class events* @param  event_idx: Event index* @param  state: Event state* @retval USBD_OK if all operations are OK else USBD_FAIL*/
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t epnum, uint8_t event_idx, uint8_t state)
{/* USER CODE BEGIN 6 */USBD_CUSTOM_HID_HandleTypeDef  *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;for( uint8_t i  = 0;i <USBD_CUSTOMHID_OUTREPORT_BUF_SIZE;i++){USB_Recive_Buffer[i] = hhid->Report_buf[i];}HID_RxCpltCallback((uint8_t)epnum,&USB_Recive_Buffer);return (USBD_OK);/* USER CODE END 6 */
}

这个函数又把把数据从(USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData.Report_buf,转移到了我们自定义的区域,这个Report区域和对应端点的区域是相等的,在下面的再次准备接收阶段定义好了
在这里插入图片描述

并且调用了这个函数来解析数据HID_RxCpltCallback((uint8_t)epnum,&USB_Recive_Buffer);

4.最后再次开启准备接收工作

在这里插入图片描述
执行了这个函数

  USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
{PCD_EPTypeDef *ep;ep = &hpcd->OUT_ep[ep_addr & EP_ADDR_MSK];/*setup and start the Xfer */ep->xfer_buff = pBuf;ep->xfer_len = len;ep->xfer_count = 0U;ep->is_in = 0U;ep->num = ep_addr & EP_ADDR_MSK;if ((ep_addr & EP_ADDR_MSK) == 0U){(void)USB_EP0StartXfer(hpcd->Instance, ep);}else{(void)USB_EPStartXfer(hpcd->Instance, ep);}return HAL_OK;
}

上面函数,把接收端口的一些存数据的地址,接收长度,计数等等信息复位

AL_StatusTypeDef USB_EPStartXfer(USB_TypeDef *USBx, USB_EPTypeDef *ep)
{uint32_t len;uint16_t pmabuffer;uint16_t wEPVal;/* IN endpoint */if (ep->doublebuffer == 0U){/* Multi packet transfer */if (ep->xfer_len > ep->maxpacket){len = ep->maxpacket;ep->xfer_len -= len;}else{len = ep->xfer_len;ep->xfer_len = 0U;}/* configure and validate Rx endpoint */PCD_SET_EP_RX_CNT(USBx, ep->num, len);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/82128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

上海内推 | 上海算法创新研究院-上海交大联合招收空间智能/具身智能算法实习生

最近这一两周不少公司已开启春招和实习招聘。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑&am…

C语言速成12之指针:程序如何在内存迷宫里找宝藏?

程序员Feri一名12年的程序员,做过开发带过团队创过业,擅长Java、鸿蒙、嵌入式、人工智能等开发,专注于程序员成长的那点儿事,希望在成长的路上有你相伴&#xff01;君志所向,一往无前&#xff01; 0. 前言&#xff1a;程序如何在内存迷宫里找宝藏&#xff1f; 想象内存是一个巨…

部署n8n

https://github.com/n8n-io/n8n docker volume create n8n_data docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n Discover 2192 Automation Workflows from the n8ns Community

ABP VNext + Orleans:Actor 模型下的分布式状态管理最佳实践

ABP VNext Orleans&#xff1a;Actor 模型下的分布式状态管理最佳实践 &#x1f680; &#x1f4da; 目录 ABP VNext Orleans&#xff1a;Actor 模型下的分布式状态管理最佳实践 &#x1f680;一、引言&#xff1a;分布式系统的状态挑战 &#x1f4a1;二、架构图与技术栈 &am…

构建安全AI风险识别大模型:CoT、训练集与Agent vs. Fine-Tuning对比

构建安全AI风险识别大模型:CoT、训练集与Agent vs. Fine-Tuning对比 安全AI风险识别大模型旨在通过自然语言处理(NLP)技术,检测和分析潜在的安全威胁,如数据泄露、合规违规或恶意行为。本文从Chain-of-Thought (CoT)设计、训练集构建、以及Agent-based方法与**AI直接调优…

Baklib内容中台的主要构成是什么?

Baklib内容中台核心架构 Baklib作为一站式知识管理平台的核心载体&#xff0c;其架构设计围绕智能搜索引擎优化技术与多终端适配响应系统展开。通过模块化内容组件的灵活配置&#xff0c;企业可快速搭建知识库、FAQ页面及帮助中心等标准化场景&#xff0c;同时借助可视化数据看…

Ubuntu Desktop 24.04 常用软件安装步骤

文章目录 Ubuntu Desktop 24.04 常用软件安装步骤Snipaste F1快捷截图&#xff08;超方便 | 我6台电脑每台都用&#xff09;搜狗输入法快速浏览工具 | 空格键快速预览文件壁纸工具 | varietySSH 工具 | Termius 终端分屏工具 | TmuxCaffeine | 避免息屏小工具 一些设置将启动台…

详细使用@rollup/plugin-inject的方式

rollup/plugin-inject 是一个 Rollup 插件&#xff0c;它允许你在构建时自动注入模块中的变量引用&#xff0c;避免手动在每个文件中 import。Vite 使用的是 Rollup 构建底层&#xff0c;因此该插件在 Vite 项目中也适用。 一、使用场景 比如你希望在代码中不手动写 import { …

Day 0017:Web漏洞扫描(OpenVAS)解析

一、NVT脚本解析&#xff1a;漏洞检测的“DNA” 1. NVT脚本结构 每个NVT脚本都是一个Lua脚本&#xff0c;包含以下核心模块&#xff1a; lua -- 示例&#xff1a;检测Apache HTTPd 2.4.49路径穿越漏洞&#xff08;CVE-2021-41773&#xff09; script_id "1.3.6.1.4.1.…

【HarmonyOS Next之旅】DevEco Studio使用指南(二十六) -> 创建端云一体化开发工程

目录 1 -> 创建HarmonyOS应用工程 1.1 -> 新建工程 1.1.1 -> 前提条件 1.1.2 -> 选择模板 1.1.3 -> 配置工程信息 1.1.4 -> 关联云开发资源 1.2 -> 工程初始化配置 1.2.1 -> 自动开通云开发服务 1.3 -> 端云一体化开发工程目录结构 1.3.1…

Python 包管理工具 uv

Python 包管理工具 uv 是由 Astral 团队&#xff08;知名工具 Ruff 的开发者&#xff09;基于 Rust 开发的新一代工具&#xff0c;旨在通过高性能和一体化设计革新 Python 生态的依赖管理体验。以下是其核心特性、优势及使用指南的全面解析&#xff1a; 一、uv 的核心优势 极致…

何谓第二大脑?读书笔记

2025/05/11 发表想法 每个人都是矛盾结合体&#xff0c;既想学到新知识、新的能力&#xff0c;又想没办法专注的学习&#xff0c;既无法专注有渴望学习新技能&#xff0c;逐渐会产生焦虑、失眠等负面症状&#xff0c;这就是现实社会现照&#xff0c;那怎么办&#xff1f;我们能…

动态防御体系实战:AI如何重构DDoS攻防逻辑

1. 传统高防IP的静态瓶颈 传统高防IP依赖预定义规则库&#xff0c;面对SYN Flood、CC攻击等常见威胁时&#xff0c;常因规则更新滞后导致误封合法流量。例如&#xff0c;某电商平台遭遇HTTP慢速攻击时&#xff0c;静态阈值过滤无法区分正常用户与攻击者&#xff0c;导致订单接…

为什么在设置 model.eval() 之后,pytorch模型的性能会很差?为什么 dropout 影响性能?| 深度学习

在深度学习的世界里&#xff0c;有一个看似简单却让无数开发者困惑的现象&#xff1a; “为什么在训练时模型表现良好&#xff0c;但设置 model.eval() 后&#xff0c;模型的性能却显著下降&#xff1f;” 这是一个让人抓耳挠腮的问题&#xff0c;几乎每一个使用 PyTorch 的研究…

[爬虫知识] http协议

相关爬虫专栏&#xff1a;JS逆向爬虫实战 爬虫知识点合集 爬虫实战案例 引言&#xff1a;爬虫与HTTP的不解之缘 爬虫作用&#xff1a;模拟浏览器请求网页为何要懂HTTP&#xff1a;http是网络通信的基石&#xff0c;爬虫抓取数据就是通过HTTP协议进行的&#xff0c;了解http有…

《Spark/Flink/Doris离线实时数仓开发》目录

欢迎加入《Spark/Flink/Doris离线&实时数仓开发》付费专栏&#xff01;本专栏专为大数据工程师、数据分析师及准备大数据面试的求职者量身打造&#xff0c;聚焦Spark、Flink、Doris等核心技术&#xff0c;覆盖离线与实时数仓开发的全流程。无论你是想快速上手项目、提升技术…

事务基础概念

事务 事务是什么&#xff1f; 事务是一种机制&#xff0c;一个操作序列&#xff0c;包含了一组数据库操作命令&#xff0c;并且把所有命令作为一个整体一起向系统提交或者撤销操作请求&#xff0c;即统一这组命令要么一起执行&#xff0c;要么一起不执行 简短概况就是&#…

四、【API 开发篇 (上)】:使用 Django REST Framework 构建项目与模块 CRUD API

【API 开发篇 】&#xff1a;使用 Django REST Framework 构建项目与模块 CRUD API 前言为什么选择 Django REST Framework (DRF)&#xff1f;第一步&#xff1a;创建 Serializers (序列化器)第二步&#xff1a;创建 ViewSets (视图集)第三步&#xff1a;配置 URLs (路由)第四步…

【北京盈达科技】GEO优化中的多模态了解

多模态数据处理领域&#xff0c;“模态”指的是不同类型的数据形式&#xff0c;每种模态都具有独特的结构和信息表达方式。以下是12种可能的模态类型&#xff0c;这些模态在实际应用中可以根据具体场景进行组合和处理&#xff1a; 1. 文本模态 描述&#xff1a;以文字形式存在…

推进可解释人工智能迈向类人智能讨论总结分享

目录 一、探索“可解释人工智能”&#xff1a;AI如何从“黑箱”走向“透明大师” 二、走进可解释人工智能&#xff1a;让AI的决策变得透明 &#xff08;一&#xff09;几种常见的特征导向方法 &#xff08;二&#xff09;像素级方法 1. 层次相关传播&#xff08;LRP&#…