IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32使用USB虚拟串口+YMODEM实现IAP升级 -> 正文阅读

[嵌入式]STM32使用USB虚拟串口+YMODEM实现IAP升级

USB虚拟串口

基于USB CDC可以将USB做成虚拟串口,这块ST已经给我们做好了,直接使用相关封装好的接口就可以了,具体使用参考:
https://blog.csdn.net/mirco_mcu/article/details/106081950
这里就不再赘述了。注意两点:

  1. 配置好后与上们机通信不用关心波特率,一般波特率都能通信
  2. 不需要改任何配置,直接使用以下接口
    CDC_Receive_FS,这个有点像串口IDLE中断了
    CDC_Transmit_FS

YMODEM协议

这也不用讲了😂,看这个就够了,一定要搞清楚一个完整的传输流程。

进入正题

  • flash编程,参考这里
  • ymodem通信实现
    主要接口:
/**
 * @brief CDC_Receive_FS中调用,解析一个CDC包
 * 
 * @param packet 
 * @param len 
 * @return uint8_t 
 */
uint8_t PacketParse(uint8_t* packet, uint8_t len);
/**
 * @brief 没有建立连接前在定时器中调用,周期性地发送'C'
 * 
 * @param arg 
 */
void YmodemHandshakeCb(void const* arg);
/**
 * @brief 处理ymodem包
 * 
 */
void YmodemPacketHandle(void);

ymodem相关的定义:

/* Packet structure defines */
#define PACKET_HEADER_SIZE      ((uint32_t)3)
#define PACKET_DATA_INDEX       ((uint32_t)3)
#define PACKET_START_INDEX      ((uint32_t)0)
#define PACKET_NUMBER_INDEX     ((uint32_t)1)
#define PACKET_CNUMBER_INDEX    ((uint32_t)2)
#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)

实现:

static const uint32_t app_max_size = 80 * 1024;  // 80k
static uint16_t packet_size = 0;
static bool connect_established;
static bool end_of_transmission;
static bool received_begin;
static bool received_data;
static bool received_end;
static bool got_file_disc;
static uint32_t update_file_total_size;
static uint8_t ack_and_c[2] = {ACK, CRC16};

uint8_t wait_second_eot;

static void ClearFlags(void) {
  connect_established = false;
  got_file_disc = false;
  end_of_transmission = false;
  wait_second_eot = 0;
}

void PutByte(uint8_t byte) {
  CDC_Transmit_FS(&byte, 1);
}

void YmodemHandshakeCb(void const* arg) {
  if (!connect_established) {
    PutByte(CRC16);
  }

  if (end_of_transmission) {
    wait_second_eot++;
    // 没有收到第二个EOT
    if (wait_second_eot >= 5) {
      ClearFlags();
    }
  }
}

static uint16_t CalculateCrc16(unsigned char *q, int len)
{
    uint16_t crc;
    char i;

    crc = 0;
    while (--len >= 0)
    {
        crc = crc ^ (int) * q++ << 8;
        i = 8;
        do
        {
            if (crc & 0x8000)
                crc = crc << 1 ^ 0x1021;
            else
                crc = crc << 1;
        }
        while (--i);
    }

    return (crc);
}

static void PutBytes(uint8_t* bytes, uint8_t len) {
  CDC_Transmit_FS(bytes, len);
}

/**
 * @brief 终止传输
 * 
 */
static void TransAbort(void) {
  uint8_t tmp[2] = {CA, CA};
  printf("%s\n", __LINE__);
  PutBytes(tmp, 2);
}

enum {
  CHECK_HEAD,
  COPY_PACKET,
  CHECK_CRC
};

uint8_t ymodem_packet[1029];

void YmodemPacketHandle(void) {
  static uint32_t current_write_size = 0;
  static uint32_t write_ptr = 0;

  // 收到传输开始包
  if (received_begin) {
    received_begin = false;
    if (!got_file_disc) {
      char* file_name = (char*)&ymodem_packet[PACKET_DATA_INDEX];
      char* file_size = (char*)&ymodem_packet[PACKET_DATA_INDEX + strlen(file_name) + 1];
      update_file_total_size = atol(file_size);
      printf("file: %s %d\n", file_name, update_file_total_size);
    
      if (update_file_total_size > app_max_size) {
        // abort transmission
        TransAbort();
      } else {
        // Unlocks Flash for write access
        FLASH_If_Init();
        // erase app area
        FLASH_If_Erase(UPDATE_APP_ADDRESS, update_file_total_size / FLASH_PAGE_SIZE + 1);
        current_write_size = 0;
        // 前一个字用来存放固件大小
        write_ptr = UPDATE_APP_ADDRESS + 4;
        PutBytes(ack_and_c, 2);
      }

      got_file_disc = true;
    }
  }

  // 收到正常数据包
  if (received_data) {
    received_data = false;
    if (current_write_size + packet_size > update_file_total_size) {
      if (FLASH_If_Write(write_ptr, (uint32_t*)&ymodem_packet[PACKET_DATA_INDEX],
         (update_file_total_size - current_write_size) / 4) == FLASHIF_OK) {
        write_ptr += packet_size;
        current_write_size += packet_size;
        PutByte(ACK);
      } else {
        TransAbort();
      }
    } else {
      if (FLASH_If_Write(write_ptr, (uint32_t*)&ymodem_packet[PACKET_DATA_INDEX], packet_size / 4)
          == FLASHIF_OK) {
        write_ptr += packet_size;
        current_write_size += packet_size;
        PutByte(ACK);
      } else {
        TransAbort();
      }
    }
  }

  // 收到结束包
  if (received_end) {
    received_end = false;
    if (FLASH_If_Write(UPDATE_APP_ADDRESS, &update_file_total_size, 1) == FLASHIF_OK) {
      printf("system reset\n");
      // reset mcu
      __set_FAULTMASK(1);
      NVIC_SystemReset();
    }
  }
}
/**
 * @brief USB虚拟串口接收回调。对CDC包解析,并解析到的完整的ymodem包放入全局缓存-ymodem_packet
 * 
 * @param packet 数据包的地址
 * @param len 收到的长度
 * @return uint8_t 
 */
uint8_t PacketParse(uint8_t* packet, uint8_t len) {
  uint32_t crc;
  uint32_t crc_local;
  uint8_t char1;
  
  static uint8_t state = CHECK_HEAD;
  static uint16_t index = 0;

  // CDC一包最大64字节
  switch (state) {
  case CHECK_HEAD:
    char1 = packet[0];
    // printf("head %02x\n", char1);
    switch (char1) {
    case SOH:
      packet_size = PACKET_SIZE;
      index = 0;
      // copy the first packet;
      memcpy(ymodem_packet, packet, len);
      index += len;
      state = COPY_PACKET;
      break;
    case STX:
      packet_size = PACKET_1K_SIZE;
      index = 0;
      // copy the first packet;
      memcpy(ymodem_packet, packet, len);
      index += len;
      state = COPY_PACKET;
      break;
    case EOT:
      if (!end_of_transmission) {
        PutByte(NAK);
        end_of_transmission = true;
      } else {
        PutBytes(ack_and_c, 2);
      }
      break;
    case CA:
      break;
    case ABORT1:
    case ABORT2:
      break;
    
    default:
      break;
    }
    break;

  case COPY_PACKET:
    memcpy(&ymodem_packet[index], packet, len);
    index += len;
    if (index == packet_size + 5) {
      // receice a packet done
      // printf("crc %d\n", index);
      crc = ymodem_packet[PACKET_DATA_INDEX + packet_size] << 8;
      crc += ymodem_packet[PACKET_DATA_INDEX + packet_size + 1];
 
      crc_local = CalculateCrc16(&ymodem_packet[PACKET_DATA_INDEX], packet_size);
      // printf("%x %x\n", crc, crc_local);
      if (crc_local == crc) {
        // printf("crc ok[%d]\n", ymodem_packet[PACKET_NUMBER_INDEX]);
        state = CHECK_HEAD;
        
        if (ymodem_packet[PACKET_START_INDEX] == SOH) {
          if (ymodem_packet[PACKET_NUMBER_INDEX] == 0) {
            if (end_of_transmission) {
              PutByte(ACK);
              received_end = true;
              printf("finish\n");
              ClearFlags();
            } else {
              connect_established = true;
              received_begin = true;
            }
          } else {
            // 小于128字节的数据包
            received_data = true;
          }
        } else {
          received_data = true;  // normal data packet
        }
      } else {
        state = CHECK_HEAD;
      }
    }
    break;
  }

  return 0;
}

usb的cdc接收中断中调用解析接口:

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  // 进入ymodem包的解析
  PacketParse(Buf, *Len);
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

在一个线程中开启一个定时器,定时发出’C’以建立连接,主循环调用ymodem包处理接口:

void IapTaskMain(void const * argument)
{
  /* USER CODE BEGIN IapTaskMain */
  osTimerId timer_handshake;
  osTimerDef(timer_def, YmodemHandshakeCb);
  timer_handshake = osTimerCreate(osTimer(timer_def), osTimerPeriodic, NULL);
  osTimerStart(timer_handshake, 1000);
  /* Infinite loop */
  for(;;)
  {
    YmodemPacketHandle();
    osDelay(1);
  }
  /* USER CODE END IapTaskMain */
}
  • 备注
  1. usb虚拟串口与真实串口在处理上的一个大的区别是,USB一包最大64字节,接收大于64字节的数据包时会多次进入CDC_Receive_FS这个接口,YMODEM的128字节的或是1K字节的包都大于64字节。接收一个ymodem包会多次进入接收中断,所以PacketParse 这个接口的基本流程是,先判别包头,再进行COPY, 最后CRC校验。
  2. 有很多ymodem上位机可选择,我用过的
    tera term 这个开源的,有个毛病就是: 选择文件期间,下位机发送的’C’,它都解析了。导致文件选择完后,下位机收到多个开始包
    xshell6 这个比较好用,有个问题,勾选“发送完成后关闭对话框” ,实际不会关闭,不知道的以为卡了
    推荐使用野火多功能调试助手
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:55:20  更:2022-04-26 11:58:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 2:35:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码