在智能硬件开发中,OTA(Over-The-Air)升级是非常常见的需求。通过OTA升级,开发者可以远程将新固件推送到设备上,修复Bug或增加功能,而无需用户手动连接电脑刷机。
在iOS的App中,我们常常通过CoreBluetooth 框架,配合设备协议,完成OTA升级。本文将基于Objective-C,讲解iOS蓝牙OTA升级的流程与核心代码。
OTA 升级大致分为以下几个步骤:
- 建立蓝牙连接:扫描并连接外设。
- 发现 OTA 服务与特征值:通过设备提供的 OTA Service/Characteristic 通道写入数据。
- 发送固件数据包:将固件(bin/hex 文件)分片写入外设。
- 外设校验数据:外设对接收的数据进行校验。
- 外设重启并运行新固件:升级完成。
第一步:蓝牙连接与发现 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升级方面提供小小帮助,如有说错的地方敬请指正,谢谢!