Arduino 이론

김준혁·2026년 3월 26일

레지스터: 명령어, 연산, 계산등을 임시 저장
각 레지스터는 주소를 가지지만, 헤더파일등 정의된 이름을 사용하여 c언어 변수처럼 사용가능하다.

Arduino vs. Atmega(AVR)
아두이노: 라이브러리 중심
AVR: 레지스터 중심

UART 통신 해보기

#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가 되는 순간을 검사하게 된다.

profile
임베디드 개발자

0개의 댓글