评估板:GD32H759I-EVAL

连接:评估板的USB_HS0(虚拟串口)连接电脑,USART(串口打印)连接电脑

工程文件:在评估板配套资料的27_USB_Device里面的CDC_ACM上直接修改main.c代码

跳线帽把JP68跳至USART即可。

工程文件的代码即USB虚拟串口通信,在此基础上加上速率评估的功能:

修改main.c

#include "drv_usb_hw.h"
#include "cdc_acm_core.h"
#include "gd32h759i_eval.h"
#include "system_gd32h7xx.h"
#include <stdio.h>
#include <string.h>

// USB核心驱动实例
usb_core_driver cdc_acm;

// 通信统计配置与变量
#define COMM_STOP_DELAY     2000U    // 通信停止判断阈值(ms)
#define USER_DATA_MIN_LEN   1U       // 最小有效数据长度(字节)
volatile uint32_t systick_counter = 0;  // 毫秒计数器
uint32_t start_time = 0;                // 通信开始时间(ms)
uint32_t last_comm_time = 0;            // 最后一次通信时间(ms)
uint32_t total_frames = 0;              // 总用户数据帧数
uint32_t current_window_frames = 0;     // 当前统计窗口内的帧数
uint32_t window_start_time = 0;         // 当前统计窗口的开始时间
uint32_t max_fps = 0;                   // 最大帧率
FlagStatus is_communicating = RESET;    // 通信状态标志
FlagStatus has_new_data = RESET;        // 新数据标志

// 函数声明
void cache_enable(void);
void usart_init(void);
uint32_t get_current_time(void);
void check_comm_stop(void);
void update_comm_stats(void);
void calculate_final_stats(void);
void systick_config(void);
int fputc(int ch, FILE *f);
void user_data_receive_hook(uint8_t *data, uint32_t len);

/*!
    \brief      启用CPU缓存
*/
void cache_enable(void)
{
    SCB_EnableICache();
#ifndef USB_INTERNAL_DMA_ENABLED
    SCB_EnableDCache();
#endif
}

/*!
    \brief      初始化USART用于统计打印
*/
void usart_init(void)
{
    gd_eval_com_init(EVAL_COM);  // 评估板默认串口
    gd_eval_led_init(LED2);
    gd_eval_led_off(LED2);
}

/*!
    \brief      获取当前时间(毫秒)
*/
uint32_t get_current_time(void)
{
    return systick_counter;
}

/*!
    \brief      检查通信是否停止
*/
void check_comm_stop(void)
{
    if(is_communicating == SET) {
        uint32_t current_time = get_current_time();
        if((current_time - last_comm_time) >= COMM_STOP_DELAY) {
            calculate_final_stats();
            
            uint32_t total_time = last_comm_time - start_time;
            if(total_time == 0) total_time = 1;
            
            printf("\r\n===== 通信结束统计 =====");
            printf("\r\n总通信时间: %d ms (%.2f 秒)", 
                   total_time, total_time / 1000.0f);
            printf("\r\n总用户数据帧数: %d 帧", total_frames);
            printf("\r\n平均帧率: %.1f 帧/秒", 
                   (float)total_frames / (total_time / 1000.0f));
            printf("\r\n最大帧率: %d 帧/秒", max_fps);
            printf("\r\n========================\r\n");
            printf("等待用户数据...\r\n");

            // 重置统计变量
            is_communicating = RESET;
            total_frames = 0;
            max_fps = 0;
            start_time = 0;
            current_window_frames = 0;
            window_start_time = 0;
            gd_eval_led_off(LED2);
        }
    }
}

/*!
    \brief      通信结束时计算最后一段的帧率
*/
void calculate_final_stats(void)
{
    if(current_window_frames > 0) {
        uint32_t window_duration = last_comm_time - window_start_time;
        if(window_duration == 0) window_duration = 1;
        
        uint32_t fps = (current_window_frames * 1000) / window_duration;
        if(fps > max_fps) {
            max_fps = fps;
        }
        printf("\r\n[结束前统计] 最后 %.1f 秒内帧率: %d 帧/秒",
               window_duration / 1000.0f, fps);
    }
}

/*!
    \brief      更新通信统计
*/
void update_comm_stats(void)
{
    uint32_t current_time = get_current_time();
    
    if(is_communicating == RESET) {
        is_communicating = SET;
        start_time = current_time;
        last_comm_time = current_time;
        window_start_time = current_time;
        current_window_frames = 0;
        max_fps = 0;
        printf("\r\n===== 检测到用户数据,开始实时统计 =====");
        gd_eval_led_on(LED2);
    }

    last_comm_time = current_time;
    total_frames++;
    current_window_frames++;

    // 每1秒打印一次实时帧率
    if((current_time - window_start_time) >= 1000U) {
        uint32_t fps = current_window_frames;
        if(fps > max_fps) {
            max_fps = fps;
        }
        printf("\r\n[实时统计] 已通信 %.1f 秒 - 帧率: %d 帧/秒 (累计: %d 帧)",
               (current_time - start_time)/1000.0f, fps, total_frames);
        
        window_start_time = current_time;
        current_window_frames = 0;
        gd_eval_led_toggle(LED2);
    }
}

/*!
    \brief      重定向printf到USART
*/
int fputc(int ch, FILE *f)
{
    usart_data_transmit(EVAL_COM, (uint8_t)ch);
    while(RESET == usart_flag_get(EVAL_COM, USART_FLAG_TBE));
    return ch;
}

/*!
    \brief      配置SysTick定时器(1ms中断)
*/
void systick_config(void)
{
    if(SysTick_Config(SystemCoreClock / 1000U)) {
        while(1);
    }
    NVIC_SetPriority(SysTick_IRQn, 0x0FU);
}

/*!
    \brief      SysTick中断服务程序(毫秒计数)
*/
void SysTick_Handler(void)
{
    systick_counter++;
}

/*!
    \brief      用户数据接收钩子函数(关键修改:所有数据都计数)
*/
void user_data_receive_hook(uint8_t *data, uint32_t len)
{
    // 只要数据长度符合要求,就视为有效帧并计数
    if(len >= USER_DATA_MIN_LEN) {
        last_comm_time = get_current_time();
        has_new_data = SET;  // 所有符合长度的数据包都触发统计更新
    }
}

/*!
    \brief      主函数
*/
int main(void)
{
    cache_enable();
    usart_init();
    systick_config();

    printf("USB虚拟串口就绪!请通过串口助手(COM11)发送数据...\r\n");

    // USB初始化
    usb_rcu_config();
    usb_timer_init();

#ifdef USE_USBHS0
#ifdef USE_USB_FS
    usb_para_init(&cdc_acm, USBHS0, USB_SPEED_FULL);
#endif
#ifdef USE_USB_HS
    usb_para_init(&cdc_acm, USBHS0, USB_SPEED_HIGH);
#endif
#endif

#ifdef USE_USBHS1
#ifdef USE_USB_FS
    usb_para_init(&cdc_acm, USBHS1, USB_SPEED_FULL);
#endif
#ifdef USE_USB_HS
    usb_para_init(&cdc_acm, USBHS1, USB_SPEED_HIGH);
#endif
#endif

    usbd_init(&cdc_acm, &cdc_desc, &cdc_class);

#ifdef USE_USB_HS
#ifndef USE_ULPI_PHY
#ifdef USE_USBHS0
    pllusb_rcu_config(USBHS0);
#elif defined USE_USBHS1
    pllusb_rcu_config(USBHS1);
#endif
#endif
#endif

    usb_intr_config();

    // 主循环
    while(1) {
        if(USBD_CONFIGURED == cdc_acm.dev.cur_status) {
            if(0U == cdc_acm_check_ready(&cdc_acm)) {
                cdc_acm_data_receive(&cdc_acm);
            } else {
                cdc_acm_data_send(&cdc_acm);
            }

            if(has_new_data) {
                update_comm_stats();
                has_new_data = RESET;
            }
        }
        check_comm_stop();
    }
}
    

修改固件库 GD32H7xx_Firmware_Library_V1.4.0中的cdc_acm_core.c


#include "cdc_acm_core.h"
// 新增:声明用户数据接收钩子函数(与app.c中的函数对应)
extern void user_data_receive_hook(uint8_t *data, uint32_t len);

#define USBD_VID                          0x28E9U
#define USBD_PID                          0x018AU

/* note:it should use the C99 standard when compiling the below codes */
/* USB standard device descriptor */
__ALIGN_BEGIN const usb_desc_dev cdc_dev_desc __ALIGN_END = {
    .header =
    {
        .bLength          = USB_DEV_DESC_LEN,
        .bDescriptorType  = USB_DESCTYPE_DEV,
    },
    .bcdUSB                = 0x0200U,
    .bDeviceClass          = USB_CLASS_CDC,
    .bDeviceSubClass       = 0x00U,
    .bDeviceProtocol       = 0x00U,
    .bMaxPacketSize0       = USB_FS_EP0_MAX_LEN,
    .idVendor              = USBD_VID,
    .idProduct             = USBD_PID,
    .bcdDevice             = 0x0100U,
    .iManufacturer         = STR_IDX_MFC,
    .iProduct              = STR_IDX_PRODUCT,
    .iSerialNumber         = STR_IDX_SERIAL,
    .bNumberConfigurations = USBD_CFG_MAX_NUM,
};

/* USB device configuration descriptor */
__ALIGN_BEGIN const usb_cdc_desc_config_set cdc_config_desc __ALIGN_END = {
    .config =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_config),
            .bDescriptorType = USB_DESCTYPE_CONFIG,
        },
        .wTotalLength         = USB_CDC_ACM_CONFIG_DESC_SIZE,
        .bNumInterfaces       = 0x02U,
        .bConfigurationValue  = 0x01U,
        .iConfiguration       = 0x00U,
        .bmAttributes         = 0x80U,
        .bMaxPower            = 0x32U
    },

    .cmd_itf =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_itf),
            .bDescriptorType = USB_DESCTYPE_ITF
        },
        .bInterfaceNumber     = 0x00U,
        .bAlternateSetting    = 0x00U,
        .bNumEndpoints        = 0x01U,
        .bInterfaceClass      = USB_CLASS_CDC,
        .bInterfaceSubClass   = USB_CDC_SUBCLASS_ACM,
        .bInterfaceProtocol   = USB_CDC_PROTOCOL_AT,
        .iInterface           = 0x00U
    },

    .cdc_header =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_header_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
        },
        .bDescriptorSubtype  = 0x00U,
        .bcdCDC              = 0x0110U
    },

    .cdc_call_managment =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_call_managment_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
        },
        .bDescriptorSubtype  = 0x01U,
        .bmCapabilities      = 0x00U,
        .bDataInterface      = 0x01U
    },

    .cdc_acm =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_acm_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
        },
        .bDescriptorSubtype  = 0x02U,
        .bmCapabilities      = 0x02U,
    },

    .cdc_union =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_union_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
        },
        .bDescriptorSubtype  = 0x06U,
        .bMasterInterface    = 0x00U,
        .bSlaveInterface0    = 0x01U,
    },

    .cdc_cmd_endpoint =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_ep),
            .bDescriptorType = USB_DESCTYPE_EP,
        },
        .bEndpointAddress    = CDC_CMD_EP,
        .bmAttributes        = USB_EP_ATTR_INT,
        .wMaxPacketSize      = USB_CDC_CMD_PACKET_SIZE,
        .bInterval           = 0x0AU
    },

    .cdc_data_interface =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_itf),
            .bDescriptorType = USB_DESCTYPE_ITF,
        },
        .bInterfaceNumber    = 0x01U,
        .bAlternateSetting   = 0x00U,
        .bNumEndpoints       = 0x02U,
        .bInterfaceClass     = USB_CLASS_DATA,
        .bInterfaceSubClass  = 0x00U,
        .bInterfaceProtocol  = USB_CDC_PROTOCOL_NONE,
        .iInterface          = 0x00U
    },

    .cdc_out_endpoint =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_ep),
            .bDescriptorType = USB_DESCTYPE_EP,
        },
        .bEndpointAddress     = CDC_DATA_OUT_EP,
        .bmAttributes         = USB_EP_ATTR_BULK,
        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
        .bInterval            = 0x00U
    },

    .cdc_in_endpoint =
    {
        .header =
        {
            .bLength         = sizeof(usb_desc_ep),
            .bDescriptorType = USB_DESCTYPE_EP
        },
        .bEndpointAddress     = CDC_DATA_IN_EP,
        .bmAttributes         = USB_EP_ATTR_BULK,
        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
        .bInterval            = 0x00U
    }
};

/* USB language ID Descriptor */
__ALIGN_BEGIN static const usb_desc_LANGID usbd_language_id_desc __ALIGN_END = {
    .header =
    {
        .bLength         = sizeof(usb_desc_LANGID),
        .bDescriptorType = USB_DESCTYPE_STR,
    },
    .wLANGID              = ENG_LANGID
};

/* USB manufacture string */
__ALIGN_BEGIN static const usb_desc_str manufacturer_string __ALIGN_END = {
    .header =
    {
        .bLength         = USB_STRING_LEN(10U),
        .bDescriptorType = USB_DESCTYPE_STR,
    },
    .unicode_string = {'G', 'i', 'g', 'a', 'D', 'e', 'v', 'i', 'c', 'e'}
};

/* USB product string */
__ALIGN_BEGIN static const usb_desc_str product_string __ALIGN_END = {
    .header =
    {
        .bLength         = USB_STRING_LEN(12U),
        .bDescriptorType = USB_DESCTYPE_STR,
    },
    .unicode_string = {'G', 'D', '3', '2', '-', 'C', 'D', 'C', '_', 'A', 'C', 'M'}
};

/* USB serial string */
__ALIGN_BEGIN static usb_desc_str serial_string __ALIGN_END = {
    .header =
    {
        .bLength         = USB_STRING_LEN(12U),
        .bDescriptorType = USB_DESCTYPE_STR,
    }
};

/* USB string descriptor set */
void *const usbd_cdc_strings[] = {
    [STR_IDX_LANGID]  = (uint8_t *)&usbd_language_id_desc,
    [STR_IDX_MFC]     = (uint8_t *)&manufacturer_string,
    [STR_IDX_PRODUCT] = (uint8_t *)&product_string,
    [STR_IDX_SERIAL]  = (uint8_t *)&serial_string
};

usb_desc cdc_desc = {
    .dev_desc    = (uint8_t *)&cdc_dev_desc,
    .config_desc = (uint8_t *)&cdc_config_desc,
    .strings     = usbd_cdc_strings
};

/* local function prototypes ('static') */
static uint8_t cdc_acm_init(usb_dev *udev, uint8_t config_index);
static uint8_t cdc_acm_deinit(usb_dev *udev, uint8_t config_index);
static uint8_t cdc_acm_req(usb_dev *udev, usb_req *req);
static uint8_t cdc_ctlx_out(usb_dev *udev);
static uint8_t cdc_acm_in(usb_dev *udev, uint8_t ep_num);
static uint8_t cdc_acm_out(usb_dev *udev, uint8_t ep_num);

/* USB CDC device class callbacks structure */
usb_class_core cdc_class = {
    .command   = NO_CMD,
    .alter_set = 0U,

    .init      = cdc_acm_init,
    .deinit    = cdc_acm_deinit,
    .req_proc  = cdc_acm_req,

    .ctlx_out  = cdc_ctlx_out,
    .data_in   = cdc_acm_in,
    .data_out  = cdc_acm_out
};

/*!
    \brief      check CDC ACM is ready for data transfer
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     0 if CDC is ready, else 1
*/
uint8_t cdc_acm_check_ready(usb_dev *udev)
{
    if(NULL != udev->dev.class_data[CDC_COM_INTERFACE]) {
        usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

        if((1U == cdc->packet_receive) && (1U == cdc->packet_sent)) {
            return 0U;
        }
    }

    return 1U;
}

/*!
    \brief      send CDC ACM data
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     none
*/
void cdc_acm_data_send(usb_dev *udev)
{
    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    if(0U != cdc->receive_length) {
        cdc->packet_sent = 0U;

        usbd_ep_send(udev, CDC_DATA_IN_EP, (uint8_t *)(cdc->data), cdc->receive_length);

        cdc->receive_length = 0U;
    }
}

/*!
    \brief      receive CDC ACM data
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     none
*/
void cdc_acm_data_receive(usb_dev *udev)
{
    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    cdc->packet_receive = 0U;
    cdc->packet_sent = 0U;

    usbd_ep_recev(udev, CDC_DATA_OUT_EP, (uint8_t *)(cdc->data), USB_CDC_DATA_PACKET_SIZE);
}

/*!
    \brief      initialize the CDC ACM device
    \param[in]  udev: pointer to USB device instance
    \param[in]  config_index: configuration index
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_acm_init(usb_dev *udev, uint8_t config_index)
{
    static usb_cdc_handler cdc_handler;

    /* initialize the data TX endpoint */
    usbd_ep_setup(udev, &(cdc_config_desc.cdc_in_endpoint));

    /* initialize the data RX endpoint */
    usbd_ep_setup(udev, &(cdc_config_desc.cdc_out_endpoint));

    /* initialize the command TX endpoint */
    usbd_ep_setup(udev, &(cdc_config_desc.cdc_cmd_endpoint));

    /* initialize CDC handler structure */
    cdc_handler.packet_receive = 1U;
    cdc_handler.packet_sent = 1U;
    cdc_handler.receive_length = 0U;

    cdc_handler.line_coding = (acm_line) {
        .dwDTERate   = 115200U,
        .bCharFormat = 0U,
        .bParityType = 0U,
        .bDataBits   = 0x08U
    };

    udev->dev.class_data[CDC_COM_INTERFACE] = (void *)&cdc_handler;

    return USBD_OK;
}

/*!
    \brief      de-initialize the CDC ACM device
    \param[in]  udev: pointer to USB device instance
    \param[in]  config_index: configuration index
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_acm_deinit(usb_dev *udev, uint8_t config_index)
{
    /* deinitialize the data TX/RX endpoint */
    usbd_ep_clear(udev, CDC_DATA_IN_EP);
    usbd_ep_clear(udev, CDC_DATA_OUT_EP);

    /* deinitialize the command TX endpoint */
    usbd_ep_clear(udev, CDC_CMD_EP);

    return USBD_OK;
}

/*!
    \brief      handle the CDC ACM class-specific requests
    \param[in]  udev: pointer to USB device instance
    \param[in]  req: device class-specific request
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_acm_req(usb_dev *udev, usb_req *req)
{
    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    usb_transc *transc = NULL;

    switch(req->bRequest) {
    case SEND_ENCAPSULATED_COMMAND:
        /* no operation for this driver */
        break;

    case GET_ENCAPSULATED_RESPONSE:
        /* no operation for this driver */
        break;

    case SET_COMM_FEATURE:
        /* no operation for this driver */
        break;

    case GET_COMM_FEATURE:
        /* no operation for this driver */
        break;

    case CLEAR_COMM_FEATURE:
        /* no operation for this driver */
        break;

    case SET_LINE_CODING:
        transc = &udev->dev.transc_out[0];

        /* set the value of the current command to be processed */
        udev->dev.class_core->alter_set = req->bRequest;

        /* enable EP0 prepare to receive command data packet */
        transc->remain_len = req->wLength;
        transc->xfer_buf = cdc->cmd;
        break;

    case GET_LINE_CODING:
        transc = &udev->dev.transc_in[0];

        cdc->cmd[0] = (uint8_t)(cdc->line_coding.dwDTERate);
        cdc->cmd[1] = (uint8_t)(cdc->line_coding.dwDTERate >> 8);
        cdc->cmd[2] = (uint8_t)(cdc->line_coding.dwDTERate >> 16);
        cdc->cmd[3] = (uint8_t)(cdc->line_coding.dwDTERate >> 24);
        cdc->cmd[4] = cdc->line_coding.bCharFormat;
        cdc->cmd[5] = cdc->line_coding.bParityType;
        cdc->cmd[6] = cdc->line_coding.bDataBits;

        transc->xfer_buf = cdc->cmd;
        transc->remain_len = 7U;
        break;

    case SET_CONTROL_LINE_STATE:
        /* no operation for this driver */
        break;

    case SEND_BREAK:
        /* no operation for this driver */
        break;

    default:
        break;
    }

    return USBD_OK;
}

/*!
    \brief      handle CDC ACM control OUT stage
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_ctlx_out(usb_dev *udev)
{
    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    if(NO_CMD != udev->dev.class_core->alter_set) {
        /* process the command data */
        cdc->line_coding.dwDTERate = (uint32_t)((uint32_t)cdc->cmd[0] | \
                                                ((uint32_t)cdc->cmd[1] << 8) | \
                                                ((uint32_t)cdc->cmd[2] << 16) | \
                                                ((uint32_t)cdc->cmd[3] << 24));

        cdc->line_coding.bCharFormat = cdc->cmd[4];
        cdc->line_coding.bParityType = cdc->cmd[5];
        cdc->line_coding.bDataBits = cdc->cmd[6];

        udev->dev.class_core->alter_set = NO_CMD;
    }

    return USBD_OK;
}

/*!
    \brief      handle CDC ACM data IN stage
    \param[in]  udev: pointer to USB device instance
    \param[in]  ep_num: endpoint identifier
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_acm_in(usb_dev *udev, uint8_t ep_num)
{
    usb_transc *transc = &udev->dev.transc_in[EP_ID(ep_num)];

    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    if((0U == transc->xfer_len % transc->max_len) && (0U != transc->xfer_len)) {
        usbd_ep_send(udev, ep_num, NULL, 0U);
    } else {
        cdc->packet_sent = 1U;
    }

    return USBD_OK;
}

/*!
    \brief      handle CDC ACM data OUT stage(修改部分:添加钩子调用)
    \param[in]  udev: pointer to USB device instance
    \param[in]  ep_num: endpoint identifier
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t cdc_acm_out(usb_dev *udev, uint8_t ep_num)
{
    usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];

    // 库原有逻辑:更新接收状态和长度
    cdc->packet_receive = 1U;
    cdc->receive_length = ((usb_core_driver *)udev)->dev.transc_out[ep_num].xfer_count;

    // 新增:当收到有效用户数据时,调用钩子函数
    if(cdc->receive_length > 0) {
        // cdc->data是接收缓冲区,receive_length是实际长度
        user_data_receive_hook(cdc->data, cdc->receive_length);
    }

    return USBD_OK;
}
    

打开两个串口调试助手,一个是USB虚拟串口进行通信,另一个是USART进行结果打印:

完整工程文件:时叶彤/MyTest - Gitee.com

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐