概述
这两天一直在搞模拟iic,模拟iic相较于硬件iic的优势在于更稳定,io口的选择更灵活。
这次编写模拟iic驱动程序还是有点坎坷,其中模拟iic的时序不是难点,直接说我遇到的问题1.io口模式的选择(一开始我是使用固定的io口模式,这中间也看到有人说io口模式不影响,经过我的实践是会影响的)2.LM75A驱动时序图的理解不到位。
下面分析遇到这两个拦路虎的原因:第一个问题是对硬件电路的一些知识不掌握,没有搞清io口模式具体的含义,第二个问题我认为还是对iic协议的不熟悉。
下面具体说说模拟iic驱动编写的过程(这里和我写的驱动TM1640的地方有异曲同工之处)
首先我们还是要看一下iic通讯协议的时序图
这张图就很清楚明白了,我们可以把上图分解成几个部分:启动信号部分、8位数据发送(数据接受与发送其本质是一样的)部分、应答(这里应答又可以分为主机发送应答信号,主机接受从机应答信号)部分、停止信号部分。
这个是LM75A中读取temp寄存器的步骤,这里解释一下器件应答和主机应答,器件应答为从机向主机发送应答信号0(正常工作)、1(非正常工作),而主机应答为主机向从机发送应答信号0(应答)、1(非应答),为什么要解释这一点呢?这是与后面io口SDA脚的初始化有关,因为器件应答需要读取SDA引脚的电平,故要将io口设置为浮空输入,而主机应答需要向从机发送信号,故需要设置成推挽输出(这里建议明白各种io口模式的作用,你可以搞完后设置成其他模式看看能不能正常工作)
下面是.h文件,有一些宏定义,在.c文件中编写方便一些
#ifndef __SOFT_IIC_H
#define __SOFT_IIC_H
#include "headfile.h"
#define SCL GPIO_Pin_6 //GPIOB
#define SDA GPIO_Pin_7
#define SCL_High GPIO_WriteBit(GPIOB,SCL,Bit_SET);
#define SDA_High GPIO_WriteBit(GPIOB,SDA,Bit_SET);
#define SCL_Low GPIO_WriteBit(GPIOB,SCL,Bit_RESET);
#define SDA_Low GPIO_WriteBit(GPIOB,SDA,Bit_RESET);
void Soft_IIC_Mode(uint8_t mode);
void Soft_IIC_Start(void);
void Soft_IIC_End(void);
void Soft_IIC_Ack(void);
void Soft_IIC_NoAck(void);
void Soft_IIC_WaitAck(void);
void Soft_IIC_BasicSend(uint8_t data);
uint8_t Soft_IIC_BasicRead(void);
uint8_t Soft_IIC_ReadByte(uint8_t SlaveAddr,uint8_t ReadAddr);
void Soft_IIC_ReadBuffer(uint8_t SlaveAddr,uint8_t ReadAddr,uint8_t* ReadBuffer,uint8_t Num);
#endif
由于我们上面说到不同部分用到的SDA脚io口模式不一样,所以我们编写一个引脚模式选择函数
void Soft_IIC_Mode(uint8_t mode)
{
switch(mode)
{
case 0: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//输出模式
GPIO_InitTypeDef GPIOB_InitStrcture;
GPIOB_InitStrcture.GPIO_Mode=GPIO_Mode_Out_PP;
GPIOB_InitStrcture.GPIO_Pin= SDA | SCL;
GPIOB_InitStrcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIOB_InitStrcture);break;
case 1: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIOB_InitStrcture.GPIO_Mode=GPIO_Mode_IN_FLOATING;//输入模式
GPIOB_InitStrcture.GPIO_Pin=SDA;
GPIOB_InitStrcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIOB_InitStrcture);break;
}
}
下面我们结合iic时序图编写程序
启动程序代码
void Soft_IIC_Start(void)
{
Soft_IIC_Mode(0);
SDA_High;
SCL_High;
Delay_us(5);//确保信号正确
SDA_Low;
Delay_us(5);//先拉低SDA
SCL_Low;
}
对照上面启动的时序图,为了确保信号的正确,我们先将SDA,SCL都拉高,延时5us是为了信号的稳定(5us应该可以更短,可以查询数据手册修改,提高通讯效率),然后拉低SDA,到这里其实已经发出了启动信号,但是我为了后续发送数据部分,SCL操作对称且好理解,在启动信号后将SCL拉低,这样在后续发送一字时只需改变SDA电平(数据,在SCL为低店电平时才能改变),再将SCL拉高(发送出去)
所以在后面任何部分的代码,你都会发现最后一步操作都是将SCL拉低,这样可以使每部分衔接更加清晰,不会出现SCL混乱的情况。
然后就是发送数据部分(8位)代码
void Soft_IIC_BasicSend(uint8_t data)
{
Soft_IIC_Mode(0);
for(uint8_t i=0;i<8;i++)
{
if(data & 0x80)
{
SDA_High;
Delay_us(5);
}
else
{
SDA_Low;
Delay_us(5);
}
data<<=1;
SCL_High;
Delay_us(5);
SCL_Low;
Delay_us(5);
}
}
这里就把刚刚启动信号完成后将SCL拉低的优势体现出来了,便于理解这部分代码。启动信号之后SCL保持在低电平,进for循环直接可以改变SDA,注意的是:iic中数据的传输都是先传输的高位,故这里用了向左位移,一位一位的从高位将数据传输出去。
一开始说到,发送与读取本质是一样,发送是将SCL拉低后,改变SDA,再拉高SCL发送出去;而读取则是拉低SCL再拉高,当SCL为高电平时,读取SDA的电平(即为从机发送的数据)
代码如下
特别注意的是,要读取器件SDA时,都需要将SDA拉高释放总线,这样才能准确读取从机SDA信号。
并且整个iic中只要涉及读取器件SDA的都需要将SDA的io口模式设置成输入模式!!!
uint8_t Soft_IIC_BasicRead(void)
{
uint8_t BasicRead=0;
Soft_IIC_Mode(1);
SDA_High;//释放SDA总线
for(uint8_t i=0;i<8;i++)
{
SCL_High;
Delay_us(5);
BasicRead<<=1;
if(GPIO_ReadInputDataBit(GPIOB,SDA))
{
BasicRead++;
Delay_us(1);
}
SCL_Low;
Delay_us(5);
}
return BasicRead;
}
接下来就是应答代码
首先是主机应答与不应答(这里是由主机产生的,故SDA仍为输出模式)
主机应答相当于在8位数据发送后,再发送第九位应答位。
void Soft_IIC_Ack(uint8_t Ack)
{
Soft_IIC_Mode(0);
switch(Ack)
{
case 0:SDA_Low;Delay_us(5);break;//0为发送应答信号
case 1:SDA_High;Delay_us(5);break;//1为发送非应答信号
}
SCL_High;
Delay_us(5);
SCL_Low;
}
其次是器件应答(这里是由从机产生的信号,故SDA需要设置为输入模式)
void Soft_IIC_WaitAck(void)
{
uint8_t timer=0;
Soft_IIC_Mode(1);
SDA_High;
SCL_High;//释放总线,由从机发SDA信号
while( GPIO_ReadInputDataBit(GPIOB,SDA) )
{
if(timer++>100)
{
Soft_IIC_End();
}
}
SCL_Low;
}
其中如果从机应答一直发回的是1(即不正常工作,则结束本次通讯)。
最后就是结束通讯代码(与启动信号类似)
void Soft_IIC_End(void)
{
Soft_IIC_Mode(0);
SDA_Low;
SCL_Low;
Delay_us(5);//确保信号正确
SCL_High;
Delay_us(5);
SDA_High;
}
以上就是全部模拟iic部分的代码了,后续驱动LM75A温度传感器只需要调用他们即可,在上一篇中讲了利用硬件iic驱动(基于标准库函数)
接下来讲一下利用模拟iic驱动以及遇到的问题
我们还是只讲一下温度寄存器的读取(不讲读取值处理成实际温度,也很简单,有需要可以参考上一篇)
只需要看图10读取temp寄存器的2字节数据,结合下面解释耐心看一下,对整个步骤有所了解
void Soft_IIC_ReadBuffer(uint8_t SlaveAddr,uint8_t ReadAddr,uint8_t* ReadBuffer,uint8_t Num)
{
Soft_IIC_Start();
Soft_IIC_BasicSend(SlaveAddr);
Soft_IIC_WaitAck();
Soft_IIC_BasicSend(ReadAddr);
Soft_IIC_WaitAck();
Soft_IIC_Start();
Soft_IIC_BasicSend( (SlaveAddr+1) );
Soft_IIC_WaitAck();
while(Num)
{
if(Num == 1)//只剩下最后一个数据时进入 if 语句
{
*ReadBuffer = Soft_IIC_BasicRead();//调用库函数将数据取出到 Buffer
Num--; //字节数减 1
Soft_IIC_Ack(1); //最后有一个数据时关闭应答位
Soft_IIC_End(); //最后一个数据时使能停止位
}
else//读取数据
{
*ReadBuffer = Soft_IIC_BasicRead();//调用库函数将数据取出到 Buffer
ReadBuffer++; //指针移位
Num--; //字节数减 1
Soft_IIC_Ack(0);
}
}
}
上面就是将图10翻译过来的代码,你可能会有一些疑问,从图10可以看出我们的器件地址实际只有7位,但是我们需要发送8位(因为你会发现7位地址后面还有一位决定读写位(0为写,1为读)这里说明其中器件地址SlaveAddr我们默认第八位读写为0(写),所以在下面我们需要读的时候只需要+1第八位读写位就变成了1(这里就是卡了我一天的坑),因为在硬件iic中调用库函数,没有考虑这一点。
这样就完完整整把这个时序图翻译过来了,从而就可以顺利驱动LM75A,亲眼看到数码管上读取的错误值到正确的温度值,这是很欣慰的,其中还经历了很多查错才发现这个问题。
有些东西必须知道它为什么存在(存在即合理),有些人说不必要重复造车轮,我同意这个观点,但是我还是认为知道车轮是怎么造的更好。可能这次写了模拟iic的代码后,之后都是Ctrl+C了,用起来心里也有底些。。
其次很多关于iic通讯的专业术语我可能表达的并不准确,如有错误,敬请指正,在错误中进步嘛!
最后
以上就是知性魔镜为你收集整理的基于Stm32f103利用模拟iic驱动LM75A温度传感器的全部内容,希望文章能够帮你解决基于Stm32f103利用模拟iic驱动LM75A温度传感器所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复