ATmega2560의 EEPROM을 이용하여 바이트 데이터를 읽고, 쓰는 과정을 진행해보겠습니다.
우선 ATmega2560에서 EEPROM의 위치와 Schematic을 확인합니다.
ATmega2560에서 사용하는 EEPROM은 마이크로칩사의 25LCn EEPROM입니다.
64Kbit의 메모리를 가지고 있습니다.
1바이트 데이터를 읽는 방법
- EEPROM 칩 선택
- CS핀에 LOW 가하기
- 읽기에 해당하는 8비트 명령어 전송(0x03)
- 데이터를 읽을 EEPROM의 주소를 전송(13비트)
- 바이트 단위로 상위 바이트부터 2번에 나누어 전송
- EEPROM에서는 해당 메모리 번지의 바이트 값을 마이크로컨트롤러로 전송할 준비 완료
- 실제로 EEPROM에서 1바이트의 데이터를 읽기 위해서는 임의의 1바이트 값을 슬레이브로 전송
- 전송이 끝나면 CS핀에 HIGH 가함
EEPROM에서 데이터를 읽고 쓰기 위해서는 여러 레지스터들을 세팅해주어야 합니다.
각 레지스터의 세팅값이 올바르게 되어 있어야만 SPI를 통해 데이터 통신이 가능합니다.
설정해야할 레지스터들을 살펴보겠습니다.
SPDR 레지스터 (SPI Data Register) [7:0]
- 데이터를 저장하면 데이터 전송이 자동으로 시작
SPSR 레지스터 (SPI Status Register) [7:0]
- 데이터 송수신 상태를 반영
- [7] SPIF(SPI Interrupt Flag) : 데이터 전송이 완료되면 세트된 후 SPI 인터럽트 발생, 전역 인터럽트 허용 비트가 세트되어 있어야 함
- [6] WCOL(Writer Collision Flag) : 데이터 전송 중에 SPDR 레지스터에 데이터를 쓰는 경우 세트, 충돌 알려줌
- [0] SPI2X(Double SPI Speed Bit) : SPCR 레지스터의 SPRn 비트와 함께 사용되어 SCK의 주파수 결정
EEPROM의 상태레지스터 [7:0]
- EEPROM_WRITE_IN_PROCRESS(WIP)의 값이 1이라면 쓰기가 진행됨
- [3] BP1
- [2] BP0
- [1] WEL
- [0] WIP
SPCR 레지스터 (SPI Control Register) [7:0]
- SPI 통신을 초기화
- [7] SPIE(SPI Interrupt Enable) : SPI 인터럽트 발생 허용
- [6] SPE(SPI Enable) : 1인 경우 SPI 통신 가능
- [5] DORD(Data Order) : 1인 경우 데이터의 LSB 전송, 0인 경우 MSB 전송
- [4] MSTR : 1인 경우 마스터 모드, 0인 경우 슬레이브 모드 설정
- [3] CPOL : 클록 극성
- [2] CPHA : 클록 위상
- [1:0] SPR1 SPR0 (SPI Clock Rate Select) : 마스터로 설정된 경우 클록을 설정하기 위해 사용
레지스터를 설정하는 법을 알았으니 레지스터를 설정하는 코드를 구현하여 UART로 EEPROM의 데이터를 출력해보도록 하겠습니다. 아래 코드들과 Putty가 필요합니다. 보드를 연결한 후 아래 코드를 업로드 시켜줍니다.
EEPROM_25LC640.cpp
#include "EEPROM_25LC640.h"
uint8_t EEPROM_read_byte(uint16_t address) {
EEPROM_select(); // EEPROM 선택
EEPROM_change_byte(EEPROM_READ); // 읽기 명령 전송
EEPROM_send_address(address); // 메모리 주소 전송
// 마스터에서 바이트 값을 전송하여야 슬레이브로부터 바이트 값을 받을 수 있으므로
// 의미 없는 0을 전송
EEPROM_change_byte(0);
EEPROM_unselect(); // EEPROM 선택 해제
return SPDR;
}
void EEPROM_change_byte(uint8_t data) {
SPDR = data; // 데이터 전송 시작
_delay_ms(1);
loop_until_bit_is_set(SPSR, SPIF); // 전송 완료 대기
}
void EEPROM_send_address(uint16_t address) {
// 상위 바이트를 먼저 전송하고 하위 바이트를 전송
EEPROM_change_byte((uint8_t)(address >> 8));
EEPROM_change_byte((uint8_t)address);
}
void EEPROM_write_enable(void) {
EEPROM_select(); // Slave Select를 LOW로
EEPROM_change_byte(EEPROM_WREN); // 쓰기 가능하도록 설정
EEPROM_unselect(); // Slave Select를 HIGH로
}
void EEPROM_write_byte(uint16_t address, uint8_t data) {
EEPROM_write_enable(); // 쓰기 가능 모드로 설정
EEPROM_select(); // EEPROM 선택
EEPROM_change_byte(EEPROM_WRITE); // 쓰기 명령 전송
EEPROM_send_address(address); // 주소 전송
EEPROM_change_byte(data); // 데이터 전송
EEPROM_unselect(); // EEPROM 선택 해제
// 쓰기가 완료될 때까지 대기
while (EEPROM_read_status() & _BV(EEPROM_WRITE_IN_PROGRESS));
}
uint8_t EEPROM_read_status(void) {
EEPROM_select(); // EEPROM 선택
EEPROM_change_byte(EEPROM_RDSR); // 상태 레지스터 읽기 명령 전송
EEPROM_change_byte(0); // 상태 레지스터 값 읽기
EEPROM_unselect(); // EEPROM 선택 해제
return SPDR;
}
void SPI_init(void) {
DDRL |= (1 << SPI_SS); // SS 핀을 출력으로 설정
// SS핀은 HIGH로 설정하여 EEPROM이 선택되지 않은 상태로 시작
PORTL |= (1 << SPI_SS);
DDRB |= (1 << SPI_MOSI); // MOSI 핀을 출력으로 설정
DDRB &= ~(1 << SPI_MISO); // MISO 핀을 입력으로 설정
DDRB |= (1 << SPI_SCK); // SCK 핀을 출력으로 설정
SPCR |= (1 << MSTR); // 마스터 모드
SPCR |= (1 << SPE); // SPI 활성화
}
EEPROM_25LC640.h
#ifndef _EEPROM_25LC640_
#define _EEPROM_25LC640_
#include <avr/io.h>
#include <util/delay.h>
#define EEPROM_READ 0b00000011 // 읽기
#define EEPROM_WRITE 0b00000010 // 쓰기
#define EEPROM_WREN 0b00000110 // 쓰기 허용
#define EEPROM_RDSR 0b00000101 // 상태 레지스터 읽기
#define EEPROM_WRITE_IN_PROGRESS 0 // 쓰기 진행 중 비트 번호
#define SPI_SS PL7
#define SPI_SCK PB1
#define SPI_MOSI PB2
#define SPI_MISO PB3
#define EEPROM_select() PORTL &= ~(1 << SPI_SS) // LOW
#define EEPROM_unselect() PORTL |= (1 << SPI_SS) // HIGH
uint8_t EEPROM_read_byte(uint16_t address);
void EEPROM_change_byte(uint8_t data);
void EEPROM_send_address(uint16_t address);
void EEPROM_write_enable(void);
void EEPROM_write_byte(uint16_t address, uint8_t data);
uint8_t EEPROM_read_status(void);
void SPI_init(void);
#endif
main.cpp
#define F_CPU 16000000L
#include <avr/io.h>
#include <util/delay.h>
#include "UART0.h"
#include "EEPROM_25LC640.h"
int main(void) {
SPI_init();
_delay_ms(1);
UART0_init();
DDRB |= 0x01;
PORTB |= 0x01;
for(int i = 0; i < 128; i++){
EEPROM_write_byte(i, i);
SPI_init();
_delay_ms(1);
}
for(int i = 0; i < 128; i++){
UART0_print("Address: ");
UART0_print(i);
UART0_print("\t: ");
UART0_print(EEPROM_read_byte(i));
UART0_write('\r');
UART0_write('\n');
}
while(1){
SPI_init();
}
return 0;
}
UART0.cpp
/*
* UART0.cpp
*
* Created: 2021-08-31 오후 1:59:55
* Author: 201721358
*/
#include "UART0.h"
//#define _BV(bit) (1<<(bit))
void UART0_init(void){
UBRR0H = 0x00;
UBRR0L = 207;
UCSR0A |= _BV(U2X0);
UCSR0C |= 0x06;
UCSR0B |= _BV(RXEN0);
UCSR0B |= _BV(TXEN0);
}
void UART0_write(uint8_t data){
while ( !(UCSR0A & (1 << UDRE0)) );
UDR0 = data;
}
uint8_t UART0_read(void){
while ( !(UCSR0A & (1 << RXC0)) );
return UDR0;
}
void UART0_print(char *str) {
while(*str)
UART0_write(*str++);
}
void UART0_print(int no, int radix /*= 10*/) {
char buffer[sizeof(int) * 8 + 1];
itoa(no, buffer, radix);
UART0_print(buffer);
}
void UART0_print(long no, int radix /*= 10*/) {
char buffer[sizeof(long) * 8 + 1];
ltoa(no, buffer, radix);
UART0_print(buffer);
}
UART0.h
/*
* UART0.h
*
* Created: 2021-08-31 오후 1:57:05
* Author: 201721358
*/
#ifndef _UART0_LIBRARY_
#define _UART0_LIBRARY_
#include <avr/io.h>
#include <stdlib.h>
void UART0_init();
uint8_t UART0_read();
void UART0_write(uint8_t data);
void UART0_print(char *str);
void UART0_print(int no, int radix = 10);
void UART0_print(long no, int radix = 10);
#endif
빌드가 정상적으로 된 모습입니다.
정상 작동을 하게 되면
address 주소가 Putty에 계속 뜨게 됩니다.
참고 : 따라 하면서 배우는 마이크로컨트롤러 - 한빛아카데미
'Embedded system > Microcontroller' 카테고리의 다른 글
ATmega2560의 ADC (2) (0) | 2021.10.26 |
---|---|
ATmega2560의 ADC (1) (0) | 2021.10.26 |
SPI(Serial Peripheral Interface) (0) | 2021.09.16 |
댓글