在嵌入式系统开发中,USB通信扮演着至关重要的角色。RL-USB作为ARM公司开发的专用软件栈,为开发者提供了完整的USB协议实现,其中Mass Storage Class(MSC)函数层是构建大容量存储设备的核心。这些函数直接处理主机与设备间的数据交换,理解其工作机制对于开发定制化USB设备至关重要。
RL-USB软件栈采用分层设计,MSC函数位于功能层(Function Layer),直接面向应用开发者。这一层抽象了底层USB协议细节,提供简洁的API接口。函数层之上是应用层,之下是协议层和硬件抽象层,这种设计使得开发者无需深入理解复杂的USB协议规范即可快速实现功能。
MSC函数的设计遵循了USB大容量存储类规范,确保与各种操作系统兼容。当主机枚举USB设备时,这些函数负责响应标准请求,处理SCSI命令集,并管理数据传输流程。函数内部实现了必要的状态机和错误处理机制,为上层应用提供稳定的服务。
MSC函数层包含多个关键函数,其中最重要的是数据传输相关的MemoryRead和MemoryWrite。MemoryRead负责将设备内存中的数据发送到主机,而MemoryWrite执行相反方向的传输。这两个函数共同构成了USB存储设备的数据通道基础。
验证函数MemoryVerify则提供数据完整性检查机制,确保写入设备的数据与主机发送的数据完全一致。这对于需要高可靠性的存储应用尤为重要。Inquiry函数处理设备标识信息,向主机报告厂商ID、产品ID等关键信息,这些信息会在设备枚举阶段被操作系统读取并显示。
所有MSC函数都遵循相同的调用约定:无返回值(void类型),通过全局变量或参数传递状态信息。这种设计减少了函数调用开销,提高了实时性,符合嵌入式系统的性能要求。
MSC_MemoryRead是RL-USB栈中处理数据上传(设备到主机)的核心函数。当主机发起读取请求时,USB协议栈会调用此函数。其内部执行流程可分为三个阶段:请求解析、数据准备和数据发送。
函数首先解析主机请求,确定需要传输的数据量和起始地址。然后从设备内存(通常是RAM)中读取相应数据到缓冲区。最后通过USB_WriteEP函数将数据发送到指定的IN端点。传输的数据量会自动适配主机请求和端点最大包大小的较小值,确保符合USB协议规范。
数据传输采用异步方式,函数返回后底层硬件会继续完成实际的数据传输。这种非阻塞设计提高了系统效率,允许CPU在数据传输期间处理其他任务。函数内部使用循环缓冲区管理技术,支持大数据量的分块传输。
函数虽然没有显式参数,但依赖于几个重要的全局变量:
典型的实现会包含流量控制机制,防止缓冲区溢出。当数据准备好后,函数调用USB_WriteEP触发实际传输:
c复制USB_WriteEP(MSC_EP_IN, &Memory, n);
这个调用将n字节数据从Memory缓冲区通过MSC_EP_IN端点发送到主机。开发者需要注意端点方向(IN表示设备到主机)和缓冲区对齐要求。
重要提示:在RTOS环境中使用MemoryRead时,必须确保对共享资源(如全局变量)的访问是线程安全的。建议使用互斥锁保护关键数据区。
基础实现使用RAM作为存储介质,实际应用中往往需要扩展以支持其他存储设备。例如,添加Flash存储器支持时,可以这样修改:
c复制void Flash_MemoryRead(U32 Address, U32 Length, U8* Buffer) {
// 实现具体的Flash读取逻辑
flash_read(Address, Buffer, Length);
}
void MSC_MemoryRead(void) {
U32 n = MIN(BulkLen, MAX_PACKET_SIZE);
Flash_MemoryRead(Offset, n, &Memory);
USB_WriteEP(MSC_EP_IN, &Memory, n);
// 更新偏移量和其他状态
}
这种扩展保持了原有函数接口,仅替换了底层存储访问逻辑,体现了良好的设计抽象。开发者可以根据需要添加错误处理、数据加密或压缩等高级功能。
MSC_MemoryWrite处理数据下载(主机到设备)操作,其工作流程与MemoryRead对称但方向相反。当主机发送OUT事务时,USB控制器接收数据并触发中断,协议栈随后调用MemoryWrite函数。
函数内部首先检查接收数据的有效性,包括长度检查和缓冲区边界验证。有效数据被复制到目标内存区域,同时更新设备状态。与读取操作不同,写入操作通常需要更严格的数据验证,因为错误的数据可能损坏设备存储内容。
数据传输同样采用分包处理机制,大块数据会被分割成多个USB事务。函数内部维护传输状态,确保数据按正确顺序重组。典型的实现会使用双缓冲技术来提高吞吐量,一个缓冲区用于接收新数据,同时另一个缓冲区处理已接收的数据。
基础实现将数据写入RAM,实际产品通常需要持久化存储。扩展支持Flash存储器的示例:
c复制void Flash_MemoryWrite(U32 Address, U32 Length, U8* Buffer) {
// 擦除目标扇区(如果需要)
flash_erase(Address);
// 编程数据到Flash
flash_program(Address, Buffer, Length);
}
void MSC_MemoryWrite(void) {
// 直接从端点缓冲区写入Flash,避免RAM中转
Flash_MemoryWrite(Offset, BulkLen, &BulkBuf);
// 更新设备状态
}
这种实现跳过了RAM中转环节,提高了效率但增加了复杂性。开发者需要注意Flash编程的特殊要求,如按页写入、需要先擦除等。对于频繁写入的应用,建议添加写缓存和磨损均衡算法。
提高MemoryWrite性能的几个关键点:
实测表明,优化后的实现可以将写入吞吐量提高3-5倍。特别是在资源受限的嵌入式系统中,这些优化能显著改善用户体验。
MemoryVerify函数提供关键的数据完整性检查功能,确保写入设备的数据与主机发送的数据完全一致。其工作原理是对比两个数据源的内容:原始数据(保存在BulkBuf中)和目标数据(从存储介质读取)。
典型的实现采用逐字节比较方式:
c复制for (n=0; n < BulkLen; n++) {
if (Memory[n] != BulkBuf[n]) {
MemOK = __FALSE;
break;
}
}
这种简单直接的验证方法虽然效率不高,但可靠性好,易于调试。对于大容量存储设备,可以考虑优化为按块比较或添加CRC校验等更高效的方法。
验证失败会设置MemOK标志为__FALSE,上层应用可以根据此标志决定重试或报错。完善的实现还应该记录错误位置和模式,帮助诊断硬件问题。
当使用Flash等特殊存储介质时,需要考虑其特性:
扩展的Flash验证函数示例:
c复制int Flash_Verify(U32 addr, U32 len, U8 *buf) {
U8 read_buf[256];
while(len > 0) {
U32 chunk = MIN(len, sizeof(read_buf));
flash_read(addr, read_buf, chunk);
if(memcmp(buf, read_buf, chunk) != 0) {
return -1; // 验证失败
}
addr += chunk;
buf += chunk;
len -= chunk;
}
return 0; // 验证成功
}
这种实现添加了分块处理,适合大容量验证,同时避免了大缓冲区带来的内存压力。
Inquiry函数响应主机的查询请求,提供设备的基本标识信息。这些信息遵循SCSI标准格式,包括:
标准要求这些字段必须是ASCII字符,右对齐并用空格填充。例如Keil的厂商ID设置为"Keil "(注意后面的四个空格)。这些信息会显示在操作系统的设备管理器中,帮助用户识别设备。
开发者应该修改默认值以反映实际产品信息:
c复制void MSC_Inquiry(void) {
// 厂商标识(8字节)
strncpy((char*)&BulkBuf[8], "MyCorp", 6);
// 产品标识(16字节)
strncpy((char*)&BulkBuf[16], "FlashDisk v1", 12);
// 产品版本(4字节)
strncpy((char*)&BulkBuf[32], "1.0", 3);
// 发送响应
DataInTransfer();
}
注意保持各字段的长度不变,仅修改内容部分。不规范的字段长度可能导致主机端驱动兼容性问题。建议在设备开发早期就确定这些标识信息,因为某些操作系统会缓存这些值。
高级应用可能需要根据配置动态改变设备标识。这可以通过添加条件判断来实现:
c复制void MSC_Inquiry(void) {
if(device_mode == MODE_A) {
set_identification("VendorA", "ProductA", "1.0");
} else {
set_identification("VendorB", "ProductB", "2.0");
}
DataInTransfer();
}
这种灵活性允许单个固件支持多种产品型号或工作模式,简化了产品线管理。但要注意不同模式应该使用明显不同的标识,避免主机端驱动混淆。
在实际项目中,MSC函数使用过程中常见的问题包括:
数据传输不完整
设备枚举失败
写入验证失败
性能低下
有效调试MSC设备的方法:
特别有用的调试手段是在MemoryRead/Write中添加状态跟踪:
c复制void MSC_MemoryWrite(void) {
debug_printf("Write: offset=%u, len=%u", Offset, BulkLen);
// ...原有实现...
if(error) debug_printf("Error at %u", error_pos);
}
在某Flash磁盘项目中,通过以下优化将传输速率从0.8MB/s提升到2.4MB/s:
端点配置优化
Flash编程优化
代码层面优化
优化前后的关键指标对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 读取速度 | 1.2MB/s | 3.0MB/s |
| 写入速度 | 0.8MB/s | 2.4MB/s |
| CPU利用率 | 85% | 45% |
| 功耗 | 120mA | 90mA |
这些优化显著改善了用户体验,特别是对大文件传输场景。