本帖最后由 L-MCU 于 2024-7-22 15:08 编辑
关于CH32L103 IIC的特征以及概述,在应用手册有相关介绍,在此不再赘述。此外,CH32L103 EVT提供了相关例程,如下图,相关应用可参考一下。本篇文章主要简单介绍一下IIC的通信协议以及模拟IIC的应用等,包含硬件IIC以及模拟IIC在使用时的相关注意事项。
1、关于IIC的通信协议 IIC是一种串行半双工通信协议,通过SCL和SDA两根线实现通信,支持双向通信,支持主从通信,常用于低速设备通信。IIC支持多设备间进行通信,每个设备地址都是唯一的。 (1)关于SCL与SDA IIC通过SCL和SDA两根信号线进行通信,其中: SCL:时钟信号线 SDA:数据信号线 IIC通信时钟始终由主机通过SCL信号线提供。 默认情况下,SCL与SDA两根信号线上拉,因此在进行IIC初始化时,IIC引脚一般配置为开漏输出模式,且硬件上IIC引脚需要接上拉。 (2)关于IIC数据传输 IIC通信为半双工通信,可运行在以下4种模式: 主设备发送模式; 主设备接收模式; 从设备发送模式; 从设备接收模式; IIC模块默认工作在从模式,在产生起始条件后,会自动地切换到主模式,当仲裁丢失或产生停止信号后,会切换到从模式。 IIC通信过程中,数据和地址都以字节(8位)为单位进行数据或地址传输。其中,地址一般设置为7位,最高位有效,高7位表示地址,最后一位则表示读写。 若为0,则表示写,如下图: 若为1,则表示读,如下图: 关于IIC通信,目前常用的两种模式是主设备发送和主设备接收模式,这两种模式通信流程介绍如下: 主设备发送流程,具体如下: (a)主机发送起始信号,开启通信流程; (b)接着发送一个命令字节,该命令字节由7位的地址位和1位读写位组成。此时读写位为0,表示发送; (c)从机收到命令字节后,向主机回ACK信号; (d)主机收到ACK信号后,向从机发送第一个数据字节; (e)从机收到数据字节后,向主机回ACK; (f)主机收到ACK信号后,向从机再次发送数据字节; (g)重复e、f流程,当主机发送最后一个数据字节且收到ACK信号后,向从机发送停止信号,从机收到停止信号后退出该次通信。 主设备接收流程,具体如下: (a)主机发送起始信号,开启通信流程; (b)接着发送一个命令字节,该命令字节由7位的地址位和1位读写位组成。此时读写位为1,表示接收; (c)从机收到命令字节后,向主机回ACK信号,接着发送第一个数据字节; (d)主机收到数据字节后,向从机回ACK; (e)从机收到ACK信号后,向主机再次发送数据字节; (f)重复d、e流程,当主机数据接收完成后,向从机发送一个NAK信号,从机收到NAK信号后停止发送; (g)最后主机再发送一个停止信号结束本次通信; (3)关于IIC通信协议 下面两张截图为EVT IIC操作EEPROM例程的通信信号,该两张图包含了IIC通信过程中的空闲信号、起始信号、停止信号、写数据信号、读数据信号、ACK信号、NAK信号等。下面依次解析: 空闲信号: SCL与SDA两根信号线均为高电平时,表现为空闲信号。如第二张截图最后部分,两根信号线状态均为高电平; 起始信号: SCL信号线为高电平时,SDA信号线由高电平变为低电平,表现为起始信号。如第一张截图圈出部分; 停止信号: SCL信号线为高电平时,SDA信号线由低电平变为高电平,表现为停止信号。如第二张截图圈出部分; ACK信号: SCL信号线为高电平时,读取SDA信号线电平,若为低电平,则表示ACK信号; NAK信号: SCL信号线为高电平时,读取SDA信号线电平,若为高电平,则表示NAK信号; 读写数据信号: 读数据信号或写数据信号,是由主机发送地址的最后一位是0还是1判断的,为0则表示写,若为1,则表示读,具体可看上面介绍。关于IIC通信过程中,SDA数据的高低电平变化则是在SCL为低期间进行变化,在SCL为高期间则保持不变,此时,SDA为高电平则表示1,为低电平则表示0,如下图
2、关于IIC的应用 在EVT例程中提供了关于硬件IIC的应用,此处主要介绍模拟IIC的应用。示例如下: 1、初始化模拟IIC引脚,一般配置为开漏输出模式,代码如下: //初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
RCC_PB2PeriphClockCmd( RCC_PB2Periph_GPIOA, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;//使用PA1和PA2作为模拟IIC引脚,PA1对应SDA,PA2对应SCL
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ; //开漏输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
2、起始信号:SCL信号线为高电平时,SDA信号线由高电平变为低电平,表现为起始信号。代码如下: //IIC开始信号
//当IIC SCL线处于高电平时,SDA线由高电平向低电平跳变,为IIC开始信号,配置开始信号前必须保证IIC总线处于空闲状态
void IIC_Start()
{
IIC_SDA_H;
IIC_SCL_H;
Delay_Us(1);
IIC_SDA_L;
Delay_Us(1);
IIC_SCL_L;
}
3、停止信号:SCL信号线为高电平时,SDA信号线由低电平变为高电平,表现为停止信号。代码如下: //IIC停止信号
//当IIC SCL线处于高电平时,SDA线由低电平向高电平跳变,为IIC停止信号
void IIC_Stop()
{
IIC_SDA_L;
IIC_SCL_H;
Delay_Us(1);
IIC_SDA_H;
}
4、ACK信号:SCL信号线为高电平时,读取SDA信号线电平,若为低电平,则表示ACK信号。代码如下: //产生应答信号ACK
void IIC_ACK(void)
{
IIC_SDA_L;
Delay_Us(1);
IIC_SCL_H; //在SCL线为高电平期间读取SDA线为低电平,则为ACK响应
Delay_Us(1);
IIC_SCL_L;
}
5、NAK信号:SCL信号线为高电平时,读取SDA信号线电平,若为高电平,则表示NAK信号。代码如下: //产生非应答信号NACK
void IIC_NACK(void)
{
IIC_SDA_H;
Delay_Us(1);
IIC_SCL_H; //在SCL线为高电平期间读取SDA线为高电平,则为NACK响应
Delay_Us(1);
IIC_SCL_L;
}
6、等待应答:释放SDA总线,由接收端控制SDA信号线。在SCL为高电平期间等待响应,若SDA线为高电平,表示NACK信号,反之则为ACK信号。代码如下: //IIC等待应答信号
u8 IIC_WaitAck(void)
{
uint8_t rvalue;
IIC_SDA_H; //发送端释放SDA总线,由接收端控制SDA线
Delay_Us(1);
IIC_SCL_H; //在SCL为高电平期间等待响应,若SDA线为高电平,表示NACK信号,反之则为ACK信号
Delay_Us(1);
if(I2C_SDA_READ()) //读取SDA线状态判断响应类型,高电平,返回去,为NACK信号,反之则为ACK信号
{
rvalue = 1;
}
else
{
rvalue = 0;
}
IIC_SCL_L;
return rvalue;
}
7、发送字节:控制SCL线产生高低电平跳变,产生通讯时钟,同时利用延时函数在SCL为高电平期间读取SDA线电平逻辑。代码如下: //IIC发送一个字节数据(即8bit)
void IIC_SendByte(u8 data)
{
u8 i;
//先发送字节的高位bit7
for (i = 0; i < 8; i++)
{
if (data & 0x80) //判断8位数据每一位的值(0或1)
{
IIC_SDA_H;
}
else
{
IIC_SDA_L;
}
Delay_Us(1); //控制SCL线产生高低电平跳变,产生通讯时钟,同时利用延时函数在SCL为高电平期间读取SDA线电平逻辑
IIC_SCL_H;
Delay_Us(1);
IIC_SCL_L;
if (i == 7)
{
IIC_SDA_H; //控制SDA线输出高电平,释放总线,等待接收方应答信号
}
data <<= 1; //左移一个bit
}
}
8、读取字节:利用延时函数在SCL为高电平期间读取SDA线电平逻辑。代码如下: //IIC读取一个字节
u8 IIC_ReadByte(void)
{
u8 i;
u8 value;
//读到第1个bit为数据的bit7
value = 0;
for(i = 0; i < 8; i++)
{
value <<= 1;
IIC_SCL_H;
Delay_Us(1);
if (I2C_SDA_READ()) //利用延时函数在SCL为高电平期间读取SDA线电平逻辑
{
value++;
}
IIC_SCL_L;
}
return value;
}
以上函数就是对模拟IIC通信过程中各种信号的表示,通过调用上函数,可以使用模拟IIC驱动各类IIC设备。
3、关于IIC使用过程中遇到各类问题解决方法 (a)当使用硬件IIC卡死在EV6事件,即卡死在下函数位置: while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 比如使用硬件IIC驱动EEPROM时有时会卡死在该位置,这是因为在判断EV6事件的时候,EEPROM此时还在写入数据,我们发送了地址过去,但并没有产生应答。 可在判断EV6事件之前加一个延时解决该问题,如下图: 或适当降低IIC的通信速度,该问题一般是由于从设备不够快造成的,可在配置IIC主机的时候总线速度适当降低一些或在各个读写操作中加入一定的延时。
(b)关于使用硬件IIC进行连续读写配置时,注意for循环中不要加打印,否则可能导致通信异常。
(c)关于使用硬件IIC卡在while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); 该问题可检测一下总线空闲时是否都为高电平,其次注意一下外设的使能,先使能开启GPIO时钟,再开启IIC时钟,最后再配置IIC,注意顺序。 当卡死在while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));时同样可按照上述方法进行检查。
(d)关于模拟IIC通信,其通信速率主要跟函数中延时函数有关,在保证通信正常的情况下,可以尽量缩短信号电平变化之间的延时。
(e)关于EVT例程中接收模式下I2C1->CTLR1 &= I2C1->CTLR1;的操作说明 该操作是为了清除停止事件标志位,具体说明如下图:
附件为相关例程,可以下载参考一下。
|