[EETB] #4 임베디드 시스템을 사용한 C언어 프로그래밍

문연수·2022년 9월 7일
0

EETB

목록 보기
4/9
post-thumbnail

1. 아두이노(Arduino) 란?

 아두이노(Arduino)는 이탈리아에서 개발된 임베디드 시스템이다. 작은 마이크로컴퓨터를 탑재한 보드와 프로그래밍 언어나 프로그램을 개발하기 위한 소프트웨어 환경을 포함해 아두이노라고 부른다. 아두이노의 CPU 는 Atmel 사(미국)의 AVR ATmega328P라는 8비트 마이크로컴퓨터다 (Atmel 사는 2016년 04월에 Microchip Technology 사에 합병되었다).

- 아두이노 우노 R3 (Arduino UNO R3) 의 하드웨어 구성

 필자가 구매한 Arduino UNO R3 호환보드이다. 급하게 구입해야 해서 쿠팡에서 다음의 키트를 샀다. 실제 정품 보드에 들어가는 Chip 과는 조금 다르다. 그래도 책에 나온 내용을 실습하는데 문제는 없다.

 필자가 돈을 받고 광고하는게 아니라 진짜 그냥 필자가 구입한 해당 제품이 실습에 큰 무리 없이 사용 가능하다는 사실을 말하기 위해 작성한 것이다. 꼭 위 링크의 제품이 아니더라도 상관없다. 더 자세한 사항은 이하의 비교를 참고하길 바란다.

* 책에선 소개되지 않은 호환보드와 정품보드의 비교

Reference: Arduino Uno R3 Datasheet

 위 Schematic 은 실제 Arduino UNO R3Board Topology 이다. 필자가 구매한 저가형 호환 보드(저가형이 나쁘다는 것은 아니다, 스펙 비교는 다음을 참고)와는 차이가 있다. 전체적인 구성은 비슷하나 U3 부분의 AtMEGA16U2 Module 이 빠지고, 반대로 CH340G 라는 모듈이 붙어서 나온다.

 또한 메인 프로세서인 Atmega328p 의 마지막 일련(ATmega328P-U-TH, ATmega328P-PU)이 조금 다르다. 세부적인 동작의 차이는 데이터싯을 더 봐야 알겠지만 여기에서는 생략한다. (필자도 직접 비교한건 아니라서 잘 모르겠고 큰 차이는 패키징 타입이 좀 다르다, 호환보드는 TQNF 고 정품보드는 DIP)

Reference: https://www.terraelectronica.ru/pdf/show?pdf_file=%2Fz%2FDatasheet%2FU%2FUNO_R3%28CH340G%29.pdf

 두 모듈이 Arduino UNO R3 보드 내에서 수행하는 동작(16U2, CH340G) 자체는 동일하다. 다만 16U2Microcontroller, CH340GIC 이다. 따라서 16U2Standalone 으로 사용 가능하지만 CH340G 는 그렇지 않다.

 또한 CH340G 는 그 자체만으로 사용이 불가능하고 드라이버를 추가적으로 다운로드받아 설치해야 한다. 중국어로 된 사이트가 나와서 놀랄 수도 있겠지만, CH340G 모듈은 중국에서 만들어졌고 위 사이트는 CH340G 모듈 제조사의 공식 CH340G Driver Download Site 이다.

 필자는 두 개를 다 구매했는데 정품과 호환 보드 사이에 가격 차이가 있다. 22년 09월 06일 기준, 쿠팡에서 구매한 호환 보드는 키트로 합쳐서 25,700 원으로 이번 챕터에서 나오는 모든 과정을 전부 다 진행 가능한 하나의 솔루션 킷인 반면, 디바이스 마트에서 구매한 정품 보드만 37,900 원이다.

 필자의 아주 아주 지극히 개인적인 생각을 얘기하자면, 중국산 제품에 대한 불안함, 드라이버 프로그램에 대한 불신만 없다면, CH340G 모듈이 붙은 저가형 호환보드가 가격 측면에서도 저렴하고 업로드 시간도 큰 차이가 없다고 하므로 (위 스펙 비교 참고) 구매에 있어 충분히 고려해볼만 하다고 생각한다. (특히 학생 입장에서는 더더욱)

 그러나 호환보드는 당연히 이탈리아에서 제조한 정품 아두이노가 아니며 그로인해 발생하는 문제는 구매자의 책임 이라는 점에 주목하길 바란다.

* 하드웨어 구성

 필자가 구매한 정품 보드는 아직 도착하지 않아 호환 보드를 사용했다. 처음 올린 삽화와 위 보드 구성 삽화와는 약간 다르지만 크게 다음과 같다:

  1. 커넥터(X2): 커넥터는 마이크로컴퓨터로부터의 제어 신호를 외부로 출력하거나 외부로부터의 입력을 받기 위해 사용되는 부품이다. 마이크로컴퓨터로부터의 출력이나 마이크로컴퓨터로부터의 입력을 하기 위한 단자를 외부로부터 제어할 수 있도록 GPIOPWM (Pulse Width Modulation) (책에서는 Pluse 라고 나와있는데 오탈자이다.) 등의 제어 신호를 내고 있다.

  2. 각종 LED(Light Emitting Diode): LED 는 각종 동작을 확인할 수 있는 발광 다이오드다.
    프로그램 동작을 확인할 수 잇는 LED나 USB 통신의 동작을 확인할 수 있는 LED, 전원이 ON 상태인 것을 확인할 수 있는 LED 등 여러 종류가 배치되어 있다.

  3. 리셋 스위치(RESET): 마이크로컴퓨터를 리셋할 수 있는 버튼 스위치다. 누르면 마이크로컴퓨터를 처음부터 동작시킬 수 있다.

  4. 전원 커넥터(X1): 직류 전원을 연결하기 위한 커넥터다. 전용 부품을 사용함으로써 전지 등으로 동작시킬 수 있도록 되어 있다.

  5. 레귤레이터(U1): 전원 커넥터로부터 입력되는 전압을 5V 로 변환하는 하드웨어 부품이다.

  6. 마이크로컴퓨터(J-ZU4): 마이크로컴퓨터는 CPU와 메모리(ROM, RAM), 주변장치가 함께 들어 있는 하드웨어다. 마이크로컴퓨터에 맞춘 크로스 개발환경을 구축함으로써 마이크로컴퓨터에서 소프트웨어를 동작시킬 수 있다.

- 마이크로컴퓨터의 데이터시트 조사하기

Atmega328p 의 데이터시트는 Microchip 홈페이지에서 확인이 가능하다:

 정품보드 마이크로프로세서의 실크 인쇄는 ATmega328P-PU 이며, 호환보드는 ATmega328P U-TH 이다. 이상하게 U-TH 에 해당하는 Ordering Code 가 없는데 위 정보를 확인하는 이유는 패키지 타입이므로 일단 스킵하려 한다.

 아래에 패키지 타입에 따른 명칭 설명이 나오는데 호환보드는 Lead 가 있는 TQFP 이므로 아마 32A 계열 중 하나일 것이다. 호환보드 중에서 정확하게 데이터시트가 나온게 없어서 뭔지 확인할 방법이 없다...

 호환보드는 좌측, 정품은 우측을 확인하면 된다. 밑에 그 밖의 패키징 타입에 대한 설명이 있긴 하지만 Lead 타입의 32 pin 은 좌상단의 32 TQFP Top View 하나이다.

Arduino UNO Rev 3Schematic공식 홈페이지에서 확인이 가능하다. 이를 통해 마이크로컨트롤러와 보드의 핀 구성을 확인할 수 있다.

 호환보드는 뭐 제대로 나와있는 정보도 없고 인터넷에 Arduino UNO R3 CH340 Schematic 검색하면 이런 저런 Schematic 이 많이 나오는데 이를 통해 확인하는 수 밖에 없다. 실제 Schematic 은 직접 소자와 배선을 하나하나 맞춰 보면서 확인하는 수 밖에 없다.

- ATmega328P 의 내부 구성

블록다이어그램 명개요
AVR CPUCPU. 플래시 메모리로부터 명령을 읽어, 데이터의 연산이나 가공을 하고, 각 주변장치 회로에 보낸다.
Flash프로그램의 명령을 보관한다. 전원을 꺼도 지워지지 않는 메모리.
SRAM프로그램에서 사용하는 데이터를 보관한다. 전원이 켜 있는 동안만 보존된다.
EEPROM프로그램에서 사용하는 데이터를 보관한다. 전원을 꺼도 지워지지 않는 메모리.
Oscillator Circuits / Clock Generation클럭의 근간이 되는 신호를 발생시켜 클록을 생성해 각 회로에 보낸다.
Power Supervision POR/BOD & RESET전원 모드와 리셋 신호의 제어를 실시한다.
debugWIRE PROGRAM마이크로컴퓨터 내부의 상태를 외부로부터 볼 수 있도록 한다.

 위 표가 마이크로컴퓨터의 주요 기능과 메모리에 대한 설명이고 이하는 마이크로컴퓨터 내부의 주변장치 기능이다:

블록다이어그램 명개요
Watchdog Timer/Watchdog Oscillator프로그램의 이상 동작, 하드웨어의 이상동작을 감지하여 CPU를 리셋한다.
8bit T/C 08비트(1~255까지)의 카운팅 타이머 PWM(Pulse Width Modulator)의 기능을 갖고 있다.
16bit T/C 116비트(1~65535까지)의 ... 중략
8bit T/C 2생략
SPI시리얼 주변장치 인터페이스(Serial Peripheral Interface)
USART 0외부 주변장치와 직력 통신을 실시한다.
TWI2선식의 동기식 직렬 통신을 실시한다.
Analog Comp.아날로그 데이터를 취득할 때에 기준이 되는 전압과 비교한다.
Internal Bandgap아날로그 데이터를 취득하기 위한 표준 전압 1.1V 를 만든다.
A/D Conv.아날로그 신호를 디지털 신호로 교환한다.
PORT D(8), PORT B(8), PORT C(7)외부 커넥터와 마이크로컴퓨터 내부 하드웨어 신호의 입출력을 제어한다.

2. LED를 ON/OFF하는 실험

- LED 접속

 LED 부품을 보면 다리가 긴 단자와 짧은 단자가 있음을 알 수 있다. 긴 쪽이 에이노드(Anode)로 양극(+)이고, 짧은 쪽이 캐소드(Cathode)로 음극(-)이다. 일반적으로 LED 를 제어하는 때에는 LED 에 과전류가 걸리지 않도록 전압을 거는 쪽에 전류 제한 저항을 넣는 식의 회로가 된다.

 LED 소자의 데이터시트를 확인하면 되는데 필자가 구매한 키트에는 사양서가 따로 동봉되어 있지 않았다. 그러나 일반적으로 빨간 LED 는 VF=2.2VV_F = 2.2V, IF=20mAI_F=20mA 정도이므로 R=VVFIR=\cfrac{V-V_F}{I} 를 통해 저항을 구할 수 있다.

150Ω150\Omega 정도의 저항만 있어도 되지만 아두이노(atmega328p) 보드에는 출력 전류 제한(40mA)이 있으므로 안전빵맨 호빵맨으로 필자는 200Ω200 \Omega 저항을 사용하였다. 책에서는 일반적으로 220Ω220 \Omega 저항을 사용한다고 하는데 저항 맞추기 귀찮아서 필자는 대충 했다.

- 점멸 프로그램 작성하기

 필자는 LED 의 Anode 를 9번 포트에 연결했다. 이는 atmega328p 칩의 하단(칩 모서리의 원형 홈이 좌측 상단의 가게 놓았을 때) 왼쪽에서부터 5번째 위치의 포트이다.

 이는 13번 핀이며 PB1 에 해당한다는 것을 확인한다. (책 기준으론 우측 하단의 15번 핀이 PB1 이다) 이를 통해 LED 를 점멸하게 만드는 코드를 작성하면 다음과 같다:

#include <stdbool.h>

#include <avr/io.h>

int main(void)
{
        int i, j;

        DDRB |= (1 << PB1);

        while (true)
        {
                PORTB ^= (1 << PB1);

                for (i = 0; i < 10; i++) {
                        for (j = 0; j < 10000; j++)
                                /* do nothing */ ;
                }
        }

        return 0;
}

avr/io.h 헤더파일에는 I/O 와 관련된 정의가 들어있다. DDRxData Direction Register 로 해당 포트를 읽기, 혹은 쓰기로 쓸지 결정한다. DDRBPort B 에 속한 포트의 Data direction 을 설정하는 Register 이다. 코드의 DDRBDDRB 레지스터의 메모리 주소가 저장되어 있고 해당 레지스터 주소에 저장된 값을 읽고 씀으로써 동작을 결정할 수 있다.

 위 코드에서 PB1PortB 1 이고, 이를 1 로 변경하는 것은 해당 포트를 쓰기 모드로 설정함을 의미한다.

 이후 PORTBPortB 1 의 비트를 끄고 켜면서 LED 를 점멸시킬 수 있게 된다. 이하의 반복문은 dummy work 를 수행함으로써 잠시 코드를 지연시킨다.

 이제 이하의 명령어를 입력하여 작성한 코드를 빌드한다:

avr-gcc -g -mmcu=atmega328p blink.c -o blink.elf
avr-objcopy -I elf32-avr -O ihex blink.elf blink.hex

- 책에는 나오지 않은 WSL 을 통한 개발 및 업로드 방법

 책에서 나오는 개발 환경은 Linux 이다. Linux 에서는 프로그램을 개발하고 빌드하고 업로드하는 것이 그리 어렵지 않다. 기본적으로 패키지가 제공이 되며 개발 환경 자체가 터미널에서 진행되기 때문이다.

 반면에 Windows 에서는 그렇지 않다. 가장 큰 문제는 WSL 에서 USB 통신을 통해 프로그램(더 정확하게, 책에서 나온 개념으론 hex 파일)을 업로드해야 하는데 WSL 에서는 Windows 에 연결된 USB 장치를 인식할 수가 없다. 따라서 WSL 에서 USB 장치를 인식할 수 있게 해야한다. (필자가 작성한 다음의 글 참고)

 장치를 WSL 2 에 붙였다면 이하의 명령을 입력하여 장치가 성공적으로 인식되었는지 확인한다:

lsusb

dmesg 명령어를 입력하여 mount 된 위치를 확인한다:

ttyACM0 라는 이름의 장치로 마운트되었으며 /dev/ttyACM0 를 통해 접근 가능하므로 avrdude 를 통해 업로드할 수 있다:

sudo avrdude -C <config-file> -v -patmega328p -carduino -P<device> -b115200 -D Uflash:w:<hex-file>:i

 여기에서 사용된 avrdude 의 설정 파일은 공식 홈페이지에서 다운로드 받을 수 있다. 따로 설정하지 않으면 /etc/avrdude.conf 에 있는 default config 을 사용하게 된다. 만일 필자와 같이 직접 라이브러리를 다운로드 받은 뒤 WSL 에서 업로드를 진행하고 싶다면 이하의 구문을 삭제해야 한다:

 여기에서 나온 programmer 부터 1509 번째 라인의 세미콜론까지 지우지 않으면 에러가 발생한다.

 위와 같이 정삭적으로 업로드 되었다면 이제 Arduino 에서 그 실행 결과를 확인하면 된다. 글을 작성하는 시점(22-09-07)에 정품 아두이노가 도착했다. 보드를 바꿔서 테스트 해본 결과, 핀 위치만 보드에 맞춰서 변경하면 정상적으로 동작함을 확인할 수 있다. hex 파일은 그대로 사용했다.

3. LED 실험 프로그램 이해하기

 ATmega328P의 경우 CPU로부터의 레지스터 제어는 I/O Mapped I/O다. C 언어 등의 고급 언어에서는 중요하지 않지만, 어셈블리 언어에서 이용하는 명령을 바꿈으로써 엑세스할 메모리 공간을 전환한다.

Atmega 시리즈는 32 개의 General Purpose Register 를 가지고 이중 상위 6개 레지스터는 간접 주소 (Indirect Addressing) 지정 용도로 사용이 가능하다.

 상위 6개 X, Y, Z 레지스터는 데이터 버스를 사용해 CPU 외부에 있는 주변장치의 주소를 지정할 때 이용할 수 있게 되어 있다.

- 어셈블러에서 확인

 이하의 명령어를 입력하여 ELF 파일로부터 어셈블러 코드를 추출한다:

avr-objdump -S blink.elf > blink.s

blink.s 파일을 열면 아래와 같이 어셈블리 명령을 확인할 수 있다:

* ldi 명령

ldi r24, 0x24
ldi r25, 0x00
ldi r18, 0x24
ldi r19, 0x00

ldi 명령은 LoaD Immediate 의 약자로 register [16-31] 에 8-bit 상수를 대입하는 명령어이다. 64행부터 67행은 r24, r25r18, r19 에 각각 0x24, 0x00 를 대입한다. 합쳐서 보면 0x0024 가 된다.

* movw 명령

movw r30, r18

movwword 크기(16-bit)로 레지스터 간의 데이터 복사를 수행한다. 따라서 위 명령은 r30, r31 레지스터에 r18, r19 내용을 기록한다.

 또한 r30r31 은 Z 레지스터 영역이므로 Z 레지스터의 값을 0x0024 로 바꾸는 명령임을 알 수 있다.

* ld 명령

ld r18, Z

 이는 Z 레지스터의 간접 주소값을 참조하여 해당 주소에 저장된 값을 r18 레지스터에 저장하는 코드이다. 따라서 위 코드는 Z 라는 레지스터의 저장된 값(위 movw 명령으로 인해 0x0024)의 주소가 가리키는 값을 읽어 r18 에 기록한다.

 여기에서 0x0024DDRB 레지스터의 주소이므로 위 코드는 DDRB 레지스터의 값을 읽어서 r18 에 저장하는 코드임을 알 수 있다.

* ori 명령

ori r18, 0x02

 위 명령은 r18 의 내용으로 논리연산 OR 를 수행한다. 0x0024 에서 읽어들인 값에 0x02 를 OR 한다. PB1 은 상수 1 이기 때문에 컴파일 타임에 1 << PB1 의 결과가 계산이 가능하고 따라서 1 << 1 의 값인 0x02OR 연산을 수행하게 된다.

* movw 명령

movw r30, r24

 위 명령을 통해 다시 r30, r31r24, r25 의 값을 기록한다. r30r31 은 Z 레지스터이며 r24r25 는 최초에 0x0024 로 초기화 했으므로 위 코드는 Z 레지스터에 다시 0x0024 라는 값을 기록한다.

* st 명령

st Z, r18

 Z 레지스터가 가리키는 주소에 r18 의 값을 기록한다. r18 의 값은 앞서 ori 명령을 통해 계산된 최종 결과이고 이를 통해 DDRB0x02 값을 OR 한 결과를 저장함을 알 수 있다.


 위 내용은 필자의 gitlab 에 정리해서 올렸다. make 명령을 통해 쉽게 확인 가능하므로 자세한 필자의 gitlab 을 통해 확인하길 바란다.

출처

[Book] 임베디드 엔지니어 교과서, 와타나베 노보루, 마키노 신지 지음, 정인식 옮김, 제이펍 출판사

[Site] https://docs.arduino.cc/resources/datasheets/A000066-datasheet.pdf
[Site] https://makersportal.com/blog/2019/3/12/testing-the-arduino-ch340-board
[Site] https://sparks.gogo.co.nz/ch340.html
[Site] https://www.wch.cn/download/CH341SER_ZIP.html
[Site] https://docs.arduino.cc/hacking/software/DFUProgramming8U2
[Site] https://www.microchip.com/en-us/product/ATmega328P
[Site] https://docs.microsoft.com/en-us/windows/wsl/connect-usb
[Site] http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf

[Product] https://www.coupang.com/vp/products/6557049479
[Product] https://www.devicemart.co.kr/goods/view?no=34404

[Datasheet] https://www.terraelectronica.ru/pdf/show?pdf_file=%2Fz%2FDatasheet%2FU%2FUNO_R3%28CH340G%29.pdf
[Datasheet] https://cdn.sparkfun.com/datasheets/Dev/Arduino/Other/CH340DS1.PDF
[Datasheet] https://ww1.microchip.com/downloads/aemDocuments/documents/MCU08/ProductDocuments/DataSheets/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf

profile
2000.11.30

0개의 댓글