目录

通过LL库初始化STM32的硬件IIC

通过LL库初始化STM32的硬件IIC

前言

talk is cheap, show me the code, 直接看下列的 初始化代码, 以及读写函数吧。

void Configure_I2Cx_Master(I2C_TypeDef *I2Cx)
{
	/* (1) Enables GPIO clock and configures the I2C3 pins **********************/
  /*    (SCL on PC.0, SDA on PC.1)                     **********************/

  /* Enable the peripheral clock of GPIOC */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);

  /* Configure SCL Pin as : Alternate function, High Speed, Open drain, Pull up */
  LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_0, LL_GPIO_MODE_ALTERNATE);
  LL_GPIO_SetAFPin_0_7(GPIOC, LL_GPIO_PIN_0, LL_GPIO_AF_7);
  LL_GPIO_SetPinSpeed(GPIOC, LL_GPIO_PIN_0, LL_GPIO_SPEED_FREQ_HIGH);
  LL_GPIO_SetPinOutputType(GPIOC, LL_GPIO_PIN_0, LL_GPIO_OUTPUT_OPENDRAIN);
  LL_GPIO_SetPinPull(GPIOC, LL_GPIO_PIN_0, LL_GPIO_PULL_UP);

  /* Configure SDA Pin as : Alternate function, High Speed, Open drain, Pull up */
  LL_GPIO_SetPinMode(GPIOC, LL_GPIO_PIN_1, LL_GPIO_MODE_ALTERNATE);
  LL_GPIO_SetAFPin_0_7(GPIOC, LL_GPIO_PIN_1, LL_GPIO_AF_7);
  LL_GPIO_SetPinSpeed(GPIOC, LL_GPIO_PIN_1, LL_GPIO_SPEED_FREQ_HIGH);
  LL_GPIO_SetPinOutputType(GPIOC, LL_GPIO_PIN_1, LL_GPIO_OUTPUT_OPENDRAIN);
  LL_GPIO_SetPinPull(GPIOC, LL_GPIO_PIN_1, LL_GPIO_PULL_UP);

  /* (2) Enable the I2C3 peripheral clock and I2C3 clock source ***************/

  /* Enable the peripheral clock for I2C3 */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C3);

  /* Set I2C3 clock source as SYSCLK */
  LL_RCC_SetI2CClockSource(LL_RCC_I2C3_CLKSOURCE_SYSCLK);
  
  /* (3) Configure NVIC for I2C3 **********************************************/

  /* Configure Event and Error IT:
   *  - Set priority for I2C3_IRQn
   *  - Enable I2C3_IRQn
   */
//  NVIC_SetPriority(I2C3_IRQn, 0);
//  NVIC_EnableIRQ(I2C3_IRQn);

  /* (4) Configure I2C3 functional parameters *********************************/

  /* Disable I2C3 prior modifying configuration registers */
  LL_I2C_Disable(I2C3);

  /* Configure the SDA setup, hold time and the SCL high, low period */
  /* (uint32_t)0x00601B28 = I2C_TIMING*/
	uint32_t timing = __LL_I2C_CONVERT_TIMINGS(0x0, 0x6, 0x0, 0x1B, 0x28);

  LL_I2C_SetTiming(I2C3, timing);

  /* Configure the Own Address1                   */
  /* Reset Values of :
   *     - OwnAddress1 is 0x00
   *     - OwnAddrSize is LL_I2C_OWNADDRESS1_7BIT
   *     - Own Address1 is disabled
   */
  //LL_I2C_SetOwnAddress1(I2C3, 0x00, LL_I2C_OWNADDRESS1_7BIT);
  //LL_I2C_DisableOwnAddress1(I2C3);

  /* Enable Clock stretching */
  /* Reset Value is Clock stretching enabled */
  //LL_I2C_EnableClockStretching(I2C3);

  /* Configure Digital Noise Filter */
  /* Reset Value is 0x00            */
  //LL_I2C_SetDigitalFilter(I2C3, 0x00);

  /* Enable Analog Noise Filter           */
  /* Reset Value is Analog Filter enabled */
  //LL_I2C_EnableAnalogFilter(I2C3);

  /* Enable General Call                  */
  /* Reset Value is General Call disabled */
  //LL_I2C_EnableGeneralCall(I2C3);

  /* Configure the 7bits Own Address2               */
  /* Reset Values of :
   *     - OwnAddress2 is 0x00
   *     - OwnAddrMask is LL_I2C_OWNADDRESS2_NOMASK
   *     - Own Address2 is disabled
   */
  //LL_I2C_SetOwnAddress2(I2C3, 0x00, LL_I2C_OWNADDRESS2_NOMASK);
  //LL_I2C_DisableOwnAddress2(I2C3);

  /* Configure the Master to operate in 7-bit or 10-bit addressing mode */
  /* Reset Value is LL_I2C_ADDRESSING_MODE_7BIT                         */
  //LL_I2C_SetMasterAddressingMode(I2C3, LL_I2C_ADDRESSING_MODE_7BIT);

  /* Enable Peripheral in I2C mode */
  /* Reset Value is I2C mode */
  //LL_I2C_SetMode(I2C3, LL_I2C_MODE_I2C);

  /* (5) Enable I2C3 **********************************************************/
  LL_I2C_Enable(I2C3);

  /* (6) Enable I2C3 transfer complete/error interrupts:
   *  - Enable Receive Interrupt
   *  - Enable Not acknowledge received interrupt
   *  - Enable Error interrupts
   *  - Enable Stop interrupt
   */
//  LL_I2C_EnableIT_RX(I2C3);
//  LL_I2C_EnableIT_NACK(I2C3);
//  LL_I2C_EnableIT_ERR(I2C3);
//  LL_I2C_EnableIT_STOP(I2C3);	
}

/**
  * @brief  This Function handle Master events to perform a transmission process
  * @note  This function is composed in different steps :
  *        -1- Initiate a Start condition to the Slave device
  *        -2- Loop until end of transfer received (STOP flag raised)
  *             -2.1- Transmit data (TXIS flag raised)
  *        -3- Clear pending flags, Data consistency are checking into Slave process
  * @param  None
  * @retval None
  */
void I2C_Write(I2C_TypeDef *I2Cx, unsigned char slave_addr, unsigned char reg_addr, unsigned char reg_data)
{
	unsigned char data_size = 2;
  /* Master Generate Start condition for a write request :              */
  /*    - to the Slave with a 7-Bit SLAVE_OWN_ADDRESS                   */
  /*    - with a auto stop condition generation when transmit all bytes */
  LL_I2C_HandleTransfer(I2Cx, slave_addr, LL_I2C_ADDRSLAVE_7BIT, data_size, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);

  /* Loop until STOP flag is raised  */
  while(!LL_I2C_IsActiveFlag_STOP(I2Cx))
  {
    /* Check TXIS flag value in ISR register */
    if(LL_I2C_IsActiveFlag_TXE(I2Cx) && data_size == 2)
    {
      /* Write data in Transmit Data register.
      TXIS flag is cleared by writing data in TXDR register */
      LL_I2C_TransmitData8(I2Cx, reg_addr);
			data_size--;
    }
		/* Check TXIS flag value in ISR register */
    if(LL_I2C_IsActiveFlag_TXE(I2Cx) && data_size == 1)
    {
      /* Write data in Transmit Data register.
      TXIS flag is cleared by writing data in TXDR register */
      LL_I2C_TransmitData8(I2Cx, reg_data);
			data_size--;
    }
		if(Loop_Is_Timeout_Xms(5))
			break;
  }
  /* (3) Clear pending flags, Data consistency are checking into Slave process */

  /* End of I2C_SlaveReceiver_MasterTransmitter Process */
  LL_I2C_ClearFlag_STOP(I2Cx);
}

/**
  * @brief  This Function handle Master events to perform a transmission process
  * @note  This function is composed in different steps :
  *        -1- Initiate a Start condition to the Slave device
  *        -2- Loop until end of transfer received (STOP flag raised)
  *             -2.1- Transmit data (TXIS flag raised)
  *        -3- Clear pending flags, Data consistency are checking into Slave process
  * @param  None
  * @retval None
  */
unsigned char I2C_Read(I2C_TypeDef *I2Cx, unsigned char slave_addr, unsigned char reg_addr)
{
	unsigned char read_byte = 0;
	unsigned char data_size = 1;
  /* Master Generate Start condition for a write request :              */
  /*    - to the Slave with a 7-Bit SLAVE_OWN_ADDRESS                   */
  /*    - with a auto stop condition generation when transmit all bytes */
  LL_I2C_HandleTransfer(I2Cx, slave_addr, LL_I2C_ADDRSLAVE_7BIT, data_size, LL_I2C_MODE_SOFTEND, LL_I2C_GENERATE_START_WRITE);

  /* Loop until STOP flag is raised  */
	/* This loop is dangerous when power support is terrrible. */
  while(!LL_I2C_IsActiveFlag_TC(I2Cx))
	{
    /* Check TXIS flag value in ISR register */
    if(LL_I2C_IsActiveFlag_TXE(I2Cx) && data_size == 1)
    {
      /* Write data in Transmit Data register.
      TXIS flag is cleared by writing data in TXDR register */
      LL_I2C_TransmitData8(I2Cx, reg_addr);
			data_size--;
    }
		if(Loop_Is_Timeout_Xms(3))
			break;
  }
	
	data_size = 1;
	LL_I2C_HandleTransfer(I2Cx, slave_addr, LL_I2C_ADDRSLAVE_7BIT, data_size, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_READ);
  /* Loop until STOP flag is raised  */
	while(!LL_I2C_IsActiveFlag_STOP(I2Cx))
	{
		if(Loop_Is_Timeout_Xms(4))
			break;
	}
	if(LL_I2C_IsActiveFlag_RXNE(I2Cx))
			read_byte = LL_I2C_ReceiveData8(I2Cx);
  /* (3) Clear pending flags, Data consistency are checking into Slave process */
  LL_I2C_ClearFlag_STOP(I2Cx);

	return read_byte;
}

问题

Q1: 该 LL_I2C_HandleTransfer(I2Cx, slave_addr, LL_I2C_ADDRSLAVE_7BIT, data_size, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_READ) 函数,非常容易造成使用上的误解问题,如果不借助逻辑分析仪的话。

A1: 关于这个问题,有几点需要了解。

  1. 如果设置了stm32中硬件iic为 7bit地址模式,要注意slave_addr的值其实要配置为写地址的值 (slave_addr<<2) 。 因为在stm32的硬件iic中, 会将传入函数的地址值的最后一位改成读写状态的值, 而不是将传入函数的地址之后再附加一位读写状态值。 比如说, 传入为7位的slave_addr地址值得话, 那么会改变了7位slave_addr地址的最后一位bit的值。

    众所周知, 在IIC器件中, 一般有三个地址, 比如hmcl5883设备器件地址为 0x1E, 写地址为0x3c,读地址为0x3d, 那么我们在使用stm32的硬件iic的时候, 要将slave_addr的地址值填写为 ( 0x1e<<1) , 也就是要设置为 写地址0x3c (不是设备地址0x1e),因为在配置了 LL_I2C_GENERATE_START_READ 之后,芯片会在最后一位自动改变读写位的电平状态, 在读的时候,自动产生高位电平, 也就是产生读地址0x3d的电平出去,而如果配置了 LL_I2C_GENERATE_START_WRITE 之后, 在传输完 ( 0x1e<<1) 的地址之后, 最后的地址位为低电平, 也就是写地址为0x3c。

  2. 设置了data_size以及LL_I2C_MODE_AUTOEND之后, I2C会在发送完所有数据或者读取完所有数据之后,自动发送终止信号。 写的过程中, 硬件iic自动会读取从机的ack信号; 而读的过程后,如果需要后续继续操作, 需要产生ack信号给从机, 而如果已经读完,那么会自动产生nack信号给从机,有些从机一定要接收到主机的nack信号, 然后才能正常接收主机的stop信号, 否则会导致后续一连串读取异常

  3. 接收器在收到数据之后, 应该拉低引脚来产生应答信号(正常来说, 引脚都是开漏上拉,所以,下拉为有效电平),以此通知对方已完整收到相关的数据。 而在主机作为接收器的时候,当接收完从设备的最后一个字节数据之后, 需要不应答, 以此告诉从设备传输即将结束, 准备接收结束信号。

Q2: 硬件IIC在信号出现抖动(频率过快的话或者供电不足的情况下), 主机发出的时序会完全错乱, 比如说, 当频率过高或者数据线接触不良,此时我们通过重新初始化从机,且重置硬件iic的错误标志,iic硬件接收到的数据虽然会有变化, 但是,读到的值完全不对, 这时候只能通过重新初始化IIC硬件才能解决, 但是,在L0系列上,利用外部中断不断地打断硬件iic时, 未发现iic数据读取出现任何较大的异常。

A2:当信号抖动之后, 根据逻辑分析仪,会发现主机在发送从机设备地址的时候,会多发一个字节的数据时序,但是,这个字节数据的值不知从何而来,这便会导致后面读到的值各种异常, 怀疑是不是移位寄存器在发送的时候,也发送了之前发生总线错误的时候,移位寄存器缓存部分字节信息 。 因为调试时,相关的接收, 发送寄存器的值都是正常。