A firmware: Kissé terjengős egy oldal lett, de szerettem volna mindent bemutatni a firmware-val kapcsolatosan.
A programozó lelke egy PIC16F887 mikrokontroller, amely közvetlenül kezeli a 28C16 EEPROM teljes párhuzamos buszát. A firmware mikroC PRO for PIC környezetben készült, 16 MHz-es külső kvarccal, 9600 baud sebességű UART kommunikációval.
A firmware fő feladatai:
- EEPROM címvonalak kezelése
- adatbusz irányának váltása
CE, OE, WE vezérlőjelek előállítása
- byte olvasás
- byte írás
- teljes memória törlés
- visszaellenőrzés
- soros parancsok feldolgozása
- státusz LED-ek vezérlése
Alap paraméterek
A 28C16 memória mérete 2048 byte, ezért a firmware-ben ez fixen definiálva van:
#define EEPROM_SIZE 2048
#define RXBUF_SZ 48
Az EEPROM_SIZE határozza meg a kezelhető címtartományt. Mivel a 28C16 memória 2K x 8 szervezésű, az érvényes címek:
0x0000 - 0x07FF
A soros parancsok fogadására egy 48 byte-os puffer szolgál. Ez bőven elegendő az egyszerű parancsformátumokhoz, például:
R 0x0000
W 0x0000 0xAA
D 0x0000 64
Valós PCB lábkiosztás
A firmware már a kész NYÁK lábkiosztásához igazodik. Ez azért fontos, mert nem elméleti, breadboardos verzióról van szó, hanem a ténylegesen legyártott panel portkiosztásáról.
|
D0-D5
|
RA0-RA5
|
|
D6-D7
|
RB0-RB1
|
|
A0-A7
|
RD0-RD7 |
| A8-A10 |
RE0-RE2
|
Státusz LED-ek:
WRITE -> RC0
READ -> RC1
ERROR -> RC2
ABORT -> RC3
CMD_PROC -> RC4
STBY -> RC5
Ez a kiosztás jól illeszkedik a 28C16 működéséhez, mert a címbusz alsó 8 bitje egy teljes porton, a PORTD-n jelenik meg, így az alsó címbyte nagyon egyszerűen kiírható.
Inicializálás:
A gpio_init() függvény készíti elő a mikrokontrollert.
Első lépésként minden analóg funkció le van tiltva:
ANSEL = 0x00;
ANSELH = 0x00;
ADCON0.ADON = 0;
Ez nagyon fontos PIC16F887 esetén, mert több láb alapból analóg bemenetként is működhet. Ha ezeket nem tiltjuk le, akkor a digitális portkezelés furcsa hibákat okozhat.
A komparátorok is letiltásra kerülnek:
C1ON_bit = 0;
C2ON_bit = 0;
Ezután a portok nullázódnak:
PORTA = 0x00;
PORTB = 0x00;
PORTC = 0x00;
PORTD = 0x00;
PORTE = 0x00;
A címbusz kimenet lesz:
ADDR_LO_TRIS = 0x00;
ADDR_HI_TRIS = 0x00;
A vezérlőjelek szintén kimenetek:
WE_TRIS = 0;
OE_TRIS = 0;
CE_TRIS = 0;
Az adatbusz viszont alaphelyzetben bemenet:
data_dir_input();
Ez biztonsági szempontból fontos. Bekapcsoláskor nem jó, ha a PIC és az EEPROM egyszerre próbálja hajtani az adatbuszt.
EEPROM alapállapot
Az eeprom_idle() függvény biztonságos nyugalmi állapotba teszi a memóriát:
CE_LAT = 1;
OE_LAT = 1;
WE_LAT = 1;
Mivel ezek aktív alacsony jelek, az 1-es állapot azt jelenti, hogy:
/CE inaktív
/OE inaktív
/WE inaktív
Vagyis a memória nincs kiválasztva, nem olvasunk, és nem írunk.
Ezután az adatbusz felszabadul:
TRISA0_bit = 1;
...
TRISB1_bit = 1;
Így a PIC nem hajtja az adatvonalakat, elkerülhető a buszütközés.
Adatbusz irányváltás
A 28C16 adatbusza kétirányú. Olvasáskor az EEPROM hajtja az adatvonalakat, íráskor pedig a PIC.
Ezért van két külön függvény:
void data_dir_input(void)
void data_dir_output(void)
Olvasás előtt mindig bemenetre kell állítani a PIC adatbusz lábait:
data_dir_input();
Írás pedig a kimenetre: data_dir_output();
Ez a firmware egyik legfontosabb része, mert ha rossz sorrendben történne, akkor a PIC és az EEPROM egyszerre hajthatná ugyanazt a vonalat.
Adat kiírása a buszra
A data_put() függvény egy 8 bites értéket rak ki az adatbuszra.
RA0_bit = (v >> 0) & 1;
RA1_bit = (v >> 1) & 1;
...
RB1_bit = (v >> 7) & 1;
Mivel az adatbusz nem egyetlen teljes porton van, hanem részben PORTA-n, részben PORTB-n, ezért bitenként történik a kiírás.
A kiosztás:
bit 0 -> RA0
bit 1 -> RA1
bit 2 -> RA2
bit 3 -> RA3
bit 4 -> RA4
bit 5 -> RA5
bit 6 -> RB0
bit 7 -> RB1
Adat beolvasása a buszról:
A data_get() ennek a fordítottja. Beolvassa a portlábak állapotát, majd egy 8 bites értékké rakja össze:
if (RA0_bit) v |= 0x01;
if (RA1_bit) v |= 0x02;
...
if (RB1_bit) v |= 0x80;
Ez azért jó megoldás, mert a firmware szintjén az EEPROM adatbyte-ja már egységes unsigned char típusként kezelhető, függetlenül attól, hogy fizikailag több porton van szétszórva.
Cím beállítása:
A addr_set() függvény állítja be a kiválasztott EEPROM címet.
Az alsó 8 címvonal egyben kerül ki a PORTD-re:
ADDR_LO_PORT = (unsigned char)(a & 0xFF);
Ez adja:
A0-A7
A felső három címvonal a PORTE alsó három bitjére kerül:
hi = ADDR_HI_PORT & 0xF8;
hi |= (unsigned char)((a >> 8) & 0x07);
ADDR_HI_PORT = hi;
Ez adja:
A8-A10
A 0xF8 maszkolás azért szerepel benne, hogy a PORTE felső bitjei ne változzanak meg feleslegesen. A 28C16-hoz összesen 11 címvonal kell, ezért a firmware csak az alsó 11 bitet használja.
Byte olvasás
Az EEPROM olvasását az eep_read_byte() végzi.
A folyamat:
data_dir_input();
addr_set(a);
WE_LAT = 1;
CE_LAT = 0;
OE_LAT = 0;
delay_us_safe(2);
d = data_get();
OE_LAT = 1;
CE_LAT = 1;
Lépésenként:
1. Az adatbusz bemenetre áll.
2. A PIC beállítja a kívánt címet.
3. A WE inaktív marad.
4. A CE aktív lesz, vagyis kiválasztjuk az EEPROM-ot.
5. Az OE aktív lesz, vagyis az EEPROM meghajtja az adatbuszt.
6. Rövid várakozás után a PIC beolvassa az adatot.
7. Az OE és CE újra inaktív lesz.
A 2 µs várakozás bőven elegendő az ilyen régi párhuzamos EEPROM-ok elérési idejéhez.
Byte írás:
Az írást az eep_write_byte() függvény végzi. Ez már összetettebb, mert az EEPROM-nak idő kell a cella tényleges programozásához.
A folyamat elején bekapcsol a WRITE LED:
LED_WRITE_LAT = LED_ON_LEVEL;
Ezután a firmware biztonságos állapotba teszi a vezérlőjeleket:
OE_LAT = 1;
WE_LAT = 1;
CE_LAT = 1;
Majd beállítja a címet és az adatot:
addr_set(a);
data_dir_output();
data_put(v);
Ezután kiválasztja az EEPROM-ot:
CE_LAT = 0;
Majd létrehozza az íróimpulzust:
WE_LAT = 0;
delay_us_safe(5);
WE_LAT = 1;
Tehát a WE rövid időre alacsony szintre kerül. Ez indítja el az adott byte programozását.
Ezután:
CE_LAT = 1;
data_dir_input();
A memória ki van választva, az adatbusz pedig visszaáll bemenetre.
Írás utáni polling
A 28C16 nem azonnal írja be fizikailag az adatot. A cellaprogramozás néhány milliszekundumot igényelhet.
Ezért a firmware nem vakon vár fix ideig, hanem visszaolvassa az adott címet:
for (t = 0; t < 25; t++) {
d = eep_read_byte(a);
if (d == v) {
LED_WRITE_LAT = LED_OFF_LEVEL;
return 1;
}
delay_ms_safe(1);
}
Ez maximum 25 ms-ig próbálkozik. Ha a visszaolvasott érték megegyezik az írt értékkel, akkor az írás sikeres.
Ha 25 ms alatt sem egyezik, akkor timeout történik:
return 0;
Ez nagyon jó megoldás, mert a firmware nem csak “reméli”, hogy sikerült az írás, hanem ténylegesen visszaellenőrzi.
Soros kommunikáció:
A firmware UART-on kommunikál a PC-s programmal:
UART1_Init(9600);
A bekapcsolási üzenet:
EEPROM-PGM HW READY V2
A fő ciklus folyamatosan figyeli az UART-ot:
while (1) {
if (UART1_Data_Ready()) {
char c = UART1_Read();
...
}
}
A karaktereket sorvége karakterig gyűjti:
'\r' vagy '\n'
Ha beérkezett egy teljes sor, akkor meghívja:
process_line(rxbuf);
A firmware csak nyomtatható ASCII karaktereket fogad el, és védi magát a túl hosszú parancsoktól is:
ERR LONG
Parancsfeldolgozás
A process_line() dolgozza fel a PC felől érkező parancsokat.
A támogatott parancsok:
P ping
S státusz
R cím egy byte olvasása
W cím adat egy byte írása
D cím hossz blokk olvasása
E teljes törlés
O LED-ek ki
L LED-ek be
T LED teszt
A parancsfeldolgozás elején bekapcsol a CMD_PROC LED, a készenléti LED pedig kikapcsol:
command_begin();
A végén minden művelet után biztonságos állapotba kerül az EEPROM busz:
eeprom_idle();
command_end();
Ez azért fontos, mert még hibás parancs után sem marad aktív írási,22 vagy olvasási állapotban a memória.
Ping parancs
A P parancs egyszerű kapcsolatellenőrzés:
Válasz:
PING: OK EEPROM-PGM HW V2 tomcii
Ez a PC-s alkalmazásnak arra jó, hogy ellenőrizze, valóban a programozó van-e a kiválasztott COM porton.
Státusz parancs
Az S parancs visszaadja az alapvető firmware információkat:
Válasz:
STATUS:OK EEPROM-HW V2 tomcii SIZE=2048 BAUD=9600
Ebben szerepel:
hardver/firmware verzió
memóriaméret
baudrate
Ez később nagyon hasznos lehet, ha több hardververzió vagy firmware-verzió készül.
Egy byte olvasása
Az R parancs egyetlen byte-ot olvas ki:
R 0x000
Válasz:
D 0000 AA
A válasz formátuma:
D (Dump)
Ha a cím kívül esik az érvényes tartományon, akkor:
ERR ADDR
Egy byte írása
A W parancs egyetlen byte-ot ír:
W 0x0000 0xAA
Sikeres írás esetén a válasz?ó:
OK 0000 AA
A firmware írás után visszaolvassa az adott címet, és a ténylegesen olvasott értéket küldi vissza.
Ha az írás nem fejeződik be időben:
ERR TIMEOUT
Ez jelzi, hogy a polling nem kapta vissza a kívánt értéket.
Blokk olvasás
A D parancs egyszerre több byte-ot olvas ki:
D 0x0000 64
A firmware maximum 64 byte-os blokkot enged egyszerre:
if (length == 0 || length > 64) {
uart_puts("ERR LEN\r\n");
}
Ez azért praktikus, mert a válasz nem lesz túl hosszú, a soros kommunikáció pedig stabil marad.
A válasz formátuma:
B 0000 40 AA BB CC ...
Itt:
B = blokk válasz
0000 = kezdőcím
40 = hossz hexában
utána = adatbyte-ok hex formában
A PC-s program ebből kényelmesen fel tudja építeni a teljes 2048 byte-os dumpot.
Teljes törlés
Az E parancs végigírja a teljes EEPROM-ot 0xFF értékkel:
E
A firmware végigmegy az összes címen:
for (i = 0; i < EEPROM_SIZE; i++) {
if (!eep_write_byte(i, 0xFF)) {
eraseOk = 0;
break;
}
}
Minden 256 byte után progress üzenetet küld:
P 0100
P 0200
P 0300
...
Hogy valamiféle visszajelzést lehetővé tegyen a törlés is a PC-s applikációban.
Sikeres törlés esetén:
OK ERASE
Ez nagyon hasznos, mert nem csak annyit tudunk, hogy sikertelen volt a törlés, hanem azt is, melyik címnél akadt el.
LED vezérlő parancsok
A firmware tartalmaz egyszerű LED tesztfunkciókat is.
LED-ek kikapcsolása:
D
Válasz:
OK LEDS OFF
LED-ek bekapcsolása:
L
Válasz:
OK LEDS ON
LED teszt:
T
Ilyenkor a firmware egymás után felvillantja a státusz LED-eket:
WRITE
READ
ERROR
ABORT
CMD
STBY
Válasz:
OK LED TEST
Ez a funkció különösen jól jön beültetés után, mert gyorsan ellenőrizhető, hogy minden LED jó irányban van-e beforrasztva.
Számformátumok kezelése
A firmware rugalmas számbevitelt enged.
Elfogad például:
R 0
R 123
R 0x000A
R 000A
W 0x0100 0xAA
A parse_number() függvény felismeri, ha a szám 0x előtaggal hexadecimális, de akkor is hexának kezeli, ha A-F karaktert talál benne.
Ez kényelmesebbé teszi a kézi terminálos használatot is.
Hibakezelés
A firmware több alapvető hibát is kezel:
Ismeretlen parancs:
ERR CMD
Érvénytelen cím:
ERR ADDR
Túl hosszú parancssor:
ERR LONG
Írás időtúllépés:
ERR TIMEOUT
Hiba esetén az ERROR LED röviden felvillan:
led_error_pulse();
Ez egyszerű, de nagyon hasznos vizuális visszajelzés.
Firmware működésének összefoglalása
A firmware alapvetően egy egyszerű, soros parancsértelmezőből, és egy alacsony szintű EEPROM buszkezelő rétegből áll.
A felépítés logikusan szétválasztható:
UART parancsok
↓
process_line()
↓
EEPROM műveletek
↓
cím-, adat-, és vezérlőbusz kezelése
↓
28C16 memória
A megoldás előnye, hogy a PC-s programnak nem kell ismernie a 28C16 időzítéseit. A számítógép csak egyszerű parancsokat küld, minden hardverközeli feladatot a PIC végez el.
Ezáltal a programozó használható akár saját Windows alkalmazással, akár egyszerű soros terminálból is.
Miért jó ez a megoldás?
A firmware nem próbál univerzális EEPROM programozó lenni. Pont az a célja, hogy a 28C16 családot egyszerűen, stabilan és átláthatóan kezelje.
A közvetlen buszvezérlés miatt nagyon jól tanulmányozható:
• hogyan működik egy párhuzamos EEPROM
• hogyan kell adatbuszt irányt váltani
• hogyan működik az aktív alacsony vezérlés
• hogyan történik az írási impulzus
• hogyan lehet visszaellenőrizni a programozást
Ezért a firmware nem csak a kész eszköz működtetésére alkalmas, hanem oktatási szempontból is értékes része a projektnek. A következőben ismertetem az általam VS-ben készített applikációt.
A cikk még nem ért véget, lapozz!