This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP430FR5969:I2C SCL 线路空闲偏高(应处于低怠速时)

Guru**** 2538950 points


请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1066367/msp430fr5969-i2c-scl-line-idling-high-when-it-should-be-idling-low

部件号:MSP430FR5969
“线程:测试”中讨论的其它部件

您好,

我将 MSP430配置为 i2c 主设备,它正在与两个从设备进行通信:一个电池电量计(LTC2943)和一个电池充电器(LT8491评估板)。 我注意到,当与电池充电器通话时,通过 i2c 总线发送一个重复的字节(这是坏的);让我们称之为“幽灵”字节。 我只在与电池充电器通话时看到这个幻影/重复字节,但从未看到过燃油表。 这两个从属设备都在同一条总线上,我正在对这两个设备执行相同的操作(读取2个字节);请参阅下面的代码。 在经历了几天的沮丧之后,我注意到这两种装置之间有着独特的区别…

我注意到,当我与燃油表(i2c 地址0x64)通话时,i2c 时钟线路(SCL)指示器过低。 但当我与电池充电器(i2c 地址0x10)通话时,SCL 线路指示灯过高。 请注意所提供图片中地址字节之后的 SCL 行行为(链接如下)。 根据 MSP430数据表,据我了解,SCL 线路应在从属线路上推送地址后处于低怠速状态(请参阅 MSP430系列指南的第833页,网址为 :www.ti.com/.../slau367p.pdf)。 有一条评论说:“总线停止(SCL 保持低电平),直到数据可用”。

与此相关的几个问题:

  1. 为什么这两台设备上的 SCL 线路的空闲情况会有所不同(请参见下图链接),即使它们位于同一个 i2c 总线上,而且我们正在执行相同的2字节读取操作并使用相同的 i2c 驱动程序代码?

  2. MSP430系列指南(第833页)的评论中说:“总线停止(SCL 保持较低),直到数据可用”。 在这种情况下,谁的责任是将 SCL 控制在低位? 是主器(MSP430)还是从器件?

  3. 在系列指南的同一页(第833页)中,有些情况下,数据字节传输过程中需要执行某些操作,这是否是一项硬性要求? 例如,让我们考虑一下我们要将一个字节写入从属设备,然后重复开始,再读取一个字节的情况。 第833页的时序图表明,在传输当前数据字节的过程中,我们需要设置 UCT=0和 UCTXSTT=1。 如果在数据字节传输后设置这些寄存器,会发生什么情况?

  4. 我在与电池充电器通话时看到的“幽灵”字节基本上是 MSP430再次将 TXBUF 的内容推离总线上! 即使我们已经将 MSP430配置为处于接收模式,也会发生这种情况。 请注意我如何/何时/何地在代码和逻辑捕获中切换 P4.6 (参见图片)。 为什么会出现这种幻影字节?! 为什么 MSP430再次推出 TXBUF?

缩小了 i2c 通信的图片: https://i.stack.imgur.com/sRJz0.png

使用燃油表放大图片(良好的通信): https://i.stack.imgur.com/NdPqM.png

使用电池充电器放大图片(通信不良): https://i.stack.imgur.com/tAOkp.png


下面是我的代码:

#include <string.h>
#include "driverlib.h"


#define BATTERY_CHARGER_I2C_ADDR    ( 0x10 )
#define FUEL_GAUGE_I2C_ADDR         ( 0x64 )

#define GENERAL_I2C_TIMEOUT_MS      ( 3    ) //When waiting for an i2c flag/condition, timeout after 3 milliseconds so we don't get stuck waiting forever


//local (static) functions
static void init_hardware( void );
static bool hwm_i2c_master_receive_data( uint8_t slave_addr, uint8_t* read_ptr, uint8_t size_bytes );
static bool hwm_i2c_master_send_data( uint8_t slave_addr, uint8_t* write_ptr, uint8_t size_bytes, bool issue_stop );


void main (void)
{

    uint8_t     write_data;
    uint8_t     read_data[2];

    //Initialize HWM manager
    init_hardware();

    while(1)
    {
        __delay_cycles( 1000000 ); //delay for about 1 second (1 cycle ~ 1us)


        //read 2 bytes from fuel gauge
        write_data = 0x08;//address of voltage register
        hwm_i2c_master_send_data( FUEL_GAUGE_I2C_ADDR, &write_data, 1, false );
        hwm_i2c_master_receive_data(FUEL_GAUGE_I2C_ADDR, read_data, 2);


        //read 2 bytes from battery charger
        write_data = 0x28;//address of Rsense1 configuration register
        hwm_i2c_master_send_data( BATTERY_CHARGER_I2C_ADDR, &write_data, 1, false );
        hwm_i2c_master_receive_data( BATTERY_CHARGER_I2C_ADDR, read_data, 2);
    }
} //main()



static void init_hardware( void )
{
    Timer_A_initContinuousModeParam     timerA_cont_param;

    //Disable internal watchdog timer
    WDTCTL = WDTPW | WDTHOLD;

    //Set VLO to drive ACLK with a divider of 1 (i.e. run ACLK at 10KHz)
    CS_initClockSignal( CS_ACLK, CS_VLOCLK_SELECT, CS_CLOCK_DIVIDER_1 );

    //This block of code basically initializes TimerA1 to run continuously at 1KHz
    memset( &timerA_cont_param, 0, sizeof(timerA_cont_param) );
    timerA_cont_param.clockSource                   = TIMER_A_CLOCKSOURCE_ACLK;         //ACLK to drive TimerA1
    timerA_cont_param.clockSourceDivider            = TIMER_A_CLOCKSOURCE_DIVIDER_10;   //Divide ACLK by 10 in order to get 1KHz
    timerA_cont_param.timerInterruptEnable_TAIE     = TIMER_A_TAIE_INTERRUPT_DISABLE;   //Disable TimerA1 overflow interrupt
    timerA_cont_param.timerClear                    = TIMER_A_DO_CLEAR;                 //Clear/reset TimerA1 counter
    timerA_cont_param.startTimer                    = true;                             //Start TimerA1 counter
    Timer_A_initContinuousMode( TIMER_A1_BASE, &timerA_cont_param );


    //Configure P4.6 as an output pin for debugging (timing purposes)
    GPIO_setAsOutputPin( GPIO_PORT_P4, GPIO_PIN6 );
    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    //This block initializes the i2c peripheral
    //Configure pins P1.6 (SDA) and P1.7 (SCL) for I2C (secondary module functionality)
    GPIO_setAsPeripheralModuleFunctionInputPin( GPIO_PORT_P1, ( GPIO_PIN6 | GPIO_PIN7 ), GPIO_SECONDARY_MODULE_FUNCTION );


    PMM_unlockLPM5(); //Clear the LOCKLPM5 bit so the GPIO and i2c configuration takes effect

    //Configure the I2C bus
    EUSCI_B_I2C_initMasterParam i2c_master_init_param = {0};
    i2c_master_init_param.selectClockSource      = EUSCI_B_I2C_CLOCKSOURCE_SMCLK;       //use SMCLK clock signal
    i2c_master_init_param.i2cClk                 = CS_getSMCLK();                       //Give SMCLK freq in Hz
    i2c_master_init_param.dataRate               = EUSCI_B_I2C_SET_DATA_RATE_100KBPS;   //100KBps datarate
    i2c_master_init_param.byteCounterThreshold   = 0;                                   //Don't care because 'no auto stop'
    i2c_master_init_param.autoSTOPGeneration     = EUSCI_B_I2C_NO_AUTO_STOP;            //We will handle the stop bit manually
    EUSCI_B_I2C_initMaster( EUSCI_B0_BASE, &i2c_master_init_param );

    EUSCI_B_I2C_enable(EUSCI_B0_BASE);  //Enable the I2C bus (i.e. pull it out of reset state)

}


static bool hwm_i2c_master_receive_data( uint8_t slave_addr, uint8_t* read_ptr, uint8_t size_bytes )
{
    bool proceed;
    proceed = true;

    //Basic sanity checks on address and size
    if( NULL == read_ptr || 0 == size_bytes )
    {
        return false;
    }

    //Set P4.6 high for debugging (see scope captures)
    GPIO_setOutputHighOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    UCB0I2CSA   =  slave_addr;  //Set slave address
    UCB0CTLW0   &= ~UCTR;       //Set I2C bus in receiver (read) mode
    UCB0IFG     &= ~UCNACKIFG;  //Clear NACK interrupt flag (fresh start)
    UCB0CTLW0   |= UCTXSTT;     //Issue START condition

    //Wait for START condition to complete
    TA1R = 0;
    while( proceed && (UCB0CTLW0 & UCTXSTT) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTT));

    //If size is one byte, request STOP condition now! This is time critical (but not sure why :/)
    if( proceed && (1 == size_bytes) )
    {
        UCB0CTLW0 |= UCTXSTP;
    }

    //Check that we received ACK from slave
    proceed = proceed && (!(UCB0IFG & UCNACKIFG));

    //Loop through and pull the requested number for bytes from the RX buffer
    while( proceed && (size_bytes > 0) )
    {
        //Wait for RX buffer to be ready/full
        TA1R = 0;
        while( (!(UCB0IFG & UCRXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (UCB0IFG & UCRXIFG0);

        if( proceed )
        {
            *read_ptr = UCB0RXBUF;  //Pull byte out of RX buffer
            read_ptr++;             //Increment pointer
            size_bytes--;           //Decrement number of bytes left to read

            //If there's only one byte left to read, request STOP condition. This is time critical (again, not sure why)
            if( 1 == size_bytes )
            {
                UCB0CTLW0 |= UCTXSTP;
            }
        }
    }

    //Wait for the STOP condition to complete
    TA1R = 0;
    while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTP));

    if( !proceed )
    {
        //If we got here, it means something went bad (e.g. timed out or slave NACK'ed),
        //let's request STOP to terminate communication
        UCB0CTLW0 |= UCTXSTP;

        //wait for stop to complete
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    }

    //Clear P4.6
    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    return proceed;
} //hwm_i2c_master_receive_data()


static bool hwm_i2c_master_send_data( uint8_t slave_addr, uint8_t* write_ptr, uint8_t size_bytes, bool issue_stop )
{
    bool proceed;
    proceed = true;

    //Basic sanity checks on address and size
    if( NULL == write_ptr || 0 == size_bytes )
    {
        return false;
    }

    UCB0I2CSA   =  slave_addr;  //Set slave address
    UCB0CTLW0   |= UCTR;        //Set I2C bus in transmit mode
    UCB0IFG     &= ~UCNACKIFG;  //Clear NACK interrupt flag (fresh start)
    UCB0CTLW0   |= UCTXSTT;     //Issue START condition

    //Wait for START condition to complete
    TA1R = 0;
    while( proceed && (UCB0CTLW0 & UCTXSTT) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTT));


    //At this point, we have just pushed the slave address over the i2c line.
    //According to the MSP430 datasheet, the MSP430 would/should hold the
    //SCL line low until we put something in the UCB0TXBUF.
    //In other words, during this delay, the SCL should be held low. This is
    //true when talking with the fuel gauge (LTC2943) but is NOT the case when
    //talking with the battery charger! Why is that?!

    __delay_cycles( 100 ); //delay of ~100us, please notice the SCL line (pictures) during this delay


    //Wait for tx buffer to be ready/empty
    TA1R = 0;
    while( proceed && (!(UCB0IFG & UCTXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (UCB0IFG & UCTXIFG0);

    //Check that we received ACK from slave
    proceed = proceed && (!(UCB0IFG & UCNACKIFG));

    //Loop through and send the data
    while( proceed && (size_bytes > 0) )
    {

        //Set P4.6 high for debugging
        GPIO_setOutputHighOnPin( GPIO_PORT_P4, GPIO_PIN6 );

        //Place byte in tx buffer for transmission
        UCB0TXBUF = *write_ptr;

        //Wait for byte to flush out of tx buffer
        TA1R = 0;
        while( proceed && (!(UCB0IFG & UCTXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (UCB0IFG & UCTXIFG0);

        //Check the ACK after every byte to make sure it went ok
        proceed = proceed && (!(UCB0IFG & UCNACKIFG));

        //Increment write pointer and decrement remaining size
        write_ptr++;
        size_bytes--;
    }

    //If caller requested a STOP condition to be sent
    if( proceed && issue_stop )
    {
        //Issue STOP condition
        UCB0CTLW0 |= UCTXSTP;

        //Wait for STOP condition to go through
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (!(UCB0CTLW0 & UCTXSTP));
    }

    if( !proceed )
    {
        //If we got here, it means something went bad (e.g. timed out or slave NACK'ed),
        //let's request STOP to terminate communication
        UCB0CTLW0 |= UCTXSTP;

        //wait for stop to complete
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    }

    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    return proceed;
} //hwm_i2c_master_send_data()

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    I2C 数据和时钟由两端的开路漏极/集电极输出驱动,因此在未主动驱动时,浮点高(上拉电阻器)。

    时钟通常由主控制器驱动,但如果需要更多时间执行某些操作,则可由从控制器(时钟伸展)保持较低的时间。 主中继器还将保持较低的时钟频率,等待数据输入 TXBUF。 请参阅第459页上的讨论。

    问题可能在代码中出现,而您没有包括代码。 I2C 比 SPI 处理更复杂,有很多方法可以出错。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    谢谢大卫,我计划 在今天晚些时候加入该代码。 我相信,应该是主控制 SCL 线路的低电平,直到将数据输入 TXBUF 为止。 这与我在与  LTC2943通话时看到的内容一致。 但我不理解为什么在与 LT8491交谈时,该线路被拉高,它们都使用相同的代码。 我知道,如果不看守则,可能很难回答这一问题;我确实计划在今天晚些时候分享这一信息。 但我希望你至少能看到逻辑捕获(SCL 行)与我的图片之间的区别。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我刚刚意识到,您发布的链接指向的指南(slau144)未涵盖此线程标记的部件号(fr5969)。 其中之一肯定是错误的。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    好的观察:)。 在我发布问题后,我也发现了这一点。 我刚刚使用正确的系列指南(SLAU367P)更新了原始帖子

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    大家好 ,我刚刚更新了原始帖子:提供了代码和更新的图片。 请看图片,我添加了一些注释,清楚地显示了我遇到的问题。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您应该阅读定时器 A 部分。建议在更改 TA1R 等内容之前停止定时器。 在读取 TA1R 之前,当时钟与此处的时钟异步时。 这可能不会在这里造成麻烦,但可能会再次造成麻烦。 或在稍后的随机时间引起问题。

    此外,我从未使用过驱动程序库,但我怀疑自己在没有调用 API 来配置 MCLK 或 SMCLK 的情况下调用 CS_getSMCLK()。 它可能具有或不具有适当的默认值。

    您可以在两台设备上使用相同的代码获得不同的结果。 更改访问顺序时,结果是否会有所不同? 如果是这样,请检查硬件在功能结束时处于什么状态,以及硬件从一开始就会发生什么变化。

    在接收数据时,如果在读取 RXBUF 之前执行此操作,您可以发送停止条件。 读取 RXFUF 会导致硬件生成 ACK 并开始接收下一个字节。 因此,如果您在读取 RXFUF 后设置停止位,则停止会在它完成接收该字节后发生。

    哦,看来您已经将 MCLK 的默认设置为1MHz。 因此,每个数据字节只有80个 MCLK。 这可能看起来很像,但如果您不知道,它可能会在意外的时间点造成延迟。 像那场比赛一样,在收到下一个字节之前设置停止标志。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    ,谢谢你的反馈! 我有几件事要与大家分享:


    1.我修改了代码,使我不再使用 TimerA 外设(即 我不再使用任何计时器)。 问题仍然存在。

    2.我修改了代码,使我不再使用任何 driverlib 函数(即 我正在对 MSP430上的时钟,寄存器和 i2c 总线进行手动配置,并通过寄存器进行注册)。 问题仍然存在。

    3.我尝试按照您的建议颠倒与设备的通信顺序(即先与电池充电器通话,然后与电量计通话,然后再切换该顺序)。 问题仍然存在,并且不管电池充电器的顺序如何,它始终关注电池充电器。

    但当我对代码进行调色时,我实际上能够在与 另一个“好设备”(燃油表)通话时出现幻影字节问题。 我想我知道 MSP430在什么情况下将这个“幽灵”字节推过 i2c,但我不明白为什么。  基本上,如果我尝试在前一字节的 ACK 发出期间或之后发出重复启动条件,MSP430会将 TXBUF 的内容推向线路上。 请看一下这条评论所附的图片,我添加了一条解释,以显示导致出现“幽灵字节”的条件。

    从我所能知,这种幻影字节在电池充电器上更显/更频繁的原因是 ,在 MSP430传输的任何字节(包括 i2c 地址字节或任何其他数据字节)之后,SCL 线路很快就会被拔出。 当 SCL 线路被如此快速地拉起来时,它会使 MSP430比预期更快地注册 ACK,因此我们执行的任何操作(例如发送重复启动)都将错过它的“机会之窗”。

    现在我还有两个问题:
    1.为什么在与电池充电器通话时 SCL 线路会快速拉高? 例如,在发送电池充电器地址后,MSP430应使 SCL 线路处于低怠速状态,直至将数据字节写入 TXBUF。 据我了解,i2c 从属设备不应像这样拉高线路。

    2.为什么我们需要切换到接收器模式并在当前字节期间设置起始位(如所附图片所示)? 如果在当前字节完成传输后执行这两项操作,为什么会出现问题? (您对停止条件的解释和读取 RXFUF 是有意义的,但现在我正在寻找一个类似的解释,具体说明为什么我们需要切换到接收模式并在当前字节完成刷新之前发出启动条件)。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    I2C 总线上的设备只能将信号线路下拉。 通常,时钟只由主控器(MSP430)下拉,但从控器可以在需要额外时间时拉伸时钟。 我为这两台设备找到了数据表,其中一台速度较慢(最高100KHz),另一台速度较快。 (至少400kHz),因此它们的时间可能不同。

    MCLK 的运行速度超过1MHz 应该有助于应对比赛条件,如在字节移出前请求重复启动。 您的代码组织是非常规的,并导致了这一问题。 以较高频率运行不会对功耗产生太大的影响,因为 MSP430在大多数情况下都应该处于低功耗模式下。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    感谢大卫 ,我又看了这两台设备(LTC2943和 LT8491)的数据表,我相信这两台设备都支持100KHz I2C 总线速度,所以在时间方面不应该有冲突。
    从 LTC2943数据表中:“I2C 总线上的数据可以在标准模式下以高达100kbit/s 的速率传输,在快速模式下以高达400kbit/s 的速率传输。”
    从 LT8491数据表中:“充电器支持 I2C 接口高达100kHz 的时钟速度。”

    我确实同意我的守则是非常规的,故意不是快速/高效的。 我在编写此 i2c 驱动程序时的意图是建立一种机制,防止我们在信号或条件上无限期等待(即构建一些超时以防止锁定);这就是为什么我使用 TimerA 外围设备超时的原因。 我不是追求速度/效率,而是追求“稳健性”。 我可能最终改写了我的整个 i2c 驱动程序,以利用中断,这会有更“负责任的计时行为”。 您是否碰巧知道一个很好的 i2c 代码示例,我可以引用哪个代码使用中断? (我对使用中断的另一个担忧是,我们可能会陷入一种状况,我们正在等待某种中断发生,但有些东西会被锁定,我们会一直等待中断发生)。 注:我编写的代码可能会在一个非常偏远的位置运行,没有人可以访问该位置执行软件更新,因此我的目标是编写一个非常强大的 i2c 驱动程序。

    无论如何,即使我转而使用中断,我仍有两个未决问题要理解:

    1.为什么在给蓄电池充电器寻址后 SCL 被拉高? 根据 MSP430规范/数据表,MSP430应将线路保持在低位,直到写入 TXBUF。
    2.为什么 MSP430数据表要求我在数据字节窗口期间设置(UCTR=0和 UCTXSTT =1)? 我很好奇为什么会这样。 如果我们将此操作延迟到字节通过总线被清除后,它会对硬件 i2c 状态机有什么作用?

    再次感谢大家的热烈讨论。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    大家好,

    我继续看到 SCL 行的奇怪行为。 我完全删除并简化了我的代码,并创建了一个非常简单的测试来复制此问题。 我在以下链接中发布了一个新问题/主题,其中包括新的代码,原理图和逻辑捕获:
    https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1069857/msp-exp430fr5969-i2c-scl-line-behavior-after-address-byte

    令我感到困惑的是,为什么 MSP430不能按预期控制 SCL 线路。 如果您能够查看新帖子并帮助回答我在其中发布的3个问题,我将不胜感激。

    非常感谢您的帮助,

    阿默尔