一、IIC 简介
IIC(Inter-Integrated Circuit)是 IIC Bus 简称,中文叫集成电路总线。它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。
🔔IIC使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA。IIC将SCL处于高时SDA拉低的动作作为开始信号,
SCL处于高时SDA拉高的动作作为结束信号;
传输数据时,SDA在SCL低电平时改变数据,在SCL高电平时保持数据,每个SCL脉冲的高电平传递1位数据。
PS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI
二、I2C最重要的功能包括:
- 只需要两条总线---半双工
- 没有严格的波特率要求,例如使用RS232,主设备生成总线时钟
- 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一的地址进行软件寻址
- I2C是真正的多主设备总线,可提供仲裁和冲突检测
- 传输速度
- 标准模式:100kbit/s
- 快速模式:400kbit/s
- 高速模式:3.4Mbit/s
- 最大主设备数:无限制
- 最大从机数:理论上是127
IIC主要特点:
常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。**
-
IIC主设备功能:主要产生时钟,产生起始信号和停止信号
-
IIC从设备功能:可编程的IIC地址检测,停止位检测
-
IIC的一个优点是它支持多主控(multimastering), 其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
-
支持不同速率的通讯速度,标准速度(最高速度100kHZ),快速(最高400kHZ)
-
SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。
-
IIC是半双工,而不是全双工 ,同一时间只可以单向通信
-
为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。这一点在等下我们会讲解
🔮IIC的高阻态
漏极开路(Open Drain)即高阻状态,适用于输入/输出,其可独立输入/输出低电平和高阻状态,若需要产生高电平,则需使用外部上拉电阻
高阻状态:高阻状态是三态门电路的一种状态。逻辑门的输出除有高、低电平两种状态外,还有第三种状态——高阻状态的门电路。电路分析时高阻态可做开路理解。
我们知道IIC的所有设备是接在一根总线上的,那么我们进行通信的时候往往只是几个设备进行通信,那么这时候其余的空闲设备可能会受到总线干扰,或者干扰到总线,怎么办呢?
为了避免总线信号的混乱,IIC的空闲状态只能有外部上拉, 而此时空闲设备被拉到了高阻态,也就是相当于断路, 整个IIC总线只有开启了的设备才会正常进行通信,而不会干扰到其他设备。
三、IIC的从地址
🍎IIC器件地址: 每一个IIC器件都有一个器件地址,有的器件地址在出厂时地址就设定好了,用户不可以更改,比如OV7670的地址为0x42。有的器件例如EEPROM,前四个地址已经确定为1010,后三个地址是由硬件链接确定的,所以一IIC总线最多能连8个EEPROM芯片。
IIC从地址有3种类型:分别是7位,8位和10位。产生这么多类型的原因是厂商采用的不同的地址约定。
这里主要讲解7位的寻址,
🍏7位寻址
在7位寻址过程中,从机地址在启动信号后的第一个字节开始传输,该字节的前7位为从机地址,第8位为读写位,其中0表示写,1表示读。
上图:7位寻址。I2C总线规范规定,标准模式I2C,从机地址为7位长,其次是读/写位。
- 第一个字节的头7 位组成了从机地址, 最低位(LSB) 是第8 位, 它决定了传输的方向。
- 第一个字节的第8位是“0” , 表示主机会写信息到被选中的从机;
- “1” 表示主机会向从机读信息, 当发送了一个地址后, 系统中的每个器件都在起始条件后将
- 头7 位与它自己的地址比较, 如果一样, 器件会判定它被主机寻址, 至于是从机接收器还是从机发送器, 都由R/W 位决定。
- 任何I2C设备都必须遵循这个标准,USB2XXX传输的从机地址即为这7bit地址,不包含读写位,读写位会根据不同的函数自动添加进去。
四、I2C的协议层
🍒I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
- 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
- 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
- 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
数据格式
📙传输流程
-
主芯片要发出一个start信号
-
然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)
-
从设备回应(用来确定这个设备是否存在),然后就可以传输数据
-
主设备发送一个字节数据给从设备,并等待回应
-
每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
-
数据发送完之后,主芯片就会发送一个停止信号。
-
上图:白色背景表示"主→从",灰色背景表示"从→主"
🍓通讯的起始和停止信号
当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止
🍑应答 信号
协议规定数据传输过程必须包含应答(ACK)。接收器通过应答告知发送的字节已被成功接收,之后发送器可以进行下一个字节的传输。主机产生数据传输过程中的所有时钟,包括用于应答的第9个时钟。发送器在应答时钟周期内释放对SDA总线的控制,这样接收器可以通过将SDA线拉低告知发送器:数据已被成功接收。
应答信号分为两种:
1)当第9位(应答位)为 低电平 时,为 ACK (Acknowledge) 信号
2)当第9位(应答位)为 高电平 时,为 NACK(Not Acknowledge)信号
主机发送数据,从机接收时,ACK信号由从机发出。当在SCL第9位时钟高电平信号期间,如果SDA仍然保持高电平,则主机可以直接产生STOP条件终止以后的传输或者继续ReSTART开始一个新的传输
从机发送数据,主机读取数据时,ACK信号由主机给出。主机响应ACK表示还需要再接收数据,而当主机接收完想要的数据后,通过发送NACK告诉从机读取数据结束、释放总线。随后主机发送STOP命令,将总线释放,结束读操作。
在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第2个数据字节。
🍍IIC发送数据
Start: IIC开始信号,表示开始传输。
DEVICE_ADDRESS:: 从设备地址,就是7位从机地址
R/W: W(write)为写,R(read)为读
ACK: 应答信号
WORD_ADDRESS : 从机中对应的寄存器地址 比方说访问 OLED中的 某个寄存器
DATA: 发送的数据
STOP: 停止信号。结束IIC
主机要向从机写数据时:
- 主机首先产生START信号
- 然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方 向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)
- 主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
- 这时候主机等待从机的应答信号(A)
- 当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号
- 当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号,
- 主机产生停止信号,结束传送过程。
🔒IIC读数据:
主机要从从机读数据时:
- 主机首先产生START信号
- 然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
- 这时候主机等待从机的应答信号(ACK)
- 当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,
- 当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据,
- 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据
- 主机进而产生停止信号,结束传送过程。
软件IIC和硬件IIC
IIC分为软件IIC和硬件IIC
软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。
硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
五、建议
IIC的基本时序是通用的,可以封装好IIC的文件进行适配,更改引脚即可。
附上两个代码,通用的
IIC.c
/***********************************************************************************************************************************
***********************************************************************************************************************************
**【文件名称】 bsp_IICSoft.c
**【功能描述】 模拟IIC时序
** 定义功能函数
**
**【函数说明】
**
***********************************************************************************************************************************/
#include "bsp_IICSoft.h"
/************************************************
** 电平控制简化_宏定义
***********************************************/
#define SCL_L (I2C_MONI_SCL_GPIO->BSRR = (I2C_MONI_SCL_PIN << 16))
#define SCL_H (I2C_MONI_SCL_GPIO->BSRR = I2C_MONI_SCL_PIN)
#define SDA_L (I2C_MONI_SDA_GPIO->BSRR = (I2C_MONI_SDA_PIN << 16))
#define SDA_H (I2C_MONI_SDA_GPIO->BSRR = I2C_MONI_SDA_PIN)
#define SDA_READ ((I2C_MONI_SDA_GPIO->IDR & I2C_MONI_SDA_PIN)?1:0) // 不用改成输入模式就能读
/*****************************************************************************
** 内部函数
*****************************************************************************/
static void delayUs(uint32_t time); // 简单延时,不准确
static void isBusy (void); // 检测总线是否空闲
static void start (void); // 起始信号
static void stop (void); // 停止信号
static void ackYes (void); // 发送应答信号
static void ackNo (void); // 不发送应答信号
static uint8_t waitForAck(void); // 等待应答信号
static void sendByte(uint8_t data); // 发送一个字节
static uint8_t readByte(uint8_t ack); // 读取一个字节
/*****************************************************************************
*函 数:IICSoft_Init
*功 能:初始化模拟IIC引脚
*参 数:
*返回值:
*备 注:
*****************************************************************************/
void IICSoft_Init(void)
{
// 使能SCL引脚端口时钟
if(I2C_MONI_SCL_GPIO == GPIOA) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOB) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOC) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOD) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOE) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOF) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF , ENABLE);
if(I2C_MONI_SCL_GPIO == GPIOG) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG , ENABLE);
// 使能SDA引脚端口时钟
if(I2C_MONI_SDA_GPIO == GPIOA) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOB) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOC) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOD) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOE) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOF) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF , ENABLE);
if(I2C_MONI_SDA_GPIO == GPIOG) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG , ENABLE);
// 配置引脚工作模式: 开漏输出
GPIO_InitTypeDef G;
G.GPIO_Pin = I2C_MONI_SCL_PIN;
G.GPIO_Mode = GPIO_Mode_Out_OD;
G.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init(I2C_MONI_SCL_GPIO, &G);
delayUs(2);
G.GPIO_Pin = I2C_MONI_SDA_PIN;
GPIO_Init(I2C_MONI_SDA_GPIO, &G);
delayUs(2);
SCL_L ;
delayUs(2);
SDA_H ;
}
// 粗略延时************************
static void delayUs( u32 times)
{
times=times*7;
while(--times)
__nop();
}
// 判断总线是否处于空闲状态,若不是,则循环的提供SCL时钟驱动,直到从机释放SDA线
static void isBusy(void)
{
uint32_t i=500; // 大约3ms
//SDA_IN ();
while((SDA_READ) == 0 && (--i)) // 读取sda线上的电平状态,低电平说明总线被从机控制,高电平则说明总线空闲,可以准备发送开始信号
{
SCL_L;
delayUs(3);
SCL_H ;
delayUs(3);
}
if(i==0) printf("\r\n IIC总线超时错误!! \r\n"); // 错误提示
}
/*****************************************************************************
* 函 数:start
* 功 能:开始信号 (SCL高电平期间,SDA由高向低 跳变)
*****************************************************************************/
static void start(void)
{
isBusy (); // 判断总线是否处于空闲状态
SCL_L; delayUs(3);
SDA_H; delayUs(3);
SCL_H; delayUs(3);
SDA_L; delayUs(3); // 在SCL高电平其间, SDA由高向低 跳变
SCL_L; delayUs(3); // 将SCL拉低,钳住SCL线,准备发送数据
}
/*****************************************************************************
* 函 数:stop
* 功 能:产生停止信号 (在SCL高电平其间, SDA由低向高 跳变)
*****************************************************************************/
static void stop(void)
{
SCL_L; delayUs(3);
SDA_L; delayUs(3);
SCL_H; delayUs(3);
SDA_H ; delayUs(1); // 在SCL高电平其间, SDA由低向高 跳变
}
/*****************************************************************************
* 函 数:ackYes
* 功 能:主机产生应答信号 (在第9个时钟周期SAL高电平期间, SDA保持低电平)
* 主机接收完一个字节数据后,主机产生ACK通知从机一个字节数据已正确接收
*****************************************************************************/
static void ackYes(void)
{
SCL_L; delayUs( 3);
SDA_L; delayUs( 3);
SCL_H; delayUs(10); // 使SDA保持一个时钟的低电平
SCL_L; delayUs( 3);
SDA_H ; delayUs( 3); // 很重要: 在这里释放SDA线,从机才能获取控制权
}
/*****************************************************************************
* 函 数:ackNo
* 功 能:主机产生非应答信号 (在第9个时钟周期SAL高电平期间, SDA保持高电平)
* 主机接收完一个字节数据后,主机产生NACK通知从机发送线束,以便主机产生停止信号
*****************************************************************************/
static void ackNo(void)
{
SCL_L; delayUs( 3);
SDA_H; delayUs( 3);
SCL_H; delayUs(10); // 使SDA保持一个时钟的高电平
SCL_L; delayUs( 3);
SDA_H; delayUs( 3); // 很重要: 在这里释放SDA线,从机才能获取控制权
}
/*****************************************************************************
* 函 数:waitForAck
* 功 能:获取应答信号或者非应答信号
* 有效应答信号:从机第9个SCL=0时,SDA被从机拉低,并且SCL=1时,SDA依然为低
*
* 返回值: 0=收到应答信号, 1=收到非应答信号
* 备 注:
*****************************************************************************/
static uint8_t waitForAck(void)
{
uint8_t d=0;
SCL_L; delayUs( 3);
SDA_H; delayUs( 3); // 主机释放SDA线,使总线空闲,以便从机能够响应信息
SCL_H; delayUs(10); // 等待10us; 按标准模式100kbit/s
d=SDA_READ;
SCL_L; delayUs( 3);
SDA_H; delayUs( 3); // 主机释放SDA线,使总线空闲,以便从机能够响应信息
if(d)
return 1; // 返回非应答信号
else
return 0; // 返回应答信号
}
/*****************************************************************************
* 函 数: sendByte
* 功 能: 输出一个字节
* 参 数:
* 返回值: 0=收到应答信号, 1=收到非应答信号
* 备 注:
*****************************************************************************/
static void sendByte(uint8_t data)
{
for(uint8_t i=0; i<8; i++)
{
SCL_L; delayUs( 3);
(data&0x80)? SDA_H : SDA_L;
data<<=1; delayUs( 3);
SCL_H; delayUs( 3);
}
SCL_L; delayUs( 3);
SDA_H; delayUs( 3); // 主机释放SDA线,使用总线空闲,以便从机能够发出响应信息,并钳信SCL线
}
/*****************************************************************************
* 函 数: readByte
* 功 能: 读取一个字节
* 参 数: ack=0:主机还没接收完, ack=1:主机数据已全部接收完成
* 返回值: 读取到的Byte值
* 备 注: 读取从机发送的数据后,并决定发出应答信号还是非应答信号
*****************************************************************************/
static uint8_t readByte(uint8_t ack)
{
uint8_t data=0;
for(uint8_t i=0; i<8; i++)
{
SCL_H; delayUs( 3);
data<<=1;
if(SDA_READ) data|=0x01;
SCL_L; delayUs( 3);
}
if(ack) ackNo (); // 1, 不应答
else ackYes(); // 0, 应答
return data;
}
/*****************************************************************************
* 函 数: IICSoft_ReadByte
* 功 能: 向指定从机设备,读取指定地址的一个值, 单位:字节
* 参 数: slave: 从机地址
* addr: 数据地址
* *buf: 数据要存储的地址
* 返回值: 0=成功, 1=失败
* 备 注: 注意,时序有点特别
*****************************************************************************/
uint8_t IICSoft_ReadByte(uint8_t slave, uint8_t addr, uint8_t *buf)
{
start (); // 起始信号
sendByte (slave<<1 | 0); // 从机地址, 写方向 , 0写1读
if(waitForAck())
{
stop();
return 1;
}
sendByte(addr); // 数据地址
if(waitForAck())
{
stop();
return 1;
}
// 写数据和读数据最大的时序区别, 写比较简单,读数据:再发一次起始信号、有读方向的从机地址
start(); // 起始信号
sendByte(slave<<1 | 1); // 从机地址, 读方向 , 0写1读
if(waitForAck())
{
stop();
return 1;
}
*buf=readByte(1); // 读值, 并发送ackNo;
stop();
return 0;
}
/*****************************************************************************
* 函 数: IICSoft_ReadBueffer
* 功 能: 向指定从机设备,读取指定地址的多个值, 单位:字节
* 参 数: uint8_t slave 从机地址
* uint8_t addr 数据地址
* uint8_t data 要写入的数据(1字节)
* 返回值: 0=成功, 1=失败
* 备 注: 注意,时序有点特别
*****************************************************************************/
uint8_t IICSoft_ReadBueffer(uint8_t slave, uint8_t addr, uint8_t *buf, uint8_t len)
{
start (); // 起始信号
sendByte (slave<<1 | 0); // 从机地址, 写方向 , 0写1读
if(waitForAck()) // 等待ACK
{
stop();
return 1;
}
sendByte(addr); // 数据地址
if(waitForAck()) // 等待ACK
{
stop();
return 1;
}
// 写、读时序区别, 读数据:再发一次起始信号、有读方向的从机地址
start(); // 起始信号
sendByte(slave<<1 | 1); // 从机地址, 读方向 , 0写1读
if(waitForAck()) // 等待ACK
{
stop();
return 1;
}
while(len)
{
if(len==1) *buf=readByte(1); // 最后一个字节,读数据后产生NACK
else *buf=readByte(0); // 读数据后产生ACK
len--;
buf++;
}
stop (); // 产生一个停止信号
return 0;
}
/*****************************************************************************
* 函 数: IICSoft_WriteByte
* 功 能: 往从机某地址写入一个字节
* 参 数: uint8_t slave 从机地址
* uint8_t addr 数据地址
* uint8_t data 要写入的数据(1字节)
* 返回值: 0=成功, 1=失败
* 备 注: 时序与读是不同的
*****************************************************************************/
uint8_t IICSoft_WriteByte(uint8_t slave, uint8_t addr, uint8_t data)
{
start (); // 起始信号
sendByte (slave<<1 | 0); // 从机地址, 写方向 , 0写1读
if(waitForAck())
{
stop();
return 1; // 若从机地址发送失败,就返回
}
sendByte(addr); // 数据地址
if(waitForAck())
{
stop();
return 1; // 若发送失败,就返回
}
sendByte(data); // 发送数据, 写数据和读数据最大的时序区别在这一步前
if(waitForAck())
{
stop ();
return 1; // 数据写入失败
}
stop(); // 写入完成,产生停止信号
return 0;
}
// 往从机写多个字节数据
uint8_t IICSoft_WriteBuffer(uint8_t slave, uint8_t addr, uint8_t *buf, uint8_t len)
{
start (); // 起始信号
sendByte (slave<<1 | 0); // 从机地址, 写方向 , 0写1读
if(waitForAck())
{
stop();
return 1; // 若发送失败,就返回
}
sendByte(addr); // 数据地址
if(waitForAck())
{
stop();
return 1; // 若发送失败,就返回
}
for(uint8_t i=0; i<len; i++)
{
sendByte(buf[i]); // 数据
if(waitForAck()) // 每一个字节都要等从机应答
{
stop ();
return 1; // 数据写入失败
}
}
stop(); // 写入完成,产生停止信号
return 0;
}
IIC .h
#ifndef __I2C_MONI_H
#define __I2C_MONI_H
/***********************************************************************************************************************************
***********************************************************************************************************************************
**【文件名称】 i2c_moni.h
**【功能描述】 模拟IIC时序
** 定义引脚、定义全局结构体、声明全局函数
**
**【适用平台】 STM32F103 + 标准库v3.5 + keil5
**
**【移植说明】 引脚修改:在i2c_moni.h文件中修改,以方便IIC总线复用、代码复用
** 器件地址:在各设备文件中修改
**
**【更新记录】 2020-03-05 创建
** 2021-05-03 完善文件格式、注释格式
**
***********************************************************************************************************************************/
#include <stm32f10x.h>
#include <stdio.h>
/*****************************************************************************
* 移植
* 为配合多个设备共用一个模拟IIC, 外部引脚在本头文件内宏定义
****************************************************************************/
// SCL
#define I2C_MONI_SCL_GPIO GPIOC
#define I2C_MONI_SCL_PIN GPIO_Pin_12
// SDA
#define I2C_MONI_SDA_GPIO GPIOC
#define I2C_MONI_SDA_PIN GPIO_Pin_11
/*****************************************************************************
* 声明 全局函数
* 数量:1个初始化, 4个读写
****************************************************************************/
void IICSoft_Init(void); // 初始化所用引脚
uint8_t IICSoft_ReadByte (uint8_t addr, uint8_t reg, uint8_t* byte); // 向从机读一字节; 从机地址, 数据或者寄存器地址,数据
uint8_t IICSoft_ReadBueffer (uint8_t addr, uint8_t reg, uint8_t* buf, uint8_t len); // 向从机读多字节; 从机地址, 数据或者寄存器地址,数据
uint8_t IICSoft_WriteByte (uint8_t addr, uint8_t reg, uint8_t byte); // 向从机写一字节; 从机地址, 数据或者寄存器地址,数据
uint8_t IICSoft_WriteBuffer (uint8_t addr, uint8_t reg, uint8_t* buf, uint8_t len); // 向从机写多字节; 从机地址, 数据或者寄存器地址,数据
#endif