“线程:测试”中讨论的其它部件
您好,
我将 MSP430配置为 i2c 主设备,它正在与两个从设备进行通信:一个电池电量计(LTC2943)和一个电池充电器(LT8491评估板)。 我注意到,当与电池充电器通话时,通过 i2c 总线发送一个重复的字节(这是坏的);让我们称之为“幽灵”字节。 我只在与电池充电器通话时看到这个幻影/重复字节,但从未看到过燃油表。 这两个从属设备都在同一条总线上,我正在对这两个设备执行相同的操作(读取2个字节);请参阅下面的代码。 在经历了几天的沮丧之后,我注意到这两种装置之间有着独特的区别…
我注意到,当我与燃油表(i2c 地址0x64)通话时,i2c 时钟线路(SCL)指示器过低。 但当我与电池充电器(i2c 地址0x10)通话时,SCL 线路指示灯过高。 请注意所提供图片中地址字节之后的 SCL 行行为(链接如下)。 根据 MSP430数据表,据我了解,SCL 线路应在从属线路上推送地址后处于低怠速状态(请参阅 MSP430系列指南的第833页,网址为 :www.ti.com/.../slau367p.pdf)。 有一条评论说:“总线停止(SCL 保持低电平),直到数据可用”。
与此相关的几个问题:
-
为什么这两台设备上的 SCL 线路的空闲情况会有所不同(请参见下图链接),即使它们位于同一个 i2c 总线上,而且我们正在执行相同的2字节读取操作并使用相同的 i2c 驱动程序代码?
-
MSP430系列指南(第833页)的评论中说:“总线停止(SCL 保持较低),直到数据可用”。 在这种情况下,谁的责任是将 SCL 控制在低位? 是主器(MSP430)还是从器件?
-
在系列指南的同一页(第833页)中,有些情况下,数据字节传输过程中需要执行某些操作,这是否是一项硬性要求? 例如,让我们考虑一下我们要将一个字节写入从属设备,然后重复开始,再读取一个字节的情况。 第833页的时序图表明,在传输当前数据字节的过程中,我们需要设置 UCT=0和 UCTXSTT=1。 如果在数据字节传输后设置这些寄存器,会发生什么情况?
-
我在与电池充电器通话时看到的“幽灵”字节基本上是 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()
