CH341的I2C接口特性:

1、支持I2C速度20K/100K/400K/750K;

2、默认不支持设备的ACK应答监测,即忽略ACK状态;强制支持需修改软件;

引脚序号 功能说明
24 SCL
23 SDA

Windows系统SPI通讯接口函数

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=高位在前
// 其它保留,必须为0BOOL    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,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开始的地址读取256字节的数据。

UCHAR OutBuf[3] = {0xA1, 0x32, 0x00};
UCHAR InBuf[256];CH341StreamI2C(0, 3, OutBuf, 256, inBuf);

示例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);

对应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~15UCHAR iAddrDelay1,     // 写地址到读地址之间的延时,单位US,数值范围:0~15UCHAR iAddrDelay2,     // 读地址和读数据的延时,单位US,数值范围:0~15UCHAR 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_MAXif (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_MAXif (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_MAXif (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_MAXif (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_MAXif (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); // 执行数据流命令,先输出再输入elsej = 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博客

CH341的I2C接口编程说明相关推荐

  1. i2c hid 触摸板不能用_I2C 总线协议初探 - STM32 I2C 接口外设学习笔记

    I2C(Inter-Integrated Circuit)总线是由 PHILIPS(飞利浦) 公司开发的两线式串行总线,用于连接微控制器及其外围设备.是微电子通信控制领域广泛采用的一种总线标准.它是同 ...

  2. EEPROM的操作---SPI接口和I2C接口

    参考:http://blog.csdn.net/yuanlulu/article/details/6163106 ROM最初不能编程,出厂什么内容就永远什么内容,不灵活.后来出现了PROM,可以自己写 ...

  3. UART SPI I2C 接口介绍 转载

    UART SPI I2C 接口介绍@TOC 做单片机开发时UART,SPI和I2C都是我们最经常使用到的硬件接口,我收集了相关的具体材料对这三种接口进行了详细的解释. UART UART是一种通用串行 ...

  4. 【转】工厂模式面向接口编程

    为了实现更好的灵活性     应改面向接口编程.因此,应该面向接口提供工场.         比如,Cat,   Dog,   Mouse,都是4条腿会跑的动物.     因此,我们建立一个接口叫做F ...

  5. linux i2c调试命令,嵌入式Linux下I2C接口调试

    By Toradex秦海 1).简介 I2C是嵌入式设备最为常用的接口之一,常用于如下面这些应用场景,因此本文就基于嵌入式Linux演示在User Space进行I2C设备调试. - Digital ...

  6. 黑马java教程是什么_Java教程:揭秘什么是面向接口编程

    先用一个案例来给大家说明一下面向接口编程. 案例:有一个电脑类(Computer),电脑除了有基本的开机关机功能外,还有连接任何外接设备的功能,比如能电脑能连接外置键盘(Keyboard),鼠标(Mo ...

  7. Java面向接口编程,低耦合高内聚的设计哲学

    接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极大的降低程序中各个模块之间的耦合,提高系统的可维护性以及可扩展性. 因此,很多的软件架构设计理念都倡导"面向接口编程"而 ...

  8. 从针对接口编程到依赖注入

    1.概况说明 2.猫狗大战举例 3.说明为什么要针对接口编程,优点 4.说明为什么要"依赖抽象,不要依赖具体类" 5.说明"依赖倒置"与抽象工厂模式 6.说明& ...

  9. 10.STM32中用I2C接口发送数据到EEPROM寄存器在从此寄存器读数据

    10.STM32中用I2C接口发送数据到EEPROM寄存器在从此寄存器读数据.

最新文章

  1. No space left on device
  2. hyperledger-simple-app
  3. 【渝粤教育】21秋期末考试管理会计10171k2
  4. LeetCode 421. 数组中两个数的最大异或值(Trie树)
  5. php正则循环,PHP正则解析多重循环模板示例
  6. python3 rrdtool 使用
  7. 强化管理远程分支机构利器之coreRODC[为企业部署Windows Server 2008系列九]
  8. 二分查找算法(Java)
  9. 哔哩哔哩APP导出缓存视频并合并成MP4
  10. CKplayer功能配置
  11. Shawn Wildermuth的《Architecting WP7 》系列文章
  12. 开源微商城 特惠端午节
  13. How to Avoid Branching on the GPU 如何在GPU避免分支
  14. 最大化最小值和最小化最大值
  15. 2020知道答案C语言,C语言及逆向2020知到答案
  16. 介绍计算机硬件的英语作文带翻译,自我介绍作文之英语作文自我介绍带翻译(35页)-原创力文档...
  17. 狂吃不胖11种食物化解油腻
  18. 【adb】adb push命令 向设备传输文件
  19. git log中文乱码的问题
  20. 记录--微信小程序,uniapp,H5端发送,显示emoji表情

热门文章

  1. 从阿里社招面试,看“野生”Java程序员的学习道路
  2. C语言中的fprintf和printf区别在哪?
  3. 英语----倒装句(上):完全倒装
  4. 为什么布局了100+社交媒体账号,却依然做不好社会化营销?
  5. C# Socket编程实现简单的局域网聊天器
  6. 最难学的10大编程语言排行榜,Java只排第三,第一出乎意料
  7. 浅读 John Backus 图灵奖获奖演讲论文
  8. ROS18.04+ Gazebo9 + robotiq85 视觉机械臂抓取仿真
  9. H.265视频EasyPlayer播放器点播FLV不断加载,如何处理?
  10. 程序员兼职的几大网站--点赞收藏