我是靠谱客的博主 知性魔镜,这篇文章主要介绍基于Stm32f103利用模拟iic驱动LM75A温度传感器,现在分享给大家,希望可以做个参考。

这两天一直在搞模拟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文件中编写方便一些

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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口模式不一样,所以我们编写一个引脚模式选择函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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时序图编写程序

启动程序代码

复制代码
1
2
3
4
5
6
7
8
9
10
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位)代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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口模式设置成输入模式!!!

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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位数据发送后,再发送第九位应答位。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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需要设置为输入模式)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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(即不正常工作,则结束本次通讯)。

最后就是结束通讯代码(与启动信号类似)

复制代码
1
2
3
4
5
6
7
8
9
10
11
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字节数据,结合下面解释耐心看一下,对整个步骤有所了解

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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温度传感器内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(76)

评论列表共有 0 条评论

立即
投稿
返回
顶部