.Net Micro Framework - USB Mass Storage功能实现

简介: 由于.Net Micro Framework的USB驱动架构中,没有为Mass Storage功能提供原生支持,所以除了要编写Mass Storage主体代码外,还需要在原有的USB驱动中添加部分枚举代码
  1. 说明
    由于.Net Micro Framework的USB驱动架构中,没有为Mass Storage功能提供原生支持,所以除了要编写Mass Storage主体代码外,还需要在原有的USB驱动中添加部分枚举代码。其实从结构上来说,该部分代码应该添加在PAL层,不过考虑到这层代码为.Net Micro Framework Poring Kit Rtm 3.0标准代码,所以把这部分代码添加到我们自己编写的USB驱动之中去了。

此外,由于Mass Storage功能需要不断地检测和处理USB端口的数据,需要一个进程(或线程)去进行驱动。.Net Micro Framework在应用层仅支持一个进程(单个用户程序),所以必须在应用程序中专开一个线程去进行驱动,考虑到这样实现需要用户做额外的工作,最后摒弃了这一实现。最终的做法是,在Mass Storage驱动中添加了时钟中断处理函数,Mass Storage被初始化后,该时钟中断被激活,以一个用户可设定的间隔去监控和处理USB端口的数据。

在实现Mass Storage功能的代码中,并没有直接去读写相关Flash,而是借助PAL层的SectorCache模块间接访问Flash,这样有两个好处,一是读写有缓存,操作速度较快,二是程序比较通用,代码在不用修改的情况下可以访问不同的Flash、SD卡等存储模块。

本Mass Storage驱动仅实现了一个功能子集,仅支持单个存储模块,不支持从PC机进行格式化(可通过本地提供的接口进行格式化,文件系统目前必须是FAT32)。

  1. USB Config
    Mass Storage要求的USB Config和我们.Net Micro Framework的标准驱动不同,一是PID和VID不同,由于Mass Storage设备是免驱动安装的,这PID和VID应该没有多大用处,但是由于以前PC上已经安装了该PID和VID的.Net Micro Framework驱动,所以Mass Storage设备插入时,优先去找匹配PID和VID的设备驱动(这样也可以让一些厂家有机会安装自己专门的Mass Storage设备驱动),所以对我们已有该驱动的PC,肯定会有问题,所以我们仅需要调整一下VID的值即可。

其二我们USB接口描述类中,接口类必须为0x08(Mass storage class),子类为0x06(SCSI transparent command set)或0x04(UFI),接口协议为0x50(Bulk-only transport)。

image.png
 

表1 子类表

image.png
 

 

表2 接口协议

 

其它描述信息由于非关键,所以可修改,也可以不改。

 

  1. 调用接口
    Mass Storage驱动位于/DeviceCode/Drivers目录下, 属于通用驱动,其它设备都可以调用。包括如下三个文件:UsbMassStorage.h、UsbMassStorage.cpp、UsbMassStorage_config.cpp。

3.1   PAL层接口
3.1.1 UsbMassStorage_Start
Mass Storage功能初始化和启动函数。该函数执行时,先关闭原先的USB驱动接口,再用新USB Config初始化USB接口,最后启动一个定时中断函数。

函数原型:int UsbMassStorage_Start(UINT32 value);

参    数:value为时钟中断间隔,单位us。

返 回 值:0。

3.1.2 UsbMassStorage_Stop
Mass Storage功能停止函数。该函数先关闭时钟中断函数及自己定义的USB接口,最后恢复默认USB接口(即UsbDefaultConfiguration)。

函数原型:int UsbMassStorage_Stop();

返 回 值:0。

3.2   P/Invoke接口
考虑到用户也可以自由开启和关闭Mass Storage功能,所以为用户提供了P/Invoke接口。该接口文件位于:/Solutions/DM335/DeviceCode目录下,其实该程序通用,可以放在PAL层。包括两部分代码,一是托管代码,二是本地代码。

接口声明如下,函数功能同PAL层接口。

namespace YFSoft

{

    public static class MassStorage

    {

        [MethodImplAttribute(MethodImplOptions.InternalCall)]

        extern static public int Start(UInt32 value);

        [MethodImplAttribute(MethodImplOptions.InternalCall)]

        extern static public int Stop();

    }

}

注意:用户在调用MassStorage.Start()函数之前,先执行以下代码,以使磁盘缓存中的内容真实写入存储设备。

    VolumeInfo[] vis = VolumeInfo.GetVolumes();

    foreach (VolumeInfo vi in vis)

    {

        vi.FlushAll();

  }

4.USB枚举代码添加
Mass Storage类规范定义了两个请求:Get_Max_LUN和Mass Storage Reset,所有的Mass Storage类设备都必须支持这两个请求。

处理GET MAX LUN命令时,我们返回实际的逻辑单元(LUN:0~15)个数即可,由于我们的Mass Storage驱动仅支持一个存储设备,所以直接返回0即可。其实该命令也可以不应答,这时PC会重试三次,不过重试过程比较缓慢,用户体验体验很不好。

对于Mass Storage Reset命令,由于我们目前没有任何工作可做,所以直接返回空数据即可。

相关代码如下:

if(len>0)

   {

         USB_SETUP_PACKET* Setup= (USB_SETUP_PACKET*)State->Data;

        //GET MAX LUN  

       if(Setup->bmRequestType == 0xA1 && Setup->bRequest ==  0xFE)

       {

              *(volatile UINT8 *)((UINT32)&usb.FIFO[0]) = 0;

                      usb.Indexed.PERI_CSR0=DM335_USB_Indexed::PERI_CSR0_TXPKTRDY | DM335_USB_Indexed::PERI_CSR0_DATAEND;

           return;

       }

       //Mass Storage Reset

       if(Setup->bmRequestType == 0x21 && Setup->bRequest ==  0xFF)

       {

            usb.Indexed.PERI_CSR0=DM335_USB_Indexed::PERI_CSR0_TXPKTRDY | DM335_USB_Indexed::PERI_CSR0_DATAEND;

         return;

       }

 }

该部分代码直接添加在/DeviceCode/Targets/Native/DM335/DeviceCode/DM335_USB.cpp中的DM335_USB_Driver::EP0_ISR()函数即可。

  1. Mass Storage功能实现
    其实只要实现了标准的USB驱动接口,在此基础上实现Mass Storage功能应该算不太难,这里不打算详细介绍Mass Storage功能的方方面面,这会涉及到太多的相关知识,我这里只是从我们实际的这部分功能出发,简明扼要地介绍一下Mass Storage功能实现的原理。

这里需要特别指出的是,Bulk-only transport协议,仅需要USB驱动提供两个端点即可,一个是端点1(输入端点),一个是端点2(输出端点),两者的类型都为BULK模式。很幸运的是我们的.Net Micro Framework的标准驱动和这个要求是一致的。

5.1 命令/数据/状态
Mass Storage设备枚举成功后,PC会通过端点2向Mass Storage设备发送各种命令,Mass Storage设备根据相应的命令,进行不同的应答。

其命令、数据、状态相关的流程图如下:

image.png

 

 

PC机发送的数据必须符合CBW格式(31byte,小端模式),而Mass Storage设备的应答,其格式必须符合CSW格式(13byte,小端模式)。至于中间过程传输的数据,根据不同的命令,格式也有不同地要求。

5.1.1 CBW命令块(Command Block Wrapper)
 
image.png

表3 CBW命令块

dCBWSignature:常数0x43425355,标识为CBW命令块。

dCBWTag: 由主机发送的CBW标签。设备应该在相关的CSW的dCSWTag以相同的值应答主机。

dCBWDataTransferLength: 在本命令执行期间,主机期望通过Bulk-In或Bulk-Out端点传输的数据长度。如果为0,则表示这之间没有数据传输。

bmCBWFlags: 定义如下(Bit7 Direction(dCBWDataTransferLength为0时,该值无意义) :

 0= DataOut,数据从主机到设备

1= DataIn, 数据从设备到主机

Bit6 Obsolete  0

Bits 5..0 Reserved  0

bCBWLUN: 表示正在发送命令字的设备的逻辑单元号(LUN)。对于支持多个LUN的设备,主机设置相对应的LUN值。否则,该值为0。

bCBWCBLength: CBWCB的有效字节长度。有效值是在1到16之间。

CBWCB: 被设备解析执行的命令块。

注:该部分是重中之重,通过对这部分的命令的解析,实现实际的Mass Storage功能。

5.1.2 CSW状态块(Command Status Wrapper)
 
image.png

表4 CSW状态块

dCSWSignature: 常数0x53425355,标识为CSW状态块

dCSWTag: 取相对应的CBW的dCBWTag值。

dCSWDataResidue:实际传输的数据个数和期望要传输的数据个数之差。

bCSWStatus:命令执行情况,相关值如下:

image.png

 

5.2 SCSI 传输协议(或UFI传输协议)
很多资料上都是把子类协议设置为0x06,也就是SCSI 传输协议,实际测试表明设置为0x04(也就是UFI传输协议)也是可以的。实际看说明书,发现二者很多命令都是相同的,所以这两种协议对我们来说都适合,不过我这里建议最好看UFI传输协议手册,它要比SCSI手册简明地多。

无论是SCSI 传输协议还是UFI传输协议,其命令都是非常多的,不过对于我们的应用,我们仅需实现如下几条指令即可。

5.2.1 INQUIRY命令
该命令询问Mass Storage设备的基本信息,如生产厂家,产品名称,产品版本等等。

image.png

 

详细参数说明请参见《UFI Command Specification》,比较有意思的是Peripheral Device Type参数,如果设置为0,则表示这是一个可移动的存储设备(类似U盘),而设置为0x1F,则表示是一个非移动设备(类似硬盘,图标在硬盘区出现)。

5.2.2 READ_FORMAT_CAPACITIES命令
该命令获取Mass Storage设备存储大小,Block长度(一般为一个扇区大小,默认为512)等信息。

image.png

 

该表仅包括部分反馈信息,详细说明请参见《UFI Command Specification》。需要注意的是,无论是块个数,还是块长度,其数据格式为大端模式。

5.2.3 READ_CAPACITY命令
该命令返回最后一个块的索引和块的长度,其实该命令可以看着是READ_FORMAT_CAPACITIES命令的一个子集。

image.png

 

注意数据格式为大端模式。

详细说明请参见《UFI Command Specification》。

5.2.4 READ_10命令
该命令由PC端发出,请求Mass Storage设备发送指定扇区索引、扇区个数的数据。

image.png

 

这是PC机请求的命令,Mass Storage设备直接返回相应的数据即可。

详细说明请参见《UFI Command Specification》。

5.2.5 WRITE_10命令
该命令由PC端发出,CBW命令块后面紧跟的就是相应扇区的数据。

image.png

 

Mass Storage设备获取数据后,写到相应扇区即可。

这里需要强调的是,由于要接收的数据量有可能很大,该部分功能又是在时钟中断中实现,所以不要试图一次获取所有的扇区数据,否则在实际的TinyCLR环境中运行是不正常的。其实在READ_10中也存在类似问题,不过实际测试,直接发送所有数据,并没有什么问题,这从侧面反映PC机的接收数据能力,远远大于MF设备。

5.2.6 REQUEST_SENSE命令
PC机每发送一个命令后,都会检测设备返回的CSW的状态值是否为0(Good Status),如果不为0,则PC机马上发送REQUEST_SENSE命令,询问出错的进一步信息。

image.png

 

我们这里Sense Key的值直接设为0x05(ILLEGAL REQUEST)即可,主要原因是PC端会询问各种命令,由于我们没有实现,返回的CSW状态都非Good Status而已。

详细说明请参见《UFI Command Specification》。

5.2.7 TEST_UNIT_READY命令
在没有其它命令进行操作时,PC端会每隔一定时间,就会发送该命令,主要是为了探测Mass Storage设备是否存在(类似心跳信号)。

由于该命令没有数据交互,我们直接返回状态Good Status的CSW状态块即可。

相关文章
|
1月前
|
人工智能 开发框架 .NET
.NET技术的强大功能:.NET技术的基础特性、在现代开发中的应用、以及它如何助力未来的软件开发。
.NET技术是软件开发领域的核心支柱,以其强大功能、灵活性及安全性广受认可。本文分三部分解析:基础特性如多语言支持、统一运行时环境;现代应用如企业级与Web开发、移动应用、云服务及游戏开发;以及未来趋势如性能优化、容器化、AI集成等,展望.NET在不断变化的技术环境中持续发展与创新。
65 4
|
22天前
|
开发框架 缓存 前端开发
实战.NET Framework 迁移到 .NET 5/6
从.NET Framework 迁移到.NET 5/6 是一次重要的技术革新,涵盖开发环境与应用架构的全面升级。本文通过具体案例详细解析迁移流程,包括评估现有应用、利用.NET Portability Analyzer 工具识别可移植代码、创建新项目、逐步迁移代码及处理依赖项更新等关键步骤。特别关注命名空间调整、JSON 序列化工具更换及数据库访问层重构等内容,旨在帮助开发者掌握最佳实践,确保迁移过程平稳高效,同时提升应用性能与可维护性。
52 2
|
24天前
|
开发框架 JSON 监控
实战指南:从 .NET Framework 迁移到 .NET 5/6 的策略与最佳实践
【8月更文挑战第28天】从 .NET Framework 迁移到 .NET 5/6 是一次重要的技术升级,涉及开发环境与应用架构的改进。本文通过具体案例分析,介绍迁移策略与最佳实践,帮助开发者顺利完成转变。
29 1
|
27天前
|
存储 API 开发工具
【Azure Storage Blob】如何通过.NET Azure Storage Blobs SDK获取到Blob的MD5值呢?
【Azure Storage Blob】如何通过.NET Azure Storage Blobs SDK获取到Blob的MD5值呢?
|
1月前
|
缓存 程序员
封装一个给 .NET Framework 用的内存缓存帮助类
封装一个给 .NET Framework 用的内存缓存帮助类
|
27天前
|
存储
【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob (二)
【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob (二)
|
27天前
|
存储 监控 开发工具
【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob (一)
【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob (一)
|
28天前
【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn
【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn
|
14天前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
21 7

热门文章

最新文章