본문 바로가기
Embedded system/Microcontroller

ATmega2560의 EEPROM을 SPI 통신으로 데이터 읽고 쓰기

by 은빛초코 2021. 9. 16.

ATmega2560의 EEPROM을 이용하여 바이트 데이터를 읽고, 쓰는 과정을 진행해보겠습니다.

 

우선 ATmega2560에서 EEPROM의 위치와 Schematic을 확인합니다.

ATmega2560에서 사용하는 EEPROM은 마이크로칩사의 25LCn EEPROM입니다.

64Kbit의 메모리를 가지고 있습니다. 

ATmega2560의 EEPROM 위치
ATmega2560 EEPROM SCHEMATIC


1바이트 데이터를 읽는 방법

  1. EEPROM 칩 선택
  2. CS핀에 LOW 가하기
  3. 읽기에 해당하는 8비트 명령어 전송(0x03)
  4. 데이터를 읽을 EEPROM의 주소를 전송(13비트)
  5. 바이트 단위로 상위 바이트부터 2번에 나누어 전송
  6. EEPROM에서는 해당 메모리 번지의 바이트 값을 마이크로컨트롤러로 전송할 준비 완료
  7. 실제로 EEPROM에서 1바이트의 데이터를 읽기 위해서는 임의의 1바이트 값을 슬레이브로 전송
  8. 전송이 끝나면 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

댓글