STM32的IIC应用详解3

发布者:EnchantedWish最新更新时间:2018-09-11 来源: eefocus关键字:STM32  IIC应用 手机看文章 扫描二维码
随时随地手机看文章

这两天将STM32的IIC按照原子哥的程序,大致走了一遍,多少对IIC不是那么地陌生了,也多少有了自己的一些感悟,在这里,将这两天的学习的一个大致步骤总结下,一来可以让自己形成一个清晰地思路,二来,希望能给一些新手一点启发。

  首先IIC是一种通信协议,通信方式相对比较简单,主要有两条线,SDA,SCL。SDA是串行数据线,上面走命令和数据,而SCL只是一条时钟线,其保证数据是按照时钟节拍来进行传输。IIC上面可以外挂很多的IIC芯片,每一个芯片对应着不同的地址,通过地址来将不同的芯片进行分开,保证不同芯片之间的数据传输,由于每一个芯片都是可以独立地收发,故,每一个芯片都是主机/从机。接下来,就是数据的传输过程了,

  大致的一个数据传输流程是:主机向SDA线上发送一个起始信号,表示有信号进行传输,此时所有连接到IIC总线上的芯片都处于接收状态,接下来,主机发送想要与其进行数据传输的从机地址信号,所有的从机都会接收到该地址信号并和自己固有的地址信号进行匹配,当配对成功时,接下来就在时钟信号的带动下进行数据传输,数据的传输是按照每8位一个单元进行数据的传输。每一位的传输过程中,在SCL高电平期间,一定要保证SDA数值的稳定,否则会出现出错的情况,SDA数值的改变发生在SCL的低电平期间。最终8位全部传输完毕,从机产生一个应答信号给主机,主机在接收到该应答信号后决定接下来是发送一组新的数据还是终止发送。

  大致按照传输的一个流程和模拟IIC的源码来对数据传输过程进行分析:

分析之前,将一些定义告诉大家,免得有疑问:

//I/O方向设置

#define SDA_OUT() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 3<<28;} //0011 通用推挽输出,50M

#define SDA_IN() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 8<<28;} //1000 上拉/下拉输入

 

//I/O操作函数

#define IIC_SCL PBout(6)

#define IIC_SDA PBout(7)

#define READ_SDA PBin(7)

 

其中SDA_IN()、 SDA_OUT()设置了I/O口的方向,也就是SDA是输入那?还是输出那?

IIC_SCL   是SCL输出管脚

IIC_SDA   是SDA输出管脚

READ_SDA  读取SDA输入引脚,等待应答信号时要用。

 

OK!接下来就磨刀霍霍,开始分析数据传输过程

1、起始信号

首先,SDA、SCL线默认是高,表示总线处于空闲状态,接着SDA线被主机拉低,表示主机有信号进行传输,要么是发数据,或者是要进行读数据,当SDA线拉低之后,SCL线也同样被拉低,准备接下来的数据传输。用了原子哥的模拟IIC,此开始信号的产生如下:

声明:此时将CPU当做了主机,而AT24C02当做了从机。

//起始信号

void IIC_Start(void)

{

SDA_OUT(); //设为主机输出

IIC_SDA = 1;

IIC_SCL = 1;

delay_us(4);

IIC_SDA = 0; //scl为高电平时,SDA出现低电平跳变,表示传输开始

delay_us(5);

IIC_SCL = 0;    //钳位SCL,方便接下来进行数据传输

 

}

此程序产生的波形就是上面起始信号所对应的波形。

2、终止信号

终止信号:就是在SCL为高电平时,SDC出现一个上升沿的跳变,即表示终止信号

源程序如下:

//终止信号

void IIC_Stop(void)

{

SDA_OUT();    //设为主机输出

IIC_SDA = 0;

IIC_SCL = 1;

delay_us(2);   //scl为高电平,SDA出现高电平跳变,表示传输结束

IIC_SDA = 1;

 

}

3、等待应答信号

  主机将一组数据发送完毕,接下来就进入等待从机发送応答信号的到来,从机会在第9个时钟信号时,发送该应答信号,此应答信号就是将SDA信号线拉低,而又由于SDA、SCL为开漏输出,故支持线与的形式,当从机那边将SDA线拉低,而主机这边依旧是将SDA线置高,主机通过判断SDA线的高低电平就可以知道是否收到了该应答信号,当然,接收到该应答信号的前提是,使能了从机的应答功能,否则接受不到,之能通过每组发送完毕的一个长延时来保证从机接收数据完毕。

//等待应答信号到来

//返回值:1,接收应答失败

//        0,接收应答成功

u8 IIC_Wait_Ack(void)

{

u16 Errtime=0;

SDA_IN(); //此时设置为输入

IIC_SCL = 1;

IIC_SDA = 1; //线与的关系

if(READ_SDA)

{

Errtime++;

if(Errtime > 250)

{

IIC_Stop();

return 1;

}

}

else

IIC_SCL = 0; //钳位,方便下次传输

return 0;

}

程序中需要注意的地方是,虽然设置了GPIO的方向为输入,但是GPIO的电平还是可以设置的,没有矛盾,GPIO方向由CRL  CRH来决定,输出电平由ODR寄存器来决定

 

4、产生应答信号

如上所说,从机可以选择产生应答信号,同样也可以选择不产生应答信号。

产生应答辛号就是从机将SDA线拉低,不产生应答辛号就是SDA线一直保持高电平

//产生应答信号

void IIC_Ack(void)

{

SDA_OUT();  //此时,相当于主机在接收数据,是被动方

IIC_SCL = 0;

IIC_SDA = 0;

IIC_SCL = 1;

delay_us(2);

IIC_SCL = 0;

}

需要注意的是,此时,CUP相当于是从机,故I/O方向为输出

5、不产生应答信号

//不产生应答信号

void IIC_NAck(void)

{

SDA_OUT();  //此时,相当于主机在接收数据,是被动方

IIC_SCL = 0;

IIC_SDA = 1;

IIC_SCL = 1;

delay_us(2);

IIC_SCL = 0;

 

}

道理同上

 

6、写一个字节

按照该时序图就可以写数据了,流程如下:

 主机:起始信号----->第一位------>第二位--------->   ........    -------->第八位--------->   等待应答信号------->停止信号

SDA数据的有效性,在上面已经分析过,就是在SCL高电平期间,SDA数据不能改变,这样才是一个有效数据,否则无效

      SDA只能在SCL低电平期间改变

//发送一个字节数据

void IIC_Send_Byte(u8 txd)

{

u8 i;

SDA_OUT();

IIC_SCL=0;//拉低时钟开始数据传输

for(i=0;i<8;i++)

{

if(txd & 0x80)

IIC_SDA = 1;

else

IIC_SDA = 0;

txd <<= 1;

delay_us(2);

IIC_SCL = 1;

delay_us(2);

IIC_SCL = 0;

delay_us(2);

}

高位先发,程序中严格保证了SDA的有效性

7、读一个字节

此时CPU相当于是从机,而芯片才是主机,主机在发数据,从机在接受

//读一个字节数据   ack = 1 时,产生应答    ack = 0  ,不产生应答

u8 IIC_Read_Byte(unsigned char ack)

{

unsigned char i,receive=0;

SDA_IN();

for(i=0;i<8;i++)

{

IIC_SCL = 0;

delay_us(2);

IIC_SCL = 1;   //高电平时数据已经稳定,故进行数据的读取

receive <<= 1;

if(READ_SDA)

receive++;

}

if(ack)

IIC_Ack();

else

IIC_NAck();

return receive;

}

以上就是模拟IIC的函数

 

接下来就要针对AT24C02来进行程序的编写

1、在AT24C02指定地址读出一个数据

先来看AT24C02的随机读数据的时序图

此时序图上需要注意两个地方,箭头所标记的两次开始信号,以及椭圆圈起来的写信号和读信号,接下来就分析该过程

首先,发送一个开始信号,接下来发送器件的地址,注意最后一位为Write标志位(0),等待应答,然后发送要读的地址,等待应答,又要发送一个起始信号,发送器件的地址,注意此时,最后一位为Read标志位(1),等待应答,接下来数据就可以读回来了。

器件的地址:

地址按照这个来进行设置,AT24CXX,这个XX即表示存储量XX(K),很显然AT24C02为2K,有256Byte,A2,A1,A0定义看芯片,如下

 

故,AT24C02的写地址为0XA0,读地址为0XA1,到这里,任督二脉已打通,接下来,上源码

//在AT24CXX指定地址读出一个数据

//ReadAddr:开始读数的地址   0-255

//返回值  :读到的数据

u8 AT24C02_ReadOneByte(u8 ReadAddr)

{

u8 temp;

 

IIC_Start();

IIC_Send_Byte(0XA0); //发送器件地址

IIC_Wait_Ack();

IIC_Send_Byte(ReadAddr); //发送首地址

IIC_Wait_Ack();

 

IIC_Start();     //注意,这个需要一个新的起始信号

IIC_Send_Byte(0XA1);

IIC_Wait_Ack();

 

temp = IIC_Read_Byte(0); //不产生应答

IIC_Stop();

return temp;

}

2、在AT24C02指定地址写入一个数据

有了上面读的分析,这里应该可以看懂,注意箭头那位就好,严格按照这个流程来就好了

//在AT24CXX指定地址写入一个数据

//WriteAddr  :写入数据的目的地址    

//DataToWrite:要写入的数据

void AT24C02_WriteOneByte(u8 WriteAddr,u8 DataToWrite)

{

IIC_Start();

IIC_Send_Byte(0XA0); //发送器件地址

IIC_Wait_Ack();

IIC_Send_Byte(WriteAddr); //发送首地址

IIC_Wait_Ack();

IIC_Send_Byte(DataToWrite);

IIC_Wait_Ack();

IIC_Stop();

 

delay_ms(10);  //这个延时是必须的,保证下次发送之前,上次的数据已经被接收

}

需要注意的地方是这个延时,红色标记,因为后面要用到这个进行连续的写操作,故这里加一个延时,保证下次写之前,上次的数据已经被完全接收,否则有可能会出错

 

3、在AT24C02里面的指定地址开始写入长度为Len的数据

直接调用上面的在指定位置写数据的函数即可

//在AT24CXX里面的指定地址开始写入长度为Len的数据

//该函数用于写入16bit或者32bit的数据.

//WriteAddr  :开始写入的地址  

//DataToWrite:数据数组首地址

//Len        :要写入数据的长度2,4

void AT24C02_WriteLenByte(u16 WriteAddr,u32 DataToWtie,u8 Len)

{

u8 i;

for(i=0;i

{

AT24C02_WriteOneByte(WriteAddr + i,(DataToWtie >> (8*i)) & 0XFF);

}

 

}

需要注意的是,此时将数据的高位写到了后面地址处,所以接下来读数据的时候,要从后面开始读,免得出错

4、在AT24C02里面的指定地址开始读取长度为Len的数据

同上,直接调用上面在指定位置读取数据的函数即可

//在AT24CXX里面的指定地址开始读取长度为Len的数据

//该函数用于读取16bit或者32bit的数据.

//WriteAddr  :开始写入的地址  

//Len        :要写入数据的长度2,4

u32 AT24C02_ReadLenByte(u16 WriteAddr,u8 Len)

{

u8 i;

u32 temp=0;

for(i=0;i

{

temp <<= 8; 

temp += AT24C02_ReadOneByte(WriteAddr + Len - i - 1);

}

return temp;

}

注意,先读的是后面的数据

5、校验IIC芯片是否在,是否正常

通过IIC芯片的最后一个地址255,写入数据0X55,当能读回这个数据时,说明芯片工作正常,否则不正常

源码如下

//检查AT24C02是否正常

//这里用了2402的最后一个地址(255)来存储标志字.0x55

//返回1:检测失败

//返回0:检测成功

u8 AT24C02_Check(void)

{

u8 temp=0;

temp = AT24C02_ReadOneByte(255);

if(temp == 0X55)

{

return 0;

}

else

{

AT24C02_WriteOneByte(255,0X55);

temp = AT24C02_ReadOneByte(255);

if(temp == 0X55)

return 0;

 

}

return 1;

}

上来直接读,可以免得每次都要写,写一次就好了,其他时间都是读她就行

 

6、在AT24C02里面的指定地址开始读出指定个数的数据

 

依旧是调用上面的,不多说了,要饿死了zzzzzzzzz

源码:

//在AT24C02里面的指定地址开始读出指定个数的数据

//ReadAddr :开始读出的地址 对24c02为0~255

//pBuffer  :数据数组首地址

//NumToRead:要读出数据的个数

void AT24C02_Read(u8 ReadAddr,u8 *pBuffer,u8 NumToRead)

{

while(NumToRead)

{

*pBuffer++ = AT24C02_ReadOneByte(ReadAddr++);

NumToRead--;

}

}

7、在AT24C02里面的指定地址开始写指定个数的数据

源码:

//在AT24C02里面的指定地址开始写指定个数的数据

//WriteAddr :开始写数据的地址 对24c02为0~255

//pBuffer  :数据数组首地址

//NumToWrite:要写数据的个数

void AT24C02_Write(u8 WriteAddr,u8 *pBuffer,u8 NumToWrite)

{

while(NumToWrite)

{

AT24C02_WriteOneByte(WriteAddr++,*pBuffer++);

NumToWrite--;

看好DATASHEET是关键,我所获得的有很多都是来自于网络,所以要记得回馈,羊有跪乳之恩,只是当今时代发展太快,都忘记了感恩,心怀感恩,世界是这么美好,希望也能给你一点帮助吧!


关键字:STM32  IIC应用 引用地址:STM32的IIC应用详解3

上一篇:STM32F1与STM32F0在GPIO_TypeDef 寄存器方面的不同
下一篇:STM32的IIC应用详解2

推荐阅读最新更新时间:2024-03-16 16:13

STM32库函数和寄存器的区别
库函数版和寄存器版的系统时钟设置的区别: **1.**库函数的目的是让用户应用的,而寄存器更加原始 库函数的系统时钟是默认设置的,且放在启动文件里。而寄存器版的系统时钟是Stm32_Clock_Init(336,8,2,7);. **2.**库函数的快捷的,但不是每个芯片都有的;寄存器是复杂的,但是每个芯片厂商都有提供系统的寄存器设置信息。 分别打开库函数和寄存器版的I/O口设置: 库函数: RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);gotoh后先通过assert_param();函数检查格式是否正确同时只要是ENABLE,RCC- AHB1ENR |=
[单片机]
<font color='red'>STM32</font>库函数和寄存器的区别
stm32矩阵键盘原理图及程序介绍
STM32F0 系列产品基于超低功耗的 ARM Cortex-M0 处理器内核,整合增强的技术和功能,瞄准超低成本预算的应用。该系列微控制器缩短了采用 8 位和 16 位微控制器的设备与采用 32 位微控制器的设备之间的性能差距,能够在经济型用户终端产品上实现先进且复杂的功能。本文为大家介绍stm32矩阵键盘原理图及程序 stm32矩阵键盘原理图 stm32矩阵键盘程序介绍 主要实现:扫描矩阵键盘,将检测到的数据通过spi 通信发送到数码管显示。 主要步骤: 1:初始化时钟 void RCC_Configuration(void) { //----------使用外部RC晶振----------- RCC_DeIn
[单片机]
<font color='red'>stm32</font>矩阵键盘原理图及程序介绍
STM32为什么需要模块的DeInit()函数
简介:一直有一个疑惑,为什么Stm32的每个模块基本上都有一个DeInit()函数。这个函数是否和Init()函数在功能上重复了。查过一部分资料以后,发现有以下的说法。 在main()函数开始时,不管各模块处于什么状态,先执行该模块的DeInit()操作,然后在程序中较晚的时间或真正需要时再开启相应的模块。这样保证在刚进入调试状态时,调试器能够有充足的时间完成初始化和下载程序的操作。先执行该模块的DeInit()操作的目的是为了关闭哪些上一次操作开启的模块。
[单片机]
浅谈STM32 模数转换器 (ADC)(下)
温度传感器和VRENFINT通道框图 要使用传感器,请执行以下操作: 选择 ADC1_IN16 或 ADC1_IN18 输入通道。 选择一个采样时间,该采样时间要大于数据手册中所指定的最低采样时间。 在 ADC_CCR 寄存器中将 TSVREFE 位置 1,以便将温度传感器从掉电模式中唤醒。 通过将 SWSTART 位置 1(或通过外部触发)开始 ADC 转换 读取 ADC 数据寄存器中生成的 V SENSE 数据 使用以下公式计算温度: 温度(单位为 °C)= {(V SENSE — V 25 ) / Avg_Slope} + 25 其中: — V 25 = 25 °C 时的 V SENSE 值 — Avg
[单片机]
浅谈<font color='red'>STM32</font> 模数转换器 (ADC)(下)
STM32——MDK4与MDK5中对于数据类型的不同
首先我们来看MD4中的对于数据类型的定义: 然后我们跳转到其定义处查看对其的定义: typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char u8; /*首先我们来认识typedef,这是用来为复杂的声明定义简单的别名,也就是说,我们可以用它来给我们的数据类型来进行定义。*/ /* 然后我们再来看之后的unsigned,unsigned用于限定后面的为无符号类型,如果后面不加什么的话,就默认为unsigned int。*/ /*unsigned long 无符号长数据 unsigned char 无符号字符型 u
[单片机]
STM32的GPIO输入输出模式配置
最近在看数据手册的时候,发现STM32的GPIO输入输出模式的配置种类有8种之多(输入和输入各4种): (1)GPIO_Mode_AIN模拟输入 (2)GPIO_Mode_IN_FLOATING浮空输入 (3)GPIO_Mode_IPD下拉输入 (4)GPIO_Mode_IPU上拉输入 (5)GPIO_Mode_Out_OD开漏输出 (6)GPIO_Mode_Out_PP推挽输出 (7)GPIO_Mode_AF_OD复用开漏输出 (8)GPIO_Mode_AF_PP复用推挽输出 我们平时接触的最多的也就是推挽输出、开漏输出、上拉输入这三种,但对于各种模式下IO口的内部电路和典型应用,STM32的数据手册中也未曾做过详细的说明和归纳
[单片机]
STM32高级开发(13)-Ubuntu下的串口助手minicom
在这么长时间里我们在Ubuntu上调试stm32,大家在使用串口的时候是不是一直都是在宿主机上的串口助手中查看串口信息呢?来回切换是不是很麻烦?那么在这篇中我们就来介绍一下在Ubuntu下的串口助手,或者准确点说应该叫串口终端,它就是minicom。 终端与串口助手的区别 在我们正式介绍minicom之前,我们首先来关注一个问题即:终端与串口助手有什么区别?(注意这里的终端不是指Ubuntu的shell指令终端,而是说串口软件终端) 其实如果大家接触过Linux嵌入式开发应该就很明白这其中的差别了,不过鉴于大家可能之前没有接触过Linux嵌入式开发,我们这里就为大家详细的讲解一下他们。 在Linux嵌入式开发中,很多时
[单片机]
<font color='red'>STM32</font>高级开发(13)-Ubuntu下的串口助手minicom
STM32单片机中断详解
中断,在单片机中占有非常重要的地位。代码默认地从上向下执行,遇到条件或者其他语句,会按照指定的地方跳转。而在单片机执行代码的过程中,难免会有一些突发的情况需要处理,这样就会打断当前的代码,待处理完突发情况之后,程序会回到被打断的地方继续执行。 1 EXTI控制器 外部中断/事件控制器(EXTI)管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。 外部信号进入经过1的边沿检测电路,检测是否符合(有2和3的上升沿和下降沿选择寄存器决定),产生信号,然后和4软件
[单片机]
<font color='red'>STM32</font>单片机中断详解
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved