라이브러리에 해당하는 글 1

[아두이노] 아두이노 라이브러리 만들기

IOT/아두이노|2019. 9. 5. 09:00

[아두이노] 아두이노 라이브러리 만들기



지금까지 아두이노를 코딩 할 때 아두이노 표준 라이브러리 함수를 사용하거나 누군가 만들어 놓은 라이브러리 함수들을 가져다 편하게 코딩을 해왔습니다. 그런 라이브러리 함수는 어떻게 만들어지는 걸까 궁금한 적이 아마 있을거에요. 누군가 만들어 놓읗 라이브러리 함수만 쓰는 것은 재미가 없습니다. 한번 쯤 자신이 만든 라이브러리 함수를 사용해보고 싶은 생각을 가져 보셨을 거라 생각 합니다. 그리고 사용되는 부품에 따라서 뭔가 2% 부족한 함수이거나 이런 동작을 수행하는 함수가 있었으면 하고 원한 적이 있을 꺼에요. 이런 라이브러리 함수는 만드는 방법이 궁금하실 것 같아서 한번 라이브러리 함수를 만드는 방법을 알아두면 좋을 것 같아서 간단히 소개합니다.

1. 클래스


C언어에서 C++으로 넘어가면서 객체지향 프로그램에 대해 배우게 되는데 처음 클래스라는 개념을 배우게 되는데 클래스는 객체를 생성하기 위한 변수와 함수를 묶어 놓은 틀이라고 말합니다. 그리고 클래스의 정의와 특징들을 열거하자면 좀 복잡하고 쉽게 그 의미를 이해하기 쉽지 않습니다. 그냥 간단히 객체를 붕어빵이라고 하면 클래스는 붕어빵틀이라고 생각에서 출발해 주세요. 클래스는 완전 실체화 되지 않는 틀이라고 생각하고 객체는 실제 그 틀을 실체화 한 대상으로 생각하면 될 듯 싶네요.

그리고 클래스는 캡슐화 의미만 가지고 있으면 됩니다. 변수와 함수들을 하나의 주제로 묶어 만들어 놓은 틀로 생각하시면 됩니다.

예를 들어, 우리가 자동차를 코딩으로 비유해 봅시다.


자동차는 위 그림처럼 엔진, 핸들, 바퀴, 몸체, 의자, 창문 등등 여러가지 자동차 부품들이 모여서 한대의 자동차를 만들게 됩니다. 일반 코딩은 자동차 부품들을 한번에 하나의 틀로 일체화 된 코딩이라고 생각해 봅시다. 이런식의 코딩을 하게 되면 나중에 자동차 엔진이 맘에 안들어 다른 엔진으로 바꾸는 일이 생기면 이 엔진에 해당되는 코딩 위치와 이 엔진과 다른 부품과 연결된 위치를 전부 찾아내어 수정해야 합니다. 즉, 한번 뭔가를 업그레이드 할려면 전체 코딩 소스를 수정해야 한다는 복잡해 지겠죠. 여기서 자동차의 각 부품을 클래스 단위로 캡슐화 한다면 어떻게 될까요. 부품별로 나누고 객체화 시키면 나중에 엔진을 교체하고 싶을 때 해당 엔진 객체만 다른 객체로 바꾸면 쉽게 수정이 가능해 집니다.


위 그림에서 3개의 엔진이 있을 때 엔진을 교체한다면 엔진 클래스 A, B, C 중 자신이 원하는 클래스를 가져다가 사용하시면 됩니다. 즉, A엔진에서 C엔진으로 교체할 경우 C엔진 클래스로 변경만 하면 되기 때문에 코딩이 쉬워집니다. 전체 소스에서 클래스 접근 부분만 수정하면 되기 때문에 쉽게 코딩이 됩니다. 클래스 단위로 엔진이 만들어 지지 않았다면 어떤 현상이 발생할까요. 전체 소스에서 엔진이 어디서 부터 어디까지 인지 또는 엔진과 연결된 다른 부품에서 엔진과 다른 부품과의 경계를 구분하기가 어려워 엔진 코딩영역인지 다른 부품영역인지 나누는 것이 쉽지 않게 됩니다. 코딩을 길게 하다면 나사(변수)가 엔진에 사용하는 나사인지 바퀴에 사용하는 나사인지도 구별하기 힘들게 되는 문제점을 가지게 됩니다.

그래서 클래스라는 것을 이용합니다. 각 부품을 클래스화 하면 각 부품을 개별적으로 접근 제어 통제가 됩니다. 엔진에 대한 동작은 엔진 클래스에 안에서만 동작하고 바퀴에 대한 동작은 바퀴클래스 안에서만 동작하기 때문에 개별적 동작 제어가 되기 때문에 실제 프로그램을 설계할 때 클래스 단위로 접근 되기 때문에 그 경계가 명확하고 부품별 제어는 쉬워집니다. 왜 클래스를 배워야 하는지 아시겠지요.

이제 클래스에 대해 알아보도록 합시다.

2. 클래스 문법


클래스 기본 구조는 아래와 같습니다.

[클래스 구조]

class 클래스명
{
  private: //접근권한
    멤버변수1;  //내부접근
    멤버함수2;  //내부접근
  public: //접근권한
    멤버변수2;  //외부접근
    멤버함수2;  //외부접근
};

클래스명으로 하나의 클래스를 만드는 데 내부에서 private, public, protected 로 접근 권한을 부여합니다.

  • private : 내부에서만 접근 가능하고 외부에서는 접근 불가능
  • public : 내부에서 접근 가능할 뿐만 아니라 외부에서도 접근 가능
  • protected : 현재 클래스에서는 private 성격을 가지지만 다른 클래스에 상속 될 경우 다른 클래스에서 접근 불가능
    현재 클래스에서만 접근 가능한 제한 된 성격을 지니고 있음

private와 public만 기본적으로 알아두시면 됩니다. private은 클래스 내부에서만 접근되고, public은 클래스 밖에서도 접근 할 수 있다는 개념만 잡아 두세요. 우리가 러이브러리 함수를 호출하여 사용할 때 public 접근지정자에 선언된 함수들을 호출하여 사용되어 왔습니다.

클래스명 클래스객체;

클래스객체.멤버함수2();

이러헥 아두이노에서 사용되어 왔지요.

[생성자 & 소멸자 함수] : 생성자와 소멸자 함수는 클래스명과 동일해야 함.

  • 생성자 함수 : 클래스 객체가 생성과 동시에 호출되는 함수
  • 소멸자 함수 : 클래그 객체가 소멸과 동시에 호출되는 함수
class abc
{
  public:
    abc(){
          생성자함수;
    }
    ~abc(){
          소멸자함수;
    }       
}

abc 클래스를 객체변수를 다음과 같이 선언됩니다.

abc obj;
abc::abc(){
  생성자함수;
}

이렇게 abc()함수가 호출됩니다.

아두이노에서 LCD 라이브러리 함수를 사용할 때를 생각해 봅시다. 처음 객체선언 할 때 을 보면 다음과 같습니다.

예)

#include <LiquidCrystal.h>

//LiquidCrystal(rs, enable, d4, d5, d6, d7) 
LiquidCrystal lcd(3, 4, 8, 9, 10, 11);

위 처럼 lcd(3,4,8,9,10,11)로 객체변수가 선언되면 lcd()생성자 함수가 호출됩니다.

class LiquidCrystal
{
  public:
    LiquidCrystal(int a, int b, int c, int d, int e, int f){
      생성자함수 수행 명령 코딩;
    }
    ~LiquidCrystal(){
      소멸자함수 수행 명령 코딩;
    }
};

대충 a,b,c,d,e,f은 실제 변수명이 아니라 예를 든 것일 뿐 실제 따른 변수명이겠죠. 예를 들것 뿐입니다. 이렇게 클래스 객체변수를 선언하면 선언과 동시에 생성자 함수가 호출됩니다.

소멸자 함수는 이 클래스 객체변수가 delete 될 때 소멸자 함수가 호출됩니다.

이제는 쉽게 클래스를 이해하기 위해서 예를 들어 실험해 봅시다.

예) 두수의 합을 클래스로 만들기

Calc이라는 클래스를 만듭니다. 두 수 이니깐 2개의 멤버변수를 만드는데 해당 클래스 내에서만 접근하고 외부에서는 이 변수를 접근하지 못한다고 하면 다음과 같이 코딩합니다.

class Calc
{
  private:
    int a=0;
    int b=0;
};

여기서, 생성자 함수를 통해서 외부에서 a,b값을 받고, sum()함수로 입력받은 두수의 덧셈을 수행 해봅시다.

class Calc
{
  private:
    int a=0;
    int b=0;
  public:
    Calc(int val1, int val2){
      a=val1;
      b=val2;       
    }
    int sum();
};
int Calc::sum(){ //클래스 외부로 빼면
  return a+b;  
}

sum()함수는 생성자 함수처럼 클래스 내부에서 표현해도 되지만 외부로 빼면 위 코딩처럼 표현하시면 됩니다. 특정 동작 코딩이 꽤! 길어진다면 클래스 내부에서 코딩하면 지져분하게 보입니다. 그래서 외부로 빼서 클래스를 보기 편하게 최소화 코딩을 하면 좋습니다.

아무튼 이렇게 표현하고 아두이노 가상시뮬레이터에서 그 결과를 살펴볼까요.

[소스]

class Calc
{
  private:
    int a=0;
    int b=0;
  public:
    Calc(int val1, int val2){
      a=val1;
      b=val2;       
    }
    int sum();
};
int Calc::sum(){ //클래스 외부로 빼면
  return a+b;  
}

Calc obj(2,3); //객체변수 선언

void setup()
{  
  Serial.begin(9600);
  Serial.println(obj.sum());
}

void loop()
{
}

[결과]

5

위 예제를 보면 클래스 만들기가 그렇게 어렵지 않죠.

3. 라이브러리 만들기


아두이노 Blink 예제를 가지고 간단히 클래스를 만들어 볼까요.

[기본 틀]

#ifndef LED_H    
#define LED_H

클래스 코딩

#endif   

우선, 위 기본틀을 이해해 주세요. #ifndef ~ #endif 구조문을 기본 틀로서 해당 파일이 한번만 포함되도록 제한하는 방식으로 중복 인크루드 되는 걸 막는 구문입니다. 두번 포함 되지 않게 하는 표현으로 생각 하세요. #ifndef은 전처리기로 LED_H라는 정의된 식별자가 있는 지 체크하는데 없으면 처음 한번은 해당 코딩 소스를 인크루드 합니다. 안에 보시면 #define으로 LED_H가 정의하는 문장이 있죠. 처음에는 없으니깐 한번 인크루드하는데 안에서 정의를 하니깐 다음 번에는 인크루드하지 않습니다. 두번 참조되는 것을 막는 구문으로 정리하시면 됩니다.

Blink 예제로 실험 한다고 했죠. 클래스를 만들어 볼까요.

[Led.h 파일]

#ifndef LED_H    
#define LED_H

#include "arduino.h"

class Led
{
  private:
      int LedPin;
  public:
      Led(int Pin){ 
         LedPin = Pin;
         pinMode(LedPin, OUTPUT);         
      }
      ~Led(){
      }
public:
      void LedHigh();
      void LedLow();
};

#endif   

[Led.cpp 파일]

#include "Led.h"
void Led::LedHigh(){
  digitalWrite(LedPin,HIGH);
}
void Led::LedLow(){
  digitalWrite(LedPin,LOW);
}

여기서, PinMode(), DigitalWrite()함수 코딩을 직접해야 하는데 너무 길어서 인용했네요. 참고로 arduino.h 파일에 아두이노 표준 함수들이 들어 있기 때문에 선언해 주셔야 클래스 안에 아두이노 변수, 상수, 함수들을 사용할 수 있습니다.

간단히 위 클래스 함수에 대해 설명하면,

  • 생성자 Led()함수로 핀번호가 입력되면 무조건 해당 핀번호는 출력모드로 지정합니다.
  • LedHigh()와 LedLow()함수로 해당 핀번호로 High or Low 신호를 출력하게 됩니다.

이런식으로 자신만의 클래스를 만들 수 있습니다. 클래스 만드는 법이 간단하죠.



위 자료 출처에 가셔서 라이브러리 함수 내부 코딩을 보시면 꽤 긴 코딩을 보실 수 있을꺼에요 오늘 post는 의미 전달이 목적이라 실제 내부로직을 위 자료 출처의 소스처럼 코딩해야 합니다. 그러면 너무 복잡하고 의미전달도 안되겠죠. 간단히 클래스라는 개념만 오늘 배우는 시간이기 때문에 간단히 예제 클래스를 만들어 실험 했네요.

4. 라이브러리 추가하기


만든 클래스를 라이브러리에 추가하려면 아래와 같은 과정을 수행합니다.

먼저,


Led 클래스 이름을 전부 통일 시켜주세요.


위 그림처럼 아두이노 IDE가 깔려있는 폴더에서 라이브러리 폴더를 찾아주세요. 그안에 방금 만든 폴더를 추가하시면 끝납니다.


위그림에서 보시는 것 같이 라이브러리가 추가 된 것을 확인 할 수 있을 꺼에요.

5. 라이브러리 실행


1) 회로도


  • 준비물 : Red Led 1개, 저항 220옴 1개, 아두이노우노
  • 내용 : Led를 13번 핀에 연결하시오


2) 코딩


위에서 만든 라이브러리를 이용하면.

[실제 코딩]

#include <Led.h>

Led obj(13); //객체선언

void setup()
{   
}

void loop()
{
  obj.LedHigh(); //13번핀을 High상태로
  delay(1000);
  obj.LedLow(); //13번핀을 Low상태로
  delay(1000);
  
}

[가상시뮬레이터 코딩] : 인크루드를 할 수 없으니 해당 클래스를 복사해 와야 합니다.

class Led
{
  private:
      int LedPin;
  public:
      Led(int Pin){
         LedPin = Pin;
         pinMode(LedPin, OUTPUT);         
      }
      ~Led(){
      }
public:
      void LedHigh();
      void LedLow();
};

void Led::LedHigh(){
  digitalWrite(LedPin,HIGH);
}
void Led::LedLow(){
  digitalWrite(LedPin,LOW);
}

Led obj(13);

void setup()
{   
}
void loop()
{
  obj.LedHigh();
  delay(1000);
  obj.LedLow();
  delay(1000);
  
}

3)결과


Led 클래스를 인크루드 할 수 없기 때문에 클래스를 복사해서 가상시뮬레이터로 돌려 봤네요.


마무리


오늘은 클래스를 만들어 보는 시간을 가졌습니다. 아두이노를 코딩하다보면 많은 라이브러리 함수들을 사용합니다. 사용하다보면 뭔가 2% 부족한 함수들이 있습니다. 이 기능과 저 기능이 합쳐졌으면 하는 함수들이 있는가 하면은 자신이 뭔가 특정 동작을 수행하는 함수를 만들 때가 있습니다 .이 함수를 나중에 다른 곳에 사용하고 싶어지는 경우가 생길 때 직접 여러분들이 여러분들의 전용 클래스를 만들어 놓으면 나중에 해당 함수를 가져다 쓸 때 편하게 사용할 수 있습니다.

나중에 도움이 많이 되니깐 이번에 클래스를 만드는 연습을 해 놓으셨으면 합니다.

댓글()