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进行编译
编译iOSDFULibrary.png

最后生成两个framework:

  • iOSDFULibrary.framework
  • Zip.framework

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

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

在设置里面导入库文件.png
Swift标准.png

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


runpath.png

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

#import <iOSDFULibrary/iOSDFULibrary-Swift.h>

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

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报错解决]

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代码编译的方法的时候,做一些针对性的改变。
调整打包设置.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


上架报错.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. 第一次运行程序,升级的时候会出现崩溃。崩溃的位置:

第一次运行升级崩溃在库里.png

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

if firmwareRanges == nil {
    // Split firmware into smaller object of at most maxLen bytes, if firmware is bigger than maxLen
    return
}
解决崩溃.png

注意

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


打开项目
导出库文件流程.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这个参数的值太大的原因。感谢@可乐超
谢谢~

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容