CH341的I2C接口特性:(CH347为CH341的升级版,支持更高速的I2C速度,支持ACK检测以及I2C时钟延展等功能)
1、支持I2C速度20K/100K/400K/750K;
2、默认不支持设备的ACK应答监测,即忽略ACK状态;强制支持需修改软件;
| 引脚序号 | 功能说明 |
| 24 | SCL |
| 23 | SDA |
Windows系统I2C通讯接口函数
HANDLE WINAPI CH341OpenDevice( // 打开CH341设备,返回句柄,出错则无效 ULONG iIndex ); // 指定CH341设备序号,0对应第一个设备 VOID WINAPI CH341CloseDevice( // 关闭CH341设备 ULONG iIndex ); // 指定CH341设备序号 BOOL WINAPI CH341SetStream( // 设置串口流模式 ULONG iIndex, // 指定CH341设备序号 ULONG iMode ); // 指定模式,见下行 // 位1-位0: I2C接口速度/SCL频率, 00=低速/20KHz,01=标准/100KHz(默认值),10=快速/400KHz,11=高速/750KHz // 位2: SPI的I/O数/IO引脚, 0=单入单出(D3时钟/D5出/D7入)(默认值),1=双入双出(D3时钟/D5出D4出/D7入D6入) // 位7: SPI字节中的位顺序, 0=低位在前, 1=高位在前 // 其它保留,必须为0 BOOL WINAPI CH341ReadI2C( // 从I2C接口读取一个字节数据 ULONG iIndex, // 指定CH341设备序号 UCHAR iDevice, // 低7位指定I2C设备地址 UCHAR iAddr, // 指定数据单元的地址 PUCHAR oByte ); // 指向一个字节单元,用于保存读取的字节数据 BOOL WINAPI CH341WriteI2C( // 向I2C接口写入一个字节数据 ULONG iIndex, // 指定CH341设备序号 UCHAR iDevice, // 低7位指定I2C设备地址 UCHAR iAddr, // 指定数据单元的地址 UCHAR iByte ); // 待写入的字节数据 BOOL WINAPI CH341StreamI2C( // 处理I2C数据流,2线接口,时钟线为SCL引脚,数据线为SDA引脚(准双向I/O),速度约56K字节 ULONG iIndex, // 指定CH341设备序号 ULONG iWriteLength, // 准备写出的数据字节数 PVOID iWriteBuffer, // 指向一个缓冲区,放置准备写出的数据,首字节通常是I2C设备地址及读写方向位 ULONG iReadLength, // 准备读取的数据字节数 PVOID oReadBuffer ); // 指向一个缓冲区,返回后是读入的数据 BOOL WINAPI CH341ReadEEPROM( // 从EEPROM中读取数据块,速度约56K字节 ULONG iIndex, // 指定CH341设备序号 EEPROM_TYPE iEepromID, // 指定EEPROM型号 ULONG iAddr, // 指定数据单元的地址 ULONG iLength, // 准备读取的数据字节数 PUCHAR oBuffer ); // 指向一个缓冲区,返回后是读入的数据 BOOL WINAPI CH341WriteEEPROM( // 向EEPROM中写入数据块 ULONG iIndex, // 指定CH341设备序号 EEPROM_TYPE iEepromID, // 指定EEPROM型号 ULONG iAddr, // 指定数据单元的地址 ULONG iLength, // 准备写出的数据字节数 PUCHAR iBuffer ); // 指向一个缓冲区,放置准备写出的数据
讯享网
如上API接口函数,根据不同的业务场景可以选用不同的函数。
CH341ReadI2C: 适用于I2C设备地址固定7位,单次读取1个字节。
CH341WriteI2C: 适用于I2C设备地址固定7位,单次写入1个字节。
CH341StreamI2C: 适用于多字节的设备地址,或设备地址后紧跟寄存器地址,或连续的多字节读写。
CH341ReadIEEPROM,CH341WriteEEPROM: 适用于直接操作EEPROM存储器件。
操作流程:

CH341StreamI2C 函数说明
iWriteLength:I2C Write的字节长度
iWriteBuffer:I2C Write的缓冲区内容,该缓冲区内容会经过SDA信号线对外输出。首字节地址通常是设备地址及读写位。如设备地址是0x50,I2C写操作时首字节为:0x50 << 1 = 0xA0,I2C读操作时首字节为:0x50 << 1 | BIT(0) = 0xA1。
iReadLength:I2C Read的字节长度
oReadBuffer:API成功返回后,其内容是从SDA信号线上采集的数据。
示例1:EEPROM 24C256的设备地址是:0x50, 从其3200H开始的地址读取6字节的数据。

讯享网UCHAR OutBuf[3] = {0xA0, 0x32, 0x00}; UCHAR InBuf[6]; CH341StreamI2C(0, 3, OutBuf, 6, inBuf);
对应CH341PAR软件操作:

示例2:EEPROM 24C256的设备地址是:0x50, 从其3200H开始的地址写入2个字节的数据,内容0x11,0x22。
UCHAR OutBuf[5]; OutBuf[0] = 0xA0; OutBuf[1] = 0x32; OutBuf[2] = 0x00; OutBuf[3] = 0x11; OutBuf[4] = 0x22; CH341StreamI2C(0, 5, OutBuf, 0, NULL);
对应CH341PAR软件操作:

对应I2C总线时序如下:(未连接真正的EEPROM器件,忽略红色NACK标识)

升级的I2C接口函数
默认库函数提供的I2C函数不支持设备ACK的应答检测,此外有些外设需要在I2C的地址和数据或数据和数据之间插入一定的延迟delay,来满足时序上的要求。此类需求,可参考如下 API的实现。
I2C Start 和 I2C Stop
讯享网BOOL WINAPI IIC_IssueStart( ULONG iIndex ) // 指定CH341设备序号 { UCHAR mBuffer[ mCH341_PACKET_LENGTH ]; ULONG mLength; mBuffer[ 0 ] = mCH341A_CMD_I2C_STREAM; // 命令码 mBuffer[ 1 ] = mCH341A_CMD_I2C_STM_STA; // 产生起始位 mBuffer[ 2 ] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = 3; return( CH341WriteData( iIndex, mBuffer, &mLength ) ); // 写出数据块 } BOOL WINAPI IIC_IssueStop( ULONG iIndex ) // 指定CH341设备序号 { UCHAR mBuffer[ mCH341_PACKET_LENGTH ]; ULONG mLength; mBuffer[ 0 ] = mCH341A_CMD_I2C_STREAM; // 命令码 mBuffer[ 1 ] = mCH341A_CMD_I2C_STM_STO; // 产生停止位 mBuffer[ 2 ] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = 3; return( CH341WriteData( iIndex, mBuffer, &mLength ) ); // 写出数据块 }
I2C Write 1个字节并检查应答
BOOL WINAPI IIC_OutByteCheckAck( // 输出一字节数据并检查应答是否有效 ULONG iIndex, // 指定CH341设备序号 UCHAR iOutByte ) // 准备写出的数据 { UCHAR mBuffer[ mCH341_PACKET_LENGTH ]; ULONG mLength, mInLen; mBuffer[ 0 ] = mCH341A_CMD_I2C_STREAM; // 命令码 mBuffer[ 1 ] = mCH341A_CMD_I2C_STM_OUT; // 输出数据,位5-位0为长度,0长度则只发送一个字节并返回应答 mBuffer[ 2 ] = iOutByte; // 数据 mBuffer[ 3 ] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = 4; mInLen = 0; if ( CH341WriteRead( iIndex, mLength, mBuffer, mCH341A_CMD_I2C_STM_MAX, 1, &mInLen, mBuffer ) ) { // 执行数据流命令,先输出再输入 if ( mInLen && ( mBuffer[ mInLen - 1 ] & 0x80 ) == 0 ) return( TRUE ); // 返回的数据的位7代表ACK应答位,ACK=0有效 } return( FALSE ); }
I2C 发送设备地址,等待设备应答(可用于检测设备是否连接,并工作)
讯享网BOOL WINAPI CH341CheckDev( //检查I2C设备是否连接 ULONG iIndex, // 指定CH341设备序号 UCHAR iDevAddr //设备地址 ) { UCHAR buf ; buf = (iDevAddr<<1); IIC_IssueStart(0); if( IIC_OutByteCheckAck(0,buf) ) { IIC_IssueStop(0); return TRUE; }else{ IIC_IssueStop(0); return FALSE; } }
CH341StreamI2C_Delay(CH341StreamI2C接口函数的升级版) 可指定I2C连续写数据之间的延时,写地址和读地址之间的延时,读地址和读数据之间的延时,连续读数据之间的延迟。
BOOL WINAPI CH341StreamI2C_Delay( ULONG iIndex, // 指定CH341设备序号 ULONG iWriteLength, // 准备写出的数据字节数 PVOID iWriteBuffer, // 指向一个缓冲区,放置准备写出的数据,首字节通常是I2C设备地址及读写方向位 ULONG iReadLength, // 准备读取的数据字节数 PVOID oReadBuffer, // 指向一个缓冲区,返回后是读入的数据 UCHAR iWriteDataDelay, // 连续写数据之间的延时,单位US,数值范围:0~15 UCHAR iAddrDelay1, // 写地址到读地址之间的延时,单位US,数值范围:0~15 UCHAR iAddrDelay2, // 读地址和读数据的延时,单位US,数值范围:0~15 UCHAR iReadDataDelay) // 连续读数据之间的延时,单位US,数值范围:0~15 { UCHAR mBuffer[mDEFAULT_COMMAND_LEN + mDEFAULT_COMMAND_LEN / 8]; ULONG i, j, k, mLength; PUCHAR mWrBuf; UCHAR iWriteTimes; mLength = max(iWriteLength, iReadLength); if (mLength > mMAX_BUFFER_LENGTH) return (FALSE); if (mLength <= mDEFAULT_BUFFER_LEN) mWrBuf = (PUCHAR)mBuffer; // 不超过默认缓冲区长度 else { // 超过则需要另外分配内存 mWrBuf = (PUCHAR)LocalAlloc(LMEM_FIXED, mMAX_COMMAND_LENGTH + mMAX_COMMAND_LENGTH / 8); // 分配内存 if (mWrBuf == NULL) return (FALSE); // 分配内存失败 } i = 0; mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 命令码 mWrBuf[i++] = mCH341A_CMD_I2C_STM_STA; // 产生起始位 if (iWriteLength) { for (j = 0; j < iWriteLength;) { mLength = mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 当前包剩余长度,<mCH341A_CMD_I2C_STM_MAX if (mLength <= 3) { while (mLength--) mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = mCH341_PACKET_LENGTH; } if (mLength >= mCH341_PACKET_LENGTH) { mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 新包的命令码 mLength = mCH341_PACKET_LENGTH - 1; } mLength--; // 去掉尾部的提前结束码 if (mLength > 3 * (iWriteLength - j)) mLength = 3 * (iWriteLength - j); // 本次输出有效数据长度 iWriteTimes = mLength / 3; for (k = 0; k < iWriteTimes; k++) { mWrBuf[i++] = (UCHAR)(mCH341A_CMD_I2C_STM_OUT | 0x01); // 输出数据,位5-位0为长度 mWrBuf[i++] = *((PUCHAR)iWriteBuffer + j++); // 复制数据 mWrBuf[i++] = mCH341A_CMD_I2C_STM_US | (iWriteDataDelay & 0x0f); mLength -= 3; } } } if (iReadLength) { mLength = mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 当前包剩余长度,<mCH341A_CMD_I2C_STM_MAX if (mLength <= 3) { while (mLength--) mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = mCH341_PACKET_LENGTH; } if (mLength >= mCH341_PACKET_LENGTH) mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 新包的命令码 mWrBuf[i++] = mCH341A_CMD_I2C_STM_US | (iAddrDelay1 & 0x0f); if (iWriteLength > 1) { // 先输出 mWrBuf[i++] = mCH341A_CMD_I2C_STM_STA; // 产生起始位 mWrBuf[i++] = (UCHAR)(mCH341A_CMD_I2C_STM_OUT | 1); // 输出数据,位5-位0为长度 mWrBuf[i++] = *(PUCHAR)iWriteBuffer | 0x01; // I2C目标设备地址,最低位为1则进行读操作 } else if (iWriteLength) { // 输出一字节后直接输入 i--; mWrBuf[i++] = *(PUCHAR)iWriteBuffer | 0x01; // I2C目标设备地址,最低位为1则进行读操作 } mWrBuf[i++] = mCH341A_CMD_I2C_STM_US | (iAddrDelay2 & 0x0f); // 延时10微秒 for (j = 1; j < iReadLength;) { mLength = mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 当前包剩余长度,<mCH341A_CMD_I2C_STM_MAX if (mLength <= 1) { if (mLength) mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = mCH341_PACKET_LENGTH; } if (mLength >= mCH341_PACKET_LENGTH) mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 新包的命令码 mLength = iReadLength - j >= mCH341A_CMD_I2C_STM_MAX ? mCH341A_CMD_I2C_STM_MAX : 1; // 本次输入有效数据长度 mWrBuf[i++] = (UCHAR)(mCH341A_CMD_I2C_STM_IN | mLength); // 输入数据,位5-位0为长度 j += mLength; if (mLength >= mCH341A_CMD_I2C_STM_MAX) { // 当前包将满 mWrBuf[i] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 i += mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 跳过当前包剩余部分 } mWrBuf[i++] = mCH341A_CMD_I2C_STM_US | (iReadDataDelay & 0x0f); // 延时10微秒 } mLength = mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 当前包剩余长度,<mCH341A_CMD_I2C_STM_MAX if (mLength <= 1) { if (mLength) mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = mCH341_PACKET_LENGTH; } if (mLength >= mCH341_PACKET_LENGTH) mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 新包的命令码 mWrBuf[i++] = mCH341A_CMD_I2C_STM_IN; // 输入数据,只接收一个字节并发送无应答 } mLength = mCH341_PACKET_LENGTH - i % mCH341_PACKET_LENGTH; // 当前包剩余长度,<mCH341A_CMD_I2C_STM_MAX if (mLength <= 1) { if (mLength) mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = mCH341_PACKET_LENGTH; } if (mLength >= mCH341_PACKET_LENGTH) mWrBuf[i++] = mCH341A_CMD_I2C_STREAM; // 新包的命令码 mWrBuf[i++] = mCH341A_CMD_I2C_STM_STO; // 产生停止位 mWrBuf[i++] = mCH341A_CMD_I2C_STM_END; // 当前包提前结束 mLength = 0; if (iReadLength) j = CH341WriteRead(iIndex, i, mWrBuf, mCH341A_CMD_I2C_STM_MAX, (iReadLength + mCH341A_CMD_I2C_STM_MAX - 1) / mCH341A_CMD_I2C_STM_MAX, &mLength, oReadBuffer); // 执行数据流命令,先输出再输入 else j = CH341WriteData(iIndex, mWrBuf, &i); // 写出数据块 if (j && mLength != iReadLength) j = FALSE; if (mWrBuf != mBuffer) LocalFree(mWrBuf); // 如果是分配的内存则释放 return (j); }
演示示意图,红色部分为作用间隔时间。
如上为CH341的I2C功能使用说明,其他平台上Linux和Android系统上接口函数均保持类似,可直接参考移植。
注:如果对I2C功能有更高要求,可选用增强版的CH347芯片来实现。链接:
高速USB转JTAG/SPI/I2C/UART/GPIO应用_PC技术小能手的博客-CSDN博客

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/22188.html