打印
[AT32F407]

【AT-START-F407测评】+IIC读写与多字节读取异常钳住总线

[复制链接]
829|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tinnu|  楼主 | 2021-2-15 23:51 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 tinnu 于 2021-2-15 23:55 编辑

使用的是轮询的例程例程修改的IIC读写程序。

(一)引脚
arduino引脚上的IIC是PB8、PB9,属于复用功能。

例程使用的PB6、PB7是默认的IIC1,复用映射需要额外使能复用功能:

  RCC_APB2PeriphClockCmd( RCC_APB2PERIPH_AFIO , ENABLE);
  GPIO_PinsRemapConfig(GPIO_Remap_I2C1,ENABLE);

(二)使能IIC功能
    /* I2C configuration */
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2CDevice;
    I2C_InitStructure.I2C_FmDutyCycle = I2C_FmDutyCycle_2_1;
    I2C_InitStructure.I2C_OwnAddr1 = I2C_SLAVE_ADDRESS7;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AddrMode = I2C_AddrMode_7bit;
    I2C_InitStructure.I2C_BitRate = I2C_SPEED;

    /* I2C Peripheral Enable */
    I2C_Cmd(I2C_PORT, ENABLE);

    /* Apply I2C configuration after enabling it */
    I2C_Init(I2C_PORT, &I2C_InitStructure);

对着例程照搬就行。

(三)器件
IIC器件采用MPU9250,一个九轴传感器,八位器件地址0xD0,WHO AM I位 0x75,数据为0x71.


(四)写IIC
写的方法很简单,例程照搬即可:

I2C_StatusType I2C_Master_Transmit(I2C_Type* I2Cx, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  /* Wait until BUSY flag is reset */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_BUSYF, SET, I2C_EVT_CHECK_NONE, Timeout) != I2C_OK)
  {
    return I2C_ERROR_STEP_1;
  }

  /* Disable Pos */
  I2C_NACKPositionConfig(I2Cx, I2C_NACKPosition_Current);

  /* Send START condition */
  I2C_GenerateSTART(I2Cx, ENABLE);

  /* Wait until SB flag is set */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_STARTF, RESET, I2C_EVT_CHECK_NONE, Timeout) != I2C_OK)
  {
    /* Send STOP Condition */
    I2C_GenerateSTOP(I2Cx, ENABLE);

    return I2C_ERROR_STEP_2;
  }

  /* Send slave address for write */
  I2C_Send7bitAddress(I2Cx, DevAddress, I2C_Direction_Transmit);

  /* Wait until ADDR flag is set */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_ADDRF, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
  {
    /* Send STOP Condition */
    I2C_GenerateSTOP(I2Cx, ENABLE);

    return I2C_ERROR_STEP_3;
  }

  /* Clear ADDR flag */
  I2C_ClearADDRFlag(I2Cx);

  while(Size > 0)
  {
    /* Wait until TDE flag is set */
    if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_TDE, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
    {
      /* Send STOP Condition */
      I2C_GenerateSTOP(I2Cx, ENABLE);

      return I2C_ERROR_STEP_4;
    }

    /* Write data to DR */
    I2C_SendData(I2Cx, (*pData++));
    Size--;
  }

  /* Wait until BTF flag is set */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_BTFF, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
  {
    /* Send STOP Condition */
    I2C_GenerateSTOP(I2Cx, ENABLE);

    return I2C_ERROR_STEP_5;
  }

  /* Send STOP Condition */
  I2C_GenerateSTOP(I2Cx, ENABLE);

  return I2C_OK;
}

写入效果:


(五)读效果
雅特力的IIC在读IIC方面有一个比较特殊的处理,会预先读两位,可以通过配置CTL1寄存器第11位阻止预读取。

这里先不处理预读取的问题。
I2C_StatusType I2C_Master_Rec2(I2C_Type* I2Cx, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  /* Wait until BUSY flag is reset */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_BUSYF, SET, I2C_EVT_CHECK_NONE, Timeout) != I2C_OK)
  {
    return I2C_ERROR_STEP_1;
  }

  /* Disable Pos */
  I2C_NACKPositionConfig(I2Cx, I2C_NACKPosition_Current);

  /* Send START condition */
  I2C_GenerateSTART(I2Cx, ENABLE);

  /* Wait until SB flag is set */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_STARTF, RESET, I2C_EVT_CHECK_NONE, Timeout) != I2C_OK)
  {
    /* Send STOP Condition */
    I2C_GenerateSTOP(I2Cx, ENABLE);

    return I2C_ERROR_STEP_2;
  }

  /* Send slave address for write */
  I2C_Send7bitAddress(I2Cx, DevAddress, I2C_Direction_Receive);

  /* Wait until ADDR flag is set */
  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_ADDRF, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
  {
    /* Send STOP Condition */
    I2C_GenerateSTOP(I2Cx, ENABLE);

    return I2C_ERROR_STEP_3;
  }

  /* Clear ADDR flag */
  I2C_ClearADDRFlag(I2Cx);

  while(Size > 0)
  {
      /* Wait until TDE flag is set */
          if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_RDNE, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
          {
              /* Send STOP Condition */
              I2C_GenerateSTOP(I2Cx, ENABLE);
              return I2C_ERROR_STEP_4;
          }

      /* Write data to DR */
      (*pData++) = I2C_ReceiveData(I2Cx);
      Size--;
  }

  if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_BTFF, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
  {
      /* Send STOP Condition */
      I2C_GenerateSTOP(I2Cx, ENABLE);
      return I2C_ERROR_STEP_5;
  }
  /* Send STOP Condition */
  I2C_GenerateSTOP(I2Cx, ENABLE);

  /* Disable Address Acknowledge */
  I2C_AcknowledgeConfig(I2Cx, DISABLE);

  return I2C_OK;
}


读取一个位的效果:
I2C_Master_Rec2(I2C_PORT, I2C_SLAVE_ADDRESS7, tx_buf, 1, 1000);


但是读取多个位的时候,则会出现把总线钳住不释放的现象:
I2C_Master_Rec2(I2C_PORT, I2C_SLAVE_ADDRESS7, tx_buf, 2, 1000);

并且总线被钳住之后,硬件复位是无法释放总线的,也就是第一次出错后,在不断电的情况下,即便复位,下一次程序重新运行,重新初始化,依旧是总线忙的状态,无法写也无法读。

main.zip (2.24 KB)

使用特权

评论回复
沙发
tinnu|  楼主 | 2021-2-19 19:27 | 只看该作者
本帖最后由 tinnu 于 2021-2-19 20:57 编辑

针对多字节发送钳住总线的问题,主要是没有规范地设置ack相关位导致的。
在addr(从机地址)发送完成后,不能简单地清除addr传输的相关位:

而是需要根据剩余字节数判断:

  switch(Size){
  case 1:
      /* Disable Acknowledge */
      I2C_AcknowledgeConfig(I2Cx, DISABLE);
      /* Clear ADDR flag */
      I2C_ClearADDRFlag(I2Cx);
      /* Send STOP Condition */
      I2C_GenerateSTOP(I2Cx, ENABLE);

      if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_RDNE, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
      {
          /* Send STOP Condition */
          I2C_GenerateSTOP(I2Cx, ENABLE);

          return I2C_ERROR_STEP_4;
      }
      break;
  case 2:
      /* Enable Pos */
      I2C_NACKPositionConfig(I2Cx, I2C_NACKPosition_Next);
      /* Disable Acknowledge */
      I2C_AcknowledgeConfig(I2Cx, DISABLE);
      /* Clear ADDR flag */
      I2C_ClearADDRFlag(I2Cx);

      if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_RDNE, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
      {
          /* Send STOP Condition */
          I2C_GenerateSTOP(I2Cx, ENABLE);

          return I2C_ERROR_STEP_4;
      }
      /* Send STOP Condition */
      I2C_GenerateSTOP(I2Cx, ENABLE);
      break;
  default:
      /* Enable Acknowledge */
      I2C_AcknowledgeConfig(I2Cx, ENABLE);
      /* Clear ADDR flag */
      I2C_ClearADDRFlag(I2Cx);
      break;
  }

1-当传输的只有一个字节:
雅特力的IIC寄存器设置特点就是,最后读的一位不应答,因为一旦应答,雅特力的寄存器逻辑就认为需要继续读下一个位:
清除掉ACKEN位,清除掉addr标志位,然后立即设置停止位,这时候才等待本次传输完成。

2-当传输的有两个字节
置位第10位,POSEN位;即本次传输会应答,即默认再预读一位
清除掉ACKEN位,清除掉addr标志位
等待本位传输完成后,雅特力寄存器逻辑会立即开启下一位的预读;
然后再立即设置停止位,第二轮读取完成后,就会发送停止标志。

3-当传输的有三个字节
置位第10位,POSEN位;即本次传输会应答,即默认再预读一位
清除掉addr标志位

后面读取的过程也要额外判断几个状态:
  while(Size > 0)
  {
      /* Wait until TDE flag is set */
      if(Size>=3)
          if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_RDNE, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
          {
              /* Send STOP Condition */
              I2C_GenerateSTOP(I2Cx, ENABLE);

              return I2C_ERROR_STEP_4;
          }

      /* Disable Address Acknowledge */
      if(Size==3) I2C_AcknowledgeConfig(I2Cx, DISABLE);

      /* Write data to DR */
      (*pData++) = I2C_ReceiveData(I2Cx);
      Size--;

      if(Size==2)
      {
          if(I2C_WaitOnFlagUntilTimeout(I2Cx, I2C_FLAG_BTFF, RESET, I2C_EVT_CHECK_ACKFAIL, Timeout) != I2C_OK)
          {
              /* Send STOP Condition */
              I2C_GenerateSTOP(I2Cx, ENABLE);
              return I2C_ERROR_STEP_5;
          }
          /* Send STOP Condition */
          I2C_GenerateSTOP(I2Cx, ENABLE);
      }
  }

1-当剩余字节>=3,需要判断传输完成,剩余2、1个字节时,默认已经启动了停机流程了。
2-而=3传输并读取完成后,需要发送停机,因为雅特力默认会预读后两位
3-同样的=3传输完成后,在读取前需要置位第10位,POSEN位;即本次传输会应答



main2.zip (2.22 KB)

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

71

帖子

0

粉丝