iOS通过蓝牙对外设进行OTA升级

91 阅读3分钟

在智能硬件开发中,OTA(Over-The-Air)升级是非常常见的需求。通过OTA升级,开发者可以远程将新固件推送到设备上,修复Bug或增加功能,而无需用户手动连接电脑刷机。

在iOS的App中,我们常常通过CoreBluetooth 框架,配合设备协议,完成OTA升级。本文将基于Objective-C,讲解iOS蓝牙OTA升级的流程与核心代码。

OTA 升级大致分为以下几个步骤:

  1. 建立蓝牙连接:扫描并连接外设。
  2. 发现 OTA 服务与特征值:通过设备提供的 OTA Service/Characteristic 通道写入数据。
  3. 发送固件数据包:将固件(bin/hex 文件)分片写入外设。
  4. 外设校验数据:外设对接收的数据进行校验。
  5. 外设重启并运行新固件:升级完成。

第一步:蓝牙连接与发现 OTA 服务


#import <CoreBluetooth/CoreBluetooth.h>

@interface OTAViewController () <CBCentralManagerDelegate, CBPeripheralDelegate>

@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, strong) CBCharacteristic *otaCharacteristic;

@end

@implementation OTAViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}

// 蓝牙状态监听
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    if (central.state == CBManagerStatePoweredOn) {
        [self.centralManager scanForPeripheralsWithServices:nil options:nil];
    }
}

// 发现外设
- (void)centralManager:(CBCentralManager *)central 
 didDiscoverPeripheral:(CBPeripheral *)peripheral 
     advertisementData:(NSDictionary<NSString *,id> *)advertisementData 
                  RSSI:(NSNumber *)RSSI {
    NSLog(@"发现外设:%@", peripheral.name);
    self.peripheral = peripheral;
    self.peripheral.delegate = self;
    [self.centralManager stopScan];
    [self.centralManager connectPeripheral:peripheral options:nil];
}

// 连接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    [peripheral discoverServices:nil];
}

// 发现服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    for (CBService *service in peripheral.services) {
        NSLog(@"发现服务:%@", service.UUID.UUIDString);
        [peripheral discoverCharacteristics:nil forService:service];
    }
}

// 发现特征值
- (void)peripheral:(CBPeripheral *)peripheral 
didDiscoverCharacteristicsForService:(CBService *)service 
             error:(NSError *)error {
    for (CBCharacteristic *ch in service.characteristics) {
        NSLog(@"发现特征值:%@", ch.UUID.UUIDString);
        // 假设外设定义了 OTA 特征 UUID
        if ([ch.UUID.UUIDString isEqualToString:@"0000FFA1-0000-1000-8000-00805F9B34FB"]) {
            self.otaCharacteristic = ch;
        }
    }
}

@end

第二步:读取固件文件并分片

固件一般是 bin 文件,需要按协议分片发送,常见大小为 20 字节(受 BLE MTU 限制)。

/// 将固件文件读取为 NSData
- (NSData *)loadFirmwareData {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"firmware" ofType:@"bin"];
    NSData *firmwareData = [NSData dataWithContentsOfFile:path];
    return firmwareData;
}

/// 将固件分片
- (NSArray<NSData *> *)splitFirmwareData:(NSData *)data chunkSize:(NSInteger)size {
    NSMutableArray *packets = [NSMutableArray array];
    NSUInteger length = data.length;
    NSUInteger offset = 0;
    while (offset < length) {
        NSUInteger thisChunkSize = MIN(size, length - offset);
        NSData *chunk = [data subdataWithRange:NSMakeRange(offset, thisChunkSize)];
        [packets addObject:chunk];
        offset += thisChunkSize;
    }
    return packets;
}

第三步:写入 OTA 数据包

使用 writeValue 将固件数据包依次写入外设 OTA 特征值。推荐使用 CBCharacteristicWriteWithoutResponse,效率更高。

- (void)startOTAUpgrade {
    NSData *firmwareData = [self loadFirmwareData];
    NSArray<NSData *> *packets = [self splitFirmwareData:firmwareData chunkSize:20];
    
    __block NSInteger index = 0;
    __weak typeof(self) weakSelf = self;

    // 定时发送数据(避免外设处理不过来)
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.02 repeats:YES block:^(NSTimer * _Nonnull t) {
        if (index >= packets.count) {
            [t invalidate];
            NSLog(@"OTA 升级数据发送完成");
            return;
        }
        
        NSData *chunk = packets[index];
        [weakSelf.peripheral writeValue:chunk forCharacteristic:weakSelf.otaCharacteristic type:CBCharacteristicWriteWithoutResponse];
        index++;
    }];
}

第四步:校验与完成升级

通常外设会在接收完数据后,返回一个校验结果。开发者需要在 didUpdateValueForCharacteristic 回调中读取结果。

// 接收外设返回的升级结果
- (void)peripheral:(CBPeripheral *)peripheral 
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic 
             error:(NSError *)error {
    if (error) {
        NSLog(@"接收错误:%@", error.localizedDescription);
        return;
    }
    
    NSData *value = characteristic.value;
    NSLog(@"外设返回校验结果:%@", value);
    
    // 根据协议判断是否升级成功
    if ([value isEqualToData:[NSData dataWithBytes:"\x01" length:1]]) {
        NSLog(@"OTA 升级成功");
    } else {
        NSLog(@"OTA 升级失败");
    }
}

OTA 升级注意事项

1、分片大小

默认 BLE 单包为 20 字节,但 iOS 10 之后支持协商 MTU,可扩展至 ~185 字节。根据外设协议设置最佳大小。

2、发送节奏

不要一次性发送太快,容易导致外设缓冲区溢出。可以采用定时器 + WriteWithoutResponse的方式。

3、升级安全性

加入CRC/XOR 校验确保数据完整性。升级前验证固件版本,避免降级或重复升级。

4、异常处理

升级失败时,提供重试机制。 保证升级中断后,设备可以回退到 BootLoader 模式。

最后~

希望本文章能帮到你在OTA升级方面提供小小帮助,如有说错的地方敬请指正,谢谢!