#技术资源# #申请原创#
一般情况下,我们开发的带MCU的EtherCAT模块应该都是要支持对MCU固件进行FOE升级的(一种远程升级固件的功能),也就是测试端、工厂端或者客户端在维修时,可以很方便的对模块进行固件升级。今天咱就以雅特力的AT32F403ACCT7芯片为例与大家聊聊FOE升级这件事,看看怎么样开发一个支持FOE升级的bootloader程序。 其实所谓的FOE,是EtherCAT协议里面的特有内容,全称为File Access Over EtherCAT,就是基于EtherCAT协议对MCU固件进行升级的一种文件传输协议,其固件文件的后缀为.efw(EtherCAT Firmware (File))格式,在通过codesys或者TwinCAT等上位机进行升级操作时,即会让你选择指定的efw格式文件: 而FOE升级的原理其实也是通过EtherCAT协议栈进行的,在协议栈代码里有几个文件,就是专门用于FOE的,包括ecatfoe.c,foeappl.c和bootmode.c源文件以及相应的头文件,而其他文件则会通过调用里面的接口的方式进行实际操作: 其中ecatfoe.c主要实现了FOE升级的主服务接口FOE_ServiceInd,这个接口会在邮箱通信里面被调用: 而foeappl.c主要实现了FOE升级的与状态机对应的相关具体操作,包括读写操作和接收数据操作等: 而bootmode.c主要实现了对MCU的flash进行擦除和写入数据的操作,这里面的代码就与MCU平台相关了,也是我们实现FOE固件升级的核心开发部分。
当然,EtherCAT协议栈默认是失能FOE升级的,我们在通过SSC工具配置协议栈代码的时候可以使能BOOTSTRAPMODE_SUPPORTED: 另外,我们还需要开启FOE_SUPPORTED来支持FOE功能: 配置完成后,在后续生成的协议栈代码里即会有所体现: 当然,在从站模块的ESI文件(xml文件)里需要配置一下以使能FOE,然后上位机里面的“下载”按钮就能操作了: 接下来我们就详细说说支持FOE升级的bootloader程序实现过程。 第一步当然是要把通过SSC工具配置好的EtherCAT协议栈导入工程,主要包括之前截图的那些文件,然后在定义的ecat_slave_main初始化接口里的HW_Init()里面实现与MCU硬件平台相关的配置,包括MCU和ESC芯片的SPI通信配置等。然后在main的while循环里调用MainLoop来执行协议栈代码。这一块和APP程序里的协议栈操作基本类似,就不详述了。 第二步就是要在foeappl.c里面实现读请求,写请求和写固件数据的接口代码。 其中FOE_Read接口是主站下发读文件数据的请求,我们这里可以不用实现;
FOE_Write接口是主站下发的写固件数据的请求,就是当我们在主站上位机上导入efw文件操作开始升级时,主站会把文件的名称和文件名长度下发给从站,这个时候我们可以加上逻辑代码,就是如果文件名称或者文件名长度不符合要求,就返回报错,停止升级,这是非常有必要的,可以防止更新错误的efw文件,参考代码如下: - /////////////////////////////////////////////////////////////////////////////////////////
- /**
- \param pName Pointer to the name of the file
- \param nameSize Length of the file name
- \param password Password for the file read
- \return okay, busy or an error code
- 0 (okay)
- ECAT_FOE_ERRCODE_NOTDEFINED (error)
- ECAT_FOE_ERRCODE_NOTFOUND (error)
- ECAT_FOE_ERRCODE_ACCESS (error)
- ECAT_FOE_ERRCODE_DISKFULL (error)
- ECAT_FOE_ERRCODE_ILLEGAL (error)
- ECAT_FOE_ERRCODE_PACKENO (error)
- ECAT_FOE_ERRCODE_EXISTS (error)
- ECAT_FOE_ERRCODE_NOUSER (error)
- (no busy response shall be returned by this function.
- If the slave stack requires some time to handle the incoming data the function FOE_Data() should return busy)
- \brief The function starts a file writing
- *////////////////////////////////////////////////////////////////////////////////////////
- UINT16 FOE_Write(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password)
- {
- u8* pFileName = (u8*)pName;
- u8* pMemName = (u8*)aFirmwareDownloadHeader;
-
- #if BOOTSTRAPMODE_SUPPORTED
- if ( ( nameSize >= 3 )
- &&( pFileName[0] == pMemName[0] )
- &&( pFileName[1] == pMemName[1] )
- &&( pFileName[2] == pMemName[2] )
- //&&( pFileName[3] == pMemName[3] )
- )
- {
- if ( bBootMode )
- {
- BL_StartDownload(0,password);
- DownloadType=0;
- return 0;
- }
- else
- return ECAT_FOE_ERRCODE_BOOTSTRAPONLY;
- }
- else
- if ( bBootMode )
- return ECAT_FOE_ERRCODE_NOTINBOOTSTRAP;
- else
- #endif
- #if FOE_SAVE_FILES
- if ( nameSize < MAX_FILE_NAME_SIZE )
- {
- /* for test every file name can be written */
- MBXSTRCPY(aFileName, pName, nameSize);
- MBXSTRCPY(aFileName+nameSize, "\0", 1); //string termination
- nFileWriteOffset = 0;
- nFileSize = 0;
- return 0;
- }
- else
- return ECAT_FOE_ERRCODE_DISKFULL;
- #else
- DataValue = 0x00;
- nFileSize = 0;
- return 0;
- #endif
- }
其中aFirmwareDownloadHeader的定义如下,即为待升级文件的前面几个字符: - #define APP_NAME "FOE_"
- #define MAX_FILE_NAME_SIZE 100
- const char aFirmwareDownloadHeader[MAX_FILE_NAME_SIZE] = APP_NAME;
正常时,则主要通过调用BL_StartDownload接口来执行flash擦除操作。
另外,如果不像被轻易升级,我们可以在程序里增加密码口令,即FOE_Write接口里的第三个形参password,只有当操作升级时输入的密码和程序里写的密码一致时,才允许升级,否则报错返回,停止升级,如下图所示,是升级时需要输入的密码,如果程序里没有配置,则不用管: FOE_Data接口则负责将二进制的固件数据通过里面的BL_Data接口写入MCU的flash里面,参考代码如下: - /////////////////////////////////////////////////////////////////////////////////////////
- /**
- \param pData Received file data
- \param Size Length of received file data
- \return okay, busy or an error code
- 0 (okay)
- FOE_MAXBUSY-100 (0%) (busy)
- FOE_MAXBUSY (100%) (busy)
- ECAT_FOE_ERRCODE_NOTDEFINED (error)
- ECAT_FOE_ERRCODE_NOTFOUND (error)
- ECAT_FOE_ERRCODE_ACCESS (error)
- ECAT_FOE_ERRCODE_DISKFULL (error)
- ECAT_FOE_ERRCODE_ILLEGAL (error)
- ECAT_FOE_ERRCODE_PACKENO (error)
- ECAT_FOE_ERRCODE_EXISTS (error)
- ECAT_FOE_ERRCODE_NOUSER (error)
- \brief The function is called to write the next part of a file
- *////////////////////////////////////////////////////////////////////////////////////////
- UINT16 FOE_Data(UINT16 MBXMEM * pData, UINT16 Size)
- {
- #if BOOTSTRAPMODE_SUPPORTED
- if ( bBootMode )
- {
- return BL_Data(DownloadType,pData, Size);
- }
- else
- #endif
- #if FOE_SAVE_FILES
- if((nFileWriteOffset + Size) > MAX_FILE_SIZE)
- return ECAT_FOE_ERRCODE_DISKFULL;
- if ( Size )
- {
- #if MBX_16BIT_ACCESS
- MBXMEMCPY(&aFileData[(nFileWriteOffset >> 1)], pData, Size);
- #else
- MBXMEMCPY(&aFileData[nFileWriteOffset], pData, Size);
- #endif
- }
- #endif
- if ( Size == (u16ReceiveMbxSize - MBX_HEADER_SIZE - FOE_HEADER_SIZE) )
- {
- /* FoE-Data services will follow */
- nFileWriteOffset += Size;
- return 0;
- }
- else
- {
- /* last part of the file is written */
- nFileSize = nFileWriteOffset + Size;
- nFileWriteOffset = 0;
- return FOE_ACKFINISHED;
- }
- }
其中全局变量nFileSize是记录文件数据大小的; BL_Data接口即操作具体的写flash过程; 这里有一点需要强调一下,也非常有用,默认的BL_Data接口是没有第一个参数DownloadType的,这个我们可以自行增加,就是我们可以通过这个来判断当前是升级MCU还是FPGA固件,当然,你必须在FOE_Write接口里根据文件名称来配置DownloadType为相应的值0或者1,然后在BL_Data里面操作,如果你的板子是支持通过MCU来给FPGA升级固件的话。 最后我们就是在bootmode.c里面实现与MCU硬件的交互了,也就是擦除flash和写flash。 我之前是用AT32F403ACCT7芯片来调试的,所以里面的flash操作接口就主要来源于at32f403a_407_flash.c文件。
其中BL_StartDownload里面负责擦除flash数据,毕竟更新数据之前要先erase: - /////////////////////////////////////////////////////////////////////////////////////////
- /**
- \param password download password
- \brief Dummy BL_StartDownload function
- *////////////////////////////////////////////////////////////////////////////////////////
- void BL_StartDownload(UINT8 ID,UINT32 password)
- {
- if(ID==0)//MCU
- {
- iFlashWriteOffset=DOWNLOAD_ADDRESS;
- iFileTotalSize=0;
- bsp_EraseCpuFlashSector(FLASH_ERASE_START_ADDR);
- }
- // else if(ID==1) //FPGA
- // {
- // }
- }
BL_Data负责写入数据到flash,另外,为了看到升级过程,我们可以通过RUN灯来指示: - /////////////////////////////////////////////////////////////////////////////////////////
- /**
- \param pData Data pointer
- \param Size Data Length
- \return FoE error code
- \brief Dummy BL_Data function
- *////////////////////////////////////////////////////////////////////////////////////////
- UINT16 BL_Data(UINT8 ID,UINT16 *pData,UINT16 Size)
- {
- if(ID==0)//MCU
- {
- bsp_WriteCpuFlash(iFlashWriteOffset,(uint8_t*)pData,Size);
- iFlashWriteOffset=iFlashWriteOffset+(Size);
- iFileTotalSize+=Size;
- }
- // else if(ID==1)//FPAG
- // {
- // }
- LedRun((*pData)&0x0001);
- return 0;
- }
另外,上面接口里操作flash的函数bsp_EraseCpuFlashSector和bsp_WriteCpuFlash,我们可以定义在bsp_flash.c里面:
|