前言:本文为手把手教学的基于 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下载
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!