STM32的推挽输出与开漏输出,及模拟IIC驱动MLX90615失败问题总结

一、前言

        前一阵子笔者在调试一款非接触式温度传感器的时候发现了一个有趣的问题——如果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寄存器
}

 

  • 9
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是使用 STM32 的 I2C 接口与 MLX90615 进行通信的示例代码: ```c #include "stm32f10x.h" // 定义 MLX90615 的 I2C 地址 #define MLX90615_I2C_ADDRESS 0x5A // 定义 I2C 发送和接收缓存的大小 #define I2C_BUFFER_SIZE 2 // 定义 I2C 等待超时时间 #define I2C_TIMEOUT 1000 // 初始化 I2C 接口 void I2C_Init(void) { // 使能 I2C1 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 配置 I2C1 引脚 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_Init(GPIOB, &GPIO_InitStructure); // 配置 I2C1 I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed = 100000; I2C_Init(I2C1, &I2C_InitStructure); // 使能 I2C1 I2C_Cmd(I2C1, ENABLE); } // 发送 I2C 数据 void I2C_SendData(uint8_t address, uint8_t* data, uint8_t size) { // 等待 I2C 总线空闲 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 生成起始条件 I2C_GenerateSTART(I2C1, ENABLE); // 等待起始条件发送完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 发送设备地址和写命令 I2C_Send7bitAddress(I2C1, address, I2C_Direction_Transmitter); // 等待设备地址发送完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 发送数据 for(uint8_t i = 0; i < size; i++) { I2C_SendData(I2C1, data[i]); // 等待数据发送完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } // 生成停止条件 I2C_GenerateSTOP(I2C1, ENABLE); } // 接收 I2C 数据 void I2C_ReceiveData(uint8_t address, uint8_t* data, uint8_t size) { // 等待 I2C 总线空闲 while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 生成起始条件 I2C_GenerateSTART(I2C1, ENABLE); // 等待起始条件发送完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 发送设备地址和读命令 I2C_Send7bitAddress(I2C1, address, I2C_Direction_Receiver); // 等待设备地址发送完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // 接收数据 for(uint8_t i = 0; i < size; i++) { // 等待数据接收完成 while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); data[i] = I2C_ReceiveData(I2C1); } // 生成停止条件 I2C_GenerateSTOP(I2C1, ENABLE); } int main(void) { // 初始化 I2C 接口 I2C_Init(); // 读取 MLX90615 的温度数据 uint8_t buffer[I2C_BUFFER_SIZE]; buffer[0] = 0x07; I2C_SendData(MLX90615_I2C_ADDRESS, buffer, 1); I2C_ReceiveData(MLX90615_I2C_ADDRESS, buffer, 2); // 计算温度 int16_t temperature = ((buffer[1] << 8) | buffer[0]) * 0.02 - 273.15; while(1) { // 此处可添加其他代码 } } ``` 以上代码仅为示例,实际应用中还需要根据具体情况进行适当修改。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值