I’m thinking about updating the Standalone Temperature Logger to use an external EEPROM and since one of the ATtiny85 pins which correspond to hardware I2C/TWI is used by V-USB, a software implementation is the way to go. I found Soft I2C Master in Arduino code which allows any 2 pins to behave as an I2C interface.
Naturally I decided to convert that code to pure AVR which is available for download here – AVR_Soft_I2C_Master_v1.0
My modifications to the Soft I2C Master code are:
- Made to work directly with AVR (De-Arduino existing code)
- Changed from C++ to C
- Added soft_i2c_eeprom_read_byte and soft_i2c_eeprom_write_byte
Example code
Here’s my example which writes 1 byte, reads that same byte and blinks the LED if the byte is the same.
... #define ledPin (1<<PB2) #define EEPROM_ADDR 0x50 ... sei(); // Turn on interrupts DDRB |= ledPin; byte address = 0; byte writeByte = 170; byte readByte = 0; SoftI2cMasterInit(); // Write 1 byte to the EEPROM soft_i2c_eeprom_write_byte(EEPROM_ADDR, address, writeByte); // Read 1 byte from the EEPROM readByte = soft_i2c_eeprom_read_byte(EEPROM_ADDR, address); // Blink LED if received byte matches written byte if (readByte == writeByte) { PORTB |= ledPin; _delay_ms(1000); PORTB &= ~ledPin; _delay_ms(1000); } SoftI2cMasterDeInit(); ...
Verify it works
I won’t explain I2C as there already are lots of tutorials around but we can verify that this software I2C works correctly by looking at the SDA (blue) and SCL (yellow) lines when writing and reading. I’m using a 4Kbit (512 bytes) EEPROM which we only send the address as 1 byte, if you use say a 256Kbit(~32K bytes) then you send the address as 2 bytes, this is so we can send an access request to an address more than 255.
We send a write to the device 0x50, send the address to write to 0x00 and the data to write 170 (0xAA). What isn’t show is that we wait 10ms before we perform any other operations on the EEPROM.
Now we read the data by sending a write to the device 0x50, send the address to read from 0x00, send a read to the device 0x50 and we receive the data which reads as 0xAA (170).
If you want to learn more about I2C EEPROMs I recommend taking a look at a datasheet, Atmel’s datasheet for the AT24C32 is a good starting point.
Your code seems to be great but does not work when using higher frequencies (12Mhz in my case). Anyone who want’s to use this code with faster atmega chips has to add some _delay_us() calls after seting clock hi to make it work. 🙂
Hi,
Thanks for the codes .
What modifications can I apply to use this code for memories with more than 1byte address?
Thanks
Hi Mohammad,
You don’t need to do anything, as it’s already there in the code, if the read address is more than one byte (255), then it will send the high byte and then the low byte.
// Send the address to read, 8 bit or 16 bit
if (readAddress > 255) {
if (!SoftI2cMasterWrite((readAddress >> 8))) return false; // MSB
if (!SoftI2cMasterWrite((readAddress & 0xFF))) return false; // LSB
}
else {
if (!SoftI2cMasterWrite(readAddress)) return false; // 8 bit
}
Dear Alex,
Thanks for your help.
The problem that I’ve faced is that I’ve modified this code to be suitable for AT90USB162+At24c512 or At24C08 eeprom in IAR systems.
As I simulate this code in proteus I don’t get the signals above .
I only see the “address of the device” part!
The other question is that:Why have you assigned 0x50 for the device?!
Is it possible!?(#define EEPROM_ADDR 0x50)
I’ll be so grateful if you help!
That’s odd, what if you manually pulse the data line high and low, does that work?
The device is assigned 0x50 because I left the address pins floating which means read as 0, so in binary it would be 1010000 or 0x50 in hex (the last bit which isn’t included is the read/write bit, we left shift our eeprom address by 1 to the left to add the read/write bit on the end (LSB)).
You need to check your EEPROM’s datasheet to see what address you receive if the address pins are high or low.
Dear Alex,
I really appreciate your help!
Finally,I could read and write the eeprom(in proteus) .
The problem was related to the address of the device . If you modify the device address to be standard as Atmel’s datasheet such as 0xA0,it will be very easy to understand .
But,unfortunately I can’t read the contents of addresses from 0 to 255!
It is important to note that I have changed the “address” declaration into “uint16_t” .
But I read the contents of addresses more than 255 successfully .
Do you have any idea about that?!
Wow!I found the solution!!!
I edited the read and write functions and made it suitable to send only 16bit addresses .
Thanks Alex for everything!
Good to know, my code that I wrote back then must have not accounted for writing 2 bytes even if the address was lower than 255.
Dear Alex,
Hi!I really appreciate your former guides about the project.I’ve faced a problem writing in addresses more than 255!!!For all the addresses I enter,it reads only one data!I don’t know whether it writes in that specific address.May you please help?!
I really appreciate it.
Hi Mohammad,
I thought that you had fixed the address issue?
If the only issue you have now is the one byte of data, then with most memory types you can enable sequential mode. When you do that, you just write the address and then you can keep writing 0xFF (or anything) and it will return the next byte of data.
Hi Alex,
The exact problem is that I like to write data in .e.g address 0x032C of the eeprom(my eeprom is AT24C08) .for addresses 0x0000 to 0x00FF,I have no problems with write/read operations,but for addresses 0x0100 to 0x03FF I can’t read valid data from eeprom.
Are you sure that this code is working well for eeproms having addresses more than a byte?!
How can I change the code to be suitable for my application?!
I really appreciate your help.
Hi Mohammad,
Try this new version, I just made the changes to the setup.c file for reading and writing: http://www.insidegadgets.com/wp-content/uploads/2013/12/AVR_Soft_I2C_Master_v1.1.zip
Dear Alex,
Hello!
Finally, I completed and submitted the project!Your help and recommendations were really important and useful.I don’t know how to appreciate…
Your changes to the setup.c file worked well!You were right,address string must be 16bits wide for all the address(For eeproms more than 256Byte) that unfortunately in the former version the high byte of address was not included…
Thanks for everything,
Mohammad.
Hi,
I am developing application for communicate between Two Atmega164P,
I debugged The program in simulator, but After sending Start condition
I didn’t get Expected Status from TSWR register.
Here I attached Program which is developing into Atmel Studio 6.2
;Sample_I2C.asm
.dseg
.DEF uartChar = r1 ; Storage for character received from UART
.DEF tempReg = r21 ; for temp purpose
; TSWR response for Acknowledgement from slave
.equ I2C_SLA_W = 0×04
.equ I2C_START = 0×08
.equ MT_SLA_ACK = 0×18
.equ MT_DATA_ACK = 0×28
;**********************************************
; IRQ TABLE
;**********************************************
.cseg
.org 0×0000
; irq table
rjmp main ; Reset Handler
; IRQ TABLE IS UPTO 0X3C
.org 0×40
;***********************************************
; Main Function For Program
;***********************************************
main:
ldi tempReg, 0×73 ; op port for DMX > 01110011
out DDRC, tempReg ; Set PORTC 0.4 AND 0.5 to 1 To enable DMX in Transmitter Mode
out PORTC, tempReg ; 0.0 > SCL and 0.1 SDA
ldi tempReg, 0xF0 ; op port for LED
out DDRD, tempReg ; USART0 and USART1 in
out PORTD, tempReg
MainProgram:
call i2cInit
call i2cStart
sbi PORTD, 4
call i2cAddressSlave
ldi tempReg, ‘J’
mov uartChar, tempReg
call i2CDataTransfer
sbi PORTD, 5
ldi tempReg, ‘A’
mov uartChar, tempReg
call i2CDataTransfer
ldi tempReg, ‘S’
mov uartChar, tempReg
call i2CDataTransfer
call i2cStop
MainProgramEnd:
rjmp MainProgram
;*******************************Initialization of I2C*************************************
i2cInit:
ldi tempReg, 0×00
sts TWSR, tempReg
ldi tempReg, 0x0C
sts TWBR, tempReg
ldi tempReg, (1<<TWEN)
sts TWCR, tempReg
ret
i2cStart:
ldi tempReg, (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) ; Send START condition
; -set TWINT/TWSTA and enable TWI
sts TWCR, tempReg ; load to TWI control register
ldi tempReg, (0<<TWSTA)|(1<<TWEN) ; Send START condition
; -set TWINT/TWSTA and enable TWI
sts TWCR, tempReg ; load to TWI control register
waitForStartSend:
lds tempReg,TWCR ; Wait for TWINT Flag set. This indicates
sbrc tempReg,TWINT ; that the START condition has been transmitted
rjmp waitForStartSend ; if TWINT flag is set
lds tempReg,TWSR ; Check value of TWI Status Register.
andi tempReg, 0xF8 ; Mask prescaler bits.
cpi tempReg, I2C_START ; If status different from START (0×08) go to ERROR
brne ERROR ; Set LED on PORTD0.5 as error signal and terminate I2C
ret
;*******************************************************
i2cAddressSlave: ; Entered into Master transmitter mode
ldi tempReg, I2C_SLA_W ; Load SLAVE ADDR = SLA_W (0×04) into TWDR Register.
sts TWDR, tempReg ; Send Address
ldi tempReg, (1<<TWINT)|(1<<TWEN) ; Clear TWINT bit in TWCR to start transmission of
; address
sts TWCR, tempReg ; load TWCR
waitForSlaveAddrSend:
lds tempReg,TWCR ; and ACK/NACK has been received.
sbrc tempReg,TWINT ; Wait for TWINT Flag set.
rjmp waitForSlaveAddrSend
lds tempReg,TWSR ; Check value of TWI Status Register.
andi tempReg, 0xF8 ; Mask prescaler bits.
cpi tempReg, MT_SLA_ACK ; If status different from MT_SLA_ACK = 0×18 go to ERROR
brne ERROR
ret
;*******************************************************
i2CDataTransfer:
mov tempReg,uartChar ; Load data byte
sts TWDR, tempReg ; Load DATA into TWDR Register.
ldi tempReg, (1<<TWINT)|(1<<TWEN) ; Clear TWINT bit in TWCR to start transmission of
; data
sts TWCR, tempReg
waitForDataSend:
lds tempReg, TWCR ; This indicates that the DATA has been transmitted,
sbrc tempReg, TWINT ; and ACK/NACK has been received.
rjmp waitForDataSend
lds tempReg,TWSR ; Check value of TWI Status Register.
andi tempReg, 0xF8 ; Mask prescaler bits.
cpi tempReg, MT_DATA_ACK ; If status different from MT_DATA_ACK =
; 0×28 go to ERROR
brne ERROR
ret
;*******************************************************
i2cStop:
ldi tempReg, (1<<TWINT)|(1<<TWEN)|(1<<TWSTO) ;Transmit STOP condition
sts TWCR, tempReg
ret
;*******************************************************
ERROR:
call i2cStop
clr tempReg
out PORTD, tempReg
rjmp MainProgramEnd
;*******************************************************
how to send a simple register value between master and slave