레지스터: 명령어, 연산, 계산등을 임시 저장
각 레지스터는 주소를 가지지만, 헤더파일등 정의된 이름을 사용하여 c언어 변수처럼 사용가능하다.
Arduino vs. Atmega(AVR)
아두이노: 라이브러리 중심
AVR: 레지스터 중심
#define F_CPU 16000000L // CPU 클럭 속도 설정 (16MHz)
#define BIT_DELAY_US 104 // 9600bps 기준 한 비트가 유지되는 시간 (1초/9600 = 약 104us)
#define MY_RX PD2 // 소프트웨어 수신(RX) 핀을 디지털 2번(PORTD 2)으로 지정
#define MY_TX PD3 // 소프트웨어 송신(TX) 핀을 디지털 3번(PORTD 3)으로 지정
#include <avr/io.h> // AVR 입출력 레지스터 정의 헤더
#include <util/delay.h> // _delay_us, _delay_ms 함수 사용 헤더
#include <stdio.h> // printf, scanf 등 표준 입출력 함수 사용 헤더
// 함수 원형 선언
void UART0_init(void);
int UART0_transmit(char data, FILE *stream);
int UART0_receive(FILE *stream);
// printf 출력을 위한 스트림 설정 (출력 방향을 UART0_transmit 함수로 연결)
FILE OUTPUT = FDEV_SETUP_STREAM(UART0_transmit, NULL, _FDEV_SETUP_WRITE);
// scanf 입력을 위한 스트림 설정 (입력 방향을 UART0_receive 함수로 연결)
FILE INPUT = FDEV_SETUP_STREAM(NULL, UART0_receive, _FDEV_SETUP_READ);
// 하드웨어 및 소프트웨어 포트 초기화 설정
void UART0_init(void)
{
// --- 하드웨어 UART0 설정 (PC 통신용) ---
UBRR0H = 0x00;
UBRR0L = 207; // 16MHz, 2배속 모드에서 9600bps 맞추는 값
UCSR0A |= _BV(U2X0); // 2배속 모드 활성화 (오차 감소)
UCSR0C |= 0x06; // 데이터 8비트, 정지 1비트 설정 (비동기)
UCSR0B |= _BV(RXEN0) | _BV(TXEN0); // 하드웨어 송수신 회로 켜기
// --- 소프트웨어 UART 설정 (상대 보드 통신용) ---
DDRD &= ~(1 << MY_RX); // PD2(2번 핀)를 입력으로 설정 (상대방 데이터 받기)
DDRD |= (1 << MY_TX); // PD3(3번 핀)를 출력으로 설정 (상대방에게 데이터 보내기)
PORTD |= (1 << MY_TX); // UART 통신 대기 상태는 항상 High(5V) 유지
}
// PC(시리얼 모니터)로 한 글자 보내는 함수 (printf 내부 동작)
int UART0_transmit(char data, FILE *stream)
{
if (data == '\n') UART0_transmit('\r', stream); // 줄바꿈(\n) 시 커서를 맨 앞으로(\r)
while( !(UCSR0A & (1 << UDRE0)) ); // 하드웨어 송신 버퍼가 빌 때까지 대기
UDR0 = data; // 버퍼에 데이터 전달 (자동 전송)
return 0;
}
// PC(시리얼 모니터)로부터 한 글자 받는 함수 (scanf 내부 동작)
int UART0_receive(FILE *stream)
{
while( !(UCSR0A & (1 << RXC0)) ); // 데이터가 완전히 들어올 때까지 대기
return UDR0; // 수신 버퍼에서 데이터 읽어서 반환
}
// 상대방 보드(3번 핀)로 데이터 전송 (직접 신호 만들기)
void my_uart_tx(char data){
// 1. Start Bit: 신호선을 Low(0V)로 떨어뜨려 전송 시작을 알림
PORTD &= ~(1 << MY_TX);
_delay_us(BIT_DELAY_US);
// 2. Data Bits: 8비트 데이터를 한 비트씩 전송 (낮은 자리수부터)
for (int i = 0; i < 8; i++) {
if (data & (1 << i))
PORTD |= (1 << MY_TX); // 비트가 1이면 High
else
PORTD &= ~(1 << MY_TX); // 비트가 0이면 Low
_delay_us(BIT_DELAY_US); // 9600bps 속도에 맞춰 대기
}
// 3. Stop Bit: 신호선을 다시 High로 올려 전송 종료를 알림
PORTD |= (1 << MY_TX);
_delay_us(500); // 다음 전송 전까지 충분한 안정 시간 확보
}
// 상대방 보드(2번 핀)로부터 데이터 수신 (직접 신호 읽기)
char my_uart_rx()
{
char data = 0;
// 데이터 비트의 중앙 지점에서 값을 읽기 위해 0.5비트 시간만큼 대기 (샘플링 정확도 향상)
_delay_us(BIT_DELAY_US/2);
for (int i = 0; i < 8; i++) {
_delay_us(BIT_DELAY_US); // 다음 비트 위치로 이동
if (PIND & (1 << MY_RX)) // 현재 핀 상태가 High(1)인지 확인
data |= (1 << i); // 비트 저장
}
// 정지 비트가 끝날 때까지 대기하여 수신 완료
_delay_us(BIT_DELAY_US);
return data;
}
int main(void)
{
UART0_init(); // 초기화 실행
stdout = &OUTPUT; // printf가 사용할 통로 연결
stdin = &INPUT; // scanf가 사용할 통로 연결
char c, c_in;
while (1)
{
// --- 1. [상대 보드 -> 내 PC] 데이터 전달 ---
// 2번 핀(RX)이 Low가 되면 상대방이 데이터를 보내기 시작한 것 (Start Bit 감지)
if (!(PIND & (1 << MY_RX))) {
_delay_us(BIT_DELAY_US); // Start Bit가 지나가길 기다림
c = my_uart_rx(); // 소프트웨어 방식으로 데이터 읽기
printf("%c", c); // 읽은 데이터를 PC 시리얼 모니터에 출력
}
// --- 2. [내 PC -> 상대 보드] 데이터 전달 ---
// 하드웨어 UART로 PC에서 데이터가 들어왔는지 확인
if (UCSR0A & (1 << RXC0)) {
scanf("%c", &c_in); // PC에서 친 글자 읽기
my_uart_tx(c_in); // 소프트웨어 방식으로 상대 보드에 전송
}
}
return 0;
}
기존 코드는 pc1과 아두이노1, pc2와 아두이노2 각각 통신하는 걸 2,3번 핀으로 설정하였고, 아두이노1과 2를 연결하는 코드가 빠졌다.
따라서, 위의 코드는 2번 핀이 Start Bit가 되는 순간을 검사하게 된다.