I2C调试
协议规定,SCL时钟和SDA数据线都必须是双向开漏结构的,通过总线上的上拉电阻拉到逻辑高电平,空闲状态SCL和SDA应该被上拉到高电平,这样的结构可以实现线与的操作。 一般情况下I2C的SDA只有在SCL为低电平的时候才能改变,为高电平的时候需要保持。对应到芯片设计上则是上升沿采样,下降沿变化。两个例外情况由主机发出的总线起始条件START(SCL为高时SDA由高变低)和停止条件STOP(SCL为高时SDA由低变高)
MCU做主机SCL一直拉低 是因为SDA被从机锁死,主机拉低SCL想要控制SDA,导致的,加个超时错误,重新IIC失能就可以不复位MCU解决
SDA被拉低: 主机会发送9个clk尝试拉高SDA,如果不能成功,需要硬件复位 SCL被拉低: 主机主动拉低SCL是合理的行为,在IP初始化配置的时候,可以选择时钟展频功能。 如下图在从机访问地址与写入数据之间加入一点延时,就会出现SCL被主机拉低的情况
其它情况的SCL拉低就要考虑代码实现是否合理了: 1、I2C是否被意外关闭了 2、是否在某个位置有while等待,一直无法跳出循环
**线与&**结构,是I2C总线设计上最关键的特征,用了这种结构才能实现:
- 多主机仲裁同步
- 慢从机同步快主机
因为这个特性,只要总线上任何一个器件拉低了SDA或者SCL,其他器件都无法拉高它们,看到的都是低电平。如果有器件不释放总线,则整个总线上的通讯都会被暂停,我们成为I2C bus hangs:I2C总线挂死
因为I2C主机一般是可编程的器件,受我们控制,如果主机主动拉低了总线,我们可以通过调试代码了解原因,也可以很方便的通过复位I2C外设或者复位芯片来退出这种状态。而I2C从机往往不带RESET引脚,如果挂死了总线即使整个系统复位都无法解除,仅重新上下电才可以恢复。很多系统上是不可接受的,因此我们需要更加小心的处理I2C从机挂死的情况,下面分析也是针对I2C从机挂死来写的。
STM32手动发送I2C数据
//========【配置IIC总线的信号读写和时序】=======
//主机拉高SCL
#define TM1650_IIC_SCL_HIGH GPIO_SetBits(TM1650_CLK_PORT,TM1650_CLK_PIN)
//主机拉低SCL
#define TM1650_IIC_SCL_LOW GPIO_ResetBits(TM1650_CLK_PORT,TM1650_CLK_PIN)
//主机拉高SDA
#define TM1650_IIC_SDA_HIGH GPIO_SetBits(TM1650_DIN_PORT,TM1650_DIN_PIN)
//主机拉低SDA
#define TM1650_IIC_SDA_LOW GPIO_ResetBits(TM1650_DIN_PORT,TM1650_DIN_PIN)
//参数b为0时主机拉低SDA,非0则拉高SDA
#define TM1650_IIC_SDA_WR(b) do{ \
if(b) GPIO_SetBits(TM1650_DIN_PORT,TM1650_DIN_PIN); \
else GPIO_ResetBits(TM1650_DIN_PORT,TM1650_DIN_PIN); \
}while(0)
//主机读取SDA线电平状态,返回值为0为低电平,非0则为高电平
#define TM1650_IIC_SDA_RD() GPIO_ReadInputDataBit(TM1650_DIN_PORT,TM1650_DIN_PIN)
//软件延时2us
#define TM1650_IIC_DELAY_2US do{for(int i=0;i<11;i++);}while(0)
//软件延时4us
#define TM1650_IIC_DELAY_4US do{for(int i=0;i<21;i++);}while(0)
//================================
static void TM1650_IIC_start(void)
{
TM1650_IIC_SCL_HIGH; //SCL=1
TM1650_IIC_SDA_HIGH; //SDA=1
TM1650_IIC_DELAY_4US;
TM1650_IIC_SDA_LOW; //SDA=0
TM1650_IIC_DELAY_4US;
TM1650_IIC_SCL_LOW; //SCL=0
}
//产生IIC总线结束信号
static void TM1650_IIC_stop(void)
{
TM1650_IIC_SCL_LOW; //SCL=0
TM1650_IIC_SDA_LOW; //SDA=0
TM1650_IIC_DELAY_4US;
TM1650_IIC_SCL_HIGH; //SCL=1
TM1650_IIC_DELAY_4US;
TM1650_IIC_SDA_HIGH; //SDA=1
}
//通过IIC总线发送一个字节
static void TM1650_IIC_write_byte(uint8_t dat)
{
uint8_t i;
for(i=0;i<8;i++)
{
TM1650_IIC_SCL_LOW;
if(dat&0x01)
{
TM1650_IIC_SDA_HIGH;
}
else
{
TM1650_IIC_SDA_LOW;
}
TM1650_IIC_SCL_HIGH;
TM1650_IIC_DELAY_2US;
dat>>=1;
}
}
//通过IIC总线接收从机响应的ACK信号
static uint8_t TM1650_IIC_wait_ack(void)
{
uint8_t ack_signal = 0;
TM1650_IIC_SDA_HIGH; //SDA=1
TM1650_IIC_DELAY_2US;
TM1650_IIC_SCL_HIGH;
TM1650_IIC_DELAY_2US;
if(TM1650_IIC_SDA_RD()) ack_signal = 1; //如果读取到的是NACK信号
TM1650_IIC_SCL_LOW;
TM1650_IIC_DELAY_2US;
return ack_signal;
}