STM32G474硬件I2C之配置方法

news2024/10/26 19:25:07

STM32G474硬件I2C接口:英文Inter-integrated circuit简写为I2C。STM32G474是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.coloradmin.cn/o/2210350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用HTML和CSS实现3D波浪动画效果

使用HTML和CSS实现3D波浪动画效果 在本篇博客中&#xff0c;将详细介绍如何使用HTML与CSS创建一个3D波浪动画效果。这个效果不仅能够在网页中创建立体感强的视觉体验&#xff0c;还能够通过悬停和聚焦实现与用户的交互。我们将逐步解析代码中的每个部分&#xff0c;帮助你掌握…

探索Theine:Python中的AI缓存新贵

文章目录 探索Theine&#xff1a;Python中的AI缓存新贵背景&#xff1a;为何选择Theine&#xff1f;Theine是什么&#xff1f;如何安装Theine&#xff1f;简单的库函数使用方法场景应用场景一&#xff1a;Web应用缓存场景二&#xff1a;分布式系统中的数据共享场景三&#xff1…

使用WordPress从零开始搭建一个本地网站实现远程访问

文章目录 前言1. 安装WordPress2. 创建WordPress数据库3. 安装相对URL插件4. 安装内网穿透发布网站4.1 命令行方式&#xff1a;4.2. 配置wordpress公网地址 5. 配置WordPress固定公网地址 前言 本文主要介绍如何在Linux Ubuntu系统上使用WordPress搭建一个本地网站&#xff0c…

C语言网络编程深入研究

网络编程是现代软件开发中的一个重要部分&#xff0c;它允许不同计算机之间相互通信和交换数据。本指南将深入探讨使用C语言进行网络编程的技术细节&#xff0c;特别是TCP/IP协议族的核心概念和技术实现。我们将通过具体的代码示例来讨论如何创建客户端和服务器程序&#xff0c…

渗透测试 之 AD域渗透 【Kerberoasting】 攻击技术讲解 对应得工具详细介绍哟~ 以及相关示例 按照步骤做你也会哟

说明 Kerberoasting 攻击发生在Kerberos协议的TGS_REP阶段&#xff0c;KDC的TGS服务返回一个由服务Hash加密的ST给客户端。由于该ST是用服务Hash进行加密的&#xff0c;因此客户端在拿到该ST后可以用于本地离线爆破。 攻击的过程 攻击者提供一个正常的域用户密码对域进行身份…

拆解学习【STC宏晶MCU-CM1020电池保护】(一)

MIJIA米家USB-C充气宝1S: 米家这款充气宝内置2串18650锂电池为电机和控制板供电。控制板采用STC宏晶MCU进行气压测量和电机控制以及压力显示&#xff0c;内部电池保护板采用创芯微CM1020进行电池保护&#xff0c;并采用捷捷微MOS管进行开关控制。 LED数码管采用贴片LED二极管…

NVM 切换Node.js版本工具

大家好我是苏麟&#xff0c;今天聊聊NVM切换版本工具。 切换 node 版本工具 &#xff1a; GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions 查看node版本 node -v 查看 nvm 版本 nvm -v 查看可安装的Nod…

JavaScript进阶笔记--深入对象-内置构造函数及案例

深入对象 创建对象三种方式 利用对象字面量new Object&#xff08;{…}&#xff09;利用构造函数 // 1. 字面量创建对象const obj1 {name: pig,age: 18};console.log(obj1); // {name: "pig", age: 18}// 2. 构造函数创建对象function Pig(name, age) {this.name…

RVIZ2可视化移动机器人模型

RVIZ2可视化移动机器人模型 上一节讲完joint和link&#xff0c;我们来把我们上面定义的简单的URDF(包含身体和雷达)用RVIZ2显示出来&#xff0c;直观的感受下&#xff0c;我们的机器人模型。 URDF可视化的步骤如下&#xff1a; 1建立机器人描述功能包 2建立urdf文件夹编写…

子组件向父组件传值$emit

点击子组件的按钮&#xff0c;将子组件的值传递给父组件&#xff0c;并进行提示。 子组件 <template><div><button click"emitIndex">clickme</button></div> </template> <script> export default {methods: {emitInde…

计算机毕业设计Django+Vue.js豆瓣图书推荐系统 图书评论情感分析 豆瓣图书可视化大屏 豆瓣图书爬虫 数据分析 图书大数据 大数据毕业设计 机器学习

《DjangoVue.js豆瓣图书推荐系统》开题报告 一、研究背景与意义 1. 研究背景 随着数字化时代的来临&#xff0c;图书资源日益丰富&#xff0c;用户面临着信息过载的问题。如何在海量图书中快速找到符合个人兴趣和需求的书籍成为了亟待解决的问题。传统的图书检索方式往往基于…

【含开题报告+文档+PPT+源码】基于SpringBoot的景区酒店点评系统的设计与实现

开题报告 旅游业的快速发展使得越来越多的人选择旅游作为休闲和放松的方式。景区酒店作为旅游的重要组成部分&#xff0c;承担着提供住宿和服务的重要角色。然而&#xff0c;对于游客来说&#xff0c;在选择合适的景区酒店时往往存在信息不对称的问题&#xff0c;缺乏可靠的点…

Windows 下安装 jdk8

一、简介 JDK&#xff1a;Java SE Development Kit&#xff08;Java 开发工具&#xff09;。JRE&#xff1a;Java Runtime Environment &#xff08;Java 运行环境&#xff09;。 如果想进行 Java 编程&#xff08;开发人员&#xff09;&#xff0c;需要安装 JDK&#xff1b;如…

【含开题报告+文档+PPT+源码】基于过滤协同算法的城市旅游网站的设计与实现

开题报告 几年的疫情对生活的各个领域都产生了巨大的影响&#xff0c;疫情之后&#xff0c;随着国内经济的加速复苏&#xff0c;旅游业也迅速回暖。2023 年我国旅游需求迅速增多&#xff0c;一季度旅游人次为12.16 亿人次&#xff0c;较 2022 年同期增长了 46.5%。在当今数字化…

79 NAT-NAT444端口块静态映射

NAT444&#xff08;Network Address Translation 444&#xff09;是一种网络地址转换技术&#xff0c;用于将私有IP地址转换为公有IP地址&#xff0c;实现私有网络与公有网络之间的通信。 端口块静态映射是NAT444中的一种映射方式&#xff0c;它将一组端口范围映射到一个公有I…

GO 语言协程知识点学习笔记

GO 语言协程知识点学习笔记 是个人从互联网上学习整理的笔记。因个人技艺不精&#xff0c;如有纰漏&#xff0c;还请斧正。 协程&#xff1f; 协程并不是 GO 语言特有的机制&#xff0c;像 Lua、Ruby、Python、Kotlin、C/C 也都有协程的支持。区别在于有些是从语言层面支持&a…

【2024最新】基于springboot+vue的xxxx平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

linux源码安装slurm以及mung和openssl

一、源码安装munge 1、编译安装munge &#xff08;1&#xff09;下载munge地址&#xff1a;https://github.com/dun/munge/releases &#xff08;2&#xff09;解压编译安装&#xff1a; 1 2 3 4 5 6 7 8 创建/data目录 复制文件munge-0.5.15.tar.xz 到/data目录下 tar -Jx…

模型 知识诅咒

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。知者难悟无知惑。 1 知识诅咒案例 1.1 会议室的误解 李经理是一家科技公司的产品经理&#xff0c;他负责领导一个新产品的开发项目。项目团队由不同背景和经验的成员组成&#xff0c;包括新入职的员…

php 生成随机数

记录:随机数抽奖 要求:每次生成3个 1 - 10 之间可重复(或不可重复)的随机数,10次为一轮,每轮要求数字5出现6次、数字4出现3次、…。 提炼需求: 1,可设置最小数、最大数、每次抽奖生成随机数的个数、是否允许重复 2,可设置每轮指定数字的出现次数 3,可设置每轮的抽奖…