基于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下载

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

相关推荐
ZenasLDR8 天前
Type-C接口OTG转接器LDR6021Q
接口·芯片·usb
y小花16 天前
安卓USB服务概述
android·usb
矜辰所致19 天前
沁恒微蓝牙芯片 USB 应用开发入门
usb·沁恒微蓝牙·usb 应用开发·usb基础·usb应用
ZenasLDR1 个月前
Type-C接口LDR多协议取电芯片
接口·芯片·usb
ZenasLDR1 个月前
LDR系列PD协议控制芯片
接口·芯片·usb
CheungChunChiu2 个月前
USB‑C PD 充电系统完整解析(SC8886 + FUSB302)
linux·usb·type-c·充电
一个平凡而乐于分享的小比特3 个月前
USB Wi-Fi 三模式详解:Station、AP与Ad-Hoc
wifi·usb·ad-hoc·ap·station
遇雪长安3 个月前
高通安卓设备DIAG端口启用指南
android·adb·usb·dm·qpst·diag·qxdm
xhBruce3 个月前
Android USB 存储 冷启动(开机自动插着 U 盘)场景
android·usb·vold