SMBus (I2C) sending extra ACK then intended - embedded

I'm trying to get a basic handshake going. Below is the ISR for the C8051F120's SMBus (System Management Bus). I'm trying to implement an I2C device on it (ads1115 7addr 0x48 for those who are curious). Note this is mainly the example given by silicon labs for the F120.
void SMBUS_ISR (void) interrupt 7
{
bit FAIL = 0; // Used by the ISR to flag failed
// transfers
static unsigned char sent_byte_counter;
static unsigned char rec_byte_counter;
// Status code for the SMBus (SMB0STA register)
switch (SMB0STA)
{
// Master Transmitter/Receiver: START condition transmitted.
// Load SMB0DAT with slave device address.
case SMB_START: //0x08
// Master Transmitter/Receiver: repeated START condition transmitted.
// Load SMB0DAT with slave device address
case SMB_RP_START: //0x10
SMB0DAT = TARGET; // Load address of the slave.
SMB0DAT &= 0xFE; // Clear the LSB of the address for the
// R/W bit
SMB0DAT |= SMB_RW; // Load R/W bit
STA = 0; // Manually clear STA bit
rec_byte_counter = 1; // Reset the counter
sent_byte_counter = 1; // Reset the counter
break;
// Master Transmitter: Slave address + WRITE transmitted. ACK received.
// For a READ: N/A
//
// For a WRITE: Send the first data byte to the slave.
case SMB_MTADDACK: //0x18
SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1];
sent_byte_counter++;
break;
// Master Transmitter: Slave address + WRITE transmitted. NACK received.
// Restart the transfer.
case SMB_MTADDNACK: //0x20
STA = 1; // Restart transfer
break;
// Master Transmitter: Data byte transmitted. ACK received.
// For a READ: N/A
//
// For a WRITE: Send all data. After the last data byte, send the stop
// bit.
case SMB_MTDBACK: //0x28
if (sent_byte_counter <= NUM_BYTES_WR)
{
// send data byte
SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1];
sent_byte_counter++;
}
else
{
STO = 1; // Set STO to terminate transfer
SMB_BUSY = 0; // And free SMBus interface
}
break;
// Master Transmitter: Data byte transmitted. NACK received.
// Restart the transfer.
case SMB_MTDBNACK: //0x30
STA = 1; // Restart transfer
break;
// Master Receiver: Slave address + READ transmitted. ACK received.
// For a READ: check if this is a one-byte transfer. if so, set the
// NACK after the data byte is received to end the transfer. if not,
// set the ACK and receive the other data bytes.
//
// For a WRITE: N/A
case SMB_MRADDACK: //0x40
if (rec_byte_counter == NUM_BYTES_RD)
{
AA = 0; // Only one byte in this transfer,
// send NACK after byte is received
}
else
{
AA = 1; // More than one byte in this transfer,
// send ACK after byte is received
}
break;
// Master Receiver: Slave address + READ transmitted. NACK received.
// Restart the transfer.
case SMB_MRADDNACK: //0x48
STA = 1; // Restart transfer
break;
// Master Receiver: Data byte received. ACK transmitted.
// For a READ: receive each byte from the slave. if this is the last
// byte, send a NACK and set the STOP bit.
//
// For a WRITE: N/A
case SMB_MRDBACK: //0x50
if (rec_byte_counter < NUM_BYTES_RD)
{
SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
AA = 1; // Send ACK to indicate byte received
rec_byte_counter++; // Increment the byte counter
}
else
{
AA = 0; // Send NACK to indicate last byte
// of this transfer
}
break;
// Master Receiver: Data byte received. NACK transmitted.
// For a READ: Read operation has completed. Read data register and
// send STOP.
//
// For a WRITE: N/A
case SMB_MRDBNACK: //0x58
SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
STO = 1;
SMB_BUSY = 0;
AA = 1; // Set AA for next transfer
break;
// Master Transmitter: Arbitration lost.
case SMB_MTARBLOST: //0x38
FAIL = 1; // Indicate failed transfer
// and handle at end of ISR
break;
// All other status codes invalid. Reset communication.
default:
FAIL = 1;
break;
}
if (FAIL) // If the transfer failed,
{
SMB0CN &= ~0x40; // Reset communication
SMB0CN |= 0x40;
STA = 0;
STO = 0;
AA = 0;
SMB_BUSY = 0; // Free SMBus
FAIL = 0;
}
SI = 0; // Clear interrupt flag
}
//-----------------------------------------------------------------------------
// Support Functions
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// SMB_Write
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters : None
//
// Writes a single byte to the slave with address specified by the <TARGET>
// variable.
// Calling sequence:
// 1) Write target slave address to the <TARGET> variable
// 2) Write outgoing data to the <SMB_DATA_OUT> array
// 3) Call SMB_Write()
//
void SMB_Write (void)
{
char SFRPAGE_SAVE = SFRPAGE; // Save Current SFR page
SFRPAGE = SMB0_PAGE;
while (SMB_BUSY); // Wait for SMBus to be free.
SMB_BUSY = 1; // Claim SMBus (set to busy)
SMB_RW = 0; // Mark this transfer as a WRITE
STA = 1; // Start transfer
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page detector
}
//-----------------------------------------------------------------------------
// SMB_Read
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters : None
//
// Reads a single byte from the slave with address specified by the <TARGET>
// variable.
// Calling sequence:
// 1) Write target slave address to the <TARGET> variable
// 2) Call SMB_Write()
// 3) Read input data from <SMB_DATA_IN> array
//
void SMB_Read (void)
{
char SFRPAGE_SAVE = SFRPAGE; // Save Current SFR page
SFRPAGE = SMB0_PAGE;
while (SMB_BUSY); // Wait for bus to be free.
SMB_BUSY = 1; // Claim SMBus (set to busy)
SMB_RW = 1; // Mark this transfer as a READ
STA = 1; // Start transfer
while (SMB_BUSY); // Wait for transfer to complete
SFRPAGE = SFRPAGE_SAVE; // Restore SFR page detector
}
The main continuously does the following: Sends 3 bytes. The first byte is the device register pointer. Then reads the same register (since the pointer is already set). It does do this.
while (1)
{
TARGET = SLAVE_ADDR; // Target the Slave for next SMBus
// transfer
SMB_DATA_OUT[0] = 0x01; // Device register
SMB_DATA_OUT[1] = 0x0A; // Register MSByte
SMB_DATA_OUT[2] = 0x03; // Register LSbyte
SMB_Write(); // Initiate SMBus write
// SMBus Read Sequence
TARGET = SLAVE_ADDR; // Target the Slave for next SMBus
// transfer
SMB_Read();
}
And here is a trace capture of transfer:
Looks to me like the master receive is sending an extra ACK. So my main focus has been on cases:
SMB_MRADDACK: //0x40
SMB_MRADDNACK: //0x48
SMB_MRDBACK: //0x50
My main focus is more so SMB_MRADDNACK: //0x48 and the number of times it goes through that if statement during the ISR calls. I'm having a little trouble wrapping my head around the exact failure point. So where is this extra ACK coming from? I'll look back here Monday afternoon if I don't figure it out myself by then.
Bonus question: Is there a embedded stack exchange of some sort? Didn't see anything that stood out for me in the communities..

Your trace shows (excluding addressing) three bytes sent and three bytes read. I'm assuming you desired to write three bytes and then only read two bytes. If that's true, then the problem is more than just a spurious ACK because your master continues clocking the third byte in as well.
If you desire to read only two bytes with the sample code from SiLabs1, you need to define NUM_BYTES_RD to 2 instead of the provided 3. That value is used in the SMB_MRADDACK and SMB_MRDBACK states to decide whether to ACK or STOp.
Just in case (since you ask about ACKs instead of extra bytes), if your question is about the final drop in the SDL line on your trace (after the 0xff) because you're afraid that's an extra ACK, then worry not. That is a STO (rises during high SCL) and is correct behavior for a Master terminating a transmission.
Edit: klamb is correct in the comments below below, there is bug in the SMB_MRDBACK state. Saving SMB0DAT and incrementing rec_byte_counter should happen before checking rec_byte_counter against NUM_BYTES_RD. Suprising that got out of SiLabs like that.

case SMB_MRDBACK: //0x50
SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
rec_byte_counter++; // Increment the byte counter
if (rec_byte_counter < NUM_BYTES_RD)
{
AA = 1; // Send ACK to indicate byte received
}
else
{
AA = 0; // Send NACK to indicate last byte
// of this transfer
}
break;

Related

The SPI shift register does not always send all data to the RX BUFFER (TI microcontroller)

I have connected 2 TI controllers via SPI. The TMS320F28055 controller is my master and the TMS320F2885 controller is my slave. I want to send complete data to the slave via spi. The data always ends up successfully in the SPIDAT register, i.e. the shift register. The shift register should then send the data to the SPIRXBUF - Buffer. Sometimes the data is successfully sent to the buffer and sometimes not it's always very random I've tried a lot. I don't use a FIFO. Does anyone know how I can fix the problem.
I made a table showing the data in the master and slave registers. I also send the configuration of the slave and master.
void spi_init(void)
{
SpiaRegs.SPICTL.all = 0x000E; //Normal SPI clocking scheme(Data in latch on rising edge)master, 4-pin option, No interrupt
SpiaRegs.SPICTL.bit.CLK_PHASE = 1; //1
SpiaRegs.SPIBRR = 0x0077; //BateRate 0.5MHz
SpiaRegs.SPICCR.all = 0x0087; //SPI is ready to transmit or receive the next character.
SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; //0
SpiaRegs.SPIPRI.bit.FREE = 1;
}
This is the code from my master, I use the TMS320F28055:
void spi_init(void)
{
SpiaRegs.SPICCR.bit.SPISWRESET = 0;
SpiaRegs.SPICTL.all = 0x000A; //8 //Normal SPI clocking scheme(Data in latch on rising edge)slave, 4-pin option, No interrupt
SpiaRegs.SPICTL.bit.CLK_PHASE = 1; //1
SpiaRegs.SPIBRR = 0x0077; //BateRate 0.5MHz ist für den Slave nicht notwendig
SpiaRegs.SPICCR.all = 0x0087; //SPI is ready to transmit or receive the next character.
SpiaRegs.SPICCR.bit.CLKPOLARITY = 0; //0
SpiaRegs.SPICTL.bit.SPIINTENA = 1 ;
SpiaRegs.SPICTL.bit.OVERRUNINTENA = 1 ;
SpiaRegs.SPIPRI.bit.FREE = 1;
SpiaRegs.SPICCR.bit.SPISWRESET=1;
}
And this is the code from my slave TMS320F28035.
I'm using an interrupt here, but I've also tried it without an interrupt.
uint16_t pdata = 0x1234;
int dataH, dataL;
dataH = 0;
dataL = 0;
dataH = (pdata >> 8);
dataL = (pdata & 0x00FF);
spi_xmit(dataH);
spi_xmit(dataL);
And with that I send example data, in this case it would be the 0x1234. When I send it it arrives successfully in the shift register and buffer. But if I want to send it more often, the shift register does not completely shift the data into the buffer. To check I debug both microcontrollers at the same time. By the way, I send 8 bits twice in a row. the buffer has a size of 16 bits.

PIC18F26Q43 SPI Slave does not receive data

I want two PIC18 to communicate by SPI. The slave has the new SPI module with FIFOs and separate RX and TX registers. For some reason I cannot receive data. I guess I misconfigure the module.
I also use the SS interrupt to prepare the transfer. Upon chip select I swap buffers and initalize counters. This part works. I also see the clock and data signal of the master on the oscilloscope.
However, the interrupts for TX and RX are not fired.
Here is the configuration:
void spiInit() {
spiTransferComplete=0;
// Chip not ready
CR=1;
// clear EN
SPI1CON0bits.EN=0;
// Set PPS
SPI1SSPPS=0b00000110; // SS RA6 > Chip select input
SPI1SCKPPS=0b00001010; // SCK > RB2
SPI1SDIPPS=0b00001011; // SDI > RB3
RB4PPS=0x32; // SDO > RB4
TRISB2=1;
TRISB3=1;
SPI1CON0=0b00000000; // EN=0, LSBF=0, MST=0, BMODE=0
SPI1CON1=0b00000100; // SMP=0, CKE=0, CKP=0, SSP=1, SDI=0, SDO=0
SPI1CON2=0b00000011; // SSET=0, TXR=1, RXR=1
// Interrupts
SPI1INTEbits.SOSIE=1;
SPI1INTEbits.EOSIE=1;
SPI1INTEbits.SPI1EOSIE=1;
SPI1INTEbits.SPI1SOSIE=1;
SPI1INTEbits.RXOIE=1;
PIE3bits.SPI1IE=1;
PIE3bits.SPI1RXIE=1;
PIE3bits.SPI1TXIE=1;
// enable EN
SPI1CON0bits.EN=1;
}
and this is the interrupt routine:
void spiInterruptHandler() {
// Start SS
if (SPI1INTEbits.SPI1SOSIE==1 && SPI1INTFbits.SPI1SOSIF==1) {
switchSpiBuffer(); // prepare sending
spiSendCsr=1;
spiRecvCsr=0;
spiRecv.pck.U1len=0;
spiRecv.pck.U2len=0;
spiRecv.pck.U3len=0;
spiRecv.pck.U4len=0;
SPI1TXB=spiSend->cBuf[spiSendCsr]; // preload first byte
CR=0; // ready to send
SPI1INTFbits.SPI1SOSIF=0;
}
// end of SS
if (SPI1INTEbits.SPI1EOSIE==1 && SPI1INTFbits.SPI1EOSIF==1) {
CR=1;
if (spiTransferComplete)
spiTransferComplete(&(spiRecv.pck));
SPI1INTFbits.SPI1EOSIF=0;
}
// byte received
if (PIR3bits.SPI1RXIF==1) {
spiRecv.cBuf[spiRecvCsr++]=SPI1RXB;
}
// byte sent
if (PIR3bits.SPI1TXIF==1) {
SPI1TXB=spiSend->cBuf[spiSendCsr++];
}
}
It was a classic. To use a digital IO properly you have to clear the according ANSEL bits for the multiplexed analog inputs. I expected MCC to do this for me but in this case it did not happen. So: always double check if TRIS and ANSEL are set correctly.

How to setup an interrupt driven SPI with stm32F4

I'am using STM32F4 board with CMSIS library and I want setup an interrupt driven SPI, it means an interrupt is triggered each time a byte is sent by the SPI peripheral. The initiaisation function is as below:
void init_SPI1(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStruct;
SPI_InitTypeDef SPI_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5|GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// connect SPI1 pins to SPI alternate function
//GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
//Set chip select high
GPIOA->BSRRL |= GPIO_Pin_4; // set PE4 high
// enable peripheral clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* configure SPI1 in Mode 0
* CPOL = 0 --> clock is low when idle
* CPHA = 0 --> data is sampled at the first edge
*/
SPI_StructInit(&SPI_InitStruct); // set default settings
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
SPI_InitStruct.SPI_Mode = SPI_Mode_Master; // transmit in master mode, NSS pin has to be always high
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // clock is low when idle
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // data sampled at first edge
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft ; // set the NSS management to internal and pull internal NSS high
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
SPI_Init(SPI1, &SPI_InitStruct);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* Enable SPI1*/
SPI_Cmd(SPI1, ENABLE);
return;
}
Then i just loopback SPI_MOSI to SPI_MISO and use a function that transmit the data (a very basic function that takes data from a buffer and then uses CMSIS functions for the transmission). The problem is that when the SPI interrupt is triggered, the program won't get out from the handler. the handler function looks lihe this:
void SPI1_IRQHandler()
{
int a;
a++;
SPI_I2S_ClearITPendingBit(SPI1,SPI_I2S_IT_TXE);
return;
}
Is it a problem in the CMSIS library, or I am not configuring the SPI interrupt in the good way? Please guide me to the right point.
EDIT
This is the function i use for data transmission
void write_SPI1()
{
int i;
for (i=0;i<SPI_TX_MAX; i++)
{
SPI_I2S_SendData(SPI1,spiTxBuff[i]);
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
}
}
and the interruption deals with the data reception, it just fill spiRxBuff when receiving new data.
void SPI1_IRQHandler()
{
while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)== RESET);
spiRxBuff[spiRxCount]= SPI_I2S_ReceiveData(SPI1);
spiRxCount++;
}
The variable used for Reception / Transmission are declared as below :
uint8_t spiTxBuff[SPI_TX_MAX] = {0x01,0x02,0x03,0x04,0x05,0x06};
uint8_t spiRxBuff[SPI_RX_MAX];
static volatile int spiRxCount= 0; // used in SPI1_IRQHandler
what is strange now is that i'am having {0x01,0x02,0x03,0x05,0x06} in spiRxBuff instead of {0x01,0x02,0x03,0x04,0x05,0x06}, but using debug mode the data in spiRxBuff are correct, what goes wrong in your opinion ?
You did not show the function doing the transmit, so I don't know exactly what are you trying to accomplish
Transmitting in a loop
If you are transmitting from a function (in a loop), then you don't need interrupts at all, just make sure that the TXE flag is set before you transmit. Note that you have to interleave sending and receiving somehow.
void SPI1_Transmit(uint8_t *send, uint8_t *receive, int count) {
while(count-- > 0) {
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET) {
if(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==SET)
*receive++ = SPI_I2S_ReceiveData(SPI1);
}
SPI_I2S_SendData(SPI1, *send++);
}
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET) {
/* wait for the last incoming byte */
}
*receive++ = SPI_I2S_ReceiveData(SPI1);
}
Transmitting from interrupt
The TXE interrupt flag is set as long as the SPI device is not busy sending. If you don't do something about it in the interrupt handler, it will trigger an interrupt immediately again and again. You can't clear it manually, but by transmitting another byte, and resetting the transmit interrupt enable flag before sending the last byte.
volatile int spi1_tx_count, spi1_rx_count;
uint8_t *spi1_tx_ptr;
volatile uint8_t *spi1_rx_ptr;
/* set these global variables before enabling interrupts */
void SPI1_IRQHandler() {
if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_TXE) == SET) {
if(--spi1_tx_count < 1)
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, DISABLE);
SPI_I2S_SendData(SPI1, *spi1_tx_ptr++);
}
if(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET) {
*spi_rx_ptr++ = SPI_I2S_ReceiveData(SPI1);
spi1_rx_count++;
}
}
Using DMA
The above examples are using processor power and cycles for a task that can be handled by the DMA conroller alone. A lot of (if not all) processor cycles, if you are talking to a peripheral at 2 MBit/s.
See Project/STM32F4xx_StdPeriph_Examples/SPI/SPI_TwoBoards in the library for an example.
Sorry, I haven't noticed at all that you've amended the question. Look like notifications are sent on new comments or answers, but not on edits.
There are multiple problems with your code. In write_SPI1(), I'd enable RX interrupt only once before the loop, there is no need to do it again and again. Also, you should definitely check whether the TX register is available before sending.
void write_SPI1() {
int i;
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
for (i=0;i<SPI_TX_MAX; i++) {
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET)
;
SPI_I2S_SendData(SPI1,spiTxBuff[i]);
}
}
It is however a bad idea to wait on a flag in the interrupt handler. If RXNE is the only possible interrupt source, then you can proceed straight to receiving.

No r/w bit made available to firmware by I2C peripheral of STM32F40x chips

I was wondering if anyone has found a way to determine the intention of a master communicating with an stm32f40x chip? From the perspective of the firmware on the stm32f40x chip, the ADDRess sent by the master is not available, and the r/w bit (bit 0 of the address) contained therein is also not available. So how can I prevent collisions? Has anyone else dealt with this? If so what techniques did you use? My tentative solution is below for reference. I delayed any writes to the DR data register until the TXE interrupt occurs. I thought at first this would be too late, and a byte of garbage would be clocked out, but it seems to be working.
static inline void LLEVInterrupt(uint16_t irqSrc)
{
uint8_t i;
volatile uint16_t status;
I2CCBStruct* buffers;
I2C_TypeDef* addrBase;
// see which IRQ occurred, process accordingly...
switch (irqSrc)
{
case I2C_BUS_CHAN_1:
addrBase = this.addrBase1;
buffers = &this.buffsBus1;
break;
case I2C_BUS_CHAN_2:
addrBase = this.addrBase2;
buffers = &this.buffsBus2;
break;
case I2C_BUS_CHAN_3:
addrBase = this.addrBase3;
buffers = &this.buffsBus3;
break;
default:
while(1);
}
// ...START condition & address match detected
if (I2C_GetITStatus(addrBase, I2C_IT_ADDR) == SET)
{
// I2C_IT_ADDR: Cleared by software reading SR1 register followed reading SR2, or by hardware
// when PE=0.
// Note: Reading I2C_SR2 after reading I2C_SR1 clears the ADDR flag, even if the ADDR flag was
// set after reading I2C_SR1. Consequently, I2C_SR2 must be read only when ADDR is found
// set in I2C_SR1 or when the STOPF bit is cleared.
status = addrBase->SR1;
status = addrBase->SR2;
// Reset the index and receive count
buffers->txIndex = 0;
buffers->rxCount = 0;
// setup to ACK any Rx'd bytes
I2C_AcknowledgeConfig(addrBase, ENABLE);
return;
}
// Slave receiver mode
if (I2C_GetITStatus(addrBase, I2C_IT_RXNE) == SET)
{
// I2C_IT_RXNE: Cleared by software reading or writing the DR register
// or by hardware when PE=0.
// copy the received byte to the Rx buffer
buffers->rxBuf[buffers->rxCount] = (uint8_t)I2C_ReadRegister(addrBase, I2C_Register_DR);
if (RX_BUFFER_SIZE > buffers->rxCount)
{
buffers->rxCount++;
}
return;
}
// Slave transmitter mode
if (I2C_GetITStatus(addrBase, I2C_IT_TXE) == SET)
{
// I2C_IT_TXE: Cleared by software writing to the DR register or
// by hardware after a start or a stop condition or when PE=0.
// send any remaining bytes
I2C_SendData(addrBase, buffers->txBuf[buffers->txIndex]);
if (buffers->txIndex < buffers->txCount)
{
buffers->txIndex++;
}
return;
}
// ...STOP condition detected
if (I2C_GetITStatus(addrBase, I2C_IT_STOPF) == SET)
{
// STOPF (STOP detection) is cleared by software sequence: a read operation
// to I2C_SR1 register (I2C_GetITStatus()) followed by a write operation to
// I2C_CR1 register (I2C_Cmd() to re-enable the I2C peripheral).
// From the reference manual RM0368:
// Figure 163. Transfer sequence diagram for slave receiver
// if (STOPF == 1) {READ SR1; WRITE CR1}
// clear the IRQ status
status = addrBase->SR1;
// Write to CR1
I2C_Cmd(addrBase, ENABLE);
// read cycle (reset the status?
if (buffers->txCount > 0)
{
buffers->txCount = 0;
buffers->txIndex = 0;
}
// write cycle begun?
if (buffers->rxCount > 0)
{
// pass the I2C data to the enabled protocol handler
for (i = 0; i < buffers->rxCount; i++)
{
#if (COMM_PROTOCOL == COMM_PROTOCOL_DEBUG)
status = ProtProcRxData(buffers->rxBuf[i]);
#elif (COMM_PROTOCOL == COMM_PROTOCOL_PTEK)
status = PTEKProcRxData(buffers->rxBuf[i]);
#else
#error ** Invalid Host Protocol Selected **
#endif
if (status != ST_OK)
{
LogErr(ST_COMM_FAIL, __LINE__);
}
}
buffers->rxCount = 0;
}
return;
}
if (I2C_GetITStatus(addrBase, I2C_IT_AF) == SET)
{
// The NAck received from the host on the last byte of a transmit
// is shown as an acknowledge failure and must be cleared by
// writing 0 to the AF bit in SR1.
// This is not a real error but just how the i2c slave transmission process works.
// The hardware has no way to know how many bytes are to be transmitted, so the
// NAck is assumed to be a failed byte transmission.
// EV3-2: AF=1; AF is cleared by writing ‘0’ in AF bit of SR1 register.
I2C_ClearITPendingBit(addrBase, I2C_IT_AF);
return;
}
if (I2C_GetITStatus(addrBase, I2C_IT_BERR) == SET)
{
// There are extremely infrequent bus errors when testing with I2C Stick.
// Safer to have this check and clear than to risk an
// infinite loop of interrupts
// Set by hardware when the interface detects an SDA rising or falling
// edge while SCL is high, occurring in a non-valid position during a
// byte transfer.
// Cleared by software writing 0, or by hardware when PE=0.
I2C_ClearITPendingBit(addrBase, I2C_IT_BERR);
LogErr(ST_COMM_FAIL, __LINE__);
return;
}
if (I2C_GetITStatus(addrBase, I2C_IT_OVR) == SET)
{
// Check for other errors conditions that must be cleared.
I2C_ClearITPendingBit(addrBase, I2C_IT_OVR);
LogErr(ST_COMM_FAIL, __LINE__);
return;
}
if (I2C_GetITStatus(addrBase, I2C_IT_TIMEOUT) == SET)
{
// Check for other errors conditions that must be cleared.
I2C_ClearITPendingBit(addrBase, I2C_IT_TIMEOUT);
LogErr(ST_COMM_FAIL, __LINE__);
return;
}
// a spurious IRQ occurred; log it
LogErr(ST_INV_STATE, __LINE__);
}
I'm not shure if I understand you. May you should provide more information or an example about what you would like to do.
Maybe this helps:
My experience is, that in many I2C implementations the R/W-Bit is used together with the 7-bit-address, so most of the times, there is no additional function to set or reset the R/W-Bit.
So that means all addresses beyond 128 should be used to read data from slaves and all addresses over 127 should be used to write data to slaves.
There seems to be no way to determine if the transaction initiated by receipt of the address is a read or a write even though the hardware know whether the LSbit is set or clear. The intention of the master will only be known once the RXNE or TXE interrupt/bit occurs.

Accessing RTC(DS1307) through I2C in ATmega16

I have written a code to access RTC(DS1307) through I2C using ATmega16 and I am using compiler AVR Studio 4.
Code is given below:-
#include<avr/io.h>
#include<util/delay.h>
#define F_CPU 1000000UL
void rtc_init(void)
{
TWSR=0x00;
TWBR=0x47;
TWCR=0x04;
}
void rtc_start(void)
{
TWCR=(1<<TWEN)|(1<<TWSTA)|(1<<TWINT);
while(TWCR & (1<<TWINT)==0);
}
unsigned char rtc_read(void)
{
TWCR=(1<<TWINT)|(1<<TWEN);
while(!(TWCR & (1<<TWINT)));
return(TWDR);
}
void rtc_write(unsigned char data)
{
TWDR=data;// sending address
TWCR=(1<<TWINT)|(1<<TWEN);
while(TWCR & (1<<TWINT)==0);
}
void rtc_stop()
{
TWCR=(1<<TWINT)|(TWSTO)|(1<<TWEN);
while(TWCR & (1<<TWINT)==0);
}
main()
{
unsigned char sec,min,hr;
DDRA=0xFF;
DDRB=0xFF;
rtc_init();
_delay_ms(1000);
rtc_start();
rtc_write(0b11010000); // 1101000=adress of ds1307 and bit 0= write
rtc_write(0x00); // pointing address location 00 i.e seconds
rtc_write(0x00);// set sec=0
rtc_write(0x00);// set min=0
rtc_write(0x00);// set hr=0
rtc_stop();
while(1)
{
rtc_start();
rtc_write(0b11010001); // 1101000=adress of ds1307 and bit 1= read
rtc_write(0x00); // pointing address location 00 i.e seconds
sec=rtc_read();
rtc_stop();
PORTA=sec;
PORTB=0x01;
_delay_ms(5000);
rtc_start();
rtc_write(0b11010001); // 1101000=adress of ds1307 and bit 1= read
rtc_write(0x01); // pointing address location 00 i.e seconds
min=rtc_read();
rtc_stop();
PORTA=min;
PORTB=0x02;
_delay_ms(5000);
rtc_start();
rtc_write(0b11010001); // 1101000=adress of ds1307 and bit 1= read
hr=rtc_read();
rtc_stop();
PORTA=hr;
PORTB=0b00000100;
_delay_ms(5000);
}
}
I have successfully build the above code. When I am running this code on PROTEUS simulator I am not getting any output, but polling in the code working properly for applying delay.
I want to know where I have done wrong and how to resolve it.
I don't think you are generating the start condition correctly, among other things. From the datasheet (page 183):
The first step in a TWI transmission is to transmit a START condition.
This is done by writing a specific value into TWCR, instructing the
TWI hardware to transmit a START condition. Which value to write is
described later on. However, it is important that the TWINT bit is set
in the value written. Writing a one to TWINT clears the flag. The TWI
will not start any operation as long as the TWINT bit in TWCR is set.
Immediately after the application has cleared TWINT, the TWI will
initiate transmission of the START condition.
There is a detailed C example in the datasheet that looks like this:
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) //Send START condition
while (!(TWCR & (1<<TWINT))); // Wait for TWINT Flag set. This indicates
// that the START condition has been transmitted
if ((TWSR & 0xF8) != START) // Check value of TWI Status Register. Mask
ERROR(); // prescaler bits. If status different from
// START go to ERROR
TWDR = SLA_W; // Load SLA_W into TWDR Register. Clear
TWCR = (1<<TWINT) | (1<<TWEN); // TWINT bit in TWCR to start transmission
// of address
while (!(TWCR & (1<<TWINT))); // Wait for TWINT Flag set. This indicates
// that the SLA+W has been transmitted,
// and ACK/NACK has been received.
if ((TWSR & 0xF8) != MT_SLA_ACK) // Check value of TWI Status Register. Mask
ERROR(); // prescaler bits. If status different from
// MT_SLA_ACK go to ERROR
TWDR = DATA; // Load DATA into TWDR Register. Clear
TWCR = (1<<TWINT) | (1<<TWEN); // TWINT bit in TWCR to start transmission
// of data
while (!(TWCR & (1<<TWINT))); // Wait for TWINT Flag set. This indicates
// that the DATA has been transmitted, and
// ACK/NACK has been received.
if ((TWSR & 0xF8) != MT_DATA_ACK) // Check value of TWI Status Register. Mask
ERROR(); // prescaler bits. If status different from
// MT_DATA_ACK go to ERROR
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO); // Transmit STOP condition
That is very different from your code. I'd start with the sample from the datasheet, and go from there.