STM32学习笔记:第十二天的实践与总结

I2C通信

一、存储器

按存储介质特性主要分为“易失性存储器”和“非易失性存储器”,主要代表是内存和硬盘。

  • 易失性存储器
    又称RAM,数据被读取或写入所需的时间与这段信息所在的位置无关
  • DRAM
    需要定时刷新保证数据的正确
  • SDRAM(同步DRAM)
  • DDR SDRAM(双倍数据率SDRAM)
  • DDRII SDRAM
  • DDRIII SDRAM
  • SRAM
    以锁存器电路存储数据,不需要定时刷新,存取所读较DRAM快。SRAM一般只用于CPU内部的高速缓存(Cache),外部扩展内存一般使用DRAM。
  • 非易失性存储器
  • ROM
  • MASK ROM(数据在其出厂时就固化,不可修改)
  • PROM
  • OTPROM(一次性可编程)
  • EPROM(可重复擦写的存储器)
  • EEPROM(电可重复擦写的存储器,主要的ROM芯片都是EEPROM)
  • FLASH
    一般以多个字节为单位,以4096个字节为扇区。擦除次数有限
  • NOR FLASH
    地址线与数据线独立分开
  • NAND FLASH
    地址线与数据线共用
  • 硬盘
  • 二、I2C协议

    2.1 I2C总线基本概念

    I2C是一种多主从机、两线制、低速串行通信总线,广泛用于微控制器和各种外围设备之间的通信。使用两条线路:串行数据线(SDA)和串行时钟线(SCL)进行双向传输

  • 特点:时钟速率可变连接简单地址分配阻塞传输冲突检测低功耗

  • 通信方式:同步串行,每个字节由8位数据构成,加上开始、停止条件以及可选的应答位,支持多种速率

  • 工作原理
    通信由主设备在SDA线上生成特定的信号模式来开始和结束,每次通信开始时主设备发送一个地址帧来指定与之通信的从设备。主设备控制时钟信号,向从设备发送或接收数据,通过应答位告知数据是否被成功接收。每个设备的地址是唯一的,在多主模式下通过仲裁机制决定哪个设备获得控制权。由于所有I2C设备的SCL连在一起,SDA连在一起,为避免总线没协调好导致电源短路设备的SCL和SDA均要配置为开漏输出模式,I2C禁止所有设备输出强上拉高电平,为避免高电平造成引脚浮空需要在SDA和SCL总线上各外置一个上拉电阻,使用若上拉电阻。同时会有一个“线与”现象,只要有任意一个或多个设备输出低电平则总线处于低电平,仅当所有设备输出高电平时总线处于高电平,这样可执行多主机模式下的时钟同步和总线仲裁。
    I2C硬件

  • I2C数据传输流程
    数据信号以8位的序列传输,在特殊的开始条件发生后,就会出现第一个8位序列,它指示了数据被发送到哪个从设备的地址,每个8位序列之后都会跟随一个称为确认的位。
    I2C单字节
    在大多数情况下,第一个确认位之后会跟着另一个寻址序列,但这次是针对从设备的内部寄存器。在寻址序列之后是数据序列,直到数据完全传输完毕,并以一个特殊的停止条件结束。
    I2C帧结构

  • 时序基本单元
    SDA和SCL都处于高电平时,总线处于空闲状态。起始和终止都是由主机产生。

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平
    I2C时序基本单元
  • 发送一个字节
    SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机在SCL高电平期间读取数据位,因此SCL高电平期间SDA不允许有数据变化,循环八次即发送一个字节。
    I2C发送一个字节
  • 接收一个字节
    SCL低电平期间,从机将数据位放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,循环8次即接收一个字节(为了防止主机干扰从机写入数据,在接收之前需要释放SDA即转换为接收模式)
    I2C接受一个字节
  • 应答
    在完成发送或接收一个字节后,下一个时钟是一位应答位,0表示应答,1表示非应答。
    I2C应答
  • I2C时序
    I2C完整时序,主要有指定地址写指定地址读当前地址读。先把从设备都确定一个唯一的设备地址(该地址由芯片手册查询,一般器件地址的最后几位可以在电路中改变以允许使用同种芯片模块),主机在起始条件之后先发送一个从机地址(7位地址和10位地址),0表示写入,1表示读取。

  • 指定地址写
    I2C指定地址写
    通过对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)。

  • 当前地址读
    I2C当前地址读
    对于指定设备(Slave Address),在当前地址指针(此时主机交换SDA控制权,接收从机SDA发送的数据,从机所有的寄存器都被分配到了一个线性区域中,指针默认指向0地址,在每写入或读出一个字节后指针自增一次)指示的地址下,读取从机数据(Data)。

  • 指定地址读 I2C指定地址读
    对于指定设备(Slave Address)中指定地址(Reg Address)下,读取从机数据(Data)。在发送完从设备地址之后的写操作是为了将指定地址写入到指针中,然后重新寻址并且指定读写标志位进行当前地址读操作。

  • 三、MPU6050

    MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景。3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。16位ADC采集传感器的模拟信号,量化范围:-32768~32767,加速度计满量程选择:±2、±4、±8、±16(g),陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec),可配置滤波器、时钟源以及采用分频。
    MPU6050硬件电路

    四、软件I2C

    #include "stm32f4xx.h"                  // Device header
    #include "Delay.h"
    
    //软件I2C,SDA和SCL引脚根据需要配置
    //GPIO全在AHB1总线,这里选择PD2作SCL和PD3作SDA
    
    /*封装I2C引脚电平函数*/
    void MyI2C_W_SCL(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOD, GPIO_Pin_2, (BitAction)BitValue);
    	Delay_us(10);
    }
    void MyI2C_W_SDA(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOD, GPIO_Pin_3, (BitAction)BitValue);
    	Delay_us(10);
    }
    uint8_t MyI2C_R_SDA()
    {
    	uint8_t BitValue = GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_3);//主机读取SDA引脚电平
    	Delay_us(10);
    	return BitValue;
    }
    
    /*I2C初始化函数,SDA和SCL同时为高电平*/
    void MyI2C_Init()
    {
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
    	GPIO_Init(GPIOD, &GPIO_InitStructure);
    	
    	GPIO_SetBits(GPIOD, GPIO_Pin_2 | GPIO_Pin_3);//I2C处于空闲状态
    }
    /*I2C起始条件*/
    void MyI2C_Start()
    {
    	//在SCL高电平期间,SDA从高电平切换到低电平,先拉低SDA再拉低SCL
    	MyI2C_W_SDA(1);//先释放SDA
    	MyI2C_W_SCL(1);
    	MyI2C_W_SDA(0);
    	MyI2C_W_SCL(0);
    }
    /*I2C终止条件*/
    void MyI2C_End()
    {
    	//在SCL高电平期间,SDA从低电平切换到高电平,先拉高SCL再拉高SDA
    	MyI2C_W_SDA(0);
    	MyI2C_W_SCL(1);
    	MyI2C_W_SDA(1);
    }
    /*发送一个字节*/
    void MyI2C_SendByte(uint8_t Byte)
    {
    	//在SCL低电平期间,主机将数据依次放到SDA线上(高位先行)然后释放SCL
    	for(uint8_t i = 0; i < 8; i ++)
    	{
    		MyI2C_W_SDA(Byte & (0x80 >> i));//通过按位与将数据以高位先行的顺序写传输
    		MyI2C_W_SCL(1);//从机在SCL高电平期间读取数据位
    		MyI2C_W_SCL(0);//保证SCL以低电平结束确保下次数据传输
    	}
    }
    /*接收一个字节*/
    uint8_t MyI2C_ReceiveByte()
    {
    	//SCL低电平期间,从机将数据依次放到SDA线上(高位先行)然后释放SCL
    	
    	uint8_t Byte = 0x00;
    	MyI2C_W_SDA(1);//为了防止主机干扰从机写入数据,需要主机释放SDA即转换为接收模式
    	for(uint8_t i = 0; i < 8; i ++)
    	{
    		MyI2C_W_SCL(1);//主机在SCL高电平期间读取数据位
    		if(MyI2C_R_SDA() == 1) Byte |= (0x80 >> i);
    		MyI2C_W_SCL(0);
    	}
    	return Byte;
    }
    /*发送应答*/
    //主机在接收完一个字节之后,在下一个时钟发送一位数据,0表示应答,1表示非应答
    void MyI2C_SendAck(uint8_t AckBit)
    {
    	MyI2C_W_SDA(AckBit);
    	MyI2C_W_SCL(1);//从机在SCL高电平期间读取应答位
    	MyI2C_W_SCL(0);
    }
    
    
    /*接收应答*/
    //主机在发送完一个字节之后,在下一个时钟接收一位数据以判断从机是否应答,0表示应答,1表示非应答
    uint8_t MyI2C_ReceiveAck()
    { 
    	uint8_t AckBit;
    	MyI2C_W_SDA(1);//主机在接受之前要释放SDA,切换到接收状态
    	MyI2C_W_SCL(1);//主机在SCL高电平期间读取应答位
    	AckBit = MyI2C_R_SDA();
    	MyI2C_W_SCL(0);
    	return AckBit;
    }
    
    
    
    
    
    
    
    

    作者:佑我中华

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习笔记:第十二天的实践与总结

    发表回复