文章目录
- 一、前言
- 1.WCH 蓝牙空中升级(BLE OTA)概述
- 2. WCH BLE SDK DFU 工作原理(方式一)
- 二、移植程序
- 1.找到BackUpgrade_OTA例程
- 2.添加文件到工程
- 2.1 添加文件
- 2.2 如何添加
- 3.修改APP工程
- 3.1 修改peripheral_main.c文件
- 3.2 修改peripheral.c文件
- 4.修改Link.Ld文件
- 三、OTA升级
- 1.合并文件
- 2.下载bin文件
- 3.APP升级程序
- 3.1 OTA升级工具
- 3.2 搜索蓝牙,连接
- 3.3 选择升级的固件
- 3.4 升级过程
- 3.5 升级完成
- 3.6 升级结果
- 四、总结
一、前言
1.WCH 蓝牙空中升级(BLE OTA)概述
DFU(Device Firmware Update)设备固件升级,OTA(Over The Air)是实现 DFU 的一种方式,OTA 的全称应该是 OTA DFU,即通过空中无线方式实现设备固件升级。OTA 也可以称为 FOTA,即 Firmware OTA。WCH BLE 系列芯片除了可以通过无线方式(OTA)-方式一/二 DFU 进行升级,也可以通过有线方式-方式三 DFU 进行升级,比如通过内置 BOOT(UART/USB)来升级设备固件。
不管采用 OTA 方式还是有线通信方式,DFU 包括后台式(background)和非后台式两种模式。后台式 DFU-类似方式一 DFU,又称静默式 DFU(Silent DFU),即新固件下载属于应用程序功能的一部分,在新固件下载过程中,应用可以正常使用,下载完成后,系统完成执行新固件的操作,至此整个升级过程结束(比如EVT/BLE/BackupUpgrade_XXX 例子)。非后台式 DFU-方式二 DFU,在升级的时候整个升级过程中 APP 正常功能都无法使用。系统需要先从应用模式跳入到 BootLoader/IAP 模式,由 BootLoader/IAP 进行新固件下载工作,直接覆盖老固件,至升级结束
(比如EVT\EXAM\BLE\OnlyUpdateApp_XXX 例子)。
2. WCH BLE SDK DFU 工作原理(方式一)
方式一上电优先运行 IAP 程序,之后跳转 APP 执行用户程序。APP 文件为独立完整的功能文件,运行时可以通过无线方式接收完整的 OTA 备份升级文件,然后软复位进入 IAP 程序,IAP 会将备份区的 OTA 升级文件拷贝到 APP应用程序区,最后跳转回升级后的 APP 应用程序运行新的固件。升级固件带 LIB 编译(LIB 约 90K)。
注意:备份升级方式,适用于 flash 资源充裕的应用,优点是 APP+LIB 都可以升级,缺点是 flash 需要对半分使用。
方式一 DFU 包含 3 个文件 JumpIAP + APP + IAP(APP 是用户功能代码,也是实现产品功能和升级功能的主体,对应 BackupUpgrade_OTA 产生的 hex 文件;IAP 是编程升级 APP 的代码,对应 BackupUpgrade_IAP 产生的 hex 文件;JumpIAP 为跳转 IAP 代码,
对应BackupUpgrade_JumpIAP 产生的 hex 文件)。
选择对应的文件,点击合并,产生一个 BIN 文件,然后使用 ISP 工具下载即可。
二、移植程序
1.找到BackUpgrade_OTA例程
根据上面原理的介绍,如果我们要在我们的APP程序上进行OTA升级,我们需要根据BackUpgrade_OTA例程来修改我们的程序。
BackUpgrade_OTA例程在如下目录里。
2.添加文件到工程
2.1 添加文件
添加ota.h,OTAprogile.c和OTAprogile.h文件到自己的APP工程中
2.2 如何添加
在需要添加的位置右键添加,选择已存在文件。
3.修改APP工程
3.1 修改peripheral_main.c文件
添加头文件
#include "OTA.h"
#include "OTAprofile.h"
添加代码
/* 记录当前的Image */
unsigned char CurrImageFlag = 0xff;
/* 用于APP判断文件有效性 */
const uint32_t Address = 0xFFFFFFFF;
__attribute__((aligned(4))) uint32_t Image_Flag __attribute__((section(".ImageFlag"))) = (uint32_t)&Address;
/* 注意:关于程序升级后flash的操作必须先执行,不开启任何中断,防止操作中断和失败 */
/*********************************************************************
* @fn ReadImageFlag
*
* @brief 读取当前的程序的Image标志,DataFlash如果为空,就默认是ImageA
*
* @return none
*/
void ReadImageFlag(void)
{
OTADataFlashInfo_t p_image_flash;
EEPROM_READ(OTA_DATAFLASH_ADD, &p_image_flash, 4);
CurrImageFlag = p_image_flash.ImageFlag;
/* 程序第一次执行,或者没有更新过,以后更新后在擦除DataFlash */
if((CurrImageFlag != IMAGE_A_FLAG) && (CurrImageFlag != IMAGE_B_FLAG))
{
CurrImageFlag = IMAGE_A_FLAG;
}
}
//在main主函数里面添加以下函数
int main(void)
{
ReadImageFlag();
}
3.2 修改peripheral.c文件
添加头文件
#include "OTA.h"
#include "OTAprofile.h"
添加代码
- 蓝牙参数
//一些蓝牙相关参数,这里就不一一介绍了
// How often to perform periodic event 多长时间执行一次周期事件
#define SBP_PERIODIC_EVT_PERIOD 1000//1600
// What is the advertising interval when device is discoverable (units of 625us, 80=50ms) 设备被发现时的广告间隔是多少(单位为625us, 80=50ms)
#define DEFAULT_ADVERTISING_INTERVAL 80
// Limited discoverable mode advertises for 30.72s, and then stops 限制可发现模式的广告时间为30.72秒,然后停止
// General discoverable mode advertises indefinitely 一般可发现模式无限广告
#define DEFAULT_DISCOVERABLE_MODE GAP_ADTYPE_FLAGS_GENERAL
// Minimum connection interval (units of 1.25ms, 6=7.5ms)最小连接间隔(以1.25ms为单位,6=7.5ms)
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL 12
// Maximum connection interval (units of 1.25ms, 100=125ms)最大连接间隔(以1.25ms为单位,100=125ms)
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL 100//100
// How often to perform read rssi event 多久执行一次读取rssi事件
#define SBP_READ_RSSI_EVT_PERIOD 32//3200
Parameter update delay 参数更新延迟
//#define SBP_PARAM_UPDATE_DELAY 6400
//
PHY update delay PHY更新延迟
//#define SBP_PHY_UPDATE_DELAY 2400
// Slave latency to use parameter update 从延迟使用参数更新
#define DEFAULT_DESIRED_SLAVE_LATENCY 5
// Supervision timeout value (units of 10ms, 100=1s) 监督超时值(单位10ms, 100=1s)
#define DEFAULT_DESIRED_CONN_TIMEOUT 1000//100
// Whether to enable automatic parameter update request when a connection is formed
#define DEFAULT_ENABLE_UPDATE_REQUEST TRUE
// Company Identifier: WCH
#define WCH_COMPANY_ID 0x07D7
#define INVALID_CONNHANDLE 0xFFFF
- OTA相关参数和函数
// OTA IAP VARIABLES
/* OTA通讯的帧 */
OTA_IAP_CMD_t iap_rec_data;
/* OTA解析结果 */
uint32_t OpParaDataLen = 0;
uint32_t OpAdd = 0;
/* flash的数据临时存储 */
__attribute__((aligned(8))) uint8_t block_buf[16];
/* Image跳转函数地址定义 */
typedef int (*pImageTaskFn)(void);
pImageTaskFn user_image_tasks;
/* Flash 擦除过程 */
uint32_t EraseAdd = 0; // 擦除地址
uint32_t EraseBlockNum = 0; // 需要擦除的块数
uint32_t EraseBlockCnt = 0; // 擦除的块计数
/* FLASH 校验过程 */
uint8_t VerifyStatus = 0;
void OTA_IAPReadDataComplete(unsigned char index);
void OTA_IAPWriteData(unsigned char index, unsigned char *p_data, unsigned char w_len);
void Rec_OTA_IAP_DataDeal(void);
void OTA_IAP_SendCMDDealSta(uint8_t deal_status);
// Simple GATT Profile Callbacks
static OTAProfileCBs_t Peripheral_OTA_IAPProfileCBs = {
OTA_IAPReadDataComplete, // Charactersitic value change callback
OTA_IAPWriteData};
- 在void Peripheral_Init()函数中修改如下
// GAP_SetParamValue(TGAP_ADV_SCAN_REQ_NOTIFY, ENABLE);
OTAProfile_AddService(GATT_ALL_SERVICES);//OTA Profile初始化
OTAProfile_RegisterAppCBs(&Peripheral_OTA_IAPProfileCBs);//OTA Profile读写回调函数注册
- 在uint16_t Peripheral_ProcessEvent(uint8_t task_id, uint16_t events)函数中添加如下代码
if (events & SBP_PARAM_UPDATE_EVT2)
{
// Send connect param update request发送连接参数更新请求
GAPRole_PeripheralConnParamUpdateReq(peripheralConnList.connHandle,
DEFAULT_DESIRED_MIN_CONN_INTERVAL,
20,
0,
DEFAULT_DESIRED_CONN_TIMEOUT,
Peripheral_TaskID);
return (events ^ SBP_PARAM_UPDATE_EVT2);
}
if (events & OTA_FLASH_ERASE_EVT)
{
uint8_t status;
PRINT("ERASE:%08x num:%d\r\n", (int)(EraseAdd + EraseBlockCnt * FLASH_BLOCK_SIZE), (int)EraseBlockCnt);
status = FLASH_ROM_ERASE(EraseAdd + EraseBlockCnt * FLASH_BLOCK_SIZE, FLASH_BLOCK_SIZE);
/* 擦除失败 */
if (status != SUCCESS)
{
OTA_IAP_SendCMDDealSta(status);
return (events ^ OTA_FLASH_ERASE_EVT);
}
EraseBlockCnt++;
/* 擦除结束 */
if (EraseBlockCnt >= EraseBlockNum)
{
PRINT("ERASE Complete\r\n");
OTA_IAP_SendCMDDealSta(status);
return (events ^ OTA_FLASH_ERASE_EVT);
}
return (events);
}
- 在Peripheral.h文件中新增定义如下
#define OTA_FLASH_ERASE_EVT 0x0040 //OTA Flash
#define SBP_PARAM_UPDATE_EVT2 0x0080
- 在Peripheral.c文件中新增如下函数
/*********************************************************************
* @fn OTA_IAP_SendData
*
* @brief OTA IAP发送数据,使用时限制20字节以内
*
* @param p_send_data - 发送数据的指针
* @param send_len - 发送数据的长度
*
* @return none
*/
void OTA_IAP_SendData(uint8_t *p_send_data, uint8_t send_len)
{
OTAProfile_SendData(OTAPROFILE_CHAR, p_send_data, send_len);
}
/*********************************************************************
* @fn OTA_IAP_SendCMDDealSta
*
* @brief OTA IAP执行的状态返回
*
* @param deal_status - 返回的状态
*
* @return none
*/
void OTA_IAP_SendCMDDealSta(uint8_t deal_status)
{
uint8_t send_buf[2];
send_buf[0] = deal_status;
send_buf[1] = 0;
OTA_IAP_SendData(send_buf, 2);
}
/*********************************************************************
* @fn OTA_IAP_CMDErrDeal
*
* @brief OTA IAP异常命令码处理
*
* @return none
*/
void OTA_IAP_CMDErrDeal(void)
{
OTA_IAP_SendCMDDealSta(0xfe);
}
/*********************************************************************
* @fn SwitchImageFlag
*
* @brief 切换dataflash里的ImageFlag
*
* @param new_flag - 切换的ImageFlag
*
* @return none
*/
void SwitchImageFlag(uint8_t new_flag)
{
uint16_t i;
uint32_t ver_flag;
/* 读取第一块 */
EEPROM_READ(OTA_DATAFLASH_ADD, (uint32_t *)&block_buf[0], 4);
/* 擦除第一块 */
EEPROM_ERASE(OTA_DATAFLASH_ADD, EEPROM_PAGE_SIZE);
/* 更新Image信息 */
block_buf[0] = new_flag;
/* 编程DataFlash */
EEPROM_WRITE(OTA_DATAFLASH_ADD, (uint32_t *)&block_buf[0], 4);
}
/*********************************************************************
* @fn DisableAllIRQ
*
* @brief 关闭所有的中断
*
* @return none
*/
void DisableAllIRQ(void)
{
SYS_DisableAllIrq(NULL);
}
/*********************************************************************
* @fn Rec_OTA_IAP_DataDeal
*
* @brief 接收到OTA数据包处理
*
* @return none
*/
void Rec_OTA_IAP_DataDeal(void)
{
switch (iap_rec_data.other.buf[0])
{
/* 编程 */
case CMD_IAP_PROM:
{
uint32_t i;
uint8_t status;
OpParaDataLen = iap_rec_data.program.len;
OpAdd = (uint32_t)(iap_rec_data.program.addr[0]);
OpAdd |= ((uint32_t)(iap_rec_data.program.addr[1]) << 8);
OpAdd = OpAdd * 16;
OpAdd += IMAGE_A_SIZE;
PRINT("IAP_PROM: %08x len:%d \r\n", (int)OpAdd, (int)OpParaDataLen);
/* 当前是ImageA,直接编程 */
status = FLASH_ROM_WRITE(OpAdd, iap_rec_data.program.buf, (uint16_t)OpParaDataLen);
if (status)
PRINT("IAP_PROM err \r\n");
OTA_IAP_SendCMDDealSta(status);
break;
}
/* 擦除 -- 蓝牙擦除由主机控制 */
case CMD_IAP_ERASE:
{
OpAdd = (uint32_t)(iap_rec_data.erase.addr[0]);
OpAdd |= ((uint32_t)(iap_rec_data.erase.addr[1]) << 8);
OpAdd = OpAdd * 16;
OpAdd += IMAGE_A_SIZE;
EraseBlockNum = (uint32_t)(iap_rec_data.erase.block_num[0]);
EraseBlockNum |= ((uint32_t)(iap_rec_data.erase.block_num[1]) << 8);
EraseAdd = OpAdd;
EraseBlockCnt = 0;
/* 检验就放在擦除里清0 */
VerifyStatus = 0;
PRINT("IAP_ERASE start:%08x num:%d\r\n", (int)OpAdd, (int)EraseBlockNum);
if (EraseAdd < IMAGE_B_START_ADD || (EraseAdd + (EraseBlockNum - 1) * FLASH_BLOCK_SIZE) > IMAGE_IAP_START_ADD)
{
OTA_IAP_SendCMDDealSta(0xFF);
}
else
{
/* 启动擦除 */
tmos_set_event(Peripheral_TaskID, OTA_FLASH_ERASE_EVT);
}
break;
}
/* 校验 */
case CMD_IAP_VERIFY:
{
uint32_t i;
uint8_t status = 0;
OpParaDataLen = iap_rec_data.verify.len;
OpAdd = (uint32_t)(iap_rec_data.verify.addr[0]);
OpAdd |= ((uint32_t)(iap_rec_data.verify.addr[1]) << 8);
OpAdd = OpAdd * 16;
OpAdd += IMAGE_A_SIZE;
PRINT("IAP_VERIFY: %08x len:%d \r\n", (int)OpAdd, (int)OpParaDataLen);
/* 当前是ImageA,直接读取ImageB校验 */
status = FLASH_ROM_VERIFY(OpAdd, iap_rec_data.verify.buf, OpParaDataLen);
if (status)
{
PRINT("IAP_VERIFY err \r\n");
}
VerifyStatus |= status;
OTA_IAP_SendCMDDealSta(VerifyStatus);
break;
}
/* 编程结束 */
case CMD_IAP_END:
{
PRINT("IAP_END \r\n");
/* 当前的是ImageA */
/* 关闭当前所有使用中断,或者方便一点直接全部关闭 */
DisableAllIRQ();
/* 修改DataFlash,切换至ImageIAP */
SwitchImageFlag(IMAGE_IAP_FLAG);
/* 等待打印完成 ,复位*/
mDelaymS(10);
SYS_ResetExecute();
break;
}
case CMD_IAP_INFO:
{
uint8_t send_buf[20];
PRINT("IAP_INFO \r\n");
/* IMAGE FLAG */
send_buf[0] = IMAGE_B_FLAG;
/* IMAGE_SIZE */
send_buf[1] = (uint8_t)(IMAGE_SIZE & 0xff);
send_buf[2] = (uint8_t)((IMAGE_SIZE >> 8) & 0xff);
send_buf[3] = (uint8_t)((IMAGE_SIZE >> 16) & 0xff);
send_buf[4] = (uint8_t)((IMAGE_SIZE >> 24) & 0xff);
/* BLOCK SIZE */
send_buf[5] = (uint8_t)(FLASH_BLOCK_SIZE & 0xff);
send_buf[6] = (uint8_t)((FLASH_BLOCK_SIZE >> 8) & 0xff);
send_buf[7] = CHIP_ID & 0xFF;
send_buf[8] = (CHIP_ID << 8) & 0xFF;
/* 有需要再增加 */
/* 发送信息 */
OTA_IAP_SendData(send_buf, 20);
break;
}
default:
{
OTA_IAP_CMDErrDeal();
break;
}
}
}
/*********************************************************************
* @fn OTA_IAPReadDataComplete
*
* @brief OTA 数据读取完成处理
*
* @param index - OTA 通道序号
*
* @return none
*/
void OTA_IAPReadDataComplete(unsigned char index)
{
PRINT("OTA Send Comp \r\n");
}
/*********************************************************************
* @fn OTA_IAPWriteData
*
* @brief OTA 通道数据接收完成处理
*
* @param index - OTA 通道序号
* @param p_data - 写入的数据
* @param w_len - 写入的长度
*
* @return none
*/
void OTA_IAPWriteData(unsigned char index, unsigned char *p_data, unsigned char w_len)
{
unsigned char rec_len;
unsigned char *rec_data;
rec_len = w_len;
rec_data = p_data;
tmos_memcpy((unsigned char *)&iap_rec_data, rec_data, rec_len);
Rec_OTA_IAP_DataDeal();
}
4.修改Link.Ld文件
//原本
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 448K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
}
//修改后
MEMORY
{
FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 216K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
}
三、OTA升级
1.合并文件
将BackupUpgrade_IAP 产生的 hex 文件和BackupUpgrade_JumpIAP 产生的 hex 文件还有刚才自己编写的APP程序的hex 文件进行合并为bin文件。
软件可以去沁恒官网下载:沁恒微电子
2.下载bin文件
3.APP升级程序
3.1 OTA升级工具
具体可以在官网下载:沁恒微电子
3.2 搜索蓝牙,连接
3.3 选择升级的固件
就是刚才APP程序产生的HEX文件
3.4 升级过程
3.5 升级完成
3.6 升级结果
RGB灯珠由颜色切换,变成了固定一个颜色点亮。
四、总结
今天主要讲了CH582蓝牙OTA的操作,感兴趣的可以去试试。
感谢你的观看!