STM32 IIC LM75B
零、参考资料:
IIC协议图解
韦东山 STM32通信模拟 I2C
韦东山 STM32通信硬件 I2C
51 IIC程序
串行总线I2C
STM32 IIC详解 有动图
软件IIC
一、IIC程序:
0、数据有效性
在SCL高电平的时候采样,也就是有效。低电平的时候切换数据。
1、端口定义和端口初始化
软件IIC的本质就是通过GPIO的输入与输出功能,来模拟实现硬件IIC的功能,按照时序图规定的通信协议进行模拟通信。
//--------------------------------IIC端口定义----------------
#define SCL_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_8)//SCL 置0
#define SCL_Set() GPIO_SetBits(GPIOB,GPIO_Pin_8) //置1
#define SDA_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_9)//SDA 置0
#define SDA_Set() GPIO_SetBits(GPIOB,GPIO_Pin_9) //置1
#define SDA_Read() GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9) //读取IO口外部电平状态
端口初始化,SCL时钟引脚只有输出功能,而SDA数据引脚既有输出功能,又有输入功能。STM32的GPIO的输入输出不能共用,因此需要在SDA引脚在不同输入输出功能时候,分别初始化为推挽输出和浮空输入。
void SCL_GPIO_Init(void) //将SCL引脚配置为推挽输出
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能B端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
GPIO_SetBits(GPIOB,GPIO_Pin_8);
}
void SDA_GPIO_Out_Init(void) //将SDA引脚配置为推挽输出
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能B端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
GPIO_SetBits(GPIOB,GPIO_Pin_9);
}
void SDA_GPIO_Read_Init(void) //将SDA引脚配置为 浮空输入
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能B端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化
GPIO_SetBits(GPIOB,GPIO_Pin_9);
}
void IIC_Init(void) //初始化IIC接口
{
SCL_GPIO_Init();
SDA_GPIO_Out_Init();
//SDA_GPIO_Read_Init();
}
2、IIC总线启动和停止条件
时序图
void IIC_Start(void)
{
SDA_GPIO_Out_Init(); //SDA引脚配置为推挽输出模式
SDA_Set(); //置1
SCL_Set(); //置1
delay_us(6);
SDA_Clr(); //置0
delay_us(6);
SCL_Clr(); //置0
}
void IIC_Stop(void)
{
SDA_GPIO_Out_Init(); //SDA引脚配置为推挽输出模式
SDA_Clr(); //置0
SDA_Set(); //置1
delay_us(6); //延时时间自定义
SDA_Set(); //置1
delay_us(6);
}
3、IIC发送与等待应答
时序图
//-------------------------------主机通过IIC发送应答----------------------------//
void IIC_SendAck(unsigned char ackbit)
{
SDA_GPIO_Out_Init(); //SDA引脚配置为推挽输出模式
SCL_Clr(); //置0 //在SCL为0时改变SDA
if(ackbit ==1) // 0:应答,1:非应答
SDA_Set(); //置1
else if(ackbit == 0)
SDA_Clr(); //置0
delay_us(6);
SCL_Set(); //置1
delay_us(6);
SCL_Clr(); //置0
SDA_Set(); //置1
delay_us(6);
}
//-----------------------------IIC等待从机应答-----------------------------//
unsigned char IIC_WaitAck(void)
{
unsigned char ackbit = 0;
SDA_GPIO_Read_Init(); //将SDA引脚配置为 浮空输入
SCL_Set(); //置1
delay_us(6);
ackbit = SDA_Read();
SCL_Clr(); //置0
delay_us(6);
return ackbit;
}
Notes:
●在开始和停止条件之间从发送机到接收机传输的数据字节数不受限制。
●每个八位数据字节后跟一个应答位。
●应答位是发送器将总线置为高电平,而来自主机的SCL也产生与应答有关的额外时钟脉冲。
●被寻址的从机接收机必须在接收到每个字节后产生一个应答。同样,主机必须在接收到从机发送机中移出的每个字节后产生应答。
●应答的设备必须在应答时钟脉冲期间拉低SDA线,以使SDA线在应答时钟脉冲的高电平期间稳定为低电平。
●主接收机必须通过不在从机时钟输出的最后一个字节上生成应答,向发射机发送数据结束信号。在这种情况下,发送机必须将数据线保持为高电平,以使主机能够产生停止条件。
4、通过I2C总线发送和接收数据
●具体的通信格式和芯片有关,仅介绍发送和接收单个字节。
//-------------------------通过I2C总线发送数据------------------------//
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
SDA_GPIO_Out_Init(); //SDA引脚配置为推挽输出模式
for(i=0; i<8; i++)
{
SCL_Clr(); //置0 //在SCL为0时改变SDA
delay_us(6);
if(byt & 0x80) SDA_Set(); //置1
else SDA_Clr(); //置0
delay_us(6);
SCL_Set(); //置1
byt <<= 1;
delay_us(6);
}
SCL_Clr(); //置0
}
//-----------------------从I2C总线上接收数据-----------------------------//
unsigned char IIC_RecByte(void)
{
unsigned char i, da = 0;
SDA_GPIO_Read_Init(); //将SDA引脚配置为 浮空输入
for(i=0; i<8; i++)
{
SCL_Set(); //置1 //高电平时进行数据读取
delay_us(6);
da <<= 1;
if(SDA_Read()) da |= 1;
SCL_Clr(); //置0
delay_us(6);
}
return da;
}
Notes:
●所有的SDA 信号变化都要在SCL 时钟为低电平时进行,除了开始和结束标志。
●I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
●I2C总线上进行一次数据传输的通信格式,即I2C总线的完整时序。
●当主控器接收数据时,在最后一个数据字节,必须发送一个非应答信号,使受控器释放数据线,以便主控器产生一个停止信号来终止总线的数据传送。
二、应用-LM75B:
unsigned int LM75BD_ReadTemp(void)
{
unsigned char High_temp = 0;
unsigned char Low_temp = 0;
unsigned int temp = 0;
unsigned char ack = 0; //应答信号检查
IIC_Start(); //总线开启
IIC_SendByte(0x90); //发送写命令+设备地址 写操作
ack = IIC_WaitAck(); //等待从机发送应答
if(ack == 1) //从机未产生应答
{
OLED_ShowString(5,30,"ERROR 1 ack",12,1); //OLED显示错误
ack = 0;
}
IIC_SendByte(0x00); //发送寄存器地址 温度寄存器的地址 0x00
ack = IIC_WaitAck();
if(ack == 1)
{
OLED_ShowString(5,40,"ERROR 2 ack",12,1);
ack = 0;
}
IIC_Start(); //再次开启总线 改变传输方向
IIC_SendByte(0x91); //地址字节 读操作
ack = IIC_WaitAck();
if(ack == 1)
{
OLED_ShowString(5,50,"ERROR 3 ack",12,1);
ack = 0;
}
High_temp = IIC_RecByte(); //接收高字节
IIC_SendAck(0); //还未接收完,接收设备主机产生应答
Low_temp = IIC_RecByte(); //接收低字节
IIC_SendAck(1); //已经接收完,接收设备主机产生非应答
IIC_Stop(); //总线停止
temp = ((High_temp<<8) | Low_temp);
return (temp>>5); //低五位 为无效数据,由数据手册可知
}
void LM75BD_Init(void)
{
ui_temperature = LM75BD_ReadTemp();
if( (ui_temperature & 0x8000) == 0x8000) //负数
{
ui_temperature &= 0x7fff;
sprintf((char *)puc_temp_Buf,"temperature:-%7.3f",(float)ui_temperature*0.125);
}
else if( (ui_temperature & 0x8000) == 0) //正数
{
sprintf((char *)puc_temp_Buf,"temperature:%7.3f",(float)ui_temperature*0.125);
}
OLED_ShowString(5,20,puc_temp_Buf,12,1);
}
void LM75BD_Temp_Proc(void)
{
if( ui_LM75BD_flag >= 200) //在定时器中递加,控制温度读取频率
{
ui_LM75BD_flag = 0;
ui_temperature = LM75BD_ReadTemp();
if( (ui_temperature & 0x8000) == 0x8000) //负数
{
ui_temperature &= 0x7fff;
sprintf((char *)puc_temp_Buf,"temperature:-%7.3f",(float)ui_temperature*0.125);
}
else if( (ui_temperature & 0x8000) == 0) //正数
{
sprintf((char *)puc_temp_Buf,"temperature:%7.3f",(float)ui_temperature*0.125);
}
OLED_ShowString(5,20,puc_temp_Buf,12,1);
}
}
三、时序图示波器测试:
●对照时序图,可以在波形图上找出启动和停止条件,以及应答和八个数据传输时钟。