1、首先必包含的两个文件一个是jumpIAP 一个是IAP
OTA 即 Over-the-Air Technology,指的是空中下载技术,蓝牙OTA就是通过蓝牙控制升级程序的下载到备份区,然后通过触发IAP来进行程序的转移,最后跳转到新程序。这里是吧IAP和APP分离开了,一般我认为OTA是属于APP的一部分。然后蓝牙协议栈单独放一个地方。jumpIAP和IAP是可以合并的。因为jumpIAP的作用就是跳转到IAP程序。那我直接运行IAP程序不就行了,估计是有自己的考量吧。jumpIAP是放在COD-FLASH的头地址,IAP放在了尾部的地址。
IAP的全称是“In-Application Programming”,即应用编程。 它是一种可以在设备运行应用程序的过程中,对设备的闪存或其他非易失性存储器进行编程或更新的技术。通过IAP,设备可以在不依赖外部编程器的情况下,实现自身程序的更新、升级等功能,常用于实现设备的固件升级、远程更新等场景。在上述代码中,就体现了通过IAP技术来实现程序在不同存储区域之间的搬运和更新等相关功能。
整个OTA流程是什么,蓝牙控制单片机擦除,数据写入B区或外部FLASH,数据备份完成。触发复位,复位起来进入JumpIAP,IAP内检查是否有升级IAP标志,有的话从B区把APP搬移到A区实现空中升级。
2、解读JumpIAP工程做了什么
空的main函数,也就是具体操作在启动文件内
int main(void)
{
}
顺带看一下链接文件
ENTRY( _start )MEMORY
{FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}SECTIONS
{.init :{_sinit = .;. = ALIGN(4);KEEP(*(SORT_NONE(.init)))KEEP(*(.ImageFlag))KEEP(*(.ImageFlag.*)). = ALIGN(4);_einit = .;} >FLASH AT>FLASH}
定义了代码占用的FLASH空间为4K,RAM为128K,当然这是585的配置,584M好像只有96K。
这段链接脚本定义了一个名为 .init
的段,该段包含了程序初始化相关的代码和数据,以及 ImageFlag
相关的段内容。段的起始地址和结束地址分别由 _sinit
和 _einit
符号记录,并且段内地址进行了 4 字节对齐,最终该段将被加载和存储在 FLASH
区域。虽然我不懂什么意思,暂时也不用懂
最关键的启动文件
.section .init,"ax",@progbits.global _start.align 1
_start:j 0x6D000
跳转到436K的地址处 跟最高地址差12K,这就要查OTA里的ota.h内存划分部分了
/* 整个用户code区分成四块,4K,216K,216K,12K,后三块下面分别叫做imageA(APP),imageB(OTA)和imageIAP */
也就是他专门留了12K的内存给IAP程序使用,IAP被放置到436K起始的位置上。
JumpIAP很简单易懂,就是一个跳转指令,根据你设计的IAP大小来跳转到不同的地址上启动IAP。4K的空间是给JumpIAP使用的,它就是0x00000000的地址。
3、解读IAP工程做了什么
链接文件,起始地址436K,CODE-FLASH最后的12K内存
MEMORY
{FLASH (rx) : ORIGIN = 0x0006D000, LENGTH = 12KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
这里分配了12K内存,整好对应上了。别的链接文件内容就大致不看了,看也看不懂。
启动文件
让程序从 main
函数开始执行,
int main(void)
{
#if(defined(DCDC_ENABLE)) && (DCDC_ENABLE == TRUE)PWR_DCDCCfg(ENABLE);
#endifSetSysClock(CLK_SOURCE_PLL_60MHz);
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
#endif
#ifdef DEBUGGPIOA_SetBits(bTXD1);GPIOA_ModeCfg(bTXD1, GPIO_ModeOut_PP_5mA);UART1_DefInit();
#endifReadImageFlag();jump_APP();
}
串口初始化是用来使能串口打印调试信息的,主要看两点ReadImageFlah和jump_APP函数。其实这个问价内的描述已经很具体了。
即,判断标志以及搬运代码到APP区,这里的APP区固定式A区,B区是备份区。当然你要是有外部FLASH的话,可以从外部搬运进A区,B区失效。
/********************************** (C) COPYRIGHT ******************************** File Name : main.c* Author : WCH* Version : V1.1* Date : 2019/11/05* Description : 判断标志以及搬运代码到APP代码区********************************************************************************** Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.* Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics.*******************************************************************************//******************************************************************************/
这个有一个写标志的函数,就是擦对应的地址,写进去,那个读操作,不知道是什么用处,难道是为了稳定对FLASH的擦写。
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);
}
去读了EEPROM的OTA标志存储地址,从前面我们也可以看出,这个就是DATAFLASH的内容,并且这是用FLASH去模拟了EEPROM的操作。
#define OTA_DATAFLASH_ADD 0x00077000 - FLASH_ROM_MAX_SIZE
#define FLASH_ROM_MAX_SIZE 0x070000 // Flash-ROM maximum program size, 448KB
带进去可以得到OTA_DATAFLASH_ADD地址实际是0x00007000,也就是28K,暂时不知道这个地址是如何确定的。
好像找到了一篇博客讲这个
【沁恒蓝牙mesh】CH58x flash分区与数据存储管理_蓝牙存储flash-CSDN博客
去找操作这个地址原来的函数定义,明确是操作Data-Flash,但是不知道为什么会跟Code-Flash的大小有关联。
/*** @brief read Data-Flash data block** @param StartAddr - Address of the data to be read.* @param Buffer - Pointer to the buffer where data should be stored, Must in RAM and be aligned to 4 bytes.* @param Length - Size of data to be read, in bytes.** @return 0-SUCCESS (!0)-FAILURE*/
#define EEPROM_READ(StartAddr,Buffer,Length) FLASH_EEPROM_CMD( CMD_EEPROM_READ, StartAddr, Buffer, Length )
后面看了一下,虽然EEPROM和FLASH调的都是同一个函数,但是输入的命令是不同的,是分两种,估计就是DataFLASH做了一个偏移,那就解释的通了
#define CMD_FLASH_ROM_START_IO 0x00 // start FlashROM I/O, without parameter
#define CMD_FLASH_ROM_SW_RESET 0x04 // software reset FlashROM, without parameter
#define CMD_GET_ROM_INFO 0x06 // get information from FlashROM, parameter @Address,Buffer
#define CMD_GET_UNIQUE_ID 0x07 // get 64 bit unique ID, parameter @Buffer
#define CMD_FLASH_ROM_PWR_DOWN 0x0D // power-down FlashROM, without parameter
#define CMD_FLASH_ROM_PWR_UP 0x0C // power-up FlashROM, without parameter
#define CMD_FLASH_ROM_LOCK 0x08 // lock(protect)/unlock FlashROM data block, return 0 if success, parameter @StartAddr
// StartAddr: 0=unlock all, 1=lock boot code, 3=lock all code and data#define CMD_EEPROM_ERASE 0x09 // erase Data-Flash block, return 0 if success, parameter @StartAddr,Length
#define CMD_EEPROM_WRITE 0x0A // write Data-Flash data block, return 0 if success, parameter @StartAddr,Buffer,Length
#define CMD_EEPROM_READ 0x0B // read Data-Flash data block, parameter @StartAddr,Buffer,Length
#define CMD_FLASH_ROM_ERASE 0x01 // erase FlashROM block, return 0 if success, parameter @StartAddr,Length
#define CMD_FLASH_ROM_WRITE 0x02 // write FlashROM data block, minimal block is dword, return 0 if success, parameter @StartAddr,Buffer,Length
#define CMD_FLASH_ROM_VERIFY 0x03 // read FlashROM data block, minimal block is dword, return 0 if success, parameter @StartAddr,Buffer,Length
关键的函数读映像
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_IAP_FLAG)){CurrImageFlag = IMAGE_A_FLAG;}PRINT("Image Flag %02x\n", CurrImageFlag);
}
初始去读,如果是这片FLASH没操作过,默认就是0xFF,跟标志位不服,就默认是第一次操作。映像调整为APP的映像。
跳转IAP函数
#define jumpApp ((void (*)(void))((int *)IMAGE_A_START_ADD))
void jump_APP(void)
{if(CurrImageFlag == IMAGE_IAP_FLAG){__attribute__((aligned(8))) uint8_t flash_Data[1024];uint8_t i;FLASH_ROM_ERASE(IMAGE_A_START_ADD, IMAGE_A_SIZE);for(i = 0; i < IMAGE_A_SIZE / 1024; i++){FLASH_ROM_READ(IMAGE_B_START_ADD + (i * 1024), flash_Data, 1024);FLASH_ROM_WRITE(IMAGE_A_START_ADD + (i * 1024), flash_Data, 1024);}SwitchImageFlag(IMAGE_A_FLAG);// 销毁备份代码FLASH_ROM_ERASE(IMAGE_B_START_ADD, IMAGE_A_SIZE);}jumpApp();
}
流程就类似Bootloader,读标志位,是否等于升级在线标志位,等于就开辟一个1K大小的RAM,从B区搬到A区,然后擦除B区内容。最后跳转到A区的APP部分执行应用程序。
4、BACK-OTA工程的解读
首先还是链接文件和启动文件
链接文件内的内容,起始地址0x1000,也就是从4K开始,跳过了JumpIAP部分,大小是OTA里设置的216K,根据自己的需求调整。RAM还是585的128K,584M为96K,如果使用了蓝牙协议栈的话还要给蓝牙留RAM使用。
MEMORY
{FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 216KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
启动函数里没有什么特殊的要讲,毕竟我现在也没完全读懂。
然后就是主函数文件内做的操作
void ReadImageFlag(void)与IAP一样,就是读取映像标志。
int main(void)
{
#if(defined(DCDC_ENABLE)) && (DCDC_ENABLE == TRUE)PWR_DCDCCfg(ENABLE);
#endifHSECFG_Capacitance(HSECap_18p);SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
#endif
#ifdef DEBUGGPIOA_SetBits(GPIO_Pin_14);GPIOPinRemap(ENABLE, RB_PIN_UART0);GPIOA_ModeCfg(GPIO_Pin_15, GPIO_ModeIN_PU);GPIOA_ModeCfg(GPIO_Pin_14, GPIO_ModeOut_PP_5mA);UART0_DefInit();
#endifPRINT("%s\n", VER_LIB);ReadImageFlag();CH58x_BLEInit();HAL_Init();GAPRole_PeripheralInit();Peripheral_Init();Main_Circulation();
}
这里面的先读映像,然后 BLE 库初始化,初始化硬件,初始化蓝牙从机,初始化从机的参数,进入主循环。
由于使用蓝牙功能会自动调用一个非抢占式的OS,TMOS。
TMOS就是对蓝牙任务和其他任务进行分别调度。其他任务一般就创建一个,然后在任务里再搞一个不同事件的调度。可以弄31事件好像。这块还没完全了解。
void HAL_Init()
{halTaskID = TMOS_ProcessEventRegister(HAL_ProcessEvent);HAL_TimeInit();
#if(defined HAL_SLEEP) && (HAL_SLEEP == TRUE)HAL_SleepInit();
#endif
#if(defined HAL_LED) && (HAL_LED == TRUE)HAL_LedInit();
#endif
#if(defined HAL_KEY) && (HAL_KEY == TRUE)HAL_KeyInit();
#endif
#if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE)tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, 800); // 添加校准任务,500ms启动,单次校准耗时小于10ms
#endif
// tmos_start_task( halTaskID, HAL_TEST_EVENT, 1600 ); // 添加一个测试任务
}
初始化硬件,注册了一个任务,HAL_ProcessEvent,然后后面后自动回调这个任务函数,根据是否发生了对应的事件,来执行相应操作后清事件位。这里就涉及TMOS系统,不过多讲解。
/******************************************************************************** @fn HAL_ProcessEvent** @brief 硬件层事务处理** @param task_id - The TMOS assigned task ID.* @param events - events to process. This is a bit map and can* contain more than one event.** @return events.*/
tmosEvents HAL_ProcessEvent(tmosTaskID task_id, tmosEvents events)
{uint8_t *msgPtr;if(events & SYS_EVENT_MSG){ // 处理HAL层消息,调用tmos_msg_receive读取消息,处理完成后删除消息。msgPtr = tmos_msg_receive(task_id);if(msgPtr){/* De-allocate */tmos_msg_deallocate(msgPtr);}return events ^ SYS_EVENT_MSG;}if(events & LED_BLINK_EVENT){
#if(defined HAL_LED) && (HAL_LED == TRUE)HalLedUpdate();
#endif // HAL_LEDreturn events ^ LED_BLINK_EVENT;}if(events & HAL_KEY_EVENT){
#if(defined HAL_KEY) && (HAL_KEY == TRUE)HAL_KeyPoll(); /* Check for keys */tmos_start_task(halTaskID, HAL_KEY_EVENT, MS1_TO_SYSTEM_TIME(100));return events ^ HAL_KEY_EVENT;
#endif}if(events & HAL_REG_INIT_EVENT){uint8_t x32Kpw;
#if(defined BLE_CALIBRATION_ENABLE) && (BLE_CALIBRATION_ENABLE == TRUE) // 校准任务,单次校准耗时小于10ms
#ifndef RF_8KBLE_RegInit(); // 校准RF,会关闭RF并改变RF相关寄存器,如果使用了RF收发函数需注意校准后再重新启用
#endif
#if(CLK_OSC32K)Lib_Calibration_LSI(); // 校准内部RC
#elif(HAL_SLEEP)x32Kpw = (R8_XT32K_TUNE & 0xfc) | 0x01;sys_safe_access_enable();R8_XT32K_TUNE = x32Kpw; // LSE驱动电流降低到额定电流sys_safe_access_disable();
#endiftmos_start_task(halTaskID, HAL_REG_INIT_EVENT, MS1_TO_SYSTEM_TIME(BLE_CALIBRATION_PERIOD));return events ^ HAL_REG_INIT_EVENT;
#endif}if(events & HAL_TEST_EVENT){PRINT("* \n");tmos_start_task(halTaskID, HAL_TEST_EVENT, MS1_TO_SYSTEM_TIME(1000));return events ^ HAL_TEST_EVENT;}return 0;
}
Peripheral_Init,外设应用任务的初始化函数。其实特指的是蓝牙相关的,最关键的是它所带的一些事件
/********************************************************************** @fn Peripheral_ProcessEvent** @brief Peripheral Application Task event processor. This function* is called to process all events for the task. Events* include timers, messages and any other user defined events.** @param task_id - The TMOS assigned task ID.* @param events - events to process. This is a bit map and can* contain more than one event.** @return events not processed*/
uint16_t Peripheral_ProcessEvent(uint8_t task_id, uint16_t events)
{// VOID task_id; // TMOS required parameter that isn't used in this functionif(events & SYS_EVENT_MSG){uint8_t *pMsg;if((pMsg = tmos_msg_receive(Peripheral_TaskID)) != NULL){Peripheral_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg);// Release the TMOS messagetmos_msg_deallocate(pMsg);}// return unprocessed eventsreturn (events ^ SYS_EVENT_MSG);}if(events & SBP_START_DEVICE_EVT){// Start the DeviceGAPRole_PeripheralStartDevice(Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs);// Set timer for first periodic eventtmos_start_task(Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD);return (events ^ SBP_START_DEVICE_EVT);}if(events & SBP_PERIODIC_EVT){// Restart timerif(SBP_PERIODIC_EVT_PERIOD){tmos_start_task(Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD);}// Perform periodic application taskperformPeriodicTask();return (events ^ SBP_PERIODIC_EVT);}//OTA_FLASH_ERASE_EVTif(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);}// Discard unknown eventsreturn 0;
}
暂时先看到这,后面有一大串,暂时没理清,理清了再继续写吧