2026/4/17 0:46:39
网站建设
项目流程
县区网站集约化平台建设研究,网站开发保密合同范本,网络直播平台,网站开发标书范本目录 软件架构
线程分配#xff1a;
OTA升级协议定义#xff1a;编辑
OTA升级流程图#xff1a;
代码编写#xff1a;
OTA状态机搭建#xff1a;
OTA状态机#xff1a;
SWC_OTA.h文件定义枚举
宏与变量
搭建ota状态机线程
按键扫描函数
软复位函数
W25Q64OTA升级协议定义编辑OTA升级流程图代码编写OTA状态机搭建OTA状态机SWC_OTA.h文件定义枚举宏与变量搭建ota状态机线程按键扫描函数软复位函数W25Q64下载(搬运)数据到外部flash线程搭建资源与任务函数串口空闲中断Ymodem.c接收函数解析ymodem.hYmodem_Receive() 接收Ymodem协议数据Receive_Packet() 接收一包数据Receive_Bytes() 与 Send_Byte()ATC24C02功能搭建地址定义所遇的bug描述解决方法软件架构线程分配线程1:Default_taskHandle(应用线程)线程2:OTA_taskHandle(OTA状态管理线程)线程3:DownloadAppData_taskHandle(下载数据到w25q中)OTA升级协议定义OTA升级流程图代码编写OTA状态机搭建OTA状态机SWC_OTA.h文件定义枚举#define REC_MAX_NUM 1030 // UART receive max num of bytes // for trigger IDLE irq, the true REC_MAX_NUM is 1029 typedef enum { WAIT_REQ_DOWNLOAD, OTA_DOWNLOADING, WAIT_REQ_UPDATE, OTA_END, }E_Ota_State_t;宏与变量/* Define -----------------------------------------------*/ #define UPPER_MACHINE_UART (huart1) // 与上位机通信的串口 /* Variable ---------------------------------------------*/ /* state machine thread -------------*/ static E_Ota_State_t s_g_OTA_State WAIT_REQ_DOWNLOAD; static uint8_t s_u8_OTA_Cmd[4] { 0 }; // Using IDLE irq receive cmd from upper computer /* Ymodem */ static int32_t s_g_file_size 0; // receive file size uint8_t g_u8_YmodemRec_A[REC_MAX_NUM] { 0 }; // receive data buffer uint8_t g_u8_YmodemRec_B[REC_MAX_NUM] { 0 }; // receive data buffer extern int32_t packet_length;搭建ota状态机线程/* OTA state machine */ QueueHandle_t Q_Ymodem_Rec_Len NULL; // Queue for Ymodem receive length osThreadId_t ota_state_machine_thread_Handle; const osThreadAttr_t ota_state_machine_thread_attributes { .name ota_state_machine_thread, .stack_size 128 * 4, .priority (osPriority_t)osPriorityNormal1, }; void ota_state_machine_thread(void* argument) { /* Variable */ uint8_t u8_rec_length 0; uint8_t u8_Ackcmd[3] { 0x44,0x55,0x66 }; // create queue Q_Ymodem_Rec_Len xQueueCreate(1, sizeof(uint16_t)); uint8_t t_u8_OTAstate 0x00; // 0x00:没有APP更新 // 0x11:APP数据下载过程中 // 0x22:APP固件下载搬运完成请求更新 // 清除eeprom的OTA状态 ee_WriteBytes(t_u8_OTAstate, 0x00, 1); // 没有APP更新 for (;;) { switch (s_g_OTA_State) { case WAIT_REQ_DOWNLOAD: // 阻塞式接收串口命令 HAL_UARTEx_ReceiveToIdle_DMA(UPPER_MACHINE_UART, s_u8_OTA_Cmd, 4); xQueueReceive(Q_Ymodem_Rec_Len, u8_rec_length, portMAX_DELAY); /* 校验数据长度 */ if (3 u8_rec_length) { /* 判断是否符合命令内容 */ if (0x11 s_u8_OTA_Cmd[0] 0x22 s_u8_OTA_Cmd[1] 0x33 s_u8_OTA_Cmd[2]) { /* 切换状态 */ s_g_OTA_State OTA_DOWNLOADING; // eeprom内写入新的OTA状态 t_u8_OTAstate 0x11; ee_WriteBytes(t_u8_OTAstate, 0x00, 1); // APP数据下载过程中 /* 创建线程以及相关队列 */ // 创建线程 DownloadAppDataTaskHandle osThreadNew(DownloadAppData_task, NULL, DownloadAppDataTaskattributes); // 创建队列 Q_AppDataBuffer xQueueCreate(2, sizeof(uint8_t*)); // 创建互斥锁 Semaphore_ExtFlashState xSemaphoreCreateMutex(); } else { for (uint8_t i 0;i 4;i) { /* 清掉数据 */ s_u8_OTA_Cmd[i] 0; } } } else { for (uint8_t i 0;i 4;i) { /* 清掉数据 */ s_u8_OTA_Cmd[i] 0; } } break; case OTA_DOWNLOADING: /* 进入固件下载并校验下载状态 */ s_g_file_size Ymodem_Receive(g_u8_YmodemRec_A, g_u8_YmodemRec_B); if (s_g_file_size 0) { log_i(file_size [%d], s_g_file_size); /* 切换状态发送应答告诉上位机数据下载完毕 */ s_g_OTA_State WAIT_REQ_UPDATE; HAL_UART_Transmit(UPPER_MACHINE_UART, u8_Ackcmd, 3, 1000); // 拿互斥锁保证w25q已经写入完成 xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY); xSemaphoreGive(Semaphore_ExtFlashState); /* 最后将数据缓冲区中剩余的数据写入 */ W25Q64_WriteData_End(); /* 清除一切生成的资源队列、线程后面用不到了 */ // 删除线程 vTaskDelete(DownloadAppDataTaskHandle); // 删除队列 vQueueDelete(Q_AppDataBuffer); // 删除互斥量 vSemaphoreDelete(Semaphore_ExtFlashState); // eeprom内写入新的OTA状态 t_u8_OTAstate 0x22; ee_WriteBytes(t_u8_OTAstate, 0x00, 1); // APP固件下载完成请求更新 // eeprom内写入文件大小 ee_WriteBytes((uint8_t*)(s_g_file_size), 0x01, 4); // 写入4字节的文件大小 #if 0 // test eeprom osDelay(1); uint8_t t_u8_readstate 0; int32_t t_32_filesize 0; if (1 ee_ReadBytes(t_u8_readstate, 0x00, 1)) { //log_i(ee_ReadBytes success: OTAstate [%x], t_u8_readstate); HAL_UART_Transmit(huart1,t_u8_readstate,1,1000); } if (1 ee_ReadBytes((uint8_t*)(t_32_filesize), 0x01, 4)) { //log_i(ee_ReadBytes success: new filesize [%d], t_32_filesize); HAL_UART_Transmit(huart1,(uint8_t*)(t_32_filesize),4,1000); } #endif } else { log_e(error: Ymodem_Receive failed! file_size [%d], s_g_file_size); s_g_file_size 0; /* 切换状态清除一切生成的资源队列、线程 */ s_g_OTA_State WAIT_REQ_DOWNLOAD; // 删除线程 vTaskDelete(DownloadAppDataTaskHandle); // 删除队列 vQueueDelete(Q_AppDataBuffer); // 删除互斥量 vSemaphoreDelete(Semaphore_ExtFlashState); } break; case WAIT_REQ_UPDATE: // 阻塞式接收串口命令 HAL_UARTEx_ReceiveToIdle_DMA(UPPER_MACHINE_UART, s_u8_OTA_Cmd, 4); xQueueReceive(Q_Ymodem_Rec_Len, u8_rec_length, portMAX_DELAY); /* 校验数据长度 */ if (3 u8_rec_length) { /* 判断是否符合命令内容 */ if (0x77 s_u8_OTA_Cmd[0] 0x88 s_u8_OTA_Cmd[1] 0x99 s_u8_OTA_Cmd[2]) { /* 切换状态 */ s_g_OTA_State OTA_END; } else { for (uint8_t i 0;i 4;i) { /* 清掉数据 */ s_u8_OTA_Cmd[i] 0; } } } else { for (uint8_t i 0;i 4;i) { /* 清掉数据 */ s_u8_OTA_Cmd[i] 0; } } break; case OTA_END: if (1 key_Scan()) { /* 执行软复位 */ SoftReset(); } else { /* 切换状态如果重启则会进入bootloader升级 */ s_g_OTA_State WAIT_REQ_DOWNLOAD; } break; default: break; } } }按键扫描函数/** * brief Scan key press * * 功能说明检测按键20s内是否被按下 * * retval 1: key has been pressed * -1: time out, key has not been pressed */ static int8_t key_Scan() { uint16_t Key_Scan_TimeCnt 0; // 检测20s20s内是否按下按键 for (Key_Scan_TimeCnt 0;Key_Scan_TimeCnt 400;Key_Scan_TimeCnt) { if (GPIO_PIN_RESET HAL_GPIO_ReadPin(Key_GPIO_Port, Key_Pin)) { osDelay(20); if (GPIO_PIN_RESET HAL_GPIO_ReadPin(Key_GPIO_Port, Key_Pin)) { /* 判断为按下 */ return 1; } } osDelay(50); } /* 判断为超时 */ return -1; }软复位函数/** * brief System SoftReset * * 功能说明系统软复位 * * retval None */ void SoftReset(void) { // __set_FAULTMASK(1); NVIC_SystemReset(); }W25Q64下载(搬运)数据到外部flash线程搭建需要注意在使用之前需要先初始化W25Q64#include w25qxx_Handler.h // ... int main(){ // ... W25Q64_Init(); // ... }资源与任务函数/* Download data from data buffer to external flash */ QueueHandle_t Q_AppDataBuffer NULL; SemaphoreHandle_t Semaphore_ExtFlashState NULL; osThreadId_t DownloadAppDataTaskHandle; const osThreadAttr_t DownloadAppDataTaskattributes { .name DownloadAppData_Task, .stack_size 128 * 4, .priority (osPriority_t)osPriorityNormal1, }; /** * brief DownloadAppData_task thread * * 功能说明将接收到的APP数据搬运到外部flash中 * * param[in] argument任务参数 * retval none */ void DownloadAppData_task(void* argument) { uint8_t* pu8_buffer NULL; int32_t* p32_size NULL; // 第一包数据接收的肯定是size xQueueReceive(Q_AppDataBuffer, p32_size, portMAX_DELAY); // 需要擦除掉flash size对应的所有扇区 // TODO // 释放互斥量 xSemaphoreGive(Semaphore_ExtFlashState); for (;;) { // 后续的数据包发送的队列消息就是buffer地址 xQueueReceive(Q_AppDataBuffer, pu8_buffer, portMAX_DELAY); // 通过互斥量锁住buffer xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY); if (pu8_buffer NULL) { continue; } // 执行写入W25q的逻辑 W25Q64_WriteData(pu8_buffer, (uint32_t)packet_length); xSemaphoreGive(Semaphore_ExtFlashState); } }需要注意的是该线程在MCU启动后并不存在而是在OTA状态机线程在串口中接收到上位机下载固件命令后才创建该线程同时开始接收固件数据接收一包写入一包使用串口空闲中断来进行队列通知串口空闲中断/** * brief Reception Event Callback (Rx event notification called after use of advanced reception service). * param huart UART handle * param Size Number of data available in application reception buffer (indicates a position in * reception buffer until which, data are available) * retval None */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t Size) { BaseType_t xHigherPriorityTaskWoken pdFALSE; // 判断是串口1 if (huart-Instance USART1) { // 确定是串口空闲中断触发的回调函数获取已经传输值 if (1 __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) { // 通过队列将接收长度发送到 Q_Ymodem_Rec_Len 队列 if (pdTRUE xQueueSendFromISR(Q_Ymodem_Rec_Len, Size, 0)) { xHigherPriorityTaskWoken pdTRUE; // 发送完毕后我们进行手动切换任务 } HAL_UART_DMAStop(huart); } // DMA传输完成后的操作 if (REC_MAX_NUM Size) // DMA全满中断 { // 使用空闲中断完成任务不应该进入全满中断 log_e(error: DMA_Cplt_Callback!); } // 触发任务切换 portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }Ymodem.c接收函数解析ymodem.h/* Define to prevent recursive inclusion -------------------------------------*/ #ifndef _YMODEM_H_ #define _YMODEM_H_ /* Includes ------------------------------------------------------------------*/ #include stdint.h /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ #define PACKET_SEQNO_INDEX (1) #define PACKET_SEQNO_COMP_INDEX (2) #define PACKET_HEADER (3) #define PACKET_TRAILER (2) #define PACKET_OVERHEAD (PACKET_HEADER PACKET_TRAILER) #define PACKET_SIZE (128) #define PACKET_1K_SIZE (1024) #define FILE_NAME_LENGTH (256) #define FILE_SIZE_LENGTH (16) #define SOH (0x01) /* start of 128-byte data packet */ #define STX (0x02) /* start of 1024-byte data packet */ #define EOT (0x04) /* end of transmission 结束传输 */ #define ACK (0x06) /* acknowledge 确认*/ #define NAK (0x15) /* negative acknowledge 无确认 */ #define CA (0x18) /* two of these in succession aborts transfer 其中两个相继中止传输 */ #define CRC16 (0x43) /* C 0x43, request 16-bit CRC 请求CRC */ #define ABORT1 (0x41) /* A 0x41, abort by user */ #define ABORT2 (0x61) /* a 0x61, abort by user */ #define NAK_TIMEOUT (0x100000)//无确认超时时间 #define MAX_ERRORS (3)//最大错误数量 /* User */ #define YMODEM_UART (huart1) /* 收发Ymodem协议的串口 */ /* Exported macro ------------------------------------------------------------*/ /* Exported functions ------------------------------------------------------- */ int32_t Ymodem_Receive(uint8_t* buf1, uint8_t* buf2); uint8_t Ymodem_Transmit (uint8_t *,const uint8_t* , uint32_t ); uint16_t UpdateCRC16(uint16_t crcIn, uint8_t byte); uint16_t Cal_CRC16(const uint8_t* data, uint32_t size); uint8_t CalChecksum(const uint8_t* data, uint32_t size); int32_t Ymodem_CheckResponse(uint8_t c); void Ymodem_PrepareIntialPacket(uint8_t *data, const uint8_t* fileName, uint32_t *length); void Ymodem_PreparePacket(uint8_t *SourceBuf, uint8_t *data, uint8_t pktNo, uint32_t sizeBlk); void Ymodem_SendPacket(uint8_t *data, uint16_t length);Ymodem_Receive() 接收Ymodem协议数据/** * brief Receive a file using the ymodem protocol * param buf: Address of the first byte * retval The size of the file */ uint8_t file_size[FILE_SIZE_LENGTH], * file_ptr, * buf_ptr; int32_t i, j, packet_length, session_done, file_done, packets_received, errors, session_begin, size 0; int32_t Ymodem_Receive(uint8_t* buf1, uint8_t* buf2) { uint8_t* packet_data buf1; // FlashDestination BACKUP_APP_FLASH_ADDR; // TODO for (session_done 0, errors 0, session_begin 0; ;)//初始化变量进入循环 { for (packets_received 0, file_done 0; ;) { // 接收一整包数据到数据缓冲区 switch (Receive_Packet(packet_data, packet_length, 1000)) { // 通过Receive_Packet的返回值判断包的状态 case 0: errors 0; switch (packet_length) // 通过Receive_Packet()返回的包长来判断包的状态 { /* Abort by sender */ case - 1: Send_Byte(ACK); return 0; /* End of transmission */ case 0: Send_Byte(ACK); file_done 1; break; /* Normal packet */ default: if ((packet_data[PACKET_SEQNO_INDEX] 0xff) ! (packets_received 0xff)) { Send_Byte(NAK); } else { if (packets_received 0) // 文件的第一包数据文件的信息 { /* Filename packet */ if (packet_data[PACKET_HEADER] ! 0) { /* Filename packet has valid data */ for (i 0, file_ptr packet_data PACKET_HEADER; (*file_ptr ! 0) (i FILE_NAME_LENGTH);) { file_name[i] *file_ptr; } file_name[i] \0; for (i 0, file_ptr ; (*file_ptr ! ) (i FILE_SIZE_LENGTH);) { file_size[i] *file_ptr; } file_size[i] \0; Str2Int(file_size, size); // 通知任务需要擦除某些w25q的扇区根据size的大小决定 int32_t* p32_size size; if (pdTRUE ! xQueueSend(Q_AppDataBuffer, p32_size, 0)) { log_e(error: xQueueSend Q_AppDataBuffer failed! packets_received [%d], packets_received); } // 切换buffer if (packet_data buf1) { packet_data buf2; } else { packet_data buf1; } Send_Byte(ACK); Send_Byte(CRC16); } /* Filename packet is empty, end session */ else { Send_Byte(ACK); file_done 1; session_done 1; break; } } /* Data packet */ else // 文件的后续数据包真正的数据包 { // 通知任务 uint8_t* data_ptr packet_data PACKET_HEADER; if (pdTRUE ! xQueueSend(Q_AppDataBuffer, data_ptr, 0)) { log_e(error: xQueueSend Q_AppDataBuffer failed! packets_received [%d], packets_received); } // 获取互斥量防止下一个buffer的数据还没被w25q下载线程处理完 // 相当于进行一个任务间的同步 xSemaphoreTake(Semaphore_ExtFlashState, portMAX_DELAY); xSemaphoreGive(Semaphore_ExtFlashState); // 切换buffer if (packet_data buf1) { packet_data buf2; } else { packet_data buf1; } Send_Byte(ACK); } packets_received ; session_begin 1; } } break; case 1: Send_Byte(CA); Send_Byte(CA); return -3; default: if (session_begin 0) { errors; // log_e(errors); } if (errors MAX_ERRORS) { Send_Byte(CA); Send_Byte(CA); log_e(errors MAX_ERRORS); return 0; } Send_Byte(CRC16); break; } if (file_done ! 0) { break; } } if (session_done ! 0) { break; } } return (int32_t)size; // 返回文件的长度 }Receive_Packet() 接收一包数据/** * brief Receive a packet from sender * param data * param length 0: end of transmission * -1: abort by sender * 0: packet length * param timeout * retval 0: normally return * -1: timeout or packet error * 1: abort by user */ static int32_t Receive_Packet (uint8_t *data, int32_t *length, uint32_t timeout) { uint16_t i, packet_size; *length 0; // 接收一整包数据 if (Receive_Bytes(data, REC_MAX_NUM, timeout) ! 0) { return -1; } switch (*data) // 检测第一个字节 { case SOH: packet_size PACKET_SIZE; break; case STX: packet_size PACKET_1K_SIZE; break; case EOT: return 0; case CA: // 如果第一个字节是CA则检测下一个字节如果也是CA则终止传输 // if ((Receive_Byte(c, timeout) 0) (c CA)) if (*(data 1) CA) { *length -1; return 0; } else { log_e(Receive_Packet error); return -1; } case ABORT1: case ABORT2: return 1; default: log_e(Receive_Packet error); return -1; } if (data[PACKET_SEQNO_INDEX] ! ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) 0xff)) { return -1; } // 校验搬运长度和接收长度是否一致 if (sg_u16_uart_rec_len ! (packet_size PACKET_OVERHEAD)) { return -1; } *length packet_size; // 通过参数返回包长(128 or 1024) return 0; }Receive_Bytes() 与 Send_Byte()/** * brief Receive bytes from sender * param data_buffer: Character * param length: length of receive bytes * param timeout: Timeout * retval 0: Byte received * -1: Timeout */ static int32_t Receive_Bytes(uint8_t *data_buffer, uint16_t length, uint32_t timeout) { // 接收指定长度的数据到缓冲区 HAL_UARTEx_ReceiveToIdle_DMA(YMODEM_UART, data_buffer, length); // check the queue is valid if (Q_Ymodem_Rec_Len NULL) { log_e(error: Queue Q_Ymodem_Rec_Len is NULL!); return -1; } // 等待空闲中断发送消息队列 if (pdTRUE xQueueReceive(Q_Ymodem_Rec_Len, sg_u16_uart_rec_len, timeout)) { return 0; } return -1; } /** * brief Send a byte * param c: Character * retval 0: Byte sent */ static uint32_t Send_Byte (uint8_t c) { HAL_UART_Transmit(YMODEM_UART, c, 1, 10); return 0; }ATC24C02功能搭建使用前需要先在 main() 中确认应答地址定义先把硬件接好AT24c MCUVCC-3.3VGND-GNDSCL-PB8SDA-PB9先添加头文件#include AT24Cxx_Driver.h下载完成后发送相关信息所遇的bug描述读取外部eeprom的相关地址的文件长度数据时串口打印的数据全为0但是数据的长度不可能为0原因 AT24C02 是 I2C 接口的 EEPROM其写入操作需要一定的时间典型 5ms最大 10ms写入过程中无法响应读操作。如果在ee_WriteBytes后立即调用ee_ReadBytes此时 EEPROM 可能还在执行写入读操作会失败返回默认的 0。解决方法加入10ms的延时