📄 smbus.c
字号:
// This file has been prepared for Doxygen automatic documentation generation.
/*! \file ********************************************************************
*
* Atmel Corporation
*
* - File : smbus.c
* - Compiler : IAR EWAVR 4.11A
*
* - Support mail : avr@atmel.com
*
* - Supported devices : ATMEGA406 only.
*
* - AppNote : AVR453 - Smart Battery Reference Design
*
* - Description : SMBus implementation (TWI low-level code & command interpreters).
*
* - Author : rgf-apt
* - Revision : $Revision: 1.25 $
* - Date : $Date: Wednesday, September 07, 2005 07:59:56 UTC $
* - Updated by : $Author: rksnilsberg $
*
*****************************************************************************/
// Revision List:
// June 13, 2005: fixed error in handling receive BLOCK (added use of byte count).
// June 6, 2005: added clearing of SMLOCK after data successfully sent.
// June 5, 2005: changed TWBR init value to be 12 instead of 11 to make 100KHz.
// May 10, 2005: added init of TWBR during Master Transmit mode.
#include "pgmspace.h"
#include <inavr.h>
#define MODULE_SMBUS /* ensure that we instantiate our variables in smbus.h */
#include "smbus.h" //instantiates variables & flash strings
#include "timer.h" //required for BusFault timer activation
#include "pack.h" //required for a few calculations
#include "analog.h" //required for SMBus analog calculations
#include "calibration.h" //required for SMBus analog calculations
//#include "iom406_320.h"
#include <iom406.h> // IAR headerfile for Mega406 (EW 410)
#include "interpret.h" // ONLY include this file in THIS module!
#include "main.h"
#include "pwrmgmt.h"
//extern __flash unsigned char SM_Cmd_Table[0x40][2]; //in interpret.c
typedef unsigned char (*ptr2funcUC_V)(void);
extern ptr2funcUC_V SMB_ReadCmd[];
extern ptr2funcUC_V SMB_WriteCmd[];
//Internally-needed prototypes
unsigned char FastCRC(unsigned char LastCRC, unsigned char newbyte);
//extern unsigned char SMLOCK;
//This is used to check for an out-of-bounds SMBus command.
#define HIGHEST_SMB_CMD 0x3F
//Two-Wire-Interface TWSR (STATUS) values
//Note that since our Prescale value is 0, we don't need to MASK the Status byte.
// Globally applicable TWI status codes:
#define TWS_MASK 0xF8 /* Two-Wire Status Mask */
#define TWS_NSTAT 0xF8 /* No Status Available now */
// MASTER-related Status codes:
#define TWS_START 0x08
#define TWS_RESTART 0x10
#define TWS_WRITE_ACK 0x18 /* sent a SLA+W, got ACK */
#define TWS_WRITE_NAK 0x20 /* sent SLA+W, got NAK */
#define TWS_TXDATA_ACK 0x28 /* Data tx'd & was ACK'd */
#define TWS_TXDATA_NAK 0x30 /* Data tx'd & was NAK'd */
#define TWS_LOST_ARB 0x38 /* lost bus arbitration */
#define TWS_READ_ACK 0x40 /* got ACK from a SLA+R request */
#define TWS_READ_NAK 0x48 /* got NAK from a SLA+R request */
#define TWS_RXDATA_ACK 0x50 /* We rcvd data and ACKd back */
#define TWS_RXDATA_NACK 0x58 /* We rcvd data and we NACKd back */
// SLAVE-related Status codes:
#define TWS_SLA_W 0x60 /* Got SLA + Write */
#define TWS_SLA_R 0xA8 /* Got SLA + Read */
#define TWS_RDATA 0x80 /* Got a data byte */
#define TWS_RCMD 0x80 /* Got a command byte */
#define TWS_RSTOP 0xA0 /* Got a Stop */
#define TWS_REPEAT 0xA0 /* Got a Repeated-Start */
#define TWS_RACK 0xB8 /* Send a data byte and got an ACK back */
#define TWS_RNAK 0xC0 /* Sent a data byte and got a NAK back */
#define TWS_FINAL 0xC8 /* Sent the final byte, got ACK back */
#define TWS_BERR 0x00 /* Saw a Bus Error */
// Two-Wire CONTROL values
#define TWC_GO 0x85 /* clr TWINT; assert ENA & IntEna */
#define TWC_READ_NoACK 0x85 /* read a byte, but don't ACK when done */
#define TWC_START 0xA5 /* send START bit, assert ENA & IntEna */
#define TWC_STOP 0x94 /* leave INT *DISabled* when done */
#define TWC_RESTART 0xB5 /* send STOP, then START again; INT ena */
/* ************************************************************************* */
unsigned char TEST50US = 0;
unsigned char SMLOCK = 0; //prevents Master bus grab attempts WHILE BUS IS IN MASTER MODE.
unsigned char TW_MTxBuf[8]; //Master-mode TX buffer
unsigned char TW_MTxBufCnt = 0; //how many valid bytes are in the buffer
unsigned char TW_MTxBufIndex = 0;
// Note for the buffers below, these must be able to contain:
// Slave Address, SMBus Command, Byte Count (if Block-mode), up to 32 bytes, plus PEC.
unsigned char TW_TxBuf[36]; //must be long enough for any outbound strings
unsigned char TW_TxBufCnt = 0; //how many valid bytes are in the buffer
unsigned char TW_TxBufIndex = 0;
unsigned char TW_RxBuf[10]; //In Application mode (non-ISP mode), only receive WORD commands.
signed char TW_RxBufCnt = 0;
unsigned char TW_RxBufIndex = 0;
//This byte contains flags from the TWI ISR to tell the Foreground code what to do.
//If this byte is ever non-zero, the foreground code will act on its contents.
//Although it is written by both the ISR and the Foreground code, it does not
// need to be declared VOLATILE because the SMBus is halted until the foreground
// code finishes processing the associated command and has cleared this flag byte.
unsigned char TWI_CmdFlags;
#define SMB_GenBusTimeout 1 /* Tell Foreground to generate a bus timeout, as we saw an error! */
#define SMB_SetUpReply 2 /* Have Foreground set up TW_TxBuf[]. */
#define SMB_GotCmdData 4 /* Have Foreground interpret the complete received command. */
unsigned char CurrentCmd = 0xFF;
unsigned char UsePEC = 0; //PEC usage is disabled by default.
/* *************************************************************************
*
* SMBus Initialization routine
*
************************************************************************* */
void InitSMBus(void)
{
SMB_RestoreBus();
TWBCSR = (1<<TWBCIF) | (1<<TWBCIE) | (1<<TWBDT1) | (1<<TWBDT0) | (0<<TWBCIP);
}
/* *************************************************************************
*
* SMBus Wakeup Interrupt
*
************************************************************************* */
//This wakes up a battery from sleep mode into the "On" state, per sbdat110, 4.4.2
#pragma vector = TWI_BUS_CD_vect
__interrupt void TWICD_ISR(void)
{
//clear bits per sbdat110, 4.4.2
SMBvariables[SMBV_BattMode][hibyte] &= ~(0xE3);
if(TWBCSR & (1<<TWBCIP)) //this int was caused by bus DISCONNECT event.
{
TWBCSR &= ~(1<<TWBCIP);
ChangePowerMode(POWERMODE_IDLE,0);
}
else //this int occurred due to a bus CONNECT event.
{
TWBCSR |= (1<<TWBCIP);
ChangePowerMode(POWERMODE_ACTIVE,0);
}
}
/* *************************************************************************
*
* SMBus PA6 Pin Change Interrupt (needed for Master Mode)
*
************************************************************************* */
#pragma vector = PCINT0_vect
__interrupt void PCINT0_ISR(void)
{
TEST50US = 0; //flag to SMBUS.C that bus activity happened
}
#pragma vector = PCINT1_vect
__interrupt void PCINT1_ISR(void)
{
; //unused
}
/* *************************************************************************
*
* Low-Level SMBus Communications State Machine
*
************************************************************************* */
/*
Slave Mode
-------------
Master Mode
-------------
Master mode is initiated by foreground code. When a message is available for
transmission via Master mode (typically due to a timer expiring), it
is placed in the TWI Master TX buffer. However, transmission cannot begin
immediately due to the SMBus requirement that "A bus master is required to
check for bus idle time of 50 us."
To meet this requirement, the AVR's pin-change detection mechanism is used
to monitor for bus idle conditions. PA5 must be wired externally to the
SMBCLK signal (pin 5 on CONN1) to allow this functionality. (Alternately,
in the user's production system, any I/O signal with pin-change capability
can be used instead of PA5 if desired.) PA5 is also used to check for the
SMCLK pin being held low for extended periods by other devices on the bus
as well as checking if the AVR itself doing so locally.
A 3-level system is employed to ensure bus availability. First, the presence
of a message in the transmit buffer results in execution of the foreground code
that manages Master mode. Next, if the Slave state machine is not in the
IDLE state, no attempt is made to take control of the bus. Lastly, PA5 is
checked to be sure it is not currently at a logical zero condition. At this
point the bus appears to be free, so a flag (called TEST50US) is asserted to
indicate that the 50uS test has now begun; the Pin-Change Interrupt for PA5 is
also enabled. The foreground SMBus Master mode management code is then exited.
With a clock speed of only 1MHz, 50 uS corresponds to only 50 instructions at
most. Therefore, when the code is re-entered it is guaranteed that at least
50uS has passed.
When the pin-change interrupt is active, bus activity will
trigger the pin-change interrupt. The corresponding ISR will clear the TEST50US
flag, indicating that the bus is not free. Thus, when the foreground code is
re-entered, if the flag is not asserted but there is a message in the buffer,
it is understood that the test has failed and must be started again; thus the
flag will again be asserted and the routine will be exited.
When the foreground code is eventually re-entered with the flag still asserted,
and if the PA5 pin is not low, and the TWI ISR Slave State Machine is in IDLE,
action is immediately taken to begin transmission while the bus is free. An
additional interlock flag (SMLOCK) is asserted to indicate that Master mode has
been entered by the TWI Hardware, so that the foreground code will not attempt to
repeatedly initiate a transmission for the same message. (Note that checking
for the IDLE state is required due to the possibility that the ISR could have
been activated just after the flag was last asserted. Additionally it is possible
that another slave device is halting the bus by keeping SMCLK low for an extended
period, so this needs to also be checked before attempting to take over the bus.)
If the AVR is successful in taking ownership of the bus, at the completion of the
transmission of the desired Slave address the ISR will be activated with a Status
code of 0x??. As a result, the state machine will now vector into the states
related to Master Mode transmission, and the ISR will handle all further aspects
of the transmission. Alternately, if the TWI module is unsuccessful in taking over
the bus, the TWI ISR will still be entered but with Status codes that are indicative
of an error having occurred. In this latter case, the SMLOCK and TEST50US flags will
be cleared to force another bus takeover attempt in the future.
*/
unsigned char TXmsg[4][4]; //leave room for PEC
volatile unsigned char TXmsgHead = 0;
volatile unsigned char TXmsgTail = 0;
volatile unsigned char TXmsgQty = 0;
#define TXmsgEmpty (TXmsgQty == 0)
#define TXmsgFull (TXmsgQty == 4)
#define TXmsgDelete {++TXmsgTail; TXmsgTail &= (4-1); TXmsgQty--;}
//We will compute the PEC for the message as well.
void MasterInsertMsg(unsigned char addr, unsigned char cmd, unsigned int data)
{ //Note that the Charger address is ALWAYS 0x12! (sbsm100.pdf, 5.2)
//The Host address is always 0x16.
//The addr always assumes WRITE.
//We only do a WORD WRITE.
unsigned char * ptr = TXmsg[TXmsgHead];
*ptr++ = addr;
*ptr++ = cmd;
*ptr++ = (unsigned char) data;
*ptr = (unsigned char)(data>>8);
if(TXmsgFull)
return;
++TXmsgHead;
TXmsgHead &= (4-1);
TXmsgQty++;
}
//TWI Interrupt Service Routine
// This ISR contains a state machine for handling the low-level bus communications.
// It uses the variable TWI_CmdFlags to communicate the need for message processing
// to the foreground-based command interpreter. Whenever it passes control off
// to the foreground routine, the SMBus is halted.
/* Changes to the operation of the SMBus State Machine as of 6/17/2005 (rgf):
Since there can be multiple Masters on the bus addressing a Smart Battery,
namely either a Host or a Charger, it is entirely possible that one Master
may support PEC while the other does not. For maximum reliability, we must
be able to support the reception of PEC on-the-fly regardless of whether it
was used in the past or not.
To do so, we determine an expected byte count WITHOUT PEC. While receiving,
we decrement this counter and store received bytes until either (a) the counter
drops below a value of (-1), or (b) we receive a STOP. If we receive a STOP
and the counter is not either 0 or -1, this is an error and is flagged as such.
(Note that AFTER receiving a STOP, it is not possible to flag an error to the
Host or Charger except through the Battery Status flags.) If the counter is
zero, this indicates that PEC is disabled. If the counter is -1, this indicates
that PEC is enabled. The message will be processed with this knowledge.
However, since the SCL line is not being held low due to TWINT being left asserted,
a different mechanism is needed to hold off incoming messages to the battery
until the previous message has been processed. This is done by turning off
the generation of ACK on any subsequent bytes and/or messages until the foreground
code has finished processing the prior message.
It is crucial therefore that the foreground code does not change the
value of the TWI_CmdFlags variable from being equal to 'SMB_GotCmdData'
until after it is completely done with processing of the prior message.
This mechanism also is valid if a Master attempts to perform a Master Read
while the battery is busy. In this case, the second byte from the Master,
specifically the SMBus Command byte, will not be acknowledged. The Master
is thereby required to generate a STOP condition.
*/
//State Machine states
enum /*TWISR_State*/ {TW_IDLE=0, TW_Wait4Stop, TW_Wait4Cmd, TW_Wait4RW, TW_Wait4Data, TW_ReplyData, TW_MSLA_W, TW_MCMD_W, TW_MDATA_W };
unsigned char TWISR_state = TW_IDLE; //state variable
#pragma vector = TWI_vect
__interrupt void TWI_ISR(void)
{
static unsigned char TWISR_CmdFeatures = 0; //Command-related feature flags
unsigned char Status;
unsigned char tmp;
Status = TWSR & 0xF8; //This identifies what caused the interrupt to fire.
switch(TWISR_state)
{
default:
case TW_IDLE: //If not SLA_W or RSTOP, is an error!
if(TWS_SLA_W == Status) // saw Slave address match with a Write bit
{
if(TWI_CmdFlags == SMB_GotCmdData)
{
//Assert that we're 'busy' to SMBus Master by leaving TWEA *off* until we get a STOP.
//Note that this is 'legal' because we have ALREADY sent an ACK to our Slave Address,
// as is required by the SMBus specification.
TWISR_state = TW_Wait4Stop;
TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWIE); //must NOT re-enable ACKing!
return;
}
else
TWISR_state = TW_Wait4Cmd;
}
else
if(TWS_RSTOP == Status) //Saw a Stop, possibly left over from previous cmd.
{
; //Everything is probably OK. Take no action.
}
else
if(TWS_START == Status) //we have successfully sent a START bit. Handle MASTER mode.
{
TWDR = TW_MTxBuf[TW_MTxBufIndex++];
TWISR_state = TW_MSLA_W;
}
else //had some type of error!
{
SMBvariables[SMBV_BattStatus][lobyte] |= SMBerr_UnknownError;
TWI_CmdFlags = SMB_GenBusTimeout; //generate a bus timeout.
TWCR = (1<<TWEA) | (1<<TWEN); //disable int, and DON'T clear the TWINT flag!
TWISR_state = TW_IDLE; //Reset the state machine.
return;
}
TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE); //must re-enable ACKing
break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -