Reklama

Kruhová vyrovnávací paměť “buffer” pro mikrokontrolér ATmega(16/32)

Úvod

V tomto příspěvku si ukážeme jak vytvořit jednoduchou kruhovou vyrovnávací paměť typu “FIFO” (First In First Out), pro mikrokontroléry ATmega(16/32). Samozřejmě obecný princip lze použít i pro jiné mikrokontroléry, ale konkrétní vzorový příklad je vytvořený pro mikrokontrolér ATmega(16/32).

Popis

Vyrovnávací paměť je vhodná pro procesy, kde se liší časová náročnost příjmu a zpracování dat. Pokud není použita vyrovnávací paměť a najednou je nutné zpracovat větší množství dat, které není procesor schopný zpracovat, tak dojde k jejich ztrátě. Aby data nebyla ztracena, tak jsou ukládána právě ve vyrovnávací paměti. Využití tyto paměti naleznou například při použití sériové komunikace apod. Zde je popsáno vytvoření paměti typu “FIFO” (First In First Out), neboli paměť typu “První uložit, první přečíst”. Opakem těchto pamětí jsou paměti typu “LIFO” (Last In First Out), nebo-li “Poslední uložený, první čtený”, které jsou přirovnávány k zásobníku. K vytvoření kruhové paměti potřebujeme dva ukazatele. První ukazuje na pozici, kde se mají nové data zapsat a druhý ukazuje na pozici odkud se budou data číst. Většinou se tyto ukazatele označují jako “Head” (hlava) a “Tail” (ocas). Někdo využívá ukazatel “Head” pro zápis nových dat a “Tail” pro čtení uložených dat. Tento způsob je přirovnáván k hadovi, který nabírá nové dílky na svoji přední část. Druhý pohled je, že by nové data měl určovat “Tail” a data ke čtení by měl určovat “Head”, protože je tento princip přirovnáván ke frontě lidí, kde nově příchozí lidi jsou na konci fronty. Záleží na vás, co si vyberete, osobně se mi víc líbí první označení. Princip funkce vyrovnávací paměti je následovný:

 
• Po vytvoření je vyrovnávací paměť prázdná a její ukazatele pro zápis “Head” a čtení “Tail” jsou na stejné pozici, viz Obr. 1.
 
Obr. 1: Inicializace paměti - Prázdná paměť

Obr. 1: Inicializace paměti – Prázdná paměť

 
• Po zápisu jednoho bytu do paměti dojde k inkrementaci ukazatele “Head” pro zápis, viz Obr. 2.
 
Obr. 2: Zápis jednoho bytu

Obr. 2: Zápis jednoho bytu

 
• Situace, kdy bylo zapsáno šest bytů a byly dva byty přečteny, ukazuje Obr. 3.
 
Obr. 3: Zápis šesti bytů a přečtení dvou bytů

Obr. 3: Zápis šesti bytů a přečtení dvou bytů

 
• Pokud dojde k přetečení kruhové paměti, tak dojde k posunutí ukazatele zápisu “Head” opět na první pozici, viz Obr. 4.
 
Obr. 4: Přetečení paměti

Obr. 4: Přetečení paměti

 
• Pokud dojde k zaplnění kruhové paměti, tak to vypadá následovně, viz Obr. 5.
 
Obr. 5: Plná paměť

Obr. 5: Plná paměť

Z obrázku je patrné, že k zaplnění celé paměti dojde o jeden prvek dříve, než jaká je velikost paměti (pátá pozice neobsahuje zapsaná data). Kdybychom toto nedodrželi, tak nedokážeme rozpoznat prázdnou paměť od plné. Samozřejmě možností k určení plné paměti je více (a poté lze zaplnit i celou paměť), ale ty zde popsány nejsou.

Elektronické schéma zapojení

Obr. 6: Elektronické schéma zapojení

Obr. 6: Elektronické schéma zapojení

Okomentovaný zdrojový kód

Pro otestování byl vytvořen zdrojový kód, který zapíše tři byty a přečte pouze jeden, který následně pošle po sériové lince. Přijatá data lze sledovat na terminálu, abychom viděli, že jsou ukládány a čteny správné hodnoty. Jak pracovat s jednotkou USART mikrokontroléru ATmega(16/32) lze nalézt ZDE. Jak použít převodník UART TTL – USB a některý z terminálu lze nalézt zase ZDE. Tímto stylem zápisu dojde k celkem rychlému zaplnění vyrovnávací paměti. Míru zaplnění paměti ukazuje bargraf vytvořený pomocí LED diod připojených na PORTA. Pokud jsou rozsvícené všechny LED, tak je paměť prázdná. Pokud dojde k zaplnění paměti, tak zhasnou všechny LED diody a přestane probíhat zápis dat a bude pokračovat již jen čtení. Po ukončení zápisu lze sledovat, jak dochází k opětovnému rozsvícení všech LED diod a tím k vyprázdnění vyrovnávací paměti. Na připojeném terminálu lze zkontrolovat, že přicházejí data, která jsou postupně inkrementována až do hodnoty 255 a poté začínají znovu od nuly. Vzorovým kódem lze ověřit, že trojnásobnou rychlostí zápisu do paměti o 254 pozicích jsme dokázali uložit 380 B, viz Obr. 7.
Obr. 7: Příjem dat na terminálu

Obr. 7: Příjem dat na terminálu

Samotná vyrovnávací kruhová paměť je realizovaná pomocí struktury “FIFO_BUFFER”, která obsahuje paměť na data “buffer”, pozici zápisu “head”, pozici čtení “tail”, indikaci plné paměti “full” a indikaci prázdné paměti “empty”. Zápis dat do vyrovnávací paměti probíhá pomocí funkce “FIFOWriteByte”. Do této funkce může být po zápisu bytu ještě také přidáno vynulování příznaku prázdné paměti “empty” (pokud byl nastaven), aby bylo možné číst data pomocí “FIFOReadByte”, pouze pokud není paměť prázdná. Čtení dat probíhá pomocí funkce “FIFOReadByte”. V této funkci může být zase přidáno smazání příznaku o plné paměti “full” (pokud byl nastaven) po přečtení bytu. Ve vzorovém zdrojovém kódu tyto podmínky přidány nejsou, protože jak zápis, tak čtení dat probíhá periodicky.

 

001: #define F_CPU 16000000UL // frekvence hodinoveho signalu (nutné pro delay.h)
002: #include <avr/io.h// knihovna AVR pro Input/output
003: #include <util/delay.h// hlavickovy soubor pro zpozdeni
004: #include <avr/interrupt.h>// hlavickovy soubor pro preruseni
005: #include <stddef.h>// hlavickovy soubor nutny pro NULL
006:
007: #define BUFFER_SIZE 255// pamet pro 254 bytu
008:
009: // struktura pro vyrovnavaci pameti
010: typedef struct{
011: uint8_t buffer[BUFFER_SIZE];// velikost vyrovnavaci pameti
012: volatile uint32_t head;// pozice zapisu noveho bytu
013: volatile uint32_t tail;// pozice cteni bytu
014: volatile uint8_t full;// indikace plne pameti
015: volatile uint8_t empty;// indikace prazdne pameti
016: }FIFO_BUFFER;
017:
018: // prototypy funkci
019: void FIFOWriteByte(uint8_t data, FIFO_BUFFER *fifoBuffer);
020: uint8_t FIFOReadByte(FIFO_BUFFER *fifoBuffer);
021: void UARTPutc(uint8_t data);
022:
023:
024: uint8_t readedByte; // precteny byte
025: uint8_t currentByte; // aktualni byte pro ulozeni
026: uint8_t emptySize; // velikost volne pameti
027: uint8_t endProcess; // ukoncit zapis = doslo k zaplneni pameti
028: uint8_t StopBuffering;// identifikace pro ukonceni zapisu
029: // vytvoreni vyrovnavaci pameti
030: FIFO_BUFFER uartBuffer = { {0}, 0, 0, 0, 1 };
031:
032: int main(void)
033: {
034: DDRA = 0xFF;// PORTA = vystup
035: PORTA = 0x00;// Vynulovani vystupu
036:
037: // Nastaveni rychlosti 9600Bd
038: // BAUD = 16e6 / (16 * (103+1)) = 9615
039: UBRRL = 0b01100111;
040: UBRRH = 0b00000000;
041:
042: // Nastaveni jednotky USART
043: UCSRB = 0b10011000// Povoleno preruseni po prijmu dat,
044: // povoleni prijimace a vysilace
045: UCSRC = 0b10000110// Asynchronni rezim, zadna parita, 1 stop bit, delka dat 8b
046:
047: // vychozi nastaveni promennych
048: currentByte = 0;
049: emptySize = 0;
050: endProcess = 0;
051: StopBuffering = 0;
052:
053: sei()// povoleni preruseni
054:
055: while(1)
056: {
057:
058: for (int i = 0; i < 3; i++)
059: {
060: if (uartBuffer.full)
061: {
062: StopBuffering = 1;
063: }
064:
065: if (!StopBuffering)// zapisovat data pouze do prvniho zaplneni
066: {
067: // zapis dat
068: FIFOWriteByte(currentByte, &uartBuffer);
069:
070: if (currentByte > 254)
071: {
072: currentByte = 0;// vynulovani cisla pro ulozeni
073: }
074: else
075: {
076: currentByte++;// inkrementace cisla pro ulozeni
077: }
078: }
079: }
080:
081: // cteni dat
082: readedByte = FIFOReadByte(&uartBuffer);
083:
084: if (!uartBuffer.empty)// pokud neni pamet prazdna, tak precti byte
085: {
086: // pokud byl precteny byte, tak poslat po seriove lince
087: UARTPutc((uint8_t) readedByte);
088: }
089:
090: // vypocet velikosti volneho mista v pameti
091: if (uartBuffer.head >= uartBuffer.tail)
092: {
093: emptySize = uartBuffer.head – uartBuffer.tail;
094: }
095: else
096: {
097: emptySize = ((BUFFER_SIZE – uartBuffer.tail) + uartBuffer.head);
098: }
099:
100: // zobrazeni volneho mista (POUZE PRO VELIKOST 255, jinak nutne predelat)
101: if (emptySize < 31)
102: {
103: PORTA = 0b00000001;
104: }
105: else if ((emptySize < 64) && (emptySize >= 31))
106: {
107: PORTA = 0b00000011;
108: }
109: else if ((emptySize < 95) && (emptySize >= 64))
110: {
111: PORTA = 0b00000111;
112: }
113: else if ((emptySize < 127) && (emptySize >= 95))
114: {
115: PORTA = 0b00001111;
116: }
117: else if((emptySize < 158) && (emptySize >= 127))
118: {
119: PORTA = 0b00011111;
120: }
121: else if((emptySize < 189) && (emptySize >= 158))
122: {
123: PORTA = 0b00111111;
124: }
125: else if ((emptySize < 220) && (emptySize >= 189))
126: {
127: PORTA = 0b01111111;
128: }
129: else if ((emptySize <= 255) && (emptySize >= 220))
130: {
131: PORTA = 0b11111111;
132: }
133:
134: _delay_ms(50);
135: }
136: return 0;
137: }
138:
139: // Zapis dat do pameti FIFO
140: void FIFOWriteByte(uint8_t data, FIFO_BUFFER *fifoBuffer)
141: {
142: uint32_t next;// dalsi pozice k zapisu
143:
144: if ((fifoBuffer != NULL))
145: {
146: // vypocet dalsi pozice
147: next = (unsigned int)(fifoBuffer->head + 1) % BUFFER_SIZE;
148:
149: // pokud nasledujici pozice neni stejna se ctenim, tak
150: // ulozit
151: if (next != fifoBuffer->tail)
152: {
153: // ulozeni dat
154: fifoBuffer->buffer[fifoBuffer->head] = data;
155: // ulozeni nove pozice
156: fifoBuffer->head = next;
157:
158: if (fifoBuffer->full)// pokud je nastaven priznak o plne pameti, tak smazat
159: {
160: fifoBuffer->full = 0;
161: }
162: }
163: else// plna pamet
164: {
165: // pokud neni nastaven priznak o plne pameti, tak
166: // nastavit
167: if (!fifoBuffer->full)
168: {
169: fifoBuffer->full = 1;
170: }
171: }
172: }
173: }
174:
175: // Cteni dat z pameti FIFO
176: uint8_t FIFOReadByte(FIFO_BUFFER *fifoBuffer)
177: {
178: uint8_t data;
179:
180: if (fifoBuffer != NULL)
181: {
182: // pokud jsou stejne pozice zapisu i cteni = prazdna pamet
183: if (fifoBuffer->head == fifoBuffer->tail)
184: {
185: // pokud neni nastaven priznak o prazdne pameti, tak
186: // nastavit
187: if (!fifoBuffer->empty)
188: {
189: fifoBuffer->empty = 1;
190: }
191:
192: return 0;
193: }
194: else// pamet neni prazdna
195: {
196: // precist byte
197: data = fifoBuffer->buffer[fifoBuffer->tail];
198: // ulozit novou pozici
199: fifoBuffer->tail = (uint32_t)(fifoBuffer->tail + 1) % BUFFER_SIZE;
200:
201: // pokud je nastaven priznak o prazdne pameti, tak
202: // smazat
203: if (fifoBuffer->empty)
204: {
205: fifoBuffer->empty = 0;
206: }
207:
208: return data;
209: }
210: }
211: else
212: {
213: return 0;
214: }
215: }
216:
217: // Poslani znaku pomoci seriove linky
218: void UARTPutc(uint8_t data)
219: {
220: // Cekani na vyprazdneni registru UDR
221: while (!( UCSRA & (1<<UDRE)));
222: UDR = data;
223: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 
Aktualizace: 17.8.2015
 

Následující a předchozí příspěvek v kategorii:

 
Následující: Výměna hodnot dvou proměnných bez nutnosti využití třetí proměnné
Předchozí: Rozšíření knihovny pro barevný grafický LCD displej s řadičem ST7735 o zobrazení sedmisegmentového displeje
 
Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.