CRC

循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。

应用场景

项目中涉及到OTA 蓝牙升级模块,由于BLE 每次发包只能发20字节,多余的需要自己分包处理。我们的上传的文件有100k

所以 CRC使用非常有必要。

内部协议

协议
SOH 包序号 序号补码 数据区 CRC
Byte 1 Byte 2 Byte 3 Byte 4-18 Byte 19-20

客户端与服务端的通信:

  1. 客户端发送OTA 申请服务端进入文件升级模式
  2. 服务端返回字符C 允许文件上传
  3. 客户端发送第一个包
  4. 服务端返回ACK 表示已经 CRC 校验成功
  5. 服务端返回NAK 表示 CRC 校验失败
  6. 客户端收到ACK 继续传递下一个包
  7. 客户端收到NAK 重新传输失败的包
  8. 在客户端3秒后没有收到 服务端的ACK 或NAK 响应 默认为超时 需要重新发送包
  9. 带客户端计算检测所有包发送完毕后,发送EOT 告诉服务端文件已上传完毕
  10. 服务端收到EOT后 开始CRC校验整个文件,校验通过后返回OK
  11. 客户端收到ok 后 提示用户文件升级成功 退出OTA 模式。

通讯过程

java 业务代码

发送OTA字符:

  1. 打开Android 内部的资源文件 获取文件流 计算传递文件的大小。
  2. 添加包头 OTA 转字节
  3. 添加4字节大小的文件长度
  4. 添加4字节 文件长度反码
  5. 添加2字节 crc 值(文件为参数计算)
  6. 添加2字节的 crc 反码

下面是发送OTA 的代码

    /*** 开始联机 OTA 初始化 获取文件信息属性 更新progress.*/private void updateUIandSendOTAcomend() {try {mInputStream = getActivity().getResources().getAssets().open(filePath == null ? Constance.odd_filename : filePath);sendInputStream = getActivity().getResources().getAssets().open(filePath == null ? Constance.odd_filename : filePath);mLength = mInputStream.available();byte[] totle_bins = readBytes(mInputStream);mPb_update.setMax(mLength);String header_ota = GattBluetoothType.OTA.BLE_RESPOND_OTA;mBytes.clear();mBytes.add(header_ota.getBytes());    //OTA 字节包int filesize = mLength;Log.i(TAG, "updateUIandSendOTAcomend: totlesize=" + mLength);byte[] filesize_byte = intToByteArray1(filesize);mBytes.add(filesize_byte);            //文件大小int filesize_contrary = ~(filesize);byte[] filesize_complement = intToByteArray1(filesize_contrary);mBytes.add(filesize_complement);        //文件大小反码byte[] crc16_rerify = CodecUtil.crc16Bytes(totle_bins);    //crcbyte[] crc16_complement = {(byte) ~crc16_rerify[0], (byte) ~crc16_rerify[1]};mBytes.add(crc16_rerify);                //CRC mBytes.add(crc16_complement);            //CRC 反码mHead_content_bytes = sysCopy(mBytes);mPb_update.setProgress(0);writeData(mHead_content_bytes);           //写入BLEint size = mLength / 1024;tvAPkSize.setText("apk :" + size + "k");tvProgress.setText("0%");Log.i(TAG, "updateUIandSendOTAcomend: ");/* for (byte[] aByte : mBytes) {Log.i(TAG, "updateUIandSendOTAcomend: 长度="+aByte.length+" tostring= "+DataSwitchUtils.bytesToHexString(aByte));}*/} catch (IOException e) {e.printStackTrace();}}

发送文件:

由于包头包尾 已经占据了5个字节(包头01,包序号,包序号反码,crc 2个字节),所以每次发包内容只是从文件中读取15个字节,100k 计算估计1万次 10分钟左右。(后面再说优化方案4分钟)

bytenum 是15 ,for循环是检测长度不够15的最后一包,没有内容字节数组做0xff处理。例如最后一包只剩下06一个字节了,那15的数组里面的内容就是06 FF FF FF FF FF FF FF FF FF FF FF FF FF FF。如果包序号是00 反码就是11,比如CRC 计算结果为C507 
最后一包:01 00 11 06 FF FF FF FF FF FF FF FF FF FF FF FF FF FF C5 07

下面是发送文件的方法

 private boolean setUpdatePackageACK(boolean interruptUPGRADE) {try {byte[] send_package = new byte[byteNums];int n = sendInputStream.read(send_package, 0, byteNums);if (n > 0) {if ((mLength - counter) < byteNums) {    //最后一包 空至0XFFint num = mLength - counter;for (int i = 0; i < send_package.length; i++) {if (i >= num) {send_package[i] = (byte) 0xff;}}}data = send_package.length;byte[] header_byte = {0x01};byte[] order = null;if (interruptUPGRADE) {order = new byte[]{order_index--};} else {order = new byte[]{order_index++};}Log.d(TAG, "--------------order_index = " + order_index);byte[] complement = {(byte) ~order[0]};byte[] crc16_rerify = CodecUtil.crc16Bytes(send_package);mBytes.clear();mBytes.add(header_byte);mBytes.add(order);mBytes.add(complement);mBytes.add(send_package);mBytes.add(crc16_rerify);content_bytes = null;content_bytes = sysCopy(mBytes);writeData(content_bytes);mPb_update.setProgress(counter);Log.i(TAG, "setUpdatePackageACK: ");} else {Log.i(TAG, "setUpdatePackageACK: false inputstream");}} catch (Exception e) {e.printStackTrace();}return false;}

后面贴出发送过程中涉及到的方法代码:

public static byte[] sysCopy(List<byte[]> srcArrays) {int len = 0;for (byte[] srcArray : srcArrays) {len += srcArray.length;}byte[] destArray = new byte[len];int destLen = 0;for (byte[] srcArray : srcArrays) {System.arraycopy(srcArray, 0, destArray, destLen, srcArray.length);destLen += srcArray.length;}return destArray;}// int 转字节public static byte[] intToByteArray1(int i) {byte[] result = new byte[4];result[0] = (byte) ((i >> 24) & 0xFF);result[1] = (byte) ((i >> 16) & 0xFF);result[2] = (byte) ((i >> 8) & 0xFF);result[3] = (byte) (i & 0xFF);return result;}// 流转换为字节数组的方法public byte[] readBytes(InputStream in) throws IOException {byte[] temp = new byte[in.available()];byte[] result = new byte[0];int size = 0;while ((size = in.read(temp)) != -1) {byte[] readBytes = new byte[size];System.arraycopy(temp, 0, readBytes, 0, size);result = Tools.mergeArray(result, readBytes);}return result;}

后面贴出的事 CRC16 计算代码

package com.rrioo.sateliteone.util;
/*** @version :2015年1月28日 下午2:03:54**          类说明 :byte\short\crc转换*/
public class CodecUtil {static CRC16 crc16 = new CRC16();private CodecUtil() {}public static byte[] short2bytes(short s) {byte[] bytes = new byte[2];for (int i = 1; i >= 0; i--) {bytes[i] = (byte)(s % 256);s >>= 8;}return bytes;}public static short bytes2short(byte[] bytes) {short s = (short)(bytes[1] & 0xFF);s |= (bytes[0] << 8) & 0xFF00;return s;}/** 获取crc校验的byte形式*/public static byte[] crc16Bytes(byte[] data) {return short2bytes(crc16Short(data));}/** 获取crc校验的short形式*/public static short crc16Short(byte[] data) {return crc16.getCrc(data);}
}
package com.rrioo.sateliteone.util;/*** @version :2015年1月28日 下午2:03:54**          类说明 :获取crc校验码*/
public class CRC16 {private short[] crcTable = new short[256];private int gPloy = 0x1021; // 生成多项式public CRC16() {computeCrcTable();}private short getCrcOfByte(int aByte) {int value = aByte << 8;for (int count = 7; count >= 0; count--) {if ((value & 0x8000) != 0) { // 高第16位为1,可以按位异或value = (value << 1) ^ gPloy;} else {value = value << 1; // 首位为0,左移}}value = value & 0xFFFF; // 取低16位的值return (short)value;}/** 生成0 - 255对应的CRC16校验码*/private void computeCrcTable() {for (int i = 0; i < 256; i++) {crcTable[i] = getCrcOfByte(i);}}public short getCrc(byte[] data) {int crc = 0;int length = data.length;for (int i = 0; i < length; i++) {crc = ((crc & 0xFF) << 8) ^ crcTable[(((crc & 0xFF00) >> 8) ^ data[i]) & 0xFF];}crc = crc & 0xFFFF;return (short)crc;}
}

优化方案:

BLE空中传输20字节是不可变的,致使你打开了最大传输字节的MTU 也就是23个字节的样子。

我的做法是 减少ACK 的校验。没发送8个20 字节包后再 ACK 校验一次 ,即一次可以发送文件内容为155个字节。服务端做好丢包检测就好,一旦发现1S之内没有收到客户端的包 就扔掉所有数据。
列如:客户端发送160个字节 结果有20 字节丢包了,服务端只检测到140个字节,并不反回ACK ,移动端检测ACk 超时重发2秒之后。此时服务端已经扔掉了不完整的包,清楚buf 继续等待下一次160字节的到来。

蓝牙BLE OTA 升级 CRC校验相关推荐

  1. iOS蓝牙开发(三):iOS中蓝牙模块OTA升级(YModem协议)

    上一篇简单介绍了蓝牙4.0的iOS实现代码,详细的东西大家可以去github上搜babyBluetooth,里面有一些学习资料,接下来分享的是OTA升级的东西,我们假定看这篇文章的时候,关于iOS和外 ...

  2. android ota 版本校验,OTA升级签名校验简析

    1. 概要 如果进行过OTA升级的开发者,都或多或少有这样的疑问,如何确定该OTA升级包是可以信任的呢?这其中其实涉及到一个签名验证的流程. 2. 签名生成 在生成正规的固件时,一般会运行生成新key ...

  3. 蓝牙模块--OTA升级

    蓝牙固件升级模块:OAT升级又称空中升级.DFU 升级, 这里使用的是Nordic Semiconductor公司开源提供的第三方升级库:https://github.com/NordicSemico ...

  4. android ble oad,android ble OTA升级(Ti OAD 方案)

    1.概念 硬件存储中可存放两个镜像:镜像A和镜像B. 关于双镜像,目前有两种做法: 2.方案一: 一个镜像作为 Load 镜像,一个作为功能. 将镜像B作为主要镜像,而镜像A只是作为升级到镜像B的桥梁 ...

  5. 如何实现蓝牙空中升级BLE OTA

    如何实现BLE OTA?什么叫DFU?如何通过UART实现固件升级?又如何通过USB实现固件升级?怎么保证升级的安全性?什么叫双备份(dual bank)DFU?什么叫单备份(single bank) ...

  6. BLE蓝牙广播包的比特流处理之白噪化和CRC校验

    目标: 1. 通过蓝牙协议了解BLE蓝牙的广播帧结构 2.了解蓝牙比特流处理流程 3.使用matlab重现白噪化和CRC校验 1.蓝牙的广播帧结构: 通过蓝牙BLE协议我们可以看到,蓝牙的广播帧结构如 ...

  7. 蓝牙BLE芯片PHY6222之OTA

    蓝牙BLE芯片PHY6222之OTA 什么是OTA 将OTA驱动移植到应用代码 一.SLB移植 用PhyPlusKit烧录支持SLB的文件 支持SLB的OTA bin文件的制作 使用APP进行SLB升 ...

  8. EFR32如何在应用程序中通过BLE 进行OTA升级

    Bluetooth SDK里的soc-empty例程里面包含了一个AppLoader,可以用OTA_DFU(Over The Air -Device Firmware Upgrade)的方式升级应用程 ...

  9. CRC校验原理及STM32 IAP在线升级程序

    CRC校验原理: 什么是CRC校验? CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能,对数据 ...

最新文章

  1. linux里运行windows,在Linux上运行Windows应用程序
  2. 修改服务器里的端口,怎么修改windows服务器登陆端口号
  3. 如何使用composer从Laravel中删除包?
  4. 抢人饭碗了!推荐一款全自动的机器学习建模神器PyCaret
  5. 【Django】基于Django架构网站代码的目录结构---转载
  6. 数据结构----单源最短路径Dijkstra
  7. 注解默认继承_默认方法和多重继承
  8. Express接口综合案例(创建项目、配置常用中间件、路由设计、提取控制器模块、配置错误统一处理中间件、用户注册的数据验证,密码加密)
  9. 常用JavaScript函数 47 - 58(自我总结)
  10. FireUIPagedScrollView
  11. 【报告分享】新零售专题报告:从直播电商的春秋战国,看mcn的进阶之道.pdf
  12. HTML中IE版本条件注释整理
  13. 解决“在上下文中找不到 owin.Environment 项”
  14. 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer
  15. 红警2补丁和联机网络配置
  16. gerrit 用法 topic
  17. tomcat jquery mysql_Docker 搭建 Tomcat + Mysql
  18. freeMarker导出word带图片
  19. Python数据提取-lxml模块
  20. SQL注入闭合方式及万能密码

热门文章

  1. android 7.1 屏蔽按压两次电源键(KEYCODE_POWER)打开相机
  2. spi: 增加slave mode支持
  3. EIP-712签名介绍以及使用hardhat实现
  4. noobs和linux区别,[原创翻译]NOOBS(树莓派官方文档)
  5. map遍历的4种方式
  6. 3.17服务器维护,3月17日服务器例行维护公告(已完成)
  7. 动圈耳机振膜_动圈式耳机振膜技术
  8. 如何开始虚拟现实(VR)开发?
  9. 毒霸网址大全的彻底删除
  10. 《中国古代文学I(唐前)》