Forum: Mikrocontroller und Digitale Elektronik I2C Atmega328p als Master und 24LC256 EEPROM IC als slave


von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Hi,

Ich habe ein Problem wenn ich versuche mit Atmega328p als Master mit 
Slave 24LC256 EEPROM zu kommunizieren.

Relevante Details:
1. TWI ist auf dem Master enabled, Clock ist auf 100Khz gesetzt,
2. SDA und SCL sind "pulled up" auf Vcc mit 4.7k Widerstände,
3. START funktioniert, bekomme ein ACK auch zurück, Problem ist wenn ich 
die Device Adresse auf der Leitung schicke, bekomme weder ACK noch NACK.

was mache ich falsch?

Code:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include <avr/interrupt.h>
4
5
#define FOSC        16000000UL
6
#define START       0x08
7
#define MT_SLA_ACK  0x18
8
#define MT_SLA_NACK  0x20
9
#define DEV_ADDR    0x50
10
11
typedef enum result_t{FAIL, SUCCESS}result;
12
13
void Debug_LED_ON()
14
{
15
   PORTD|=1<<PD4; //Turn on LED on PIN4 of PORTD
16
}
17
18
void set_clock(int freq_in_khz)
19
{
20
   TWCR = 1<<TWEN;     //Enable TWI module
21
   TWSR |=(1<<TWPS0); //Prescaler set to 4
22
   TWBR  = FOSC/freq_in_khz;
23
   TWBR -= 16;
24
   TWBR /= 8;  //2*Prescaler_value
25
26
}
27
28
void send_start()
29
{
30
   TWCR= ( (1<<TWINT) | (1<<TWEN) | (1<<TWSTA) ); //send START
31
   while( !(TWCR & (1<<TWINT)) ) ;                //wait for TWINT flag SET
32
}
33
34
void send_stop()
35
{
36
   TWCR= ( (1<<TWINT) | (1<<TWEN) | (1<<TWSTO) ); //send STOP
37
   //while( !(TWCR & (1<<TWINT)) ) ;                //wait for TWINT flag SET
38
}
39
40
result check_start_status()
41
{
42
   if ((TWSR & 0xF8) == START )
43
     return SUCCESS;
44
   else
45
     return FAIL;
46
}
47
48
void send_address(char addr_w)
49
{
50
51
   TWDR=addr_w; //7bit  address + W bit (write bit)
52
   TWCR = (1<<TWINT) | (1<<TWEN); //Clear TWINT bit to start transmission of address
53
   while (!(TWCR &(1<<TWINT)));  //wait for TWINT flag SET
54
   
55
}
56
result check_MT_slave_ack()
57
{
58
   if ((TWSR & 0xF8) == MT_SLA_ACK )
59
     return SUCCESS;
60
   else
61
     return FAIL;
62
}
63
result check_MT_slave_nack()
64
{
65
   if ((TWSR & 0xF8) == MT_SLA_NACK )
66
     return SUCCESS;
67
   else
68
     return FAIL;
69
}
70
71
void  transmit_data(char data)
72
{
73
   TWDR=data;
74
   TWCR = (1<<TWINT) | (1<<TWEN); //Clear TWINT bit to start transmission of Data on the bus
75
   while (!(TWCR &(1<<TWINT)));  //wait for TWINT flag SET
76
77
}
78
79
int main (void)
80
{
81
   DDRC=0xff;
82
   PORTC=0x30;   //Enable internal pullups on PORTC PINS  SDA(PC4) , SCL(PC5)
83
   DDRD=0xFF;   //Port D as output
84
   
85
   
86
   set_clock(100); //setting clock 100KHz
87
   send_start();
88
   
89
   if(check_start_status()==FAIL)
90
     Debug_LED_ON();
91
     
92
    
93
   send_address(DEV_ADDR << 1); //send as 8-bit address
94
   
95
   if(check_MT_slave_nack()==FAIL)
96
    Debug_LED_ON();
97
   
98
   
99
   return 0;
100
}


wäre sehr cool wenn ihr über mein Code-Qualität etwa kommentieren 
konntet, möchte  mich verbessern, deshalb! Danke!

: Bearbeitet durch User
von Baldrian (Gast)


Lesenswert?

> ... bekomme weder ACK noch NACK.

Seltsam. Was bekommst du stattdessen?

von Baldrian (Gast)


Lesenswert?

Die Adresse ist falsch: 0xA0 statt 0x50.

von Aa B. (aaab)


Lesenswert?

Richtig!

Baldrian schrieb:
> Die Adresse ist falsch: 0xA0 statt 0x50.

Genau das! Glaube habe die Frage zu früh gepostet. :) Jetzt bekomme ich 
ACK zurück! funktioniert noch!

von Quantfr (Gast)


Lesenswert?

sollte hier nicht 0x20 stehen ?

#define MT_SLA_NACK  0x18 <- 0x20

von Aa B. (aaab)


Lesenswert?

Quantfr schrieb:
> sollte hier nicht 0x20 stehen ?
>
> #define MT_SLA_NACK  0x18 <- 0x20

Jo richtig, war ein CTrl-C+CTrl-V fehler :)

von Karl M. (Gast)


Lesenswert?

Guten Morgen,

hier ist noch ein systematischer Fehler eingebaut.
1
void set_clock(int freq_in_khz)
2
{
3
   TWCR = 1<<TWEN;     //Enable TWI module
4
   TWSR |=(1<<TWPS0); //Prescaler set to 4
5
   TWBR  = FOSC/freq_in_khz;
6
   TWBR -= 16;
7
   TWBR /= 8;  //2*Prescaler_value
8
9
}

TWSR hat zwar in diesem Fall den richtigen Wert, aber der Wert von TWBR
ist nicht in allen möglichen Fällen korrekt !
War um schreibst Du die Formel nicht in eine Zeile und vermeidest so 
Überläufe im Zahlenbereich?
1
uint32_t twi_freq = 100000;
2
const uint8_t TWI_PRESCALER = 4;
3
const uint8_t TWI_SCL = (FOSC/twi_freq -16 +TWI_PRESCALER) / (2 *TWI_PRESCALER); // mit Aufrunden

von Aa B. (aaab)


Lesenswert?

Karl M. schrieb:
> Guten Morgen,
>
> hier ist noch ein systematischer Fehler eingebaut.void set_clock(int
> freq_in_khz)
> {
>    TWCR = 1<<TWEN;     //Enable TWI module
>    TWSR |=(1<<TWPS0); //Prescaler set to 4
>    TWBR  = FOSC/freq_in_khz;
>    TWBR -= 16;
>    TWBR /= 8;  //2*Prescaler_value
>
> }
> TWSR hat zwar in diesem Fall den richtigen Wert, aber der Wert von TWBR
> ist nicht in allen möglichen Fällen korrekt !
> War um schreibst Du die Formel nicht in eine Zeile und vermeidest so
> Überläufe im Zahlenbereich?uint32_t twi_freq = 100000;
> const uint8_t TWI_PRESCALER = 4;
> const uint8_t TWI_SCL = (FOSC/twi_freq -16 +TWI_PRESCALER) / (2
> *TWI_PRESCALER); // mit Aufrunden

Danke für den Tipp, bist Du Dir sicher wegen dieser Formel was Du da 
hast?
Ich hätte so geschrieben
1
 
2
   uint32_t twi_freq=0 ;
3
   const unsigned int prescaler=4;
4
   
5
   TWCR = 1<<TWEN;     //Enable TWI module
6
   TWSR |=(1<<TWPS0); //Prescaler set to 4
7
   
8
   
9
   twi_freq = freq_in_khz* 1000;
10
11
   TWBR  = (FOSC/twi_freq - 16)/(2*prescaler) ;

Beitrag #5031977 wurde vom Autor gelöscht.
von spess53 (Gast)


Lesenswert?

Hi

>   const unsigned int prescaler=4;

Wozu ist das gut? Mit Prescaler=1 kann man eigentlich Taktfrequenzen 
vonn 100 bis 400 KHz bei üblichen Controllertakten erzeugen.

MfG Spess

von Baldrian (Gast)


Lesenswert?

spess53 schrieb:
> Hi
>
>>   const unsigned int prescaler=4;
>
> Wozu ist das gut? Mit Prescaler=1 kann man eigentlich Taktfrequenzen
> vonn 100 bis 400 KHz bei üblichen Controllertakten erzeugen.
>
> MfG Spess

Low,

worüber echauffierst du dich mal wieder? Vorausgesetzt, dass Code und 
Formel korrekt sind, ist es doch die Angelegenheit des Programmierers, 
ob er durch 1 oder durch 4 dividiert.

Mampf Baldrian

von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Jetzt habe ich den Program vollständig geschrieben aber er funktioniert 
immer noch irgendwie falsch, also ich versuche Datenbyte zu schreiben 
auf ein bestimmte Adresse und die dann wieder zu lesen aber das klappt 
nicht so richtig.

von Karl M. (Gast)


Lesenswert?

Hallo Spess,

Basierend auf dem Code vom TO,
hat dieser den Prescaler 4 gewählt.

Aus dem Datenblatt eines ATmega328p
TWS[1:0] | Prescaler Value
00 |  1 = 4⁰
01 |  4 = 4¹
10 |  16 = 4²
11 |  64 = 4³

Meine schreibweise mit const uint8_t soll verdeutlichen, dass es sich 
hierbei um Konstanten handelt.

zur Formel, diese kannst Du aus der aus dem Datenblatt S.266f
/26.5.2. Bit Rate Generator Unit/  herleiten, in dem man beim Aufrunden 
+1/2 = +0.5 rechnet.

spess53 schrieb:
> Hi
>
>>   const unsigned int prescaler=4;
>
> Wozu ist das gut? Mit Prescaler=1 kann man eigentlich Taktfrequenzen
> von 100 bis 400 KHz bei üblichen Controllertakten erzeugen.
>
> MfG Spess

von Bastian W. (jackfrost)


Lesenswert?

Bekommst du das falsche Ergebnis , oder gibt's ein NACK?

Hast du einen LA?

Gruß JackFrost

von Aa B. (aaab)


Lesenswert?

Bastian W. schrieb:
> Bekommst du das falsche Ergebnis , oder gibt's ein NACK?
>
> Hast du einen LA?
>
> Gruß JackFrost

LA -Logic Analyser? leider nicht!

Ich bekomme ein NACK von Slave weil ich nur ein Byte lesen will!
Ich habe Data an der stelle 0x00 z.B 0x0A geschrieben und gelesen, das 
klappt. wenn ich versuche auf die Adresse 0x000A irgendwas zu schreiben 
und zurück zu lesen, das klappt nicht.

Ich habe den code in gedit geschrieben und mit avr-gcc compiliert und 
gelinkt. Habe kein Debugger auch für Arduino, deshalb ist Debugging ein 
Problem.

von Bastian W. (jackfrost)


Lesenswert?

Beim lesen kommt das ACK/NACK vom Master. Du musst also NACK oder ACK 
senden.

Schreib in die ersten 16 Bytes die Werte 1 - 16 und schau was rauskommt. 
Sende die Werte per Uart an den PC.

Gruß JackFrost

von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Bastian W. schrieb:
> Beim lesen kommt das ACK/NACK vom Master. Du musst also NACK oder
> ACK
> senden.
>
> Schreib in die ersten 16 Bytes die Werte 1 - 16 und schau was rauskommt.
> Sende die Werte per Uart an den PC.
>
> Gruß JackFrost
Also beim lesen, lese ich nur ein Byte deshalb lese ich mit NACK also 
TWEA wird nicht gesetzt in TWCR. Wenn ich die Bytes hintereinander lese 
dann schicke ich ACK von Master


Gute Idee mit dem Terminal! obwohl ich 0x00 - 0x0A schicke bekomme ich
irgendwelche Blödsinn!

: Bearbeitet durch User
Beitrag #5032344 wurde vom Autor gelöscht.
von S. Landolt (Gast)


Lesenswert?

> send_address(DEV_ADDR<<1);
innerhalb von read_from_slave_dev - fehlt da nicht ein +1?

von S. Landolt (Gast)


Lesenswert?

Read-bit oder so?

von Aa B. (aaab)


Lesenswert?

S. Landolt schrieb:
> Read-bit oder so?

Ja, ich habe Random- Read operation gemacht wie beschrieben im 24LC256 
Datenblatt. das heißt, Adresse muss gesetzt werden, also die Adresse von 
wo ich lesen möchte, deshalb schreibe ich die Device Adresse+/W zuerst, 
und dann schicke ich die MSB und LSB von Adresse, und danach Lese ich 
mit Dev_Adresse+R.
1
 send_address(0xA1);

von S. Landolt (Gast)


Lesenswert?

Okay, ich hatte mich an dem Parameter DEV_ADDR orientiert.

von Bastian W. (jackfrost)


Lesenswert?

Du hast keine Pause zwischen dem Schreiben und dem Leden ? Der Bus muss 
bei 5 V mindestens 1300 ns frei sein nach dem Stopp. Setz mal ein Delay 
mit 5 us

Gruß JackFrost

: Bearbeitet durch User
von Aa B. (aaab)


Lesenswert?

Bastian W. schrieb:
> Du hast keine Pause zwischen dem Schreiben und dem Leden ? Der Bus
> muss
> bei 5 V mindestens 1300 ns frei sein nach dem Stopp. Setz mal ein Delay
> mit 5 us
>
> Gruß JackFrost

OKay, das wusste ich nicht, habe ich gemacht, aber hat sich nicht viel 
geändert!

von Bastian W. (jackfrost)


Lesenswert?

Nachdem du  n Bytes der Page beschrieben hast musst du 5 ms warten so 
das das EEPROM die Daten tatsächlich speichern kann. Setze nach dem 
Stopp vom Write mal ein 5 - 8 ms Delay.

Gruß JackFrost

: Bearbeitet durch User
von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Bastian W. schrieb:
> Nachdem du  n Bytes der Page beschrieben hast musst du 5 ms warten
> so
> das das EEPROM die Daten tatsächlich speichern kann. Setze nach dem
> Stopp vom Write mal ein 5 - 8 ms Delay.
>
> Gruß JackFrost

Danke JackFrost! Ich habe schon paar millisec nachdem 16 Byte schreiben 
gewartet! aber es hat sich nicht so viel geändert! Muss nochmal die 
Datenblätter gut durchlesen. :)

von Bastian W. (jackfrost)


Lesenswert?

Wenn das dein Send_Slave ist
1
void write_to_slave(char dev_address, uint16_t address_to_write_data, char data_to_send)
2
{
3
   uint8_t MSB, LSB=0;
4
   MSB = (uint8_t) (address_to_write_data >> 8) ;
5
   LSB =  (uint8_t) (address_to_write_data);
6
   
7
   
8
   //MASTER WRITE MODE
9
   // Step 1. Send START
10
   send_start();
11
   
12
   // Step 2. Check TWSR for START code
13
   if(check_TWI_status(START)==FAIL)
14
     Debug_LED_ON();
15
     
16
   // Step 3. Send Address + /W bit 
17
   send_address(dev_address << 1); 
18
   
19
   // Step 4. Check for ACK from Slave
20
   if(check_TWI_status(MT_SLA_ACK) == FAIL )
21
    Debug_LED_ON();
22
23
   _delay_ms(200);
24
   
25
   // Step 5. Send MSB of Address in the EEPROM chip 
26
    
27
   transmit_data( MSB );  
28
   
29
   // Step 6. Check for ACK from Slave
30
   if(check_TWI_status(MT_SLA_DATA_ACK)==FAIL)
31
    Debug_LED_ON();
32
    
33
   // Step 7. Send MSB of Address in the EEPROM chip  
34
   transmit_data( LSB );  
35
   
36
   // Step 6. Check for ACK from Slave
37
   if(check_TWI_status(MT_SLA_DATA_ACK)==FAIL)
38
    Debug_LED_ON();
39
   
40
   // Step 8. Send Data to address in the EEPROM chip 
41
   transmit_data(data_to_send);
42
   if(check_TWI_status(MT_SLA_DATA_ACK)==FAIL)
43
    Debug_LED_ON();
44
   
45
   //Step 9. Send STOP
46
   send_stop();
47
48
}

Dann Überschreibst du dir immer das erste Byte, da nach dem Stop wieder 
die gleiche Adresse gesendet wird.

Entweder üebrgibst du hier nicht ein Byte, sonder ein Array. Oder du 
sendest jedesmal eine neue Adresse. Zudem hast du ja das Stop schon in 
deiner Funktion, Daher solltest du hier das Delay setzen. Nicht Stop , 
dann in der Main nochmal Stop und dann das Delay. Du musst dann warten 
wenn das Stop kommt, denn dann werden die Daten die du gesendet hast in 
die Page geschrieben. Du kannst maximal eine Page am Stück schreiben.

Du wartest aber erst nachdem du die 16 Bytes gesendet hast, der EEPROM 
will aber nach dem ersten Stop die Daten schon vom Puffer in den 
Speicher schreiben. Ggf. kommen dann nicht mehr alle Daten an, da der 
EEPROM beschäftigt ist.

Gruß JackFrost

von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Bastian W. schrieb:

> Dann Überschreibst du dir immer das erste Byte, da nach dem Stop wieder
> die gleiche Adresse gesendet wird.
>
> Entweder üebrgibst du hier nicht ein Byte, sonder ein Array. Oder du
> sendest jedesmal eine neue Adresse. Zudem hast du ja das Stop schon in
> deiner Funktion, Daher solltest du hier das Delay setzen. Nicht Stop ,
> dann in der Main nochmal Stop und dann das Delay. Du musst dann warten
> wenn das Stop kommt, denn dann werden die Daten die du gesendet hast in
> die Page geschrieben. Du kannst maximal eine Page am Stück schreiben.
>
> Du wartest aber erst nachdem du die 16 Bytes gesendet hast, der EEPROM
> will aber nach dem ersten Stop die Daten schon vom Puffer in den
> Speicher schreiben. Ggf. kommen dann nicht mehr alle Daten an, da der
> EEPROM beschäftigt ist.
>
> Gruß JackFrost

Fast da :) ich habe kontinuierlich 16 Bytes geschrieben und erst dann 
STOP gesendet. Danach setze ich Delay bevor ich die Master in Master 
Receive mode setze und einzelne Byte zurück von TWDR lese.


Das Problem ist nun wenn die Delay mehr als 15us ist, liest er nichts 
zurück.  Danach wenn ich die Delay in us verringere, und dann kompiliere 
erst dann wird gelesen. Ich  vermute dass hat irgendwas mit send_stop() 
oder NACK zu tun. Code ist beigefügt, hoffe die ist lesbar. :)

von Bastian W. (jackfrost)


Angehängte Dateien:

Lesenswert?

Hi,

beim lesen passt es noch nicht.

Du musst ja folgendes machen :

Start
Adresse + 0 senden
EEPROM Addresse senden
Rep. Start
Address + 1 senden
So lange ACK setzten und das Register auslesen bis zum vorletzten Byte 
das du lesen willst
NACK setzen und das letzte Byte lesen
Stopp.

Du machst aber folgendes
Start
Adresse + 0 senden
EEPROM Addresse senden
Rep. Start

Address + 1 senden
Byte mit ACK lesen
Dann wiederholst du in dem du wieder die Adresse + schickst.

Ich hab deine Datei mal umgeschrieben, ich hab nur keinen Atmega328p um 
es zu testen.

Gruß JackFrost

von Aa B. (aaab)


Angehängte Dateien:

Lesenswert?

Bastian W. schrieb:
> Hi,
>
> beim lesen passt es noch nicht.
>
> Du musst ja folgendes machen :
>
> Start
> Adresse + 0 senden
> EEPROM Addresse senden
> Rep. Start
> Address + 1 senden
> So lange ACK setzten und das Register auslesen bis zum vorletzten Byte
> das du lesen willst
> NACK setzen und das letzte Byte lesen
> Stopp.
>
> Du machst aber folgendes
> Start
> Adresse + 0 senden
> EEPROM Addresse senden
> Rep. Start
>
> Address + 1 senden
> Byte mit ACK lesen
> Dann wiederholst du in dem du wieder die Adresse + schickst.
>
> Ich hab deine Datei mal umgeschrieben, ich hab nur keinen Atmega328p um
> es zu testen.
>
> Gruß JackFrost

Hi,

Ich habe von Datenblatt so interpretiert dass um die MR mode zu treiben 
muss man jedes mal "repeated Start" schicken und dann Address+R, usw. 
Nachdem du bytes rein geschrieben hast, musst du nochmal die Device 
Addresse und addresse in EEPROM senden, und dann nur einmal diese 
repeated Start, danach Adresse+R, dann ist man in MR mode schon drin.

Das war der erste Fehler, dann mit der Benamsung "enter_MR_mode" ist 
völlige quatsch, muss was sinnvolles heißen.
Mit dem NACK hatte ich schon verstanden, ausprobiert aber war nicht 
richtig implementiert!

Jetzt verstehe ich! :) Jetzt funktioniert das auch! habe ich getestet, 
Awesome :) vielen Dank!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.