STM32G474硬件I2C之配置方法

news/2024/10/16 17:07:20 标签: stm32, 嵌入式硬件, 单片机, STM32G474, 经验分享, I2C

STM32G474硬件I2C接口:英文Inter-integrated circuit简写为I2CSTM32G474是M4核,在使用硬件I2C时,配置方法和M3核相差较大。通过阅读参考手册和HAL,总算了解了其配置原理。

1、I2C工作模式
I2C标准模式:最高时钟频率为100KHz;
I2C快速模式:最高时钟频率为400KHz;
I2C快速模式+:最高时钟频率为1MHz,通常用500KHz;

2、SCL时钟频率配置
I2C_TIMINGR寄存器bit31:28(PRESC[3:0]),PRESC[3:0]表示I2C时钟分频器值,分频后的周期:
tPRESC = (PRESC[3:0]+1) x tI2CCLK
I2C_TIMINGR寄存器bit23:20(SCLDEL[3:0]),SCLDEL[3:0]表示SDA边沿和SCL上升沿之间的时间,是指数据建立时间:
tSCLDEL = (SCLDEL[3:0]+1) x tPRESC
I2C_TIMINGR寄存器bit19:16(SDADEL[3:0]),SDADEL[3:0]表示SCL下降沿和SDA边沿之间的时间,是指数据保持时间:tSDADEL= SDADEL[3:0] x tPRESC
I2C_TIMINGR寄存器bit15:8(SCLH[7:0]),SCLH[7:0]表示SCL高电平的时间:tSCLH = (SCLH[7:0]+1) x tPRESC
I2C_TIMINGR寄存器bit7:0(SCLL[7:0]),SCLL[7:0]表示SCL低电平的时间:tSCLL = (SCLL[7:0]+1) x tPRESC

主时钟周期:
tSCL = tSYNC1 + tSYNC2 + { [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK }
注意:tSYNC1 + tSYNC2 >= “4 * tI2CCLK”

I2C时钟要求,I2C内核时钟来自I2CCLK时钟,I2CCLK时钟必须满足下面的条件:
tI2CCLK < (tLOW - tfilters) / 4 且 tI2CCLK < tHIGH
tLOW是指SCL低电平时间;
tHIGH是指SCL高电平时间;
tfilters是指模拟滤波器延时时间和数据滤波器的延时时间之和;
模拟滤波器最大延时时间为260ns
数字滤波器的延时时间为:I2C_CR1寄存器的DNF[3:0] * tI2CCLK;

1)、当tI2CCLK=170MHz时,计算I2C快速模式+”主时钟周期
tSYNC1 + tSYNC2 = 4 x tI2CCLK = (4 / 170) * 1000 =23.529ns
I2C时钟分频器值:PRESC[3:0]=0
SCL高电平的时间:tSCLH = (SCLH[7:0]+1) x tPRESC = 0x85+1 = 134 
SCL低电平的时间:tSCLL = (SCLL[7:0]+1) x tPRESC = 0xC8+1 = 201
{ [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK } =[ (134+201) * (0+1) / 170 ] *1000 = 1970.588ns
因此,SCL时钟周期:tSCL = 23.529 + 1970.588 = 1996.08ns,fSCL = 500.98KHz

2)、当tI2CCLK=170MHz时,计算I2C快速模式”主时钟周期
tSYNC1 + tSYNC2 = 4 x tI2CCLK = (4 / 170) * 1000 =23.529ns
I2C时钟分频器值:PRESC[3:0]=0
SCL高电平的时间:tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA7+1 = 168 
SCL低电平的时间:tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFB+1 = 252
{ [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK } =[ (168+252) * (0+1) / 170 ] *1000 = 2470.588ns
因此,SCL时钟周期:tSCL = 23.529 + 2476.471 = 2500.117ns,fSCL = 399.981KHz

3)、当tI2CCLK=170MHz时,计算I2C标准模式”主时钟周期
tSYNC1 + tSYNC2 = 4 x tI2CCLK = (4 / 170) * 1000 =23.529ns
I2C时钟分频器值:PRESC[3:0]=3
SCL高电平的时间:tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA8+1 = 169 
SCL低电平的时间:tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFD+1 = 254
{ [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK } =[ (169+254) * (3+1) / 170 ] *1000 = 9952.941ns
因此,SCL时钟周期:tSCL = 23.529 + 9952.941 = 9976.47ns,fSCL = 100.23585KHz

3、I2C主从切换
I2C_CR2寄存器bit13(START),令START=1产生“启动条件”,清除I2C_ISR寄存器bit6(TC)
I2C_CR2寄存器bit14(STOP),令STOP=1产生“停止条件”,清除I2C_ISR寄存器bit6(TC)
默认情况下,硬件I2C工作在从机模式。
当硬件I2C产生启动条件时,它就自动从“从机模式”切换到“主机机模式”;
当产生“仲裁丢失”或“停止条件”时,自动从“主机机模式”切换到“从机模式”;
因此,I2C总线上可以有多个主机和多个从机存在。

4、I2C主机的主要任务
I2C主机负责产生“启动条件”,“时钟信号”,“传输数据”和“停止条件”。
I2C串行数据传输”始终以“启动条件”开始,并以“停止条件”结束。

I2C从机负责识别它自己的“器件地址”(7位或10位)和“普通的访问地址”。
“普通的访问地址检测”由软件启用或禁用。
“保留的SMBus地址”也可以通过软件来启用。

5、初步认识I2C波形图
I2C传输的“数据和地址”均以“8位字节”的形式进行传输,首先传送的是MSB位
“启动条件”产生后,传输的第一个字节是“器件地址”(一个7位模式或两个10位模式),该地址始终由“I2C主机”发送给“I2C从机”。 
当“8位字节”传输完成后,在第9个时钟脉冲期间,“接收机”必须向“发送机”发送“一个确认位”,这个确认位就是I2C应答;
“确认位”为低电平叫“ACK应答”;“确认位”为高电平叫“NACK应答”。
波形图如下:

 SCL时钟拉伸:

6、软件复位I2C
1)、配置I2C_CR1寄存器bit0(PE),令PE=0
2)、读I2C_CR1寄存器bit0(PE),查询PE是否为0
3)、配置I2C_CR1寄存器bit0(PE),令PE=1

7、I2C初始化流程:
1)、配置I2C_CR1寄存器bit0(PE),令PE=0,不使能I2C设备
2)、配置I2C_CR1寄存器bit12(ANFOFF),令ANFOFF=0使能“模拟滤波器”
3)、配置I2C_CR1寄存器bit11:8(DNF[3:0]),令DNF[3:0]=0000b,不使能“数字滤波器”
4)、配置“I2C_TIMINGR寄存器”
I2C_TIMINGR寄存器bit31:28(PRESC[3:0]),PRESC[3:0]表示I2C时钟分频器值,分频后的周期:
tPRESC = (PRESC[3:0]+1) x tI2CCLK  = (0+1)/170 = T
I2C_TIMINGR寄存器bit23:20(SCLDEL[3:0]),SCLDEL[3:0]表示SDA边沿和SCL上升沿之间的时间,是指数据建立时间:
tSCLDEL = (SCLDEL[3:0]+1) x tPRESC = (3+1) * T = 4T
I2C_TIMINGR寄存器bit19:16(SDADEL[3:0]),SDADEL[3:0]表示SCL下降沿和SDA边沿之间的时间,是指数据保持时间:
tSDADEL= SDADEL[3:0] x tPRESC = 0 * T = 0
I2C_TIMINGR寄存器bit15:8(SCLH[7:0]),SCLH[7:0]表示SCL高电平的时间:
tSCLH = (SCLH[7:0]+1) x tPRESC = (0x3D+1) * T = 62T
I2C_TIMINGR寄存器bit7:0(SCLL[7:0]),SCLL[7:0]表示SCL低电平的时间:
tSCLL = (SCLL[7:0]+1) x tPRESC = (0x5B+1) * T = 92T
5)、配置I2C_CR1寄存器bit17(NOSTRETCH),令NOSTRETCH=0,使能“时钟拉伸”
6)、配置I2C_CR1寄存器bit0(PE),令PE=1,使能I2C设备
7)、I2C初始化完成

8、从机初始化流程:
1)、配置I2C_CR1寄存器bit0(PE),令PE=0,不使能I2C设备
2)、配置I2C_CR1寄存器bit12(ANFOFF),令ANFOFF=0使能“模拟滤波器”
3)、配置I2C_CR1寄存器bit11:8(DNF[3:0]),令DNF[3:0]=0000b,不使能“数字滤波器”
4)、配置“I2C_TIMINGR寄存器”
I2C_TIMINGR寄存器bit31:28(PRESC[3:0]),PRESC[3:0]表示I2C时钟分频器值,分频后的周期:
tPRESC = (PRESC[3:0]+1) x tI2CCLK  = (0+1)/170 = T
I2C_TIMINGR寄存器bit23:20(SCLDEL[3:0]),SCLDEL[3:0]表示SDA边沿和SCL上升沿之间的时间,是指数据建立时间:
tSCLDEL = (SCLDEL[3:0]+1) x tPRESC = (3+1) * T = 4T
I2C_TIMINGR寄存器bit19:16(SDADEL[3:0]),SDADEL[3:0]表示SCL下降沿和SDA边沿之间的时间,是指数据保持时间:
tSDADEL= SDADEL[3:0] x tPRESC = 0 * T = 0
I2C_TIMINGR寄存器bit15:8(SCLH[7:0]),SCLH[7:0]表示SCL高电平的时间:
tSCLH = (SCLH[7:0]+1) x tPRESC = (0x3D+1) * T = 62T
I2C_TIMINGR寄存器bit7:0(SCLL[7:0]),SCLL[7:0]表示SCL低电平的时间:
tSCLL = (SCLL[7:0]+1) x tPRESC = (0x5B+1) * T = 92T
5)、配置I2C_CR1寄存器bit17(NOSTRETCH),令NOSTRETCH=0,使能“时钟拉伸”
6)、配置I2C_CR1寄存器bit0(PE),令PE=1,使能I2C设备
7)、配置I2C_OAR1寄存器bit15(OA1EN),令OA1EN=0,不使能“自身设备地址1”
8)、配置I2C_OAR2寄存器bit15(OA2EN),令OA2EN=0,不使能“自身设备地址2”
9)、配置I2C_OAR1寄存器bit9:0(OA1[9:0]),设置“自身设备地址1”,最多为10位
10)、配置I2C_OAR1寄存器bit10(OA1MODE),令OA1MODE=1表示OA1[9:0]是10位地址
11)、配置I2C_OAR1寄存器bit15(OA1EN),令OA1EN=1,使能“自身设备地址1”
12)、I2C_OAR2寄存器bit7:1(OA2[7:1]),设置“自身设备地址2”,最多为6位
13)、I2C_OAR2寄存器bit10:8(OA2MSK[2:0]),OA2MSK[2:0]=000b表示不屏蔽OA2[7:1]
14)、配置I2C_OAR2寄存器bit15(OA2EN),令OA2EN=1,使能“自身设备地址2
15)、配置I2C_CR1寄存器bit19(GCEN),令GCEN=1,General call enabled.
16)、配置I2C_CR1寄存器bit16(SBC),令SBC=1,Slave byte control enabled.
17)、使能中断
I2C_CR1寄存器bit1(TXIE),令TXIE=1表示使能“I2C发送中断”:当I2C_TXDR为空时,传输中断状态TXIS=1,写I2C_TXDR会使TXIS=0;
I2C_CR1寄存器bit4(NACKIE),令NACKIE=1表示使能“I2C接收到NACK中断”:当收到NACK应答时,NACKF=1,I2C从机自动释放SCL和SDA,让I2C主机执行停止条件和重启条件
I2C_CR1寄存器bit5(STOPIE),令STOPIE=1表示使能“I2C检测到STOP中断”:当收到“STOP条件”时,STOPF=1
I2C_CR1寄存器bit2(RXIE),令RXIE=1表示使能“I2C接收中断”
I2C_CR1寄存器bit3(ADDRIE),令ADDRIE=1表示使能“I2C地址匹配中断”
I2C_CR1寄存器bit6(TCIE),令TCIE=1表示使能“I2C传输完成中断”
I2C_CR1寄存器bit7(ERRIE),令ERRIE=1表示使能“I2C错误中断”

9、时间溢出中断的条件
I2C_TIMEOUTR寄存器bit15(TIMOUTEN),令TIMOUTEN=1使能SCL时间溢出检测。
I2C_TIMEOUTR寄存器bit10:0(TIMEOUTA[11:0])
I2C_TIMEOUTR寄存器bit12(TIDLE),配置TIDLE=0,就可以用来检测SCL低电平时间:
tTIMEOUT= (TIMEOUTA[11:0]+1) x 2048 x tI2CCLK
如果SCL被拉低时间超过tTIMEOUT,则I2C_ISR寄存器bit12(TIMEOUT)被置1,令TIMEOUTCF=1可以清除TIMEOUT=0

配置TIDLE=1用来检测SCL低电平时间和高电平时间之和:tIDLE= (TIMEOUTA[11:0]+1) x 4 x tI2CCLK

10、读写EEPROM的步骤
1)、从EEPROM读取1个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

发送“I2C重启动条件”
发送“读器件地址”,告诉“这个地址的I2C从机”做好通讯准备
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
从EEPROM读取一个字节
CPU发送不应答信号
发送“I2C停止条件”

2)、从EEPROM读取2个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

发送“I2C重启动条件”
发送“读器件地址”,告诉“这个地址的I2C从机”做好通讯准备
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
从EEPROM读取第1个字节
CPU发送应答信号
从EEPROM读取最后1个字节
CPU发送不应答信号
发送“I2C停止条件”

3)、向EEPROM写1个字节的步骤:
发送“I2C启动条件”
发送“写器件地址”,告诉“这个地址的I2C从机”做好通讯准备,24LC256的“写器件地址”为0xA0
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址高8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送器件的子地址低8位值,告诉I2C从机读/写数据的位置;
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;

无须发送“I2C重启动条件”,直接向EEPROM写入1个字节
等待从器件接收方的应答,即等待SDA脚被EEPROM拉低;
发送“I2C停止条件”

void I2C1_Init(void)
{
    I2C_HandleTypeDef hi2c1;
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    RCC_PeriphCLKInitTypeDef  RCC_PeriphCLKInitStruct;

    RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C1;//初始化I2C1时钟
    RCC_PeriphCLKInitStruct.I2c1ClockSelection = RCC_I2C1CLKSOURCE_SYSCLK;
    //RCC_CCIPR寄存器bit13:12(I2C1SEL[1:0]),令I2C1SEL[1:0]=01b设置I2C1时钟源为SYSCLK系统时钟170MHz
    HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
    //HAL_RCCEx_PeriphCLKConfig()初始化I2C1外设时钟
    //Configure the I2C clock source. The clock is derived from the SYSCLK

    __HAL_RCC_I2C1_CLK_ENABLE();   //使能I2C1外设时钟
  __HAL_RCC_GPIOA_CLK_ENABLE();  //使能GPIOA外设时钟

  GPIO_InitStruct.Pin = GPIO_PIN_15;//选择引脚编号15
  GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;      //复用功能开漏极模式
  GPIO_InitStruct.Pull = GPIO_PULLUP;          //引脚上拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //引脚速度为低速
  GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;   //将引脚复用为I2C1_SCL
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    __HAL_RCC_GPIOB_CLK_ENABLE();  //使能GPIOB外设时钟
  GPIO_InitStruct.Pin = GPIO_PIN_9;//选择引脚编号9
  GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;      //复用功能开漏极模式
  GPIO_InitStruct.Pull = GPIO_PULLUP;          //引脚上拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //引脚速度为低速
  GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;   //将引脚复用为I2C1_SDA
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  hi2c1.Instance = I2C1;

#if I2C_SPeed== I2C_100KHz
  hi2c1.Init.Timing = 0x3030A8FD;
    //I2C_TIMINGR寄存器bit31:28(PRESC[3:0]),PRESC[3:0]表示I2C时钟分频器值,分频后的周期:
    //tPRESC = (PRESC[3:0]+1) x tI2CCLK = 3+1 = 4
    //I2C_TIMINGR寄存器bit23:20(SCLDEL[3:0]),SCLDEL[3:0]表示SDA边沿和SCL上升沿之间的时间,是指数据建立时间:
    //tSCLDEL = (SCLDEL[3:0]+1) x tPRESC = (3+1) / 170 = 0.0235294117647059us;
  //I2C_TIMINGR寄存器bit19:16(SDADEL[3:0]),SDADEL[3:0]表示SCL下降沿和SDA边沿之间的时间,是指数据保持时间:
    //tSDADEL= SDADEL[3:0] x tPRESC = 0 / 170 = 0us
    //I2C_TIMINGR寄存器bit15:8(SCLH[7:0]),SCLH[7:0]表示SCL高电平的时间:
    //tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA8+1 = 169
    //I2C_TIMINGR寄存器bit7:0(SCLL[7:0]),SCLL[7:0]表示SCL低电平的时间:
    //tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFD+1 =254
  //1)、当tI2CCLK=170MHz时,计算“I2C标准模式”主时钟周期:
  //tSYNC1 + tSYNC2 = 4 x tI2CCLK = (4 / 170) * 1000 =23.529ns
  //PRESC[3:0]=3
  //tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA8+1 = 169 
  //tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFD+1 = 254
  //{ [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK } =[ (169+254) * (3+1) / 170 ] *1000 = 9952.941ns
  //因此,tSCL = 23.529 + 9952.941 = 9976.47ns,fSCL = 100.23585KHz

#elif I2C_SPeed == I2C_400KHz
  hi2c1.Init.Timing = 0x00FFA7FB;
    //I2C_TIMINGR寄存器bit31:28(PRESC[3:0]),PRESC[3:0]表示I2C时钟分频器值,分频后的周期:
    //tPRESC = (PRESC[3:0]+1) x tI2CCLK = 0+1 = 1
    //I2C_TIMINGR寄存器bit23:20(SCLDEL[3:0]),SCLDEL[3:0]表示SDA边沿和SCL上升沿之间的时间,是指数据建立时间:
    //tSCLDEL = (SCLDEL[3:0]+1) x tPRESC = (15+1) / 170
  //I2C_TIMINGR寄存器bit19:16(SDADEL[3:0]),SDADEL[3:0]表示SCL下降沿和SDA边沿之间的时间,是指数据保持时间:
    //tSDADEL= SDADEL[3:0] x tPRESC = 15 / 170
    //I2C_TIMINGR寄存器bit15:8(SCLH[7:0]),SCLH[7:0]表示SCL高电平的时间:
    //tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA7+1 = 168
    //I2C_TIMINGR寄存器bit7:0(SCLL[7:0]),SCLL[7:0]表示SCL低电平的时间:
    //tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFB+1 =252
  //1)、当tI2CCLK=170MHz时,计算“I2C标准模式”主时钟周期:
  //tSYNC1 + tSYNC2 = 4 x tI2CCLK = (4 / 170) * 1000 =23.529ns
  //PRESC[3:0]=0
  //tSCLH = (SCLH[7:0]+1) x tPRESC = 0xA7+1 = 168 
  //tSCLL = (SCLL[7:0]+1) x tPRESC = 0xFB+1 = 252
  //{ [ (SCLH[7:0]+1) + (SCLL[7:0]+1) ] * (PRESC[3:0]+1) * tI2CCLK } =[ (168+252) * (0+1) / 170 ] *1000 = 2470.588ns
  //因此,tSCL = 23.529 + 2470.588 = 2494.178ns,fSCL = 400.933KHz

#endif

#if EEPROM_Device_Address_Size == 0 //I2C从机“器件地址”为7位数据
  hi2c1.Init.OwnAddress1 = I2C_Own_Address;
    //I2C_OAR1寄存器bit9:0(OA1[9:0]),自身地址1:OA1[9:0]=hi2c->Init.OwnAddress1
  hi2c1.Init.OwnAddress2 = 0;
    //I2C_OAR2寄存器bit17:1(OA2[7:1]),自身地址2:OA2[7:1]=hi2c->Init.OwnAddress2

  hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
    //I2C_OAR2寄存器bit110:8(OA2MSK[2:0]),自身地址2的掩码OA2MSK[2:0]=000b,自身地址2(OA2[7:1])无掩码
    //OA2MSK[2:0]=000b,OA2[7:1]必须要全部匹配才能使I2C正常通讯
    //OA2MSK[2:0]=001b,忽略OA2[1]匹配,但OA2[7:2]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=010b,忽略OA2[2:1]匹配,但OA2[7:3]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=011b,忽略OA2[3:1]匹配,但OA2[7:4]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=100b,忽略OA2[4:1]匹配,但OA2[7:5]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=101b,忽略OA2[5:1]匹配,但OA2[7:6]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=110b,忽略OA2[6:1]匹配,但OA2[7]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=111b,忽略OA2[7:1]匹配,OA2[7:1]不做比较就可以使I2C通讯了

  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    //I2C_OAR2寄存器bit15(OA2EN),令OA2EN=0不使能“双地址模式”,即不使用自身地址2(OA2[7:1])
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    //I2C_CR2寄存器bit11(ADD10),令ADD10=0表示I2C主机工作在7位地址模式,则对应的I2C从机的器件地址必须是7位
    //I2C主机和从机地址模式相同,因此从机地址占7位

#else
  hi2c1.Init.OwnAddress1 = I2C_Own_Address;
    //I2C_OAR1寄存器bit9:0(OA1[9:0]),自身地址1:OA1[9:0]=hi2c->Init.OwnAddress1
  hi2c1.Init.OwnAddress2 = 0;
    //I2C_OAR2寄存器bit17:1(OA2[7:1]),自身地址2:OA2[7:1]=hi2c->Init.OwnAddress2

  hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
    //I2C_OAR2寄存器bit110:8(OA2MSK[2:0]),自身地址2的掩码OA2MSK[2:0]=000b,自身地址2(OA2[7:1])无掩码
    //OA2MSK[2:0]=000b,OA2[7:1]必须要全部匹配才能使I2C正常通讯
    //OA2MSK[2:0]=001b,忽略OA2[1]匹配,但OA2[7:2]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=010b,忽略OA2[2:1]匹配,但OA2[7:3]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=011b,忽略OA2[3:1]匹配,但OA2[7:4]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=100b,忽略OA2[4:1]匹配,但OA2[7:5]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=101b,忽略OA2[5:1]匹配,但OA2[7:6]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=110b,忽略OA2[6:1]匹配,但OA2[7]必须要匹配才能使I2C正常通讯
    //OA2MSK[2:0]=111b,忽略OA2[7:1]匹配,OA2[7:1]不做比较就可以使I2C通讯了

  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_ENABLE;
    //I2C_OAR2寄存器bit15(OA2EN),令OA2EN=1使能“双地址模式”,即使用自身地址2(OA2[7:1])

  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_10BIT;
    //I2C_CR2寄存器bit11(ADD10),令ADD10=1表示I2C主机工作在10位地址模式,则对应的I2C从机的器件地址必须是10位
    //I2C主机和从机地址模式相同,因此从机地址占10位

#endif

  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    //I2C_CR1寄存器bit19(GCEN),GCEN=0不使能“普通地址访问”
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    //I2C_CR1寄存器bit17(NOSTRETCH),NOSTRETCH=0表示“SCL时钟不拉伸”不使能,意思是允许时钟拉伸;
    //即在从机应答期间,SCL的低电平时间会被拉长了。

  HAL_I2C_Init(&hi2c1);

  HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE);
    //使能“模拟滤波器”,并使能I2C外设,Analog filter delay is maximum 260 ns.

  HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0);
    //不使能“数字滤波器”,并使能I2C外设,Digital filter delay is DNF x tI2CCLK.

  __HAL_SYSCFG_FASTMODEPLUS_ENABLE(I2C_FASTMODEPLUS_I2C1);
    //使能“I2C1快速模式+”,I2C Fast mode Plus enable
    //配置I2C1快速模式(1MHz)和驱动能力 

}

//开始24LC256驱动程序
 /**-------------------------------------------------------------------------------
 函数说明:从EEPROM芯片24LC256的某个地址单元addr,读取到rerurn_value中.
           并将读到的值返回;
 ---------------------------------------------------------------------------------*/

uint8_t EEPROM_U8_Data_Read1(uint16_t addr)
{
    uint8_t rerurn_value;
    uint8_t ret,cnt,i;
  uint32_t Timeout;

    delay_ms(1); //在连续进行"字节连续读"时,此处必须加delay_ms(5),否则程序可能读不到EEPROM中的数据;
    Timeout=10;
    ret=0;
    cnt=Timeout;
  while (_HAL_I2C_GET_FLAG(I2C1, I2C_FLAG_BUSY) == SET)
  {//等待I2C总线空闲
        delay_ms(1);
        cnt--;
        if (cnt== 0)
        { ret=I2C_Err_BUSY;//表示I2C忙超时
            break;
        }
  }

    if(ret==0)//发送“器件地址”
    {
#if EEPROM_Device_SubAddress_Size == 0 //I2C从机“器件子地址”为1个字节
    My_I2C_TransferConfig(I2C1,EEPROM_Device_Address, 1, I2C_SOFTEND_MODE, I2C_GENERATE_START_WRITE);
      //发送“启动条件”和“写从机器件地址”,发送字节数为1
    //Request=I2C_GENERATE_START_WRITE表示需要产生I2C启动条件,主机请求写
    //I2C_CR2寄存器bit25(AUTOEND),Mode=I2C_SOFTEND_MODE表示“软件结束模式”,当NBYTES[7:0]个字节被发送完后,SCL被拉伸
    //I2C_CR2寄存器bit24(RELOAD),Mode=I2C_SOFTEND_MODE表示当NBYTES[7:0]个字节被发送完后,传送器完成发送,要求后面紧接着是“停止条件或重启条件”

#else //I2C从机“器件子地址”为2个字节
    My_I2C_TransferConfig(I2C1,EEPROM_Device_Address, 2, I2C_SOFTEND_MODE, I2C_GENERATE_START_WRITE);
      //发送“启动条件”和“写从机器件地址”,发送字节数为2
    //Request=I2C_GENERATE_START_WRITE表示需要产生I2C启动条件,主机请求写
    //I2C_CR2寄存器bit25(AUTOEND),Mode=I2C_SOFTEND_MODE表示“软件结束模式”,当NBYTES[7:0]个字节被发送完后,SCL被拉伸
    //I2C_CR2寄存器bit24(RELOAD),Mode=I2C_SOFTEND_MODE表示当NBYTES[7:0]个字节被发送完后,传送器完成发送,要求后面紧接着是“停止条件或重启条件”

#endif

    /* Wait until TXIS flag is set */
    if (My_I2C_WaitOnTXISFlagUntilTimeout(I2C1,Timeout))
    { //返回值为0,表示I2C操作写完成
      //返回值为1,表示读到了NACK位
      //返回值为2,表示没有接收到“I2C停止条件”
      //返回值为3,表示TXIS=0,发送器件地址失败

      ret=I2C_Err_TXIS_Zero;//表示写超时
    }
    }

    if(ret==0)//发送“器件子地址”
    {
#if EEPROM_Device_SubAddress_Size == 0 //I2C从机“器件子地址”为8位数据
    I2C1->TXDR = I2C_MEM_ADD_LSB(addr);//发送“器件子地址”
#else //I2C从机“器件子地址”为16位数据
    I2C1->TXDR = I2C_MEM_ADD_MSB(addr);//发送“器件子地址高8位”
    /* Wait until TXIS flag is set */
    if (My_I2C_WaitOnTXISFlagUntilTimeout(I2C1,Timeout))
    { //返回值为0,表示TXIS=1
      //返回值为1,表示读到了NACK位
      //返回值为2,表示没有接收到“I2C停止条件”
      //返回值为3,表示TXIS=0,发送器件地址失败

      ret=I2C_Err_TXIS_Zero;//表示TXIS=0,发送器件地址失败
    }

        if(ret==0) I2C1->TXDR = I2C_MEM_ADD_LSB(addr);//发送“器件子地址低8位”
#endif
    if ( ret==0 && My_I2C_WaitOnFlagUntilTimeout(I2C1,I2C_FLAG_TC, RESET, Timeout) )
    { //读I2C_ISR寄存器bit6(TC),等待I2C主机传送完“I2C_CR2寄存器bit23:16(NBYTES[7:0])”个数据后TC=1,
            //当“RELOAD=0, AUTOEND=0”时,NBYTES[7:0]个数据被发送完成后令TC=1,而当发送“启动条件或停止条件”时令TC=0
            //这里是查询“器件子地址”是否发送完成

      ret=I2C_Err_TC_Zero;//表示TC=0,发送器件子地址失败
    }
    }

  if(ret==0)//主备读I2C从机的数据
    {
        My_I2C_TransferConfig(I2C1,EEPROM_Device_Address, 1, I2C_AUTOEND_MODE, I2C_GENERATE_START_READ);
        //发送“重启条件”和“读从机器件地址EEPROM_Device_Address”,发送字节数为n
        //I2C_CR2寄存器bit25(AUTOEND),I2C_AUTOEND_MODE表示“自动结束模式”,即一次可以读完
      //I2C_GENERATE_START_READ表示需要产生I2C启动条件,主机请求读

    if (My_I2C_WaitOnFlagUntilTimeout(I2C1,I2C_FLAG_RXNE, RESET, Timeout))
    {//等待新数据
        ret=I2C_Err_RXNE_Zero;//表示RXNE=0,读数据超时
    }
    if(ret==0)//读到新数据
      {
          rerurn_value = (uint8_t)I2C1->RXDR;//保存接收到新数据
        }

    /* No need to Check TC flag, with AUTOEND mode the stop is automatically generated */
    /* Wait until STOPF flag is reset */

    if ( ret==0 && My_I2C_WaitOnSTOPFlagUntilTimeout(I2C1,Timeout))
    {//读I2C_ISR寄存器bit5(STOPF),等待“I2C停止条件”STOPF=1,并查询I2C主机发送一个字节后是否建立NACKF标志
        ret=I2C_Err_STOP_Zero;//表示STOP=0,没有收到停止条件
    }

        if(ret==0)//发送停止条件正确
        {
      /* Clear STOP Flag */
     _HAL_I2C_CLEAR_FLAG(I2C1,I2C_FLAG_STOPF);
          //设置I2C_ICR寄存器bit5(STOPCF)为1,清除“I2C_ISR寄存器bit5(STOPF),“I2C停止条件”标志”

      /* Clear Configuration Register 2 */
      _I2C_RESET_CR2(I2C1);

//清除“I2C_CR2寄存器bit9:0(SADD[9:0]),bit10(RD_WRN),bit12(HEAD10R),bit23:16(NBYTES[7:0]),bit24(RELOAD)”
        }
    }

    return rerurn_value;
}

上面的代码,是从HAL库中抠出来,读EEPROM是可以的。HAL库的代码确实太复杂,不适合实际使用。


http://www.niftyadmin.cn/n/5708255.html

相关文章

Windows 添加右键以管理员身份运行 PowerShell

在 Windows 系统中添加一个右键菜单选项&#xff0c;以便可以使用管理员权限打开 PowerShell&#xff0c;可以通过编辑注册表来实现。 打开注册表编辑器&#xff1a; 按 Win R 打开运行对话框。输入 regedit 并按回车&#xff0c;这将打开注册表编辑器。 导航到文件夹背景键&…

2024-10-15 学习人工智能的Day7

在简单的了解完学习人工智能所需的高数、线代、概率论后&#xff0c;我们又重新开始了国庆的学习&#xff0c;因为已经有十余天没有接触python&#xff0c;所以今天的内容主要是对之前学习的python的回顾与总结&#xff0c;然后对各个部分进行了简单的实践&#xff0c;在最后学…

Linux的zookeeper安装部署

1.zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是hadoop和HBASE的重要组件 2.下载zookeeper安装包zookeeper安装包https://archive.apache.org/dist/zookeeper/zookeeper-3.5.9/ 移动到Linux解压 解压到/export/server文件夹 命令: tar -xvf apache-zooke…

Java实现八种排序

目录 分类 直接插入排序 希尔排序 选择排序 堆排序 冒泡排序 快速排序 挖坑法 hoare法 双指针法 优化 非递归实现 归并排序 非递归实现 计数排序 分类 这里的排序可以分为两大类&#xff0c; 基于比较的排序非基于比较的排序 其中有七种基于比较的排序&…

Reality Capture 软件安装 附下载链接

Reality Capture 软件安装 文章目录 Reality Capture 软件安装一、Reality Capture v1.4汉化版安装包下载并解压二、Epic Games Launcher安装三、设置路径并安装![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f077210990674d9fa9c10b52338b52fe.png)四、启动Epic Ga…

Jmeter脚本录制、Badboy脚本录制

目录 Jmeter脚本录制 Badboy脚本录制 Jmeter脚本录制 1、首先添加一个http测试脚本记录器 2、选择目标控制器&#xff0c;把录制的内容放到的地方 3、分组&#xff0c;就是在录制的时候&#xff0c;每一步操作之间间隔方式&#xff0c;根据自己需要进行选择 4、开启浏览…

工程师 - 版本管理工具从CVS到SVN的演变

CVS - Concurrent Versions System CVS&#xff08;Concurrent Versions System&#xff0c;并发版本系统&#xff09;在软件开发早期曾被广泛用作版本控制系统&#xff0c;但后来由于多种原因而过时&#xff0c;并被 SVN&#xff08;Subversion&#xff09;等更新的系统所取代…

golang用any类型去接收前端传的数字类型的值,类型断言为float64

在 Go 中&#xff0c;使用 any 类型接收前端传来的数字时&#xff0c;通常会发现其被类型断言为 float64。这是因为在 JSON 解码的过程中&#xff0c;Go 的 encoding/json 包会将数字解析为 float64。但如果你在结构体中指明字段为 int 类型&#xff0c;框架会根据字段类型进行…