본문 바로가기
Arduino(아두이노) 기초 강좌

아두이노(Arduino) 프로그래밍

by 오마이엔지니어 2015. 11. 11.
반응형

아두이노 프로그래밍

아두이노는 마이크로컨트롤러 이므로 원하는 기능을 먼저 설정하고 이에 맞는 프로그래밍 통해 기능을 구현합니다

사용 언어는 C/C++을 사용하고 컴파일러 및 라이브러리는 AVR-GCC을 사용합니다

avr-gcc(AVR Libs) 함수에 존재하는 표준함수의 라이브러리와 아두이노에서 제공하는 함수 등

다양한 함수를 적용 시킬 수 있습니다

gcc 컴파일러를 기본으로 하지만 일반 범용의 컴퓨터와 다른 부분을 수정하여 만들어진 것이므로 이를 고려해야 합니다

예를 들어 입출력 장치가 아두이노와 범용의 개인용 컴퓨터와 다릅니다

따라서 몇몇 함수는 특수한 처리를 해야 하고 어떤 것은 사용이 불가능합니다

아두이노 설치 시, 이미 존재하는 함수도 있지만, 그 외의 많은 기능들은 함수가 없는 경우도 있습니다

그러나 아두이노의 활발한 사용으로 많은 오픈 소스 함수 들이 존재하므로 검색을 통해 찾아,

파일을 받아 라이브러리 함수를 등록해서 사용할 수 있습니다

내장된 함수 사용 시, 마이크로컨트롤러의 모델에 따라 해당 함수나 클래스가 지원되지 않는 경우가 있으므로

우선 사용하는 모델 파악이 필요합니다

아두이노 우노는 시리얼 포트가 아두이노 메가에 비해 적습니다

따라서 우노에서는 Serial1,2,3 등이 존재하지 않으므로, 이것을 사용하면 안됩니다

 

함수와 연산자

아두이노는 C/C++언어 기반으로 작성하고, 표준 C와 일부 C ++의 모든 기능을 지원합니다 

 

기본문법
구분 기호 : ;, {}

주석 : //, /* */

define : #define, #include

산술 연산자 : +, -, *, /, %

할당 연산자 : =

비교 연산자 : ==, !=, <, >, <=, >=

부울 연산자 : &&, ||, !

포인터 연산자 : *, &

비트와 쉬프트 연산자 : &, |, ^, , <<, >>

증감 연산자 : ++, --

결합 산술 연산자 : +=, -=, *=, /=, &=, |=

조건반복

조건 : if, if...else, switch case

반복 : for, while, do... while

분기 및 점프 : break, continue, return, goto

 

변수

C++을 사용하므로 이 언어의 변수 사용과 같다

 

상수

HIGH / LOW : 입출력 핀의 상태를 말하며, 디지털 회로의 논리 표현과 같습니다

LOW(논리 0, 0 V 근처), HIGH(논리 1, 높은 전압, 5 V 또는 3.3 V 논리 게이트 VCC 전압)

INPUT / OUTPUT: 입출력의 방향으로 입력과 출력. INPUT (입력), OUTPUT(출력)

true / false  : 논리의 부울

 

변수 데이터 유형 및 C++ 클래스

void, boolean, char, unsigned char, byte, int, unsigned int, word, long, unsigned long, float, double

string, array

 

형 변환 함수

다음 형 변환 함수는 인수로 받은 형의 데이터를 형 변환하여 반환합니다

char(), byte(), int(), word(), long(), float()

 

변수의 특성 정의 예약어

static, volatile, const

 

유틸리티

sizeof() - C/C++에서 함수가 아니라 변수나 형의 크기를 측정하는 도구이다

따라서 변수의 크기를 컴파일러가 알고 있기 때문에 바이트 단위로 변환한다

C/C++의 모든 정적 변수는 선언 시, 크기가 정해지기 때문이다

 

활용예:

--------------------------------------------------------------------------------------------------------------------------------

const int pinSens = A0;  // 아두이노 우노의 A0 포트 사용 예

int gdata[] = { 1, 2, 3, 4, 5, 3};
const int SZARR_DATA = sizeof(gdata)/sizeof(gdata[0]); // 배열 크기 6개를 얻는 경우
#define  SZ_IARRDATA  (sizeof(gdata)/sizeof(gdata[0]))
const int sz_int = sizeof(int);  // 8비트 CPU에서는 주로 2바이트

void loop() {
    char cnt;

    for (cnt = 0;cnt < SZARR_DATA;cnt++) {
         gdata[cnt ] = analogRead(pinSens);
         deley(100);
    }
}

--------------------------------------------------------------------------------------------------------------------------------

 

sizeof가 함수가 아니므로 전역변수를 선언 할 때도 문제가 없이 숫자화 한다

 

 

기초 함수들

다음의 기본함수는 아두이노 전용의 함수들이다. 일부는 표준함수에 존재하는 함수도 있습니다

 

디지털 입출력 함수

pinMode(pin, mode) - 핀의 입출력을 정의

digitalWrite(pin, value) - 디지털 출력 핀에 출력하는 함수

int digitalRead(pin) - 디지털 핀의 상태를 읽는 함수


아날로그 입력 및 PWM 출력

ATmega는 ADC을 내장하고 있다. 따라서 아날로그 신호를 디지털화할 수 있습니다

이 ADC 모듈을 사용하는 함수입니다

analogReference(유형)

int analogRead(pin) - ADC 보통 10비트 정밀도를 갖는 모듈이 많습니다

ADC는 보통 10비트를 많이 사용하므로 최대 숫자가 10비트입니다

따라서 8비트 정수형 char 변수를 사용하여 데이터를 저장하면 위 부분 MSB의 값이 버려지므로 int 형의 정수 변수가 필요합니다

아두이노 보드에 따라 ADC의 비트수는 주로 10비트를 많이 사용하지만,

고사양의 아두이노를 사용한다면 비트수가 달라질 수 있으므로 미리 확인하는 것도 좋은 방법입니다

ADC의 응용에서 10, 12 그리고 16비트 등을 사용하지만 이 이상의 분해능은 거의 사용하지 않으므로 int 형이면 거의 처리됩니다

 

PWM 출력

아두이노에 사용하는 ATmega는 디지털 값을 아날로그 전압으로 바꾸어 주는 DAC가 거의 존재하지 않습니다

따라서 다음 함수는 아날로그 출력이 아니고 PWM 출력으로 사용하는 함수입니다

만약 사용하는 마이크로컨트롤러가 DAC가 존재한다면 이에 맞는 함수를 찾아 확인하고 사용하여야 합니다

analogWrite(pin, value) - PWM는 타이머 모듈을 사용하므로 OCxA, OCxB의 기능을 사용합니다

별도의 설정없이 이 함수를 사용하면 됩니다

 

1.핀번호 (pin) : 타이머와 연결된 OCxA, OCxB 핀을 사용하므로 숫자가 정해지면 타이머 모듈이 자동 결정됩니다

2.듀티값 (vlaue) : 펄스파의 HIGH와 LOW의 시간 비입니다

최댓값이 8비트 타이머는 비교레지스터도 8비트를 사용하므로 0 ~255 이다. 0 은 항상 LOW가 출력되고 이 값의 비에 따라 펄스폭이 결정됩니다

 

PWM 출력은 아두이노 핀이 제한되어 있습니다 보통 핀 숫자 앞에 ~,-,# 등의 문자가 있는 핀이 PWM 가능한 핀입니다

각각의 타이머 마다 특성이 틀리므로 주파수는 변경은 관련 레지스터를 별도로 제어 해야 합니다

ATmega에서 사용하는 타이머는 8비트(타이머 0, 2)와 16비트 타이머(1,3,...)을 사용하므로

프리스케일러와 제어 비트 설정으로 원하는 주파수를 바꿀 수 있습니다

그러나 8비트 타이머의 주파수 변환은 프로스케일러에 의해 제한 되므로, 이 함수를 사용하면 세밀한 조정을 힘듭니다

이 함수를 사용하지 않고, 마이크로컨트롤러의 레지스터를 직접 제어해서 다양한 방식의 PWM을 구현할 수 있습니다

PWM 발생은 디지털 회로인 타이머 모듈에서 이루어 지게 때문에 레지스터 설정값에 따라 각 요소값이 변화합니다

이 때 소프트웨어는 레지스터 설정만 하면 되므로 신호를 만드는 과정상 직접적으로 관여하지 않습니다

따라서 효과적으로 자원을 사용할 수 있습니다

만약 8비트 타이머를 사용한다면

 

--------------------------------------------------------------------------------------------------------------------------------

analogWrite(6, 127);  // 보통 뒤의 값이 듀티값이므로 8비트 255 까지이다. 따라서 127은 듀티가 거의 50%입니다

--------------------------------------------------------------------------------------------------------------------------------

 

아두이노 우노의 핀 6번은 OC0A 이므로, ATmega328P의 타이머 0을 사용한다. 따라서 이 함수 호출로 8비트 타이머0로 자동 할당됩니다

따라서 듀티값은 최댓값이 255까지입니다

함수의 한번 호출로 타이머의 레지스터가 설정되기 때문에 다른 설정이 있기 전까지는 계속 PWM이 작동합니다

 

쉬프트 및 펄스 함수

shiftOut(dataPin, clockPin, bitOrder, value)

unsigned long pulseIn(pin, value)

 

시간 함수

시간 읽기:

unsigned long millis()

unsigned long micros()

 

지연 함수:

delay(ms)

delayMicroseconds(microseconds)

 


*아두이노 map 함수 그래프*


 

수학 관련 함수

min(x, e), max(x, e), abs(x), constrain(x, a, b), pow(base, exponent), sqrt(x)

map(value, fromLow, fromHigh, toLow, toHigh) - 특정 범위의 위치 값을 다른 범위의 위치 값으로 매핑하여 변환한다.

이 map 함수는 특정 범위의 값을 다른 범위의 값으로 변환하는 것이다. 수학적 개념은 일차함수 계산과 같은 의미이다.

 

 

함수 내에서 실수가 아니라 정수형의 계산을 통해 맵핑을 하고 정수형 숫자를 반환합니다

여기서 사용 된 변수는 long이며, 32 비트(4 바이트)를 사용하므로 -2,147,483,648부터 2,147,483,647 범위를 갖습니다

따라서 계산 중에 또는 계산 결과가 32비트 이상이 되면 문제를 일으키므로, 숫자가 크면 예상되는 값을 예측해야 합니다

 

--------------------------------------------------------------------------------------------------------------------------------

int val = analogRead(A0); // 아두이노 우노나 메가2560은 10비트 ADC을 사용하므로 10비트 값이 읽힌다.
val = map(val, 0, 1023, 0, 255);

--------------------------------------------------------------------------------------------------------------------------------

 

이 경우 아날로그 변환값이 500이라면:

가 됩니다

위의 예에서 처럼 비트수가 딱 떨어질 때는 오히려 비트 쉬프트 연산이 효과적입니다

결국 10비트 정수값을 8비트 정수 값으로 변환하고자 하는 것과 같은 의미입니다

다음 경우처럼 간단히 처리하면 기계어 코드의 실시간 실행에서 효율적일 수 있습니다

10비트를 8비트화 하는 것이므로 다음과 같이 쉬프트 연산을 사용할수 있습니다

--------------------------------------------------------------------------------------------------------------------------------

int val = analogRead(A0);
val >>=2;
unsigned char u8val = (unsigned char) val;

--------------------------------------------------------------------------------------------------------------------------------

 

삼각함수

sin(rad), cos(rad), tan(rad)

 

랜덤 함수

randomSeed(seed), long random(max), long random(min, max)

 

비트 바이트 함수

lowByte(), highByte(), bitRead(), bitWrite(), bitSet(), bitClear(), bit()

 

외부 인터럽트 함수

attachInterrupt(interrupt, function, mode) : 외부 인터럽트가 발생했을 때, 처리를 하기위해 설정하는 함수입니다

여기서 function은 인터럽트가 발생했을 때 기능을 처리하기 위한 인터럽트 핸들러 입니다

인수 :

1.interrupt : 인터럽트 구분용 숫자입니다 아두이노 우노의 경우 0(핀 2번)과 1(핀 3번) 2개의 외부 인터럽트가 있습니다

마이크로컨트롤러에 따라 갯수가 정해져 있습니다

2.function : 인터럽트 핸들러로 void 타입의 함수로 정의해야 합니다, 인터럽트가 걸리면 이 함수가 자동 호출됩니다

3.mode : 인터럽트 접수 방법을 지정합니다, 디지털 입력이기 때문에 각각의 상황을 정의합니다

1.LOW : 핀이 LOW일 때, 인터럽트가 걸립니다

2.CHANGE : 핀의 입력 상태가 변하면 인터럽트가 걸립니다

3.RISING : 상승 엣지(LOW -> HIGH)일 때, 인터럽트가 걸립니다

4.FALLING : 하강 엣지(HIGH -> LOW)일 때, 인터럽트가 걸립니다

detachInterrupt(interrupt) - 인터럽트 실행을 중지합니다

 

인터럽트 함수

interrupts(), noInterrupts()

 

시리얼 통신 함수

시리얼 포트의 통신을 위해 데이터를 보내기 전에 우선 설정 함수를 사용하여 통신 채널을 설정합니다

설정 완료 후, 관련 입출력 함수를 사용합니다

begin() - 통신 설정

available() - 통신 수신 여부에 따라 함수 자동 호출됨

read(), flush(), print(), println(), write() - 데이터 읽거나 쓰기

 

포트 조작 관련

마이크로컨트롤러의 포트를 조작할 때, 하나의 핀을 사용할 경우 입출력 모드 함수로 설정하고 입출력 함수를 데이터를 입출력 할 수 있습니다 그러나 포트를 동시에 한 묶음으로 조작 하거나 분산된 입출력 이라면 마이크로컨트롤러의 레지스터를 직접 조작하여 사용할 수 있습니다

다음과 같은 레지스터 조작을 통해 포트를 사용할 수 있습니다

DDR[B/C/D] : 데이터 방향 레지스터로 각 포트의 방향을 지정합니다, 주로 각 비트의 값이 1은 출력, 0은 입력입니다

PORT[B/C/D] : 디지털 출력 레지스터로 여기에 써지면 바로 핀으로 논리 상태가 출력 됩니다

PIN[B/C/D] : 핀으로 부터 디지털 입력 레지스터로 디지털 상태를 읽습니다

 

포트 조작 예

아두이노 우노는 디지털 출력 핀 0~7까지 PORTD에 연결되어 있습니다

만약 디지털 출력 핀 1 부터 7까지 출력으로 설정하고, 데이터를 출력 한다면 다음과 같은 예를 생각할 수 있습니다

 

--------------------------------------------------------------------------------------------------------------------------------

void setup()  {
    char cnt;
    for (cnt =1;cnt <=7;cnt++)
         pinMode(cnt, OUTPUT);
}
void loop() {
    char cnt;
    char data;
    data = 0xFE;

    for (cnt =1;cnt <=7;cnt++) {
         data >>= 1;

if (data & 0x01)
             digitalWrite(cnt, HIGH);
         else
             digitalWrite(cnt, LOW);
     }
}

--------------------------------------------------------------------------------------------------------------------------------

 

그러나 마이크로컨트롤러의 포트 구조상 8비트 CPU는 포트 묶음이 8비트로 처리되고, 조작도 8비트 단위로 할 수 있습니다

따라서 위의 코드의 기능을 다음과 같은 방법으로 구현할 수 있습니다

 

--------------------------------------------------------------------------------------------------------------------------------

void setup()  { DDRD = B11111110;
}
void loop() {
     PORTD = 0xFE;
}

--------------------------------------------------------------------------------------------------------------------------------

 

위의 2가지 코딩 방법을 비교할 때, 묶음으로 처리하는 것이 실행 시간 측면이나 기계어 코드 크기 측면에서 효율적일 수 있습니다

특정 핀 만을 재 설정하는 예:

 

--------------------------------------------------------------------------------------------------------------------------------

DDRD = DDRD | B11111100;  // 포트 D의 핀 2부터 7까지 출력 설정,  핀0과 1은 시리얼 통신의 RX와 TX으로 사용합니다
PORTD = B10101000;             // 핀  7,5,3을 HIGH 그리고 핀 6,4,2는 LOW로 출력, 위의 DDRD 설정에 따라 PORTD0-1은 무시 됩니다

--------------------------------------------------------------------------------------------------------------------------------

 

주어진 함수 만 사용할 수 있는 것이 아니고 마이크로컨트롤러의 레지스터를 직접 제어할 수 있습니다

이 레지스터 명과 정의는 마이크로컨트롤러 마다의 헤더파일에 이미 지정되어 있습니다

이렇게 포트를 비트 별로 설정을 하려면, 우선 아두이노의 회로를 확인하여 어떤 포트가 어느 커넥터에 연결되었는지 인지하고

입출력 회로의 기능이 동작하도록 포트를 조작할 수 있습니다

 

AVR Libc

AVR-gcc는 gcc를 Atmel AVR적용하기 위해 만들어 졌습니다

일반 컴퓨터와 마이크로컨트롤러의 개발 특성이 다른 부분이 있기 때문에, AVR 특성에 맞는 몇가지가 추가되거나 변형 되었습니다

각 마이크로컨트롤러의 레지스터, 사용하는 마이크로프로세서 체계 등에 적합하도록 변형 및 추가 되었습니다

예를 들어 사용하는 AVR의 SFR 레지스터가 정의되어 있습니다

gcc도구로 부터 변형 되었으므로 틀의 유사성이 있으며, 다음 3부분으로 구성 됩니다

 

avr-binutils

avr-gcc

avr-libc

AVR을 기반으로 하는 아두이노 프로그래밍은 AVR-gcc를 아두이노 통합개발환경에 결합하여,

개발 환경을 설치하면 별도의 추가 설치없이 사용할 수 있습니다

 

인터럽트

마이크로컨트롤러의 인터럽트 처리를 허용할 것인지를 설정합니다

 

전체 인터럽트 요청 신호를 무시:

 

--------------------------------------------------------------------------------------------------------------------------------

cli();  //  CPU가 인터럽트 요청을 무시

--------------------------------------------------------------------------------------------------------------------------------

 

전체 인터럽트 요청 신호를 허용 :

 

--------------------------------------------------------------------------------------------------------------------------------

sei(); //  CPU는 인터럽트 요청을 허용하여 인터럽트 핸들러를 실행합니다

--------------------------------------------------------------------------------------------------------------------------------

 

delayMicroseconds()나 시리얼 통신는 인터럽트의 cli()에 의한 비활성화에 영향을 받습니다

 

시간 지연

delayMicroseconds()는 가장 작은 시간 지연이 2μs 정도입니다

이것보다 짧은 시간을 지연할 때는 어셈블리어의 'NOP' 명령을 사용할 수 있습니다

'NOP' 명령 실행에 걸리는 시간은 16 Mhz을 사용할 경우 62.5ns 정도의 지연 시간을 얻을 수 있습니다

이를 위해 다음과 같이 코딩합니다

 

--------------------------------------------------------------------------------------------------------------------------------__asm__("nop\n\t");

--------------------------------------------------------------------------------------------------------------------------------

 

포트조작

이미 언급 했드시, AVR 포트 제어 시, 레지스터를 직접 제어하여 빠른 포트 제어를 실현 할 수 있습니다

 

특수 레지스터의 조작 (SET/RESET)

AVR의 SFR(특수 기능 레지스터)의 비트 단위 조작이 가능합니다

다음 헤더에 정의된 정의 구조를 사용할 수 있습니다

 

--------------------------------------------------------------------------------------------------------------------------------# ifndef cbi
# define cbi(sfr, bit) (_SFR_BYTE(sfr) &= _BV(bit))
# endif
# ifndef sbi
# define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
# endif

--------------------------------------------------------------------------------------------------------------------------------

 

 

 

 

 

 

 

반응형

댓글