Skocz do zawartości

[C] [STM32] I2C external EEPROM


torcek

Pomocna odpowiedź

Witam,

od dłuższego czasu męczę się z zapisywaniem i odczytywaniem danych do/z zewnętrznej pamięci EEPROM AT24C02B. Pamięć tą mam w obudowie DIP (zmontowane na płytce stykowej), układ podłączony do płytki STM32F4 DISCOVERY zgodnie z fragmentem schematu poniżej.

PB6 SCL

PB9 SDA

Adres urządzenia to 1010A2A1A0 zatem przy mojej konfiguracji 1010000. WP podłączone do masy aby umożliwić zapis do pamięci.

Podpatrując przebiegi kanałów SCL, SDA przy wykorzystaniu analizatora stanów logicznych można dojść do wniosku, że I2C jest skonfigurowane poprawnie (screen poniżej). Staram się zapisać wartość w tym przypadku 0x02 do adresu 0xE2 pamięci, a następnie odczytać wartość znajdującą się pod tym adresem. Kod poniżej. Przy odczytywaniu pamięć zwraca mi zawsze wartość 0xFF czyli coś jest nie tak.

#define SLAVE_ADDRESS 0x50 // EEPROM ADDRESS

void I2C1_init(void){

GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;

// enable APB1 peripheral clock for I2C1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// enable clock for SCL and SDA pins
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

/* setup SCL and SDA pins
 * You can connect the I2C1 functions to two different
 * pins:
 * 1. SCL on PB6 or PB8
 * 2. SDA on PB7 or PB9
 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_9; // we are going to use PB6 and PB9
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;			// set pins to alternate function
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;		// set GPIO speed
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;			// set output to open drain --> the line has to be only pulled low, not driven high
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;			// enable pull up resistors
GPIO_Init(GPIOB, &GPIO_InitStruct);					// init GPIOB

// Connect I2C1 pins to AF
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);	// SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_I2C1); // SDA

// configure I2C1
I2C_InitStruct.I2C_ClockSpeed = 100000; 		// 100kHz
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;			// I2C mode
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;	// 50% duty cycle --> standard
I2C_InitStruct.I2C_OwnAddress1 = 0x00;			// own address, not relevant in master mode
I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;		// disable acknowledge when reading (can be changed later on)
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // set address length to 7 bit addresses
I2C_Init(I2C1, &I2C_InitStruct);				// init I2C1

// enable I2C1
I2C_Cmd(I2C1, ENABLE);
}

/* This function issues a start condition and
* transmits the slave address + R/W bit
*
* Parameters:
* 		I2Cx --> the I2C peripheral e.g. I2C1
* 		address --> the 7 bit slave address
* 		direction --> the transmission direction can be:
* 						I2C_Direction_Tranmitter for Master transmitter mode
* 						I2C_Direction_Receiver for Master receiver
*/
void I2C(I2C_TypeDef* I2Cx){

// Send I2C1 STOP Condition after last byte has been transmitted
I2C_GenerateSTOP(I2Cx, ENABLE);
// wait for I2C1 EV8_2 --> byte has been transmitted
while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}

int main(void){

I2C1_init(); // initialize I2C peripheral
uint16_t received_data = 0;

I2C(I2C1); // stop the transmission

I2C(I2C1);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
I2C_start(I2C1, SLAVE_ADDRESS<<1, I2C_Direction_Receiver);
received_data = I2C_read_nack(I2C1); // read one byte and don't request another byte, stop transmission

while(1){

}
return 0;
}
Link do komentarza
Share on other sites

No właśnie nie. Obserwując przebiegi z analizatora dochodzę do wniosku, że coś jest poważnie nie tak. Przede wszystkim EEPROM to nie RAM i do zapisania bajtu (lub strony bajtów) potrzebuje ok 5ms. To dużo czasu. Kostka musi Ci jakoś dać znać o tym, że nie może z Tobą gadać - nie może w tym czasie realizować ani kolejnych zapisów ani odczytów. Sygnalizacja polega na kompletnym odłączeniu się od szyny a Twój procesor widzi to jak zniknięcie układu o tym adresie z magistrali I2C. W rezultacie po prawidłowo zaakceptowanym i rozpoczętym wewnątrz zapisie nie powinieneś dostawać ACK przez ok. 5ms.

Po pierwsze Twój kod w ogóle tego nie sprawdza, bo wyłączyłeś to sobie tutaj:

I2C_InitStruct.I2C_Ack = I2C_Ack_Disable;

Po drugie widać ACK po wysłaniu adresu do odczytu co może być spowodowane albo "udawaniem" go przez sam procesor albo brakiem akceptacji poprzedniej komendy zapisu. Czy WP jest rzeczywiście na 0?

No i po trzecie Twój kod powinien być przygotowany na długie oczekiwanie po zapisie poprzez odliczanie jakiegoś timeoutu bo przecież brak kontaktu z EEPROMem może kiedyś być oznaką awarii systemu a nie trwania zapisu. Oczekiwanie na ACK po zaadresowaniu układu musisz robić przed każdą operacją bo przecież zawsze możesz zwrócić się do pamięci w trakcie trwania poprzedniego zapisu chyba, że po każdym zapisie będziesz weryfikował go przez odczyt lub choćby przez sprawdzenie, że EEPROM ponownie "ożył".

Przykład obsługi EEPROMu z 32F4 masz tutaj:

http://www.electroons.com/blog/2013/01/hello-world/

To jest co prawda większy układ (24C512, 64Kbajty) potrzebujący dwóch bajtów adresu, ale jeśli wiesz co robisz to sobie poradzisz. Trochę dziwne, że nie przeczytałeś danych katalogowych 24C02. Tam w rozdziale "Write Operations" napisali to wprost:

"...At this time the EEPROM enters an internally timed write cycle, tWR, to the nonvolatile memory. All inputs are disabled during this write cycle and the EEPROM will not respond until the write is complete."

Wspomniany tWR to właśnie te 5ms (tabelka 5).

  • Pomogłeś! 1
Link do komentarza
Share on other sites

Dodanie opóźnienia do powyższego kodu przed próbą odebrania pomogło, dzięki marek1707 😉 jakoś mi ta informacja o trwaniu zapisu umknęła. W sumie długo się z tym męczyłem także ze względu na to, że

The internal data word address counter maintains the last address accessed during the last read or write operation, incremented by one

nie chcąc niepotrzebnie komplikować kodu czytałem "current address", czyli bez wskazania konkretnego miejsca w pamięci, przez co cały czas układ wysyłał mi wartość wpisaną do adresu o 1 dalej, niż ten który miałem na myśli 🙂

zatem podsumowując kod w pierwszym poście działa, jedynie trzeba dodać opóźnienie ok 5ms po wysłaniu komend do zapisu danych.

@edit

napisałem sobie funkcję odpytującą EEPROM czy "już wstał" i zapisanie danych do pamięci nie zajmuje mu więcej niż 2ms, ale to pewnie nie jest regułą więc w przypadku generowania opóźnienia za pomocą np pętli for warto odczekać około 5ms.

Link do komentarza
Share on other sites

No tak, te 5ms to górna granica podawana przez producenta. Ponieważ obietnica długości zapisu musi być dochowana we wszystkich skrajnych przypadkach (małe napięcie zasilania, skrajne temperatury czy zużyta komórka do której zrobiono już np. 500000 zapisów) to w komfortowych warunkach jest to trochę szybciej.

Ja jednak nie rozumiem dlaczego układ odpowiadał ACK podczas drugiego adresowania mimo, iż teoretycznie trwał zapis i EEPROM nie był gotowy? Przecież odcięcie od magistrali powinno być widoczne również podczas próby odczytu. A może kostka zapisała sobie już tę daną kiedyś wcześniej (np. podczas poprzednich przebiegów tego samego programu) i wcale nie zaczynała zapisu jeszcze raz tej samej wartości? Czy mógłbyś sprawdzić, czy EEPROM samodzielnie anuluje zapisy, gdy próbujesz zapisać to samo co już w danej komórce jest? Tego nigdy nie sprawdzałem.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Kupiłem taki sam scalak po to aby wykluczyć możliwość jego uszkodzenia na drodze pierwszych testów. Gdy program zadziałał na nowym, podmieniłem go na ten poprzedni i okazało się, że układ nie wchodzi w stan zapisu danych, ponieważ wysyła ACK zaraz po otrzymaniu komend do zapisu, czyli tak jak mówisz o wiele za wcześnie.

Podczas debugowania przeprowadzałem także próby software resetu (screen zapisałem sobie jakby co) ale najwidoczniej nie pomogło:

@edit:

teraz widzę że nie wygenerowałem bitu startu po 9 cyklach, no trudno sprawdzę innym razem, bo układ już leży w koszu (żebym kiedyś przypadkiem go nie wykorzystał i nie tracił czasu niepotrzebnie)

sprawdziłem teraz czas zapisywania dla nowej wartości, oraz potem od razu dla tej samej jeszcze raz i przebiegi wyglądały praktycznie tak samo. Nie widzę różnicy. Screen poniżej ale niewiele widać

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.