nRF芯片设备DFU升级

前言

  • 这里主要参考这个项目:iOS-nRF-Toolbox(这个是Swift版的),它是Nordic公司开发的测试工程,包含一整套nRF设备的测试解决方案

  • OC版的可以参考这个项目:nRF-Toolbox-master 密码: w7kd

nRF-Toolbox项目包含BGM,HRM,HTM,DFU等多个模块,我们只用到了其中的DFU升级模块。打开项目,在对应的NORDFUViewController.swift中我们能够看到有三个引用库:
import UIKit,import CoreBluetooth,import iOSDFULibrary,这里的iOSDFULibrary就是DFU升级的库,也是解决DFU升级最重要的组件。我们只要把这个库集成到我们的项目中,就能够完成nRF设备的DFU升级了。

下面是对于OC引用DFU升级的操作步骤和我遇到的问题(使用的是蓝牙连接升级)。

集成iOSDFULibrary

我这里使用的是直接把库导入到项目里面。(最好还是用cocoaPod导入,不过好像会出现很多问题,我这里还没试过)

编译出framework然后把库导入项目
  • 这一步是最关键也是最容易出问题的,这个库也是由Swift写成的,直接打开项目,然后选择iOSDFULibrary进行编译
nRF芯片设备DFU升级_第1张图片
编译iOSDFULibrary.png

最后生成两个framework:

  • iOSDFULibrary.framework
  • Zip.framework

编译完成后,这两个文件在项目下面:pod-->products文件夹,右键在find里就可找到,直接放到你项目里面去就OK了。

导入到项目里面去了之后,更改一下项目配置:

nRF芯片设备DFU升级_第2张图片
在设置里面导入库文件.png
nRF芯片设备DFU升级_第3张图片
Swift标准.png

把RunPath这里设置加一项:@executable_path/Frameworks


nRF芯片设备DFU升级_第4张图片
runpath.png

这时候在项目里面用头文件就可以使用库了:

#import 

运行程序,如果报错如下:

dyld: Library not loaded: @rpath/libswiftCore.dylib
Referenced from: /private/var/containers/Bundle/Application/02516D79-BB30-4278-81B8-3F86BF2AE2A7/XingtelBLE.app/Frameworks/iOSDFULibrary.framework/iOSDFULibrary
Reason: image not found 

[dyld: Library not loaded: @rpath/libswiftCore.dylib报错解决]

nRF芯片设备DFU升级_第5张图片
embed

这个默认设置是NO,设置为YES就可以了!!!

还有一种错误就是运行的时候,一直崩在这个地方

//这里一直崩溃,Message from debugger: Terminated due to signal 9
//如果出现这种错误,你用他们demo跑的时候,有可能也会出现这个问题
//这里报错就是编译的库有问题,重新换库,使用carthage可以打包的库可以解决这个问题
DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:url];

那一般情况是你编译的包有问题。下面提供2种重新打包的方法,可以尝试一下:

  • 1 直接是用作者提供的解决方法

1、On your mac please install carthage (instructions)
2、Create a file named cartfile anywhere on your computer
3、add the following content to the file:

github "NordicSemiconductor/IOS-Pods-DFU-Library" ~> 2.1.2
github "marmelroy/Zip" ~> 0.6

1、Open a new terminal and cd to the directory where the file is
2、Enter the command carthage update --platform iOS
3、Carthage will now take care of building your frameworks, the produced .framework files will be found in a newly created directory called Carthage/Build/iOS,copy over iOSDFULibrary.framework and Zip.framework to your project and you are good to go.

注意

a. carthage是一种和cocoapods相似的的类库管理工具,如果不会使用的话可以参照carthage的使用,将framework文件导入到自己的项目。

b. 用这个方法导出的库,在你的电脑上跑是正确的,在你同事的电脑上跑可能就不行了~~~

  • 2在使用我上面的用Demo代码编译的方法的时候,做一些针对性的改变。
nRF芯片设备DFU升级_第6张图片
调整打包设置.png

附加一些另外的解决方法:

  • 把target -- > General -- > 下面的Linked Frameworks and Libraries下面的IOSDFULibrary和Zip两个库的右边的status改成options试试。
  • 把RunPath里面的@executable_path/Frameworks删了再重新添加的试试

打包上架时报ERROR IT MS-90087等问题

ERROR ITMS-90087: "Unsupported Architectures. The executable for ***.app/Frameworks/SDK.framework contains unsupported architectures '[x86_64, i386]'."
ERROR ITMS-90362: "Invalid Info.plist value. The value for the key 'MinimumOSVersion' in bundle ***.app/Frameworks/SDK.framework is invalid. The minimum value is 8.0"
ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at '***.app/Frameworks/SDK.framework/SDK' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
ERROR ITMS-90125: "The binary is invalid. The encryption info in the LC_ENCRYPTION_INFO load command is either missing or invalid, or the binary is already encrypted. This binary does not seem to have been built with Apple's linker."

解决方法,添加Run Script Phase


nRF芯片设备DFU升级_第7张图片
上架报错.png

Shell脚本内容填写如下内容,再次编译即可

 APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"

# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
    FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"

EXTRACTED_ARCHS=()

for ARCH in $ARCHS
do
echo "Extracting $ARCH from   $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done

echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"

echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged"   "$FRAMEWORK_EXECUTABLE_PATH"

done
上面的是集成IOSDFULibrary时遇到的一些问题,解决这些问题后就可以正常进行固件升级了。

下面了解一下使用这个库来进行固件升级。

步骤:

  • 一、连接蓝牙,发送指令对蓝牙设备进行控制
  • 二、发送指令查询固件版本
  • 三、收到蓝牙发回来的应答,判断是否进行版本升级
  • 四、发送蓝牙升级指令(此时蓝牙会进入Dfu模式,此时蓝牙名称会改变,设备会断开蓝牙连接,需要重新连接蓝牙)
  • 五、重新连接改名后的蓝牙,下载固件版本到本地
  • 六、使用IOSDFULibrary发送估计到蓝牙进行升级
  • 七、发送成功,固件升级成功~~~
  • 八、估计升级完成之后,蓝牙会回到正常模式,名称改成之前的名称,设备会断开蓝牙,重新扫描蓝牙连接即可
  • 九、大功告成

下面是对IOSDFULibrary的使用:

导入三个delegate:LoggerDelegateDFUServiceDelegateDFUProgressDelegate,它们的作用分别为打印状态日志,DFU升级及蓝牙连接状态,,监视DFU升级进度。

//DFU
@property (strong, nonatomic) DFUServiceController *dfuService;
@property (strong, nonatomic) DFUFirmware *selectedFirmware;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions     {
    // Override point for customization after application launch.

    //在AppDelegate里面要设置这些默认的属性,否则文件传输会失败,直接报operation not  permitted或者Sending firwmare failed
    //注意这里的 [NSNumber numberWithInt:12],如果是iPhone8或以上,如果设置为12以上就会崩溃,最大为6
    NSDictionary* defaults = [NSDictionary dictionaryWithObjects:@[@"2.3", [NSNumber numberWithInt:12], @NO] forKeys:@[@"key_diameter", @"dfu_number_of_packets", @"dfu_force_dfu"]];
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];

    return YES;
}

- (void)performDFU
{
    // To start the DFU operation the DFUServiceInitiator must be used
    DFUServiceInitiator *initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.manager target:_val_peripheral];
    //注意这里要在AppDelegate里面设置
    initiator.forceDfu = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_force_dfu"] boolValue];
    //注意这里要在AppDelegate里面设置
    initiator.packetReceiptNotificationParameter = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_number_of_packets"] intValue];
    initiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = YES;
    initiator.logger = self;
    initiator.delegate = self;
    initiator.progressDelegate = self;
    //下载网络文件升级
    NSString *firmwaresPath = [[verManager getFirmwaresPath] stringByAppendingPathComponent:@"firmWareVersion.zip"];
    NSURL *url = [NSURL fileURLWithPath:firmwaresPath];
    _selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:url];
    //本地zip文件升级
    //NSURL *filePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"app_only.zip" ofType:nil]];
    //_selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:filePath];
    //开始发送文件  如果这里没有文件会报错:Firmare  not  specified
    _dfuService = [[initiator withFirmware:_selectedFirmware] start];     
}


#pragma mark - LoggerDelegate
-(void)logWith:(enum LogLevel)level message:(NSString *)message
{
    NSLog(@"%ld: %@", (long) level, message);
}


#pragma mark - DFUServiceDelegate
//监听状态
- (void)dfuStateDidChangeTo:(enum DFUState)state
{
    switch (state) {
        case DFUStateConnecting:
             NSLog(@"Connecting...");
             break;
        case DFUStateStarting:
             NSLog(@"Starting...");
             break;
        case DFUStateEnablingDfuMode:
             NSLog(@"EnablingDfuMode...");
             break;
        case DFUStateUploading:
             NSLog(@"Uploading...");
             break;
        case DFUStateValidating:
             NSLog(@"Validating...");
             break;
        case DFUStateDisconnecting:
             NSLog(@"Disconnecting...");
             break;
        case DFUStateCompleted:
        {
            //升级成功   Upload complete
            //重新连接蓝牙
            self.manager = nil;
            //初始化并设置委托和线程队列
            //重新给蓝牙连接Manager赋值,不然有可能升级后连接不上蓝牙
            self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:@{CBCentralManagerOptionShowPowerAlertKey:[NSNumber numberWithBool:NO]}];
            break;
        }
        case DFUStateAborted:
            NSLog(@"Aborted...");
            break;
        default:
            break;
    }
}

- (void)dfuError:(enum DFUError)error didOccurWithMessage:(NSString * _Nonnull)message
{
    NSLog(@"Error %ld: %@", (long) error, message);
}



#pragma mark - DFUProgressDelegate
//进度
- (void)dfuProgressDidChangeFor:(NSInteger)part outOf:(NSInteger)totalParts to:(NSInteger)progresscurrentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond;
{
    [SVProgressHUD showProgress:(float) progress / 100.0f status:[NSString stringWithFormat:@"升级固件中%ld%%",(long)progress]];
//    progress.progress = (float) percentage / 100.0f;
//    progressLabel.text = [NSString stringWithFormat:@"%ld%% (%ld/%ld)", (long) percentage, (long) part, (long) totalParts];
}

补充:

后面在测试的时候发现:1. 第一次运行程序,升级的时候会出现崩溃。崩溃的位置:

nRF芯片设备DFU升级_第8张图片
第一次运行升级崩溃在库里.png

解决方案:
崩溃的原因是firmwareRanges是nil,在前面添加代码进行判断就行了:

if firmwareRanges == nil {
    // Split firmware into smaller object of at most maxLen bytes, if firmware is bigger than maxLen
    return
}
nRF芯片设备DFU升级_第9张图片
解决崩溃.png

注意

改代码的时候,不能直接改项目里面崩溃的那个位置的代码,那里改了没用,要改在编译成库的地方的代码,然后重新编译,导出库文件,把库文件重新加到项目里面。如果用的是carthage,使用流程如下:


nRF芯片设备DFU升级_第10张图片
打开项目
nRF芯片设备DFU升级_第11张图片
导出库文件流程.png

重新导入包之后要更新一下项目配置,在Build Phases里面的Embed Frameworks里面把IOSDFULibrary库加进了。这里不需要重新更换Zip库,只用更改IOSDFULibrary库就OK~

注意:有朋友反映升级的时候会断开连接,解决方法:
在APPDelegate文件里加上:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions     {
    // Override point for customization after application launch.

    //在AppDelegate里面要设置这些默认的属性,否则文件传输会失败,直接报operation not  permitted或者Sending firwmare failed
    NSDictionary* defaults = [NSDictionary dictionaryWithObjects:@[@"2.3", [NSNumber numberWithInt:12], @NO] forKeys:@[@"key_diameter", @"dfu_number_of_packets", @"dfu_force_dfu"]];
    [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];

    return YES;
}

有朋友@可乐超反映,写了上面的代码,在升级的时候还是会崩溃,断开连接。
原因是设置的packetReceiptNotificationParameter这个参数的值太大的原因。感谢@可乐超。
谢谢~

慢慢来,一步一个巴掌印。。。。。

你可能感兴趣的:(nRF芯片设备DFU升级)