CH58X_IAP功能的使用

  • IAP(In-Application Programming,在应用编程)是指 MCU 在运行用户程序的过程中,利用芯片自身的 Flash 擦写能力和通信接口,对 Flash 中的程序或数据进行更新,设备不需要拆机、不需要外部编程器/调试器,就能自己完成固件升级;IAP是将Flash划分为两部分,两部分区域各存放一个程序,一个叫Bootloader(引导加载程序),另一个叫user application(用户应用程序),简称APP。在程序执行初始进入bootloader,在bootloader里面检测条件是否被触发(可通过按键是否被按下、串口是否接收到特定的数据、U盘是否插入等等),如果有则进行对APP进行擦除和重新写入操作,擦除用户代码(Old APP)并重新写入新的用户代码(New APP),如果没有则直接跳转到APP执行应用;
  • IAP升级方式解决产品在安装外壳后无法接触调试口、应用运行过程中触发升级等场景中使用,调试时使用ISP升级方式

CH58X_IAP示例

  • CH585 EVT中IAP文件夹内提供了IAP例程,例程有USB/USBHS/UART三种更新方式,本文以全速USB_IAP为例
    image
  • flash分区:从link.ld和iap.h文件可以看到flash分区信息
区域 地址范围 说明
用户bootloader 0x00000000-0x00001000(4KB)
应用程序 0x00001000-0x00070000(444KB) jumpAPP()
RAM 0x20000000-0x2001FFFF(128KB) highcode
  • 示例中的IAP方式是直接擦除444KB的flash空间,没有备份和回滚的,但是用户bootloader不会擦除,而且擦除代码用highcode修饰,都在RAM中,所以升级失败可以重新升级;备份升级方案在BLE/BackupUpgrade_IAP中

由于沁恒官方并没有给出相关寄存器来实现IAP,只能在官方示例上直接修改代码实现自定义IAP功能


USB_IAP

  • USB_IAP即用户bootloader,主要功能是在上电后运行用户bootloder,判断是否需要进入升级模式,如果需要,通过USB接收上位机发送的固件并写入flash,如果不需要,就直接跳转到APP中运行
  • main()逻辑
/********************
 * @fn      main
 *
 * @brief   主函数
 *
 * @return  none
 */
int main()
{
    uint16_t i;
    uint8_t  s;

    mySetSysClock(); //为了精简程序体积,该函数比普通库的初始化函数有修改,只可以将时钟设置为62.4M
//USE_EEPROM_FLAG默认为0
#if USE_EEPROM_FLAG 
    EEPROM_READ(IAP_FLAG_DATAFLASH_ADD, &p_image_flash, 4);//从dataflash读取标志
    if ((p_image_flash.ImageFlag != FLAG_USER_CALL_IAP))
    {
        jumpApp();//跳转到APP
    }
#else
    //初始化引脚为上拉输入。为了减小程序大小,采用寄存器编写。
    R32_PB_PD_DRV &= ~GPIO_Pin_4;
    R32_PB_PU |= GPIO_Pin_4;
    R32_PB_DIR &= ~GPIO_Pin_4;
    DelayMs(10);
//读取PB4,通过PB4是否拉低来检测是否需要跳转到APP;跳转方式用户可以自定义
    if (GPIOB_ReadPortPin(GPIO_Pin_4))
    {
        DelayMs(5);
        if (GPIOB_ReadPortPin(GPIO_Pin_4))
        {
            //启动前判断是否进入iap,没有按键按下
            jumpApp();
        }
    }
#endif
    /* USB初始化 */
    R8_USB_CTRL = 0x00; // 先设定模式,取消 RB_UC_CLR_ALL

    R8_UEP4_1_MOD = RB_UEP4_RX_EN | RB_UEP4_TX_EN | RB_UEP1_RX_EN | RB_UEP1_TX_EN; // 端点4 OUT+IN,端点1 OUT+IN
    R8_UEP2_3_MOD = RB_UEP2_RX_EN | RB_UEP2_TX_EN | RB_UEP3_RX_EN | RB_UEP3_TX_EN; // 端点2 OUT+IN,端点3 OUT+IN

    R32_UEP0_DMA = (uint32_t)EP0_Databuf;
    R32_UEP1_DMA = (uint32_t)EP1_Databuf;
    R32_UEP2_DMA = (uint32_t)EP2_Databuf;
    R32_UEP3_DMA = (uint32_t)EP3_Databuf;

    R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
    R8_UEP1_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_UEP2_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_UEP3_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK | RB_UEP_AUTO_TOG;
    R8_UEP4_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;

    R8_USB_DEV_AD = 0x00;
    R8_USB_CTRL = RB_UC_DEV_PU_EN | RB_UC_INT_BUSY | RB_UC_DMA_EN;  // 启动USB设备及DMA,在中断期间中断标志未清除前自动返回NAK
    R16_PIN_CONFIG |= RB_PIN_USB_EN | RB_UDP_PU_EN;                 // 防止USB端口浮空及上拉电阻
    R8_USB_INT_FG = 0xFF;                                           // 清中断标志
    R8_UDEV_CTRL = RB_UD_PD_DIS | RB_UD_PORT_EN;                    // 允许USB端口
    R8_USB_INT_EN = 0;  //禁止usb中断,采用查询方式

    /* 进入highcode主循环 */
    Main_Circulation();
}

/*********************************************************************
 * @fn      Main_Circulation
 *
 * @brief   IAP主循环,程序放ram中运行,提升速度.
 *
 * @param   None.
 *
 * @return  None.
 */
__attribute__((section(".highcode")))
void Main_Circulation()
{
    uint16_t j = 0;
    while (1)
    {
        j++;
        if (j > 5)//100us处理一次数据
        {
            j = 0;
            USB_DevTransProcess();//采用查询方式进行usb操作,不使用中断。该函数接收上位机命令操作flash
        }
        DelayUs(20);
        g_tcnt++;
        if (g_tcnt > 3000000)
        {
            //1分钟没有操作,进入app
            R8_USB_CTRL = RB_UC_RESET_SIE;
            R16_PIN_CONFIG &= ~(RB_UDP_PU_EN | RB_PIN_USB_EN);
            DelayMs(10);
            jumpApp();//跳转进入用户程序
        }
    }
}

APP

  • APP是用户程序,其与IAP相关的就是如何跳转到IAP程序相关逻辑
  • main()逻辑
int main()
{
    uint16_t i = 0;
    uint8_t  s = 0;

    HSECFG_Capacitance(HSECap_18p);
    SetSysClock(SYSCLK_FREQ);

    /* 配置串口调试 */
    DebugInit();
    PRINT("Start @ChipID=%02x\n", R8_CHIP_ID);
    /* app程序必须执行该语句,保证app更新失败时,下次依然运行IAP */
    SwitchImageFlag(FLAG_USER_CALL_APP);

    GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);
    while (1)
    {
		PRINT("i:%d\n",i);
		i++;
		DelayMs(10);
		if (GPIOB_ReadPortPin(GPIO_Pin_4) == 0)
		{
			s++;
			//连续两次检测到按键按下,跳转到IAP
			if(s >= 2)
			{
				jumpToIap();//跳转到IAP
			}
		}
		else
		{
			s = 0;
		}
		DelayMs(100);
    }
}

  • IAP和APP都有PB4跳转逻辑,IAP中PB4跳转是上电后检测是否需要升级,APP是运行过程中检测是否需要升级,跳转逻辑可以用户自定义,如读取某个寄存器或者蓝牙接收到某个特征值

ld文件

  • IAP工程中分配了4KBflash大小,地址范围是0x00000000-0x00001000
MEMORY
{
	FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4K
	RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
  • APP工程起始地址就是0x1000,占用后面的444KB flash,IAP+APP为448KB(其他芯片根据flash大小修改)
  • myDevEP2_OUT_Deal()中擦除地址从0x1000开始
MEMORY
{
	FLASH (rx) : ORIGIN = 0x00001000, LENGTH = 444K
	RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}

startup_ch585.s

  • 还需要修改startup_ch585.s中红框部分,普通程序为0x88,用户模式;需要升级的APP以及IAP程序需要改成0x1888,机器模式(用户模式和机器模式是RISCV中的特权等级)
    image

WCH USB_IAP升级流程

  • 烧录时需要同时烧录APP和IAP固件,APP和IAP的代码以及ld文件、startup_ch585.s文件需要按上面所描述做出修改
    image
  • 烧录完成后例程代码为循环打印变量i的值,需要升级的APP程序则打印i+5的值
    image
  • 需要升级的APP编译完成后使用EVT\EXAM\IAP\WCHMcuIAP_WinAPP\WCHMcuIAP_WinAPP\Release文件夹内的WCHMcuIAP_WinAPP程序
    image
  • 根据代码中拉低PB4两次进入IAP升级的方式,将PB4接地2次,按下图中操作,升级APP程序,注意目标地址就是APP起始地址,操作完成后点击程序下载,直到提示IAP下载成功
    image
  • 通过打印可以发现已经运行新APP
    image
posted @ 2026-10-09 15:50  J_02346  阅读(14)  评论(0)    收藏  举报