Post

레고로 RC카 만들기 1

레고로 RC카 만들기 1

모터들을 제어하기 전에 가장 먼저 LED 제어를 해보려고한다.
Arduino(ATmega328p) 레지스터로 LED Blink 와 PWM 제어를 해볼 것이다.

사용 부품 및 회로 구성

  • Arduino UNO
  • LED 파란색
  • 320Ω 저항

Image

일단 코드작성하기 (led_blink.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>

int main()
{
	DDRB |= (1 << PB1); // pin write initialize
	while (1)
	{
	    PORTB ^= (1 << PB1); // PB1 pin write
        _delay_ms(500) // delay 500ms
	}
}

일단 실행하기

  1. avr-gcc를 이용해서 작성한 코드를 빌드를 한다.
  2. 아두이노 우노로 전송하기 위한 HEX 파일 avr-objcopy를 이용해서 만들어준다.
  3. avrdude로 컴퓨터와 연결한 아두이노에 업로드 해줄 것이다.
    • 자신의 환경에 맞게 avrdude.conf 파일의 위치와
    • 아두이노가 연결된 포트를 작성해줘야한다.

위 과정을 makefile로 만들어 보았다.

Makefile
       TARGET = led_blink
       ELF = $(TARGET).elf
       HEX = $(TARGET).hex

        CC = avr-gcc
        CFLAG = -g -mmcu=atmega328p -Os

        OBJCOPY = avr-objcopy
        OBJCOPY_FLAG = -I elf32-avr -O ihex

        AVRDUDE = avrdude
        AVRDUDE_CONF = /opt/homebrew/etc/avrdude.conf
        MCU = atmega328p
        PROGRAMMER = arduino
        BAUD = 115200
        PORT := $(shell ls /dev/cu.usbmodem* 2>/dev/null | head -n 1)

        SRCS = main.c

        all:
            $(CC) $(CFLAG) $(SRCS) -o $(ELF)

        upload: $(HEX)
            @if [ -z "$(PORT)" ]; then \
                echo "⚠️  Arduino 포트를 찾을 수 없습니다!\n"\
                echo "USB 연결을 확인하세요."; \
                exit 1; \
            fi
            $(AVRDUDE) -C $(AVRDUDE_CONF) -v -c$(PROGRAMMER) -p$(MCU) -P$(PORT) -b$(BAUD) -Uflash:w:$(HEX):i


        $(HEX): $(ELF)
            $(OBJCOPY) $(OBJCOPY_FLAG) $(ELF) $(HEX)

        clean:
            rm -f $(ELF) $(HEX)
    
1
2
3
4
5
avr-gcc -g -mmcu=atmega328p -Os led_blink.c -o led_blink.elf

avr-objcopy -I elf32-avr -O ihex led_blink.elf led_blink.hex

avrdude -C YOUR_AVRDUDE_CONF -v -carduino -patmega328p -P YOUR_ARDUINO_CONNECT_PORT -b115200 -Uflash:w:led_blink.hex:i

이제 대충 이해 시작해보기

사실 코드 작성전에 atmeag328p의 데이터시트를 확인해보고 내가 사용하는 MCU에 대한 이해가 필요하다. 데이터시트에는 MCU가 어떻게 구성되어 있는지, 레지스터들을 어떻게 제어하면 될지 등에 대해 자세하게 기술되어있다.

내가 구성한 회로에서는 PORT B를 이용했기 때문에 해당하는 레지스터들을 조작해야한다.
C 파일 작성되어 있는 DDRB, PORTB, PB1 모두 데이터시트에 모든 정보가 자세하게 기술되어 있다.

  • DDRB - The Port B Data Direction Register
    • 이 레지스터는 해당 핀을 입력 또는 출력으로 설정하는 레지스터이다.
    • 우리는 출력을 하고 싶은 상태이기 때문에 pin번호 PB1에 해당하는 부분을 1로 변경해줄 것이다.
    • 해당 부분 코드 (line 8) : DDRB |= (1 << PB1);
  • PORTB - The Port B Data Register
    • 이 레지스터는 입력/출력값을 설정하는 레지스터이다.
    • 값은 출력 모드 : 1 - HIGH, 0 - LOW,
      입력 모드 : 1 - 내부 풀업 활성화, 0 - Tri-state이다.
    • LED를 on/off를 하기 위해서 우리가 원하는 레지스터 부분 0, 1로 계속 변경해줘야한다.
    • 계속해서 변경해주기 위해 위 코드에서는 xor 연산을 통해 해당 pin 부분을 on/off 한다.
    • 해당 부분 코드 (line 11) : PORTB ^= (1 << PB1);

LED PWM

DC 모터도 PWM 제어를 이용하기 때문에 모터를 바로 제어하기 전에 PWM을 이용해 LED 밝기 조절을 먼저 해보려고 한다.

이제 일단 코드를 작성해기 (led_pwm.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
#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRD |= (1 << PD5);
    TCCR0A |= (1 << WGM00) | (1 << WGM01);
    TCCR0A |= (1 << COM0B1);
    TCCR0B |= (1 << CS00) | (1 << CS00);

    while (1)
    {
        for (uint8_t brightness = 0; brigthness < 255; brightness++)
        {
            OCR0B = brightness;
            _delay_ms(10);
        }
        for (uint8_t brightness = 254; brightness >= 0; brightness--)
        {
            OCR0B = brightness;
            _delay_ms(10);
        }
    }

    return 0;
}

일단 실행해 보기

위 led_blink 예제 실행하는 방법 처럼하는데 뒤에 오는 파일 명들만 바꿔서 실행하면된다.

1
2
3
4
5
avr-gcc -g -mmcu=atmega328p -Os led_pwm.c -o led_pwm.elf

avr-objcopy -I elf32-avr -O ihex led_pwm.elf led_pwm.hex

avrdude -C YOUR_AVRDUDE_CONF -v -carduino -patmega328p -P YOUR_ARDUINO_CONNECT_PORT -b115200 -Uflash:w:led_pwm.hex:i

이제 대충 이해 시작해보기

그럼 먼저 PWM이 무엇인가?

PWM은 Pulse-width modulation의 약자로 펄스의 폭을 변조하는 것이다. 디지털 신호를 사용하여 아날로그 출력을 제어에 사용되는 기술이다. 특정 주파수에서 빠르게 펄스를 LOW, HIGH로 변경하면서 평균 전압을 조절하여 전력 전달량을 조절할 수 있다.
이때, PWM의 주기 중 HIGH인 상태의 시간 비율(백분율) 을 Duty Cycle이라고 한다.

example

  • Duty Cycle (0%) => 항상 LOW (출력 없음)
  • Duty Cycle (50%) => HIGH와 LOW의 비율이 각각 절반이 반복 (출력이 절반만 됨)
  • Duty Cycle (100%) => 항상 HIGH (계속 HIGH 상태 지속)

이해해보기

Timer을 사용하면 일정 시간마다 인터럽트를 발생시킬 수 있는데 이를 이용해 PWM을 생성해 사용할 수 있다. 그래서 이것들을 이용해기 위해 Timer을 설정해줘야한다.
또한 코드의 DDRD, TCCR0A, TCCR0B 등에 대해 알아면 각각의 define들이 데이터시트에 굉장히 상세하게 명시 되어 있다.

  • DDRD : 이거는 위의 예제에서 봤던 DDRB와 같이 PORT D의 입/출력을 설정하는 레지스터이다.
    • ATmega328p에는 PWM을 제어가 가능한 Pin들이 존재하는데 우리는 PD5를 이용해서 제어를 할 것이기 때문에 이 핀의 출력을 1로 활성화 해주었다.
    • in code : DDRD |= (1 << PD5);
  • TCCR0A, TCCR0B : 타이머의 동작 및 클럭을 설정하는 레지스터이다. 각각의 설정 옵션들을 아래에 표로 그려두었다.
    • TCCR0A : Timer/Counter0의 동작 모드를 설정하는 레지스터
    • TCCR0B : Timer의 클럭을 설정하는 역할
  • WGM0x : 타이머 모드를 설정할 수 있는 옵션들이다. 나는 Fast PWM을 사용하기 위해 해당 옵션들을 이용해 활성화 시켰다.
    • code: TCCR0A |= (1 << WGM00) | (1 << WGM01);
  • COM0Bx : OC0B (D5) 핀의 PWM 출력 설정

    COM0B1 COM0B0 출력 모드
    0 0 Normal Mode (출력 없음)
    0 1 Toggle OC0B on Compare Match (CTC 모드 전용)
    1 0 Clear OC0B on Compare Match (Non-inverting PWM)
    1 1 Set OC0B on Compare Match (Inverting PWM)

    Non-invering 모드를 사용했다.

    • code : TCCR0A |= (1 << COM0B1);
  • CS0x : 클럭을 설정하기 위해 존재하는 define이다.

    CS02 CS01 CS00 Description
    0 0 0 타이머 정지 (No clock)
    0 0 1 시스템 클럭
    0 1 0 시스템 클럭 / 8
    0 1 1 시스템 클럭 / 64
    1 0 0 시스템 클럭 / 256
    1 0 1 시스템 클럭 / 1024
    1 1 0 외부 클럭 (T0 핀, Falling Edge)
    1 1 1 외부 클럭 (T0 핀, Rising Edge)

    나는 128kHz 로 설정하기 위해 시스템 클럭(8MHz) / 64를 사용했다.

    • code : TCCR0B |= (1 << CS00) | (1 << CS00);
  • OCR0B : 이 레지스터의 값을 이용해 PWM의 Duty Cycle를 설정한다.
This post is licensed under CC BY 4.0 by the author.