一、前言
前一阵子笔者在调试一款非接触式温度传感器的时候发现了一个有趣的问题——如果SCL和SDA写数据的时候都是推挽输出的时候,会导致写失败,而把推挽输出全部替换成开漏输出这个问题就迎刃而解了。这段时间里笔者一直在寻找其中的答案,而笔者找到的答案一般就是1、干扰的问题。2、IIC的速度太快的问题。就这两个答案笔者在接下来调试中证明:的确有关系,但是关系不是很大,并不是主要的原因
二、STM32内部I/O接口的结构
STM32的I/O接口一共有可以有8钟状态,分别为:模拟输入、上拉输入、下拉输入、浮空输入、推挽输出、开漏输出、复用开漏、复用推挽,见图1。
图1:STM32内部I/O结构
在这里笔者重要介绍一下推挽输出和开漏输出
三、推挽输出
推挽输出电路图见图2
图2:推挽输出电路
推挽输出的有一个很大的优点就是输出阻抗小,电流大。(STM32可以直接驱动一个电磁继电器都没有问题)一般该电路作为集成运算放大器的输出部分。但是推挽输出在MCU中有一个特点就是就是给它提供输出电流的电源是VDD,也就是MCU内部的电源。(VCC为电路电压,VDD为芯片工作电压)通俗的说就是:假如你的单片机是3.3V供电,而推挽输出的高电平的电压是略小于3.3V的。
四、开漏输出
开漏输出与开集输出类似,只是一个使用的是三极管(电流型)一个使用的是MOS管(电压型),其实道理是一样的如图3为开漏输出电路,因为MOS管在这里只起到开关作用所以可以直接把MOS管等效为一个开关,见图4
图三:开漏输出电路
图四:等效电路
当内部信号为‘1’时,该电路对外输出高阻态,当加上一个上拉电阻时,电流就通过电路电源VCC通过上拉电阻与负载的地构成回路以达到输出‘1’的目的。这样输出的电流就与上拉电阻的大小有关了,而且可以起到电平的匹配。
五、IIC总线通讯协议问题
笔者调试的器件是一款型号为MLX90615的非接触式温度传感器,使用的是IIC总线。它内部有E²PROM用来储存数据,而用户需要做的就是将其内部E²PROM的数据读出来就可以了。根据该器件提供的数据手册可知:该器件内部自带弱上拉。并在使用IIC功能时总线经过施密特触发器到达内部。也就是说该器件总线上高电平电压是需要等于VDD(器件本身)<VCC≠VDD(MCU内部电压)。故STM32使用推挽输出是不能满足该要求的。而开漏输出具有一个非常显著的特点就是,可以匹配电平,故满足该要求。
图5:器件手册参考电路
当然如果只有以上描述的问题可能还不足以让通讯无法建立,笔者在翻阅该器件的数据手册时发现了一个笔者一直忽略的问题,就是该器件的SCL与SDA总线上内部有钳位二极管,所以需要向该器件的总线提供电源(上拉电阻)使得总线不为负载才可以。这样一来推挽输出的方式就可以彻底的pass掉了。
六、总结
推挽输出说到底相当于两个射随器组成的,它一般可以适用于驱动一些需要电流比较大的负载,例如发光二极管O(∩_∩)O。而开漏输出最大的特点就是可以电平的匹配,适用于电流型的驱动,一般用于总线协议例如IIC,SPI等。笔者学识尚浅,多谢各位前辈指点与指正。
七、附IIC驱动代码
void iic_start(void)
{
MLX_GPIO_out();
SCL=0;
delay_us(1);
SDAo = 1;
delay_us(1);
SCL = 1;
delay_us(2);
SDAo = 0;
delay_us(4);
SDAo = 0;
}
/***********************************************************************************
* Function: iic_stop;
*
* Description: iic通信停止函数;
*
* Input: none;
*
* Output: none;
*
* Return: none;
*
* Note: none;
************************************************************************************/
void iic_stop(void)
{
MLX_GPIO_out();
SCL=0;
delay_us(5);
SDAo=0;
delay_us(5);
SCL=1;
delay_us(5);
SDAo=1;
}
/***********************************************************************************
* Function: iic_ack;
*
* Description: 发送ACK信号函数;
*
* Input: none;
*
* Output: none;
*
* Return: none;
*
* Note: none;
************************************************************************************/
void iic_ack(void)
{
MLX_GPIO_out();
SCL = 0;
SDAo = 0;
delay_us(10);
SCL = 1;
delay_us(10);
SCL=0;
delay_us(10);
}
/***********************************************************************************
* Function: read_ack;
*
* Description: 读取从器件ACK信号函数;
*
* Input: none;
*
* Output: none;
*
* Return: 返回TRUE,地址或数据字节已发送收到ACK,否则没收到;
*
* Note: none;
************************************************************************************/
unsigned char read_ack(void)
{
unsigned char flag;
MLX_GPIO_out();
SCL = 0;
delay_us(1);
SDAo = 1;
SCL = 1;
MLX_GPIO_in();
delay_us(8);
flag = SDAi;
SCL = 0;
//SDA=0;
delay_us(1);
return flag;
}
/***********************************************************************************
* Function: iic_nack;
*
* Description: 发送NACK信号函数;
*
* Input: none;
*
* Output: none;
*
* Return: none;
*
* Note: none;
************************************************************************************/
void iic_nack()
{
MLX_GPIO_out();
SCL=0;
SDAo = 1;
delay_us(1);
SCL = 1;
delay_us(4);
SCL = 0;
delay_us(1);
}
/***********************************************************************************
* Function: get_byte;
*
* Description: 读取字节数据函数;
*
* Input: none;
*
* Output: none;
*
* Return: 字节数据;
*
* Note: none;
************************************************************************************/
unsigned char get_byte(void) //输入一个字节
{
unsigned char dd;
int i;
MLX_GPIO_out();
dd=0;
SDAo = 1;
MLX_GPIO_in();
for (i=0;i<8;i++)
{
SCL = 0;
delay_us(1);
SCL = 1;
delay_us(8);
dd<<=1;
if (SDAi)
dd|=0x01;
SCL=0;
delay_us(1);
}
//SCL = 0;
return(dd);
}
/***********************************************************************************
* Function: out_byte;
*
* Description: 输出字节数据函数;
*
* Input: 字节数据;
*
* Output: none;
*
* Return: none;
*
* Note: none;
************************************************************************************/
void out_byte(unsigned char dd) //输出一个字节
{
unsigned char i;
MLX_GPIO_out();
for(i=0;i<8;i++)
{
SCL = 0;
SDAo = (dd & 0x80)>>7;
delay_us(1);
SCL = 1;
delay_us(8);
dd <<= 1;
SCL=0;
delay_us(1);
}
}
/***********************************************************************************
* Function: iic_write_addr;
*
* Description: 向IIC器件写入地址数据函数;
*
* Input: addr,器件地址;data_addr,数据地址;
*
* Output: none;
*
* Return: 写入成功;
*
* Note: none;
************************************************************************************/
unsigned char iic_write_addr(unsigned char addr,unsigned char data_addr)
{
unsigned char i;
iic_start();
out_byte(addr);
read_ack();
out_byte(data_addr);
i=read_ack();
//iic_stop();
return i;
}
/***********************************************************************************
* Function: IICA_Write;
*
* Description: IIC总线写数据函数;
*
* Input: id,IIC器件识别地址, addr,写数据的起始地址; *p, 写入数据存储的地址指针;len,要写入数据长度;
*
* Output: none;
*
* Return: none;
*
* Note: 只支持AT24C01/02/04/08/16或地址是8位的器件;
************************************************************************************/
void IICA_Write(unsigned char addr, unsigned char commond, unsigned char *p)
{
int i;
unsigned char *Ptr;
unsigned char s1[3];
unsigned char num[6];
unsigned char EA_SAVE = EA;
EA = 0;
num[5]=0; //擦除2个地址字节,写0x00擦除
num[4]=addr<<1; //
num[3]=commond; //
num[2]=0; //Load array arr
num[1]=0; //
num[0]=0; //
s1[0]=0;
s1[1]=0;
s1[2]=ocr_8(num);
Ptr=s1;
iic_write_addr(addr|IIC_WRITE,commond);
for (i=0;i<3;i++)
{
out_byte(*(Ptr++));
read_ack();
delay_ms(5);
}
delay_ms(5);
iic_stop();
delay_ms(5); //擦除2个地址字节,写0x00擦除
num[5]=0; //写2个地址字节
num[4]=addr<<1; //
num[3]=commond; //
num[2]=p[0]; //Load array arr
num[1]=p[1]; //
num[0]=0; //
p[2]=ocr_8(num);
Ptr=p;
iic_write_addr(addr|IIC_WRITE,commond);
for (i=0;i<3;i++)
{
out_byte(*(Ptr++));
read_ack();
delay_ms(5);
}
delay_ms(5);
iic_stop();
delay_ms(5);
EA = EA_SAVE;
}
/***********************************************************************************
* Function: IICA_Read;
*
* Description: IIC总线读数据函数;
*
* Input: id,IIC器件识别地址, addr,读数据的起始地址; *p, 读出数据存储的地址指针;len,要读出数据长度;
*
* Output: none;
*
* Return: none;
*
* Note: 只支持AT24C01/02/04/08/16或地址是8位的器件;
************************************************************************************/
unsigned char IICA_Read(unsigned char addr, unsigned char command, unsigned char *p)
{
int i;
unsigned char crc;
unsigned char num[6];
unsigned char EA_SAVE = EA;
unsigned char *Ptr;
EA = 0;
Ptr=p;
iic_write_addr(addr|IIC_WRITE,command);
iic_start();
out_byte(addr|IIC_READ);
read_ack();
for (i=0;i<3;i++)
{
*(Ptr++) = get_byte();
if (i<2)
iic_ack();
else
iic_nack();
}
iic_stop();
num[5]=addr<<1; //
num[4]=command; //
num[3]=(addr<<1)+1; //Load array arr
num[2]=p[0]; //
num[1]=p[1]; //
num[0]=0;
crc= ocr_8(num);
if(p[2]!=crc)
{
EA = EA_SAVE;
return(0);
}
EA = EA_SAVE;
return 1;
}
unsigned char ocr_8(unsigned char *s)
{
unsigned char crc[6];
unsigned char BitPosition=47;
unsigned char shift;
unsigned char i;
unsigned char j;
unsigned char temp;
do
{
crc[5]=0; //Load CRC value 0x000000000107
crc[4]=0;
crc[3]=0;
crc[2]=0;
crc[1]=0x01;
crc[0]=0x07;
BitPosition=47; //Set maximum bit position at 47
shift=0; //Find first 1 in the transmited message
i=5; //Set highest index
j=0;
while(((s[i]&(0x80>>j))==0)&& (i>0))
{
BitPosition--;
if(j<7){j++;}
else{
j=0x00;
i--;
}
}//End of while /
shift=BitPosition-8; //Get shift value for crc value
//Shift crc value
while(shift)
{
for(i=5; i<0xFF; i--)
{
if((crc[i-1]&0x80) && (i>0))
{temp=1;}
else{temp=0;}
crc[i]<<=1;
crc[i]+=temp;
}//End of for
shift--;
}//End of while
//Exclusive OR between pec and crc
for(i=0; i<6; i++)
{s[i] ^=crc[i];}//End of for
}while(BitPosition>8);//End of do-while
return s[0];
}
/***********************************************************************************/
// 文件结束
/***********************************************************************************/
void MLX_GPIO_in()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//SCL
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;//最高输出速率10MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化外设GPIOx寄存器
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//SDA
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;//最高输出速率10MHz
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
// GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化外设GPIOx寄存器
}
void MLX_GPIO_out()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//SCL
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;//最高输出速率10MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化外设GPIOx寄存器
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//SDA
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;//最高输出速率10MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化外设GPIOx寄存器
}