1.源码生成与移植
从站源码创建和移植部分不说了,重点是在SSC中勾选BOOTSTRAPMODE_SUPPORTED和FOE_SUPPORTED。FOE 进行固件更新可以再OP模式下进行,但是最好是在BootStrap模式下进行,在该状态下只能进行FOE通信,更安全一些。生成的文件中有几个文件bootloaderappl.c、bootloaderappl.h、bootmode.c、bootmode.h。我主要对bootloaderappl.c中的函数进行修改,bootmode中的函数我没有去做修改。
2.代码解析
1.FOE_ServiceInd
FOE_ServiceInd在接收到FOE数据的时候会调用,输入参数为接收到的数据包。
UINT8 FOE_ServiceInd(TFOEMBX MBXMEM * pFoeInd)
{
...
初始化状态,计算接收的FOE数的长度
...
switch ( SWAPWORD(pFoeInd->FoeHeader.OpCode) )
{
case ECAT_FOE_OPCODE_RRQ:
如果是读请求,调用FOE_Read处理,返回状态值。
case ECAT_FOE_OPCODE_WRQ:
如果是写请求,调用FOE_Write处理,返回状态值。
case ECAT_FOE_OPCODE_DATA:
如果是数据包,调用FOE_Data处理,返回状态值
case ECAT_FOE_OPCODE_ACK:
如果是应答包,调用FOE_Ack处理,返回状态值
case ECAT_FOE_OPCODE_ERR:
如果是错误码,调用FOE_Error处理,返回状态值
case ECAT_FOE_OPCODE_BUSY:
如果是重发请求,调用FOE_Busy处理,返回状态值
if ( nextState <= FOE_MAXDATA )
{
}
else if ( nextState <= FOE_MAXBUSY )
{
...
根据状态值,对数据进行填充,然后发送
....
return 0;
}
因此我们需要实现的就是FOE_Read、FOE_Write、FOE_Data、FOE_Ack、FOE_Error、FOE_Busy这几个函数。
2.FOE_Read
FOE_Read调用的pAPPL_FoeRead函数;FOE_Read里面主要就是判断一下文件名和密钥,然后读取第一包数据包
/**
* @******************************************************************************:
* @func: [recv_RRQ]
* @description: 接收到读请求信号调用函数
* @note:
* @author: gxf
* @param [UINT16 MBXMEM *] pName 请求的文件名
* @param [UINT16] nameSize 请求的文件名长度
* @param [UINT32] password 密钥
* @param [UINT16] maxBlockSize 读取数据长度
* @param [UINT16] *pData 读取数据的存储地址
* @return [UINT16] 返回读取的数据的长度
* @==============================================================================:
*/
UINT16 recv_RRQ(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password, UINT16 maxBlockSize, UINT16 *pData)
{
UINT16 i = 0;
UINT16 readsize = 0;
CHAR aReadFileName[MAX_FILE_NAME_SIZE];
/* 判断文件名长度 */
if ((nameSize + 1) > MAX_FILE_NAME_SIZE)
{
return ECAT_FOE_ERRCODE_DISKFULL;
}
if(password != PSWD)
{
/* 密钥无效 */
return ECAT_FOE_ERRCODE_NORIGHTS;
}
/* 拷贝文件名到数组中 */
MBXSTRCPY(aReadFileName, pName, nameSize);
aReadFileName[nameSize] = '\0';
/* 判断文件名 获取文件地址 文件长度 */
if(strcmp("app1",aReadFileName) == 0)
{
u32Appaddr = APP1_ADDR;
u32FileSize = flash_read32(APP1_LEN_ADDR);
if(u32FileSize>196608) u32FileSize = 196608;
}else if(strcmp("app2",aReadFileName) == 0){
u32Appaddr = APP2_ADDR;
u32FileSize = flash_read32(APP2_LEN_ADDR);
if(u32FileSize>196608) u32FileSize = 196608;
}else{
/* 文件未找到 */
return ECAT_FOE_ERRCODE_NOTFOUND;
}
/* 判断文件大小 */
if (u32FileSize == 0)
{
return ECAT_FOE_ERRCODE_NOTFOUND;
}
readsize = maxBlockSize;
if (u32FileSize < readsize)
{
readsize = (UINT16)u32FileSize;
}
flash_read8_multiple(u32Appaddr, (uint8_t *)pData, readsize);
return readsize;
}
3.FOE_Write
FOE_Write调用的pAPPL_FoeWrite函数;FOE_Write主要就是判断一下文件名和密钥;然后做一些文件写入的准备比如打开文件,我这里是操作flash,所以是擦除flash。如果没有错误,在FOE_ServiceInd函数中会自动帮我们返回第一包ACK。
/**
* @******************************************************************************:
* @func: [recv_WRQ]
* @description: 接收到写请求的调用函数
* @note:
* @author: gxf
* @param [UINT16 MBXMEM *] pName 待写文件的名字
* @param [UINT16] nameSize 名字长度
* @param [UINT32] password 密钥
* @return [*] 0:正确;其他:错误
* @==============================================================================:
*/
UINT16 recv_WRQ(UINT16 MBXMEM * pName, UINT16 nameSize, UINT32 password)
{
if(password != PSWD)
{
/* 密钥无效 */
return ECAT_FOE_ERRCODE_NORIGHTS;
}
/* 如果当前运行的是APP2 ,则更新APP1 所在的位置 */
if(flash_read32(APP_FLAG) == APP2_ADDR)
{
u32Appaddr = APP1_ADDR;
}else{
u32Appaddr = APP2_ADDR;
}
/* 擦除flash等待写入 */
if(u32Appaddr == APP1_ADDR)
{
if(flash_erase_sector_multiple(4,5) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
}else{
if(flash_erase_sector_multiple(6,7) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
}
nFileWriteOffset = 0;
u32FileSize = 0;
return 0;
}
4.FOE_Ack
FOE_Ack调用的是pAPPL_FoeReadData,它主要就是读取数据,然后存到指定的地址中
/**
* @******************************************************************************:
* @func: [recv_ACK]
* @description: 接收到应答信号的调用函数
* @note:
* @author: gxf
* @param [UINT32] offset 读取的地址的偏移量(相对于首次读取)
* @param [UINT16] maxBlockSize 读取的数据长度
* @param [UINT16] *pData 读取数据的存储地址
* @return [UINT16] 返回读取的数据的长度
* @==============================================================================:
*/
UINT16 recv_ACK(UINT32 offset, UINT16 maxBlockSize, UINT16 *pData)
{
UINT16 readsize = 0;
/* 如果文件大小小于指针的偏移量,说明文件已经读完了*/
if (u32FileSize < offset)
{
return 0;
}
/* 剩余未读取 */
readsize = (UINT16)(u32FileSize - offset);
/* 读取大小 */
if (readsize > maxBlockSize)
{
readsize = maxBlockSize;
}
/* 读取文件大小 */
flash_read8_multiple(u32Appaddr+offset, (uint8_t *)pData, readsize);
return readsize;
}
5.FOE_Data
FOE_Data调用的pAPPL_FoeWriteData,主要就是向文件中写入数据
/**
* @******************************************************************************:
* @func: [recv_DATA]
* @description: 接收到数据包的调用函数
* @note:
* @author: gxf
* @param [UINT16 MBXMEM *] pData 带写入数据的地址
* @param [UINT16] Size 写入数据的长度
* @param [BOOL] bDataFollowing 是否还有数据数据
* @return [*] 0:正确;其他:错误
* @==============================================================================:
*/
UINT16 recv_DATA(UINT16 MBXMEM * pData, UINT16 Size, BOOL bDataFollowing)
{
if ((nFileWriteOffset + Size) > MAX_FILE_SIZE)
{
return ECAT_FOE_ERRCODE_DISKFULL;
}
if (Size)
{
/* 写flash */
if(flash_write8_multiple(u32Appaddr+nFileWriteOffset,(uint8_t *)pData,Size)!=0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
}
/* 后面是否还有数据 */
if (bDataFollowing)
{
nFileWriteOffset += Size;
}
else
{
/* 最后一包数据 计算总的接收文件长度 */
u32FileSize = nFileWriteOffset + Size;
nFileWriteOffset = 0;
/* 保存下次上电运行的APP地址 和 APP的长度*/
UINT32 app1length = flash_read32(APP1_LEN_ADDR);
UINT32 app2length = flash_read32(APP2_LEN_ADDR);
UINT32 appflag = flash_read32(APP_FLAG);
if(u32Appaddr == APP1_ADDR)
app1length = u32FileSize;
else
app2length = u32FileSize;
if(flash_erase_sector(2) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
if(flash_write32(APP_FLAG,appflag) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
if(flash_write32(APP1_LEN_ADDR,app1length) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
if(flash_write32(APP2_LEN_ADDR,app2length) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
if(flash_write32(UPDATE_APP_FLAG,u32Appaddr) != 0)
return ECAT_FOE_ERRCODE_FLASH_ERROR;
}
return 0;
}
6.FOE_Error
FOE_Error调用了pAPPL_FoeError,由于是测试程序,所以这个函数我没有去修改,当接收到错误包的时候,我们可以进行一些关闭文件等操作。
7.FOE_Busy
FOE_Busy调用了FOE_Ack,重新发送上一包数据。
UINT16 FOE_Busy(UINT16 done, UINT32 fileOffset, UINT16 MBXMEM * pData)
{
return FOE_Ack(fileOffset, pData);
}
其他
上面的几个函数都是bootloaderappl.c中的,只是函数名字我改了。在MainInit函数中做一下赋值,如下:
UINT16 MainInit(void)
{
...
pAPPL_FoeRead = recv_RRQ;
pAPPL_FoeReadData = recv_ACK;
pAPPL_FoeError = NULL;
pAPPL_FoeWrite = recv_WRQ;
pAPPL_FoeWriteData = recv_DATA;
pAPPL_MainLoop = NULL;
/*ECATCHANGE_END(V5.12) ECAT8*/
...
}
TwinCAT测试
测试很简单,我是使用TwinCAT作为主机进行测试的。OP模式下和Bootstrap模式下都可以进行上传和下载。图中标记的Download是将PC端的文件写入从站的。Upload是将从站中的文件读取到PC端。