ATmega328P DIY

 

Intro

아두이노 보드 중에 가장 싸게, 대중적으로 사용하는 보드라면 단연 Arduino Uno일 것이다. 근데 쓸데 없는게 많이 달려있어서 그런지, 커도 너무 크다. 단가도 비싸고…(싸구려 호환보드는 싸긴 하지만) 사실 대부분의 연산기능이 Arduino Uno의 MCU인 ATmega328p의 기능임을 생각하면 굳이 Uno를 쓰지 않고 ATmega328p만을 이용해도 된다는 결론에 도달한다.

해보자!


Design Circuit

우선 실험적으로 쓸수 있도록 브레드보드에 ATmega328P PU타입을 사용하기로 하였다.

Arduino Uno Analysis

뭐 어떻게 쓰냐는 내 마음대로 해도 상관 없다만, Uno의 구조를 빌려오는게 효율적이지 않을까 싶어서 가져왔다.

Uno-Rev3-Schematic.png

Download UNO-TH_Rev3e_sch.pdf

회로도의 우측 상단에는 Regulator(AMS1117)로 입력전압을 일정하게 유지시키고 있고, 좌측 하단에는 ATmega16U2를 이용해 USB Serial 통신을 구현하고 있다. 그리고 우측 하단에 우리가 쓸 ATmega328P PU가 있다.

Uno 정품과 호환보드(SZH-EK002) 두가지 모두 가지고 있는데 정품은 위 회로도와 동일한 부품으로 동일하게 구현했지만, 호환보드의 경우 그냥 호환되게 동작하도록 좀 더 싼 부품들로 구현한 듯 하다.

가령 ATmega328P PU 대신 AU를 사용한다던지, Serial 구현을 위해 ATmega16U2에 USB Emulation Code를 올려놨지만, 호환보드는 그냥 CH340을 때려박아 때우는식이다.(CH340 driver가 있어야한다.)

결론적으로 다른 부품으로 대체하고 구성을 좀 바꿔도 원하는대로 동작하면 그만이다.

ATmega328P PU Analysis

Microchips에서 배포하는 ATmega328P의 DataSheet: Download

링크 변경등을 대비한 Mirror: Download

위 datasheet를 앞으로 참고해야할 일이 많을 것같다. 그때그때 찾아보는 걸로 하고,

3번 Ordering Information 항목을 찾아보면 ATmega328P PU는

'28P3': 28-lead, 0.300” Wide, Plastic Dual Inline Package (PDIP)

로 명시되어 있다.

5번 Pin Configurations 항목에 각 Pin에 대한 설명이 자세하게 나와있고, 우선 5.1의 Pin-out만 간단히 보자.

28P3-PinMap.png

이제 각 핀의 이름, 번호, 위치를 알았으니, Uno Rev3 회로도를 참고해서 ATmega328P를 응용하여 브레드보드에 회로를 구현할 수 있겠다.

Part Selection

Uno-Rev3의 회로를 기반으로 사용목적과, 비용 가격에 따라 구성을 조금 바꾸고 아래와 같은 부품들을 사용하기로 했다.

  • BreadBoard: MB-102
    • full-size, single
  • PowerSupply for BreadBoard: YwRobot_MB-V2
    • USB또는 DC 입력단자로부터 안정적인 5V, 3.3V 출력을 공급하는 장치
    • (집에 있는 9V 건전지로부터 안정적으로 브레드보드에 전원공급하기에 적합)
  • ISP Programmer: MAI-ISP-STK500
    • Silicon Labs CP2102 USB to UART Bridge
    • 6핀 ISP단자를 통해 flashing 가능
  • MCU: ATmega328P-PU
    • AVR ATmega328P
    • 28-lead, Plastic Dual Inline Package (Compatible with BreadBoard)
  • Tact Button: DM626 x 2EA
    • 1EA for RESET Switch
    • 1EA for Green LED turn-on/off (External Interrupt Rising Edge)
  • Resistor: \(1k\Omega \times 2EA\), \(10k\Omega \times 2EA\)
    • \(1k\Omega\) Resistors for Red and Orange LED Resistor
    • \(10k\Omega\) Resistors for RESET Pull-up Resistor, External Interrupt pull-up resistor and Green LED Resistor
  • Ceramic Capacitor: \((22pF, 1kV) \times 2EA\), \((0.1\mu F, 35V) \times 2EA\)
    • \(100nF\) Capacitor for RESET and Interrupt Switch
    • \(22pF, 1kV\) Capacitors for Crystal
  • Crystal Oscillator: HC-49S (\(16MHz\))
    • Passive Oscillator using Crystal
    • \(16MHz\) quartz crystal
    • in order to satisfy the required high frequency precision
  • Jumper Cable
  • Battery: PP3 (9V)
  • Battery Adaptor: Snap Adaptor(SZH-BH010)
    • 2.0 x 5.5 DC Adaptor

Circuit Design w/ Fritzing

PCB회로나 브레드보드 회로를 그려내기 편리한 fritzing를 사용했다. 다른 회로 설계 툴은 비싸거나, 기능이 없거나, 심지어 ATmega328이 없다…(LibrePCB) 원래 무료로 사용가능 했으나 최근부터 Donation을 강제하고 있으며 사실상 유료 소스가 오픈되어 있기때문에 fritzing의 git repository로부터 소스를 받아 빌드하여 사용하여도 된다.

breadboard.png schematic.png

USB Serial과 ICSP1을 담당하던 ATmega16U2와 관련 회로는 전부 들어내었다.

회로도 상에는 구현하지 않았지만 5V, 3.3V 전원과 관련한 회로는 Power Supply YwRobot_MB-V2로 대체해였다.

나머지 부분은 Uno-Rev3 회로와 유사하게 구현하였다. 예제를 위해 LED 3개와 이를 위한 LED 저항도 설치하였다.

device_photo.jpg

MB-102 Full Size 필요없이 MB-400 Half Size만해도 충분했겠다. 지금 Half Size 브레드보드가 없을뿐더러 앞으로 더 많은 핀을 사용하면 더 많은 Line이 필요할 수 있으니 그냥 Full Size를 사용했다.

YwRobot_MB-V2의 설명서대로 전원을 공급하니 USB 전원 없이도 전원이 공급됨도 확인했다. 이제 하드웨어 설계는 끝났다.

위와 같이 외부 진동자를 사용한 경우, 별도의 Fusebit 설정이 있어야 정상적으로 사용가능하다.


Example Source

옛날에 잘 이해도 못하고 아무렇게나 만든 코드를 사용하였다.

/* main.c
 *
 * Created: 2019-05-25
 * Author : 강준구
 */

// load MCU Type from predefined from <avr/io.h>
#ifndef __AVR_ATmega328P__
#define __AVR_ATmega328P__
#endif

//#define _BV(bit) ( 1 << (bit) )
#define F_CPU 16000000UL                // Frequency of X-TAL in pin 9,10
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

uint8_t stateTimer, stateExt;           // State Variable for Interrupt


/*****************************Timer Interrput*****************************
 * Timer/Counter Mode is determined by WGMnx
 *
 * CTC Mode (Clear-Time-on-Compare-match Mode)
 * (WGMn3 | WGMn2 | WGMn1 | WGMn0): 0b0100 (OCRnA as top value register)
 * (WGMn3 | WGMn2 | WGMn1 | WGMn0): 0b1100 (ICRn as top value register)
 *
 * Clock Select Bit is determined by CSnx (Prescaler)
 * (CSn2 | CSn1 | CSn0): 0b000 (1, no clock source)
 * (CSn2 | CSn1 | CSn0): 0b001 (1, no prescaling)
 * (CSn2 | CSn1 | CSn0): 0b010 (8, prescaled as 1/8)
 * (CSn2 | CSn1 | CSn0): 0b011 (64, prescaled by 1/64)
 * (CSn2 | CSn1 | CSn0): 0b100 (256, prescaled by 1/256)
 * (CSn2 | CSn1 | CSn0): 0b101 (1024, prescaled by 1/1024)
 * (CSn2 | CSn1 | CSn0): 0b110 (External: Clock on Falling Edge)
 * (CSn2 | CSn1 | CSn0): 0b111 (External: Clock on Rising Edge)
 *
 * freq_OCRnA = {freq_clk_I/O} / {2 (prescaler from CSnx) (TOP := (1+OCRnA))}
 ************************************************************************/
void timer1_init(void){     // Initialization for Timer Interrupt
    TCCR1A &= ~_BV(WGM10); TCCR1A &= ~_BV(WGM11);
    TCCR1B |= _BV(WGM12); TCCR1B &= ~_BV(WGM13);
    TCCR1B &= ~_BV(CS10); TCCR1B &= ~_BV(CS11); TCCR1B |= _BV(CS12);
    OCR1A = 0xC34;

    TCNT1=0x0000;
    TIMSK1 |= _BV(OCIE1A);
    TIFR1 |= _BV(OCF1A);
}
ISR(TIMER1_COMPA_vect){                     // ISR for Timer Interrupt
    if(stateTimer) PORTD &= ~_BV(PORTD6);   // Assign 'LOW' on pin12(PD6)
    else PORTD |= _BV(PORTD6);              // Assign 'HIGH' on pin12(PD6)
    stateTimer = !stateTimer;               // Reverse the state
}

/***************************External Interrupt***************************
 * EIMSK (External Interrupt Mask Register) determines external pin.
 * ISCnx in EICRA determines Interrupt Sense Control
 *  ISC0x for INT0 / ISC1x for INT1
 *  (ISCn1 | ISCn0): 0b00 (Low Level)
 *  (ISCn1 | ISCn0): 0b01 (Any Logical Change)
 *  (ISCn1 | ISCn0): 0b10 (Falling Edge)
 *  (ISCn1 | ISCn0): 0b11 (Rising Edge)
 ***********************************************************************/
void int1_init(void){   // Initialization for External Interrupt
    EIMSK |= _BV(INT1); // pin5 is reserved for INT1
    EICRA |= _BV(ISC10) | _BV(ISC11);
}

ISR(INT1_vect){                             // ISR for External Interrupt
    if(stateExt) PORTD &= ~_BV(PORTD7);     // Assign 'LOW' on pin13(PD7)
    else PORTD |= _BV(PORTD7);              // Assign 'HIGH' on pin13(PD7)
    stateExt = !stateExt;                   // Reverse the state
}


/*********************************main*********************************/
int main(void){
    timer1_init();
    int1_init();

    // OUTPUT Mode for pin11(PD5), 12(PD6), 13(PD7)
    DDRD |= _BV(DDD5) | _BV(DDD6) | _BV(DDD7);

    // Assign 'HIGH' on pin11(PD5), 12(PD6), 13(PD7)
    PORTD |= _BV(PORTD5) | _BV(PORTD6) | _BV(PORTD7);
    _delay_ms(500);
    // Assign 'LOW' on pin11(PD5), 12(PD6), 13(PD7)
    PORTD &= ~(_BV(PORTD5) | _BV(PORTD6) | _BV(PORTD7));
    _delay_ms(500);

    sei();      // Set Global Interrupt Flag (Enable Global Interrupt)

    while (1){
        PORTD |= _BV(PORTD5);   // Assign 'HIGH' on pin11(PD5)
        _delay_ms(100);
        PORTD &= ~_BV(PORTD5);  // Assign 'LOW' on pin11(PD5)
        _delay_ms(100);
    }

    // cli();   // Clear Global Interrupt Flag (Disable Global Interrupt)
    return 1;
}

보다시피 pin11(PD5), 12(PD6), 13(PD7)에 연결된 LED들을 각기 다른 방식으로 제어한다.

pin11은 단순히 _delay_ms함수를 사용하여 LED Blink를 구현하였다.

pin12는 CTC mode를 이용해 Timer Interrupt를 구현하여 LED Blink를 구현하였다.

pin13은 Rising Edge에서 반응하는 Control External Interrupt를 구현해 버튼이 눌릴 때 LED의 상태가 바뀌도록 구현하였다.


Integrated Development Environment

앞서 구현한 하드웨어에 여러가지 방법으로 제어 소프트웨어를 업로드할 수 있다. CLI의 순수 GNU 소프트웨어들을 이용하는 방법은 후술하고, 통합 개발환경들을 먼저 소개하고자 한다.

Arduino IDE

먼저, 아두이노 IDE를 이용할 수 있다. 다만, 아두이노 IDE로 업로드하는 경우, 아두이노 부트로더가 필요하다. 부트로더 굽는 법은 ISP 장치마다 조금씩 다르며, MAI-ISP-STK500 매뉴얼에 자세하게 설명이 나와있다.

이렇게 부트로더를 굽고나면 아두이노에 업로드하듯 그대로 업로드하여 사용할 수 있다.

Microchip Studio (舊 AVR Studio, Atmel Studio)

과거 AVR Studio에서 시작하여 Atmel Studio로 리뉴얼되었었는데, 2016년 Microchips가 Atmel을 인수하면서… Microchip Studio가 되어버렸다…

Atmel Studio 시절부터 Visual Studio의 쉘을 이용하여 친숙한 UI/UX를 가진다. 무거움은 덤

Visual Studio의 쉘을 이용하는만큼 윈도우에서만 지원하고 있기 때문에 필자는 잘 사용하지 않는다. 하지만, 제조사 공식 프로그램이고, 강력하기때문에 가끔 사용한다. (어쩌다 ATmega328P의 Device Signature가 엉뚱하게 인식된 적이 있는데 본인도 뭘 했길래 날아갔나 모른다 avrdude에서는 인식 못하던걸 microchip studio에서는 잘 잡아줬다. avrdude로도 해결할 수 있는 방법이 있을텐데 그건 나중에 알아보는 걸로…)

AVRStudio1.jpg

Visual Studio랑 진짜 비슷하게 생겼다. 어쨌든 프로젝트 내에 소스코드를 잘 넣어준 후, 솔루션이나 프로젝트를 빌드해주자. 그럼 프로젝트 내 Debug 폴더 내에

$(TARGET).eep
$(TARGET).elf
$(TARGET).hex
$(TARGET).lss
$(TARGET).map
$(TARGET).srec

등등 다양한 파일들이 출력된다. 각각의 파일들이 뭔지는 나중에 알아보고, 출력된 파일들을 업로드 할 방법을 알아보자.

상단의 ‘Tools’ 탭으로부터 ‘Add target’ 항목을 찾아보자.

AVRStudio2.jpg

사용중인 ISP Programmer를 선택 후 해당 ISP Programmer가 연결된 Serial COM 포트를 선택해준다. 이제 Microchip Studio가 Targer을 잘 인식할테니 ‘Tools’ 탭의 ‘Device Programming’ 항목으로 가보자.

AVRStudio3.jpg

상단의 Tool, Device, Interface를 적절히 선택 후 Apply를 눌러준다. Device Signature도 Read를 눌러주면 잘 읽어올 것이다. 다른 메뉴들을 살펴보면 알겠지만, bitrate설정, 발진기 보정, fuse/lockbits 설정 등이 가능하다.

이것들도 나중에 살펴보기로 하고, ‘Memories’ 메뉴로 가서 업로드해보자.

AVRStudio4.jpg

‘Memories’ 메뉴에 진입하면, Device의 Flash영역과 EEPROM영역에 쓰기를 할 수 있다. ‘Read’는 Device로부터 읽어오고, ‘Program’은 선택된 파일을 Device의 해당 영역에 쓴다.

앞서 언급했던 Debug 폴더 아래의 $(TARGET).hex 파일을 선택하고,

  • Erase device before programming
  • Verify Flash after programming

위 항목들을 활성화 후 ‘Program’ 버튼을 눌러 업로드하면 잘 업로드 될거다. 아마


GNU Environment: gcc-avr, avr-libc w/ avrdude

윈도우에서는 WinAVR을 사용 할수도 있으나. 여기서는 관심 없으니 다루진 않겠고, 대신 Linux에서 사용하는 경우를 보자. Ubuntu 기준.

Installation

$ sudo apt install binutils-avr     # Binary Utilities for AVR
$ sudo apt install gcc-avr          # GCC Compiler for AVR
$ sudo apt install avr-libc         # Standard C libracy for AVR-GCC
$ sudo apt install avrdude          # AVR Downloader/Uploader
$ sudo apt install libc6-dev-i386   # (only for amd64)

먼저 위와같이 관련 유틸부터 설치하고, 이제 ISP Programmer와 연결하자.

Serial Port Setup

$ lsusb

$ dmesg | grep tty

$ sudo chmod 666 /dev/ttyUSB0

연결 후, lsusb명령어롤 통해 USB연결 목록을 확인할 수 있다. MAI-ISP-STK500이라면,

Silicon Labs CP210x UART Bridge

라고 나타날 것이다. 이제 dmesg | grep tty 명령어를 통해 연결된 시리얼포트를 확인해보자.

cp210x converter now attached to ttyUSB0

tty 이력이 주르륵 나올 것이다. 위와 같이 나온다면, Port는 ‘/dev/ttyUSB0’이다. 이제 이 포트에 적절한 권한을 주어야 한다.

$ sudo chmod 666 /dev/ttyUSB0

을 통해 해당 포트에 rw-rw-rw- 권한을 주어야 avrdude에서 해당 포트로 잘 업로드 할수 있다.

Build

그 다음, 프로젝트 디렉토리에서 아래와 같이 실행시켰다.

$ avr-gcc -w -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o main.o main.c
$ avr-gcc -w -mmcu=atmega328p -o main.elf main.o
$ avr-objcopy -O ihex -R .eeprom main.elf main.hex

Upload

이제 프로젝트 디렉토리에 object파일, elf파일과 hex코드로 옮겨진 파일이 있을 것이다.

루트권한으로 avrdude를 통해 hex파일을 target의 flash에 업로드해주자.

$ sudo avrdude -c stk500v2 -p m328p -P /dev/ttyUSB0 -U flash:w:main.hex:i

잘 될거다. 아마

avrdude는 단순 업로드 뿐 아니라 fuse, lockbits 설정도 있기때문에 시간내어 매뉴얼을 읽어볼 만하다.

Fuse Setup for External Oscillator

이걸 또 한참 헤맸다. 지금와서 보면 당연한건데 잘 찾아보지 않고 대충 넘겼다가 엉뚱한 곳에서 한참 고생했다. 이래서 데이터 시트를 열심히 읽어야한다. 언제 다 읽어

참고한 글: Just4Fun님 (앞으로도 자주 참고할 것 같다.)

AVR Fuse Calculator

Just4Fun님의 블로그는 앞으로도 자주 참고할 것 같다. 원래는 시간날 때 데이터시트를 정독할 생각이었지만, 계획 했던바가 이 블로그에 거의 그대로 있어 사실 이 블로그만 참고해도 될 것 같다.

시스템 클럭에 관해서는 나중에 좀 더 자세히 설명하기로하고, 어쨌든 진동자에 대한 fuse 설정을 알아보자.

ATmega328P는 Internal 8MHz RC Oscillator가 내장되어 있으며, 공장 출하 시 이 내부 발진회로를 시스템클럭 소스로 사용하도록 기본값으로 설정되어있다. 하지만 우리는 (RC 발진회로의 단점들이 있기 때문에) 외부 16MHz 결정진동자를 사용하였고, fuse bit 설정을 이에 맞게 적절히 바꾸어야 정상적으로 사용가능하다.

필자는 덕분에 진귀한 경험을 했다. 어찌보면 당연하지만, ISP Programmer가 ATmega328P의 Device Signature를 제대로 읽어오지 못했다. (이것도 avrdude의 버전마다 다른게, WinAVR에서 avrdude v5.x는 0x000000으로 Invalid Signature를 돌려줬는데, apt로 설치한 avrdude v6.x는 0xC0xxxx와 같이 그럴싸한 signature를 계속 읽어오는데 값이 읽을때마다 바뀌었다.)

어쨌든 Oscillator에 대한 설정이 잘못되었으니, 당연히 정상적으로 통신이 불가능했을 것이다. (Microchip Studio에서는 이 Fuse bit 설정을 무시하고 제대로 읽어오고 업로드가 가능했는데, 아마 Atmel이 설계시 fuse설정을 무시하고 자동으로 인식해서 읽어오는 수단을 만들어 둔 듯 하다. 하지만 이는 공개되어 있지 않다.)

필자는 lfuse: 0xE7, hfuse: 0xD9, efuse: 0xFF로 사용하고자 한다. 만약 현재 m328p가 공장출하 상태라면, 8MHz 내부 발진회로를 사용중이니, 16MHz X-TAL을 제거한 후 프로그래머나 타겟, 포트 등을 잘 설정하여 아래와 같이 명령어를 입력한다.

avrdude -c stk500v2 -p atmega328p -P /dev/ttyUSB0 -U lfuse:w:0xE7:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m

다시 16MHz X-TAL 설치 후 hex 파일이 flash에 잘 업로드 되는지 다시 시도해보자.


Build & Upload Automation

빌드 및 업로드 도구도 사실 개발환경에 의존적이다보니 앞서 개발 환경 구성에서 다 설명하였다. 하지만 Arduino IDE나 Microchip Studio는 쉽게 할 수 있어서 상관 없지만 avr-gcc나 avrdude는 CLI환경이고 매번 복잡한 명령어를 입력할수는 없으니 자동화시켜줘야겠다.

오늘도 열일하는 Makefile과 VS Code.

Makefile

MCU=atmega328p
F_CPU=16000000UL
PROGRAMMER=stk500v2
PORT=/dev/ttyUSB0
BAUDRATE=115200

CC = avr-gcc
OBJCPY = avr-objcopy
AVRDUDE = avrdude
SRCS = $(notdir $(wildcard *.c))
OBJS = $(SRCS:.c=.o)
TARGET=main
RM=rm -rf
 
$(TARGET).hex: $(TARGET).elf
	$(OBJCPY) -O ihex -R .eeprom $^ $@

$(TARGET).elf: ${OBJS}
	$(CC) -w -mmcu=$(MCU) -o $@ $^

%.o: %.c
	$(CC) -w -Os -DF_CPU=$(F_CPU) -mmcu=$(MCU) -c -o $@ $^

flash:
	$(AVRDUDE) -c $(PROGRAMMER) -p $(MCU) -P $(PORT) -b $(BAUDRATE) -U flash:w:$(TARGET).hex:i

clean:
	$(RM) *.o *.elf *.hex

여느때처럼, 상황에 따라 바뀔수 있는 내용들(MCU, Programmer, Port, Baudrate 등)은 변수로 빼놓고, 이를 활용해 적절한 옵션을 주었다.

또, c source → object → elf → hex 순으로 컴파일, 링크 되도록 종속관계를 잘 설정해놓았으며, flash upload는 따로 분리해두었다.

make로 빌드 후 make flash로 hex 파일을 flash에 업로드 하고, make clean으로 소스를 제외한 중간과정 및 결과물을 제거할 수 있다.

$ make
avr-gcc -w -Os -DF_CPU=16000000UL -mmcu=atmega328p -c -o main.o main.c
avr-gcc -w -mmcu=atmega328p -o main.elf main.o
avr-objcopy -O ihex -R .eeprom main.elf main.hex
$ make flash
sudo avrdude -c stk500v2 -p atmega328p -P /dev/ttyUSB0 -b 115200 -U flash:w:main.hex:i

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (464 bytes):

Writing | ################################################## | 100% 0.12s

avrdude: 464 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 464 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.10s

avrdude: verifying ...
avrdude: 464 bytes of flash verified

avrdude: safemode: Fuses OK (E:FD, H:DE, L:FF)

avrdude done.  Thank you.

$

잘 된다. ㅎㅎ

VS Code

여기서 끝내도 되겠지만, VS Code Task와 이어줘야 제맛. task는 못 참지.

프로젝트 디렉토리 아래에 .vscode 폴더를 만들고, tasks.json을 만들자. 아니면 그냥 vscode로 해당 폴더를 연 후, Ctrl+Shift+P로 Command Palette를 열어 ‘Configure Tasks’를 눌러도 된다.

{
    "version": "2.0.0",
    "runner": "terminal",
    "type": "shell",
    "echoCommand": true,
    "presentation": { "reveal": "always" },
    "tasks": [
        {
            "label": "Build",
            "type": "shell",
            "command": "make",
            "args":[],
            "options":{},
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ]
        },
        {
            "label": "flash",
            "type": "shell",
            "command": "make flash",
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ]
        },
        {
            "label": "make clean",
            "type": "shell",
            "command": "make clean",
            "group": "none",
            "problemMatcher": [
                "$gcc"
            ]
        }
    ]
}

보면 알다시피, VS Code Build Automation에서 조금 수정했다.


Outro

거의 다 된 듯싶다.

아직 다뤄야 할 내용들은 많지만 ATmega328P로 여러가지를 할 준비를 거진 마친 듯하다. 여기까지만 쓰고, 추후 추가할 내용들은 추가하고자 한다.

This work is licensed under Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
(The excerpted works are exceptionally subject to a licence from its source.) Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)