Jste zde

Stručný popis sběrnice I2C a její praktické využití k připojení externí eeprom 24LC256 k mikrokontroléru PIC16F877

V článku stručně popisuji princip dvojdrátové sběrnice I²C, její výhody a nevýhody, možnosti využití a především uvádím konkrétní příklad komunikace mikrokontroléru PIC16F877 s externí eeprom 24LC256 firmy Microchip. Můžete si zde stáhnout i části zdrojového programu pro tento mikrokontrolér sloužící k zápisu a čtení eeprom.

Něco málo o sběrnici


I²C bus je zkratka která vznikla z IIC bus, tedy Internal-Integrated-Circuit Bus. Jak již název napovídá, jedná se o interní datovou sběrnici sloužící pro komunikaci a přenos dat mezi jednotlivými integrovanými obvody většinou v rámci jednoho zařízení. Vyvinula ji firma Philips přibližně před 20 lety a od té doby prošla několika vylepšeními, ale o tom až později. V dnešní době tento způsob "komunikace" podporuje řada integrovaných obvodů nejen firmy Philips. Jedná se především o mikrokontroléry, sériové paměti, inteligentní LCD, audio a video obvody, a/d a d/a převodníky a některé další digitálně řízené obvody. Hlavní výhodou je, že obousměrný přenos probíhá pouze po dvou vodičích - "data SDA (serial data)" a "hodiny SCL (serial clock)". To především u mikrokontrolérů výrazně optimalizuje nároky na počet vstupně-výstupních pinů a celkově zjednodušuje výsledné zapojení. Na jednu sběrnici může být připojeno více integrovaných obvodů. V základní verzi jsou obvody adresovány 7bitově a v rozšířené verzi 10bitově. To umožňuje připojení 128 respektive 1024 čipů s různou adresou na jednu společnou sběrnici. V praxi jsou tato čísla však podstatně nižší, protože adresa čipu většinou nelze určit plnými 7 (10) bity ale třeba jen třemi. Někdy nelze určit vůbec a je dána na pevno pro daný typ čipu - takových čipů tedy na jedné sběrnici nemůže být více než jeden. Přenosová rychlost sběrnice je pro většinu aplikací dostatečná i v základní verzi, kde je frekvence hodin 100kHz. Ve vylepšených verzích to může být 400kHz nebo 1MHz, ale ne všechny intergované obvody tuto verzi podporují. Rychlost přenosu pak musí být přizpůsobena pochopitelně "nejpomalejšímu" čipu na sběrnici. Oba vodiče musí být implicitně v logické jedničce a to je zajištěno pull-up rezistory. Jejich odpory mají hodnotu v řádech jednotek kiloohmů. Čím je vyšší komunikační frekvence, tím musí být nižší hodnoty těchto odporů. Pro 100kHz postačuje 4k7.

Možné propojení jednotlivých integrovaných obvodů ukazuje následující obrázek.

Možné propojení jednotlivých integrovaných obvodů


Princip přenosu


Jeden z integrovaných obvodů (většinou mikrokontrolér) je nastaven jako MASTER a všechny ostatní obvody jsou SLAVE. Obvody se dají zapojit i jako tzv. multi-master, kdy je čipů master několik. V tomto článku se však omezím pouze na zapojení s jedním Master čipem a 7 bitovou adresací.

Master při jakémkoli přenosu generuje hodinový signál na vodiči SCL. Když jeden čip vysílá, přijímají všechny ostatní a pouze podle adresy určují, zda jsou data určena jim. Čip, který chce vyslat/přijmout data musí nejprve definovat adresu čipu, s kterým chce komunikovat a zda půjde o příjem nebo vysílání - tedy o čtení nebo zápis. To určuje R/W (read/write) bit, který je součástí adresy.

Přenos probíhá kombinováním následujících celků:


  • stav klidu - Je zajištěn logickými jedničkami na obou vodičích, master tedy negeneruje hodinový signál a neprobíhá žádný přenos. Logické jedničky jsou na obou vodičích zajištěny pull-up rezistory (rezistory mezi vodičem a napájecím napětím), takže klidový stav nastane i pokud jsou výstupy obvodu master ve stavu vysoké impedance (tedy odpojeny).

  • start bit - Zahajuje přenos nebo jeho další část. Je vygenerován tak, že se změní úroveň SDA z 1 na 0 zatímco je SCL v logické 1.

  • stop bit - Ukončuje přenos. Je vygenerován podobně jako start bit. Logická úroveň SDA se změní z 0 na 1 zatímco je SCL v logické 1. Stop bit může být generován pouze po "nepotvrzení přenosu", tedy pouze po přijmutí Ack v logické 1. (viz níže)

  • přenos dat - Data jsou přenášena po 1Byte tedy 8 po sobě jdoucích bitů od nejvyššího po nejnižší. Při přenosu dat se může logická úroveň na SDA měnit pouze pokud je SCL v logické 0. Při každém pulzu na SCL je přenesen jeden bit.

  • potvrzující bit Ack (acknowledge) - Tento bit slouží k potvrzení správného přijmutí dat. Ack bit se odesílá stejným způsobem jako by se odesílal devátý bit dat, ale s tím rozdílem, že ho generuje čip, který přijímal (přijímač) a nikoliv ten který data odesílal. Pokud přenos proběhl v pořádku tak odešle logickou 0. Logická 0 potvrzujícího bitu znamená rovněž to, že je přijímač připraven na příjem dalšího byte, který následuje okamžitě po něm při dalším pulzu na SCL. Pokud přenos selhal odešle logickou 1. Nebo pokud má dojít k ukončení přenosu, tak "neodešle nic". Pull-up rezistor pak zajistí, že bude na SDA logická 1 a Ack bit (v logické 0) odešle vysílač.


Časový průběh logických úrovní na vodičích SDA a SCL je na následujícím obrázku.
Časový průběh logických úrovní na vodičích SDA a SCL (klikněte pro zvětšení)


Konkrétní příklad propojení sériové eeprom 24LC256 s mikrokontrolérem PIC16F877

Posloupnost těchto celků je různá pro různé integrované obvody a je třeba přečíst si v katalogovém listu pro konkrétní obvod, jak má přesně komunikace probíhat. Přenos popíši pro konkrétní případ a to čtení 1Byte dat z externí eeprom 24LC256 z adresy 0 (0000000 00000000 - adresa v této eeprom je určena 15-ti bity).

Zahájení přenosu, poslání adresy čipu včetně R/W bitu a nastavení adresy v eeprom

Zápis i čtení z eeprom začíná následujícími kroky:

  • stav klidu

  • odeslání start bitu

  • odeslání adresy čipu - odesílá se podobně jako kdyby se odesílala data. Prvních sedm bitů definuje adresu a poslední (nejnižší) bit slouží k určení R/W (čtení/zápisu). Logická 0 znamená zápis a logická 1 čtení. I když se jedná o čtení z eeprom, tak paradoxně nejprve musí být nastaven zápis, aby se do ní zapsala adresa dat (nikoliv čipu), ze které se bude následně číst. Adresa čipu této konkrétní paměti může být 1010000 až 1010111. První 4 bity určují, že jde o tento typ paměti a nelze je měnit, další tři pak o jakou konkrétní paměť se jedná. Tyto tři bity adresy čipu lze nastavit hardwarově připojením tří vstupů eeprom k logické 0 nebo 1. Těchto pamětí může být na jedné sběrnici tedy maximálně osm. Poslední bit má být nastaven na zápis, takže celá adresa včetně R/W bitu bude: 10100000.

  • kontrola (přijmutí) ack - kontroluje, zda eeprom odeslala ack bit.

  • přenos (zápis) dat do eeprom - Nyní je do eeprom zapsána horní část adresy z jaké se má číst (neplést z adresou čipu, zde už se z hlediska I²C jedná o odesílaná data). V našem případě to bude: x0000000 (kde "x" může být logická 1 nebo 0. Tento bit je nevyužitý).

  • kontrola (přijmutí) ack

  • přenos (zápis) dat do eeprom - Odeslání drhé (spodní) části adresy z jaké se má číst. Pro náš případ: 00000000.

  • kontrola (přijmutí) ack



Dále program pokračuje různě podle toho, zda se jedná o čtení nebo zápis eeprom.

zápis do eeprom

Program pro zápis pokračuje takto:

  • přenos (zápis) dat do eeprom - Tento 1Byte dat se zapíše do eeprom na požadovanou adresu v eeprom.

  • odeslání stop bitu

  • stav klidu



čtení z eeprom

A pro čtení:

  • odeslání start bitu - opakuje odeslání start bitu.

  • odeslání adresy čipu - znova musí odeslat adresu čipu. Adresa je shodná jako předtím, ale změnil se R/W bit, protože teď již požadujeme příjem (čtení). Adrsa včetně R/W bitu bude tedy: 10100001

  • kontrola (přijmutí) ack

  • přijmutí dat - přijme 1byte dat z eeprom

  • odeslání ack

  • odeslání stop bitu

  • stav klidu



Spolehlivost, konstrukční řešení

Především při vyšších přenosových rychlostech respektive hodinových frekvencích (400kHz a 1MHz) a delších vodičích SCL a SDA by mohlo při nesprávném návrhu plošného spoje nebo celé konstrukce docházet k rušení a chybám v přenosu. Kromě potvrzujícího bitu Ack není přenos nijak kontrolován. Bit Ack potvrzuje pouze přenesení každého Byte dat, ale ne to, zda byla data přenesena správně. Proto je vhodné aby byly vodiče SDA a SCL co nejkratší a aby se v jejich blízkosti nevyskytovaly výkonné nebo vysokofrekvenční obvody. Také doporučuji vyvarovat se vzniku zemních smyček. V praxi jsem se zatím s chybami při přenosu nesetkal ani u několika metrového nestíněného třížilého kabelu (SCL, SDA, GND), ale pro jistotu je lepší (pokud je to možné) s tímto při návrhu počítat.

Schéma propojení mikrokontroléru s eeprom

I²C komunikaci lze pochopitelně naprogramovat prakticky do jakéhokoli mikrokontroléru, ale musí se pak přesně dodržovat časové prodlevy popsané v [1]. Některé mikrokontroléry však přímo podporují I²C a mají ji v hardware. Zde bych rád upozornil na to, že některé mikrokontroléry (například PIC16F77) mají hardware pouze pro funkci SLAVE, takže neumožňují propojení s většinou ostatních obvodů. PIC16F877 však obsahuje i hardware pro funkci MASTER. Zapojení je podobné jako na prvním obrázku. Pouze se propojí vývody SDA a SCL mikrokontroléru s vývody SDA a SCL sériové paměti a oba vodiče sběrnice se připojí přes pull-up rezistory k napájecímu napětí. Identifikační adresa paměti eeprom je hardwarově nastavena na 000 (piny 1,2 a 3 jsou připojeny na 0V) Oscilátor mikrokontroléru je tvořen 4MHz krystalem. Pokud by byla frekvence jiná, tak je třeba upravit hodnotu v registru SSPADD (viz inicializace na začátku programu - I2C).

Schéma propojení:

Schéma propojení PIC16F877 a 24LC256


Následují části programu pro mikrokotrolér PIC16F877. Do programu stačí nadefinovat registry "I2C_data" a "I2C_chyba", vložit na začátek inicializaci (vstupů/výstupů a I2C) a pak už jen volat podprogramy "MEM_I2C_zapis" a MEM_I2C_cti. Před voláním podprogramu MEM_I2C_zapis se data (1Byte) určená k zápisu vloží do registru I2C_data a následně se tímto podprogramem data z tohoto registru zapíší na adresu 0 do externí eeprom. Adresa je v podprogramu zadána konstantně jako 0, ale jednoduchou úpravou lze měnit. Voláním podprogramu MEM_I2C_cti budou přečtena data z adresy 0 externí eeprom a budou uložena do registru SSPBUF, ze kterého se dají nasledně přečíst. Pokud je bit 0 v registru I2C_chyba v logické jedničce, tak došlo při přenosu k selhání (přijímač neodeslal Ack bit).


;-----------------------------------------------------------------------------------
; inicializace mikrokontroléru (na začátku programu)
;-----------------------------------------------------------------------------------
;-----------------------------------------------  vstupy/výstupy
	BSF		STATUS,RP0
	BCF		STATUS,RP1		; výběr banky 1
	MOVLW		b'00011000'		; SDA a SCL musí být nastaveny jako vstupy
	MOVWF		TRISC
	BCF		STATUS,RP0

;-----------------------------------------------  I2C
	MOVLW		b'00101000'		; nastavení registru SSPCON
	MOVWF		SSPCON
	BSF		STATUS,RP0		; banka 1
	MOVLW		b'10000000'		; nastavení registru SSPSTAT
	MOVWF		SSPSTAT
	MOVLW		10				; (Fosc / (4 * přenosová rychlost)) - 1
	MOVWF		SSPADD
	BCF		STATUS,RP0		; banka 0

;-----------------------------------------------------------------------------------
; MEM I2C (eeprom 24LC256)
;-----------------------------------------------------------------------------------

MEM_I2C_zapis
	CALL		I2C_start		; odešli start bit

	MOVLW		b'10100000'		; adresa periferie, R(1)/W(0) bit
	CALL		I2C_odesli		; odešli tento byte do paměti eeprom
	CALL		I2C_cekani		; čekej než eeprom dokončí svou činnost
	CALL		I2C_prijmi_ack		; zkontroluj, zda přišlo potvrzení o příjmu ACK

	MOVLW		b'00000000'		; odešli adresu dat v eeprom (horní část adresy v paměti eeprom, bit 7 je u 256kb eeprom nevyužit)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	MOVLW		b'00000000'		; odešli adresu dat v eeprom (dolní část adresy v paměti eeprom)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	MOVFW		I2C_data		; odešli data
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	CALL		I2C_stop		; odešli stop bit

	RETURN


;-----------------------------------------------------------------------------------

MEM_I2C_cti
	CALL		I2C_start

	MOVLW		b'10100000'		; adresa periferie, R(1)/W(0) bit (zatím je nastaven zápis W pro odeslání adresy dat v eeprom)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	MOVLW		b'00000000'		; odešli adresu dat v eeprom (horní část adresy v paměti eeprom, bit 7 je u 256kb eeprom nevyužit)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	MOVLW		b'00000000'		; odešli adresu dat v eeprom (dolní část adresy v paměti eeprom)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	CALL		I2C_start2

	MOVLW		b'10100001'		; adresa periferie, R(1)/W(0) bit (až teď se nastaví čtení R)
	CALL		I2C_odesli
	CALL		I2C_cekani
	CALL		I2C_prijmi_ack

	CALL		I2C_prijmi		; přijmi data z paměti a ulož je do SSPBUF
	CALL		I2C_odesli_ack		; odešli potvrzení o přijetí dat ACK

	CALL		I2C_stop		; odešli stop bit

	RETURN


;-----------------------------------------------------------------------------------
; I2C podprogramy sériové komunikace
;-----------------------------------------------------------------------------------
;-----------------------------------------------  start bit

I2C_start
	BSF		STATUS,RP0		; banka 1
	BSF		SSPCON2,SEN		; odešli start bit
	BCF		STATUS,RP0		; banka 0
	CALL		I2C_cekani
	RETURN

;-----------------------------------------------  opakovaný start bit

I2C_start2
	BSF		STATUS,RP0		; banka 1
	BSF		SSPCON2,RSEN		; odešli opakovaný start bit
	BCF		STATUS,RP0		; banka 0
	CALL		I2C_cekani
	RETURN

;-----------------------------------------------  stop bit

I2C_stop
	BSF		STATUS,RP0		; banka 1
	BSF		SSPCON2,PEN		; odešli stop bit
	BCF		STATUS,RP0		; banka 0
	CALL		I2C_cekani
	RETURN

;-----------------------------------------------  odeslání byte

I2C_odesli
	MOVWF		SSPBUF			; odešli byte z pracovního registru W do eeprom
	RETLW		0

;-----------------------------------------------  přijmutí byte

I2C_prijmi
	BSF		STATUS,RP0		; banka 1
	BSF		SSPCON2,RCEN		; nastav režim příjmu a přijmi data (1 byte) do SSPBUF
	BCF		STATUS,RP0		; banka 0
	CALL		I2C_cekani		; čekej na dokončení přímu
	BSF		STATUS,RP0		; banka 1
	BCF		SSPCON2,RCEN		; nastav zpět režim odesílání
	BCF		STATUS,RP0		; banka 0
	RETURN

;-----------------------------------------------  přijmutí ack

I2C_prijmi_ack
	BSF		STATUS,RP0		; banka 1
	BTFSC		SSPCON2,ACKSTAT		; zkontroluj zda bylo přijato potvrzení ACK
	BSF		I2C_chyba,0			; pokud ne, tak nastav bit chyba,0
	BCF		STATUS,RP0		; banka 0
	RETURN

;-----------------------------------------------  odeslání ack

I2C_odesli_ack
	BSF		STATUS,RP0		; banka 1
	BSF		SSPCON,ACKEN		; odešli potvrzení ACK
	BCF		STATUS,RP0		; banka 0
	RETURN

;-----------------------------------------------  čekání na dokončení operace

I2C_cekani
I2C_c
	BCF		STATUS,RP0		; banka 0
	BTFSS		PIR1,SSPIF		; byla již operace dokončena ?
	GOTO		I2C_c			; pokud ne, tak čekej na její dokončení
	BCF		PIR1,SSPIF		; vynuluj příznak dokončení
	RETURN


Použitá literatura:

[1] Philips, I²C-bus specification
[2] Microchip, PIC16F877 data sheet
[3] Microchip, 24LC256 data sheet
[4] Microchip, PICmicro® Mid-Range MCU Family Reference Manual
[5] B.Kainka a H.Berndt, Využití rozhraní PC pod Windows


Odkazy:
části programu pro mikrokontrolér PIC16F877
I²C na stránkách firmy Philips
Internetové stránky firmy Microchip


Martin Olejár
martin@ elweb.cz
www.elweb.cz

Hodnocení článku: 

Komentáře

Dobrý den,
nebylo by možné kromě příkladu zdrojového kódu v assembleru přiložit zdrojový kód v C, jelikož se mnoho procesorů dnes programuje v C

Asi ne z duvodu ze c-cko ja na softverove zbernice pomale

zkoušel jsem váš program pro komunikaci pic- rtc ds1307.
komunikace běží bez problémů ale když chcí číst druhý byte z rtc tak neodešle potvrzovací bit ACK a pak se načtou samé jedničky.Zápis do rtc jde skvěle i více bytu najednou.(podprogramy jsem tady nepsal jsou úplně stejné jako ty uveřejněné na webu). Prosím o radu, strávil jsem už spoustu času hledáním chyby ale nic jsem nenašel.
opis programu:
I2CCTI
CALL I2C_start

; adresa periferie, R(1)/W(0) bit (zatím je nastaven zápis W pro odeslání adresy dat v eeprom)
MOVF ADRESAIO,W
MOVWF ADRESAIO1
BCF ADRESAIO1,0
MOVF ADRESAIO1,W

CALL I2C_odesli
CALL I2C_cekani
CALL I2C_prijmi_ack

MOVF ADRESADAT,W
CALL I2C_odesli
CALL I2C_cekani
CALL I2C_prijmi_ack

CALL I2C_start2

MOVF ADRESAIO,W
MOVWF ADRESAIO1
BSF ADRESAIO1,0
MOVF ADRESAIO1,W

CALL I2C_odesli
CALL I2C_cekani
CALL I2C_prijmi_ack

CALL I2C_prijmi ; přijmi data z paměti a ulož je do SSPBUF
MOVF SSPBUF,W
MOVWF I2CDAT
CALL I2C_odesli_ack ; odešli potvrzení o přijetí dat ACK

CALL I2C_prijmi ; přijmi data z paměti a ulož je do SSPBUF
MOVF SSPBUF,W
MOVWF I2CDAT1
CALL I2C_odesli_ack

CALL I2C_stop ; odešli stop bit

RETURN

Především děkuji autorovi za napsání tohoto článku. Použil jsem ho k seznámení se s I2C eeprom.

Bohužel se do programu vloudilo pár chybiček, které způsobily, že čtení nefunguje korektně a donutily mě k bližšímu prozkoumání.

 

1.) bit ACKEN se nachází v registru SSPCON2, nikoliv v SSPCON. (nastavení tohoto bitu způsobí odeslání ACK - log.0, nebo NO_ACK - log.1 na sběrnici) To, zda bude odesáno ACK nebo NO_ACK, určuje bit ACKDT v registru SSPCON2. Po resetu je ACKDT=0, což znamená ACK. Jenže! Pokud čteme z eeprom 24LCxx (předpokládám, že to bude i jinde, ale zatím jsem nezkoušel), musíme poslední přečtený byte z eeprom potvrdit NO_ACK a nikoliv ACK. Po něm následuje STOP bit. (Pak může následovat START bit a další příkazy.) Pokud přečtený byte potvrdíme ACK, paměť očekává, že cheme číst další byte a s dalším taktem na SCL začne na SDA vysouvat další byte. Takže pokud jednotlivé byty potvrzujeme ACK, můžeme najednou přečíst celý obsah paměti. Pokud chceme přečíst jen jeden byte, musíme ho potvrdit NO_ACK.

 

2.) Z výše uvedeného plyne, že před odesláním ACK / NO_ACK ještě musíme správně nastavit ACKDT.

 

3.) V proceduře "MEM_I2C_cti" je po odeslání NO_ACK ještě potřeba před odesláním STOP bitu zavolat "I2C_cekani", jinak nedoje k vygenerování STOP bitu.

 

Pro přehlednost uvádím poupravenou proceduru "I2C_odesli_ack" - ve skutečnosti po úpravě odesílá NO_ACK

 

I2C_odesli_ack
 BSF  STATUS,RP0  ; banka 1
 BSF  SSPCON2,ACKDT ; bude se odesílat NO_ACK
 BSF  SSPCON2,ACKEN ; odešli potvrzení ACK (NO_ACK)
 BCF  STATUS,RP0  ; banka 0
 RETURN

 

Moc děkuji za pěkný popis.

Jen si dovolím jednu věc.

Na obrázku i2c01.gif je ACK bit omylem zakreslen na signálu SCL místo SDA.

Martin