macompengluo 发表于 2019-10-21 19:04:56

STM32L073RZ I2C的问题!!!

    使用“NUCLEO-L073RZ”开发板进行调试。在调试I2C时,遇到了一些问题。
    下面介绍一下我们的使用背景以及遇到的问题:
    - 使用背景
      对外通信是通过I2C接口,L073作为I2C从机,供主机读/写数据
         
    - 遇到的问题
      注:通过一个自制的USB转I2C通信板,与“NUCLEO-L073RZ”开发板进行I2C通信。
    1. 尝试模拟主机向从机的"BYTE ADDRESS"为0x08的地址写入一个字节的数据,假设为0x12。
      通过在线仿真,发现数据已经被正确的写入并保存到相关的数组中了。

      接下来,尝试模拟主机从从机的"BYTE ADDRESS"为0x08的地址读出一个字节的数据,出现的现象是:
      第一次读,可以正确的读出数据为0x12;
      第二次读,读出数据变成了0x00;
      第三次读,可以正确的读出数据为0x12;
      后面都能正确的读出数据为0x12;

   2. 尝试模拟主机向从机的"BYTE ADDRESS"为0x08和0x09的地址连续写入两个字节的数据,假设为0x12和0x34。
       通过在线仿真,发现数据已经被正确的写入并保存到相关的数组中了。

      接下来,尝试模拟主机从从机的"BYTE ADDRESS"为0x08的地址连续读出两个字节的数据,出现的现象是:
      第一次读,可以正确的读出数据为0x12和0x34;
      第二次读,读出数据变成了0x00和0x12;
      第三次读,读出数据变成了0x34和0x12;
      后面读出数据都为0x34和0x12;

   3. 尝试模拟主机向从机的"BYTE ADDRESS"为0x08、0x09和0x0A的地址连续写入三个字节的数据,假设为0x12、0x34和0x56。
       通过在线仿真,发现数据已经被正确的写入并保存到相关的数组中了。

      接下来,尝试模拟主机从从机的"BYTE ADDRESS"为0x08的地址连续读出三个字节的数据,出现的现象是:
      第一次读,可以正确的读出数据为0x12、0x34和0x56;
      第二次读,读出数据变成了0x00、0x12和0x34;
      第三次读,读出数据变成了0x56、0x12和0x34;
      后面读出数据都为0x56、0x12和0x34;
   ... ...
   
另外,我在调试过程中发现一个现象:就是如果Firmware初次运行的时候,如果接收到I2C读一个字节操作的请求,程序会连续进入TXIS中断两次!
但是紧接着再多次发送读一个字节操作的请求,程序就只会进入TXIS一次了!为什么第一次读操作的时候会进入TXIS中断两次?
附I2C初始化及中断部分代码:
/* I2C1 init function */
void MX_I2C1_Init(void)
{
    LL_I2C_InitTypeDef I2C_InitStruct = {0};

    LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

    LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
    /**I2C1 GPIO Configuration
    PB8   ------> I2C1_SCL
    PB9   ------> I2C1_SDA
    */
    GPIO_InitStruct.Pin = I2C1_SCL_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
    GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
    LL_GPIO_Init(I2C1_SCL_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = I2C1_SDA_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
    GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
    LL_GPIO_Init(I2C1_SDA_GPIO_Port, &GPIO_InitStruct);

    /* Peripheral clock enable */
    LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);                           // 使能I2C1的时钟
    RCC->CCIPR &= ~RCC_CCIPR_I2C1SEL;                                             // 选择APB时钟作为I2C1的时钟(Cube代码里没有这一行)


    NVIC_SetPriority(I2C1_IRQn, 0);
    NVIC_EnableIRQ(I2C1_IRQn);


    LL_I2C_EnableAutoEndMode(I2C1);
    LL_I2C_DisableOwnAddress2(I2C1);
    LL_I2C_DisableGeneralCall(I2C1);
    LL_I2C_EnableClockStretching(I2C1);
    I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
    I2C_InitStruct.Timing = 0x20302E37;
    I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
    I2C_InitStruct.DigitalFilter = 0;
    I2C_InitStruct.OwnAddress1 = SLAVE_OWN_ADDRESS;
    I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
    I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
    LL_I2C_Init(I2C1, &I2C_InitStruct);
    LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK);

    LL_I2C_EnableIT_ADDR(I2C1);
    //LL_I2C_EnableIT_NACK(I2C1);
    //LL_I2C_EnableIT_ERR(I2C1);
    LL_I2C_EnableIT_STOP(I2C1);
    //LL_I2C_EnableIT_TC(I2C1);
    //LL_I2C_EnableIT_RX(I2C1);
}


/* USER CODE BEGIN 1 */
void I2C_interrupt_op(void)
{
    uint8_t DoPrefetch = 0;                                                         // 为I2C读操作执行预取数操作的标志,如果不为0,则需要预先取数
    uint8_t Receiverdata;

    //---------------------------------------- Check ADDR flag value in ISR register
    //---------------------------------------- 检查是否为地址匹配中断
    if(LL_I2C_IsActiveFlag_ADDR(I2C1))
    {
      LL_I2C_ClearFlag_ADDR(I2C1);                                                // Clear ADDR flag value in ISR register
      //---------------------------------------- Verify the Address Match with the OWN Slave address
      //---------------------------------------- 检查地址是否为设置的从机地址
      if(LL_I2C_GetAddressMatchCode(I2C1) == SLAVE_OWN_ADDRESS)
      {
            //---------------------------------------- Verify the transfer direction, a read direction, Slave enters transmitter mode */
            //---------------------------------------- 如果传输方向是读操作
            if(LL_I2C_GetTransferDirection(I2C1) == LL_I2C_DIRECTION_READ)
            {
                I2CBusy = TRUE;
                LL_I2C_EnableIT_TX(I2C1);                                           // Enable Transmit Interrupt
            }
            //---------------------------------------- 如果传输方向是写操作
            else if (LL_I2C_GetTransferDirection(I2C1) == LL_I2C_DIRECTION_WRITE)
            {
                LL_I2C_EnableIT_RX(I2C1);                                           // Enable Receive Interrupt
                I2CRxCount = 0;
                I2CBusy = TRUE;
            }
            else
            {
                I2C_error_callback();                                             // Call Error function
            }
      }
      else
      {
            I2C_error_callback();                                                   // Call Error function
      }
    }

    //---------------------------------------- Check TXIS flag value in ISR register
    //---------------------------------------- 发送中断
    else if(LL_I2C_IsActiveFlag_TXIS(I2C1))
    {
      //LL_I2C_DisableIT_TX(I2C1);
      LL_I2C_TransmitData8(I2C1, I2CTxBufData);                                 // Send the Byte requested by the Master
      DoPrefetch = 1;

      //---------------------------------------- 低128字节地址寻址
      if(DataAddr == 127)
      {   
            DataAddr = 0;
      }
      //---------------------------------------- 高128字节地址寻址
                else if(DataAddr == 255)
      {
                        DataAddr = 128;
      }
      //---------------------------------------- 地址索引增1
      else
      {
            DataAddr++;
      }
    }
    //---------------------------------------- Check RXNE flag value in ISR register
    //---------------------------------------- 接收中断
    else if(LL_I2C_IsActiveFlag_RXNE(I2C1))
    {
      Receiverdata = LL_I2C_ReceiveData8(I2C1);                                 // 将RXDR中的数据保存到Receiverdata
      //---------------------------------------- 如果是接收到的Data的第一个字节
            if(I2CRxCount == 0)
      {
            I2CRxCount++;
                   DataAddr = Receiverdata;                                                                // 按照协议规定,Data的第一个字节为MEMORY ADDR
            DoPrefetch = 2;
      }      
      else
      {
            Page00H = Receiverdata;
            //---------------------------------------- 低128字节地址寻址
            if(DataAddr == 127)
            {   
                DataAddr = 0;
            }
            //---------------------------------------- 高128字节地址寻址
            else if(DataAddr == 255)
            {
                DataAddr = 128;
            }
            //---------------------------------------- 地址索引增1
            else
            {
                DataAddr++;
            }
      }      
    }
    //---------------------------------------- Check STOP flag value in ISR register
    //---------------------------------------- STOPF中断
    else if(LL_I2C_IsActiveFlag_STOP(I2C1))
    {
      LL_I2C_ClearFlag_STOP(I2C1);                                                // Clear STOP flag value in ISR register
      I2CBusy = FALSE;      
    }

    else
    {
      I2CBusy = FALSE;
      I2C_error_callback();                                                         // Call Error function
    }

    //---------------------------------------- 如果预取数标志大于0,则需要预先将数据取出并保存到变量I2CTxBufData中去,等待Host去读取
    if (DoPrefetch > 0)
    {
      I2CTxBufData = Page00H;
    }
}

maxtch 发表于 2019-10-22 10:43:36

某处缓存没有清理。

macompengluo 发表于 2019-10-23 13:04:39

maxtch 发表于 2019-10-22 10:43
某处缓存没有清理。

你指的缓存是?能说具体点吗?
多谢了!

maxtch 发表于 2019-10-23 18:33:31

macompengluo 发表于 2019-10-23 13:04
你指的缓存是?能说具体点吗?
多谢了!

FIFO 有残余数据

edmundlee 发表于 2019-10-26 09:16:02

我也认为是FIFO 有残余数据

macompengluo 发表于 2019-10-28 15:10:15

问题已经解决,在I2C地址匹配中断处理的读操作方向分支,要加上 LL_I2C_ClearFlag_TXE(I2C1); 这条语句!!
         //---------------------------------------- 如果传输方向是读操作
            if(LL_I2C_GetTransferDirection(I2C1) == LL_I2C_DIRECTION_READ)
            {
                LL_I2C_ClearFlag_TXE(I2C1);
                LL_I2C_EnableIT_TX(I2C1);                                           // Enable Transmit Interrupt
                I2CEvent = EVENT_OPCOD_SEND;                                        // Set I2C entor transmit mode
            }

lrzxc111 发表于 2019-10-29 08:51:04

macompengluo 发表于 2019-10-28 15:10
问题已经解决,在I2C地址匹配中断处理的读操作方向分支,要加上 LL_I2C_ClearFlag_TXE(I2C1); 这条语句!! ...

好滴,多谢分享,还是LL库的

macompengluo 发表于 2019-10-30 10:21:16

lrzxc111 发表于 2019-10-29 08:51
好滴,多谢分享,还是LL库的

HAL库里面很多操作都限定死了,不是很方便。因为我需要用到很多自己定制化的功能,还是觉得LL更好用,甚至我都想用寄存器直接设置:lol

butterflyspring 发表于 2019-10-30 16:02:38

HAL库可以帮着把外设先调通,然后照着改LL库。过了河~~~~~:lol:lol:lol
页: [1]
查看完整版本: STM32L073RZ I2C的问题!!!