目录
[Ymodem_Receive() 接收Ymodem协议数据](#Ymodem_Receive() 接收Ymodem协议数据)
[Receive_Packet() 接收一包数据](#Receive_Packet() 接收一包数据)
[Receive_Bytes() 与 Send_Byte()](#Receive_Bytes() 与 Send_Byte())
软件架构
线程分配:
-
线程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状态机线程
cpp
/* 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;
}
}
}
按键扫描函数
cpp
/**
* @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;
// 检测20s,20s内是否按下按键
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;
}
软复位函数
cpp
/**
* @brief System SoftReset
*
* 功能说明:系统软复位
*
* @retval None
*/
void SoftReset(void)
{
// __set_FAULTMASK(1);
NVIC_SystemReset();
}
W25Q64:下载(搬运)数据到外部flash线程搭建
需要注意,在使用之前需要先初始化W25Q64:
cpp
#include "w25qxx_Handler.h"
// ...
int main(){
// ...
W25Q64_Init();
// ...
}
资源与任务函数:

cpp
/* 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状态机线程在串口中接收到上位机下载固件命令后,才创建该线程,同时开始接收固件数据,接收一包,写入一包,使用串口空闲中断来进行队列通知:
串口空闲中断
cpp
/**
* @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
cpp
/* 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协议数据
cpp
/**
* @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() 接收一包数据
cpp
/**
* @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()
cpp
/**
* @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 MCU
VCC->3.3V
GND->GND
SCL->PB8
SDA->PB9
先添加头文件#include "AT24Cxx_Driver.h"


下载完成后发送相关信息

所遇的bug:
描述:
读取外部eeprom的相关地址的文件长度数据时,串口打印的数据全为0,但是数据的长度不可能为0

原因: AT24C02 是 I2C 接口的 EEPROM,其写入操作需要一定的时间(典型 5ms,最大 10 ms ) ,写入过程中无法响应读操作。如果在 ee_WriteBytes 后立即调用 ee_ReadBytes,此时 EEPROM 可能还在执行写入,读操作会失败,返回默认的 0。
解决方法:
加入10ms的延时
