基于CH32L103的USB转I2C/SMBUS的调试助手项目(代码开源)

前言:本文为手把手教学的基于 CH32L103 的 USB 转 I2C/SMBUS 的调试助手项目,项目使用的 MCU 为沁恒的 CH32L103C8T6 搭配 Qt Creator 制作上位机,Qt 的版本为 Qt 5.9.0,且作者额外提供本项目的硬件电路设计图。嵌入式产品开发过程中不可避免地可能需要利用 I2C/SMBUS 进行调试,例如:redriver 与 eeprom 等,作者本篇博客制作的 iKun USB IO DEBUG Helper 将很有可能成为您工作中调试的利器。希望这篇博文能给读者朋友的工程项目给予些许帮助,Respect(代码开源)!

**硬件与软件:**CH32L103C8T6、iKun USB IO DEBUG Helper、MounRiver Studio Ⅱ、嘉立创 EDA、Qt 5.9.0

一、USB 转 I2C/SMBUS 助手的概述

1.1 USB 转 I2C/SMBUS 概述

USB 转 I2C/SMBUS 是一种协议转换硬件工具,核心是把计算机 USB 信号转为 I2C/SMBUS 总线信号,让 PC 直接读写 I2C/SMBUS 设备,无需单片机中转。I2C/SMBUS 属于嵌入式开发领域中很常用的数据传输协议,包括:直接操作EEPROM(AT24CXX)、传感器(SHT30/LM75)、OLED/LCD、读写电源管理芯片(如 TI BQ、英飞凌 IR)、电池管理系统(BMS)、电压 / 电流监控器 IAN226 等。作者制作的 iKun USB IO DEBUG Helper 就是帮助嵌入式工程师日常调试I2C/SMBUS过程中利器。

1.2 USB 转 I2C/SMBUS 项目概述

本项目使用南京沁恒微电子有限公司的 CH32L103C8T6 作为核心 MCU。南京沁恒微电子股份有限公司(WCH,Win Chip Head)是国内专注连接技术与 MCU的集成电路设计企业,以自研 IP + 一体化芯片为核心竞争力。沁恒家的 USB 可以说是国产的 USB 大王,比如 CH340、CH334、CH397 和 CH634 等都是很出名的 USB 接口类型芯片。

故作者选用了这种 USB 技术很成熟的公司产品芯片 CH32L103C8T6 作为开发对象。CH32L103C8T6 拥有 Full Speed 的 USB 接口非常切合本项目的 USB 接口速度需求,且芯片功耗与芯片成本都很低。

本项目利用 CH32L103C8T6 编写代码实现 CDC 设备(串行接口),并初始化 I2C/SMBUS 外设实现基本的数据传输。iKun USB IO DEBUG Helper 项目的流程如下:USB CDC(虚拟串口) ↔ MCU USB 协议栈 ↔ 自定义通信协议 ↔ I2C/SMBus 控制器 ↔ 外部从设备。故项目的本质:把 USB 虚拟串口的 "指令流" 翻译成 I2C/SMBus 的 "硬件时序",实现 PC 通过 COM 口直接控制 I2C 从设备。

二、iKun USB IO DEBUG Helper 的硬件设计

2.1 iKun USB IO DEBUG Helper 的电路设计

沁恒系列的MCU外围电路都是属于比较简单的,作者这边仅搭建项目所需的元器件,如下:

1、MCU部分:使用 CH32L103C8T6 作为核心搭建最小系统核心电路;

2、8MHz晶振:CH32L103C8T6 的 HSE 为 8 MHz;

3、USB_type_A:iKun USB IO DEBUG Helper 的 CDC USB 接口;

4、4.7k 的上拉电阻:i2c 传输中的 SDA 和 SCL 都需要依赖外部上拉;

5、Power 部分:使用 AMS1117 这款 LDO 进行 5v 转 3.3v;

6、LED 与 Download:下载引脚排针与电源指示灯;

2.2 iKun USB IO DEBUG Helper 的 PCB 设计

本项目并不涉及高速信号走线且外围电路很简单,故作者仅使用 2 层板 PCB 进行打样。稍微需要注意项如下:

1、USBFS 走线需要等长操作:

2、AMS1117的电源线稍微加粗:

iKun USB IO DEBUG Helper:

2.3 iKun USB IO DEBUG Helper 的 USBFS 信号测试

由于沁恒官方并未在 CH32L103C8T6 的 USBFS 的官方例程中直接提供 USBFS 的眼图测试功能项,作者这边直接利用正常通信抓 SOF/IN (模拟TEST_PACKET)包测眼图,测试结果如下:

测试报告:

USBFS 信号源:

作为USB通信接口的专家,USBIF 的眼图测试对沁恒 USB 系列的芯片来说都是很轻松的,测试结果的模拟余量还是很大的!

三、iKun USB IO DEBUG Helper 的下位机代码

3.1 USBFS 模拟 CDC 代码

作者推荐的 USB 学习博客:USB 2.0 协议专栏之 USB 2.0 概述(一)_usb协议栈-CSDN博客

本项目的 iKun USB IO DEBUG Helper 利用 USB 协议中的 CDC 设备规范进行 USB 数据传输,这部分代码可以直接使用沁恒微电子的 CDC 官方例程即可。作者这边就是在此例程代码上进行修改的,包含:描述符和 CDC 设备枚举中断:

usb_desc.h:

cpp 复制代码
#include "usb_desc.h"

/* Device Descriptor */
const uint8_t  MyDevDescr[] =
{
    0x12,       // bLength
    0x01,       // bDescriptorType (Device)
    0x10, 0x01, // bcdUSB 1.10
    0x02,       // bDeviceClass
    0x00,       // bDeviceSubClass
    0x00,       // bDeviceProtocol
    DEF_USBD_UEP0_SIZE,   // bMaxPacketSize0 64
    (uint8_t)DEF_USB_VID, (uint8_t)(DEF_USB_VID >> 8),  // idVendor 0x1A86
    (uint8_t)DEF_USB_PID, (uint8_t)(DEF_USB_PID >> 8),  // idProduct 0xFE0C
    DEF_IC_PRG_VER, 0x00, // bcdDevice 0.01
    0x01,       // iManufacturer (String Index)
    0x02,       // iProduct (String Index)
    0x03,       // iSerialNumber (String Index)
    0x01,       // bNumConfigurations 1
};

/* Configuration Descriptor */
const uint8_t  MyCfgDescr[] =
{
    /* Configure descriptor */
    0x09, 0x02, 0x43, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32,

    /* Interface 0 (CDC) descriptor */
    0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01,  0x00,

    /* Functional Descriptors */
    0x05, 0x24, 0x00, 0x10, 0x01,

    /* Length/management descriptor (data class interface 1) */
    0x05, 0x24, 0x01, 0x00, 0x01,
    0x04, 0x24, 0x02, 0x02,
    0x05, 0x24, 0x06, 0x00, 0x01,

    /* Interrupt upload endpoint descriptor */
    0x07, 0x05, 0x81, 0x03, (uint8_t)DEF_USBD_ENDP1_SIZE, (uint8_t)( DEF_USBD_ENDP1_SIZE >> 8 ), 0x01,

    /* Interface 1 (data interface) descriptor */
    0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00,

    /* Endpoint descriptor */
    0x07, 0x05, 0x02, 0x02, (uint8_t)DEF_USBD_ENDP2_SIZE, (uint8_t)( DEF_USBD_ENDP2_SIZE >> 8 ), 0x00,

    /* Endpoint descriptor */
    0x07, 0x05, 0x83, 0x02, (uint8_t)DEF_USBD_ENDP3_SIZE, (uint8_t)( DEF_USBD_ENDP3_SIZE >> 8 ), 0x00,
};

/* Language Descriptor */
const uint8_t  MyLangDescr[] =
{
    0x04, 0x03, 0x09, 0x04
};

/* Manufacturer Descriptor */
const uint8_t  MyManuInfo[] =
{
    0x0E, 0x03, 'w', 0, 'c', 0, 'h', 0, '.', 0, 'c', 0, 'n', 0
};

/* Product Information */
const uint8_t  MyProdInfo[] =
{
    0x26, 0x03,
    'i',0, 'K',0, 'u',0, 'n',0, ' ',0,
    'W',0, 'C',0, 'H',0, ' ',0,
    'A',0, 'g',0, 'r',0, ' ',0,
    'D',0, 'e',0, 'b',0, 'u',0, 'g',0
};
/* Serial Number Information */
const uint8_t  MySerNumInfo[] =
{
    0x26, 0x03,
    'i',0, 'K',0, 'u',0, 'n',0, ' ',0,
    'W',0, 'C',0, 'H',0, ' ',0,
    'A',0, 'g',0, 'r',0, ' ',0,
    'D',0, 'e',0, 'b',0, 'u',0, 'g',0
};

ch32l103_usbfs_device.c:

沁恒系列的 MUC 在进行 USB 设备枚举过程中是利用 void USBFS_IRQHandler 函数进行 Device Descriptors、Configuration Descriptors 和 String Descriptors 的数据交换,作者这边重点讲述一下这部分代码的核心区域:

cpp 复制代码
/********************************** (C) COPYRIGHT *******************************
* File Name          : ch32l103_usbfs_device.c
* Author             : 混分巨兽龙某某
* Version            : V1.0.0
* Date               : 2026/01/19
* Description        : This file provides all the USBOTG firmware functions.
*******************************************************************************/

#include "ch32l103_usbfs_device.h"
#include "i2c_smbus.h"

/*******************************************************************************/
/* Variable Definition */
/* Global */
const    uint8_t  *pUSBFS_Descr;

/* Setup Request */
volatile uint8_t  USBFS_SetupReqCode;
volatile uint8_t  USBFS_SetupReqType;
volatile uint16_t USBFS_SetupReqValue;
volatile uint16_t USBFS_SetupReqIndex;
volatile uint16_t USBFS_SetupReqLen;

/* USB Device Status */
volatile uint8_t  USBFS_DevConfig;
volatile uint8_t  USBFS_DevAddr;
volatile uint8_t  USBFS_DevSleepStatus;
volatile uint8_t  USBFS_DevEnumStatus;

/* Endpoint Buffer */
__attribute__ ((aligned(4))) uint8_t USBFS_EP0_Buf[ DEF_USBD_UEP0_SIZE ];
__attribute__ ((aligned(4))) uint8_t USBFS_EP1_Buf[ DEF_USBD_ENDP1_SIZE ];
__attribute__ ((aligned(4))) uint8_t USBFS_EP2_Buf[ DEF_USBD_ENDP2_SIZE ];
__attribute__ ((aligned(4))) uint8_t USBFS_EP3_Buf[ DEF_USBD_ENDP3_SIZE ];

/* USB IN Endpoint Busy Flag */
volatile uint8_t  USBFS_Endp_Busy[ DEF_UEP_NUM ];

/******************************************************************************/
/* Interrupt Service Routine Declaration*/
void USBFS_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*********************************************************************
 * @fn      USBFS_RCC_Init
 *
 * @brief   Initializes the usbotg clock configuration.
 *
 * @return  none
 */
void USBFS_RCC_Init( void )
{
    if( SystemCoreClock == 96000000 )
    {
        RCC_USBCLKConfig( RCC_USBCLKSource_PLLCLK_Div2 );
    }
    else if( SystemCoreClock == 72000000 )
    {
        RCC_USBCLKConfig( RCC_USBCLKSource_PLLCLK_Div1_5 );
    }
    else if( SystemCoreClock == 48000000 )
    {
        RCC_USBCLKConfig( RCC_USBCLKSource_PLLCLK_Div1 );
    }
    RCC_HBPeriphClockCmd( RCC_HBPeriph_USBFS, ENABLE );
}

/*********************************************************************
 * @fn      USBFS_Device_Endp_Init
 *
 * @brief   Initializes USB device endpoints.
 *
 * @return  none
 */
void USBFS_Device_Endp_Init( void )
{
    uint8_t i;

    USBFSD->UEP4_1_MOD = USBFS_UEP1_TX_EN;
    USBFSD->UEP2_3_MOD = USBFS_UEP2_RX_EN|USBFS_UEP3_TX_EN;

    USBFSD->UEP0_DMA = (uint32_t)USBFS_EP0_Buf;

    USBFSD->UEP1_DMA = (uint32_t)USBFS_EP1_Buf;
    USBFSD->UEP2_DMA = (uint32_t)(uint8_t *)&UART2_Tx_Buf[ 0 ];
    USBFSD->UEP3_DMA = (uint32_t)(uint8_t *)&USBFS_EP3_Buf[ 0 ];

    USBFSD->UEP0_RX_CTRL = USBFS_UEP_R_RES_ACK;
    USBFSD->UEP2_RX_CTRL = USBFS_UEP_R_RES_ACK;

    USBFSD->UEP1_TX_LEN = 0;
    USBFSD->UEP3_TX_LEN = 0;

    USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_RES_NAK;
    USBFSD->UEP1_TX_CTRL = USBFS_UEP_T_RES_NAK;
    USBFSD->UEP3_TX_CTRL = USBFS_UEP_T_RES_NAK;

    /* Clear End-points Busy Status */
    for( i=0; i<DEF_UEP_NUM; i++ )
    {
        USBFS_Endp_Busy[ i ] = 0;
    }
}

/*********************************************************************
 * @fn      USBFS_Device_Init
 *
 * @brief   Initializes USB device.
 *
 * @return  none
 */
void USBFS_Device_Init( FunctionalState sta )
{
    if( sta )
    {
        USBFSH->BASE_CTRL = USBFS_UC_RESET_SIE | USBFS_UC_CLR_ALL;
        Delay_Us( 10 );
        USBFSH->BASE_CTRL = 0x00;
        USBFSD->INT_EN = USBFS_UIE_SUSPEND | USBFS_UIE_BUS_RST | USBFS_UIE_TRANSFER;
        USBFSD->BASE_CTRL = USBFS_UC_DEV_PU_EN | USBFS_UC_INT_BUSY | USBFS_UC_DMA_EN;
        USBFS_Device_Endp_Init( );
        USBFSD->UDEV_CTRL = USBFS_UD_PD_DIS | USBFS_UD_PORT_EN;
        NVIC_EnableIRQ( USBFS_IRQn );
    }
    else
    {
        USBFSH->BASE_CTRL = USBFS_UC_RESET_SIE | USBFS_UC_CLR_ALL;
        Delay_Us( 10 );
        USBFSD->BASE_CTRL = 0x00;
        NVIC_DisableIRQ( USBFS_IRQn );
    }
}

/*********************************************************************
 * @fn      USBFS_Endp_DataUp
 *
 * @brief   USBFS device data upload
 *
 * @return  none
 */
uint8_t USBFS_Endp_DataUp(uint8_t endp, uint8_t *pbuf, uint16_t len, uint8_t mod)
{
    uint8_t endp_mode;
    uint8_t buf_load_offset;

    /* DMA config, endp_ctrl config, endp_len config */
    if( (endp>=DEF_UEP1) && (endp<=DEF_UEP7) )
    {
        if( USBFS_Endp_Busy[ endp ] == 0 )
        {
            if( (endp == DEF_UEP1) || (endp == DEF_UEP4) )
            {
                /* endp1/endp4 */
                endp_mode = USBFSD_UEP_MOD(0);
                if( endp == DEF_UEP1 )
                {
                    endp_mode = (uint8_t)(endp_mode>>4);
                }
            }
            else if( (endp == DEF_UEP2) || (endp == DEF_UEP3) )
            {
                /* endp2/endp3 */
                endp_mode = USBFSD_UEP_MOD(1);
                if( endp == DEF_UEP3 )
                {
                    endp_mode = (uint8_t)(endp_mode>>4);
                }
            }
            else if( (endp == DEF_UEP5) || (endp == DEF_UEP6) )
            {
                /* endp5/endp6 */
                endp_mode = USBFSD_UEP_MOD(2);
                if( endp == DEF_UEP6 )
                {
                    endp_mode = (uint8_t)(endp_mode>>4);
                }
            }
            else
            {
                /* endp7 */
                endp_mode = USBFSD_UEP_MOD(3);
            }

            if( endp_mode & USBFSD_UEP_TX_EN )
            {
                if( endp_mode & USBFSD_UEP_RX_EN )
                {
                    buf_load_offset = 64;
                }
                else
                {
                    buf_load_offset = 0;
                }

                if( buf_load_offset == 0 )
                {
                    if( mod == DEF_UEP_DMA_LOAD )
                    {
                        /* DMA mode */
                        USBFSD_UEP_DMA(endp) = (uint16_t)(uint32_t)pbuf;
                    }
                    else
                    {
                        /* copy mode */
                        memcpy( USBFSD_UEP_BUF(endp), pbuf, len );
                    }
                }
                else
                {
                    memcpy( USBFSD_UEP_BUF(endp)+buf_load_offset, pbuf, len );
                }
                /* Set end-point busy */
                USBFS_Endp_Busy[ endp ] = 0x01;
                /* tx length */
                USBFSD_UEP_TLEN(endp) = len;
                /* response ack */
                USBFSD_UEP_CTRL(endp) = (USBFSD_UEP_CTRL(endp) & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_ACK;
            }
        }
        else
        {
            return 1;
        }
    }
    else
    {
        return 1;
    }
    return 0;
}


/*********************************************************************
 * @fn      USBFS_IRQHandler
 *
 * @brief   This function handles HD-FS exception.
 *
 * @return  none
 */
void USBFS_IRQHandler( void )
{
    uint8_t  intflag, intst, errflag;
    uint16_t len;

    intflag = USBFSD->INT_FG;
    intst   = USBFSD->INT_ST;

    if( intflag & USBFS_UIF_TRANSFER )
    {
        switch (intst & USBFS_UIS_TOKEN_MASK)
        {
            /* data-in stage processing */
            case USBFS_UIS_TOKEN_IN:
                switch ( intst & ( USBFS_UIS_TOKEN_MASK | USBFS_UIS_ENDP_MASK ) )
                {
                    /* end-point 0 data in interrupt */
                    case USBFS_UIS_TOKEN_IN | DEF_UEP0:
                        if( USBFS_SetupReqLen == 0 )
                        {
                            USBFSD->UEP0_RX_CTRL = USBFS_UEP_R_TOG | USBFS_UEP_R_RES_ACK;
                        }
                        if ( ( USBFS_SetupReqType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )
                        {
                            /* Non-standard request endpoint 0 Data upload */
                        }
                        else
                        {
                            /* Standard request endpoint 0 Data upload */
                            switch( USBFS_SetupReqCode )
                            {
                                case USB_GET_DESCRIPTOR:
                                        len = USBFS_SetupReqLen >= DEF_USBD_UEP0_SIZE ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;
                                        memcpy( USBFS_EP0_Buf, pUSBFS_Descr, len );
                                        USBFS_SetupReqLen -= len;
                                        pUSBFS_Descr += len;
                                        USBFSD->UEP0_TX_LEN   = len;
                                        USBFSD->UEP0_TX_CTRL ^= USBFS_UEP_T_TOG;
                                        break;

                                case USB_SET_ADDRESS:
                                        USBFSD->DEV_ADDR = (USBFSD->DEV_ADDR & USBFS_UDA_GP_BIT) | USBFS_DevAddr;
                                        break;

                                default:
                                        break;
                            }
                        }
                        break;

                    /* end-point 1 data in interrupt */
                    case ( USBFS_UIS_TOKEN_IN | DEF_UEP1 ):
                        USBFSD->UEP1_TX_CTRL ^= USBFS_UEP_T_TOG;
                        USBFSD->UEP1_TX_CTRL = (USBFSD->UEP1_TX_CTRL & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_NAK;
                        USBFS_Endp_Busy[ DEF_UEP1 ] = 0;
                        break;

                    /* end-point 3 data in interrupt */
                    case ( USBFS_UIS_TOKEN_IN | DEF_UEP3 ):
                        USBFSD->UEP3_TX_CTRL ^= USBFS_UEP_T_TOG;
                        USBFSD->UEP3_TX_CTRL = (USBFSD->UEP3_TX_CTRL & ~USBFS_UEP_T_RES_MASK) | USBFS_UEP_T_RES_NAK;
                        USBFS_Endp_Busy[ DEF_UEP3 ] = 0;
                        // Uart.USB_Up_IngFlag = 0x00;
                        break;

                    default :
                        break;
                }
                break;

            /* data-out stage processing */
            case USBFS_UIS_TOKEN_OUT:
                switch ( intst & ( USBFS_UIS_TOKEN_MASK | USBFS_UIS_ENDP_MASK ) )
                {
                    /* end-point 0 data out interrupt */
                    case USBFS_UIS_TOKEN_OUT | DEF_UEP0:
                        len = USBFSD->RX_LEN;
                        if ( intst & USBFS_UIS_TOG_OK )
                        {
                            if ( ( USBFS_SetupReqType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )
                            {
                                /* Non-standard request end-point 0 Data download */
                                USBFS_SetupReqLen = 0;
                                /* Non-standard request end-point 0 Data download */
                                if( USBFS_SetupReqCode == CDC_SET_LINE_CODING )
                                {
                                      /* Save relevant parameters such as serial port baud rate */
                                      /* The downlinked data is processed in the endpoint 0 OUT packet, the 7 bytes of the downlink are, in order
                                         4 bytes: baud rate value: lowest baud rate byte, next lowest baud rate byte, next highest baud rate byte, highest baud rate byte.
                                         1 byte: number of stop bits (0: 1 stop bit; 1: 1.5 stop bit; 2: 2 stop bits).
                                         1 byte: number of parity bits (0: None; 1: Odd; 2: Even; 3: Mark; 4: Space).
                                         1 byte: number of data bits (5,6,7,8,16); */
                                      Uart.Com_Cfg[ 0 ] = USBFS_EP0_Buf[ 0 ];
                                      Uart.Com_Cfg[ 1 ] = USBFS_EP0_Buf[ 1 ];
                                      Uart.Com_Cfg[ 2 ] = USBFS_EP0_Buf[ 2 ];
                                      Uart.Com_Cfg[ 3 ] = USBFS_EP0_Buf[ 3 ];
                                      Uart.Com_Cfg[ 4 ] = USBFS_EP0_Buf[ 4 ];
                                      Uart.Com_Cfg[ 5 ] = USBFS_EP0_Buf[ 5 ];
                                      Uart.Com_Cfg[ 6 ] = USBFS_EP0_Buf[ 6 ];
                                      Uart.Com_Cfg[ 7 ] = DEF_UARTx_RX_TIMEOUT;
                                      Uart.Com_Cfg[ 7 ] = Uart.Rx_TimeOutMax;

                                      UART2_USB_Init( );
                                 }
                            }
                            else
                            {
                                /* Standard request end-point 0 Data download */
                                /* Add your code here */
                            }
                            if( USBFS_SetupReqLen == 0 )
                            {
                                USBFSD->UEP0_TX_LEN  = 0;
                                USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_TOG | USBFS_UEP_T_RES_ACK;
                            }
                        }
                        break;

                    /* end-point 2 data out interrupt */
                    case USBFS_UIS_TOKEN_OUT | DEF_UEP2:
                        USBFSD->UEP2_RX_CTRL ^= USBFS_UEP_R_TOG;

                        // 1. 保存当前包长度
                        uint8_t curr_pack_len = USBFSD->RX_LEN;
                        uint8_t curr_buf_idx = Uart.Tx_LoadNum;

                        // 2. 保存数据
                        Uart.Tx_PackLen[curr_buf_idx] = curr_pack_len;

                        // 3. 缓冲区索引自增(环形)
                        uint8_t next_buf_idx = (Uart.Tx_LoadNum + 1) % DEF_UARTx_TX_BUF_NUM_MAX;

                        // 4. 设置下一次DMA地址(正确顺序!)
                        USBFSD->UEP2_DMA = (uint32_t)(uint8_t *)&UART2_Tx_Buf[next_buf_idx * DEF_USB_FS_PACK_LEN];

                        // 5. 更新LoadNum
                        Uart.Tx_LoadNum = next_buf_idx;

                        // 6. 统计待处理数量
                        if(Uart.Tx_RemainNum < DEF_UARTx_TX_BUF_NUM_MAX)
                            Uart.Tx_RemainNum++;

                        // 7. 流控:缓冲区满则NAK
                        if(Uart.Tx_RemainNum >= (DEF_UARTx_TX_BUF_NUM_MAX - 2))
                        {
                            USBFSD->UEP2_RX_CTRL &= ~USBFS_UEP_R_RES_MASK;
                            USBFSD->UEP2_RX_CTRL |= USBFS_UEP_R_RES_NAK;
                            Uart.USB_Down_StopFlag = 1;
                        }

                        // ==============================
                        // 关键:中断里只标记"有数据"
                        // 绝对不在中断里处理I2C/SMBus!
                        // ==============================
                        Uart.Cmd_Pending_Flag = 1;
                        break;

                    default:
                        break;
                }
                break;

            /* Setup stage processing */
            case USBFS_UIS_TOKEN_SETUP:
                USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_TOG|USBFS_UEP_T_RES_NAK;
                USBFSD->UEP0_RX_CTRL = USBFS_UEP_R_TOG|USBFS_UEP_R_RES_NAK;
                /* Store All Setup Values */
                USBFS_SetupReqType  = pUSBFS_SetupReqPak->bRequestType;
                USBFS_SetupReqCode  = pUSBFS_SetupReqPak->bRequest;
                USBFS_SetupReqLen   = pUSBFS_SetupReqPak->wLength;
                USBFS_SetupReqValue = pUSBFS_SetupReqPak->wValue;
                USBFS_SetupReqIndex = pUSBFS_SetupReqPak->wIndex;
                len = 0;
                errflag = 0;
                if ( ( USBFS_SetupReqType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )
                {
                    /* usb non-standard request processing */
                    if( USBFS_SetupReqType & USB_REQ_TYP_CLASS )
                    {
                        /* Class requests */
                        switch( USBFS_SetupReqCode )
                        {
                            case CDC_GET_LINE_CODING:
                                pUSBFS_Descr = (uint8_t *)&Uart.Com_Cfg[ 0 ];
                                len = 7;
                                break;

                            case CDC_SET_LINE_CODING:
                                break;

                            case CDC_SET_LINE_CTLSTE:
                                break;

                            case CDC_SEND_BREAK:
                                break;

                            default:
                                errflag = 0xff;
                                break;
                        }
                    }
                    else if( USBFS_SetupReqType & USB_REQ_TYP_VENDOR )
                    {
                        /* Manufacturer request */
                    }
                    else
                    {
                        errflag = 0xFF;
                    }

                    /* Copy Descriptors to Endp0 DMA buffer */
                    len = (USBFS_SetupReqLen >= DEF_USBD_UEP0_SIZE) ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;
                    memcpy( USBFS_EP0_Buf, pUSBFS_Descr, len );
                    pUSBFS_Descr += len;
                }
                else
                {
                    /* usb standard request processing */
                    switch( USBFS_SetupReqCode )
                    {
                        /* get device/configuration/string/report/... descriptors */
                        case USB_GET_DESCRIPTOR:
                            switch( (uint8_t)( USBFS_SetupReqValue >> 8 ) )
                            {
                                /* get usb device descriptor */
                                case USB_DESCR_TYP_DEVICE:
                                    pUSBFS_Descr = MyDevDescr;
                                    len = DEF_USBD_DEVICE_DESC_LEN;
                                    break;

                                /* get usb configuration descriptor */
                                case USB_DESCR_TYP_CONFIG:
                                    pUSBFS_Descr = MyCfgDescr;
                                    len = DEF_USBD_CONFIG_DESC_LEN;
                                    break;

                                /* get usb string descriptor */
                                case USB_DESCR_TYP_STRING:
                                    switch( (uint8_t)( USBFS_SetupReqValue & 0xFF ) )
                                    {
                                        /* Descriptor 0, Language descriptor */
                                        case DEF_STRING_DESC_LANG:
                                            pUSBFS_Descr = MyLangDescr;
                                            len = DEF_USBD_LANG_DESC_LEN;
                                            break;

                                        /* Descriptor 1, Manufacturers String descriptor */
                                        case DEF_STRING_DESC_MANU:
                                            pUSBFS_Descr = MyManuInfo;
                                            len = DEF_USBD_MANU_DESC_LEN;
                                            break;

                                        /* Descriptor 2, Product String descriptor */
                                        case DEF_STRING_DESC_PROD:
                                            pUSBFS_Descr = MyProdInfo;
                                            len = DEF_USBD_PROD_DESC_LEN;
                                            break;

                                        /* Descriptor 3, Serial-number String descriptor */
                                        case DEF_STRING_DESC_SERN:
                                            pUSBFS_Descr = MySerNumInfo;
                                            len = DEF_USBD_SN_DESC_LEN;
                                            break;

                                        default:
                                            errflag = 0xFF;
                                            break;
                                    }
                                    break;

                                default :
                                    errflag = 0xFF;
                                    break;
                            }

                            /* Copy Descriptors to Endp0 DMA buffer */
                            if( USBFS_SetupReqLen>len )
                            {
                                USBFS_SetupReqLen = len;
                            }
                            len = (USBFS_SetupReqLen >= DEF_USBD_UEP0_SIZE) ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;
                            memcpy( USBFS_EP0_Buf, pUSBFS_Descr, len );
                            pUSBFS_Descr += len;
                            break;

                        /* Set usb address */
                        case USB_SET_ADDRESS:
                            USBFS_DevAddr = (uint8_t)( USBFS_SetupReqValue & 0xFF );
                            break;

                        /* Get usb configuration now set */
                        case USB_GET_CONFIGURATION:
                            USBFS_EP0_Buf[0] = USBFS_DevConfig;
                            if ( USBFS_SetupReqLen > 1 )
                            {
                                USBFS_SetupReqLen = 1;
                            }
                            break;

                        /* Set usb configuration to use */
                        case USB_SET_CONFIGURATION:
                            USBFS_DevConfig = (uint8_t)( USBFS_SetupReqValue & 0xFF );
                            USBFS_DevEnumStatus = 0x01;
                            break;

                        /* Clear or disable one usb feature */
                        case USB_CLEAR_FEATURE:
                            if ( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE )
                            {
                                /* clear one device feature */
                                if( (uint8_t)( USBFS_SetupReqValue & 0xFF ) == USB_REQ_FEAT_REMOTE_WAKEUP )
                                {
                                    /* clear usb sleep status, device not prepare to sleep */
                                    USBFS_DevSleepStatus &= ~0x01;
                                }
                            }
                            else if( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )
                            {
                                /* Clear End-point Feature */
                                if( (uint8_t)( USBFS_SetupReqValue & 0xFF ) == USB_REQ_FEAT_ENDP_HALT )
                                {
                                    switch( (uint8_t)( USBFS_SetupReqIndex & 0xFF ) )
                                    {
                                        case ( DEF_UEP_IN | DEF_UEP1 ):
                                            /* Set End-point 1 IN NAK */
                                            USBFSD->UEP1_TX_CTRL = USBFS_UEP_T_RES_NAK;
                                            break;

                                        case ( DEF_UEP_OUT | DEF_UEP2 ):
                                            /* Set End-point 2 OUT ACK */
                                            USBFSD->UEP2_RX_CTRL = USBFS_UEP_R_RES_ACK;
                                            break;

                                        case ( DEF_UEP_IN | DEF_UEP3 ):
                                            /* Set End-point 3 IN NAK */
                                            USBFSD->UEP3_TX_CTRL = USBFS_UEP_T_RES_NAK;
                                            break;

                                        default:
                                            errflag = 0xFF;
                                            break;
                                    }
                                }
                                else
                                {
                                    errflag = 0xFF;
                                }
                            }
                            else
                            {
                                errflag = 0xFF;
                            }
                            break;

                        /* set or enable one usb feature */
                        case USB_SET_FEATURE:
                            if( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE )
                            {
                                /* Set Device Feature */
                                if( (uint8_t)( USBFS_SetupReqValue & 0xFF ) == USB_REQ_FEAT_REMOTE_WAKEUP )
                                {
                                    if( MyCfgDescr[ 7 ] & 0x20 )
                                    {
                                        /* Set Wake-up flag, device prepare to sleep */
                                        USBFS_DevSleepStatus |= 0x01;
                                    }
                                    else
                                    {
                                        errflag = 0xFF;
                                    }
                                }
                                else
                                {
                                    errflag = 0xFF;
                                }
                            }
                            else if( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )
                            {
                                /* Set End-point Feature */
                                if( (uint8_t)( USBFS_SetupReqValue & 0xFF ) == USB_REQ_FEAT_ENDP_HALT )
                                {
                                    /* Set end-points status stall */
                                    switch( (uint8_t)( USBFS_SetupReqIndex & 0xFF ) )
                                    {
                                        case ( DEF_UEP_IN | DEF_UEP1 ):
                                            /* Set End-point 1 IN STALL */
                                            USBFSD->UEP1_TX_CTRL = ( USBFSD->UEP1_TX_CTRL & ~USBFS_UEP_T_RES_MASK ) | USBFS_UEP_T_RES_STALL;
                                            break;

                                        case ( DEF_UEP_OUT | DEF_UEP2 ):
                                            /* Set End-point 2 OUT STALL */
                                            USBFSD->UEP2_RX_CTRL = ( USBFSD->UEP2_RX_CTRL & ~USBFS_UEP_R_RES_MASK ) | USBFS_UEP_R_RES_STALL;
                                            break;

                                        case ( DEF_UEP_IN | DEF_UEP3 ):
                                            /* Set End-point 3 IN STALL */
                                            USBFSD->UEP3_TX_CTRL = ( USBFSD->UEP3_TX_CTRL & ~USBFS_UEP_T_RES_MASK ) | USBFS_UEP_T_RES_STALL;
                                            break;

                                        default:
                                            errflag = 0xFF;
                                            break;
                                    }
                                }
                                else
                                {
                                    errflag = 0xFF;
                                }
                            }
                            else
                            {
                                errflag = 0xFF;
                            }
                            break;

                        /* This request allows the host to select another setting for the specified interface  */
                        case USB_GET_INTERFACE:
                            USBFS_EP0_Buf[0] = 0x00;
                            if ( USBFS_SetupReqLen > 1 )
                            {
                                USBFS_SetupReqLen = 1;
                            }
                            break;

                        case USB_SET_INTERFACE:
                            break;

                        /* host get status of specified device/interface/end-points */
                        case USB_GET_STATUS:
                            USBFS_EP0_Buf[ 0 ] = 0x00;
                            USBFS_EP0_Buf[ 1 ] = 0x00;
                            if ( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_DEVICE )
                            {
                                if( USBFS_DevSleepStatus & 0x01 )
                                {
                                    USBFS_EP0_Buf[ 0 ] = 0x02;
                                }
                            }
                            else if( ( USBFS_SetupReqType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )
                            {
                                switch( (uint8_t)( USBFS_SetupReqIndex & 0xFF ) )
                                {
                                    case ( DEF_UEP_IN | DEF_UEP1 ):
                                        if( ( (USBFSD->UEP1_TX_CTRL) & USBFS_UEP_T_RES_MASK ) == USBFS_UEP_T_RES_STALL )
                                        {
                                            USBFS_EP0_Buf[ 0 ] = 0x01;
                                        }
                                        break;

                                    case ( DEF_UEP_OUT | DEF_UEP2 ):
                                        if( ( (USBFSD->UEP2_RX_CTRL) & USBFS_UEP_R_RES_MASK ) == USBFS_UEP_R_RES_STALL )
                                        {
                                            USBFS_EP0_Buf[ 0 ] = 0x01;
                                        }
                                        break;

                                    case ( DEF_UEP_IN | DEF_UEP3 ):
                                        if( ( (USBFSD->UEP3_TX_CTRL) & USBFS_UEP_T_RES_MASK ) == USBFS_UEP_T_RES_STALL )
                                        {
                                            USBFS_EP0_Buf[ 0 ] = 0x01;
                                        }
                                        break;

                                    default:
                                        errflag = 0xFF;
                                        break;
                                }
                            }
                            else
                            {
                                errflag = 0xFF;
                            }

                            if( USBFS_SetupReqLen > 2 )
                            {
                                USBFS_SetupReqLen = 2;
                            }

                            break;

                        default:
                            errflag = 0xFF;
                            break;
                    }
                }
                /* errflag = 0xFF means a request not support or some errors occurred, else correct */
                if( errflag == 0xff)
                {
                    /* if one request not support, return stall */
                    USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_TOG|USBFS_UEP_T_RES_STALL;
                    USBFSD->UEP0_RX_CTRL = USBFS_UEP_R_TOG|USBFS_UEP_R_RES_STALL;
                }
                else
                {
                    /* end-point 0 data Tx/Rx */
                    if( USBFS_SetupReqType & DEF_UEP_IN )
                    {
                        /* tx */
                        len = (USBFS_SetupReqLen>DEF_USBD_UEP0_SIZE) ? DEF_USBD_UEP0_SIZE : USBFS_SetupReqLen;
                        USBFS_SetupReqLen -= len;
                        USBFSD->UEP0_TX_LEN  = len;
                        USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_TOG|USBFS_UEP_T_RES_ACK;
                    }
                    else
                    {
                        /* rx */
                        if( USBFS_SetupReqLen == 0 )
                        {
                            USBFSD->UEP0_TX_LEN  = 0;
                            USBFSD->UEP0_TX_CTRL = USBFS_UEP_T_TOG|USBFS_UEP_T_RES_ACK;
                        }
                        else
                        {
                            USBFSD->UEP0_RX_CTRL = USBFS_UEP_R_TOG|USBFS_UEP_R_RES_ACK;
                        }
                    }
                }
                break;

            default :
                break;
        }
        USBFSD->INT_FG = USBFS_UIF_TRANSFER;
    }
    else if( intflag & USBFS_UIF_BUS_RST )
    {
        /* usb reset interrupt processing */
        USBFSD->DEV_ADDR = 0;
        USBFS_Device_Endp_Init( );
        UART2_ParaInit( 1 );
        USBFSD->INT_FG = USBFS_UIF_BUS_RST;
    }
    else if( intflag & USBFS_UIF_SUSPEND )
    {
        /* usb suspend interrupt processing */
        if ( USBFSD->MIS_ST & USBFS_UMS_SUSPEND )
        {
            USBFS_DevSleepStatus |= 0x02;
            if( USBFS_DevSleepStatus == 0x03 )
            {
                /* Handling usb sleep here */
            }
        }
        else
        {
            USBFS_DevSleepStatus &= ~0x02;
        }
        USBFSD->INT_FG = USBFS_UIF_SUSPEND;
    }
    else
    {
        /* other interrupts */
        USBFSD->INT_FG = intflag;
    }
}

/*********************************************************************
 * @fn      USBFS_Send_Resume
 *
 * @brief   USBFS device sends wake-up signal to host
 *
 * @return  none
 */
void USBFS_Send_Resume( void )
{
    USBFSD->UDEV_CTRL ^= USBFS_UD_LOW_SPEED;
    Delay_Ms( 8 );
    USBFSD->UDEV_CTRL ^= USBFS_UD_LOW_SPEED;
    Delay_Ms( 1 );
}

3.2 I2C/SMBUS 代码

这段代码是基于 CH32L103 的 I2C/SMBus 主机驱动程序,专门实现 MCU 与 I2C 从机设备(传感器、EEPROM 等)的标准通信,是嵌入式中最常用的 I2C 底层驱动。根据 I2C/SMBUS 在实际过程中的应用,作者在代码中添加了超时处理机制,提升了代码整体的鲁棒性!

cpp 复制代码
/********************************** (C) COPYRIGHT *******************************
* File Name          : i2c_smbus.c
* Author             : 混分巨兽龙某某
* Version            : V1.0.0
* Date               : 2026/04/16
* Description        : I2C/SMBus operation functions
*******************************************************************************/

#include "i2c_smbus.h"

// 状态返回宏定义
#define I2C_OK              0   // I2C操作成功
#define I2C_ERROR           1   // I2C操作失败

//=================== 超时等待函数 ===================
/**
 * @brief  I2C标志位超时等待函数
 * @param  event: 待检测的I2C事件标志
 * @retval 0:成功 1:超时失败
 * @note   超时后强制发送停止信号释放总线
 */
static uint8_t I2C_WaitFlag(uint32_t event)
{
    uint32_t timeout = I2C_TIMEOUT_MAX;  // 初始化超时计数器
    // 循环等待事件标志置位或超时
    while (!I2C_CheckEvent(I2C1, event) && timeout--)
    {
        if (timeout == 0)  // 超时判断
        {
            I2C_GenerateSTOP(I2C1, ENABLE);  // 超时强制发送停止信号释放总线
            return I2C_ERROR;                // 返回超时错误
        }
    }
    return I2C_OK;  // 事件标志置位,返回成功
}

/**
 * @brief  I2C主机初始化函数
 * @param  无
 * @retval 无
 * @note   初始化I2C1引脚、时钟、通信参数并使能I2C外设
 */
void I2C_SMBus_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;   // GPIO初始化结构体
    I2C_InitTypeDef I2C_InitStructure;     // I2C初始化结构体

    /* 使能GPIO端口和I2C1外设时钟 */
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOB, ENABLE);
    RCC_PB1PeriphClockCmd(RCC_PB1Periph_I2C1, ENABLE);

    /* 配置I2C1引脚:SCL和SDA */
    GPIO_InitStructure.GPIO_Pin = I2C1_SCL_PIN | I2C1_SDA_PIN;  // SCL和SDA引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;            // 复用开漏输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;          // GPIO速度50MHz
    GPIO_Init(I2C1_GPIO_PORT, &GPIO_InitStructure);             // 初始化GPIO端口

    /* I2C1参数配置 */
    I2C_InitStructure.I2C_ClockSpeed = I2C_CLOCK_SPEED;        // I2C时钟速度
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;                 // I2C模式
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;        // 占空比2:1
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;                  // 本机地址
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;                // 使能应答
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;  // 7位地址模式
    I2C_Init(I2C1, &I2C_InitStructure);                        // 初始化I2C外设

    /* 使能I2C1外设 */
    I2C_Cmd(I2C1, ENABLE);
}

/**
 * @brief  I2C主机写数据函数
 * @param  addr: 从机设备地址(7位地址)
 * @param  reg:  从机寄存器地址
 * @param  data: 待写入数据缓冲区指针
 * @param  len:  待写入数据长度
 * @retval 0:成功 1:失败
 * @note   标准I2C写时序:起始+地址(写)+寄存器地址+连续数据+停止
 */
uint8_t I2C_Write(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
    // 1. 发送I2C起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT)) return I2C_ERROR;  // 等待主机模式选中

    // 2. 发送从机地址+写方向位
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) return I2C_ERROR;  // 等待发送模式选中

    // 3. 发送目标寄存器地址
    I2C_SendData(I2C1, reg);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return I2C_ERROR;  // 等待字节发送完成

    // 4. 连续写入指定长度的数据
    for (uint8_t i = 0; i < len; i++)
    {
        I2C_SendData(I2C1, data[i]);  // 发送单字节数据
        if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return I2C_ERROR;  // 等待数据发送完成
    }

    // 5. 发送I2C停止信号
    I2C_GenerateSTOP(I2C1, ENABLE);

    return I2C_OK;  // 写入操作完成
}

/**
 * @brief  I2C主机读数据函数
 * @param  addr: 从机设备地址(7位地址)
 * @param  reg:  从机寄存器地址
 * @param  data: 读取数据存储缓冲区指针
 * @param  len:  待读取数据长度
 * @retval 0:成功 1:失败
 * @note   标准I2C读时序:起始+地址(写)+寄存器地址+重复起始+地址(读)+连续读数据+停止
 */
uint8_t I2C_Read(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
    // 1. 发送第一次起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT)) return I2C_ERROR;

    // 2. 发送从机地址+写方向,用于指定寄存器地址
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) return I2C_ERROR;

    // 3. 发送要读取的寄存器地址
    I2C_SendData(I2C1, reg);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_TRANSMITTED)) return I2C_ERROR;

    // 4. 发送重复起始信号,切换为读操作
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT)) return I2C_ERROR;

    // 5. 发送从机地址+读方向位
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) return I2C_ERROR;

    // 6. 循环读取指定长度的数据
    for (uint8_t i = 0; i < len; i++)
    {
        // 最后一个字节发送非应答信号(NACK),其余字节发送应答信号(ACK)
        if (i == len - 1)
            I2C_AcknowledgeConfig(I2C1, DISABLE);  // 最后一字节禁止ACK
        else
            I2C_AcknowledgeConfig(I2C1, ENABLE);   // 非最后一字节使能ACK

        // 等待数据接收完成
        if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_RECEIVED))
        {
            I2C_AcknowledgeConfig(I2C1, ENABLE);  // 恢复ACK配置
            return I2C_ERROR;
        }
        data[i] = I2C_ReceiveData(I2C1);  // 读取接收到的数据
    }

    // 7. 发送停止信号并恢复应答配置
    I2C_GenerateSTOP(I2C1, ENABLE);
    I2C_AcknowledgeConfig(I2C1, ENABLE);  // 读取完成后恢复使能ACK

    return I2C_OK;  // 读取操作完成
}

/**
 * @brief  I2C先写后读复合操作函数
 * @param  addr:  从机设备地址(7位地址)
 * @param  reg:   从机寄存器地址
 * @param  wlen:  写入数据长度
 * @param  wdata: 待写入数据缓冲区指针
 * @param  rlen:  读取数据长度
 * @param  rdata: 读取数据存储缓冲区指针
 * @retval I2C_STATUS_SUCCESS:成功 I2C_STATUS_ERROR:失败
 * @note   封装I2C_Write和I2C_Read函数,实现先写后读操作
 */
uint8_t I2C_WriteRead(uint8_t addr, uint8_t reg, uint8_t wlen, uint8_t *wdata, uint8_t rlen, uint8_t *rdata)
{
    /* 执行I2C写入操作 */
    if(I2C_Write(addr, reg, wdata, wlen) != I2C_STATUS_SUCCESS)
    {
        return I2C_STATUS_ERROR;  // 写入失败返回错误
    }

    /* 执行I2C读取操作 */
    if(I2C_Read(addr, reg, rdata, rlen) != I2C_STATUS_SUCCESS)
    {
        return I2C_STATUS_ERROR;  // 读取失败返回错误
    }

    return I2C_STATUS_SUCCESS;  // 先写后读操作完成
}

/**
 * @brief  SMBus写数据函数
 * @param  addr: 从机设备地址(7位地址)
 * @param  reg:  从机寄存器地址
 * @param  data: 待写入数据缓冲区指针
 * @param  len:  待写入数据长度
 * @retval 0:成功 1:失败
 * @note   目前与标准I2C写函数实现一致
 */
uint8_t Special_I2C_Write(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
    /* SMBus写操作当前与标准I2C写操作实现相同 */
    return I2C_Write(addr, reg, data, len);
}

/**
 * @brief  SMBus读数据函数
 * @param  addr: 从机设备地址(7位地址)
 * @param  reg:  从机寄存器地址
 * @param  data: 读取数据存储缓冲区指针
 * @param  len:  待读取数据长度
 * @retval 0:成功 1:失败
 * @note   目前与标准I2C读函数实现一致
 */
uint8_t Special_I2C_Read(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
    /* SMBus读操作当前与标准I2C读操作实现相同 */
    return I2C_Read(addr, reg, data, len);
}

/**
 * @brief  检测I2C从机设备是否存在
 * @param  addr: 待检测的从机设备地址(7位地址)
 * @retval 1:设备存在 0:设备不存在
 * @note   通过发送设备地址并检测应答信号判断设备是否在线
 */
uint8_t I2C_CheckDevice(uint8_t addr)
{
    // 1. 产生I2C起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT)) {
        return 0; // 起始信号发送失败,返回设备不存在
    }

    // 2. 发送7位从机地址+写方向位
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    
    // 3. 等待设备应答信号(核心检测步骤)
    // 返回0表示设备应答成功,返回1表示无应答
    if (I2C_WaitFlag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
        I2C_GenerateSTOP(I2C1, ENABLE); // 无应答,发送停止信号释放总线
        return 0;                      // 无应答,设备不存在
    }

    // 4. 设备应答成功,发送停止信号
    I2C_GenerateSTOP(I2C1, ENABLE);

    return 1;  // 设备存在
}

// 纯I2C读:无寄存器,直接读 len 个字节
uint8_t I2C_Read_NoReg(uint8_t addr, uint8_t *data, uint8_t len)
{
    // 1. 起始
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT)) return I2C_ERROR;

    // 2. 直接发【读地址】,不发写+寄存器
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) return I2C_ERROR;

    // 3. 读数据
    for (uint8_t i = 0; i < len; i++)
    {
        if (i == len - 1)
            I2C_AcknowledgeConfig(I2C1, DISABLE);
        else
            I2C_AcknowledgeConfig(I2C1, ENABLE);

        if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_RECEIVED))
        {
            I2C_AcknowledgeConfig(I2C1, ENABLE);
            return I2C_ERROR;
        }
        data[i] = I2C_ReceiveData(I2C1);
    }

    // 4. 停止
    I2C_GenerateSTOP(I2C1, ENABLE);
    I2C_AcknowledgeConfig(I2C1, ENABLE);

    return I2C_OK;
}

// 纯I2C写:无寄存器,直接写 len 个字节
uint8_t I2C_Write_NoReg(uint8_t addr, uint8_t *data, uint8_t len)
{
    // 1. 产生起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_MODE_SELECT))
        return I2C_ERROR;

    // 2. 发送设备地址 + 写方向(0 = 写)
    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    if (I2C_WaitFlag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
        return I2C_ERROR;

    // 3. 连续写入数据
    for (uint8_t i = 0; i < len; i++)
    {
        // 发送一个字节
        I2C_SendData(I2C1, data[i]);
        
        // 等待发送完成
        if (I2C_WaitFlag(I2C_EVENT_MASTER_BYTE_TRANSMITTED))
            return I2C_ERROR;
    }

    // 4. 产生停止信号
    I2C_GenerateSTOP(I2C1, ENABLE);

    return I2C_OK;
}

3.3 核心逻辑代码

作者本人的代码在 USB 数据传输过程中仅标记 "有数据" 和数据的接收,而在 main 函数的 while(1) 循环处理。此段代码的核心为:1、循环检查有没有收到上位机指令(中断中置位);2、收到 → 关中断 → 安全取出指令数据 → 开中断;3、解析指令(读、写、扫描等);4、调用 I2C 驱动执行操作;5、如需返回数据(如读 / 扫描),通过 USB 上传给上位机;6、处理完成,等待下一条指令;

cpp 复制代码
/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : 混分巨兽龙某某
* Version            : V1.0.0
* Date               : 2026/04/29
* Description        : Main program body.
*******************************************************************************/

#include "ch32l103_usbfs_device.h"
#include "debug.h"
#include "i2c_smbus.h"

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init( );
    USART_Printf_Init( 115200 );
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf("iKun_WCH_Agr_DEBUG\r\n");
    printf("作者:混分巨兽龙某某\r\n");
    RCC_Configuration( );

    /* Tim2 init */
    TIM2_Init( );

    /* Usart1 init */
    UART2_Init( 1, DEF_UARTx_BAUDRATE, DEF_UARTx_STOPBIT, DEF_UARTx_PARITY );

    /* I2C/SMBus init */
    I2C_SMBus_Init();

    /* USB20 device init */
    USBFS_RCC_Init( );
    USBFS_Device_Init( ENABLE );

    while(1)
    {
        if(Uart.Cmd_Pending_Flag)
        {
            Uart.Cmd_Pending_Flag = 0;

            __disable_irq();
            uint8_t buf_idx = (Uart.Tx_LoadNum - 1 + DEF_UARTx_TX_BUF_NUM_MAX) % DEF_UARTx_TX_BUF_NUM_MAX;
            uint8_t *data = &UART2_Tx_Buf[buf_idx * DEF_USB_FS_PACK_LEN];
            uint8_t len = Uart.Tx_PackLen[buf_idx];
            __enable_irq();

            if(len >= 4)
            {
                uint8_t cmd = data[0];
                uint8_t addr = data[1];
                uint8_t reg = data[2];
                uint8_t data_len = data[3];

                if(data_len > 64) data_len = 64;
                static uint8_t response[64];  // 全局/静态,不使用栈

                switch(cmd)
                {
                    case 0x01:
                        I2C_Write(addr, reg, &data[4], data_len);
                        break;

                    case 0x02:
                        I2C_Read(addr, reg, response, data_len);
                        USBFS_Endp_DataUp(DEF_UEP3, response, data_len, DEF_UEP_DMA_LOAD);
                        break;

                    case 0x03:
                        I2C_Write_NoReg(addr, &data[4], data_len);
                        break;

                    case 0x04:
                        I2C_Read_NoReg(addr,response,data_len);
                        USBFS_Endp_DataUp(DEF_UEP3, response, data_len, DEF_UEP_DMA_LOAD);
                        break;
                    
                    case 0x05:
                        {
                            uint8_t found_devs[32];  // 最多存32个设备地址
                            uint8_t found_cnt = 0;  // 找到的数量

                            // 扫描 I2C 地址 1~127
                            for (uint8_t i = 1; i < 128; i++)
                            {
                                if (I2C_CheckDevice(i))  // 检测设备是否存在
                                {
                                    found_devs[found_cnt++] = i;
                                }
                            }

                            // 把扫描到的地址上传给上位机
                            USBFS_Endp_DataUp(DEF_UEP3, found_devs, found_cnt, DEF_UEP_DMA_LOAD);
                            printf("I2C 扫描完成,找到 %d 个设备\r\n", found_cnt);
                            break;
                        }

                    case 0x06:
                        /* 进入IAP升级,待开发 */
                        break;
                }

                __disable_irq();
                if(Uart.Tx_RemainNum > 0)
                    Uart.Tx_RemainNum--;
                __enable_irq();
            }
        }
    }
}

四、iKun USB IO DEBUG Helper 的上位机代码

4.1 iKun USB IO DEBUG Helper 的 UI 布局

iKun USB IO DEBUG Helper 的上位机拥有如下功能:

1、USB 转 I2C/SMBUS 的数据写入与数据读取;

2、自动周期性读取 I2C/SMBUS 总线上的数据;

3、I2C/SMBUS 总线上从机地址的自动扫描;

4、日志模式与从机地址的 7 或 8 位地址的模式切换;

4.2 iKun USB IO DEBUG Helper 自动连接检测

作者这边是利用下位机 USB CDC 设备上报的字符串描述符 IKUN_WCH_AGR_DEBUG 来判断是否连接。当判断出目标设备连接之后创建串口对象并进行绑定即可(利用定时器来进行定时判断连接状态),代码如下:

cpp 复制代码
/*********************************************************************
 * 函数名称:checkUSBConnection
 * 功能描述:定时/主动检查 USB 转串口设备是否在线,并更新连接状态
 * 参    数:无
 * 返回值  :无
 ********************************************************************/
void MainWindow::checkUSBConnection()
{
    // 检查设备是否存在并获取当前连接状态
    bool connected = checkForiKunDevice();

    // 只有状态发生变化时,才更新指示灯(避免重复刷新)
    if (connected != isConnected) {
        isConnected = connected;   // 更新连接状态标志
        updateStatusIndicator();   // 更新界面状态指示灯
    }
}

/*********************************************************************
 * 函数名称:checkForiKunDevice
 * 功能描述:查找并打开指定序列号的 USB 转串口设备(IKUN_WCH_AGR_DEBUG)
 *           若已打开,则检查设备是否被拔出;未打开则重新搜索并打开
 * 参    数:无
 * 返回值  :true - 设备已连接并打开
 *           false - 设备不存在或打开失败
 ********************************************************************/
bool MainWindow::checkForiKunDevice()
{
    // 1. 如果设备已经打开,检查设备是否还插在电脑上
    if (m_iKunDevice && m_iKunDevice->isOpen()) {
        bool deviceExist = false;

        // 遍历系统当前所有可用串口
        foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
            // 根据序列号匹配目标设备
            if (info.serialNumber().contains("IKUN_WCH_AGR_DEBUG")) {
                deviceExist = true;
                break;
            }
        }

        // 如果设备不存在(已被拔出),关闭并释放串口对象
        if (!deviceExist) {
            m_iKunDevice->close();
            m_iKunDevice->deleteLater();  // Qt 对象安全释放
            m_iKunDevice = nullptr;
            return false;
        }

        // 设备仍在线
        return true;
    }

    // 2. 设备未打开 → 重新搜索并尝试打开
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        // 匹配设备序列号
        if (info.serialNumber().contains("IKUN_WCH_AGR_DEBUG")) {
            // 创建串口对象并绑定端口信息
            m_iKunDevice = new QSerialPort(info);

            // 配置串口通信参数:115200 8N1 无流控
            m_iKunDevice->setBaudRate(115200);
            m_iKunDevice->setDataBits(QSerialPort::Data8);
            m_iKunDevice->setParity(QSerialPort::NoParity);
            m_iKunDevice->setStopBits(QSerialPort::OneStop);
            m_iKunDevice->setFlowControl(QSerialPort::NoFlowControl);

            // 以读写方式打开串口
            if (m_iKunDevice->open(QIODevice::ReadWrite)) {
                qDebug() << "设备连接成功:" << info.portName();
                return true;
            } else {
                // 打开失败 → 释放内存
                delete m_iKunDevice;
                m_iKunDevice = nullptr;
            }
        }
    }

    // 未找到目标设备
    return false;
}

/*********************************************************************
 * 函数名称:updateStatusIndicator
 * 功能描述:更新界面状态指示灯(圆形)
 *           绿色 = 已连接
 *           红色   = 未连接/断开
 * 参    数:无
 * 返回值  :无
 ********************************************************************/
void MainWindow::updateStatusIndicator()
{
    // 创建 20x20 透明画布
    QPixmap pixmap(20, 20);
    pixmap.fill(Qt::transparent);

    QPainter painter(&pixmap);
    painter.setRenderHint(QPainter::Antialiasing);  // 抗锯齿

    // 根据连接状态设置指示灯颜色
    if (isConnected) {
        painter.setBrush(Qt::green);   // 已连接:绿色
        qDebug() << "状态:已连接";
    } else {
        painter.setBrush(Qt::red);    // 未连接:红色
        qDebug() << "状态:未连接";
    }

    // 绘制圆形指示灯
    painter.drawEllipse(2, 2, 16, 16);

    // 将绘制好的图标设置到界面控件上
    statusIndicator->setPixmap(pixmap);
}

4.3 USB CDC 数据的传输

USB CDC 的数据读取:作者的上位机代码其实就是利用了 Qt Creator 库自带的 Serial 进行数据的读取操作。相比于传统的 Serial 操作,作者这边针对发送数据进行了命令名解析,且增加了各式各样的模式切换功能!

cpp 复制代码
/*****************************************************************************************
  函数名称:on_readButton_clicked
  函数功能:I2C读取按钮点击槽函数 ------ 从UI获取参数,构建I2C读取指令,通过USB转I2C设备发送并读取返回数据
  入口参数:无
  返回值:无
  说明:支持7位/8位I2C地址模式切换,支持普通I2C读取与特殊I2C读取,带日志打印与超时处理
******************************************************************************************/
void MainWindow::on_readButton_clicked()
{
    // 1. 定义字符串
    QString slaveAddrStr = ui->slaveAddrEdit->text();
    QString regTypeStr = ui->regTypeComboBox->currentText();
    QString regAddrStr = ui->regAddrEdit->text();
    QString lengthStr = ui->lengthEdit->text();

    // 2. 把字符串转成数字
    bool ok;
    uint8_t slaveAddr = slaveAddrStr.toUInt(&ok, 16);
    uint8_t regType = regTypeStr.toUInt(&ok, 16);
    uint8_t regAddr = regAddrStr.toUInt(&ok, 16);
    uint8_t length = lengthStr.toUInt(&ok, 16);

    // 地址模式判断
    uint8_t real_addr;
    if (ui->modeComboBox->currentIndex() == 0) {
        // 7位模式:直接用
        real_addr = slaveAddr & 0x7F;
    } else {
        // 8位模式:必须右移1位!!!
        real_addr = (slaveAddr >> 1) & 0x7F;
    }

    // 3. 构建命令
    QByteArray cmd;
    if(ExtendCheck == 0){
        cmd.append(0x02);           // I2C Read 命令
    }else{
        cmd.append(0x04);           // Special I2C Read 命令
    }
    cmd.append(real_addr);
    cmd.append(regAddr);
    cmd.append(length);

    // 4. 发送
    if (m_iKunDevice && m_iKunDevice->isOpen()) {
        // 发送读指令
        m_iKunDevice->write(cmd);
        m_iKunDevice->flush();

        // 打印发送日志
        ui->logTextEdit->append(QString());
        if(R_Button == 1)
        {
            if(L_Button == 0)
            {
                ui->logTextEdit->append(QString("读取从机地址=0x%1, 寄存器地址=0x%2, 长度=%3")
                                        .arg(real_addr, 2, 16, QChar('0'))
                                        .arg(regAddr, 2, 16, QChar('0'))
                                        .arg(length));
            }else{
                ui->logTextEdit->append(QString("【%1】读取从机地址=0x%2, 寄存器地址=0x%3, 长度=%4")
                                        .arg(QDateTime::currentDateTime().toString("HH:mm:ss"))
                                        .arg(real_addr, 2, 16, QChar('0'))
                                        .arg(regAddr, 2, 16, QChar('0'))
                                        .arg(length));
            }
        }

        // ============================
        // 等待并读取串口返回数据
        // ============================
        QByteArray recvData;
        // 等待数据到来(最多等待 500ms)
        if (m_iKunDevice->waitForReadyRead(500)) {
            // 读取所有收到的数据
            recvData = m_iKunDevice->readAll();
        }

        // 如果有数据
        if (!recvData.isEmpty()) {
            if(L_Button == 0)
            {
                ui->logTextEdit->append(QString("读取成功,接收数据 = 0x%1")
                                        .arg(QString(recvData.toHex(' '))));
            }else{
                ui->logTextEdit->append(QString("【%1】读取成功,接收数据 = 0x%2")
                                        .arg(QDateTime::currentDateTime().toString("HH:mm:ss"))
                                        .arg(QString(recvData.toHex(' '))));
            }

        } else {
            if(L_Button == 0)
            {
                ui->logTextEdit->append("读取失败,未接收到数据!");
            }else{
                ui->logTextEdit->append(QString("【%1】读取失败,未接收到数据!")
                                        .arg(QDateTime::currentDateTime().toString("HH:mm:ss")));
            }
        }

    } else {
        ui->logTextEdit->append("iKun USB IO DEBUG Helper未打开,无法读取!");
    }
}

USB CDC 的数据写入:作者的上位机代码其实就是利用了 Qt Creator 库自带的 Serial 进行数据的写入操作。相比于传统的 Serial 操作,作者这边针对发送数据进行了命令名解析,且增加了各式各样的模式切换功能!

cpp 复制代码
/***************************************************************************************
  函数名称:on_writeButton_clicked
  函数功能:I2C写入按钮点击槽函数 ------ 从UI获取参数与待写入数据,构建I2C写入指令,通过USB转I2C设备发送
  入口参数:无
  返回值:无
  说明:支持7位/8位I2C地址模式,支持普通/特殊I2C写入,支持多字节十六进制数据解析,带日志打印
****************************************************************************************/
void MainWindow::on_writeButton_clicked()
{
    QString slaveAddrStr = ui->slaveAddrEdit->text();
    QString regAddrStr = ui->regAddrEdit->text();
    QString lengthStr = ui->lengthEdit->text();
    QString writeDataStr = ui->writeDataEdit->toPlainText().trimmed();

    // 2. 把字符串转成数字
    bool ok;
    uint8_t slaveAddr = slaveAddrStr.toUInt(&ok, 16);
    uint8_t regAddr = regAddrStr.toUInt(&ok, 16);
    uint8_t length = lengthStr.toUInt(&ok, 16);

    // ======================
    // 关键:解析多个十六进制数据
    // ======================
    QByteArray writeDataBytes;
    if (!writeDataStr.isEmpty()) {
        // 按空格分割
        QStringList hexList = writeDataStr.split(' ', QString::SkipEmptyParts);
        for (QString hexStr : hexList) {
            bool byteOk;
            uint8_t byte = hexStr.toUInt(&byteOk, 16);
            if (byteOk) {
                writeDataBytes.append(byte);
            }
        }
    }

    // 地址模式判断
    uint8_t real_addr;
    if (ui->modeComboBox->currentIndex() == 0) {
        // 7位模式:直接用
        real_addr = slaveAddr & 0x7F;
    } else {
        // 8位模式:必须右移1位!!!
        real_addr = (slaveAddr >> 1) & 0x7F;
    }

    // 3. 构建命令
    QByteArray cmd;
    if(ExtendCheck == 0){
        cmd.append(0x01);           // I2C Write 命令
    }else{
        cmd.append(0x03);           // Special I2C Write 命令
    }
    cmd.append(real_addr);
    cmd.append(regAddr);
    cmd.append(length);
    cmd.append(writeDataBytes); // 直接追加所有数据

    // 4. 发送
    if (m_iKunDevice && m_iKunDevice->isOpen()) {
        m_iKunDevice->write(cmd);
        m_iKunDevice->flush();

        qDebug() << "已发送数据:" << cmd.toHex(' ');

        // 日志显示所有写入数据
        ui->logTextEdit->append(QString());
        if(W_Button == 1)
        {
            if(L_Button == 0)
            {
                ui->logTextEdit->append(QString("写入操作:从机地址=0x%1, 寄存器地址=0x%2, 长度=%3, 写入数据=%4")
                                            .arg(real_addr, 2, 16, QChar('0'))
                                            .arg(regAddr, 2, 16, QChar('0'))
                                            .arg(length, 2, 16, QChar('0'))
                                            .arg(QString(writeDataBytes.toHex(' ')))); // 显示所有数据
            }
            else{
                ui->logTextEdit->append(QString("【%1】写入操作:从机地址=0x%2, 寄存器地址=0x%3, 长度=%4, 写入数据=%5")
                                            .arg(QDateTime::currentDateTime().toString("HH:mm:ss"))
                                            .arg(real_addr, 2, 16, QChar('0'))
                                            .arg(regAddr, 2, 16, QChar('0'))
                                            .arg(length, 2, 16, QChar('0'))
                                            .arg(QString(writeDataBytes.toHex(' '))));
            }
        }else{
            if(L_Button == 0){
                ui->logTextEdit->append(QString("数据写入成功!"));
            }else{
                ui->logTextEdit->append(QString("【%1】数据写入成功!")
                                        .arg(QDateTime::currentDateTime().toString("HH:mm:ss")));
            }
        }
    } else {
        ui->logTextEdit->append("iKun USB IO DEBUG Helper未打开,无法写入!");
    }
}

4.4 iKun USB IO DEBUG Helper 附加功能项

1、从机地址扫描功能:

cpp 复制代码
/***************************************************************************************
  函数名称:on_scanButton_clicked
  函数功能:I2C扫描按钮点击槽函数 ------ 发送扫描指令,自动扫描总线上所有在线的I2C从机设备并显示结果
  入口参数:无
  返回值:无
  说明:通过指令 0x05 触发下位机扫描I2C总线,超时1秒等待返回,支持打印找到的从机地址列表
****************************************************************************************/
void MainWindow::on_scanButton_clicked()
{
    ui->logTextEdit->append("==================================");
    ui->logTextEdit->append("正在扫描 I2C 总线上的从机设备...");

    // 发送扫描命令 0x05
    QByteArray cmd;
    cmd.append(0x05);  // 对应下位机 case 0x05
    cmd.append(0x01);  // 对应下位机 case 0x05
    cmd.append(0x01);  // 对应下位机 case 0x05
    cmd.append(0x01);  // 对应下位机 case 0x05

    if (m_iKunDevice && m_iKunDevice->isOpen()) {
        m_iKunDevice->write(cmd);
        m_iKunDevice->flush();

        // 等待返回数据(最多等1秒)
        QByteArray recvData;
        QElapsedTimer timer;
        timer.start();

        while (timer.elapsed() < 1000) {
            if (m_iKunDevice->waitForReadyRead(20)) {
                recvData += m_iKunDevice->readAll();
            }
        }

        // 显示结果
        if (!recvData.isEmpty()) {
            QString list;
            for (uint8_t addr : recvData) {
                list += QString("0x%1 ").arg(addr, 2, 16, QChar('0'));
            }
            ui->logTextEdit->append("扫描完成!找到设备:");
            ui->logTextEdit->append(list);
        } else {
            ui->logTextEdit->append("扫描完成!未找到任何 I2C 设备。");
        }
    } else {
        ui->logTextEdit->append("错误:设备未打开!");
    }

    ui->logTextEdit->append("==================================\n");
}

2、格式媒体表示功能:

cpp 复制代码
/*************************************************************************************************
  函数名称:on_rButton_clicked
  函数功能:读取显示开关按钮点击槽函数
  入口参数:无
  返回值:无
  说明:控制是否在日志中显示【读取操作】的详细参数信息
*************************************************************************************************/
void MainWindow::on_rButton_clicked()
{
    ui->logTextEdit->append(QString());
    // 根据当前状态,提示用户是开启还是关闭
    if(R_Button == 0){
        ui->logTextEdit->append("开启读取显示");
    }else{
        ui->logTextEdit->append("关闭读取显示");
    }
    // 切换标志位状态(0变1,1变0)
    R_Button = !R_Button;
}

/*************************************************************************************************
  函数名称:on_wButton_clicked
  函数功能:写入显示开关按钮点击槽函数
  入口参数:无
  返回值:无
  说明:控制是否在日志中显示【写入操作】的详细参数信息
*************************************************************************************************/
void MainWindow::on_wButton_clicked()
{
    ui->logTextEdit->append(QString());
    // 根据当前状态,提示用户是开启还是关闭
    if(W_Button == 0){
        ui->logTextEdit->append("开启写入显示");
    }else{
        ui->logTextEdit->append("关闭写入显示");
    }
    // 切换标志位状态
    W_Button = !W_Button;
}

/*************************************************************************************************
  函数名称:on_aButton_clicked
  函数功能:自动读取开关按钮点击槽函数
  入口参数:无
  返回值:无
  说明:启动/停止定时器,实现周期性自动执行I2C读取功能
*************************************************************************************************/
void MainWindow::on_aButton_clicked()
{
    ui->logTextEdit->append(QString());
    // 判断当前是否正在自动读取
    if (!m_isAutoReading)
    {
        // 未启动:启动定时器,开始自动读取
        m_readTimer->start();
        m_isAutoReading = true;
        ui->logTextEdit->append("【Auto_Read】启动周期性自动读取...");
    }
    else
    {
        // 已启动:停止定时器,关闭自动读取
        m_readTimer->stop();
        m_isAutoReading = false;
        ui->logTextEdit->append("【Auto_Read】已停止周期性自动读取");
    }
}

/*************************************************************************************************
  函数名称:on_lButton_clicked
  函数功能:日志时间显示开关按钮点击槽函数
  入口参数:无
  返回值:无
  说明:控制日志输出是否携带【时间戳】(HH:mm:ss)
*************************************************************************************************/
void MainWindow::on_lButton_clicked()
{
    ui->logTextEdit->append(QString());
    // 根据当前状态,提示用户是开启还是关闭
    if(L_Button == 0){
        ui->logTextEdit->append("开启时间显示");
    }else{
        ui->logTextEdit->append("关闭时间显示");
    }
    // 切换标志位状态
    L_Button = !L_Button;
}

/*************************************************************************************************
  函数名称:on_menuButton_clicked
  函数功能:菜单按钮点击槽函数
  入口参数:无
  返回值:无
  说明:弹出IAP升级工具窗口(当前功能未实现,仅占位提示)
*************************************************************************************************/
void MainWindow::on_menuButton_clicked()
{
    ui->logTextEdit->append("点击了菜单按钮");

    // 创建一个模态对话框
    QDialog *iapDialog = new QDialog(this);
    iapDialog->setWindowTitle("IAP 升级工具");
    iapDialog->setFixedSize(420, 280);
    iapDialog->setStyleSheet("background-color: #f8f8f8;");

    // 创建布局管理器
    QVBoxLayout *layout = new QVBoxLayout(iapDialog);
    layout->setSpacing(20);
    layout->setContentsMargins(40,40,40,40);

    // 创建标题标签
    QLabel *titleLabel = new QLabel("IAP 在线升级");
    titleLabel->setStyleSheet("font-size:22px; font-weight:bold; color:#333;");
    titleLabel->setAlignment(Qt::AlignCenter);

    // 创建提示标签
    QLabel *tipLabel = new QLabel("功能正在开发,敬请期待...");
    tipLabel->setStyleSheet("font-size:24px; color:#666;");
    tipLabel->setAlignment(Qt::AlignCenter);

    // 将控件添加到布局并显示对话框
    layout->addWidget(titleLabel);
    layout->addWidget(tipLabel);
    iapDialog->exec(); // 阻塞显示对话框
}

/*************************************************************************************************
  函数名称:on_extendCheckBox_stateChanged
  函数功能:扩展模式复选框状态改变槽函数
  入口参数:state -> 复选框状态(Qt::Checked / Qt::Unchecked)
  返回值:无
  说明:切换ExtendCheck标志,控制使用【普通I2C指令】还是【特殊扩展I2C指令】
*************************************************************************************************/
void MainWindow::on_extendCheckBox_stateChanged(int state)
{
    if (state == Qt::Checked) {
        ui->logTextEdit->append("自定义扩展模式已开启");
        ExtendCheck = 1; // 开启:使用特殊指令(0x03写, 0x04读)
    } else {
        ui->logTextEdit->append("自定义扩展模式已关闭");
        ExtendCheck = 0; // 关闭:使用普通指令(0x01写, 0x02读)
    }
}

/*************************************************************************************************
  函数名称:onAddrModeChanged
  函数功能:地址模式下拉框选择改变槽函数
  入口参数:index -> 下拉框索引(0=7位,1=8位)
  返回值:无
  说明:切换地址解析模式,控制I2C地址是否需要右移一位
*************************************************************************************************/
void MainWindow::onAddrModeChanged(int index)
{
    if (index == 0) {
        m_is7BitMode = true;
        qDebug() << "当前地址模式:7位模式";
    } else {
        m_is7BitMode = false;
        qDebug() << "当前地址模式:8位模式";
    }
}

五、项目演示

5.1 USB 转 I2C/SMBUS 写入

作者的 ikun usb 转 I2C/SMBUS 项目中提供的了 2 种写入方式:1、常规使用包含从机地址、寄存器地址与写入长度的 I2C/SMBUS 数据传输;2、仅发送从机地址与所需写入的数据长度(单纯的 I2C/SMBUS 数据写入,自由度更高,更方便读者朋友们调试所有)

5.2 USB 转 I2C/SMBUS 读取

作者的 ikun usb 转 I2C/SMBUS 项目中提供的了 2 种读取方式:1、常规使用包含从机地址、寄存器地址与读取长度的 I2C/SMBUS 数据传输;2、仅发送从机地址与所需读取的数据长度(单纯的 I2C/SMBUS 数据读取,自由度更高,更方便读者朋友们调试所有);

5.3 自动从机地址扫描

作者通过不断扫描 I2C/SMBUS 总线上各个地址来判断从机的地址,那个ACK了就记录那个。

六、代码开源

代码地址: 基于CH32L103的USB转I2C/SMBUS的调试助手项目资料资源-CSDN下载

如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!

相关推荐
sinat_399010276 天前
snps usb ip及 vip 使用
usb
Championship.23.2417 天前
Linux 3.0 USB机制深度解析:USB 3.0支持与传统外设驱动架构
linux·运维·架构·usb
ZenasLDR23 天前
Type-C接口iPad键盘皮套
接口·芯片·usb
smallerxuan23 天前
九、CherryUSB 设计架构与工作逻辑分析
usb·cherryusb·cherryusb分析
smallerxuan23 天前
二、USB协议中的设备类
usb·usb协议·usb设备类
smallerxuan25 天前
三、USB协议通信过程
usb·usb协议·usb通信过程
smallerxuan25 天前
七、USB协议中的事务
usb·usb协议·usb事务
smallerxuan1 个月前
五、USB协议中的请求
usb·usb协议·usb请求
smallerxuan1 个月前
八、USB协议分析与调试实战
usb·usb协议分析·usb协议·usb协议调测
smallerxuan1 个月前
四、USB协议中的描述符
usb·usb协议·usb描述符