STM32CubeIDE 使用Ymodem协议通过串口在线更新程序IAP

一:单片机的BootLoader

Bootloader部分移植ST官网的例程,官网例程的IAP有多个文件,为了移植方便我把多个文件合成了一个ymodem.c文件和ymodem.h文件

复制代码
/*
 * ymodem.c
 *
 *  Created on: Jun 6, 2023
 *      Author: MingYi-LZQ
 */
#include "main.h"
#include "ymodem.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Convert an Integer to a string  将整数转换为字符串
  * @param  p_str: The string output pointer   字符串输出指针
  * @param  intnum: The integer to be converted   要转换的整数
  * @retval None
  */
void Int2Str(uint8_t *p_str, uint32_t intnum)
{
  uint32_t i, divider = 1000000000, pos = 0, status = 0;

  for (i = 0; i < 10; i++)
  {
    p_str[pos++] = (intnum / divider) + 48;

    intnum = intnum % divider;
    divider /= 10;
    if ((p_str[pos-1] == '0') & (status == 0))
    {
      pos = 0;
    }
    else
    {
      status++;
    }
  }
}

/**
  * @brief  Convert a string to an integer 将字符串转换为整数
  * @param  p_inputstr: The string to be converted  要转换的字符串
  * @param  p_intnum: The integer value 整数值
  * @retval 1: Correct
  *         0: Error
  */
uint32_t Str2Int(uint8_t *p_inputstr, uint32_t *p_intnum)
{
  uint32_t i = 0, res = 0;
  uint32_t val = 0;

  if ((p_inputstr[0] == '0') && ((p_inputstr[1] == 'x') || (p_inputstr[1] == 'X')))
  {
    i = 2;
    while ( ( i < 11 ) && ( p_inputstr[i] != '\0' ) )
    {
      if (ISVALIDHEX(p_inputstr[i]))
      {
        val = (val << 4) + CONVERTHEX(p_inputstr[i]);
      }
      else
      {
        /* Return 0, Invalid input  返回0,无效输入 */
        res = 0;
        break;
      }
      i++;
    }

    /* valid result  有效的结果   */
    if (p_inputstr[i] == '\0')
    {
      *p_intnum = val;
      res = 1;
    }
  }
  else /* max 10-digit decimal input 最大10位十进制输入 */
  {
    while ( ( i < 11 ) && ( res != 1 ) )
    {
      if (p_inputstr[i] == '\0')
      {
        *p_intnum = val;
        /* return 1 */
        res = 1;
      }
      else if (((p_inputstr[i] == 'k') || (p_inputstr[i] == 'K')) && (i > 0))
      {
        val = val << 10;
        *p_intnum = val;
        res = 1;
      }
      else if (((p_inputstr[i] == 'm') || (p_inputstr[i] == 'M')) && (i > 0))
      {
        val = val << 20;
        *p_intnum = val;
        res = 1;
      }
      else if (ISVALIDDEC(p_inputstr[i]))
      {
        val = val * 10 + CONVERTDEC(p_inputstr[i]);
      }
      else
      {
        /* return 0, Invalid input */
        res = 0;
        break;
      }
      i++;
    }
  }

  return res;
}

/**
  * @brief  Print a string on the HyperTerminal  在超级终端上打印一个字符串
  * @param  p_string: The string to be printed   要打印的字符串
  * @retval None
  */
void Serial_PutString(uint8_t *p_string)
{
  uint16_t length = 0;

  while (p_string[length] != '\0')
  {
    length++;
  }
  HAL_UART_Transmit(&huart1, p_string, length, TX_TIMEOUT);
}

/**
  * @brief  Transmit a byte to the HyperTerminal 向超终端发送一个字节
  * @param  param The byte to be sent
  * @retval HAL_StatusTypeDef HAL_OK if OK
  */
HAL_StatusTypeDef Serial_PutByte( uint8_t param )
{
  return HAL_UART_Transmit(&huart1, &param, 1, TX_TIMEOUT);
}


/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
pFunction JumpToApplication;
uint32_t JumpAddress;
uint32_t FlashProtection = 0;
uint8_t aFileName[FILE_NAME_LENGTH];

/* Private function prototypes -----------------------------------------------*/
void SerialDownload(void);
void SerialUpload(void);

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Download a file via serial port
  * @param  None
  * @retval None
  */
void SerialDownload(void)
{
  uint8_t number[11] = {0};
  uint32_t size = 0;
  COM_StatusTypeDef result;

  Serial_PutString((uint8_t *)"Waiting for the file to be sent ... (press 'a' to abort)\n\r");
  result = Ymodem_Receive( &size );
  if (result == COM_OK)
  {
    Serial_PutString((uint8_t *)"\n\n\r Programming Completed Successfully!\n\r--------------------------------\r\n Name: ");
    Serial_PutString(aFileName);
    Int2Str(number, size);
    Serial_PutString((uint8_t *)"\n\r Size: ");
    Serial_PutString(number);
    Serial_PutString((uint8_t *)" Bytes\r\n");
    Serial_PutString((uint8_t *)"-------------------\n");
  }
  else if (result == COM_LIMIT)
  {
    Serial_PutString((uint8_t *)"\n\n\rThe image size is higher than the allowed space memory!\n\r");
  }
  else if (result == COM_DATA)
  {
    Serial_PutString((uint8_t *)"\n\n\rVerification failed!\n\r");
  }
  else if (result == COM_ABORT)
  {
    Serial_PutString((uint8_t *)"\r\n\nAborted by user.\n\r");
  }
  else
  {
    Serial_PutString((uint8_t *)"\n\rFailed to receive the file!\n\r");
  }
}

/**
  * @brief  Upload a file via serial port.
  * @param  None
  * @retval None
  */
void SerialUpload(void)
{
  uint8_t status = 0;

  Serial_PutString((uint8_t *)"\n\n\rSelect Receive File\n\r");

  HAL_UART_Receive(&huart1, &status, 1, RX_TIMEOUT);
  if ( status == CRC16)
  {
    /* Transmit the flash image through ymodem protocol */
    status = Ymodem_Transmit((uint8_t*)APPLICATION_ADDRESS, (const uint8_t*)"UploadedFlashImage.bin", USER_FLASH_SIZE);

    if (status != 0)
    {
      Serial_PutString((uint8_t *)"\n\rError Occurred while Transmitting File\n\r");
    }
    else
    {
      Serial_PutString((uint8_t *)"\n\rFile uploaded successfully \n\r");
    }
  }
}

/**
  * @brief  Display the Main Menu on HyperTerminal
  * @param  None
  * @retval None
  */
void Main_Menu(void)
{
  uint8_t key = 0;

  Serial_PutString((uint8_t *)"\r\n======================================================================");
  Serial_PutString((uint8_t *)"\r\n=              (C) COPYRIGHT 2016 STMicroelectronics                 =");
  Serial_PutString((uint8_t *)"\r\n=                                                                    =");
  Serial_PutString((uint8_t *)"\r\n=  STM32F1xx In-Application Programming Application  (Version 1.0.0) =");
  Serial_PutString((uint8_t *)"\r\n=                                                                    =");
  Serial_PutString((uint8_t *)"\r\n=                                   By MCD Application Team          =");
  Serial_PutString((uint8_t *)"\r\n======================================================================");
  Serial_PutString((uint8_t *)"\r\n\r\n");

  /* Test if any sector of Flash memory where user application will be loaded is write protected */
  FlashProtection = FLASH_If_GetWriteProtectionStatus();

  while (1)
  {

    Serial_PutString((uint8_t *)"\r\n=================== Main Menu ============================\r\n\n");
    Serial_PutString((uint8_t *)"  Download image to the internal Flash ----------------- 1\r\n\n");
    Serial_PutString((uint8_t *)"  Upload image from the internal Flash ----------------- 2\r\n\n");
    Serial_PutString((uint8_t *)"  Execute the loaded application ----------------------- 3\r\n\n");


    if(FlashProtection != FLASHIF_PROTECTION_NONE)
    {
      Serial_PutString((uint8_t *)"  Disable the write protection ------------------------- 4\r\n\n");
    }
    else
    {
      Serial_PutString((uint8_t *)"  Enable the write protection -------------------------- 4\r\n\n");
    }
    Serial_PutString((uint8_t *)"==========================================================\r\n\n");

    /* Clean the input path */
    __HAL_UART_FLUSH_DRREGISTER(&huart1);

    /* Receive key */
    HAL_UART_Receive(&huart1, &key, 1, RX_TIMEOUT);

    switch (key)
    {
    case '1' :
      /* Download user application in the Flash */
      SerialDownload();
      break;
    case '2' :
      /* Upload user application from the Flash */
      SerialUpload();
      break;
    case '3' :
      Serial_PutString((uint8_t *)"Start program execution......\r\n\n");
      /* execute the new program */
      JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
      /* Jump to user application */
      JumpToApplication = (pFunction) JumpAddress;
      /* Initialize user application's Stack Pointer */
      __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
      JumpToApplication();
      break;
    case '4' :
      if (FlashProtection != FLASHIF_PROTECTION_NONE)
      {
        /* Disable the write protection */
        if (FLASH_If_WriteProtectionConfig(FLASHIF_WRP_DISABLE) == FLASHIF_OK)
        {
          Serial_PutString((uint8_t *)"Write Protection disabled...\r\n");
          Serial_PutString((uint8_t *)"System will now restart...\r\n");
          /* Launch the option byte loading */
          HAL_FLASH_OB_Launch();
        }
        else
        {
          Serial_PutString((uint8_t *)"Error: Flash write un-protection failed...\r\n");
        }
      }
      else
      {
        if (FLASH_If_WriteProtectionConfig(FLASHIF_WRP_ENABLE) == FLASHIF_OK)
        {
          Serial_PutString((uint8_t *)"Write Protection enabled...\r\n");
          Serial_PutString((uint8_t *)"System will now restart...\r\n");
          /* Launch the option byte loading */
          HAL_FLASH_OB_Launch();
        }
        else
        {
          Serial_PutString((uint8_t *)"Error: Flash write protection failed...\r\n");
        }
      }
      break;
    default:
    Serial_PutString((uint8_t *)"Invalid Number ! ==> The number should be either 1, 2, 3 or 4\r");
    break;
    }
  }
}

/**
  * @brief  解锁闪存进行写入访问
  * @param  None
  * @retval None
  */
void FLASH_If_Init(void)
{
  /* 解锁程序内存 */
  HAL_FLASH_Unlock();

  /* 清除所有闪存标志 */
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
  /* 锁定程序内存 */
  HAL_FLASH_Lock();
}

/**
  * @brief  此函数对所有用户闪存区域进行擦除a
  * @param  start: 用户闪存区域的开始
  * @retval FLASHIF_OK : 用户闪存区域成功擦除
  *         FLASHIF_ERASEKO : 发生了错误
  */
uint32_t FLASH_If_Erase(uint32_t start)
{
  uint32_t NbrOfPages = 0;
  uint32_t PageError = 0;
  FLASH_EraseInitTypeDef pEraseInit;
  HAL_StatusTypeDef status = HAL_OK;

  /* 解锁闪存以启用闪存控制寄存器访问 *************/
  HAL_FLASH_Unlock();

  /* 获取启动用户闪存区域的扇区 */
  NbrOfPages = (USER_FLASH_END_ADDRESS - start)/FLASH_PAGE_SIZE;

  pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
  pEraseInit.PageAddress = start;
  pEraseInit.NbPages = NbrOfPages;
  status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);

  /* 锁定闪存以禁用闪存控制寄存器访问(建议保护闪存免受可能不必要的操作)*********/
  HAL_FLASH_Lock();

  if (status != HAL_OK)
  {
    /* 页面擦除时发生错误 */
    return FLASHIF_ERASEKO;
  }

  return FLASHIF_OK;
}

/* Public functions ---------------------------------------------------------*/
/**
  * @brief  此函数在闪存中写入数据缓冲区 (数据是32位对齐的).
  * @note   写入数据缓冲区后,检查闪存内容
  * @param  destination: 目标位置的起始地址
  * @param  p_source: 指向要写入数据的缓冲区的指针
  * @param  length: 数据缓冲区的长度(单位是32位字)
  * @retval uint32_t 0: 数据成功地写入闪存
  *         1: 在闪存中写入数据时发生错误
  *         2: 闪存中的写入数据不同于预期的数据
  */
uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length)
{
  uint32_t i = 0;

  /* 解锁闪存以启用闪存控制寄存器访问 *************/
  HAL_FLASH_Unlock();

  for (i = 0; (i < length) && (destination <= (USER_FLASH_END_ADDRESS-4)); i++)
  {
    /* 设备电压范围应为[2.7V至3.6V],操作将通过Word完成*/
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, destination, *(uint32_t*)(p_source+i)) == HAL_OK)
    {
     /* 检查写入的值 */
      if (*(uint32_t*)destination != *(uint32_t*)(p_source+i))
      {
        /* 闪存内容与SRAM内容不匹配 */
        return(FLASHIF_WRITINGCTRL_ERROR);
      }
      /* 增量Flash目的地地址 */
      destination += 4;
    }
    else
    {
      /* 在闪存中写入数据时发生错误 */
      return (FLASHIF_WRITING_ERROR);
    }
  }

  /* 锁定闪存以禁用闪存控制寄存器访问(建议保护闪存免受可能不必要的操作 *********/
  HAL_FLASH_Lock();

  return (FLASHIF_OK);
}

/**
  * @brief  返回应用程序闪存区域的写保护状态
  * @param  None
  * @retval 如果应用程序区域中的扇区是写保护的,则返回值是一个组合
                   可能的值 : FLASHIF_PROTECTION_WRPENABLED, FLASHIF_PROTECTION_PCROPENABLED, ...
  *        如果没有扇区是写保护的  FLASHIF_PROTECTION_NONE 被退回.
  */
uint32_t FLASH_If_GetWriteProtectionStatus(void)
{
  uint32_t ProtectedPAGE = FLASHIF_PROTECTION_NONE;
  FLASH_OBProgramInitTypeDef OptionsBytesStruct;

  /* 解锁闪存以启用闪存控制寄存器访问*************/
  HAL_FLASH_Unlock();

  /* 检查用户闪存区域内是否有写保护扇区 ****/
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct);

  /* 锁定闪存以禁用闪存控制寄存器访问(建议保护闪存免受可能不必要的操作) *********/
  HAL_FLASH_Lock();

  /* 获取已写入保护的页面 ****************************************/
  ProtectedPAGE = ~(OptionsBytesStruct.WRPPage) & FLASH_PAGE_TO_BE_PROTECTED;

  /* 检查所需页面是否已经写入保护 ***********************/
  if(ProtectedPAGE != 0)
  {
    /* 用户闪存区域内的一些扇区被写入保护 */
    return FLASHIF_PROTECTION_WRPENABLED;
  }
  else
  {
    /* 用户闪存区域内没有写保护扇区 */
    return FLASHIF_PROTECTION_NONE;
  }
}

/**
  * @brief  配置用户闪存区域的写保护状态.
  * @param  protectionstate : FLASHIF_WRP_DISABLE or FLASHIF_WRP_ENABLE the protection
  * @retval uint32_t FLASHIF_OK if change is applied.
  */
uint32_t FLASH_If_WriteProtectionConfig(uint32_t protectionstate)
{
  uint32_t ProtectedPAGE = 0x0;
  FLASH_OBProgramInitTypeDef config_new, config_old;
  HAL_StatusTypeDef result = HAL_OK;


  /* 获取页面写入保护状态 ****************************************/
  HAL_FLASHEx_OBGetConfig(&config_old);

  /* 参数表示我们是否打开或关闭保护 */
  config_new.WRPState = (protectionstate == FLASHIF_WRP_ENABLE ? OB_WRPSTATE_ENABLE : OB_WRPSTATE_DISABLE);

  /* 我们只想修改写保护 */
  config_new.OptionType = OPTIONBYTE_WRP;

  /* 没有读取保护,保持BOR和重置设置*/
  config_new.RDPLevel = OB_RDP_LEVEL_0;
  config_new.USERConfig = config_old.USERConfig;
  /* 获取已写入保护的页面 ****************************************/
  ProtectedPAGE = config_old.WRPPage | FLASH_PAGE_TO_BE_PROTECTED;

  /* 解锁闪存以启用闪存控制寄存器访问 *************/
  HAL_FLASH_Unlock();

  /* 解锁选项字节*************************************************/
  HAL_FLASH_OB_Unlock();

  /* 删除所有选项字节 ***********************************************/
  result = HAL_FLASHEx_OBErase();

  if (result == HAL_OK)
  {
    config_new.WRPPage    = ProtectedPAGE;
    result = HAL_FLASHEx_OBProgram(&config_new);
  }

  return (result == HAL_OK ? FLASHIF_OK: FLASHIF_PROTECTION_ERRROR);
}

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define CRC16_F       /* activate the CRC16 integrity 激活CRC16  */
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* @note ATTENTION - please keep this variable 32bit alligned 请保持此变量32位对齐*/
uint8_t aPacketData[PACKET_1K_SIZE + PACKET_DATA_INDEX + PACKET_TRAILER_SIZE];

/* Private function prototypes -----------------------------------------------*/
static void PrepareIntialPacket(uint8_t *p_data, const uint8_t *p_file_name, uint32_t length);
static void PreparePacket(uint8_t *p_source, uint8_t *p_packet, uint8_t pkt_nr, uint32_t size_blk);
static HAL_StatusTypeDef ReceivePacket(uint8_t *p_data, uint32_t *p_length, uint32_t timeout);
uint16_t UpdateCRC16(uint16_t crc_in, uint8_t byte);
uint16_t Cal_CRC16(const uint8_t* p_data, uint32_t size);
uint8_t CalcChecksum(const uint8_t *p_data, uint32_t size);

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Receive a packet from sender
  * @param  data
  * @param  length
  *     0: end of transmission
  *     2: abort by sender
  *    >0: packet length
  * @param  timeout
  * @retval HAL_OK: normally return
  *         HAL_BUSY: abort by user
  */
static HAL_StatusTypeDef ReceivePacket(uint8_t *p_data, uint32_t *p_length, uint32_t timeout)
{
  uint32_t crc;
  uint32_t packet_size = 0;
  HAL_StatusTypeDef status;
  uint8_t char1;

  *p_length = 0;
  status = HAL_UART_Receive(&huart1, &char1, 1, timeout);

  if (status == HAL_OK)
  {
    switch (char1)
    {
      case SOH:
        packet_size = PACKET_SIZE;
        break;
      case STX:
        packet_size = PACKET_1K_SIZE;
        break;
      case EOT:
        break;
      case CA:
        if ((HAL_UART_Receive(&huart1, &char1, 1, timeout) == HAL_OK) && (char1 == CA))
        {
          packet_size = 2;
        }
        else
        {
          status = HAL_ERROR;
        }
        break;
      case ABORT1:
      case ABORT2:
        status = HAL_BUSY;
        break;
      default:
        status = HAL_ERROR;
        break;
    }
    *p_data = char1;

    if (packet_size >= PACKET_SIZE )
    {
      status = HAL_UART_Receive(&huart1, &p_data[PACKET_NUMBER_INDEX], packet_size + PACKET_OVERHEAD_SIZE, timeout);

      /* Simple packet sanity check 简单的数据包健全检查 */
      if (status == HAL_OK )
      {
        if (p_data[PACKET_NUMBER_INDEX] != ((p_data[PACKET_CNUMBER_INDEX]) ^ NEGATIVE_BYTE))
        {
          packet_size = 0;
          status = HAL_ERROR;
        }
        else
        {
          /* Check packet CRC 检查分组CRC */
          crc = p_data[ packet_size + PACKET_DATA_INDEX ] << 8;
          crc += p_data[ packet_size + PACKET_DATA_INDEX + 1 ];
          if (Cal_CRC16(&p_data[PACKET_DATA_INDEX], packet_size) != crc )
          {
            packet_size = 0;
            status = HAL_ERROR;
          }
        }
      }
      else
      {
        packet_size = 0;
      }
    }
  }
  *p_length = packet_size;
  return status;
}

/**
  * @brief  Prepare the first block 准备第一个块区
  * @param  p_data:  output buffer
  * @param  p_file_name: name of the file to be sent
  * @param  length: length of the file to be sent in bytes
  * @retval None
  */
static void PrepareIntialPacket(uint8_t *p_data, const uint8_t *p_file_name, uint32_t length)
{
  uint32_t i, j = 0;
  uint8_t astring[10];

  /* first 3 bytes are constant 前3个字节是常数 */
  p_data[PACKET_START_INDEX] = SOH;
  p_data[PACKET_NUMBER_INDEX] = 0x00;
  p_data[PACKET_CNUMBER_INDEX] = 0xff;

  /* Filename written 文件名写的 */
  for (i = 0; (p_file_name[i] != '\0') && (i < FILE_NAME_LENGTH); i++)
  {
    p_data[i + PACKET_DATA_INDEX] = p_file_name[i];
  }

  p_data[i + PACKET_DATA_INDEX] = 0x00;

  /* file size written 写入文件大小*/
  Int2Str (astring, length);
  i = i + PACKET_DATA_INDEX + 1;
  while (astring[j] != '\0')
  {
    p_data[i++] = astring[j++];
  }

  /* padding with zeros 填充零 */
  for (j = i; j < PACKET_SIZE + PACKET_DATA_INDEX; j++)
  {
    p_data[j] = 0;
  }
}

/**
  * @brief  Prepare the data packet  准备数据包
  * @param  p_source: pointer to the data to be sent  指向要发送的数据的指针
  * @param  p_packet: pointer to the output buffer   指向输出缓冲区的指针
  * @param  pkt_nr: number of the packet
  * @param  size_blk: length of the block to be sent in bytes  以字节为单位发送的块的长度
  * @retval None
  */
static void PreparePacket(uint8_t *p_source, uint8_t *p_packet, uint8_t pkt_nr, uint32_t size_blk)
{
  uint8_t *p_record;
  uint32_t i, size, packet_size;

  /* Make first three packet */
  packet_size = size_blk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
  size = size_blk < packet_size ? size_blk : packet_size;
  if (packet_size == PACKET_1K_SIZE)
  {
    p_packet[PACKET_START_INDEX] = STX;
  }
  else
  {
    p_packet[PACKET_START_INDEX] = SOH;
  }
  p_packet[PACKET_NUMBER_INDEX] = pkt_nr;
  p_packet[PACKET_CNUMBER_INDEX] = (~pkt_nr);
  p_record = p_source;

  /* Filename packet has valid data */
  for (i = PACKET_DATA_INDEX; i < size + PACKET_DATA_INDEX;i++)
  {
    p_packet[i] = *p_record++;
  }
  if ( size  <= packet_size)
  {
    for (i = size + PACKET_DATA_INDEX; i < packet_size + PACKET_DATA_INDEX; i++)
    {
      p_packet[i] = 0x1A; /* EOF (0x1A) or 0x00 */
    }
  }
}

/**
  * @brief  Update CRC16 for input byte
  * @param  crc_in input value
  * @param  input byte
  * @retval None
  */
uint16_t UpdateCRC16(uint16_t crc_in, uint8_t byte)
{
  uint32_t crc = crc_in;
  uint32_t in = byte | 0x100;

  do
  {
    crc <<= 1;
    in <<= 1;
    if(in & 0x100)
      ++crc;
    if(crc & 0x10000)
      crc ^= 0x1021;
  }

  while(!(in & 0x10000));

  return crc & 0xffffu;
}

/**
  * @brief  Cal CRC16 for YModem Packet
  * @param  data
  * @param  length
  * @retval None
  */
uint16_t Cal_CRC16(const uint8_t* p_data, uint32_t size)
{
  uint32_t crc = 0;
  const uint8_t* dataEnd = p_data+size;

  while(p_data < dataEnd)
    crc = UpdateCRC16(crc, *p_data++);

  crc = UpdateCRC16(crc, 0);
  crc = UpdateCRC16(crc, 0);

  return crc&0xffffu;
}

/**
  * @brief  Calculate Check sum for YModem Packet
  * @param  p_data Pointer to input data
  * @param  size length of input data
  * @retval uint8_t checksum value
  */
uint8_t CalcChecksum(const uint8_t *p_data, uint32_t size)
{
  uint32_t sum = 0;
  const uint8_t *p_data_end = p_data + size;

  while (p_data < p_data_end )
  {
    sum += *p_data++;
  }

  return (sum & 0xffu);
}

/* Public functions ---------------------------------------------------------*/
/**
  * @brief  Receive a file using the ymodem protocol with CRC16.
  * @param  p_size The size of the file.
  * @retval COM_StatusTypeDef result of reception/programming
  */
COM_StatusTypeDef Ymodem_Receive ( uint32_t *p_size )
{
  uint32_t i, packet_length, session_done = 0, file_done, errors = 0, session_begin = 0 ,packets_received;
  uint32_t flashdestination, ramsource, filesize;
  uint8_t *file_ptr;
  uint8_t file_size[FILE_SIZE_LENGTH], tmp ;
  COM_StatusTypeDef result = COM_OK;

  /* Initialize flashdestination variable */
  flashdestination = APPLICATION_ADDRESS;

  while ((session_done == 0) && (result == COM_OK))
  {
    packets_received = 0;
    file_done = 0;
    while ((file_done == 0) && (result == COM_OK))
    {
      switch (ReceivePacket(aPacketData, &packet_length, DOWNLOAD_TIMEOUT))
      {
        case HAL_OK:
          errors = 0;
          switch (packet_length)
          {
            case 2:
              /* Abort by sender */
              Serial_PutByte(ACK);
              result = COM_ABORT;
              break;
            case 0:
              /* End of transmission */
              Serial_PutByte(ACK);
              file_done = 1;
              break;
            default:

            /**************if (aPacketData[PACKET_NUMBER_INDEX] != (packets_received & 0xff))****************/
            /**************:原始产生错误的判断条件//if (aPacketData[PACKET_NUMBER_INDEX] != packets_received)  */
            /**************:修改后的判断条件//if (aPacketData[PACKET_NUMBER_INDEX] != (packets_received & 0xff)) */
              /* Normal packet 正常的包*/
              if (aPacketData[PACKET_NUMBER_INDEX] != (packets_received & 0xff))
              {
                Serial_PutByte(NAK);
              }
              else
              {
                if (packets_received == 0)
                {
                  /* File name packet 文件名包*/
                  if (aPacketData[PACKET_DATA_INDEX] != 0)
                  {
                    /* File name extraction  文件名提取*/
                    i = 0;
                    file_ptr = aPacketData + PACKET_DATA_INDEX;
                    while ( (*file_ptr != 0) && (i < FILE_NAME_LENGTH))
                    {
                      aFileName[i++] = *file_ptr++;
                    }

                    /* File size extraction  文件大小提取 */
                    aFileName[i++] = '\0';
                    i = 0;
                    file_ptr ++;
                    while ( (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH))
                    {
                      file_size[i++] = *file_ptr++;
                    }
                    file_size[i++] = '\0';
                    Str2Int(file_size, &filesize);

                    /* Test the size of the image to be sent  测试要发送的数据的大小 */
                    /* Image size is greater than Flash size    数据大小大于闪存大小*/
                    if (*p_size > (USER_FLASH_SIZE + 1))
                    {
                      /* End session 结束传输*/
                      tmp = CA;
                      HAL_UART_Transmit(&huart1, &tmp, 1, NAK_TIMEOUT);
                      HAL_UART_Transmit(&huart1, &tmp, 1, NAK_TIMEOUT);
                      result = COM_LIMIT;
                    }
                    /* erase user application area  擦除用户应用程序区域*/
                    FLASH_If_Erase(APPLICATION_ADDRESS);
                    *p_size = filesize;

                    Serial_PutByte(ACK);
                    Serial_PutByte(CRC16);
                  }
                  /* File header packet is empty, end session 文件头数据包为空,结束会话 */
                  else
                  {
                    Serial_PutByte(ACK);
                    file_done = 1;
                    session_done = 1;
                    break;
                  }
                }
                else /* Data packet 数据包;*/
                {
                  ramsource = (uint32_t) & aPacketData[PACKET_DATA_INDEX];

                  /* Write received data in Flash 在闪存中写入接收到的数据 */
                  if (FLASH_If_Write(flashdestination, (uint32_t*) ramsource, packet_length/4) == FLASHIF_OK)
                  {
                    flashdestination += packet_length;
                    Serial_PutByte(ACK);
                  }
                  else /* An error occurred while writing to Flash memory  写入闪存时发生错误*/
                  {
                    /* End session */
                    Serial_PutByte(CA);
                    Serial_PutByte(CA);
                    result = COM_DATA;
                  }
                }
                packets_received ++;
                session_begin = 1;
              }
              break;
          }
          break;
        case HAL_BUSY: /* Abort actually 实际上中止 */
          Serial_PutByte(CA);
          Serial_PutByte(CA);
          result = COM_ABORT;
          break;
        default:
          if (session_begin > 0)
          {
            errors ++;
          }
          if (errors > MAX_ERRORS)
          {
            /* Abort communication */
            Serial_PutByte(CA);
            Serial_PutByte(CA);
          }
          else
          {
            Serial_PutByte(CRC16); /* Ask for a packet */
          }
          break;
      }
    }
  }
  return result;
}

/**
  * @brief  Transmit a file using the ymodem protocol
  * @param  p_buf: Address of the first byte
  * @param  p_file_name: Name of the file sent
  * @param  file_size: Size of the transmission
  * @retval COM_StatusTypeDef result of the communication
  */
COM_StatusTypeDef Ymodem_Transmit (uint8_t *p_buf, const uint8_t *p_file_name, uint32_t file_size)
{
  uint32_t errors = 0, ack_recpt = 0, size = 0, pkt_size;
  uint8_t *p_buf_int;
  COM_StatusTypeDef result = COM_OK;
  uint32_t blk_number = 1;
  uint8_t a_rx_ctrl[2];
  uint8_t i;
#ifdef CRC16_F
  uint32_t temp_crc;
#else /* CRC16_F */
  uint8_t temp_chksum;
#endif /* CRC16_F */

  /* Prepare first block - header */
  PrepareIntialPacket(aPacketData, p_file_name, file_size);

  while (( !ack_recpt ) && ( result == COM_OK ))
  {
    /* Send Packet */
    HAL_UART_Transmit(&huart1, &aPacketData[PACKET_START_INDEX], PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

    /* Send CRC or Check Sum based on CRC16_F */
#ifdef CRC16_F
    temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
    Serial_PutByte(temp_crc >> 8);
    Serial_PutByte(temp_crc & 0xFF);
#else /* CRC16_F */
    temp_chksum = CalcChecksum (&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
    Serial_PutByte(temp_chksum);
#endif /* CRC16_F */

    /* Wait for Ack and 'C' */
    if (HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK)
    {
      if (a_rx_ctrl[0] == ACK)
      {
        ack_recpt = 1;
      }
      else if (a_rx_ctrl[0] == CA)
      {
        if ((HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK) && (a_rx_ctrl[0] == CA))
        {
          HAL_Delay( 2 );
          __HAL_UART_FLUSH_DRREGISTER(&huart1);
          result = COM_ABORT;
        }
      }
    }
    else
    {
      errors++;
    }
    if (errors >= MAX_ERRORS)
    {
      result = COM_ERROR;
    }
  }

  p_buf_int = p_buf;
  size = file_size;

  /* Here 1024 bytes length is used to send the packets */
  while ((size) && (result == COM_OK ))
  {
    /* Prepare next packet */
    PreparePacket(p_buf_int, aPacketData, blk_number, size);
    ack_recpt = 0;
    a_rx_ctrl[0] = 0;
    errors = 0;

    /* Resend packet if NAK for few times else end of communication */
    while (( !ack_recpt ) && ( result == COM_OK ))
    {
      /* Send next packet */
      if (size >= PACKET_1K_SIZE)
      {
        pkt_size = PACKET_1K_SIZE;
      }
      else
      {
        pkt_size = PACKET_SIZE;
      }

      HAL_UART_Transmit(&huart1, &aPacketData[PACKET_START_INDEX], pkt_size + PACKET_HEADER_SIZE, NAK_TIMEOUT);

      /* Send CRC or Check Sum based on CRC16_F */
#ifdef CRC16_F
      temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], pkt_size);
      Serial_PutByte(temp_crc >> 8);
      Serial_PutByte(temp_crc & 0xFF);
#else /* CRC16_F */
      temp_chksum = CalcChecksum (&aPacketData[PACKET_DATA_INDEX], pkt_size);
      Serial_PutByte(temp_chksum);
#endif /* CRC16_F */

      /* Wait for Ack */
      if ((HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK) && (a_rx_ctrl[0] == ACK))
      {
        ack_recpt = 1;
        if (size > pkt_size)
        {
          p_buf_int += pkt_size;
          size -= pkt_size;
          if (blk_number == (USER_FLASH_SIZE / PACKET_1K_SIZE))
          {
            result = COM_LIMIT; /* boundary error */
          }
          else
          {
            blk_number++;
          }
        }
        else
        {
          p_buf_int += pkt_size;
          size = 0;
        }
      }
      else
      {
        errors++;
      }

      /* Resend packet if NAK  for a count of 10 else end of communication */
      if (errors >= MAX_ERRORS)
      {
        result = COM_ERROR;
      }
    }
  }

  /* Sending End Of Transmission char */
  ack_recpt = 0;
  a_rx_ctrl[0] = 0x00;
  errors = 0;
  while (( !ack_recpt ) && ( result == COM_OK ))
  {
    Serial_PutByte(EOT);

    /* Wait for Ack */
    if (HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK)
    {
      if (a_rx_ctrl[0] == ACK)
      {
        ack_recpt = 1;
      }
      else if (a_rx_ctrl[0] == CA)
      {
        if ((HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK) && (a_rx_ctrl[0] == CA))
        {
          HAL_Delay( 2 );
          __HAL_UART_FLUSH_DRREGISTER(&huart1);
          result = COM_ABORT;
        }
      }
    }
    else
    {
      errors++;
    }

    if (errors >=  MAX_ERRORS)
    {
      result = COM_ERROR;
    }
  }

  /* Empty packet sent - some terminal emulators need this to close session */
  if ( result == COM_OK )
  {
    /* Preparing an empty packet */
    aPacketData[PACKET_START_INDEX] = SOH;
    aPacketData[PACKET_NUMBER_INDEX] = 0;
    aPacketData[PACKET_CNUMBER_INDEX] = 0xFF;
    for (i = PACKET_DATA_INDEX; i < (PACKET_SIZE + PACKET_DATA_INDEX); i++)
    {
      aPacketData [i] = 0x00;
    }

    /* Send Packet */
    HAL_UART_Transmit(&huart1, &aPacketData[PACKET_START_INDEX], PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

    /* Send CRC or Check Sum based on CRC16_F */
#ifdef CRC16_F
    temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
    Serial_PutByte(temp_crc >> 8);
    Serial_PutByte(temp_crc & 0xFF);
#else /* CRC16_F */
    temp_chksum = CalcChecksum (&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
    Serial_PutByte(temp_chksum);
#endif /* CRC16_F */

    /* Wait for Ack and 'C' */
    if (HAL_UART_Receive(&huart1, &a_rx_ctrl[0], 1, NAK_TIMEOUT) == HAL_OK)
    {
      if (a_rx_ctrl[0] == CA)
      {
          HAL_Delay( 2 );
          __HAL_UART_FLUSH_DRREGISTER(&huart1);
          result = COM_ABORT;
      }
    }
  }

  return result; /* file transmitted successfully */
}
ymodem.c
复制代码
复制代码
/*
 * ymodem.h
 *
 *  Created on: Jun 6, 2023
 *      Author: MingYi-LZQ
 */

#ifndef INC_YMODEM_H_
#define INC_YMODEM_H_

/**
  * @brief  Comm status structures definition
  */
typedef enum
{
  COM_OK       = 0x00,
  COM_ERROR    = 0x01,
  COM_ABORT    = 0x02,
  COM_TIMEOUT  = 0x03,
  COM_DATA     = 0x04,
  COM_LIMIT    = 0x05
} COM_StatusTypeDef;
/**
  * @}
  */

/* Exported constants --------------------------------------------------------*/
/* Packet structure defines */
#define PACKET_HEADER_SIZE      ((uint32_t)3)
#define PACKET_DATA_INDEX       ((uint32_t)4)
#define PACKET_START_INDEX      ((uint32_t)1)
#define PACKET_NUMBER_INDEX     ((uint32_t)2)
#define PACKET_CNUMBER_INDEX    ((uint32_t)3)
#define PACKET_TRAILER_SIZE     ((uint32_t)2)
#define PACKET_OVERHEAD_SIZE    (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE - 1)
#define PACKET_SIZE             ((uint32_t)128)
#define PACKET_1K_SIZE          ((uint32_t)1024)

/* /-------- Packet in IAP memory ------------------------------------------\
 * | 0      |  1    |  2     |  3   |  4      | ... | n+4     | n+5  | n+6  |
 * |------------------------------------------------------------------------|
 * | unused | start | number | !num | data[0] | ... | data[n] | crc0 | crc1 |
 * \------------------------------------------------------------------------/
 * the first byte is left unused for memory alignment reasons                 */

#define FILE_NAME_LENGTH        ((uint32_t)64)
#define FILE_SIZE_LENGTH        ((uint32_t)16)

#define SOH                     ((uint8_t)0x01)  /* start of 128-byte data packet */
#define STX                     ((uint8_t)0x02)  /* start of 1024-byte data packet */
#define EOT                     ((uint8_t)0x04)  /* end of transmission */
#define ACK                     ((uint8_t)0x06)  /* acknowledge */
#define NAK                     ((uint8_t)0x15)  /* negative acknowledge */
#define CA                      ((uint32_t)0x18) /* two of these in succession aborts transfer */
#define CRC16                   ((uint8_t)0x43)  /* 'C' == 0x43, request 16-bit CRC */
#define NEGATIVE_BYTE           ((uint8_t)0xFF)

#define ABORT1                  ((uint8_t)0x41)  /* 'A' == 0x41, abort by user */
#define ABORT2                  ((uint8_t)0x61)  /* 'a' == 0x61, abort by user */

#define NAK_TIMEOUT             ((uint32_t)0x100000)
#define DOWNLOAD_TIMEOUT        ((uint32_t)1000) /* One second retry delay */
#define MAX_ERRORS              ((uint32_t)5)

/* Exported functions ------------------------------------------------------- */
COM_StatusTypeDef Ymodem_Receive(uint32_t *p_size);
COM_StatusTypeDef Ymodem_Transmit(uint8_t *p_buf, const uint8_t *p_file_name, uint32_t file_size);




/* Imported variables --------------------------------------------------------*/
extern uint8_t aFileName[FILE_NAME_LENGTH];

/* Private variables ---------------------------------------------------------*/
typedef  void (*pFunction)(void);

extern pFunction JumpToApplication;
extern uint32_t JumpAddress;

/* Base address of the Flash sectors */
#define ADDR_FLASH_PAGE_0     ((uint32_t)0x08000000) /* Base @ of Page 0, 2 Kbytes */
#define ADDR_FLASH_PAGE_1     ((uint32_t)0x08000800) /* Base @ of Page 1, 2 Kbytes */
#define ADDR_FLASH_PAGE_2     ((uint32_t)0x08001000) /* Base @ of Page 2, 2 Kbytes */
#define ADDR_FLASH_PAGE_3     ((uint32_t)0x08001800) /* Base @ of Page 3, 2 Kbytes */
#define ADDR_FLASH_PAGE_4     ((uint32_t)0x08002000) /* Base @ of Page 4, 2 Kbytes */
#define ADDR_FLASH_PAGE_5     ((uint32_t)0x08002800) /* Base @ of Page 5, 2 Kbytes */
#define ADDR_FLASH_PAGE_6     ((uint32_t)0x08003000) /* Base @ of Page 6, 2 Kbytes */
#define ADDR_FLASH_PAGE_7     ((uint32_t)0x08003800) /* Base @ of Page 7, 2 Kbytes */
#define ADDR_FLASH_PAGE_8     ((uint32_t)0x08004000) /* Base @ of Page 8, 2 Kbytes */
#define ADDR_FLASH_PAGE_9     ((uint32_t)0x08004800) /* Base @ of Page 9, 2 Kbytes */
#define ADDR_FLASH_PAGE_10    ((uint32_t)0x08005000) /* Base @ of Page 10, 2 Kbytes */
#define ADDR_FLASH_PAGE_11    ((uint32_t)0x08005800) /* Base @ of Page 11, 2 Kbytes */
#define ADDR_FLASH_PAGE_12    ((uint32_t)0x08006000) /* Base @ of Page 12, 2 Kbytes */
#define ADDR_FLASH_PAGE_13    ((uint32_t)0x08006800) /* Base @ of Page 13, 2 Kbytes */
#define ADDR_FLASH_PAGE_14    ((uint32_t)0x08007000) /* Base @ of Page 14, 2 Kbytes */
#define ADDR_FLASH_PAGE_15    ((uint32_t)0x08007800) /* Base @ of Page 15, 2 Kbytes */
#define ADDR_FLASH_PAGE_16    ((uint32_t)0x08008000) /* Base @ of Page 16, 2 Kbytes */
#define ADDR_FLASH_PAGE_17    ((uint32_t)0x08008800) /* Base @ of Page 17, 2 Kbytes */
#define ADDR_FLASH_PAGE_18    ((uint32_t)0x08009000) /* Base @ of Page 18, 2 Kbytes */
#define ADDR_FLASH_PAGE_19    ((uint32_t)0x08009800) /* Base @ of Page 19, 2 Kbytes */
#define ADDR_FLASH_PAGE_20    ((uint32_t)0x0800A000) /* Base @ of Page 20, 2 Kbytes */
#define ADDR_FLASH_PAGE_21    ((uint32_t)0x0800A800) /* Base @ of Page 21, 2 Kbytes  */
#define ADDR_FLASH_PAGE_22    ((uint32_t)0x0800B000) /* Base @ of Page 22, 2 Kbytes  */
#define ADDR_FLASH_PAGE_23    ((uint32_t)0x0800B800) /* Base @ of Page 23, 2 Kbytes */
#define ADDR_FLASH_PAGE_24    ((uint32_t)0x0800C000) /* Base @ of Page 24, 2 Kbytes */
#define ADDR_FLASH_PAGE_25    ((uint32_t)0x0800C800) /* Base @ of Page 25, 2 Kbytes */
#define ADDR_FLASH_PAGE_26    ((uint32_t)0x0800D000) /* Base @ of Page 26, 2 Kbytes */
#define ADDR_FLASH_PAGE_27    ((uint32_t)0x0800D800) /* Base @ of Page 27, 2 Kbytes */
#define ADDR_FLASH_PAGE_28    ((uint32_t)0x0800E000) /* Base @ of Page 28, 2 Kbytes */
#define ADDR_FLASH_PAGE_29    ((uint32_t)0x0800E800) /* Base @ of Page 29, 2 Kbytes */
#define ADDR_FLASH_PAGE_30    ((uint32_t)0x0800F000) /* Base @ of Page 30, 2 Kbytes */
#define ADDR_FLASH_PAGE_31    ((uint32_t)0x0800F800) /* Base @ of Page 31, 2 Kbytes */
#define ADDR_FLASH_PAGE_32    ((uint32_t)0x08010000) /* Base @ of Page 32, 2 Kbytes */
#define ADDR_FLASH_PAGE_33    ((uint32_t)0x08010800) /* Base @ of Page 33, 2 Kbytes */
#define ADDR_FLASH_PAGE_34    ((uint32_t)0x08011000) /* Base @ of Page 34, 2 Kbytes */
#define ADDR_FLASH_PAGE_35    ((uint32_t)0x08011800) /* Base @ of Page 35, 2 Kbytes */
#define ADDR_FLASH_PAGE_36    ((uint32_t)0x08012000) /* Base @ of Page 36, 2 Kbytes */
#define ADDR_FLASH_PAGE_37    ((uint32_t)0x08012800) /* Base @ of Page 37, 2 Kbytes */
#define ADDR_FLASH_PAGE_38    ((uint32_t)0x08013000) /* Base @ of Page 38, 2 Kbytes */
#define ADDR_FLASH_PAGE_39    ((uint32_t)0x08013800) /* Base @ of Page 39, 2 Kbytes */
#define ADDR_FLASH_PAGE_40    ((uint32_t)0x08014000) /* Base @ of Page 40, 2 Kbytes */
#define ADDR_FLASH_PAGE_41    ((uint32_t)0x08014800) /* Base @ of Page 41, 2 Kbytes */
#define ADDR_FLASH_PAGE_42    ((uint32_t)0x08015000) /* Base @ of Page 42, 2 Kbytes */
#define ADDR_FLASH_PAGE_43    ((uint32_t)0x08015800) /* Base @ of Page 43, 2 Kbytes */
#define ADDR_FLASH_PAGE_44    ((uint32_t)0x08016000) /* Base @ of Page 44, 2 Kbytes */
#define ADDR_FLASH_PAGE_45    ((uint32_t)0x08016800) /* Base @ of Page 45, 2 Kbytes */
#define ADDR_FLASH_PAGE_46    ((uint32_t)0x08017000) /* Base @ of Page 46, 2 Kbytes */
#define ADDR_FLASH_PAGE_47    ((uint32_t)0x08017800) /* Base @ of Page 47, 2 Kbytes */
#define ADDR_FLASH_PAGE_48    ((uint32_t)0x08018000) /* Base @ of Page 48, 2 Kbytes */
#define ADDR_FLASH_PAGE_49    ((uint32_t)0x08018800) /* Base @ of Page 49, 2 Kbytes */
#define ADDR_FLASH_PAGE_50    ((uint32_t)0x08019000) /* Base @ of Page 50, 2 Kbytes */
#define ADDR_FLASH_PAGE_51    ((uint32_t)0x08019800) /* Base @ of Page 51, 2 Kbytes */
#define ADDR_FLASH_PAGE_52    ((uint32_t)0x0801A000) /* Base @ of Page 52, 2 Kbytes */
#define ADDR_FLASH_PAGE_53    ((uint32_t)0x0801A800) /* Base @ of Page 53, 2 Kbytes */
#define ADDR_FLASH_PAGE_54    ((uint32_t)0x0801B000) /* Base @ of Page 54, 2 Kbytes */
#define ADDR_FLASH_PAGE_55    ((uint32_t)0x0801B800) /* Base @ of Page 55, 2 Kbytes */
#define ADDR_FLASH_PAGE_56    ((uint32_t)0x0801C000) /* Base @ of Page 56, 2 Kbytes */
#define ADDR_FLASH_PAGE_57    ((uint32_t)0x0801C800) /* Base @ of Page 57, 2 Kbytes */
#define ADDR_FLASH_PAGE_58    ((uint32_t)0x0801D000) /* Base @ of Page 58, 2 Kbytes */
#define ADDR_FLASH_PAGE_59    ((uint32_t)0x0801D800) /* Base @ of Page 59, 2 Kbytes */
#define ADDR_FLASH_PAGE_60    ((uint32_t)0x0801E000) /* Base @ of Page 60, 2 Kbytes */
#define ADDR_FLASH_PAGE_61    ((uint32_t)0x0801E800) /* Base @ of Page 61, 2 Kbytes */
#define ADDR_FLASH_PAGE_62    ((uint32_t)0x0801F000) /* Base @ of Page 62, 2 Kbytes */
#define ADDR_FLASH_PAGE_63    ((uint32_t)0x0801F800) /* Base @ of Page 63, 2 Kbytes */
#define ADDR_FLASH_PAGE_64    ((uint32_t)0x08020000) /* Base @ of Page 64, 2 Kbytes */
#define ADDR_FLASH_PAGE_65    ((uint32_t)0x08020800) /* Base @ of Page 65, 2 Kbytes */
#define ADDR_FLASH_PAGE_66    ((uint32_t)0x08021000) /* Base @ of Page 66, 2 Kbytes */
#define ADDR_FLASH_PAGE_67    ((uint32_t)0x08021800) /* Base @ of Page 67, 2 Kbytes */
#define ADDR_FLASH_PAGE_68    ((uint32_t)0x08022000) /* Base @ of Page 68, 2 Kbytes */
#define ADDR_FLASH_PAGE_69    ((uint32_t)0x08022800) /* Base @ of Page 69, 2 Kbytes */
#define ADDR_FLASH_PAGE_70    ((uint32_t)0x08023000) /* Base @ of Page 70, 2 Kbytes */
#define ADDR_FLASH_PAGE_71    ((uint32_t)0x08023800) /* Base @ of Page 71, 2 Kbytes */
#define ADDR_FLASH_PAGE_72    ((uint32_t)0x08024000) /* Base @ of Page 72, 2 Kbytes */
#define ADDR_FLASH_PAGE_73    ((uint32_t)0x08024800) /* Base @ of Page 73, 2 Kbytes */
#define ADDR_FLASH_PAGE_74    ((uint32_t)0x08025000) /* Base @ of Page 74, 2 Kbytes */
#define ADDR_FLASH_PAGE_75    ((uint32_t)0x08025800) /* Base @ of Page 75, 2 Kbytes */
#define ADDR_FLASH_PAGE_76    ((uint32_t)0x08026000) /* Base @ of Page 76, 2 Kbytes */
#define ADDR_FLASH_PAGE_77    ((uint32_t)0x08026800) /* Base @ of Page 77, 2 Kbytes */
#define ADDR_FLASH_PAGE_78    ((uint32_t)0x08027000) /* Base @ of Page 78, 2 Kbytes */
#define ADDR_FLASH_PAGE_79    ((uint32_t)0x08027800) /* Base @ of Page 79, 2 Kbytes */
#define ADDR_FLASH_PAGE_80    ((uint32_t)0x08028000) /* Base @ of Page 80, 2 Kbytes */
#define ADDR_FLASH_PAGE_81    ((uint32_t)0x08028800) /* Base @ of Page 81, 2 Kbytes */
#define ADDR_FLASH_PAGE_82    ((uint32_t)0x08029000) /* Base @ of Page 82, 2 Kbytes */
#define ADDR_FLASH_PAGE_83    ((uint32_t)0x08029800) /* Base @ of Page 83, 2 Kbytes */
#define ADDR_FLASH_PAGE_84    ((uint32_t)0x0802A000) /* Base @ of Page 84, 2 Kbytes */
#define ADDR_FLASH_PAGE_85    ((uint32_t)0x0802A800) /* Base @ of Page 85, 2 Kbytes */
#define ADDR_FLASH_PAGE_86    ((uint32_t)0x0802B000) /* Base @ of Page 86, 2 Kbytes */
#define ADDR_FLASH_PAGE_87    ((uint32_t)0x0802B800) /* Base @ of Page 87, 2 Kbytes */
#define ADDR_FLASH_PAGE_88    ((uint32_t)0x0802C000) /* Base @ of Page 88, 2 Kbytes */
#define ADDR_FLASH_PAGE_89    ((uint32_t)0x0802C800) /* Base @ of Page 89, 2 Kbytes */
#define ADDR_FLASH_PAGE_90    ((uint32_t)0x0802D000) /* Base @ of Page 90, 2 Kbytes */
#define ADDR_FLASH_PAGE_91    ((uint32_t)0x0802D800) /* Base @ of Page 91, 2 Kbytes */
#define ADDR_FLASH_PAGE_92    ((uint32_t)0x0802E000) /* Base @ of Page 92, 2 Kbytes */
#define ADDR_FLASH_PAGE_93    ((uint32_t)0x0802E800) /* Base @ of Page 93, 2 Kbytes */
#define ADDR_FLASH_PAGE_94    ((uint32_t)0x0802F000) /* Base @ of Page 94, 2 Kbytes */
#define ADDR_FLASH_PAGE_95    ((uint32_t)0x0802F800) /* Base @ of Page 95, 2 Kbytes */
#define ADDR_FLASH_PAGE_96    ((uint32_t)0x08030000) /* Base @ of Page 96, 2 Kbytes */
#define ADDR_FLASH_PAGE_97    ((uint32_t)0x08030800) /* Base @ of Page 97, 2 Kbytes */
#define ADDR_FLASH_PAGE_98    ((uint32_t)0x08031000) /* Base @ of Page 98, 2 Kbytes */
#define ADDR_FLASH_PAGE_99    ((uint32_t)0x08031800) /* Base @ of Page 99, 2 Kbytes */
#define ADDR_FLASH_PAGE_100   ((uint32_t)0x08032000) /* Base @ of Page 100, 2 Kbytes */
#define ADDR_FLASH_PAGE_101   ((uint32_t)0x08032800) /* Base @ of Page 101, 2 Kbytes */
#define ADDR_FLASH_PAGE_102   ((uint32_t)0x08033000) /* Base @ of Page 102, 2 Kbytes */
#define ADDR_FLASH_PAGE_103   ((uint32_t)0x08033800) /* Base @ of Page 103, 2 Kbytes */
#define ADDR_FLASH_PAGE_104   ((uint32_t)0x08034000) /* Base @ of Page 104, 2 Kbytes */
#define ADDR_FLASH_PAGE_105   ((uint32_t)0x08034800) /* Base @ of Page 105, 2 Kbytes */
#define ADDR_FLASH_PAGE_106   ((uint32_t)0x08035000) /* Base @ of Page 106, 2 Kbytes */
#define ADDR_FLASH_PAGE_107   ((uint32_t)0x08035800) /* Base @ of Page 107, 2 Kbytes */
#define ADDR_FLASH_PAGE_108   ((uint32_t)0x08036000) /* Base @ of Page 108, 2 Kbytes */
#define ADDR_FLASH_PAGE_109   ((uint32_t)0x08036800) /* Base @ of Page 109, 2 Kbytes */
#define ADDR_FLASH_PAGE_110   ((uint32_t)0x08037000) /* Base @ of Page 110, 2 Kbytes */
#define ADDR_FLASH_PAGE_111   ((uint32_t)0x08037800) /* Base @ of Page 111, 2 Kbytes */
#define ADDR_FLASH_PAGE_112   ((uint32_t)0x08038000) /* Base @ of Page 112, 2 Kbytes */
#define ADDR_FLASH_PAGE_113   ((uint32_t)0x08038800) /* Base @ of Page 113, 2 Kbytes */
#define ADDR_FLASH_PAGE_114   ((uint32_t)0x08039000) /* Base @ of Page 114, 2 Kbytes */
#define ADDR_FLASH_PAGE_115   ((uint32_t)0x08039800) /* Base @ of Page 115, 2 Kbytes */
#define ADDR_FLASH_PAGE_116   ((uint32_t)0x0803A000) /* Base @ of Page 116, 2 Kbytes */
#define ADDR_FLASH_PAGE_117   ((uint32_t)0x0803A800) /* Base @ of Page 117, 2 Kbytes  */
#define ADDR_FLASH_PAGE_118   ((uint32_t)0x0803B000) /* Base @ of Page 118, 2 Kbytes  */
#define ADDR_FLASH_PAGE_119   ((uint32_t)0x0803B800) /* Base @ of Page 119, 2 Kbytes */
#define ADDR_FLASH_PAGE_120   ((uint32_t)0x0803C000) /* Base @ of Page 120, 2 Kbytes */
#define ADDR_FLASH_PAGE_121   ((uint32_t)0x0803C800) /* Base @ of Page 121, 2 Kbytes */
#define ADDR_FLASH_PAGE_122   ((uint32_t)0x0803D000) /* Base @ of Page 122, 2 Kbytes */
#define ADDR_FLASH_PAGE_123   ((uint32_t)0x0803D800) /* Base @ of Page 123, 2 Kbytes */
#define ADDR_FLASH_PAGE_124   ((uint32_t)0x0803E000) /* Base @ of Page 124, 2 Kbytes */
#define ADDR_FLASH_PAGE_125   ((uint32_t)0x0803E800) /* Base @ of Page 125, 2 Kbytes */
#define ADDR_FLASH_PAGE_126   ((uint32_t)0x0803F000) /* Base @ of Page 126, 2 Kbytes */
#define ADDR_FLASH_PAGE_127   ((uint32_t)0x0803F800) /* Base @ of Page 127, 2 Kbytes */

/* Error code */
enum
{
  FLASHIF_OK = 0,
  FLASHIF_ERASEKO,
  FLASHIF_WRITINGCTRL_ERROR,
  FLASHIF_WRITING_ERROR,
  FLASHIF_PROTECTION_ERRROR
};

/* protection type */
enum{
  FLASHIF_PROTECTION_NONE         = 0,
  FLASHIF_PROTECTION_PCROPENABLED = 0x1,
  FLASHIF_PROTECTION_WRPENABLED   = 0x2,
  FLASHIF_PROTECTION_RDPENABLED   = 0x4,
};

/* protection update */
enum {
    FLASHIF_WRP_ENABLE,
    FLASHIF_WRP_DISABLE
};

/* 定义从哪里加载用户应用程序的地址
   Note: 此区域为IAP代码保留                 */
#define FLASH_PAGE_STEP         FLASH_PAGE_SIZE           /* Size of page : 2 Kbytes */
#define APPLICATION_ADDRESS     (uint32_t)0x8008000      /* Start user code address: ADDR_FLASH_PAGE_8 */
//文件大小64k
/* Notable Flash addresses */
#define USER_FLASH_END_ADDRESS        0x8040000

/* 定义用户应用程序大小 */    //64k
#define USER_FLASH_SIZE               ((uint32_t)0x00010000) /* Small default template application */  //已修改  默认 3

/* 定义表示可以写入保护的用户闪存区域的位图(检查仅限于第8-39页). */
#define FLASH_PAGE_TO_BE_PROTECTED (OB_WRP_PAGES8TO9   | OB_WRP_PAGES10TO11 | OB_WRP_PAGES12TO13 | OB_WRP_PAGES14TO15 | \
                                    OB_WRP_PAGES16TO17 | OB_WRP_PAGES18TO19 | OB_WRP_PAGES20TO21 | OB_WRP_PAGES22TO23 | \
                                    OB_WRP_PAGES24TO25 | OB_WRP_PAGES26TO27 | OB_WRP_PAGES28TO29 | OB_WRP_PAGES30TO31 | \
                                    OB_WRP_PAGES32TO33 | OB_WRP_PAGES34TO35 | OB_WRP_PAGES36TO37 | OB_WRP_PAGES38TO39 | \
                                    OB_WRP_PAGES40TO41 | OB_WRP_PAGES42TO43 | OB_WRP_PAGES44TO45 | OB_WRP_PAGES46TO47 | \
                                    OB_WRP_PAGES48TO49 | OB_WRP_PAGES50TO51 | OB_WRP_PAGES52TO53 | OB_WRP_PAGES54TO55 | \
                                    OB_WRP_PAGES56TO57 | OB_WRP_PAGES58TO59 | OB_WRP_PAGES60TO61)


/* Exported macro ------------------------------------------------------------*/
/* ABSoulute value */
#define ABS_RETURN(x,y)               (((x) < (y)) ? (y) : (x))

/* 从加载用户程序的位置获取扇区数 */
#define FLASH_SECTOR_NUMBER           ((uint32_t)(ABS_RETURN(APPLICATION_ADDRESS,FLASH_START_BANK1))>>12)

/* 计算掩码以测试闪存(其中将加载用户程序)是否被写入保护 */
#define FLASH_PROTECTED_SECTORS       (~(uint32_t)((1 << FLASH_SECTOR_NUMBER) - 1))
/* Exported functions ------------------------------------------------------- */
void FLASH_If_Init(void);
uint32_t FLASH_If_Erase(uint32_t StartSector);
uint32_t FLASH_If_GetWriteProtectionStatus(void);
uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length);
uint32_t FLASH_If_WriteProtectionConfig(uint32_t protectionstate);



/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Constants used by Serial Command Line Mode */
#define TX_TIMEOUT          ((uint32_t)100)
#define RX_TIMEOUT          HAL_MAX_DELAY

/* Exported macro ------------------------------------------------------------*/
#define IS_CAP_LETTER(c)    (((c) >= 'A') && ((c) <= 'F'))
#define IS_LC_LETTER(c)     (((c) >= 'a') && ((c) <= 'f'))
#define IS_09(c)            (((c) >= '0') && ((c) <= '9'))
#define ISVALIDHEX(c)       (IS_CAP_LETTER(c) || IS_LC_LETTER(c) || IS_09(c))
#define ISVALIDDEC(c)       IS_09(c)
#define CONVERTDEC(c)       (c - '0')

#define CONVERTHEX_ALPHA(c) (IS_CAP_LETTER(c) ? ((c) - 'A'+10) : ((c) - 'a'+10))
#define CONVERTHEX(c)       (IS_09(c) ? ((c) - '0') : CONVERTHEX_ALPHA(c))

/* Exported functions ------------------------------------------------------- */
void Int2Str(uint8_t *p_str, uint32_t intnum);
uint32_t Str2Int(uint8_t *inputstr, uint32_t *intnum);
void Serial_PutString(uint8_t *p_string);
HAL_StatusTypeDef Serial_PutByte(uint8_t param);



/* Imported variables --------------------------------------------------------*/
extern uint8_t aFileName[FILE_NAME_LENGTH];

/* Private variables ---------------------------------------------------------*/
typedef  void (*pFunction)(void);

/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
void Main_Menu(void);
#endif /* INC_YMODEM_H_ */
ymodem.h
复制代码

在ymodem.h文件里需要指定用户APP起始地址APPLICATION_ADDRESS,用户结束地址USER_FLASH_END_ADDRESS,以及用户应用程序大小。

 bootloader的Flash地址是从0x8000000-0x8007ffff 总共占用Flash大小32K.

app的Flash地址是从0x8008000-0x803ffff  总共占用Flash大小256K-32K = 224K = 0x00038000

添加完这两个文件后,只需在main.c文件添加使用即可。

复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ymodem.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int __io_putchar(int ch)
{
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000);
    return ch;
}


/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    if (1)
    {
        /* Execute the IAP driver in order to reprogram the Flash */
        FLASH_If_Init();
        /* Display main menu */
        Main_Menu();
    }
    /* Keep the user application running */
    else
    {
        /* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
        if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
        {
            /* Jump to user application */
            JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
            JumpToApplication = (pFunction) JumpAddress;
            /* Initialize user application's Stack Pointer */
            __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
            JumpToApplication();
        }
    }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI48;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
main.c
复制代码

 二:单片机的APP

APP部分需要修改两处地方

F0系列的单片机:

在STM32F0XXXXX_FLASH.ld文件中修改用户APP的RAM和FLASH起始地址ORIGIN以及LENGTH

 在main入口重映射中断向量表

    //F0系列的单片机没有SCB->VTOR这个寄存器,使用如下方法重映射中断向量表
    memcpy((void*)0x20000000, (void*)0x08008000, 0xBC);
    __HAL_SYSCFG_REMAPMEMORY_SRAM();

 拷贝长度0xBC的计算方法在startup_stm32f091rctx.s文件中有(171-125+1)个word中断向量,每个word四个字节,所以拷贝长度为(171-125+1)*4=188 = 0xBC

 非F0系列的单片机:

只需修改FLASH的ORIGIN以及LENGTH即可

 在main入口添加一句SCB->VTOR = APPLICATION_ADDRESS;重映射中断向量表

三:通过上位机发送文件更新单片机程序

 

Ymodem协议上位机源码如下

C++源码

复制代码
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <windows.h>
#include <thread>

bool IsStopPrintfReceive = false;

class SerialPort {
public:
    HANDLE hSerial;
    //构造函数,打开串口并设置参数
    SerialPort(const char* portName) {
        std::string fullPortName = "\\\\.\\" + std::string(portName);
        hSerial = CreateFileA(
            fullPortName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hSerial == INVALID_HANDLE_VALUE) {
            std::cerr << "Error opening serial port\n";
            exit(1);
        }

        // 初始化串口参数
        DCB dcbSerialParams = { 0 };
        COMMTIMEOUTS timeouts = { 0 };
        dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

        if (!GetCommState(hSerial, &dcbSerialParams)) {
            std::cerr << "Error getting serial port state\n";
            CloseHandle(hSerial);
            exit(1);
        }

        // 设置串口参数
        dcbSerialParams.BaudRate = CBR_115200; // 波特率为115200
        dcbSerialParams.ByteSize = 8; // 数据位为8位
        dcbSerialParams.StopBits = ONESTOPBIT; // 停止位为1位
        dcbSerialParams.Parity = NOPARITY; // 无校验位
        if (!SetCommState(hSerial, &dcbSerialParams)) {
            std::cerr << "Error setting serial port state\n";
            CloseHandle(hSerial);
            exit(1);
        }

        // 设置超时时间
        timeouts.ReadIntervalTimeout = 50; // 读取数据之间的间隔时间
        timeouts.ReadTotalTimeoutConstant = 50; // 读取数据的固定超时时间
        timeouts.ReadTotalTimeoutMultiplier = 10; // 读取数据的超时时间倍数
        timeouts.WriteTotalTimeoutConstant = 50; // 写入数据的固定超时时间
        timeouts.WriteTotalTimeoutMultiplier = 10; // 写入数据的超时时间倍数
        if (!SetCommTimeouts(hSerial, &timeouts)) {
            std::cerr << "Error setting serial port timeouts\n";
            CloseHandle(hSerial);
            exit(1);
        }
    }

    //析构函数,关闭串口
    ~SerialPort() {
        CloseHandle(hSerial);
    }

    /**
     * @brief 重置串口超时时间
     *
     * @param timeout 读写超时时间
     * @return true 重置成功
     * @return false 重置失败
     */
    bool resetTimeout(DWORD timeout) {
        COMMTIMEOUTS timeouts = { 0 };
        timeouts.ReadIntervalTimeout = 50; // 读取数据之间的间隔时间
        timeouts.ReadTotalTimeoutConstant = timeout; // 读取数据的固定超时时间
        timeouts.ReadTotalTimeoutMultiplier = 10; // 读取数据的超时时间倍数
        timeouts.WriteTotalTimeoutConstant = timeout; // 写入数据的固定超时时间
        timeouts.WriteTotalTimeoutMultiplier = 10; // 写入数据的超时时间倍数
        if (!SetCommTimeouts(hSerial, &timeouts)) {
            std::cerr << "Error setting serial port timeouts\n";
            CloseHandle(hSerial);
            exit(1);
        }
        return true;
    }

    //向串口写入数据
    bool write(const char* data) {
        DWORD bytes_written;
        if (!WriteFile(hSerial, data, strlen(data), &bytes_written, NULL)) {
            std::cerr << "Error writing to serial port\n";
            return false;
        }
        return true;
    }
    bool write(const uint8_t data) {
        DWORD bytes_written;
        if (!WriteFile(hSerial, &data, 1, &bytes_written, NULL)) {
            std::cerr << "Error writing to serial port\n";
            return false;
        }
        return true;
    }
    /**
     * @brief 向串口写入数据
     *
     * @param data 要写入的数据
     * @param start_index 数据的起始位置
     * @param length 数据的长度
     * @param timeout 写入数据的超时时间
     * @return true 写入成功
     * @return false 写入失败
     */
    bool write(const uint8_t* data, uint32_t start_index, uint32_t length, DWORD timeout) {
        DWORD bytes_written;
        COMMTIMEOUTS timeouts = { 0 };
        timeouts.WriteTotalTimeoutConstant = timeout; // 写入数据的固定超时时间
        timeouts.WriteTotalTimeoutMultiplier = 10; // 写入数据的超时时间倍数
        if (!SetCommTimeouts(hSerial, &timeouts)) {
            std::cerr << "Error setting serial port timeouts\n";
            CloseHandle(hSerial);
            exit(1);
        }
        if (!WriteFile(hSerial, data + start_index, length, &bytes_written, NULL)) {
            std::cerr << "Error writing to serial port\n";
            return false;
        }
        return true;
    }

    //从串口读取数据
    bool read(char* buffer, DWORD buffer_size, DWORD& bytes_read) {
        if (!ReadFile(hSerial, buffer, buffer_size, &bytes_read, NULL)) {
            std::cerr << "Error reading from serial port\n";
            return false;
        }
        return true;
    }
    //从串口读取数据
    bool read(uint8_t* buffer, uint32_t length, DWORD timeout, DWORD& bytes_read) {
        COMMTIMEOUTS timeouts = { 0 };
        timeouts.ReadIntervalTimeout = MAXDWORD;
        timeouts.ReadTotalTimeoutConstant = timeout;
        timeouts.ReadTotalTimeoutMultiplier = 10;
        if (!SetCommTimeouts(hSerial, &timeouts)) {
            std::cerr << "Error setting serial port timeouts\n";
            CloseHandle(hSerial);
            exit(1);
        }
        if (!ReadFile(hSerial, buffer, length, &bytes_read, NULL)) {
            std::cerr << "Error reading from serial port\n";
            return false;
        }
        return true;
    }

};


/**
  * @brief  Convert an Integer to a string  将整数转换为字符串
  * @param  p_str: The string output pointer   字符串输出指针
  * @param  intnum: The integer to be converted   要转换的整数
  * @retval None
  */
void Int2Str(uint8_t* p_str, uint32_t intnum) {
    uint32_t i, divider = 1000000000, pos = 0, status = 0;
    for (i = 0; i < 10; i++) {
        p_str[pos++] = (intnum / divider) + 48;
        intnum = intnum % divider;
        divider /= 10;
        if ((p_str[pos - 1] == '0') & (status == 0)) {
            pos = 0;
        }
        else {
            status++;
        }
    }
}

/**
  * @brief  Update CRC16 for input byte
  * @param  crc_in input value
  * @param  input byte
  * @retval None
  */
uint16_t UpdateCRC16(uint16_t crc_in, uint8_t byte) {
    uint32_t crc = crc_in;
    uint32_t in = byte | 0x100;
    do {
        crc <<= 1;
        in <<= 1;
        if (in & 0x100)
            ++crc;
        if (crc & 0x10000)
            crc ^= 0x1021;
    } while (!(in & 0x10000));
    return crc & 0xffffu;
}
/**
  * @brief  Cal CRC16 for YModem Packet
  * @param  data
  * @param  length
  * @retval None
  */
uint16_t Cal_CRC16(const uint8_t* p_data, uint32_t size) {
    uint32_t crc = 0;
    const uint8_t* dataEnd = p_data + size;
    while (p_data < dataEnd)
        crc = UpdateCRC16(crc, *p_data++);
    crc = UpdateCRC16(crc, 0);
    crc = UpdateCRC16(crc, 0);
    return crc & 0xffffu;
}

/**
  * @brief  Comm status structures definition
  */
typedef enum {
    COM_OK = 0x00,
    COM_ERROR = 0x01,
    COM_ABORT = 0x02,
    COM_TIMEOUT = 0x03,
    COM_DATA = 0x04,
    COM_LIMIT = 0x05
} COM_StatusTypeDef;

/* Packet structure defines */
#define PACKET_HEADER_SIZE      ((uint32_t)3)
#define PACKET_DATA_INDEX       ((uint32_t)4)
#define PACKET_START_INDEX      ((uint32_t)1)
#define PACKET_NUMBER_INDEX     ((uint32_t)2)
#define PACKET_CNUMBER_INDEX    ((uint32_t)3)
#define PACKET_TRAILER_SIZE     ((uint32_t)2)
#define PACKET_OVERHEAD_SIZE    (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE - 1)
#define PACKET_SIZE             ((uint32_t)128)
#define PACKET_1K_SIZE          ((uint32_t)1024)

/* /-------- Packet in IAP memory ------------------------------------------\
 * | 0      |  1    |  2     |  3   |  4      | ... | n+4     | n+5  | n+6  |
 * |------------------------------------------------------------------------|
 * | unused | start | number | !num | data[0] | ... | data[n] | crc0 | crc1 |
 * \------------------------------------------------------------------------/
 * the first byte is left unused for memory alignment reasons                 */

#define FILE_NAME_LENGTH        ((uint32_t)64)
#define FILE_SIZE_LENGTH        ((uint32_t)16)

#define SOH                     ((uint8_t)0x01)  /* start of 128-byte data packet */
#define STX                     ((uint8_t)0x02)  /* start of 1024-byte data packet */
#define EOT                     ((uint8_t)0x04)  /* end of transmission */
#define ACK                     ((uint8_t)0x06)  /* acknowledge */
#define NAK                     ((uint8_t)0x15)  /* negative acknowledge */
#define CA                      ((uint32_t)0x18) /* two of these in succession aborts transfer */
#define CRC16                   ((uint8_t)0x43)  /* 'C' == 0x43, request 16-bit CRC */
#define NEGATIVE_BYTE           ((uint8_t)0xFF)

#define ABORT1                  ((uint8_t)0x41)  /* 'A' == 0x41, abort by user */
#define ABORT2                  ((uint8_t)0x61)  /* 'a' == 0x61, abort by user */

#define NAK_TIMEOUT             ((uint32_t)0x100000)
#define DOWNLOAD_TIMEOUT        ((uint32_t)1000) /* One second retry delay */
#define MAX_ERRORS              ((uint32_t)5)


#define USER_FLASH_SIZE               ((uint32_t)0x00010000) /* Small default template application */


 /**
   * @brief  Prepare the first block
   * @param  p_data:  output buffer
   * @param  p_file_name: name of the file to be sent
   * @param  length: length of the file to be sent in bytes
   * @retval None
   */
static void PrepareIntialPacket(uint8_t* p_data, const uint8_t* p_file_name, uint32_t length) {
    uint32_t i, j = 0;
    uint8_t astring[10] = {0};

    /* first 3 bytes are constant  */
    p_data[PACKET_START_INDEX] = SOH;
    p_data[PACKET_NUMBER_INDEX] = 0x00;
    p_data[PACKET_CNUMBER_INDEX] = 0xff;

    /* Filename written  */
    for (i = 0; (p_file_name[i] != '\0') && (i < FILE_NAME_LENGTH); i++) {
        p_data[i + PACKET_DATA_INDEX] = p_file_name[i];
    }

    p_data[i + PACKET_DATA_INDEX] = 0x00;

    /* file size written */
    Int2Str(astring, length);
    i = i + PACKET_DATA_INDEX + 1;
    while (astring[j] != '\0') {
        p_data[i++] = astring[j++];
    }

    /* padding with zeros */
    for (j = i; j < PACKET_SIZE + PACKET_DATA_INDEX; j++) {
        p_data[j] = 0;
    }
}

/**
  * @brief  Prepare the data packet
  * @param  p_source: pointer to the data to be sent
  * @param  p_packet: pointer to the output buffer
  * @param  pkt_nr: number of the packet
  * @param  size_blk: length of the block to be sent in bytes
  * @retval None
  */
static void PreparePacket(uint8_t* p_source, uint8_t* p_packet, uint8_t pkt_nr, uint32_t size_blk) {
    uint8_t* p_record;
    uint32_t i, size, packet_size;

    /* Make first three packet */
    packet_size = size_blk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
    size = size_blk < packet_size ? size_blk : packet_size;
    if (packet_size == PACKET_1K_SIZE) {
        p_packet[PACKET_START_INDEX] = STX;
    }
    else {
        p_packet[PACKET_START_INDEX] = SOH;
    }
    p_packet[PACKET_NUMBER_INDEX] = pkt_nr;
    p_packet[PACKET_CNUMBER_INDEX] = (~pkt_nr);
    p_record = p_source;

    /* Filename packet has valid data */
    for (i = PACKET_DATA_INDEX; i < size + PACKET_DATA_INDEX; i++) {
        p_packet[i] = *p_record++;
    }
    if (size <= packet_size) {
        for (i = size + PACKET_DATA_INDEX; i < packet_size + PACKET_DATA_INDEX; i++) {
            p_packet[i] = 0x1A; /* EOF (0x1A) or 0x00 */
        }
    }
}

/* @note ATTENTION - please keep this variable 32bit alligned 请保持此变量32位对齐*/
uint8_t aPacketData[PACKET_1K_SIZE + PACKET_DATA_INDEX + PACKET_TRAILER_SIZE];

void send_file(SerialPort* serial, const char* file_path) {
    uint32_t errors = 0, ack_recpt = 0, size = 0, pkt_size;
    uint8_t* p_buf_int;
    COM_StatusTypeDef result = COM_OK;
    uint32_t blk_number = 1;
    uint8_t a_rx_ctrl[2];
    uint8_t i;
    uint32_t temp_crc;
    uint8_t* p_file_name;
    uint32_t file_size;
    DWORD bytes_read;

    FILE* file = fopen(file_path, "rb");
    if (!file) {
        std::cerr << "Error opening file\n";
        return;
    }
    //提取file_path路径里的文件名并读取文件的大小
    std::string path(file_path);
    std::string filename = path.substr(path.find_last_of("\\/") + 1);
    uint8_t files[128];
    strcpy((char*)files, filename.c_str());
    p_file_name = files;
    fseek(file, 0, SEEK_END);
    file_size = ftell(file);
    fseek(file, 0, SEEK_SET);
    std::cout << "Sending file: " << filename << ", size: " << file_size << " bytes\n";
    //uint8_t data[32000];
    //fread(data, 1, file_size, file);
    p_buf_int = (uint8_t*)malloc(file_size * sizeof(byte));
    fread(p_buf_int, 1, file_size, file);
    fclose(file);

    /* Prepare first block - header */
    PrepareIntialPacket(aPacketData, p_file_name, file_size);

    while ((!ack_recpt) && (result == COM_OK)) {
        /* Send Packet */
        serial->write(aPacketData, PACKET_START_INDEX, PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

        /* Send CRC or Check Sum based on CRC16_F */
        temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
        serial->write((uint8_t)(temp_crc >> 8));
        serial->write((uint8_t)(temp_crc & 0xFF));

        /* Wait for Ack and 'C' */
        if (serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true) {
            if (a_rx_ctrl[0] == ACK) {
                ack_recpt = 1;
            }
            else if (a_rx_ctrl[0] == CA) {
                if ((serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true) && (a_rx_ctrl[0] == CA)) {
                    Sleep(2);
                    PurgeComm(serial->hSerial, PURGE_RXCLEAR);
                    result = COM_ABORT;
                }
            }
        }
        else {
            errors++;
        }
        if (errors >= MAX_ERRORS) {
            result = COM_ERROR;
        }
    }
    //p_buf_int = data;
    size = file_size;

    /* Here 1024 bytes length is used to send the packets */
    while ((size) && (result == COM_OK)) {
        /* Prepare next packet */
        PreparePacket(p_buf_int, aPacketData, blk_number, size);
        ack_recpt = 0;
        a_rx_ctrl[0] = 0;
        errors = 0;
        /* Resend packet if NAK for few times else end of communication */
        while ((!ack_recpt) && (result == COM_OK)) {
            /* Send next packet */
            if (size >= PACKET_1K_SIZE) {
                pkt_size = PACKET_1K_SIZE;
            }
            else {
                pkt_size = PACKET_SIZE;
            }

            /* Send CRC or Check Sum based on CRC16_F */
            temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], pkt_size);
            aPacketData[pkt_size + PACKET_HEADER_SIZE + PACKET_START_INDEX + 0] = (uint8_t)(temp_crc >> 8);
            aPacketData[pkt_size + PACKET_HEADER_SIZE + PACKET_START_INDEX + 1] = (uint8_t)(temp_crc & 0xFF);
            serial->write(aPacketData, PACKET_START_INDEX, pkt_size + PACKET_HEADER_SIZE + 2, NAK_TIMEOUT);
            PurgeComm(serial->hSerial, PURGE_RXCLEAR);

            uint8_t progress = (uint8_t)((float)(file_size - size) / file_size * 100);
            printf("current progress:%d%%\n", progress);
            /* Wait for Ack */
            if ((serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true)) {
                if (a_rx_ctrl[0] == ACK) {
                    ack_recpt = 1;
                    if (size > pkt_size) {
                        p_buf_int += pkt_size;
                        size -= pkt_size;
                        if (blk_number == (USER_FLASH_SIZE / PACKET_1K_SIZE)) {
                            result = COM_LIMIT; /* boundary error */
                        }
                        else {
                            blk_number++;
                        }
                    }
                    else {
                        p_buf_int += pkt_size;
                        size = 0;
                    }
                }
            }
            else {
                errors++;
            }
            /* Resend packet if NAK  for a count of 10 else end of communication */
            if (errors >= MAX_ERRORS) {
                result = COM_ERROR;
            }
        }
    }

    /* Sending End Of Transmission char */
    ack_recpt = 0;
    a_rx_ctrl[0] = 0x00;
    errors = 0;
    while ((!ack_recpt) && (result == COM_OK)) {
        serial->write(EOT);

        /* Wait for Ack */
        if (serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true) {
            if (a_rx_ctrl[0] == ACK) {
                ack_recpt = 1;
            }
            else if (a_rx_ctrl[0] == CA) {
                if ((serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true) && (a_rx_ctrl[0] == CA)) {
                    Sleep(2);
                    PurgeComm(serial->hSerial, PURGE_RXCLEAR);
                    result = COM_ABORT;
                }
            }
        }
        else {
            errors++;
        }

        if (errors >= MAX_ERRORS) {
            result = COM_ERROR;
        }
    }

    /* Empty packet sent - some terminal emulators need this to close session */
    if (result == COM_OK) {
        /* Preparing an empty packet */
        aPacketData[PACKET_START_INDEX] = SOH;
        aPacketData[PACKET_NUMBER_INDEX] = 0;
        aPacketData[PACKET_CNUMBER_INDEX] = 0xFF;
        for (i = PACKET_DATA_INDEX; i < (PACKET_SIZE + PACKET_DATA_INDEX); i++) {
            aPacketData[i] = 0x00;
        }

        /* Send Packet */
        serial->write(aPacketData, PACKET_START_INDEX, PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

        /* Send CRC or Check Sum based on CRC16_F */
        temp_crc = Cal_CRC16(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
        serial->write((uint8_t)(temp_crc >> 8));
        serial->write((uint8_t)(temp_crc & 0xFF));

        /* Wait for Ack and 'C' */
        if (serial->read(&a_rx_ctrl[0], 1, NAK_TIMEOUT, bytes_read) == true) {
            if (a_rx_ctrl[0] == CA) {
                Sleep(2);
                PurgeComm(serial->hSerial, PURGE_RXCLEAR);
                result = COM_ABORT;
            }
        }
    }
    printf("current progress:100%%\n");
    serial->resetTimeout(50);
    IsStopPrintfReceive = false;
}
void receive_thread(SerialPort* serial) {
    char buffer[32];
    DWORD bytes_read;
    while (true) {
        if (IsStopPrintfReceive == false) {
            if (serial->read(buffer, sizeof(buffer), bytes_read)) {
                if (bytes_read > 0) {
                    std::cout.write(buffer, bytes_read);// Print received data
                }
            }
        }
        else {
            Sleep(1);
        }

    }
}

void send_thread(SerialPort* serial) {
    char input[32];
    while (true) {
        std::cin.getline(input, sizeof(input));
        if (input[0] == '6') {
            IsStopPrintfReceive = true;
            std::string file_path = "C:\\Users\\ME-LZQ\\Desktop\\STM32F091RC\\app\\1062\\Debug\\ZhuKongBan.bin";
            send_file(serial, file_path.c_str());
        }
        else
            serial->write(input);
    }
}

int main() {
    SerialPort serial("COM5");
    std::thread t1(receive_thread, &serial);
    std::thread t2(send_thread, &serial);
    t1.join();
    t2.join();
    return 0;
}
View Code
复制代码

C#源码

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace 更新下位机
{
    class Program
    {
        static SerialPortWrapper serialWrapper;
        public static bool IsStopPrintfReceive { get; set; } = false;
        static void Main(string[] args)
        {
            serialWrapper = new SerialPortWrapper("COM5");

            Thread t1 = new Thread(new ThreadStart(ReceiveThread));
            Thread t2 = new Thread(new ThreadStart(SendThread));

            t1.Start();
            t2.Start();

            t1.Join();
            t2.Join();

        }
        // 接收线程
        static public void ReceiveThread()
        {
            byte[] buffer = new byte[2048];
            int bytes_read;
            while (true)
            {
                if (!IsStopPrintfReceive)
                {
                    bytes_read = serialWrapper.Read2(buffer, 0,buffer.Length);
                    if (bytes_read > 0)
                    {
                        Console.Write(Encoding.ASCII.GetString(buffer, 0, bytes_read)); // 打印接收到的数据
                    }
                }
                else
                {
                    Thread.Sleep(1000); // Sleep为毫秒单位,这里休眠1秒
                }
            }
        }

        // 发送线程
        static public void SendThread()
        {
            string input;
            while (true)
            {
                input = Console.ReadLine();

                if (serialWrapper.Write(input))
                {
                    Console.WriteLine("Sent: " + input);

                    if (input[0] == '6')
                    {
                        IsStopPrintfReceive = true;
                        string file_path = @"C:\Users\ME-LZQ\Desktop\STM32F091RC\app\1062\Debug\ZhuKongBan.bin";
                        YmodemCommunication.SendFile(serialWrapper, file_path);
                        IsStopPrintfReceive = false;
                    }
                }
            }
        }
    }

    public class SerialPortWrapper
    {
        private SerialPort _serialPort; // 使用.NET内置的SerialPort类实现串口通信

        /**
         * 构造函数,打开串口并设置参数
         * @param portName 串口名称(如"COM1")
         */
        public SerialPortWrapper(string portName)
        {
            _serialPort = new SerialPort(portName); // 创建SerialPort对象实例

            // 设置串口参数
            _serialPort.BaudRate = 115200; // 波特率为115200
            _serialPort.DataBits = 8; // 数据位为8位
            _serialPort.StopBits = StopBits.One; // 停止位为1位
            _serialPort.Parity = Parity.None; // 无校验位

            try
            {
                _serialPort.Open(); // 打开串口
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine("未经授权访问串口");
                return;
            }
            catch (IOException ex)
            {
                Console.WriteLine($"无法打开串口:{ex.Message}");
                return;
            }

            // 设置读写超时时间
            _serialPort.ReadTimeout = 50; // 读取数据的固定超时时间(单位:毫秒)
            _serialPort.WriteTimeout = 50; // 写入数据的固定超时时间(单位:毫秒)
        }

        /**
         * 析构函数,关闭串口
         */
        ~SerialPortWrapper()
        {
            if (_serialPort != null && _serialPort.IsOpen)
            {
                _serialPort.Close(); // 关闭串口
            }
        }

        /**
         * 重置串口读写超时时间
         * @param timeout 新的读写超时时间(单位:毫秒)
         * @return 是否成功重置超时时间
         */
        public bool ResetTimeout(int timeout)
        {
            _serialPort.ReadTimeout = timeout;
            _serialPort.WriteTimeout = timeout;
            return true;
        }

        // 向串口写入字符串数据
        public bool Write(string data)
        {
            try
            {
                _serialPort.Write(data); // 将字符串写入串口
                return true;
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine("写入串口失败:" + ex.Message);
                return false;
            }
        }

        // 向串口写入单个字节数据
        public bool Write(byte data)
        {
            try
            {
                _serialPort.Write(new[] { data }, 0, 1); // 将字节数组(长度为1)写入串口
                return true;
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine("写入串口失败:" + ex.Message);
                return false;
            }
        }

        /**
         * 向串口写入指定长度的数据
         * @param data 要写入的数据缓冲区
         * @param start_index 数据的起始位置
         * @param length 数据的长度
         * @return 是否成功写入数据
         */
        public bool Write(byte[] data, int start_index, int length, int timeout)
        {
            try
            {
                _serialPort.WriteTimeout = timeout;
                _serialPort.Write(data, start_index, length); // 将字节数组的部分内容写入串口
                return true;
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine("写入串口失败:" + ex.Message);
                return false;
            }
        }

        public int Read2(byte[] buffer, int offset, int count)
        {
            // 设置一个较大的超时时间(这里使用int类型的最大值)
            _serialPort.ReadTimeout = int.MaxValue;

            // 尝试读取数据
            return _serialPort.Read(buffer, offset, count);
        }
        /**
         * 从串口读取指定长度的数据
         * @param buffer 存储读取数据的目标缓冲区
         * @param length 需要读取的字节数
         * @param timeout 读取操作的超时时间(单位:毫秒)
         * @return 是否成功读取数据
         */
        public bool Read(byte[] buffer, int length, int timeout)
        {
            _serialPort.ReadTimeout = timeout;
            try
            {
                int bytes_read = _serialPort.Read(buffer, 0, length); // 从串口读取数据
                return true;
            }
            catch (TimeoutException)
            {
                Console.WriteLine("串口读取超时");
                return false;
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine("读取串口失败:" + ex.Message);
                return false;
            }
        }

        // 清除接收缓冲区(模拟)
        public void ClearReceiveBuffer()
        {
            while (_serialPort.BytesToRead > 0)
            {
                var buffer = new byte[_serialPort.BytesToRead];
                _serialPort.Read(buffer, 0, buffer.Length);
            }
        }
    }

    public class YmodemCommunication
    {
        // 定义通讯状态枚举类型
        public enum COM_StatusTypeDef
        {
            COM_OK = 0x00,
            COM_ERROR = 0x01,
            COM_ABORT = 0x02,
            COM_TIMEOUT = 0x03,
            COM_DATA = 0x04,
            COM_LIMIT = 0x05
        }

        // 数据包结构定义常量
        private const int PACKET_HEADER_SIZE = 3;
        private const int PACKET_DATA_INDEX = 4;
        private const int PACKET_START_INDEX = 1;
        private const int PACKET_NUMBER_INDEX = 2;
        private const int PACKET_CNUMBER_INDEX = 3;
        private const int PACKET_TRAILER_SIZE = 2;
        private const int PACKET_OVERHEAD_SIZE = PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE - 1;
        private const int PACKET_SIZE = 128;
        private const int PACKET_1K_SIZE = 1024;
        private const int FILE_NAME_LENGTH = 64;
        private const int FILE_SIZE_LENGTH = 16;

        // 协议中特定字节含义的常量
        private const byte SOH = 0x01;
        private const byte STX = 0x02;
        private const byte EOT = 0x04;
        private const byte ACK = 0x06;
        private const byte NAK = 0x15;
        private const int CA = 0x18;
        private const byte CRC16 = 0x43;
        private const byte NEGATIVE_BYTE = 0xFF;
        private const byte ABORT1 = 0x41;
        private const byte ABORT2 = 0x61;
        private const int NAK_TIMEOUT = 0x100000;
        private const int DOWNLOAD_TIMEOUT = 1000; // 重试延迟时间:1秒
        private const int MAX_ERRORS = 5;
        private const int USER_FLASH_SIZE = 0x00010000; // 默认的小型模板应用大小

        // 初始化一个足够大的缓冲区用于存储数据包
        private static readonly byte[] aPacketData = new byte[PACKET_1K_SIZE + PACKET_DATA_INDEX + PACKET_TRAILER_SIZE];

        // 将整数转换为字符数组(模拟C语言中的字符串)
        public static void IntToCharArray(int intnum, char[] p_str)
        {
            int divider = 1000000000;
            int pos = 0, status = 0;
            for (int i = 0; i < 10; i++)
            {
                p_str[pos++] = (char)((intnum / divider) + '0');
                intnum %= divider;
                divider /= 10;
                // 若当前字符为'0'且状态为初始态,则清空之前填充的字符
                if ((p_str[pos - 1] == '0') && (status == 0))
                {
                    pos = 0;
                }
                else
                {
                    status++;
                }
            }
        }

        // 更新CRC16值
        public static ushort UpdateCRC16(ushort crc_in, byte byteValue)
        {
            uint crc = crc_in;
            uint inValue = byteValue | 0x100u;
            do
            {
                crc <<= 1;
                inValue <<= 1;
                // 按照CRC16校验规则更新crc值
                if ((inValue & 0x100u) != 0)
                    crc++;
                if ((crc & 0x10000u) != 0)
                    crc ^= 0x1021u;
            } while ((inValue & 0x10000u) == 0);

            return (ushort)(crc & 0xffff);
        }

        // 计算YModem协议数据包的CRC16校验值
        public static ushort CalculateCRC16(byte[] data, int offset, int size)
        {
            uint crc = 0;
            // 遍历数据并逐个更新CRC值
            for (int i = offset; i < offset + size; i++)
                crc = UpdateCRC16((ushort)crc, data[i]);
            // 根据协议要求额外进行两次UpdateCRC16操作
            crc = UpdateCRC16((ushort)crc, 0);
            crc = UpdateCRC16((ushort)crc, 0);
            return (ushort)(crc & 0xffff);
        }

        // 准备初始数据包(文件名和长度信息)
        private static void PrepareInitialPacket(byte[] p_data, string p_file_name, int length)
        {
            int i = 0, j = 0;
            char[] astring = new char[10];

            // 前3个字节固定不变
            p_data[PACKET_START_INDEX] = SOH;
            p_data[PACKET_NUMBER_INDEX] = 0x00;
            p_data[PACKET_CNUMBER_INDEX] = 0xff;

            // 写入文件名
            for (; i < FILE_NAME_LENGTH && i < p_file_name.Length; i++)
            {
                p_data[i + PACKET_DATA_INDEX] = (byte)p_file_name[i];
            }

            p_data[i + PACKET_DATA_INDEX] = 0x00; // 文件名以'\0'结束

            // 写入文件大小
            IntToCharArray(length, astring);
            i += PACKET_DATA_INDEX + 1;
            for (; j < astring.Length && astring[j] != '\0'; j++, i++)
            {
                p_data[i] = (byte)astring[j];
            }

            // 用0填充剩余空间
            for (j = i; j < PACKET_SIZE + PACKET_DATA_INDEX; j++)
            {
                p_data[j] = 0;
            }
        }

        // 准备数据包内容
        private static void PreparePacket(byte[] p_source, int recordIndex, byte[] p_packet, byte pkt_nr, int size_blk)
        {
            int packet_size, size, i;

            // 确定数据包大小
            packet_size = size_blk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;
            size = size_blk < packet_size ? size_blk : packet_size;
            if (packet_size == PACKET_1K_SIZE)
            {
                p_packet[PACKET_START_INDEX] = STX;
            }
            else
            {
                p_packet[PACKET_START_INDEX] = SOH;
            }
            // 设置包序号和校验序号
            p_packet[PACKET_NUMBER_INDEX] = pkt_nr;
            p_packet[PACKET_CNUMBER_INDEX] = (byte)(~pkt_nr);

            // 将有效数据写入数据包
            for (i = PACKET_DATA_INDEX; i < size + PACKET_DATA_INDEX; i++)
            {
                p_packet[i] = p_source[recordIndex++];
            }
            // 如果数据大小小于等于包大小,则用EOF标识填充剩余空间
            if (size <= packet_size)
            {
                for (i = size + PACKET_DATA_INDEX; i < packet_size + PACKET_DATA_INDEX; i++)
                {
                    p_packet[i] = 0x1A; // EOF (0x1A) 或者填充0x00
                }
            }
        }

        public static void SendFile(SerialPortWrapper serialWrapper, string filePath)
        {
            int size = 0;
            int pktSize;
            uint errors = 0, ackRecpt = 0;
            byte[] pBufInt;
            COM_StatusTypeDef result = COM_StatusTypeDef.COM_OK;
            byte blkNumber = 1;
            byte[] aRxCtrl = new byte[2];
            byte i;
            uint tempCrc;
            string fileName;
            long fileSize;
            uint bytesRead;
            int currentIndex = 0;

            // 打开文件并获取相关信息
            using (FileStream file = File.Open(filePath, FileMode.Open, FileAccess.Read))
            {
                fileName = Path.GetFileName(filePath);
                fileSize = file.Length;

                Console.WriteLine($"发送文件:{fileName},大小:{fileSize} 字节");

                // 分配缓冲区并读取文件内容
                pBufInt = new byte[fileSize];
                file.Read(pBufInt, 0, (int)fileSize);

                // 准备首包 - 文件头信息
                PrepareInitialPacket(aPacketData, fileName, (int)fileSize);

                while (0 == ackRecpt && result == COM_StatusTypeDef.COM_OK)
                {
                    // 发送数据包及CRC校验值
                    serialWrapper.Write(aPacketData, PACKET_START_INDEX, PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

                    tempCrc = CalculateCRC16(aPacketData, PACKET_DATA_INDEX, PACKET_SIZE);
                    serialWrapper.Write((byte)(tempCrc >> 8));
                    serialWrapper.Write((byte)(tempCrc & 0xFF));

                    // 等待ACK或CA响应
                    if (serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT))
                    {
                        if (aRxCtrl[0] == ACK)
                        {
                            ackRecpt = 1;
                        }
                        else if (aRxCtrl[0] == CA)
                        {
                            if (serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT) && aRxCtrl[0] == CA)
                            {
                                Thread.Sleep(2);
                                serialWrapper.ClearReceiveBuffer();
                                result = COM_StatusTypeDef.COM_ABORT;
                            }
                        }
                    }
                    else
                    {
                        errors++;
                    }

                    if (errors >= MAX_ERRORS)
                    {
                        result = COM_StatusTypeDef.COM_ERROR;
                    }
                }

                size = (int)fileSize;

                // 按照每包1024字节长度发送后续数据包
                while ((size > 0) && (result == COM_StatusTypeDef.COM_OK))
                {
                    // 准备下一个数据包
                    PreparePacket(pBufInt, currentIndex, aPacketData, blkNumber, size);

                    ackRecpt = 0;
                    Array.Clear(aRxCtrl, 0, aRxCtrl.Length);
                    errors = 0;

                    // 若收到NAK则重发数据包,直到接收到ACK或达到最大错误次数
                    while (0 == ackRecpt && result == COM_StatusTypeDef.COM_OK)
                    {
                        /* Send next packet */
                        if (size >= PACKET_1K_SIZE)
                        {
                            pktSize = PACKET_1K_SIZE;
                        }
                        else
                        {
                            pktSize = PACKET_SIZE;
                        }
                        // 发送下一数据包及其CRC校验值
                        tempCrc = CalculateCRC16(aPacketData, PACKET_DATA_INDEX, pktSize);

                        aPacketData[pktSize + PACKET_HEADER_SIZE + PACKET_START_INDEX + 0] = ((byte)(tempCrc >> 8));
                        aPacketData[pktSize + PACKET_HEADER_SIZE + PACKET_START_INDEX + 1] = ((byte)(tempCrc & 0xFF));
                        serialWrapper.Write(aPacketData, PACKET_START_INDEX, pktSize + PACKET_HEADER_SIZE + 2, NAK_TIMEOUT);
                        serialWrapper.ClearReceiveBuffer();

                        // 计算进度并输出
                        uint progress = (uint)((float)(fileSize - size) / fileSize * 100);
                        Console.WriteLine($"当前进度:{progress}%");

                        // 等待ACK响应
                        if (serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT))
                        {
                            if (aRxCtrl[0] == ACK)
                            {
                                ackRecpt = 1;
                                if (size > pktSize)
                                {
                                    currentIndex += pktSize;
                                    size -= pktSize;
                                    if (blkNumber == (USER_FLASH_SIZE / PACKET_1K_SIZE))
                                    {
                                        result = COM_StatusTypeDef.COM_LIMIT; /* boundary error */
                                    }
                                    else
                                    {
                                        blkNumber++;
                                    }
                                }
                                else
                                {
                                    currentIndex += pktSize;
                                    size = 0;
                                }
                            }
                        }
                        else
                        {
                            errors++;
                        }

                        if (errors >= MAX_ERRORS)
                        {
                            result = COM_StatusTypeDef.COM_ERROR;
                        }
                    }
                }

                // 发送结束传输字符EOT
                ackRecpt = 0;
                Array.Clear(aRxCtrl, 0, aRxCtrl.Length);
                errors = 0;

                while (0 == ackRecpt && result == COM_StatusTypeDef.COM_OK)
                {
                    serialWrapper.Write(EOT);

                    // 等待ACK响应
                    if (serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT))
                    {
                        if (aRxCtrl[0] == ACK)
                        {
                            ackRecpt = 1;
                        }
                        else if (aRxCtrl[0] == CA)
                        {
                            if ((serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT)) && (aRxCtrl[0] == CA))
                            {
                                Thread.Sleep(2);
                                serialWrapper.ClearReceiveBuffer();
                                result = COM_StatusTypeDef.COM_ABORT;
                            }
                        }
                    }
                    else
                    {
                        errors++;
                    }

                    if (errors >= MAX_ERRORS)
                    {
                        result = COM_StatusTypeDef.COM_ERROR;
                    }
                }

                // 发送空数据包以关闭会话
                if (result == COM_StatusTypeDef.COM_OK)
                {
                    // 准备空数据包
                    aPacketData[PACKET_START_INDEX] = SOH;
                    aPacketData[PACKET_NUMBER_INDEX] = 0;
                    aPacketData[PACKET_CNUMBER_INDEX] = 0xFF;
                    for (i = PACKET_DATA_INDEX; i < (PACKET_SIZE + PACKET_DATA_INDEX); i++)
                    {
                        aPacketData[i] = 0x00;
                    }

                    // 发送数据包及CRC校验值
                    serialWrapper.Write(aPacketData, PACKET_START_INDEX, PACKET_SIZE + PACKET_HEADER_SIZE, NAK_TIMEOUT);

                    tempCrc = CalculateCRC16(aPacketData, PACKET_DATA_INDEX, PACKET_SIZE);
                    serialWrapper.Write((byte)(tempCrc >> 8));
                    serialWrapper.Write((byte)(tempCrc & 0xFF));

                    // 等待CA响应
                    if (serialWrapper.Read(aRxCtrl, 1, NAK_TIMEOUT))
                    {
                        if (aRxCtrl[0] == CA)
                        {
                            Thread.Sleep(2);
                            serialWrapper.ClearReceiveBuffer();
                            result = COM_StatusTypeDef.COM_ABORT;
                        }
                    }
                }

                Console.WriteLine("当前进度:100%");
                serialWrapper.ResetTimeout(50);
            }
        }
    }
}
View Code
复制代码

使用方法:烧好bootloader的板子上电,进入菜单,摁1进入下载镜像文件STM32F091RC_APP.bin到内部Flash,等待回复C摁下6开始把STM32F091RC_APP.bin传输到flash,进度达到100%即传输完毕。摁3程序启动APP。

 

posted @   阿坦  阅读(1792)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-06-07 WPF使用MVVMLight的ViewModel 访问控件的属性方法事件以及多页面传递信息
点击右上角即可分享
微信分享提示