/* 连接到FTP服务器 ,从FTP服务器中下载文件,下载逻辑 */ /* includes ----------------------------------------------------------*/ #include "EC800_FTP_OTA.h" /* typedef -----------------------------------------------------------*/ ftpInfo s_ftpInfo = { .account = "cdzupdate", .passWord = "cdz2021!Z", .fileType = 1, .transmode = 1, .rsptimeout = 90, .ftpAddr = "39.98.211.244", .ftpPort = 21, .textDirectory = "/data/cdz", .textName = "centralCtrSys.bin", }; /* define ------------------------------------------------------------*/ // 登陆到FTP服务器 // 第一步:配置和激活 PDP 上下文 #define AT_QIACT_1 "AT+QIACT=1\r\n" // 激活 PDP 上下文 1 #define AT_QIACT_query "AT+QIACT?\r\n" // 查询 PDP 上下文状态 #define AT_QFTPCFG_ID_1 "AT+QFTPCFG=\"contextid\",1\r\n" // 查询 PDP 上下文状态 // 第二步: 配置用户账号和传输设置 #define AT_QFTPCFG_ACCOUNT "AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n" // 设置用户名和密码。 #define AT_QFTPCFG_FILE(fileType) "AT+QFTPCFG=\"filetype\"," #fileType "\r\n" // 设置文件类型为ASCII AT+QFTPCFG="filetype",1 #define AT_QFTPCFG_TRANS(mode) "AT+QFTPCFG=\"transmode\"," #mode "\r\n" // 设置为被动传输方式AT+QFTPCFG="transmode",1 #define AT_QFTPCFG_TIMEOUT(time) "AT+QFTPCFG=\"rsptimeout\"," #time "\r\n" // 设置最大响应时间(默认为90秒) //第三步:登录FTP服务器。 #define AT_QFTPOPEN(addr,port) "AT+QFTPOPEN=\"" #addr "\"," #port "\r\n" // 登录FTP服务器。 // 从FTP服务器下载文件 本项目文件较小,选择直接通过COM口输出下载数据 #define AT_QFTPCWD(directory) "AT+QFTPCWD=\"" #directory "\"\r\n" // 设置当前目录。 #define AT_QFTPSIZE(textName) "AT+QFTPSIZE=\"" #textName "\"\r\n" // 查询 FTP(S)服务器 test_my1.txt 文件大小 #define AT_QFTPGET(textName, startByte, downloadNum) \ "AT+QFTPGET=\"" #textName "\",\"COM:\"," #startByte "," #downloadNum "\r\n" // 下载文件, 通过COM口输出特定字段的数据 // 一次下载的字节个数 #define DOWNLOAD_BYTE_LEN (512) /* macro -------------------------------------------------------------*/ /* variables ---------------------------------------------------------*/ /* function prototypes -----------------------------------------------*/ /* 1.登陆到FTP服务器--------------------------------------------------------*/ static uint8_t logIn_step = 0; /** * @brief 通过EC800登录FTP * @param NONE * @note NONE * @retval 无 */ void EC800_LogIn_FTP(void){ char* found = NULL; uint8_t errorCnt = 0; // 错误计数 ftpInfo *p_ftpInfo = &s_ftpInfo; char atCmd[100] = {0}; // 静态缓冲区用于存放AT命令 p_ftpInfo->fileType = p_ftpInfo->fileType; // 避免报警告 switch(logIn_step){ case 0: // 配置和激活 PDP 上下文 // EC800M_SendCommand(AT_QIACT_1); // // found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); // // if (found != NULL) { // printf("Activation successful\r\n"); // }else{ // errorCnt++; // printf("Activation unsuccessful\r\n"); // } EC800M_SendCommand(AT_QIACT_query); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("PDP Context Status OK\r\n"); }else{ errorCnt++; printf("PDP Context Status error\r\n"); } EC800M_SendCommand(AT_QFTPCFG_ID_1); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("Set PDP context to 1.\r\n"); }else{ errorCnt++; printf("ERROR! Set PDP context to 1.\r\n"); } if(errorCnt == 0){ logIn_step = 1; } break; case 1: // 配置用户账号和传输设置 snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"account\",\"%s\",\"%s\"\r\n", p_ftpInfo->account, p_ftpInfo->passWord); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"filetype\",%d\r\n", p_ftpInfo->fileType); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"transmode\",%d\r\n", p_ftpInfo->transmode); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set to passive transfer mode.\r\n"); }else{ errorCnt++; printf("ERROR! Set to passive transfer mode.\r\n"); } snprintf(atCmd, sizeof(atCmd), "AT+QFTPCFG=\"rsptimeout\",%d\r\n", p_ftpInfo->rsptimeout); EC800M_SendCommand(atCmd); found = EC800M_RecRespond(g_usart3_rx_buf, AT_RESP_OK); if (found != NULL) { printf("FINISH! Set username and password.\r\n"); }else{ errorCnt++; printf("ERROR! Set username and password.\r\n"); } if(errorCnt == 0){ logIn_step = 2; } break; case 2: // 登录FTP服务器。 snprintf(atCmd, sizeof(atCmd), "AT+QFTPOPEN=\"%s\",%d\r\n", p_ftpInfo->ftpAddr, p_ftpInfo->ftpPort); EC800M_SendCommand(atCmd); errorCnt = Accept_and_Compare_Str("+QFTPOPEN: 0,0"); if(errorCnt == 1){ errorCnt = 0; printf("FINISH! Log in to the FTP server.\r\n"); }else{ printf("ERROR! Log in to the FTP server.\r\n"); } if(errorCnt == 0){ logIn_step = 3; } break; default : break; } } /* 2.从FTP服务器上下载文件--------------------------------------------------------*/ /** * @brief 剔除除去固件的其他的数据 * @param NONE * @note NONE * @retval 无 */ int extract_data_as_uint32(char* input, uint32_t* output, size_t output_size) { const char* start_marker = "\r\nCONNECT\r\n"; const char* end_marker = "\r\nOK\r\n"; const char* start; const char* end; // 寻找开始标记 start = strstr(input, start_marker); if (start != NULL) { // 如果发现开始标记,移动指针越过开始标记 start += strlen(start_marker); } else { // 如果没有发现开始标记,则从输入串的开始处理 start = input; } // 寻找结束标记 end = search_sequence(start, USART3_REC_LEN - (start - input), end_marker, strlen(end_marker)); if (end == NULL) { return HAL_ERROR; } // 计算要提取数据的长度 size_t data_length = end - start; // 确保提取的数据长度为4的倍数,适用于 uint32_t if (data_length % sizeof(uint32_t) != 0) { return HAL_ERROR; } // 计算 uint32_t 的数量 size_t data_length_uint32 = data_length / sizeof(uint32_t); if (data_length_uint32 > output_size) { // 确保输出缓冲区能够存放提取的数据 return HAL_ERROR; } // 提取数据并转换为 uint32_t 数组 memcpy(output, start, data_length_uint32 * sizeof(uint32_t)); memset(input, 0, USART3_REC_LEN); // 清空输入缓冲区 return HAL_OK; } uint8_t writeFlashPage(uint32_t address, uint32_t *pData) { if (FLASH_Erase(address, 1) != HAL_OK) { return 0; } if (FLASH_Write(address, pData, (FLASH_PAGE_SIZE / sizeof(uint32_t))) != HAL_OK) { return 0; } return 1; } #define AT_CMD_SIZE 100 uint32_t totalBytesReceived = 0; uint32_t currentFlashAddress = APP2_ADDRESS; uint32_t buffer[FLASH_PAGE_SIZE / sizeof(uint32_t)] = {0}; // 2KB缓冲区 uint32_t bufferIndex = 0; // 缓冲区当前索引(以uint32_t为单位) /** * @brief 更新固件 * @note 执行过程中不能使用printf(会触发hardfault),暂不清楚原因 * @retval 0表示未完成更新,1表示更新完成 */ uint8_t UpdateFirmware(void) { ftpInfo *p_ftpInfo = &s_ftpInfo; char atCmd[AT_CMD_SIZE] = {0}; // 用于存放AT命令的静态缓冲区 if (totalBytesReceived < p_ftpInfo->filesize) { // 计算剩余要接收的字节数 uint32_t remaining = p_ftpInfo->filesize - totalBytesReceived; uint32_t bytesToRead = remaining < DOWNLOAD_BYTE_LEN ? remaining : DOWNLOAD_BYTE_LEN; // 设置FTP信息 p_ftpInfo->startAddr = totalBytesReceived; p_ftpInfo->byteNum = bytesToRead; // 发送AT指令以开始接收 snprintf(atCmd, AT_CMD_SIZE, "AT+QFTPGET=\"%s\",\"COM:\",%u,%u\r\n", p_ftpInfo->textName, p_ftpInfo->startAddr, bytesToRead); EC800M_SendCommand(atCmd); // 只有当还有数据需要接收时,才进行下载和写入操作 if (Accept_and_Compare_Str("+QFTPGET: 0,")) { // 接收成功 // 临时缓冲区,用于存放这次接收的数据 uint32_t tempBuffer[DOWNLOAD_BYTE_LEN / sizeof(uint32_t)] = {0}; memset(tempBuffer, 0xff, DOWNLOAD_BYTE_LEN); // 保证内存数据为缺省值0xff if (extract_data_as_uint32(g_usart3_rx_buf, tempBuffer, sizeof(tempBuffer) / sizeof(uint32_t)) != HAL_OK) { // printf("ERROR! UpdateFirmware_extract_data\n"); return 0; } // 将临时缓冲区中的数据追加到主缓冲区 for (uint32_t i = 0; i < (DOWNLOAD_BYTE_LEN / sizeof(uint32_t)); i++) { buffer[bufferIndex++] = tempBuffer[i]; // 如果主缓冲区满了,写入Flash if (bufferIndex == (FLASH_PAGE_SIZE / sizeof(uint32_t))) { s_param_boot.pageNumMove += 1; if (!writeFlashPage(currentFlashAddress, buffer)) { // printf("ERROR! Write to flash failed\n"); return 0; } // 重置缓冲区索引,准备下一轮数据接收 memset(buffer, 0xFF, FLASH_PAGE_SIZE); // 保证内存数据为缺省值0xff bufferIndex = 0; currentFlashAddress += FLASH_PAGE_SIZE; } } totalBytesReceived += bytesToRead; } else { // printf("ERROR! Receive fault!\n"); return 0; } // 如果下载完成,则处理剩余数据并写入最后一部分 if (totalBytesReceived >= p_ftpInfo->filesize) { // 检查buffer是否有未写入的数据 if (bufferIndex > 0) { s_param_boot.pageNumMove += 1; // 写入最后一页数据 if (!writeFlashPage(currentFlashAddress, buffer)) { // printf("ERROR! Write to flash failed\n"); return 0; } } return 1; // 更新完成 } } return 0; // 更新未完成 } uint8_t downloadStep = 0; /** * @brief FTP服务器上下载文件 * @param NONE * @note 每一个任务周期过来下载2k的数据,以保证其他任务的正常运行 * @retval 无 */ void EC800_FTP_DownloadText(void){ uint8_t errorCnt = 0; // 错误计数 ftpInfo *p_ftpInfo = &s_ftpInfo; char atCmd[100] = {0}; // 静态缓冲区用于存放AT命令 uint8_t backTemp = 0; // 返回值临时变量 switch(downloadStep){ case 0: // 设置文件目录 snprintf(atCmd, sizeof(atCmd), "AT+QFTPCWD=\"%s\"\r\n", p_ftpInfo->textDirectory); EC800M_SendCommand(atCmd); errorCnt = Accept_and_Compare_Str("+QFTPCWD: 0,0"); if (errorCnt == 1) { errorCnt = 0; printf("FINISH! Set the file directory.\r\n"); }else{ printf("ERROR! Set the file directory.\r\n"); } if(errorCnt == 0){ downloadStep = 1; } break; case 1: // 获取文件长度 snprintf(atCmd, sizeof(atCmd), "AT+QFTPSIZE=\"%s\"\r\n", p_ftpInfo->textName); EC800M_SendCommand(atCmd); errorCnt = Accept_and_Compare_Str("+QFTPSIZE: 0,"); if (errorCnt == 1) { errorCnt = 0; printf("FINISH! Get the length of the file.\r\n"); // 使用sscanf从字符串中解析整数 if(sscanf(g_usart3_rx_buf, "\r\n+QFTPSIZE: %*d,%u", &(p_ftpInfo->filesize)) == 1) { // 解析成功,filesize变量现在包含值1000 printf("The filesize is: %u\n", p_ftpInfo->filesize); } else { // 解析失败 errorCnt++; printf("Failed to parse the filesize.\n"); } }else{ printf("ERROR! Get the length of the file.\r\n"); } if(errorCnt == 0){ downloadStep = 2; } break; case 2: // 判断当前所处APP地址 选取更新地址 currentFlashAddress = APP2_ADDRESS; downloadStep = 3; break; case 3: // 更新固件 backTemp = UpdateFirmware(); if(backTemp == 1){ downloadStep = 4; } break; case 4: // 固件更新完成,设置更新标志,并复位 s_param_boot.updateFlag = 1; // 需要更新固件并搬移 Write_paramArea(); downloadStep = 5; break; default : break; } } /* 3. OTA升级从FTP流程--------------------------------------------------------*/ uint8_t upgradeStep = 0; /** * @brief OTA升级从FTP * @param NONE * @note NONE * @retval 无 */ void EC800_FTP_OTA_Upgrade(void){ ec800Date *p_ec800Date = &s_ec800Date; // 等待ec800模块初始化完成 并满足更新条件 if(!((p_ec800Date->ec800InitFlag) && (p_ec800Date->hardwareUpdate))){ return; } switch (upgradeStep){ case 0: // 登陆到FTP服务器 EC800_LogIn_FTP(); if(logIn_step == 3){ upgradeStep = 1; } break; case 1: // 从FTP服务器上下载文件 EC800_FTP_DownloadText(); if(downloadStep == 5){ upgradeStep = 2; } break; case 2: // 复位升级 osDelay(1000); HAL_NVIC_SystemReset(); break; default: break; } }