当前位置: 首页 > article >正文

stm32主从机硬件IIC实现

前言:

        IIC作为一个基础的通信协议,活跃于各种设备之间。I2C作为两线通信协议,相较于spi来说所需引脚更少,我们可以使用硬件I2C在设备与设备之间通信,但在硬件I2c被其他功能所占据引脚时,也可以使用软件拉高拉低来模拟,32位单片机中我们常用的方式是软件模拟,但实际上软件模拟的I2C速度有时又达不到我们需求,软件的有点在于可以灵活使用各个引脚,硬件的优点在于能够更快的相应。硬件I2C网传存在一些小bug,但作者常常使用硬件I2C也并没有遇到什么bug,只能说具体问题具体看待了,当正遇到了的情况下,再去该软件模拟也可以的。毕竟硬件相对简单,便利。参考文章一般思路先介绍一下IIC后附加代码分析硬件I2C

一、IIC介绍

(一)、简介      
          I2C 是一种短距离总线通信协议,物理实现上,I2C 总线由两根信号线( SDA 与 SCL)和一个地线组成,两根信号线为双向发送的。它可以提供多主模式功能,支持标准(100KHZ)和快速模式(400KHZ),它可以用于多种用途,包括 CRC 生成和验证、SMBus(系统管理总线)以及 PMBus(电源管理总线)。 根据器件的不同,可利用 DMA 功能来减轻 CPU 的工作量。(作者暂时还没有使用过DMA在IIC中,后续有机会会进行补充)。
(二)、IIC特征
        1、支持多设备通信,多主模式功能:同一接口既可用作主模式也可用作从模式。
        2、IIC主模式特征:时钟生成,起始位和停止位生成
        3、IIC从模式特征: 可编程 I 2 C 地址检测,双寻址模式,可对 2 个从地址应答,停止位检测
        4、7 /10 位寻址以及广播呼叫的生成和检测
        5、总线需要接上拉电阻到电源,I2C 总线空闲状态下,输出为高阻态,所有设备空闲时,都输出高阻态,上拉电阻把总线拉成高电平。
(三)、补充关于IIC的上拉电阻
        
          1、  I2C总线采用 开漏输出(Open-Drain) 或 开集电极输出(Open-Collector) 的设计:
  • SDA(数据线)和SCL(时钟线)在空闲时处于高电平状态。

  • 当设备需要发送数据时,会拉低SDA或SCL。

  • 由于开漏输出无法主动拉高信号,因此需要通过外部上拉电阻将总线拉高到逻辑高电平。

如果没有上拉电阻,总线将无法正常工作,因为信号无法回到高电平状态

        2、上拉电阻的选择
  • 总线电容(Bus Capacitance)

    • 总线上的电容包括导线电容、设备引脚电容等。

    • 电容越大,信号的上升时间越长,通信速度越慢。

  • 通信速度(Clock Speed)

    • 标准模式(100 kHz)和快速模式(400 kHz)对上升时间的要求不同。

    • 高速模式(3.4 MHz)需要更小的上拉电阻。

  • 电源电压(VDD)

    • 电源电压越高,上拉电阻可以适当增大。

常用阻值范围:
  • 标准模式(100 kHz):通常使用 4.7 kΩ 到 10 kΩ 的上拉电阻。

  • 快速模式(400 kHz):通常使用 2.2 kΩ 到 4.7 kΩ 的上拉电阻。

  • 高速模式(3.4 MHz):通常使用 1 kΩ 到 2.2 kΩ 的上拉电阻。

计算公式:

上拉电阻的阻值可以通过以下公式估算:

Rmax=trise0.8473×CbusRmax​=0.8473×Cbus​trise​​

其中:

  • trisetrise​ 是信号的上升时间(由I2C规范决定)。

  • CbusCbus​ 是总线的电容值

3、上拉电阻布局
  • 每个总线都需要上拉电阻

    • SDA和SCL线都需要单独的上拉电阻。

  • 避免多个上拉电阻

    • 如果总线上有多个设备,不要为每个设备单独加上拉电阻,否则会导致总阻值过小,影响通信。

  • 靠近主设备放置

    • 上拉电阻应尽量靠近主设备(Master)放置,以减少信号反射和干扰。

4、电源匹配问题
  • 上拉电阻的电源电压(VDD)需要与I2C设备的逻辑电平匹配。

  • 例如,如果I2C设备的工作电压是3.3V,上拉电阻也应连接到3.3V电源。

5、常见问题
问题1:上拉电阻过小
  • 现象:总线电流过大,可能导致设备损坏或电源不稳定。

  • 解决方法:增大上拉电阻。

问题2:上拉电阻过大
  • 现象:信号上升时间过长,导致通信错误或速度下降。

  • 解决方法:减小上拉电阻。

问题3:没有上拉电阻
  • 现象:总线无法拉高,通信完全失败。

  • 解决方法:添加合适的上拉电阻。

注:补充部分来源与deepseek介绍。

二、硬件IIC连接

1、引脚选择

        本次实验所使用的是芯片是stm32f411ceu6的PB9,PB10 I2C2。手册显示

2、原理图

由于板子并未有上拉电阻,所以自己手动焊接了两个2.2K的上拉电阻

三、软件编程

(一)、I 2C 主机:
1、I2C主模式初始化:
I2C_HandleTypeDef gI2c2;//I2C句柄
/*****************************************************************************
 函 数 名  : AudioI2cInit
 功能描述  : IIC初始化,速度400000外部添加2.2K的上拉电阻外部实现上拉
 输入参数  : void
 返 回 值  : void
 作  者    : Bright
 创建日期 : 20240624
*****************************************************************************/
void MasterI2cInit(void)
{
	gI2c2.Instance=I2C2;
	gI2c2.Init.ClockSpeed=400000;						/*快速模式400,标准模式100KHZ*/
	gI2c2.Init.DutyCycle = I2C_DUTYCYCLE_16_9;              /*占空比:1/2 */ 
	gI2c2.Init.OwnAddress1=0;							/*主机不需要配置地址*/
	gI2c2.Init.AddressingMode=I2C_ADDRESSINGMODE_7BIT;	/*7位地址模式*/
	gI2c2.Init.DualAddressMode=I2C_DUALADDRESS_DISABLE;/*禁用双地址模式*/
	gI2c2.Init.OwnAddress2=0;							/*地址2未使用*/
	gI2c2.Init.GeneralCallMode=I2C_GENERALCALL_DISABLE;/*禁用广播呼叫*/
	gI2c2.Init.NoStretchMode=I2C_NOSTRETCH_DISABLE;	   /*始终拉伸*/
	gI2c2.Mode=HAL_I2C_MODE_MASTER;					   /*配置为主模式*/
	if(HAL_I2C_Init(&gI2c2)!=HAL_OK)
	{
		 
	};
}
/*****************************************************************************
 函 数 名  : HAL_I2C_MspInit
 功能描述  : IIC引脚初始胡初始化
 输入参数  : void
 返 回 值  : void
 作  者    : Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	if(hi2c->Instance==I2C1)
	{
		
	}
	if(hi2c->Instance==I2C2)
	{
		__HAL_RCC_GPIOB_CLK_ENABLE();
		__HAL_RCC_I2C2_CLK_ENABLE();
		GPIO_InitStruct.Pin = IIC_SCL_GPIO_PIN;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//GPIO_MODE_AF_OD;
		GPIO_InitStruct.Pull = GPIO_PULLUP;//GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
		HAL_GPIO_Init(IIC_SCL_GPIO_PORT,&GPIO_InitStruct);
		
		//I2C_SDA config
		GPIO_InitStruct.Pin = IIC_SDA_GPIO_PIN;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//GPIO_MODE_AF_OD;
		GPIO_InitStruct.Pull = GPIO_PULLUP;//GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF9_I2C2;
		HAL_GPIO_Init(IIC_SDA_GPIO_PORT,&GPIO_InitStruct);

	}
}
2、I2C读写数据
/*****************************************************************************
 函 数 名  : I2C_Write
 功能描述  : 向从机写入数据
 输入参数  : void
 返 回 值  : void
 作  者    : Bright
 创建日期 : 20240624
*****************************************************************************/
void I2C_Write(uint8_t reg_address, uint8_t data)
{
    uint8_t buffer[2];
    buffer[0] = reg_address;  // 寄存器地址
    buffer[1] = data;         // 要写入的数据
    // 发送数据到从机
    HAL_I2C_Master_Transmit(&gI2c2, I2C_DEVICE_ADDR << 1, buffer, 2, 1000);
}
/*****************************************************************************
 函 数 名  : I2C_Read
 功能描述  : 从从机读取数据
 输入参数  : void
 返 回 值  : void
 作  者    : Bright
 创建日期 : 20240624
*****************************************************************************/
uint8_t I2C_Read(uint8_t reg_address)
{
    uint8_t data = 0; 
    // 先发送寄存器地址
    HAL_I2C_Master_Transmit(&gI2c2, I2C_DEVICE_ADDR << 1, &reg_address, 1, 1000);
    // 然后读取数据
    HAL_I2C_Master_Receive(&gI2c2,(I2C_DEVICE_ADDR << 1)|0x01, &data, 1, 1000);

    return data;
}
3、I2C.h文件
#ifndef __I2C_H_
#define __I2C_H_
#include "sys/sys.h"


#define I2C_DEVICE_ADDR					0x50					
#define I2C_WRITE_ADDR					(I2C_DEVICE_ADDR<<1)
#define I2C_READ_ADDR					(I2C_WRITE_ADDR|0x01)
#define IIC_SCL_GPIO_PORT               GPIOB
#define IIC_SCL_GPIO_PIN                GPIO_PIN_10
#define IIC_SCL_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

#define IIC_SDA_GPIO_PORT               GPIOB
#define IIC_SDA_GPIO_PIN                GPIO_PIN_9
#define IIC_SDA_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */







/******************************************************************************************/

/* IO操作 */
#define IIC_SCL(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SCL */

#define IIC_SDA(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SDA */

#define IIC_READ_SDA     HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA */

extern void MasterI2cInit(void);
extern void I2C_Write(uint8_t reg_address, uint8_t data);
extern uint8_t I2C_Read(uint8_t reg_address);
#endif
(二)、从模式代码
1、从模式初始化
#include "config.h"
I2C_HandleTypeDef gI2c2;//I2C句柄
uint8_t ram[256];             // 模拟I2C从机数据寄存器(主机读写的数据都放在这块内存)
uint8_t offset;                      // 从机寄存器当前偏移地址
static uint8_t first_byte_state = 1; // 是否收到第1个字节,也就是偏移地址(0:已收到,1:没有收到)
/*****************************************************************************
 函 数 名  : I2c2SlaveInit
 功能描述  : IIC初始化,速度400000外部添加2.2K的上拉电阻外部实现上拉
 输入参数  : void
 返 回 值  : void
 作  者    : Bright
 创建日期 : 20240624
*****************************************************************************/
void SlaveI2cInit(void)
{
	gI2c2.Instance=I2C2;
	gI2c2.Init.ClockSpeed=400000;						/*快速模式400,标准模式100KHZ*/
	gI2c2.Init.DutyCycle = I2C_DUTYCYCLE_16_9;          /*占空比:1/2 */ 
	gI2c2.Init.OwnAddress1=I2C_DEVICE_ADDR;				/*主机不需要配置地址*/
	gI2c2.Init.AddressingMode=I2C_ADDRESSINGMODE_7BIT;	/*7位地址模式*/
	gI2c2.Init.DualAddressMode=I2C_DUALADDRESS_DISABLE; /*禁用双地址模式*/
	gI2c2.Init.OwnAddress2=0;							/*从机地址2未使用*/
	gI2c2.Init.GeneralCallMode=I2C_GENERALCALL_DISABLE; /*禁用广播呼叫*/
	gI2c2.Init.NoStretchMode=I2C_NOSTRETCH_DISABLE;	    /*时钟拉伸*/
	gI2c2.Mode=HAL_I2C_MODE_SLAVE;					    /*配置为从模式*/
	HAL_I2C_Init(&gI2c2);
	
	HAL_I2C_EnableListen_IT(&gI2c2);       // 使能I2C1的侦听中断  
}
/*****************************************************************************
 函 数 名  : HAL_I2C_MspInit
 功能描述  : IIC初始化,会自动调用
 输入参数  : void
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	if(hi2c->Instance==I2C1)
	{
		//I2c1
	}else if(hi2c->Instance == I2C2)
	{
		__HAL_RCC_GPIOB_CLK_ENABLE();
		__HAL_RCC_I2C2_CLK_ENABLE();
		GPIO_InitStruct.Pin = IIC_SCL_GPIO_PIN;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//GPIO_MODE_AF_OD;
		GPIO_InitStruct.Pull = GPIO_NOPULL;//GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF4_I2C2;
		HAL_GPIO_Init(IIC_SCL_GPIO_PORT,&GPIO_InitStruct);
		
		//I2C_SDA config
		GPIO_InitStruct.Pin = IIC_SDA_GPIO_PIN;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//GPIO_MODE_AF_OD;
		GPIO_InitStruct.Pull = GPIO_NOPULL;//GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF9_I2C2;
		HAL_GPIO_Init(IIC_SDA_GPIO_PORT,&GPIO_InitStruct);
		
		HAL_NVIC_SetPriority(I2C2_EV_IRQn,1,1);  // 事件中断(必须有)
		HAL_NVIC_EnableIRQ(I2C2_EV_IRQn);
	}
	
}
/*****************************************************************************
 函 数 名  : I2C2_EV_IRQHandler
 功能描述  :I2C 事件中断服务函数
 输入参数  : void
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void I2C2_EV_IRQHandler(void)
{
	HAL_I2C_EV_IRQHandler(&gI2c2);
}
/*****************************************************************************
 函 数 名  : HAL_I2C_ListenCpltCallback
 功能描述  :从函数回调函数,用于监听主发送的数据
 输入参数  : i2c句柄
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
	if(hi2c->Instance == I2C2)
	{
		first_byte_state = 1;
		offset = 0;	//偏移位
		HAL_I2C_EnableListen_IT(hi2c); // slave is ready again
		// 完成一次通信,清除状态
	}
}
/*****************************************************************************
 函 数 名  : HAL_I2C_AddrCallback
 功能描述  :从设备地址回调函数,地址匹配上以后会进入该函数
 输入参数  : void
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
	if(hi2c->Instance == I2C2)
	{
		if(TransferDirection == I2C_DIRECTION_TRANSMIT) 
		{	// 主机发送,从机接收
			if(first_byte_state) 
			{// 准备接收第1个字节数据
				HAL_I2C_Slave_Seq_Receive_IT(hi2c, &offset, 1, I2C_NEXT_FRAME);  // 每次第1个数据均为偏移地址
			} 
		} 
		else 
		{	// 主机接收,从机发送
			HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], 1, I2C_NEXT_FRAME);  // 打开中断并把ram[]里面对应的数据发送给主机
		}
	}
}
/*****************************************************************************
 函 数 名  : HAL_I2C_AddrCallback
 功能描述  :I2C数据接收回调函数(在I2C完成一次接收时会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
 输入参数  : hi2c 句柄
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
	if(hi2c->Instance == I2C2)
	{
		if(first_byte_state) 
		{
			first_byte_state = 0;// 收到的第1个字节数据(偏移地址)
		} 
		else 
		{	
			// 收到的第N个字节数据
			offset++;  // 每收到一个数据,偏移+1
		}
		// 打开I2C中断接收,下一个收到的数据将存放到ram[offset]
		HAL_I2C_Slave_Seq_Receive_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 接收数据存到ram[]里面对应的位置
	}
	
}
/*****************************************************************************
 函 数 名  : HAL_I2C_AddrCallback
 功能描述  :I2C数据发送回调函数(在I2C完成一次发送后会关闭中断并调用该函数,因此在处理完成后需要手动重新打开中断)
 输入参数  : hi2c 句柄
 返 回 值  : void
 作  者    :  Bright
 创建日期 : 20240624
*****************************************************************************/
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
	if(hi2c->Instance == I2C2)
	{
		offset++;  // 每发送一个数据,偏移+1
		HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &ram[offset], sizeof(ram), I2C_NEXT_FRAME);  // 打开中断并把ram[]里面对应的数据发送给主机
	}
}

2、I2c.h文件
#ifndef __I2C_H_
#define __I2C_H_
#include "sys/sys.h"
extern uint8_t ram[256]; 

#define I2C_DEVICE_ADDR						0xA0

#define IIC_SCL_GPIO_PORT               GPIOB
#define IIC_SCL_GPIO_PIN                GPIO_PIN_10
#define IIC_SCL_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

#define IIC_SDA_GPIO_PORT               GPIOB
#define IIC_SDA_GPIO_PIN                GPIO_PIN_9
#define IIC_SDA_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)   /* PB口时钟使能 */

/******************************************************************************************/

/* IO操作 */
#define IIC_SCL(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SCL */

#define IIC_SDA(x)        do{ x ? \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \
                              HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \
                          }while(0)       /* SDA */

#define IIC_READ_SDA     HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA */

extern void SlaveI2cInit(void);
/*************************************IIC RAM[256]内存说明*****************************************************/

					  

/******************************************************************************************/
#endif



						  

四、主从模式测试

1、主测试代码:
#include "config.h"
u8 readData=0;
int main(void)
{
	DemoSystemInit();
	printf("%d\r\n",HAL_RCC_GetSysClockFreq());
	printf("Mcu pow On \r\n");
	uint8_t writeData=0XAA;
	uint8_t writeData2=0XBB;
	
	I2C_Write(0X00,writeData);
	readData=I2C_Read(0X02);//电池温度ram[2]

	while(1)
	{
		if(readData == 0XAA)
		{
			LED_ON();
			
		}else if(readData == 0XBB)
		{
			 LED_ON();
			DelayMs(1000);
			 LED_OFF();
			DelayMs(1000);
		}
		else
		{
			LED_OFF(); 
		}
	}
}




2、从测试代码
#include "config.h"
extern uint8_t ram[256]; 

int main(void)
{
	DemoSystemInit();
	while(1)
	{
		if(ram[0] == 0xAA)
		{
			LED_ON();
		}else if(ram[0] == 0xBB)
		{
			LED_ON();
			DelayMs(500);
			 LED_OFF();
			DelayMs(500);
		}else
		{
			LED_OFF(); 
		}
	}
	
}




3、实际效果

五、结尾

        总体上硬件i2c操作便是如此,可以作为少量快速相应的通信方式, 之前是想将其与I2S一起实现,但I2S的数据量需要的相应数据过快,I2C不适合。后续还会更新一下软件i2c实现主从机模式,作为学习I2c的对比总结。


http://www.kler.cn/a/572653.html

相关文章:

  • Windows10下本地搭建Manim环境
  • 新品速递 | 多通道可编程衰减器+矩阵系统,如何破解复杂通信测试难题?
  • 探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(二)
  • 【工具】COME对比映射学习用于scRNA-seq数据的空间重构
  • 文件压缩与解压工具7-Zip的安装和使用(免费)
  • 探索DeFi世界:用Python开发去中心化金融应用
  • 第5章 使用OSSEC进行监控(网络安全防御实战--蓝军武器库)
  • 【深度学习CV】【图像分类】从CNN(卷积神经网络)、ResNet迁移学习到GPU高效训练优化【案例代码】详解
  • c语言、c++怎么将string类型数据转成int,怎么将int转成string
  • 基于stm32的多旋翼无人机(Multi-rotor UAV based on stm32)
  • 软考中级-数据库-3.3 数据结构-树
  • Leetcode3270:求出数字答案
  • C#实现语音合成播报器——基于System.Speech的语音交互方案,在windows上实现语音播报指定文本
  • 请谈谈 HTTP 中的请求方法(GET、POST、PUT、DELETE等),它们的区别是什么?
  • Python----数据分析(Matplotlib四:Figure的用法,创建Figure对象,常用的Figure对象的方法)
  • alloc、malloc 与 allocator:内存管理三剑客
  • 7.1 Hugging Face PEFT 快速入门:参数高效微调实战指南
  • Cpu100%问题处理(包括-线上docker服务)
  • 基于Asp.net的农产品销售管理系统
  • DeepSeek R1助力,腾讯AI代码助手解锁音乐创作新