8.1 EEPROM 과 Flash Memory ATmega128에는프로그램메모리로서 128KB의플래시메모리를내장하고있고데이터메모리로서 4KB의 EEPROM을내장하고있다. EEPROM과플래시메모리는하나의뿌리에서발전해온매우유사한메모리이지만사용방법이서로다르다. 이것들은오늘날독립된메모리소자로서도널리사용되고있으므로충분히알아둘필요가있다. EEPROM(Electrically Erasable PROM) 은온보드 (on-board) 상태에서사용자가내용을바이트단위로 read 하거나 write할수있으므로사실상 SRAM처럼사용할수있는비휘발성메모리이며, 따라서제조회사에따라서는이를 EEPROM이라고부르지않고 EAPROM(Electrically Alterable PROM) 또는 NVRAM (Non-Volatile RAM) 이라고부르기도한다. 그러나이것의리드동작은액세스시간이다소느리기는하더라도 SRAM과유사하므로별문제가없는데비하여, Write 동작을수행하는경우에는 1바이트를 write할때마다수 [ms] 이상의시간지연이필요하므로 SRAM과동일하게사용할수는없다. 따라서, EEPROM은실시간으로사용되는변수를저장하는메모리나스택메모리로는사용될수없으며, 한번내용을저장하면비교적오랫동안이를기억하고있으면서주로이를읽어사용하기만하거나전원을꺼도지워져서는안되는중요한데이터를백업하여두어야하는설정값저장용메모리로적합하다. 이메모리소자는 28Cxxx의형태로이름을짓는경우가많다. 플래시메몰 (flash memory) 는온보드상태에서사용자가내용을바이트단위로자유로이리드할수는있지만, Write는페이지 (page) 또는섹터 (sector) 라고불리는블록 (block) 단위로만수행할수있는변형된 EEPROM 이다. 블록의크기는메모리소자나메이커에따라다르지만대체로 64바이트, 128바이트, 256바이트등에서부터 128KB까지도사용한다. 이렇게플래시메모리는 EEPROM과매우유사하지만바이트단위로 Write 하는것이불가능하므로 Page mode write 기능만을가지는 EEPROM 이라고생각하면되며, 따라서역시 SRAM처럼실시간데이터메모리로사용하는것은불가능하다. 그러나, 플래시메모리는 EEPROM보다메모리셀의구조가간단하여훨씬대용량의메모리소자를만드는데적합하며, 1개의블록전체를 Write 하는데수 [ms] 정도가걸리므로대용량데이터를라이트할때는 EEPROM보다훨씬속도가빠르다는장점을가진다. 그런데, 플래시메모리는내부구조에따라서 NOR형플래시와 NAND형플래시메모리로나눌수있다. NOR형플래시는다른메모리소자와같이외부구조가어드레스버스, 데이터버스, 그리고몇가지의제어신호및전원으로되어있어서프로그램저장용으로널리사용된다. NOR형플래시메모리는대부분 29Cxxx 형태의이름을가지고있다. 그러나, NAND형플래시메모리는어드레스버스와데이터버스가따로있는것이아니고 8개 ( 또는 16개 ) 의데이터신호와몇개의제어신호및전원핀을가지고있다. 8개의데이터신호선은어드레스및제어명령을 Write하거나데이터값을읽고쓰는용도로사용된다. 이렇게하면메모리를읽고쓰는동작은좀번거로워지지만메모리용량이증가하더라도핀수가늘어나지않으므로하드웨어규격을통일할수있어서플래시메모리를마치하드디스크처럼사용하는것이가능하다. 특히, 요즈음에는휴대용기기를염두에두고 1.8V 나 3.3V의저전압에서동작하는수십MB~ 수 GB짜리대용량의 NAND형플래시메모리가많이개발되어휴대용통신기기, 디지털카메라, MP3 플레이어, 이동형 USB 메모리등과같은용도에널리사용되고있다.
8.1.1 내부 EEPROM의이해 ATmega128은바이트단위로읽고쓰기가가능한 4K의내부 EEPROM을가지고있다. 이 EEPROM은최소 10만번이상의쓰고지우기가가능하다. 관련된레지스터는 EEPROM Address 레지스터, Data 레지스터, Control 레지스터가있다. 8.1.2 EEARH/EEARL (EEPROM Address Register) EEARH/EEARL (EEPROM Address Register) 0x1F(0x3F), 0x1E(0x3E) bit 7 6 5 4 3 2 1 0 - - - - EEARH[11:8] EEARL[7:0] Read/Write R/W R/W R/W R/W R/W R/W R/W R/W 초기값 0 0 0 0 x x x x x x x x x x x x ATmega128의 4K EEPROM 공간이있고바이트단위로읽고쓰기때문에주소공간이 12비트가필요하다. 그래서상위와하위로구분되는두개의레지스터를사용해서필요한주소를지정할수있다. 8.1.3 EEDR (EEPROM Data Register) EEDR (EEPROM Data Register) 0x1F(0x3F), 0x1E(0x3E) bit 7 6 5 4 3 2 1 0 MSB LSB Read/Write R/W R/W R/W R/W R/W R/W R/W R/W 초기값 0 0 0 0 0 0 0 0 실제로읽거나쓰기위해사용되는바이트단위의데이터가저장되어있는레지스터이다. 8.1.4 EECR (EEPROM Control Register) EECR (EEPROM Control Register) 0x1C(0x3C) bit 7 6 5 4 3 2 1 0 - - - - EERIE EEMWE EEWE EERE Read/Write R/W R/W R/W R/W R/W R/W R/W R/W 초기값 0 0 0 0 0 0 0 0 Bit 3 : EERIE (EEPROM Ready Interrupt Enable) 이비트가 1 로설정되고 SREG 레지스터의 I 비트가 1 로설정되면 EEPROM Ready 인터럽트가활성화되고 0 으로설정되면비활성화된다. EEPROM Ready 인터럽트는 EEWE 가클리어되었을때발생한다.
Bit 2 : EEMWE (EEPROM Master Write Enable) EEMWE (EEPROM Master Write Enable) 레지스터가 1 로설정되고나서, EEWE가설정되면 4클럭사이클이지나고난후선택된주소에데이터가써지게된다. EEMWE가 0 이면 EEWE는효과를볼수없다. EEMWE는소프트웨어에의해서 1로설정되고 4클럭사이클이지난후에하드웨어에의해서클리어된다. Bit 1 : EEWE (EEPROM Wirte Enable) EEMWE 레지스터가 1로설정되어있다면 EEWE를 1로설정해야쓰기기능이동작한다. 쓰기가완료되면하드웨어에의해서클리어된다. EEWE가 1로설정되고나면다음인스트럭션이실행되기전에 CPU는 2클럭사이클동안멈추고있는다. Bit 0 : EERE (EEPROM Read Enable) 정상적으로 EEAR 레지스터에주소값이설정되면 EERE 레지스터를 1로설정하여읽기를한다. 읽기동작은한인스트럭션 (instruction) 으로끝나기때문에바로데이터를읽을수있다. 읽기가실행되고나면다음인스트럭션이실행되기전에 CPU는 4클럭사이클동안멈추고있는다. 8.1.4 쓰기예제 아래의예제소스는데이터시트에서나오는간단한쓰기예제이다. void EEPROM_write(unsigned int uiaddress, unsigned char ucdata) // 이번의쓰기가완료되기를기다린다 while(eecr & (1<<EEWE)) // 주소와데이터레지스터를설정한다. EEAR = uiaddress EEDR = ucdata // EEMWE 에 1을설정한다. EECR = (1<<EEMWE) //EEWE를설정하여쓰기를시작한다. EECR = (1<<EEWE) EEWE가클리어될때까지기다리고있다가, 주소와데이터를설정하고 EEMWE를 1로설정하여기다린후 EEWE를설정하여쓰기를시작하면된다.
8.1.5 읽기예제 void EEPROM_read(unsigned int uiaddress) // 이번의쓰기가완료되기를기다린다 while(eecr & (1 << EEWE)) // 주소레지스터를설정한다. EEAR =uiaddress //EERE를설정하여읽기를시작한다. EECR = (1<<EERE) // 데이터레지스터에서데이터를읽는다. return EEDR 쓰기와같이 EEWE가클리어되는것을기다리다가, 주소를설정하고 EERE를 1로설정하여읽기동작을한후에데이터를읽어오면된다. 8.1.6 avr-gcc에서지원하는함수위와같이레지스터를사용하는방법외에 avr-gcc에서지원하는내부 EEPROM 관련함수들이있다. 이함수들은 avr/eeprom.h 에정의가되어있다. 헤더파일에는다양한종류의함수들을지원하고있고, 그중주요함수는다음과같다. 함수원형 함수기능 eeprom_is_ready() EECR 레지스터에서 EEWE 비트가클리어될때까지대기 eeprom_write_byte(addr, val) 해당주소에값을쓰기가가능하다 eeprom_read_byte(addr) 해당주소에바이트단위로읽기가가능하다.
8.1.7 학습예제 8.1.7.1 내부 EEPROM 에 Hello DK128" 을쓰고읽어서하이퍼터미널에출력하라.. ex5_1.c 내부 EEPROM 에 Hello DK128" 을쓰고, 읽어서하이퍼터미널에출력하는예제 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <avr/io.h> #include <stdlib.h> #include <stdio.h> #include <avr/eeprom.h> #define CPU_CLOCK 16000000 #define BAUD_RATE 19200 #define BAUD_RATE_L (CPU_CLOCK / (16l * BAUD_RATE)) - 1 #define BAUD_RATE_H ((CPU_CLOCK / (16l * BAUD_RATE)) - 1) >> 8 #define UART_RX_BUFLEN 128 #define BS 0x08 #define CR 0x0D void uart_send_byte(unsigned char byte) while (!(UCSR1A & (1 << UDRE))) UDR1 = byte void eeprom_write(unsigned int addr, unsigned char data) // EEPROM 쓰기함수 while (EECR & (1 << EEWE))// 이번의쓰기가완료되기를기다린다. EEAR = addr // 주소와데이터레지스터를설정한다. EEDR = data EECR = (1 << EEMWE) // EEMWE에 1을설정한다. EECR = (1 << EEWE) // EEWE를설정하여쓰기를시작한다. unsigned char eeprom_read(unsigned int addr) // EEPROM 읽기함수 while (EECR & (1 << EEWE)) // 이번의쓰기가완료되기를기다린다. EEAR = addr // 주소레지스터를설정한다. EECR = (1 << EERE) // EERE를설정하여읽기를시작한다. // 데이터레지스터에서데이터를읽는다. return EEDR
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 int main(void) unsigned char data unsigned char buf[] = "Hello DK128!" unsigned int i // baud rate 설정 UBRR1L = (unsigned char)baud_rate_l UBRR1H = (unsigned char)baud_rate_h // no parity, 1 stop bit, 8bit 설정 UCSR1C = (0 << UPM1) (0 << UPM0) (0 << USBS) (1 << UCSZ1) (1 << UCSZ0) // rx/tx interrupt 설정, 8bit 설정 UCSR1B = (1 << TXEN) (1 << RXEN) (0 << UCSZ2) for (i = 0 i < sizeof(buf) i++) // 문자열의길이만큼 EEPROM의 0번지부터쓴다. eeprom_write(i, buf[i]) for (i = 0 i < sizeof(buf) i++) // EEPROM의 0번지부터바이트단위로읽는다. data = eeprom_read(i) // 읽은값을터미널에보여준다. uart_send_byte(data) return 1 21~29행 : 주소값과 1 바이트의데이터를인자로하는 EEPROM 쓰기함수이다. 우선전송이가능한지확인하기위해서 EECR 레지스터의 EEWE 비트가 0이될때까지기다린다. EEWE 비트가클리어되면 EEAR 레지스터에쓰고자하는위치의주소값을쓴다. 그리고레지스터에쓸데이터값을쓰면내부 EEPROM으로데이터가전송된다. 32~41행 : 주소값을인자로하는 EEPROM 읽기함수이다. 현재사용중인지를확인하기위해서쓰기동작과마찬가지로 EEWE 비트가클리어될때까지기다린다. 읽고자하는주소값을 EEAR 레지스터에써주고 EERE 비트를 1로설정하여 EEDR 레지스터에저장된데이터를가져온다.
60~64행 : EEPROM의첫주소인 0x00 번지부터문자열길이만큼반복문을사용하여해당번지에값을써준다. 66~73행 : EEPROM의 0번지부터읽을문자열의길이만큼문자를읽어서해당문자를하이퍼터미널에보여준다.
8.1.7.1 아래와같은메뉴를구성하고각해당하는번호에따라동작수행하는프로그램을 작성하라.( 단, 레지스터만사용 ) 메뉴 1. Internal EEPROM Initializing 2. Internal EEPROM Reading 3. Internal EEPROM Writing Input number (1-3) : 동작 1. Internal EEPROM Initializing EEPROM의내용을모두 0x00으로만들기 (0x00번지부터 0xFF 번지까지만동작 ) 2. Internal EEPROM Reading EEPROM의내용을번지단위로 16진수로표현하여 RS232 터미널에출력 (0x00번지부터 0xFF번지까지만동작 ) 3. Internal EEPROM Writing RS232를통해받은한라인의데이터를첫번째번지부터 EEPROM에저장 ( 기존의데이터는덮어쓰기 ) ex5_2.c 메뉴를구성하고각해당하는번호에따라동작수행하는예제 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <avr/io.h> #include <stdlib.h> #include <stdio.h> #define CPU_CLOCK 16000000 #define BAUD_RATE 19200 #define BAUD_RATE_L (CPU_CLOCK / (16l * BAUD_RATE)) - 1 #define BAUD_RATE_H ((CPU_CLOCK / (16l * BAUD_RATE)) - 1) >> 8 #define UART_RX_BUFLEN 128 #define BS 0x08 #define CR 0x0D void uart_send_byte(unsigned char byte) // 1 byte 전송함수 while (!(UCSR1A & (1 << UDRE))) // 전송버퍼가빌때까지기다린다. UDR1 = byte void uart_send_string(unsigned char *str, unsigned char len) // 문자열전송함수 int i for (i = 0 i < len i++) if (!(*(str + i))) break uart_send_byte(*(str + i))
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 unsigned char uart_recv_byte(void) // 1 byte 수신함수 while (!(UCSR1A & (1 << RXC)))// 수신버퍼가찰때까지기다린다. return UDR1 void menu_display(void) // 메뉴를보여준다 uart_send_string("1. Internal EEPROM Initializing n r", 33) uart_send_string("2. Internal EEPROM Reading n r", 28) uart_send_string("3. Internal EEPROM Writing n r", 28) uart_send_string("input number(1-3): ", 19) unsigned char uart_recv_string(unsigned char *str) // 문자열수신함수 unsigned char len = 0 unsigned char byte for () if (len >= UART_RX_BUFLEN) // 글자수제한 break byte = uart_recv_byte() // 1 byte씩읽어들인다. if (byte == 0x0D) // carriage return (ENTER Key) uart_send_byte(' n') uart_send_byte(' r') break // 수신종료 else if (byte == 0x08) // back space if (len) len-- uart_send_byte(byte) uart_send_byte(' ') uart_send_byte(byte) else str[len++] = byte uart_send_byte(byte) // 받은데이터보여주기 str[len] = ' 0' return len
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 void eeprom_write(unsigned int addr, unsigned char data) // EEPROM 쓰기함수 while (EECR & (1 << EEWE))// EEMWE에 1을설정한다. // 주소와데이터레지스터를설정한다. EEAR = addr EEDR = data EECR = (1 << EEMWE) // EEMWE에 1을설정한다. EECR = (1 << EEWE) // EEWE를설정하여쓰기를시작한다. unsigned char eeprom_read(unsigned int addr) // EEPROM 읽기함수 while (EECR & (1 << EEWE)) // 이번의쓰기가완료되기를기다린다. EEAR = addr // 주소레지스터를설정한다. EECR = (1 << EERE) // EERE를설정하여읽기를시작한다. // 데이터레지스터에서데이터를읽는다. return EEDR int main(void) unsigned char eeprom_data[5] unsigned char buf[uart_rx_buflen] unsigned int i, len unsigned char data // baud rate 설정 UBRR1L = (unsigned char)baud_rate_l UBRR1H = (unsigned char)baud_rate_h // no parity, 1 stop bit, 8bit 설정 UCSR1C = (0 << UPM1) (0 << UPM0) (0 << USBS) (1 << UCSZ1) (1 << UCSZ0) // rx/tx interrupt 설정, 8bit 설정 UCSR1B = (1 << TXEN) (1 << RXEN) (0 << UCSZ2)
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 for () menu_display() uart_recv_string(buf) switch (atoi(buf)) case 1: // 0x00부터 0xFF 까지만 0x00으로초기화 for (i = 0x00 i < 0xFF i++) eeprom_write(i, 0x00) uart_send_string("ok. r n", 5) break case 2: // 0x00부터 0xFF 까지만읽어서출력 for (i = 0x00 i < 0xFF i++) data = eeprom_read(i) sprintf(eeprom_data, "0x%02X ", data) uart_send_string(eeprom_data, sizeof(eeprom_data)) if (((i + 1) % 15) == 0) // 15개씩한라인에출력 uart_send_string(" n r", 2) uart_send_string("ok. r n", 5) break case 3: // 읽은데이터를 0x00부터저장 len = uart_recv_string(buf) for (i = 0 i < len i++) eeprom_write(i, buf[i]) uart_send_string("ok. r n", 5) break default: uart_send_string("invalid arguments. r n", 20) break return 1
127행 : 해당메뉴를출력하는함수를호출한다. 명령이한번실행되고나면다시실행되도록반복문안에넣는다. 128 행 : 메뉴번호를입력받도록한다. 해당메뉴번호가아닌것인지는그뒤에체크한다. 130행 : switch 반복문을사용한다. 인자로는받은데이터를숫자로변환하는함수인 atoi() 를사용하여숫자로변환하여사용한다. Case문을사용하여실행가능한메뉴번호가아니라면잘못된인자라는메시지를출력하고무시한다. 132~137행 : 모든공간에쓰기동작을하기에는무리가있기때문에 EEPROM 의 0x00 번지부터 0xFF 번지까지만대상으로하여 0x00 값을저장한다. 동작이완료되면완료가되었다는메시지를출력하고다시메인메뉴로돌아가서새로운동작을기다린다. 138~150행 : 1번메뉴와마찬가지로 0x00 번지부터 0xFF 번지에만해당하는값을읽어서모두출력한다. 다만한라인에 15 단위로출력을하기위해서 % ( 나머지 ) 연산을사용하여 15개의데이터를출력하고나면개행문자를출력하여새로운행에데이터가출력되도록한다. 여기도마찬가지로완료가되면완료되었다는메시지를출력한다. 151~159행 : 우선하이퍼터미널로데이터가입력되길기다리다가데이터가입력되면해당데이터의길이를리턴받아서저장하고있다가해당길이만큼내부 EEPROM에저장한다. 0x00 번지부터저장을하기시작하므로저장되는범위는 0x00 번지부터문자열길이만큼이다. 마찬가지로완료메시지를출력한다. 161~162행 : 1, 2, 3번메뉴가아닌다른번호이거나전혀상관없는데이터를입력받았을때는잘못된인자라고메시지를출력한후다시데이터입력을대기하는모드로돌아간다. 8.1.8 실습예제 8.1.8.1 학습예제 2 와같은기능을하되, avr-gcc 에서제공하는함수를사용하여작성하라.