响应格式规范

响应格式规范

  • 方式一
    基于内置的标准状态码进行响应,不做任何新的错误码定义,异常、错误就直接响应对应的HttpStatus;正常就返回200并在body中带上业务数据;
    优点
    基于标准的状态码;不用进行新的定义;
    减少前后端对于状态码的沟通
    缺点
    标准的只是定义了一些最基本的,无法满足一些个性化的业务场景;不过这种场景,也可以基于响应数据的格式去做

  • 方式二
    不用内置的标准状态码,所有的接口请求不管是正常、异常、错误;全部返回200;然后在doby的数据中定义自己系统的状态码;客户端收到body的数据之后,根据前后端约定的状态码进行校验并友好提示;
    优点
    灵活性强;可以根据自己的业务场景,去定义个性化的规则,
    可扩展性强;可以根据需要任意扩展;
    缺点
    规则约定带来的负担,
    维护成本增加;可能因为定义不规范导致后续维护的困难;
    上面说的方式,没有对错,只有合不合适,更多的是根据业务的需要,场景的需要,找更合适的方式;
    下面采用了上面两种方式的二合一版本;去定义一套响应规范;

成功:

{
    "status": 200,
    "msg": "成功!",
    "data": {
        "userName": "李四",
        "email": "lisi@qq.com"
    }
}

失败:

{
    "status": 1000,
    "msg": "参数错误!"
}
  • status

当前请求的状态码;这里定义的是200为成功;200之外的为异常情况;

  • msg

状态码对应的描述

  • data

响应的数据;该属性是一个泛型值;其类型、值都是根据具体的业务场景需要进行匹配

纯枚举的错误码定义(不采取)

我们可以延用系统自带的状态码;即org.springframework.http.HttpStatus枚举;但是这个往往只表述的一些通用的状态,不能够表达或说明一些详细的问题点;因此通常情况下我们会对错误码进行自定义;以更加详细的描述出现的问题;如下:

@Getter
@AllArgsConstructor
public enum BaseStatusCode {
    SUCCESS(200,"成功!"),

    ERR_1000(1000,"参数错误!"),

    ERR_9999(9999,"未知错误!")
    ;
    // 状态码
    private Integer status;

    // 状态码描述
    private String msg;
}

通常情况下就是这样去定义,使用也没有什么问题;但是这样写有一个比较大的问题;就是不够灵活、不易于扩展;因为这样,意味着所有的错误码都得定义在这一个枚举里面(后面的异常对象需要通过这个枚举值实例化);比如说,用户模块、设备模块、电商模块、库存模块都有自己个性化的错误码;就意味着,所有的验证码都堆在这么一个注解里面;耦合性太强,不便于扩展;

可扩展性状态码

为了解决上面不灵活的问题,那我们就采用一种面向接口的状态码定义;来提高状态码的可扩展性和灵活性;

调整起来也很简单,状态码同样也是以上面的枚举当时定义,但是增加一个接口出来;

第一步;定义接口

/**
 * 错误码的接口
 */
public interface IStatusCode {
    /**
     * 获取状态码
     * @return 返回
     */
     Integer getStatus();

    /**
     * 获取状态描述
     * @return 返回描述信息
     */
     String getMsg();
}

第二步;定义状态码

此时;枚举中status和msg对应的get方法也就对应成了IStatusCode的实现

Getter
@AllArgsConstructor
public enum BaseStatusCode implements IStatusCode {
    SUCCESS(200,"成功!"),

    ERR_1000(1000,"参数错误!"),

    ERR_9999(9999,"未知错误!")
    // 状态码
    private Integer status;

    // 状态码描述
    private String msg;
}

第三步;扩展错误码

**
 * @title: UserStatusCode
 * @projectName springcloud-mbb
 * @description: TODO 用户相关的状态码
 */
@Getter
@AllArgsConstructor
public enum UserStatusCode implements IStatusCode{
    ERR_2000(2000,"用户信息不存在"),

    ERR_2001(2001,"用户昵称格式错误")
    ;
    // 状态码
    private Integer status;

    // 状态码描述
    private String msg;
}
/**
 * @title: DeviceStatusCode
 * @projectName springcloud-mbb
 * @description: TODO 设备相关的状态码
 */
@Getter
@AllArgsConstructor
public enum DeviceStatusCode implements IStatusCode{
    ERR_3000(3000,"设备id有误"),
    ERR_3001(3001,"设备名称格式错误"),
    ERR_3002(3002,"设备MAC地址无效")
    ;
    // 状态码
    private Integer status;

    // 状态码描述
    private String msg;
}

第四步;状态码的获取

IStatusCode baseStatusCode = BaseStatusCode.ERR_9999;
IStatusCode userStatusCode = UserStatusCode.ERR_2000;
IStatusCode deviceStatusCode = DeviceStatusCode.ERR_3002;

如此获取,优势就展现出来了,不管是以枚举的状态码还是对象的方式,只要是实现了IStatusCode接口的类,都可以作为一个状态码对象;通过接口的getStatus()和getMsg()即可拿到状态码和状态描述;

这样,我们就可以只需要把所有模块公共的状态码定义在公共模块里面;其他模块个性化的状态码,定义在模块内部即可;

优点分析

定义解耦;不需要将所有的状态码定义到一起了;只要实现了IStatusCode接口即可
不限于枚举;因为是基于接口获取状态码和描述,因此不限于枚举,任何只要实现了IStatusCode都可以作为状态码

公共响应对象定义
有了规范好的响应对象的格式;有了状态码;那就可以定义一个基础的响应对象用来包装最后的返回结果;其中定义了4个构造方法,用于能够快速的实例化一个响应对象;

为了能更好的兼容;这里将HttpStatus状态码也封装了进来,这样就既可以使用默认状态码,可以使用自定义状态码,根据自己的需要灵活选择。

其中@JsonView的可以先不看,后面会介绍;只是为了后面不重复贴这一块的代码,先全部贴出来

/**
 * 基础的响应对象
 *
 * @param <T> 响应数据
 */
@Data
public class BaseResponceDto<T> {
    /**
     * 响应数据最外层的视图 也是所有响应视图的父类
     */
    public interface ResponceBaseDtoView {
    }

    /**
     * 状态码
     */
    @JsonView(ResponceBaseDtoView.class)
    private Integer status;

    /**
     * 状态描述
     */
    @JsonView(ResponceBaseDtoView.class)
    private String msg;

    /**
     * 响应数据
     */
    @JsonView(ResponceBaseDtoView.class)
    private T data;

    /**
     * 只有状态码的响应
     *
     * @param statusCode
     */
    public BaseResponceDto(IStatusCode statusCode) {
        if (null != statusCode) {
            this.status = statusCode.getStatus();
            this.msg = statusCode.getMsg();
        }
    }

    /**
     * 有状态码且有参数的响应
     *
     * @param statusCode
     * @param data
     */
    public BaseResponceDto(IStatusCode statusCode, T data) {
        if (null != statusCode) {
            this.status = statusCode.getStatus();
            this.msg = statusCode.getMsg();
        }
        if (null != data) {
            this.data = data;
        }
    }

    /**
     * 根据HttpStatus响应
     *
     * @param httpStatus http请求状态码
     */
    public BaseResponceDto(HttpStatus httpStatus) {
        if (null != httpStatus) {
            this.status = httpStatus.value();
            this.msg = httpStatus.getReasonPhrase();
        }
    }

    /**
     * 根据http状态码返回 并返回额外返回数据
     *
     * @param httpStatus http状态码
     * @param data       数据
     */
    public BaseResponceDto(HttpStatus httpStatus, T data) {
        if (null != httpStatus) {
            this.status = httpStatus.value();
            this.msg = httpStatus.getReasonPhrase();
        }
        if (null != data) {
            this.data = data;
        }
    }

    /**
     * 根据异常响应错误码
     *
     * @param baseException 异常对象
     */
    public BaseResponceDto(BaseException baseException) {
        if (null != baseException) {
            this.status = baseException.getError();
            this.msg = baseException.getMsg();
            this.data = (T) baseException.getData();
        }
    }
}

自定义异常对象
在整个业务的请求到响应,异常并不是必定会出现的;但是,异常并不是必定会出现,但是又是不得不处理的;并且他贯穿了整个业务的始终,从请求到响应,都有可能牵扯到异常;所以一个好的异常处理机制,是整个代码健壮性必定要考虑的因素。

@Data
public class BaseException extends RuntimeException {
    /**
     * 错误码
     */
    private Integer error;

    /**
     * 错误描述
     */
    private String msg;

    /**
     * 错误后响应的信息
     */
    private Object data;

    /**
     * 根据错误码实例化异常
     *
     * @param statusCode 自定义错误码
     */
    public BaseException(IStatusCode statusCode) {
        // 校验是否传递了异常码
        if (null == statusCode) {
            // 如果没有统一设置为未知错误
            setInfo(BaseStatusCode.ERR_9999);
        } else {
            setInfo(statusCode);
        }
    }

    /**
     * 根据http状态码抛出异常
     *
     * @param httpStatus http状态码
     */
    public BaseException(HttpStatus httpStatus) {
        if (null == httpStatus) {
            // 没有传递默认使用 未知异常
            setInfo(BaseStatusCode.ERR_9999);
        } else {
            setInfo(httpStatus);
        }
    }

    /**
     * 根据错误码实例化异常 并返回数据
     *
     * @param statusCode 自定义错误码
     * @param data       数据
     */
    public BaseException(IStatusCode statusCode, Object data) {
        // 校验是否传递了异常码
        if (null == statusCode) {
            // 如果没有统一设置为未知错误
            setInfo(BaseStatusCode.ERR_9999);
        } else {
            setInfo(statusCode);
        }
        // 校验数据是否为null
        if (null != data) {
            this.data = data;
        }
    }

    /**
     * 根据http的状态码实例化异常 并返回数据
     *
     * @param httpStatus http状态码
     * @param data       数据
     */
    public BaseException(HttpStatus httpStatus, Object data) {
        // 校验是否传递了异常码
        if (null == httpStatus) {
            // 如果没有统一设置为未知错误
            setInfo(BaseStatusCode.ERR_9999);
        } else {
            setInfo(httpStatus);
        }
        // 校验数据是否为null
        if (null != data) {
            this.data = data;
        }
    }


    /**
     * 设置状态码及描述信息
     * 内部使用的方法
     *
     * @param statusCode
     */
    private void setInfo(IStatusCode statusCode) {
        if (null != statusCode) {
            this.error = statusCode.getStatus();
            this.msg = statusCode.getMsg();
        }
    }

    /**
     * 根据HttpStatus设置属性
     * @param httpStatus
     */
    private void setInfo(HttpStatus httpStatus) {
        if (null != httpStatus) {
            this.error = httpStatus.value();
            this.msg = httpStatus.getReasonPhrase();
        }
    }
}
  • error:错误码

  • msg:错误描述

  • data:绑定的数据,异常也可能需要返回数据,因此可以在这里去指定

  • 构造方法:基于IStatusCode和HttpStatus的构造方法;用于快速实例化异常对象

  • 扩展异常:如果因业务需要,在特定场所需要一些一些特殊的异常;我们可以再建BaseException的子类去进一步细化。

  • 抛异常:异常定义好之后,想抛一个异常,自然就是很简单的啦

throw new BaseException(HttpStatus.ACCEPTED, "123456");

响应数据初始化工具
上面是提供了各种方式的构造方法,可以根据实际的需要进行实例化;为了能够更加方便的使用,所以这里写了一个静态工具类;用于将实例化响应对象的动作进一步封装,让响应数据对象的实例化更加简单、便捷;

/*
 * 响应帮助类
 */
public class ReturnUtils {

    /**
     * 响应成功
     *
     * @return
     */
    public static BaseResponceDto<Void> success() {
        return new BaseResponceDto(BaseStatusCode.SUCCESS);
    }

    /**
     * 根据Http状态码返回
     *
     * @return 基础的响应对象
     */
    public static BaseResponceDto<Void> successByHttpStatus() {
        return new BaseResponceDto(HttpStatus.OK);
    }

    /**
     * 根据自定义的状态码返回
     * 有响应数据的成功
     *
     * @param data 响应的数据
     * @param <T>  响应的数据类型
     * @return 基础的响应对象
     */
    public static <T> BaseResponceDto success(T data) {
        return new BaseResponceDto<T>(BaseStatusCode.SUCCESS, data);
    }

    /**
     * 根据http状态码返回
     *
     * @param data 响应的数据
     * @param <T>  响应的数据类型
     * @return 基础的响应对象
     */
    public static <T> BaseResponceDto successByHttpStatus(T data) {
        return new BaseResponceDto<T>(HttpStatus.OK, data);
    }

    /**
     * 没有响应数据的失败
     *
     * @param statusCode 状态码
     * @return
     */
    public static BaseResponceDto<Void> error(BaseStatusCode statusCode) {
        return new BaseResponceDto(statusCode);
    }

    /**
     * 有响应数据的失败
     *
     * @param statusCode 状态码
     * @param data       数据
     * @return
     */
    public static <T> BaseResponceDto error(BaseStatusCode statusCode, T data) {
        return new BaseResponceDto<T>(statusCode, data);
    }

    /**
     * 异常后的响应
     *
     * @param baseException 异常
     * @return
     */
    public static BaseResponceDto error(BaseException baseException) {
        return new BaseResponceDto(baseException);
    }
}

使用示例

成功响应

// 不带数据
return ReturnUtils.success();
// 带数据
return ReturnUtils.success("123456");

失败响应

// 不带数据
return ReturnUtils.error(BaseStatusCode.ERR_9999);
// 带数据
return ReturnUtils.error(BaseStatusCode.ERR_9999,"123456");

使用JsonView;规范响应对象

这个放在响应的最后说,是因为他并不属于响应结构的东西,但是他又属于响应的一部分,而且很重要;一个系统,权限是不可缺少的一部分,所谓的权限,简单的说,也就是不同的人,不同的接口,看到的数据不一样;同样是用户查询,用户列表只需要返回用户名即可,而用户详情就需要返回更多的数据;那么这种情况我们需要怎么去响应呢?定义多个响应DTO,当然这是最简单的方式;同样,我们也可以和validator中的分组一样;使用JsonView对响应的结果进行分组,使得同一个对象,在不同接口中返回不同的属性;

JsonView说明

JsonView的定义和validator中的group是类似的概念;也是基于接口,使用也和validator类似;

使用
定义顶级接口

此接口为所有JsonView接口的父类;其作用于响应的基础属性上;如下:

@Data
public class BaseResponceDto<T> {
    /**
     * 响应数据最外层的视图 也是所有响应视图的父类
     */
    public interface ResponceBaseDtoView {
    }
    
    /**
     * 状态码
     */
    @JsonView(ResponceBaseDtoView.class)
    private Integer status;
    
    //....
}

业务接口定义

如下所示:

所有视图都直接或者间接继承自ResponceBaseDtoView基础视图;否则会导致响应的BaseResponceDto对象为空json {}

简单视图只返回用户名和手机号码

详情视图,返回所有的属性

	/**
	 * 用户响应请求
	 */
	@Data
	public class UserResponceDto {
    // 简单视图,只返回最基数的属性
    public interface UserResponceSimpleDtoView extends BaseResponceDto.ResponceBaseDtoView {};

    // 详情视图,返回详细的属性参数
    public interface UserResponceDetailDtoView extends UserResponceSimpleDtoView {};


    /**
     * 用户名
     */
    @JsonView(UserResponceSimpleDtoView.class)
    public String userName;

    /**
     * 年龄
     */
    @JsonView(UserResponceDetailDtoView.class)
    private Integer age;

    /**
     * 性别
     */
    @JsonView(UserResponceDetailDtoView.class)
    private Integer gender;

    /**
     * 邮箱
     */
    @JsonView(UserResponceDetailDtoView.class)
    private String email;

    /**
     * 电话号码
     */
    @JsonView(UserResponceSimpleDtoView.class)
    private String phoneNum;

    /**
     * 修改人
     */
    @JsonView(UserResponceDetailDtoView.class)
    private String optUser;
}

Controller指定视图

	@GetMapping("getSimple")
	// 指定JsonView的简单视图
	@JsonView(UserResponceDto.UserResponceSimpleDtoView.class)
	public BaseResponceDto getSimple() {
	    UserResponceDto userResponceDto = new UserResponceDto();
	    userResponceDto.setUserName("张三");
	    userResponceDto.setAge(10);
	    userResponceDto.setEmail("zhangsan@qq.com");
	    userResponceDto.setGender(0);
	    userResponceDto.setPhoneNum("13888888888");
	    userResponceDto.setOptUser("admin");
	
	    return ReturnUtils.success(userResponceDto);
	}
	
	@GetMapping("getDetail")
	// 指定详细视图
	@JsonView(UserResponceDto.UserResponceDetailDtoView.class)
	public BaseResponceDto getDetail() {
	    UserResponceDto userResponceDto = new UserResponceDto();
	    // 内容和上面一样
	
	    return ReturnUtils.success(userResponceDto);
	}

在这里插入图片描述
即可看到,两个接口,根据我们的指定,返回了不同的属性值。

如何统一返回包装对象

需求
上面定义的代码;为了保证数据的响应格式是BaseResponceDto格式的;因此Controller所有的方法都是返回了这个对象;目的也是为了保证响应格式的一致性;但是,我们不返回这个对象可以吗?完全是可以的,而且也不会有任何报错;但是,这样却打破了我们定义的规则,导致响应的结构不一致了。

能够在一个统一的地方去配置返回;保证响应的都是BaseResponceDto;而controller只需要返回数据即可;如下:

@GetMapping("getSimple")
@JsonView(UserResponceDto.UserResponceSimpleDtoView.class)
public UserResponceDto getSimple() {
    UserResponceDto userResponceDto = new UserResponceDto();
    // ....
    return userResponceDto;
}

响应自动包装外层结构

{
    "status": 200,
    "msg": "成功!",
    "data": {
        "userName": "张三",
        "phoneNum": "13888888888"
    }
}

restControllerAdvice拦截并重构响应

  • supports
    判断是否指定了特定的注解

  • beforeBodyWrite
    在写入body之前,会调用这个方法;因此,就可以在这里将响应对象给改掉

if (body instanceof BaseResponceDto) {
    return body;
}
// 否则的话就直接返回
return ReturnUtils.success(body);

@RestControllerAdvice(basePackages = "com.zhubayi")

如果以jar的方式加入;这里务必要指明一下当前类所处的路径;否则可能因为没有扫描到导致加载失败。

创建继承自@ResponseBody的注解,用来添加到方法或者类上;当响应写入body之间拦截结果

/**
 * @title: ResponseDataBody
 * @projectName springcloud-mbb
 * @description: TODO 规范响应数据的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface ResponseDataBody {
}

定义ResponseDataBodyAdvice拦截添加了@ResponseDataBody注解的响应

@RestControllerAdvice(basePackages = "com.lupf")
@Slf4j
public class ResponseDataBodyAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 得到自定义的注解
     */
    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseDataBody.class;

    /**
     * 判断类或者方法是否使用了 @ResponseDataBody
     * 这里将注解添加在BaseController上面;以为着只要继承了BaseController的Controller都使用了该注解
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 当类或者方法使用了 @ResponseDataBody 也就是上面的方法返回的true 就会调用这个方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 防止重复包裹的问题出现 如果已经是要返回的基础对象了 就直接返回
        if (body instanceof BaseResponceDto) {
            return body;
        }
        // 否则的话就直接返回
        return ReturnUtils.success(body);
    }
}

封装的优点

  • 规范响应:
    避免因为代码错误或者响应错误导致报文格式异常;这样写,可以包装返回的对象必定是BaseResponceDto

  • 减少冗余代码
    Controller中直接返回数据对象;封装统一去进行。

如何优雅的全局捕获异常

问题点

当我们的业务逻辑中出现了异常;比如要修改某个用户,请求的数据也没有问题;结果在修改直接去查找用户的时候,发现已经没有这个用户了;那么一般就抛出一个用户不存在的异常,如果不对异常进行处理的话,前端就只会收到一个400的错误;而我们希望的是这样:

{
    "status": 2001,
    "msg": "用户不存在!"
}

通过ExceptionHandler捕获全局异常

使用RestControllerAdvice注解

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Object HttpMessageNotReadableExceptionHandler(HttpMessageNotReadableException httpMessageNotReadableException){
        log.error("捕获请求参数读取异常....",httpMessageNotReadableException);
        // 前端未传递参数 导致读取参数异常
        return ReturnUtils.error(BaseStatusCode.ERR_1000);
    }

    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Object bindExceptionHandler(BindException bindException){
        log.error("捕获请求参数校验异常....",bindException);
        // 获取到所有的校验失败的属性
        List<FieldError> fieldErrors = bindException.getFieldErrors();

        // 实例化一个用于装参数错误的list
        List<ParamErrDto> paramErrDtos = new ArrayList<>();
        for (FieldError fieldError : fieldErrors) {
            // 那段字段名
            String field = fieldError.getField();
            // 拿到异常的描述
            String defaultMessage = fieldError.getDefaultMessage();
            log.info("field:{} msg:{}", field, defaultMessage);
            // 添加到list中去
            paramErrDtos.add(new ParamErrDto(field, defaultMessage));
        }

        // 返回前端参数错误 并告诉前端那些字段不对 具体描述是什么
        return ReturnUtils.error(BaseStatusCode.ERR_1000, paramErrDtos);
    }

    @ExceptionHandler(BaseException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Object baseExceptionHandler(BaseException baseException){
        log.error("捕获到业务异常!",baseException);
        // 基础的业务异常
        return ReturnUtils.error(baseException);
    }

    /**
     * 通过ExceptionHandler 捕获controller未捕获到的异常,给用户一个友好的返回
     *
     * @param ex 异常信息
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Object exceptionHandler(Exception ex) {
        log.error("exceptionHandler....");
        // 所有的  自定义的、已知的异常全部都没有匹配上
        // 直接响应响应一个未知错误的提醒
        return ReturnUtils.error(BaseStatusCode.ERR_9999);
    }
}
  • @ExceptionHandler(HttpMessageNotReadableException.class)

    当body没有传参数时,会触发这个异常,并返回参数错误的状态码

  • @ExceptionHandler(BindException.class)

    当validator校验失败之后,会触发这个异常;因此这里将所有不符合规范的传参整理成列表返回。

  • @ExceptionHandler(BaseException.class)

    自定义业务异常;直接将异常对象转换为响应对象;返回给前端

  • @ExceptionHandler(Exception.class)

    用来处理那些没有特定处理的异常;然后由这里拦截之后,统一返回未知错误;

<think>好的,用户现在想了解SDCMD8命令的响应格式。我需要先回忆一下SD卡协议的相关内容,尤其是CMD8的具体细节。根据之前提供的引用,用户提到CMD8是用来查询SD卡版本的,返回0x01表示支持版本2,0x05表示版本1。但用户现在需要更详细的响应格式解析,而不仅仅是状态码。 首先,我需要确认SD规范中CMD8的响应结构。通常,SD命令的响应有不同的类型,比如R1、R3等。CMD8的响应应该是R7类型,包含多个字段。需要分解各个位的含义,比如电压范围、检查模式、命令版本等。 接下来,需要检查之前的引用内容是否有相关线索。引用[1]提到CMD8返回的状态,但只说了0x01和0x05,这可能指的是响应中的部分位,比如状态码部分。但完整的R7响应应该更长,比如32位或更多。用户可能需要知道每个位的具体作用,比如高8位是状态,接下来的4位是电压接受情况,后面的位可能是检查模式或其他信息。 另外,要注意用户之前的问题中提到的OCR寄存器和电压协商,这可能与CMD8中的电压参数有关。CMD8的参数中包含主机提供的电压信息,响应中的电压部分需要确认是否匹配。比如,主机发送的电压参数如果卡支持,则会在响应中返回相同的值,否则可能不支持。 还需要考虑用户可能的实际应用场景,比如在初始化过程中如何处理CMD8的响应,如何根据响应判断卡的类型和版本,以及后续的初始化步骤是否需要调整。例如,如果卡不支持主机请求的电压,可能需要降级处理或报错。 另外,用户提供的引用[3]提到了CMD17的读取流程,这可能与数据传输相关,但当前问题集中在CMD8的响应格式,暂时不需要涉及数据读取部分。但需要注意,不同版本的SD卡在后续命令的使用上可能有差异,比如版本2可能支持更高的容量或更快的传输速率。 总结来说,需要详细说明CMD8响应的结构,包括状态码、电压接受字段、检查模式等,并结合示例代码或位域分解来解释每个部分的作用。同时,要指出如何处理不同的响应结果,比如版本判断和电压兼容性检查,以及可能出现的错误情况。确保引用之前的引用内容,特别是引用[1]中提到的版本判断部分,并补充更详细的技术细节,帮助用户全面理解响应格式。</think>### SDCMD8 命令响应格式详解 SD 卡 **CMD8**(SEND_IF_COND)命令用于**检测 SD 卡版本兼容性与电气参数匹配性**,其响应格式为 **R7 类型**(共 5 字节,32 位),具体结构如下: --- #### **响应数据位域解析**(高位在前) | 位域范围 | 字段名称 | 功能描述 | 示例值 | |----------|-------------------|--------------------------------------------------------------------------|--------------| | **31-24**| **Command Version** | 命令版本标识(固定为 `0x01`,表示支持 CMD8) | `00000001` | | **23-20**| **Voltage Accepted** | 电压接受标志(4 位掩码,表示卡支持的电压范围是否匹配主机请求) | `0001` | | **19-16**| **Check Pattern** | 检查模式验证值(固定为 `0x01`,需与主机发送的 CMD8 参数低 4 位一致) | `0001` | | **15-0** | **Reserved** | 保留位(固定为 `0x0000`) | `0000000000000000` | --- #### **关键字段详解** 1. **Command Version(bit31-24)** - 固定为 `0x01`,表示卡支持 **SD 规范版本 2.0 或更高**。若卡不支持 CMD8(如版本 1.0 卡),将返回错误代码 `0x05`(通过响应类型 R1 的 bit5 标识)[^1]。 2. **Voltage Accepted(bit23-20)** - 主机通过 CMD8 参数的高 12 位(bit19-8)指定支持的电压范围(例如 `0x1` 表示 2.7-3.6V),卡通过此字段返回是否接受: - **`0001`**:接受主机请求的电压 - **`0000`**:电压不兼容(需重新协商或终止初始化)[^1] 3. **Check Pattern(bit19-16)** - 主机在 CMD8 参数的低 4 位(bit3-0)设置任意值(例如 `0x01`),卡需原样返回此值以验证通信完整性。若不匹配,表示物理层通信异常。 --- #### **典型响应示例** 1. **版本 2.0+ 卡响应**(电压兼容): ```c 0x01 0x10 0x01 0x00 0x00 // 十六进制表示:0x0110010000 ``` - 解析: - Command Version = `0x01`(版本 2.0+) - Voltage Accepted = `0001`(接受 2.7-3.6V) - Check Pattern = `0001`(与主机参数匹配) 2. **版本 1.0 卡响应**(不支持 CMD8): ```c 0x05 // R1 响应(bit5=1 表示非法命令) ``` --- #### **应用场景与处理逻辑** 1. **初始化流程中的 CMD8 调用** ```c // 主机发送 CMD8(参数示例:电压范围 2.7-3.6V,检查模式 0x01) send_command(CMD8, 0x00000100 | 0x01); // 参数 = 电压掩码 << 8 | 检查模式 // 解析 R7 响应 if (response[0] == 0x01) { // 版本 2.0+ 卡,继续初始化(发送 ACMD41) } else if (response[0] == 0x05) { // 版本 1.0 卡,跳过 CMD8 直接使用 ACMD41[^1] } ``` 2. **电压兼容性错误处理** - 若 `Voltage Accepted` 为 `0000`,需重新协商电压或终止操作(如切换主机供电模块)。 --- ### 相关引用说明 - CMD8 的版本检测机制是 SD 卡初始化流程的关键步骤,直接影响后续命令(如 ACMD41)的兼容性。 - 宽电压卡(如 UHS-II)可能通过扩展位域支持更多电压范围,需结合 OCR 寄存器进一步验证[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值