基于STM32F103的USB学习笔记35 - Mass Storage之SCSI命令

72 篇文章 36 订阅

参考文档:UFI Command Specification、SCSI Reference Guide

CBW包中CB部分(0FH-1EH)的第一个字节是操作码,对于Mass Storage来说,使用的SCSI协议定义了其具体意义。常用的命令如下:

1. SCSI_TEST_UNIT_READY(0x00)

作用是查询设备是否ready。空闲的时候,主机就会发送test ready命令下来,这个命令不用回复和应答数据,如果已经Ready,只要回复个CSW即可。如果设备无法运行或处于需要主机操作以准备就绪的状态,设备应返回检查条件状态,检测键为NOT ready。

void scsiCmdTestUnitReady(uint8_t lun)
{
    if(massMalGetStatus(lun) > 0)
    {
        scsiSetSenseData(gMscCBW.bLun, SCSI_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
        mscBotSendCSW(MSC_BOT_CSW_CMD_FAILED, MSC_BOT_SEND_CSW_ENABLE);
        mscBotAbort(MSC_DIR_IN);
        return;
    }
    mscBotSendCSW(MSC_BOT_CSW_CMD_PASSED, MSC_BOT_SEND_CSW_ENABLE);
}

CSW的数据格式如下图:

前面4个字节0x53425355表示CSW的标志字,接下来的4个字节是Tag,然后是4个字节的dDataResidue,表示剩余数据长度,最后一个字节是Status。

2. SCSI_REQUEST_SENSE(0x03)

设备将指定LUN的SENSE数据发送给HOST。HOST发送的命令数据格式如下表:

Allocation Length: 指定HOST可以接收的Sense数据的最大字节数。如果这小于Sense数据的大小,设备将只返回请求的字节数。

返回的Sense数据的格式如下图所示:

uint8_t gScsiSenseData[] =
{
    0x70, /*RespCode*/
    0x00, /*SegmentNumber*/
    SCSI_NO_SENSE, /* Sens_Key*/
    0x00,
    0x00,
    0x00,
    0x00, /*Information*/
    0x0A, /*AdditionalSenseLength*/
    0x00,
    0x00,
    0x00,
    0x00, /*CmdInformation*/
    SCSI_NO_SENSE, /*Asc*/
    0x00, /*ASCQ*/
    0x00, /*FRUC*/
    0x00, /*TBD*/
    0x00,
    0x00 /*SenseKeySpecific*/
};

设置Sense数据的函数如下:

void scsiSetSenseData(uint8_t lun, uint8_t sensKey, uint8_t asc)
{
    gScsiSenseData[2] = sensKey;
    gScsiSenseData[12] = asc;
}

发送Sense数据:

void scsiCmdRequestSense(uint8_t lun)
{
    uint8_t len;
    len = (gMscCBW.CB[4] > sizeof(gScsiSenseData)) ? sizeof(gScsiSenseData) : gMscCBW.CB[4];
    mscBotSendData(gScsiSenseData, len);
}

3. SCSI_FORMAT_UNIT(0x04)

HOST发送格式化单元命令。命令格式如下表:

FmtData必须为1,CmpList必须为0,Defect List Format必须为7. 如果不是这些值,Sense Key = ILLEGAL REQUEST,Sense Code = INVALID FIELD IN COMMAND PACKET。

Track Number: 指定要格式化的Track。此参数对HD和磁盘的单Track格式有效。

Interleave: 指定格式化会用到的交错功能。

    0: 使用默认交错。对于USB-FDU,这是1:1。

    1: 使用1:1的交错。

交错为1意味着连续的逻辑块将按连续的升序排列。

Parameter List Length: 参数列表中的字节数。HOST在发送FORMAT UNIT命令后,将参数列表发送到Bulk Out端点上的设备。对于FORMAT UNIT命令,长度为零不是错误。参数列表长度通常为4+8字节。

【Format Parameter List】

在发送命令包后,HOST应在Bulk Out端点输出格式参数列表。格式参数列表可能包括:

Defect List Header(4字节) + Format Descriptor(8字节)

FOV:Format Options Valid的缩写,格式化参数有效位,当DCRT = 1或Immediate = 1时,FOV设置为1.

Extend:一直为0

DCRT:Disable Certification的缩写,禁止证书。如果设置为1,设备不会认证此磁盘。对于USB Mass Storage,这个位设置为1.

Single Track:设置为1时,表示仅格式化Track Number指定的Track。只有在格式化兼容的HD或DD软盘时才需要该位。

Immediate:当设置为1时,该命令应立即返回状态。USB Mass Storage不支持这个设置,如果设置为1,则Sense Key = ILLEGAL REQUEST & Sense Code = ILLEGAL FIELD IN PARAMETER LIST。

Side:指定使用Single Track格式时哪一面格式化。如果Side = 1,Top Side被格式化;Side = 0, Bottom Side被格式化。

Defect List Length:值默认为8。如果不是8,Sense Key = ILLEGAL REQUEST & Sense Code = ILLEGAL FIELD IN PARAMETER LIST。

4. SCSI_INQUIRY(0x12)

INQUIRY命令请求将有关设备本身参数的信息发送到主机。

EVPD: Enable Vital Product Data的缩写,使能必不可少的产品数据。

Logical Unit Number: 有效值0-7.

Page Code:设备仅支持页面代码零(00h),即标准查询数据。

Allocation Length:指定要返回的查询数据的最大字节数。值为零不会导致错误。

标准INQUIRY数据格式:

Peripheral Device Type:标识当前连接到请求的逻辑单元的设备。

      00h:直接存取设备

     1Fh: none(没有连接到请求的逻辑单元的FDD)

RMB:Removable Media Bit的缩写,设置为1表示可移动磁盘。

ISO Version:为0

ECMA Version:为0

ANSI Version:为2,表示非标准。

Response Data Format:

Additional Length:指定参数的字节长度。

Vendor Identification:制造商信息,默认8字节长。

Product Identification:产品信息,默认16字节长。

命令实现的代码如下:

void scsiCmdInquiry(uint8_t lun)
{
    uint8_t *buf;
    uint16_t len;
    if( (gMscCBW.CB[1] & 0x01) > 0)//EVPD > 0
    {
        buf = (uint8_t *)gScsiInquiryPage00Data;
        len = SCSI_INQUIRYPAGE00_DATA_LEN;
    }
    else
    {
        buf = (uint8_t *)gScsiInquiryData;
        len = (gMscCBW.CB[4] > SCSI_INQUIRY_DATA_LEN) ? SCSI_INQUIRY_DATA_LEN : gMscCBW.CB[4];
    } 
    mscBotSendData(buf, len);
}

const uint8_t gScsiInquiryPage00Data[] = 
{
    0x00, /* PERIPHERAL QUALIFIER & PERIPHERAL DEVICE TYPE*/
    0x00,
    0x00,
    0x00,
    0x00 /* Supported Pages 00*/
};

const uint8_t gScsiInquiryData[] =
{
    0x00,           /* Direct Access Device */
    0x80,           /* RMB = 1: Removable Medium */
    0x02,           /* Version: No conformance claim to standard */
    0x02,

    31,             /* Additional Length */
    0x00,           /* SCCS = 1: Storage Controller Component */
    0x00,
    0x00,
    /* Vendor Identification */
    'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',
    /* Product Identification */
    'R', 'A', 'M', ' ', 'D', 'i', 's', 'k', 
    ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
    /* Product Revision Level */
    '1', '.', '0', ' '
};

5. SCSI_START_STOP_UNIT(0x1B)

启用或禁用介质访问操作。

IMMED: 是否立即执行

Start: 1表示启用介质访问操作;0表示禁止。

LoEj: Load Eject,1表示当Start = 0时弹出介质;0表示不弹出介质。

这里不支持这个功能,直接返回PASSED状态。

void scsiCmdStartStopUnit(uint8_t lun)
{
    mscBotSendCSW(MSC_BOT_CSW_CMD_PASSED, MSC_BOT_SEND_CSW_ENABLE);
}

6. SCSI_ALLOW_MEDIUM_REMOVAL(0x1E)

允许或禁用移除逻辑单元中的介质。

Prevent:0表示允许介质的移除;1表示禁止介质的移除。

如果设备不支持锁定功能,介质将始终处于解锁状态。在这种情况下,(Prevent=0)命令将成功完成,Sense Key = NO sense,而(Prevent=1)命令将导致错误,Sense Key = ILLEGAL REQUEST & Sense Code = ILLEGAL FIELD IN PARAMETER LIST。

此命令这里和SCSI_START_STOP_UNIT一样调用scsiCmdStartStopUnit,即不支持这个功能。

case SCSI_ALLOW_MEDIUM_REMOVAL:
            scsiCmdStartStopUnit(gMscCBW.bLUN);
            break;

7. SCSI_MODE_SENSE6(0x1A)SCSI_MODE_SENSE10(0x5A)

LLBA(仅10字节命令): Large LBA? 值为0.

DBD: Disable Block Descriptor的缩写。值为0.

Page Control: 

       0h(00b)= 当前值。设备将当前的参数给请求的page,然后返回该page。

       1h(01b)= 可变参数值。指示程序可以更改哪些参数(1表示可更改的参数,0表示不可更改的参数)。

       2h(10b)= 默认值。将默认的参数给请求的page。

       3h(11b)= 保存的值。此选项仅对可保存的模式页有效。如果无法保存模式页,则返回检查条件。

Page Code: 指定要返回的模式页。指定3Fh请求所有模式页。在这种情况下,模式页应按升序页代码顺序返回,模式页00h除外。

SubPage Code: 指定库返回的模式子页。

Allocation Length: 指定要返回数据的最大字节数。

const uint8_t gScsiModeSense6data[] =
{
    0x03,
    0x00,
    0x00,
    0x00,
};

 

void scsiCmdModeSense6(uint8_t lun)
{
    mscBotSendData((uint8_t *)gScsiModeSense6data, SCSI_MODE_SENSE6_DATA_LEN);
}

const uint8_t gScsiModeSense10data[] =
{
    0x00,
    0x06,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00
};
void scsiCmdModeSense10(uint8_t lun)
{
    mscBotSendData((uint8_t *)gScsiModeSense10data, SCSI_MODE_SENSE10_DATA_LEN);
}

8. SCSI_READ_FORMAT_CAPACITIES(0x23)

READ FORMAT CAPACITIES命令是HOST请求在当前安装的介质上可以格式化的可能容量的列表。如果当前没有安装介质,UFI设备应返回设备可以格式化的最大容量。

设备返回容量列表给HOST,其结构如下:

容量列表分2种情况,

        无介质的情况:Capacity List Header + Maximum Capacity Header(这里只考虑这种情况)

        有介质的情况:Capacity List Header + Current Capacity Header + Formattable Capacity Descriptors

Capacity List Header:4个字节长度,前面3个字节保留,第4个自己表示容量列表的长度(不包括这4个字节)。

Current/Maximum Capacity Descriptor:描述了当前介质容量/设备支持的格式化最大容量

Number of Blocks:可寻址块的数量

Block Length:指定返回到主机的描述符的类型。

void scsiCmdReadFormatCapacity(uint8_t lun)
{
    uint8_t readFormatCapacityData[] =
    {
        0x00,
        0x00,
        0x00,
        0x08, /* Capacity List Length */

        /* Block Count */
        0,
        0,
        0,
        0,

        /* Block Length */
        0x02,/* Descriptor Code: Formatted Media */
        0,
        0,
        0
    };
    
    if(massMalGetStatus(lun) > 0)
    {
        scsiSetSenseData(gMscCBW.bLUN, SCSI_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
        mscBotSendCSW(MSC_BOT_CSW_CMD_FAILED, MSC_BOT_SEND_CSW_ENABLE);
        mscBotAbort(MSC_DIR_IN);
        return;
    }
    readFormatCapacityData[4] = (uint8_t)(gMassMemory.blockCount[lun] >> 24);
    readFormatCapacityData[5] = (uint8_t)(gMassMemory.blockCount[lun] >> 16);
    readFormatCapacityData[6] = (uint8_t)(gMassMemory.blockCount[lun] >> 8);
    readFormatCapacityData[7] = (uint8_t)(gMassMemory.blockCount[lun] >> 0);
    
    readFormatCapacityData[9] = (uint8_t)(gMassMemory.blockSize[lun] >> 16);
    readFormatCapacityData[10] = (uint8_t)(gMassMemory.blockSize[lun] >> 8);
    readFormatCapacityData[11] = (uint8_t)(gMassMemory.blockSize[lun] >> 0);
    mscBotSendData(readFormatCapacityData, sizeof(readFormatCapacityData));
}

 

9. SCSI_READ_CAPACITY10(0x25)

读取当前安装介质的容量。

RelAdr: 值为0.

Logical Block Address:值为0.

PMI: 值为0.

返回的容量数据结构:

void scsiCmdReadCapacity10(uint8_t lun)
{
    uint8_t readCapacity10Data[] =
    {
        /* Last Logical Block */
        0,
        0,
        0,
        0,

        /* Block Length */
        0,
        0,
        0,
        0
    };
    if(massMalGetStatus(lun) > 0)
    {
        scsiSetSenseData(gMscCBW.bLUN, SCSI_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
        mscBotSendCSW(MSC_BOT_CSW_CMD_FAILED, MSC_BOT_SEND_CSW_ENABLE);
        mscBotAbort(MSC_DIR_IN);
        return;
    }
    readCapacity10Data[0] = (uint8_t)((gMassMemory.blockCount[lun] - 1) >> 24);
    readCapacity10Data[1] = (uint8_t)((gMassMemory.blockCount[lun] - 1) >> 16);
    readCapacity10Data[2] = (uint8_t)((gMassMemory.blockCount[lun] - 1) >>  8);
    readCapacity10Data[3] = (uint8_t)(gMassMemory.blockCount[lun] - 1);

    readCapacity10Data[4] = (uint8_t)(gMassMemory.blockSize[lun] >>  24);
    readCapacity10Data[5] = (uint8_t)(gMassMemory.blockSize[lun] >>  16);
    readCapacity10Data[6] = (uint8_t)(gMassMemory.blockSize[lun] >>  8);
    readCapacity10Data[7] = (uint8_t)(gMassMemory.blockSize[lun]);
    mscBotSendData(readCapacity10Data, sizeof(readCapacity10Data));
}

10. SCSI_READ10(0x28)

HOST从设备读取数据。

DPO,FUA,RelAdr:这3个参数都为0.

Logical Block Address:指定开始读取操作的逻辑块地址。

Transfer Length:指定读取的数据长度。

/* Calculate Logical Block Address */
gScsiInfo.LBA = (gMscCBW.CB[2] << 24) | (gMscCBW.CB[3] << 16) | (gMscCBW.CB[4] <<  8) | gMscCBW.CB[5];
/* Calculate the Number of Blocks to transfer */
gScsiInfo.BlkLen = (gMscCBW.CB[7] <<  8) | gMscCBW.CB[8];

读数据分2个阶段,如果这笔数据是第一次读入,则先判断是否地址和长度有效。

if(scsiAddressManagement(lun, SCSI_READ10, lba, blockNbr) == FALSE)
      return;

然后判断数据方向,确认是IN则读入数据,并将状态改为DATA IN。如果方向错误则发送错误Sense。

        if((gMscCBW.bmFlags & 0x80) != 0)
        {
            gMscBotState = MSC_BOT_DATA_IN;
            massReadMemory(lun, lba, blockNbr);
        }
        else
        {
            mscBotAbort(MSC_DIR_BOTH);
            scsiSetSenseData(gMscCBW.bLUN, SCSI_ILLEGAL_REQUEST, SCSI_ASC_INVALID_FIELED_IN_COMMAND);
            mscBotSendCSW(MSC_BOT_CSW_CMD_FAILED, MSC_BOT_SEND_CSW_ENABLE);
            
        }

第二个阶段是持续读入数据阶段,这是状态变量已经是DATA IN,只需要读入数据即可。

massReadMemory(lun, lba, blockNbr);

11. SCSI_WRITE10(0x2A)

HOST写数据给设备。

基本与READ10差不多。

12. SCSI_VERIFY10(0x2F)

CB[1]的Bit2具体什么意思没有查到资料,代码里面有判断这个bit是否为0,为0则表示正常。判断CBW的dDataLength是否为0,为0则表示数据已经读写完,只有读写完了才能有Verify命令。

void scsiCmdVerify10(uint8_t lun)
{
    if(gMscCBW.dDataLength == 0 && (gMscCBW.CB[1] & SCSI_VERIFY_BLKVFY) == 0)
    {
        mscBotSendCSW(MSC_BOT_CSW_CMD_PASSED, MSC_BOT_SEND_CSW_ENABLE);
    }
    else
    {
        mscBotAbort(MSC_BOTH_DIR);
        scsiSetSenseData(gMscCBW.bLUN, SCSI_ILLEGAL_REQUEST, SCSI_ASC_INVALID_FIELED_IN_COMMAND);
        mscBotSendCSW(MSC_BOT_CSW_CMD_FAILED, MSC_BOT_SEND_CSW_DISABLE);
    }
}

 

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
基于STM32F103C8T6的HC-05蓝牙通信可以通过以下步骤实现。首先,我们需要使用AT指令对HC-05蓝牙模块进行配置。这可以通过连接一个CH340模块到电脑的串口调试助手,然后发送AT指令到HC-05蓝牙模块来完成。初始波特率为38400,无奇偶校验,一位停止位。\[2\] 在STM32F103C8T6上,我们需要使用两个USART串口来实现蓝牙通信。一个串口用于与电脑进行通信,另一个串口用于与蓝牙模块进行通信。电脑发送调试命令给单片机,单片机再发送给蓝牙,蓝牙再返还命令处理结果给单片机,单片机再上传给电脑。\[3\] 通过配置STM32F103C8T6的串口参数,我们可以实现与蓝牙模块的通信。可以使用STM32的串口库函数来发送和接收数据。通过串口与蓝牙模块进行通信,我们可以实现一些功能,比如远程控制LED亮灭、蓝牙小车、远程监控等等。这为我们提供了更多的发挥空间和创造力。\[1\] 总结起来,基于STM32F103C8T6的HC-05蓝牙通信需要使用AT指令对蓝牙模块进行配置,并通过两个USART串口实现与蓝牙模块的通信。这样我们就可以实现各种有趣的功能了。 #### 引用[.reference_title] - *1* *3* [基于STM32F103C8T6的HC-06蓝牙通信](https://blog.csdn.net/qq_46015224/article/details/127714326)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [stm32f103c8t6+HC-05蓝牙模块+L298N电机驱动+直流电机组成的蓝牙遥控小车](https://blog.csdn.net/NJWZS/article/details/120680567)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值