• STM32单片机——IIC概念与协议软件模及固件库配置


    IC2协议简介

    I2C 是很常见的一种总线协议,I2C 是飞利浦公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。IIC属于半双工同步通信方式。I2C 使用两条线在主控制器和从机之间进行数据通信。

    I2C协议总线构成

    I2C 总线在物理连接上非常简单,分别由SDA(双向串行数据线)SCL(串行时钟线)上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信。每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备直接的访问。下图是I2C总线系统结构具体电路原理图。

    I2C总线物理层特点

    • 总线通过上拉电阻接到电源。当IIC设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态,由上拉电阻把总线拉成高电平
    • 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定哪个设备占用总线。
    • 具有三种传输模式:标准模式传输速率为100kbit/s,快速模式为400kbit/s,高速模式下可达3.4M/s,但目前大多IIC设备尚不支持高速模式。

    软硬件I2C比较

    硬件IIC:对应芯片上的IIC外设,有相对应的IIC驱动电路,其所使用的IIC管教也是专用的;
    软件IIC:一般是用GPIO管教,用软件控制管脚状态以及模拟IIC通信波形;
    区别
     1. 硬件IIC的效率要远高于软件的,而软件IIC不受引脚限制,接口比较灵活。
     2. 软件IIC是通过GPIO,软件模拟寄存器的工作方式,而硬件IIC是直接调用内部寄存器进行配置。

    如何区分?
     1.硬件IIC用法复杂,模拟IIC流程更加清楚
     2.硬件IIC速度比模拟快,并且可以用DMA
     3.模拟IIC可以在任何管脚上,硬件IIC在固定管脚上

    I2C协议通信方式

    • 宏定义
      #define  SCLK_Set()    GPIO_SetBits(GPIOB, GPIO_Pin_0)  			//PB0(SCL)输出高
      #define  SCLK_CLr()    GPIO_ResetBits(GPIOB, GPIO_Pin_0)			//PB0(SCL)输出低
      #define  SDIN_Set()    GPIO_SetBits(GPIOB, GPIO_Pin_1)  			//PB1(SDA)输出高
      #define  SDIN_CLr()    GPIO_ResetBits(GPIOB, GPIO_Pin_1) 			//PB1(SDA)输出低
      #define  READ_SDIN()   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) 	//读取PB1(SDA)电平
      
      #define IIC_ACK  0 		 //应答
      #define IIC_NO_ACK  1 	 //不应答
      
      //对应模拟I2C引脚IO口初始化
      static void OLED_GPIO_Init(void)
      {
      	GPIO_InitTypeDef GPIO_InitStructure;					//定义一个GPIO_InitTypeDef类型的结构体
      	
      	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);	//打开GPIOC的外设时钟
      	
      	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;	//选择控制的引脚
      	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;		//设置为通用开漏输出
      	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//设置输出速率为50MHz
      	GPIO_Init(GPIOB,&GPIO_InitStructure);					//调用库函数初始化GPIOA
      	
      	SCLK_Set();	//设PC2(SCL)为高电平
      	SDIN_Set();	//设PC3(SDA)为高电平
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    空闲状态

    • IIC总线的SDASCL两条信号线同时处于高电平时,规定位总线的空闲状态

    开始信号

    • SCL高电平期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
      static void IIC_Start(void)
      {
      	SCLK_Set();	//时钟线置高
      	SDIN_Set();	//数据线置高
      	delay_us(1);
      	SDIN_CLr();	//数据线置低,数据发送从高到低跳变
      	delay_us(1);
      	SCLK_CLr();	//时钟线置低
      	delay_us(1);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    停止信号

    • SCL高电平期间,SDA由低到高的跳变;停止信号也是一种高电平跳变时序信号,而不是一个电平信号。起始信号和停止信号一般由主机产生
      在这里插入图片描述
      static void OLED_IIC_Stop(void)
      {
      	//时钟线高时,数据从低到高跳变
      	SDIN_CLr();	//数据线置低
      	delay_us(1);
      	SCLK_Set();	//时钟线置高
      	delay_us(1);
      	SDIN_Set();	//数据线置高		
      	delay_us(1);		
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    应答信号

    • 发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
    • 应答信号为低电平时,规定为有效应答位(ACK简称应答位)表示接收器已经成功地接收了该字节;
    • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
    • 对于反馈有效应答位ACK的要求是,接收器在第九个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
      在这里插入图片描述
      //模拟I2C读取从机应答信号
      static unsigned char IIC_Wait_Ack(void)
      {
      	unsigned char ack;
      
      	SCLK_CLr();	//时钟线置低
      	delay_us(1);
      	SDIN_Set();	//信号线置高
      	delay_us(1);
      	SCLK_Set();	//时钟线置高
      	delay_us(1);
      
      	if(READ_SDIN())			//读取SDA的电平
      		ack = IIC_NO_ACK;	//如果为1,则从机没有应答
      	else
      		ack = IIC_ACK;		//如果为0,则从机应答
      
      	SCLK_CLr();//时钟线置低
      	delay_us(1);
      
      	return ack;	//返回读取到的应答信息
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

    数据的有效性

    • IIC总线进行数据传输时
    • 时钟信号为高电平,数据线上的数据必须保持稳定
    • 时钟线上为低电平数据线上电平状态才允许变化
    • SDA数据线在SCL的每个时钟周期传输一位数据
      即:数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定
      在这里插入图片描述

    数据传输

    • 在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据,高位先行数据位的传输是边沿触发

    I2C读写数据函数

    • I2C读取一个字节Byte
      //I2C读取一个字节Byte
      static void Write_IIC_Byte(unsigned char IIC_Byte)
      {
      	unsigned char i;  		//定义变量
      	for(i=0;i<8;i++) 			//for循环8次
      	{
      		SCLK_CLr();					//时钟线置低,为传输数据做准备
      		delay_us(1);
      
      		if(IIC_Byte & 0x80)	//读取最高位
      		  	SDIN_Set();			//最高位为1
      		else
      			SDIN_CLr();				//最高位为0
      
      		IIC_Byte <<= 1;  		//数据左移1位
      		delay_us(1);
      		SCLK_Set(); 				//时钟线置高,产生上升沿,发送数据
      		 delay_us(1);
      	}
      	SCLK_CLr();						//时钟线置低
      	delay_us(1);
      
      	while(IIC_Wait_Ack());//从机应答
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • I2C写命令
      //I2C写命令
      static void Write_IIC_Command(unsigned char IIC_Command)
      {
         OLED_IIC_Start();
         Write_IIC_Byte(0x78);	//写入从机地址,SD0 = 0
         Write_IIC_Byte(0x00);	//写入命令
         Write_IIC_Byte(IIC_Command);//数据
         OLED_IIC_Stop();  			//发送停止信号
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • I2C写入数据
      static void Write_IIC_Data(unsigned char IIC_Data)
      {
         OLED_IIC_Start();
         Write_IIC_Byte(0x78);		//写入从机地址,SD0 = 0
         Write_IIC_Byte(0x40);		//写入数据
         Write_IIC_Byte(IIC_Data);//数据
         OLED_IIC_Stop();					//发送停止信号
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    I2C标准库结构体解析及程序设计

    • STM32 I2C的结构体

      typedef struct
      {
        uint32_t I2C_ClockSpeed;         //设置SCL时钟频率,不得高于4000000
      	
        uint16_t I2C_Mode;                //指定I2C工作模式,可选I2C模式及SMBUS模式
      	
        uint16_t I2C_DutyCycle;         //时钟占空比,可选low/high = 2:0 或 16:9
      	
        uint16_t I2C_OwnAddress1;       //自身的I2C设备地址
      
        uint16_t I2C_Ack;               //使能或关闭相应,一般是使能
      	
        uint16_t I2C_AcknowledgedAddress; //指定地址长度,可为7或10
      	
      }I2C_InitTypeDef;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • STM32 I2C常用库函数

      //配置自身设备地址2
      void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);		
      //发送设备地址
      void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
      //接收数据
      uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
      //停止接收
      void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
      //外设开始正常工作
      void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • I2C1 使能配置

      void I2C_Configuration(void)
      {
      	
      	I2C_InitTypeDef   I2C_InitStructure;				
      	GPIO_InitTypeDef   GPIO_InitStructure;
      	
      	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB ,  ENABLE);	//使能GPIOB时钟
      	RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE );	//使能I2C1时钟
      	
      	//PB6 --SCL ;PB7 --SDA
      	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;			//复用开漏
      	GPIO_InitStructure.GPIO_Pin   =  GPIO_Pin_6 | GPIO_Pin_7;	//I2C1   SCL:PB6   SDA: PB7
      	GPIO_InitStructure.GPIO_Speed =  GPIO_Speed_50MHz;
       	GPIO_Init(GPIOB, &GPIO_InitStructure);						//初始化GPIOB结构体
      
        	I2C_DeInit(I2C1);											//初始化I2C1
        	I2C_InitStructure.I2C_Ack  		 	=  I2C_Ack_Enable;		//使能应答
      	I2C_InitStructure.I2C_AcknowledgedAddress =  I2C_AcknowledgedAddress_7bit;		//7位地址
      	I2C_InitStructure.I2C_ClockSpeed	= 400000 ; 				//时钟速度 400K
      	I2C_InitStructure.I2C_DutyCycle  	= I2C_DutyCycle_2 ;		//2分频  16:9
      	I2C_InitStructure.I2C_Mode 			=  I2C_Mode_I2C;		//I2C模式
      	I2C_InitStructure.I2C_OwnAddress1 	= 0X30 ;				//主机地址 随便写 不影响
      	I2C_Init(I2C1,&I2C_InitStructure );							//初始化结构体
      	I2C_Cmd(I2C1,ENABLE);											//使能I2C1
      }
      
      • 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
    • I2C写一个字节

      //I2C写一个字节
      void I2C_WriteByte(uint8_t addr,uint8_t data)
      {
      	
      	while (I2C_GetFlagStatus(I2C1,  I2C_FLAG_BUSY));  					//检查I2C总线是否繁忙
      	
      	I2C_GenerateSTART(I2C1,  ENABLE);                							//开启I2C1
      	while( !I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_MODE_SELECT)); //EV5,主模式
      	
      	I2C_Send7bitAddress(I2C1,OLED_ADDRESS, I2C_Direction_Transmitter); //发送器件地址
      	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
      	
      	I2C_SendData(I2C1,  addr);   //寄存器地址
      	while(!I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_BYTE_TRANSMITTING));
      	
      	I2C_SendData(I2C1,  data);   //发送数据
      	while(!I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_BYTE_TRANSMITTING));
      	
      	I2C_GenerateSTOP( I2C1,  ENABLE); //关闭I2C总线
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    • I2C写命令

      //写命令
      void WriteCmd(unsigned char I2C_Command)
      {
         I2C_WriteByte(0X00,I2C_Command);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • I2C写数据

      //写数据
      void WriteData(unsigned char I2C_Data)
      {
         I2C_WriteByte(0x40,I2C_Data);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
  • 相关阅读:
    Canal安装
    【C语言】库宏offsetof
    一种能让大型数据聚类快2000倍的方法,真不戳
    Leetcode-1620. 网络信号最好的坐标
    Linux安装DMETL4
    深入理解Linux内核页表映射分页机制原理
    【BLE CORE】二、GAP(Generic Access Profile)
    控制台中查看莫格命令的详细信息
    将字符串中的数据按指定分隔符分割依次存入一维数组中 numpy.fromstring()
    探索古彝文的秘密,AI实现古籍传承
  • 原文地址:https://blog.csdn.net/weixin_46216674/article/details/133046257