USB虚拟串口
基于USB CDC可以将USB做成虚拟串口,这块ST已经给我们做好了,直接使用相关封装好的接口就可以了,具体使用参考: https://blog.csdn.net/mirco_mcu/article/details/106081950 这里就不再赘述了。注意两点:
- 配置好后与上们机通信不用关心波特率,一般波特率都能通信
- 不需要改任何配置,直接使用以下接口
收CDC_Receive_FS ,这个有点像串口IDLE中断了 发CDC_Transmit_FS
YMODEM协议
这也不用讲了😂,看这个就够了,一定要搞清楚一个完整的传输流程。
进入正题
- flash编程,参考这里
- ymodem通信实现
主要接口:
uint8_t PacketParse(uint8_t* packet, uint8_t len);
void YmodemHandshakeCb(void const* arg);
void YmodemPacketHandle(void);
ymodem相关的定义:
#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)
#define FILE_NAME_LENGTH ((uint32_t)64)
#define FILE_SIZE_LENGTH ((uint32_t)16)
#define SOH ((uint8_t)0x01)
#define STX ((uint8_t)0x02)
#define EOT ((uint8_t)0x04)
#define ACK ((uint8_t)0x06)
#define NAK ((uint8_t)0x15)
#define CA ((uint32_t)0x18)
#define CRC16 ((uint8_t)0x43)
#define NEGATIVE_BYTE ((uint8_t)0xFF)
#define ABORT1 ((uint8_t)0x41)
#define ABORT2 ((uint8_t)0x61)
#define NAK_TIMEOUT ((uint32_t)0x100000)
#define DOWNLOAD_TIMEOUT ((uint32_t)1000)
#define MAX_ERRORS ((uint32_t)5)
实现:
static const uint32_t app_max_size = 80 * 1024;
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++;
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);
}
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) {
TransAbort();
} else {
FLASH_If_Init();
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");
__set_FAULTMASK(1);
NVIC_SystemReset();
}
}
}
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;
switch (state) {
case CHECK_HEAD:
char1 = packet[0];
switch (char1) {
case SOH:
packet_size = PACKET_SIZE;
index = 0;
memcpy(ymodem_packet, packet, len);
index += len;
state = COPY_PACKET;
break;
case STX:
packet_size = PACKET_1K_SIZE;
index = 0;
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) {
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);
if (crc_local == crc) {
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 {
received_data = true;
}
} else {
received_data = true;
}
} else {
state = CHECK_HEAD;
}
}
break;
}
return 0;
}
usb的cdc接收中断中调用解析接口:
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
PacketParse(Buf, *Len);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
}
在一个线程中开启一个定时器,定时发出’C’以建立连接,主循环调用ymodem包处理接口:
void IapTaskMain(void const * argument)
{
osTimerId timer_handshake;
osTimerDef(timer_def, YmodemHandshakeCb);
timer_handshake = osTimerCreate(osTimer(timer_def), osTimerPeriodic, NULL);
osTimerStart(timer_handshake, 1000);
for(;;)
{
YmodemPacketHandle();
osDelay(1);
}
}
- usb虚拟串口与真实串口在处理上的一个大的区别是,USB一包最大64字节,接收大于64字节的数据包时会多次进入
CDC_Receive_FS 这个接口,YMODEM的128字节的或是1K字节的包都大于64字节。接收一个ymodem包会多次进入接收中断,所以PacketParse 这个接口的基本流程是,先判别包头,再进行COPY, 最后CRC校验。 - 有很多ymodem上位机可选择,我用过的
tera term 这个开源的,有个毛病就是: 选择文件期间,下位机发送的’C’,它都解析了。导致文件选择完后,下位机收到多个开始包 xshell6 这个比较好用,有个问题,勾选“发送完成后关闭对话框” ,实际不会关闭,不知道的以为卡了 推荐使用野火多功能调试助手。
|