[아두이노] 아두이노 코딩 변수

IOT/아두이노기초문법|2019. 4. 10. 09:00

[아두이노] 아두이노 코딩 변수



오늘은 예전에 아두이노 변수란 포스팅을 한 적이 있습니다. 불가피하게 이번 연재한 포스트의 흐름을 이여가기 위해서 약간은 중복된 내용이 포함되게 되었네요. 참고로 오늘 내용은 변수에 대해서 간단히 살펴보고 변수를 사용하여 코딩하는 것 까지 다루도록 하겠습니다.

1. 아두이노의 변수


이 부분은 자세히 참고 포스트에서 설명했지만 처음부터 다시 시작한다는 기분으로 간단히 복습 차원으로 짧게 기초만 알아둘 것만 정리하겠습니다.


자료형은 담을 수 있는 그릇이고 변수는 그 그릇의 이름입니다. C에서는 변수를 선언할때 변수의 크기인 자료형을 선언해줘야 합니다. 사용할 변수에는 자료형이 무조건 선언된다고 암기해 주세요.

char A = 'a';

char은 문자형 그릇이고 A라는 이름을 가집니다. A라는 그릇에 'a'라는 문자를 저장합니다.

int B = 123;

int은 정수형 그릇이고 B라는 이름을 가집니다. B라는 그릇에 '123' 이라는 숫자를 저장합니다.

float C = 123,12;

float은 실수형 그릇이고 C라는 이름을 가집니다. C라는 그릇에 '123.12'이라는 실수를 저장합니다.


위의 그림처럼 그릇의 크기가 대충 이런 느낌이라고 생각하시면 됩니다.


자료형 범위

  • 1 Bytes => -128~127
  • 2 Bytes => -32768~32767
  • 4 Bytes => -2147483648 ~2.147.483.647

그냥 작은수, 큰수, 실수 이정도로만 생각하시고 넘어가세요. 나중에 코딩하다보면 자연스럽게 자료형을 뭘 써야지 알게 됩니다. 아두이노에서 핀번호를 지정한다면 byte로 하면 가장 알맞겠죠. 그리고 아날로그 출력은 0~255 사이의 값이니깐 대충 int 변수겠죠. 아날로그입력도 0~1024니깐 int 정도면 되겠죠. 뭔가 복잡한 실수 계산이 들어가면 float 실수형으로 선언하면 되겠죠.

간단히, 작은 정수는 byte, 큰 정수면 int or long , 실수면 float or double 형으로 선택하셔서 자료형을 선언한다고 머리속에 넣어두세요.

그외,

boolean D = true; //참거짓에 사용되는 자료형
byte E =1;  //1byte 크기의 자료형
long F = 123; //int자료형과 같은데 그릇의 크기가 4byte으로 int형보다 더 크다.
double G =123,23; //float형보다 더 큰 그릇인데 아두이노에서 4byte 크기로 동일하게 나오네요. 

결론, 변수는 값을 담는 그릇이라고 생각하시면 됩니다.

[ 코딩 ]

void setup()
{
  Serial.begin(9600);

  char A = 'a';
  int B =123;
  float C =123.23;

  Serial.println(A);
  Serial.println(B);
  Serial.println(C);  

}
void loop()
{  
}

결과만 보기 위해서 처음 한번만 수행하는 setup()함수에 코딩했습니다. 각 자료형 별 변수들에 저장된 값들을 시리얼모니터로 출력을 하면 아래와 같습니다.


변수에 값을 저장할 수 있고 그 값을 이제 시리얼모니터에 출력할 수 있게 되었습니다. 여기까지 따라오셨나요.

2. 사칙연산 코딩


프로그램 코딩은 수학과 아주 밀접한 관계가 있습니다. 덧셈을 하나 생각해 볼까요.

예) 임의의 두 수 a, b를 더하는 식을 만드세요.

S = a + b

위 식은 초등학교 기초 수학시간에 배웠을 꺼에요. 이 표현은 코딩에서는 어떻게 표현되냐면 변경없이 그대로 적용됩니다.

S = a + b;

이렇게 세미콜론(;)만 뒤에 붙이고 그대로 코딩하시면 됩니다. 바뀐건 세미콜론(;) 하나 추가된 것 밖에 없죠. 어떻게 보면 코딩은 우리가 처리하고자 하는 명령들을 수학공식과 같은 형식으로 식을 만들고 그 식을 그대로 코딩하는 거라고 생각하시면 쉬울꺼에요.

그러면, 수학 사칙연산 프로그램을 만들어 볼까요.


사칙연산 표현을 그대로 그냥 동일하게 표현 하시면 됩니다. 덧셈, 뺄셈, 곱셈, 나눗셈을 하고 그 값을 저장하는 변수만 선언해주면 됩니다.

void setup()
{
  Serial.begin(9600);

  int A = 10;
  int B = 2;
  int addAB = 0;
  int subAB = 0;
  int mulAB = 0;
  int divAB = 0;
  
  addAB = A + B;
  subAB = A - B;
  mulAB = A * B;
  divAB = A / B;
  
  Serial.println(addAB);
  Serial.println(subAB);
  Serial.println(mulAB);  
  Serial.println(divAB);  

}

addAB, subAB, mulAB, divAB라는 4개의 저장변수를 선언했고 해당 식을 수행하면 아래과 같은 결과가 시리얼 모니터로 출력됩니다.


10+2 = 12
10-2 = 8
10*2 = 20
10/2 = 5 

여기까지 따라 오셨나요.

3. 삼각형의 넓이 구하는 프로그램을 만들자



  • 공식 : S= 1/2ah

수학시간에 배우셨죠. 삼각형의 넓이 공식을 떠올려 보세요. 위 공식이 프로그램 전부입니다. 이걸 코딩을 하면

S= (float)1/2*a*h;

세미콜론(;)만 붙이고 곱셈부분은 곱셈기호(*)를 붙여 줍니다. 그리고 계산된 값이 실수 형태로 저장될려면 (float)을 붙여줘야 합니다. 그 이유는 정수/정수는 결과가 정수가 되기 때문에 실수형으로 저장하기 위해서는 이 문장을 표현해야 합니다. 그냥 단순한 수학 공식인데 세미콜론(;)을 붙이니깐 프로그램 로직 명령어가 되었네요. 이렇게 수학적인 표현을 하는게 코딩입니다. 이건 순수 수학의 표현이고요. 이런 의미로 일상의 표현 특정한 동작의 표현을 공식으로 만들고 그걸 그대로 코딩하면 프로그램 코딩이 됩니다. 어렵게 생각하실 필요가 없어요.

실제 돌아가는걸 확인해 볼까요. 공식을 보면 필요한 변수가 3개 입니다. 밑변, 높이, 삼각형넓이 변수가 필요합니다.

void setup()
{
  Serial.begin(9600);

  int a = 10;
  int h = 5;
  float S =0;
    
    //삼각형 넓이 공식
    S = (float) 1/2*a*h;
    
  Serial.print("base : ");
  Serial.println(a);
  Serial.print("height  : ");
  Serial.println(h);
  Serial.println("Area of triangles : ");  
  Serial.println(S);  

}


[ 결과 ]


밑변(a)와 높이(h)의 값만 바꾸면 모든 삼각형의 넓이를 구할 수 있는 프로그램을 완성했습니다. 그렇게 어렵지 않죠. 이렇게 식을 만들고 그 식을 그대로 코딩하면 그게 프로그램이 됩니다. 이 의미만 오늘 기억하시면 됩니다.

(float) 1/2 * a * b

이부분이 약간 햇갈릴 수 있기 때문에 다시 설명합니다. C언어에서는 정수/정수는 정수가 출력됩니다. 즉, 실수값이 나와도 그 값은 정수형으로 나와버린다는 것이죠. 실수값을 얻기 위해서는 다음과 같은 표현으로 식을 만들어야 합니다.

정수/실수 or 실수/정수 or (flaot) 정수/정수

이렇게 세가지 표현 중에서 하나를 선택해서 표현 하시면 됩니다. 입력값이 a, h가 정수임으로 위 나눗셈이 들어간 식에서는 저장변수가 실수변수여서 식 앞에 (float)형으로 선언하게 된 것이죠. 이 표현은 해당 자료형으로 저장하겠되는 의미입니다.

여러분들도 한번 생각 나는 수학공식을 제가 코딩했던 스타일로 표현을 해 보세요. 위에서 사칙연산을 수행한 계산 값을 출력했는데 뭘 계산한지 모르잖아요 보기 좋게 하기 위해서 이번에는 print(), println()함수로 어떤 값들을 계산했고 결과가 뭔지 출력문에 담았습니다. 이런 표현들을 꼭 숙지하시고 이해해 주세요.

이렇게 해서 대충 변수를 사용하는 방식을 알아 봤습니다.

4. 코딩의 과정



  • 입력단계 : 변수를 선언해서 그 값을 직접 입력하거나 아니면 외부로 부터 값을 입력을 받는다.
  • 처리단계 : 입력된 값을 위에서 수행한 사칙연산과 같은 원하는 동작의 계산식을 만든다.
  • 출력단계 : 결과를 외부로 보내거나 특정한 위치에 출력한다.

무조건 이 과정으로 코딩을 한다는 점 꼭 기억해 주세요. 그러면 아두이노를 생각해 볼까요. 예를 든다면 아두이노에서 센서의 값을 읽으면 입력부분이 되고 그 입력된 센서의 값을 특정한 동작을 수행하도록 하는 표현식(계산식)이 처리단계가 됩니다. 그 결과를 실질적으로 보여지는게 출력 단계입니다.

여러분들이 이제 코딩을 하게 되면 "입력 ->처리->출력" 이걸 머리속에 꼭 담고 코딩를 해주세요.

어떤 값을 받을 것인지!
그 값을 받았으면 어떻게 처리할 것인지!
최종적으로 어떻게 보여질 것인지!

이 개념을 머리속에 담고 코딩을 전개해 나가시면 됩니다.

마무리


오늘은 변수의 자료형인 문자형, 정수형, 실수형 표현을 배웠으며 그 변수를 이용하여 수학공식을 프로그램 코딩을 해 보았습니다. 그리고 마지막으로 코딩은 어떤식으로 이루어지는지 그 과정을 간단히 살펴보았습니다.

프로그램 언어를 배우면 젤 먼저 사칙연산을 코딩하게 됩니다. 그리고 더 관심이 생기면 이 원리를 이용해서 공업용 계산기를 만들기도 합니다. 저도 처음에 사칙연산을 배우고 나서 젤 먼저 공업용 계산기를 코딩을 해 봤고 더 나아가 전자 회로로 계산기도 응용해서 표현을 해 봤네요. 뭔가 원리를 배우면 이걸 어디에다 써먹을까 상상의 나래를 잘 펼치거든요. 여러분들도 오늘 사칙연산과 삼각형의 넓이 구하는 식을 코딩으로 배웠습니다.
여러분들은 이걸 토대로 상상의 나래를 펼쳐보세요. 난 이 원리로 뭘 만들어 볼까하고 계속 상상을 하셔야 응용력이 생겨 새로운것을 만들 수 있게 됩니다.

더 내용을 추가 할 부분이 있었지만 너무 많이 설명하고 깊게 들어가면 오히려 역효과가 날 것 같아서 이부분은 이정도로 마무리 하겠습니다.

프로그램 코딩을 너무 어렵게 생각하지 마시고 그냥 수학식을 하나 만든다고 생각하시고 그 식을 그대로 코딩하면 그게 코딩이 되니깐 어렵게 생각 안하셔도 돼요.

댓글()

[아두이노] 아두이노 코딩 입문

IOT/아두이노기초문법|2019. 4. 9. 09:00

[아두이노] 아두이노 코딩 입문



오늘은 간단히 코딩에 대해 포스팅을 하겠습니다. 사실 비전공자분들은 코딩이 약간 외계어처럼 느껴지시는 분들이 있는 것 같아서 원래는 이런 내용을 초창기 포스트에서 소개 했었야 했는데 늦게 포스팅을 하게 되었네요. 아두이노 코딩을 할때 알아야 기초적인 문법만 포스팅을 하도록 하겠습니다. 이전에 포스팅한 내용들이 일부 중복되지만 그래요 한번에 기초적인 문법을 묶어서 설명하는게 나을 것 같아서 몇일간 코딩에 관련해서 포스팅을 하도록 하겠습니다.

1. 코딩의 준비


[ 아두이노 ]

[ 동작 ]


아두이노에 전원이 공급되면 젤 먼저 setup()함수가 한번 수행되고 그다음 loop()함수가 반복 수행됩니다. 간단히 아두이노에 전류가 공급되면 setup()함수를 수행후 loop()함수가 반복 호출된다는 정도만 머리속에 넣어 두세요.

2. "Hello World" 출력하기


[ 메인창 ]



아두이노를 편집창으로 드래그 하고 나면 오른쪽 항목에 Code 클릭을 해주세요.



기본 베이스 블록을 위 그림처럼 휴지통이나 블록이 있는 쪽으로 드래그 하면 블록은 삭제됩니다.

[ 블록 ]



시리얼통신 블록 :



오른쪽 항목에 Code가 있는데 그걸 선택하면 위 그림처럼 세가지 방식을 선택 할 수 있습니다. 여기서 두번째 줄에 있는 Bloocks+Text를 선택하십시오. 그리고 Output 항목에서 아래 print 블록을 오른쪽으로 드래그 해주세요. Output는 파란색 블록에 있습니다. 그러면 아래와 같은 코딩이 같이 오른쪽에 text창에 나타납니다. print 블록은 Serial 블록이여서 Serial 관련 기본 세팅이 코딩으로 보여집니다. 사실 블록으로 간단히 표현하면 코딩을 구지 할 필요 없습니다. 하지만 블록을 사용하시면 표현하실 때 약간 제약을 받기 때문에 제 경우는 블록보다 Text 코딩을 선호 합니다.

블록 하나를 배치했을때 아래 코딩이 자동으로 생성됩니다.

[ 코딩 ]

void setup()
{
  Serial.begin(9600); //시리얼통신 시작

}

void loop()
{
  Serial.println("hello world"); //시리얼모니터로 출력
  delay(10); 
}

실행을 하기 위해서 아두이노 IDE 시뮬레이터 창을 띄워야겠죠.


위 그림을 보시면 Serial Monitor를 누르면 모니터창이 나타납니다.

시뮬레이터 실행을 누르시면


"hello world"가 계속 출력됩니다.

println("내용");

위 명령문으로 내용을 시리얼모니터로 출력하라는 명령입니다. 이제 C문법을 배울때 그 결과를 이 함수를 사용하여 출력할 예정입니다.

3. Text 모드로 변경


현재상태에서 Text모드로 바꾸시면 아래와 같이 블록만 없어지고 Serial 출력코딩은 그대로 남습니다. 그러면 loo()함수안에 있는 명령들을 아래 그림처럼 setup()함수로 옮겨주세요.


그리고 실행을 누르면 다음과 같이 딱 한번만 수행합니다.


setup()함수와 loop()함수의 차이점을 구별하셨나요. loop() "hello World"문장을 계속 출력하지만 setup()함수는 딱한번 출력하는 것을 확인 하실 수 있을거에요.

여기까지해서 기본 세팅은 끝났네요. 아두이노를 이용해서 "hello World"란 문장으로 시리얼모니터에 출력할 수 있게 되었습니다. 이제 코딩을 하면 그결과를 시리얼모니터로 출력할 수 있겠죠. 여기까지 따라 오셨으면 코딩의 절반을 배운 거나 다름 없습니다.

이제는 코딩 문법을 하나씩 기초 위주로 배워보도록 하겠습니다. 사실 C언어를 C컴파일를 사용해서 C공부를 하면 좋지만 아두이노 환경에서 아두이노와 친근감을 가지기 위해서 아두이노 상에서 프로그램 언어 공부를 배워 보도록 하겠습니다.

4. 시리얼모니터 출력 함수


시리얼모니터에 출력하는 함수가 print(), println(), write()가 있습니다. write()함수를 제외한 print(), println()함수만 주로 사용할 예정입니다. 그래서 이 둘의 차이점만 구별해 주세요.

  • print("내용") : 해당 내용을 출력하고 커서의 위치는 출력된 해당 라인에 계속 머물어 있게 됩니다.
  • println("내용") : 해당 내용을 출력하고 커서의 위치는 새로운 라인으로 이동하게 됩니다.

print()함수의 예제



위 그림을 보시면 두개의 print()함수를 사용했습니다. 첫번째 "hello "문장을 출력하고 커서는 현재라인에 머물어 있기 때문에 다음 "world"라는 문장이 이여서 출력됩니다. 그런데 또 print()함수를 사용한다면 계속 현재라인에서 이여서 출력되겠죠. 그래서 마지막 출력문에는 println()함수를 사용해서 출력라인의 끝을 표현해야 합니다.

println()함수의 예제



위 그림을 보시면 두개의 println()함수를 사용했습니다. 첫번째 "hello "문장을 출력하고 커서는 새로운 라인으로 이동한다고 했죠. 그래서 "world"문장이 새로운 라인에 출력됩니다. 여기서 두번째 함수도 println()함수임으로 3번째 새로운 라인으로 커서가 옮겨져 있겠죠.

출력함수 올바름 사용



위 그림처럼 두이상의 여러개 내용을 한 라인에 모두 출력을 해야 할경우는 print()문으로 표현하다가 마지막 내용에서는 println()으로 표현해서 커서를 옮겨주는 코딩을 하셔야 합니다.

참고로, "내용"에서 안에 공백문자도 인식을 하니깐 공백(스페이스바) 효과도 넣어 띄워쓰기를 할 수 있습니다.

이 차이점만 이해하시고 출발하시면 되겠습니다.

추가 주의사항

세미클론(;) 이라는 기호가 있습니다. 명령문의 끝을 나타내는 기호로 편하게 명령어 수행하라 정도로만 이해해 두세요. 명령문에는 무조건 세미클론이 붙습니다. 이게 생략되면 에러가 발생하니깐 꼭 기억해 주세요.

'{' 와 '}' 괄호는 함수에는 함수의 영역을 표시하는 기호입니다. setup()함수가 수행하면 이 괄호가 시작하는데 부터 괄호가 끝나는 데까지의 내용을 기계가 읽고 수행합니다. 간단히 "괄호는 영역이다!" 정도로 이해하시면 되겠습니다.

마무리


오늘은 코딩의 첫 관문인 시리얼모니터에 명령한 결과를 출력해보는 시간으로 채웠습니다. 이정도만 이해하시면 프로그램언어를 절반정도 이해하셨다고 보면 될꺼에요.
처음하시는분들은 어색할 수 있지만 setup(), loop()함수가 있는데 전원이 공급되면 setup()이 젤 먼저 호출되어 수행한 뒤에 loop()함수가 그 다음에 반복해서 호출 수행한다 정도정도만 이해하세요.
그리고, 오늘 소개한 Serial.print(), Serial.println()함수만 이해하시면 프로그램언어 공부 절반을 배운것이니깐 꼭 원리를 기억해 주세요. 출력함수 하나만 이해해도 프로그램언어를 절반 배운거에요.

원래는 내용이 더 있는데 너무 길게 빼면 그렇 것 같아서 여기서 마무리 합니다.



댓글()

[아두이노] LED를 통한 전류의 흐름 이해

IOT/아두이노|2019. 4. 8. 09:00

[아두이노] LED를 통한 전류의 흐름 이해



오늘은 코딩이 없는 간단한 LED 제어를 통해 전류의 흐름을 배우는 시간을 갖도록 하겠습니다. 실험할 회로도는 Tinkercad에서 제공하는 RGB LED 예제를 기반으로 하겠습니다. Tinkercad 예제를 우연히 보다가 공식적인 실험 예제를 발견하고 이 예제로 실험하면 전류의 흐름을 좀 더 쉽게 이해하는 할 수 있을 것 같아 이번 포스팅 주제로 결정을 했네요.

1. RGB LED 스위치



위 사진은 RGB LED를 사용해야 하는데 Blue LED가 없어서 그 위치에 어쩔 수 없이 Yellow LED를 배치했네요. 스위치를 누르면 해당 Color Led에 불이 들어오고 3색 LED에 해당 Color가 출력되는 회로도입니다. 두개 이상을 누르면 두가지 Color가 혼합된 Color로 3색 LED에 불이 들어오게 됩니다. Blue LED가 없어서 Yellow LED에 불이 들어오지만 해당 위치는 Blue이기 때문에 3색 LED에는 Blue가 출력되도록 세팅 했네요.

2. RGB LED


1) Tinkercad의 공식 RGB LED 회로도

출처 : Tinkercad 예제

해당 회로도에서는 9V의 건전지를 사용했더군요. 그러면 LED의 저항은 9V에 맞게 다시 계산되어야 합니다. "V=IR" 공식에서 R의 저항값을 다시 계산기로 계산해야 하는데 귀찮아서 회로도의 저항 속성창을 통해 저항을 확인해보니 480옴이더군요.

위 회로도는 뭘 표현한건지 대충 아니깐 직접 안보고 만들어 보도록 하겠습니다. 혹시 원리를 모르시는 분들을 위해 간단히 설명을 하면 스위치 버턴을 누르면 해당 스위치에 연결된 선을 전류가 흐릅니다. 여기서 Red, Blue, Green Led가 각 스위치마다 연결되어 있습니다. 해당 스위치를 누르면 각 color의 Led에 불이 들어옵니다. 여기까지 이해하셨으면 여기서 스위치에 선이 추가되어 3색 LED에 연결하면 어떻게 될까요. Red Led에 연결된 선이 3색 LED의 Red핀에 연결시키고 Blue Led에 연결된 선은 Blue핀에 연결하고 Green Led에 연결된 선을 Green 핀에 연결시켜 놓으면 해당 스위치를 누르면 개별 LED와 3색 LED에 해당된 color가 불이 들어오겠죠. 3색 LED에 해당 RGB핀이 아래 Red, Blue, Green LED랑 한몸이 되어서 같이 동작하는 회로도입니다.

재밌는것은 가상시뮬에이터에서 할 수 없지만 두개 이상의 스위치를 누르면 색이 3색 LED에 혼합된 Color로 출력됩니다. 즉, Red 스위치랑 Blue 스위치를 누르면 해당 LED불이 들어오고 3색 LED에는 Red+Blue가 혼합된 Color가 출력됩니다. Color를 공부하는 아이들에게 좋은 학습자료가 될 수 있겠죠.

참고로 여기서는 스위치로 표현되었지만 가변저항기를 스위치 자리에 교체하면 가변저항기를 돌릴때마다 Color 값을 조절 할 수 있겠죠. 그러면 더 많은 Color를 만들어내는 장치가 될 수 있습니다.

2) 직접 만들기


  • 준비물 : 9V 건전지, 저항 480옴 4개, Red, Blue, Green 각각 1개, 3색 LED 1개, 스위치 3개
  • 내용 : RGB LED 스위치

스위치를 이용해 RGB LED를 제어했구나 하고 대충 몇초만 보고 쉬운 예제이니깐 그 의미에 맞게 동일하게 만들어 봤네요


썩 마음에 들게 만들지 못했네요.

3. 전류의 흐름


이제는 전류가 어떤식으로 흘러가서 LED에 불이 들어오는지 살펴보도록 하겠습니다.


위 그림처럼 Led에 전류를 공급하면 불이 들어옵니다. 전류는 '+'에서 '-'로 이동 합니다.


그런데 Vcc쪽에 스위치를 달게 되면 초기 상태는 끊어진 상태로 전류가 흐리지 않습니다. 여기서, 스위치를 누르면 스위치 내부에 선이 서로 연결되어서 Vcc(+) 전류가 Led에 공급되면서 Led에 불이 들어오게 됩니다. 우리가 일상에서 흔히 형광등을 켤때 벽에 스위치가 붙어 있잖아요. 그걸 상상하시면 됩니다.


위의 그림처럼 3개의 Led를 연결했을 때 각 스위치 중에 누른 스위치는 전류가 공급되고 해당 Led에 불이 들어 옵니다. 이부분을 잘 기억해 두세요. Vcc(+)와 Gnd(-)의 선이 어떻게 연결되었고 여기서 해당 선에 전류가 흐르게 되면 어느 방향으로 전류가 흘러갈지를 머리속에서 잘 그려주세요.


위 그림에서 빨간선이 전류의 진행 방향입니다. Green 스위치를 누르게 되면 전류의 진행 방향에 따라서 흐르게 되고 Green Led에 불이 들어오게 됩니다.



위 그림을 보시면 3색 LED만 추가 되면은 Green Led와 3색 LED로 두갈래로 전류가 공급되고 Green Color의 불이 들어오게 됩니다. 대충 어떻게 전류가 흘러가는지 아시겠지요.

4. 두개의 스위치를 누른 효과 만들기


이 예제는 한개의 스위치 뿐이 누를 수 없습니다. 가상에서도 두개의 스위치를 누르고 싶은 욕망이 생기더군요. 오늘 이야기 하고 싶은 포스트 내용으로 들어가겠습니다. 이 걸 하기 위한 연습단계였네요. 어떻게 하면 두개의 스위치를 누른 효과를 만들 수 있을까요. 임의의 스위치를 만들어서 2개의 LED에 불이 들어오게 전류를 공급하면 됩니다.


위 그림처럼 새로운 스위치를 만들어서 Blue, Green Led 선에 연결을 했을 경우에 스위치를 누르면 2개의 Color에 불이 들어오고 위 3색 LED에 혼합된 색이 출력됩니다. 정상적으로 동작하는 것처럼 보이지만 전류의 흐름에 문제가 있습니다. 과연 뭘까요. 처음의 3개의 스위치를 눌러보세요. Green 스위치를 누르게 되면 Green, Blue에 불이 들어오게 됩니다.


위 그림에서 빨간원을 잘 보시기 바랍니다. Green 스위치를 누르면 해단 선에 전류가 공급되는데 빨간 원모양은 새로운 스위치에 purple 선으로 전류가 흘러가게 되고 purple에 연결된 Blue Led에도 전류가 공급되는 현상이 발생하게 됩니다. 결론은 Blue, Green은 서로 연결되었다는 의미가 됩니다. 그래서 어느 스위치를 누르 든 2개의 LED에 불이 들어오는 현상이 발생합니다.

이 문제를 어떻게 해결 할까요. 전자부품에는 다이오드라는 게 있습니다. 전류를 특정 방향으로만 흘러가도록 하는 부품인데 역전류 현상을 막는 부품이기도 합니다. 아래 그림에서 보는 것처럼 한쪽으로만 진행되고 마이너스 쪽에서 전류가 들어온다면 +쪽으로 전류가 흐르지 못하도록 차단하는 부품입니다.


이 부품을 연결을 아래와 같이 표현하면 Green 스위치를 누를 경우 새로 만든 스위치로 전류가 흘러가지 않게 다이오드가 차단 합니다.


참 재밌는 부품이지요. Green 스위치를 누른 결과인데 Green 스위치를 누르면 새로 만든 pulple선에 연결된 다이오드에 의해서 전류가 흘러가지 못하게 차단이 됩니다. Blue스위치를 눌러도 마찬가지이고요.

회로도 재구성


  • 준비물 : 9V 건전지, 저항 480옴 4개, Red, Blue, Green 각각 1개, 3색 LED 1개, 스위치 7개, 다이오드 9개
  • 내용 : RGB LED 스위치


세개의 Color가 동시에 눌러 졌을 때 상황을 표현한 스위치를 한개 더 추가했습니다. 그리고 그 결과는 위 그림에서 화살표가 가리키는 스위치를 눌렀을 때의 결과 이미지 입니다. 좀 지져분하게 되었지만 위에서 다이오드 연결한 것처럼 추가로 몇개만 더 만드시면 됩니다. 보기만 지져분할 뿐 동작은 잘 됩니다. 위에 공개 회로도로 링크 걸어놓았으니깐 가셔서 시뮬레이터를 돌려 보세요.

다른 방법으로 제어하는 방법도 있겠지만 순간 떠오르는 방법이고 다이오드를 소개하기 딱 좋을 아이템이라고 생각하여 이렇게 표현 했네요. 전류의 흐름을 잘 이해하셨으면 합니다.

5. 실제로 구현


여기서 끝나면 재미 없어서 한번 실제로 만들어 봤네요. 전원부분은 건전지를 사기가 싫어서 아두이노 5V 전원공급으로 처리했습니다. 그래서 저항은 그냥 220옴으로 수정 되었네요. 참고로 실제 구현에서는 스위치를 동시에 누를 수 있기 때문에 구지 복잡한 다이오드 연결을 하고 새로운 스위치를 만들 필요는 없었습니다.

1) 회로도 구성


  • 준비물 : 저항 220옴 4개, Red, Blue, Green 각각 1개, 3색 LED 1개, 스위치 3개, 아두이노우노, 뻥판
  • 내용 : RGB LED 스위치


위 회로도는 깔금한데 실제 구현은 선때문에 보기 지져분합니다. 실제 모습을 포스트 첫 이미지 사진을 보시면 되겠습니다. 좀 지져분하여 이해를 돕기 위해서 가상 시뮬레이터로 먼저 표현을 했네요. 이것도 위에 공개회로도에 링크를 걸어 놓았으니깐 시뮬레이터를 돌려 보세요.

2) 결과

실제 실험 영상에서는 Blue Led가 없어서 해당 위치에 Yellow Led를 넣었네요. 참고로 Yellow LED이지만 3색 LED에서는 Blue로 정확히 출력됩니다. 감안하시고 보세요.


마무리


쓰다보니깐 간단히 포스팅 할려다가 또 길어졌네요. 짧게 표현하는 능력이 부족해서 글이 좀 길어지는 경향이 있네요. 암튼 이번 실험을 통해서 전류가 어떻게 흘러가는지 이해하는 시간이 되었으면 합니다.

마무리는 깔끔하게 여기서 마칩니다.


댓글()

[아두이노] 사운드센서로 그래픽 이퀄라이저 표현

IOT/아두이노|2019. 4. 7. 09:00

[아두이노] 사운드센서로 그래픽 이퀄라이저 표현



어제 마이크사운드 감지센서를 통해서 센서의 값의 초기값 조정하는 것과 읽는 법을 배웠습니다. 오늘은 마이크사운드 감시센서을 이용하여 소리를 NeoPixel로 출력하는 실험을 해보도록 하겠습니다. "아두이노 이퀄라이저" 키워드로 찾으시면 화려한 표현들을 많이 보실 수 있을거에요. 오늘 포스팅은 그런 화려한 표현이 아닌 간단히 어떻게 소리를 NeoPixel로 표현을 하는지에 대한 원리를 이해하는 시간으로 채우겠습니다. 화려한 그래픽 이퀄라이저를 상상했다면 아마 실망하실지 모르겠군요.

1. 마이크사운드 감지센서와 3색LED




3색 LED에 대한 설명은 아래 링크된 포스트로 가셔서 잠깐 살펴보세요.
[아두이노] 3색 LED 제어



마이크사운드 감지센서는 어제 포스트인 아래 링크로 다시 복습를 해주세요.
[아두이노] 마이크사운드 감지센서 제어(아두이노)

입력은 마이크르사운드 감지센서를 사용하고 출력은 NeoPixel이 없는 관계로 비슷한 3색 LED를 이용하여 실험이 이루어집니다.

2. 회로도 구성


  • 준비물 : 마이크사운드 감지센서 1개, 3색 LED 1개, 저항 220옴 3개, 아두이노우노
  • 내용 : 사운센서랑 3색 LED를 사용할 핀을 원하는 위치에 배치해 보자.

가상회로도 이미지에서 저항은 실수 1k옴으로 그려졌네요. 혼동하시면 안되고 정상적으로 220옴을 실제로 사용하셔야 합니다. 이미지의 저항색깔을 보시고 혼동하시면 안됩니다.


RGB 색상핀은 아날로그 출력을 해야하기 때문에 PWM핀들 중에 9,10,11번 핀을 선택했습니다. 그리고 마이크사운드 감지센서의 값을 읽는 아날로그 핀은 이전시간과 동일한 A0핀을 선택했습니다.

3. 코딩


  • 사용함수 : analogRead(), PinMode(), analogWrite(), map(), randomSeed(), random()
  • 내용 : 마이크사운드 감지센서의 소리의 값을 기준으로 RGB 색상을 만들어 그래픽 이퀄라이저 효과를 표현하자.

[ 기본 소스 ] : 마이크사운드 감지센서를 이용한 소리의 최소값과 최대값 출력

int m_min = 26;
int m_max = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  int SoundSensor =analogRead(A0);
    if(SoundSensor<m_min) m_min=SoundSensor;
    if(SoundSensor>m_max) m_max=SoundSensor;
    
    Serial.print(m_min);    
    Serial.print("  :  ");
  Serial.println(m_max);    
  delay(50); 
}

이전 시간에 실험한 소스입니다. 이 소스를 기반으로 접근해 보겠습니다.

우선 RGB핀을 사용하기 때문에 RGB핀 변수를 만들어야 겠죠.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

그 다음에는 RGB핀들은 출력모드입니다. 그러면 setup()함수에서 "출력모드로 사용할꺼야!" 하고 선언해야겠죠.

void setup()
{
  Serial.begin(9600);
 
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

이제 소리를 어떤 기준으로 해서 색을 만들면 좋을지 생각해보세요. 마이크사운드 감지센서의 아날로그 신호 0~1023 값을 아두이노가 읽는다고 했죠. 그러면 0~1023값을 쪼개서 색으로 나타내면 되겠다고 처음에 다들 여기서 부터 출발합니다. 여기서, RGB핀 출력은 PWM핀으로 아날로그 0~255 출력을 낼 수 있습니다. 0(0V)이고 255(5V)입니다. 그러면 마이크사운드 감지센서에서 읽은 0~1023 값을 아날로그 출력 0~255값에 매핑 시켜서 출력시키면 되겠다고 생각 하실꺼에요.

 int SoundSenser =analogRead(A0);
 int SoundColor = map(SoundSenser,0,1023,0,255);

map()함수는 다시 복습을 하면

  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력최소~최대범위에서 입력값이 출력 최소~최대범위에 어느정도 위치인지 그 출력위치값을 반환합니다.

예) map(입력값,0,100,0,10) 이면, 입력값이 10일때 출력값는 1이 됩니다. 입력값이 20일때 출력값은 2가 출력됩니다. 즉, 이말은 입력범위를 출력범위에 맞춰서 출력된다고 생각하시면 됩니다. 일정한 간격 비율에 맞춰서 출력값에 매칭 된다고 생각하면 될듯요. 0~10사이의 값은 1로 매칭되고 11~20사이의 값은 2로 매칭이 되고 이렇게 생각하시면 될 듯 하네요. 입력범위에 대한 입력값의 위치가 출력범위에서 어느정도의 위치가 출력위치인지 그 출력위치의 값을 반환해주는 함수라고 생각하시면 됩니다.

 Serial.println(SoundSenser);
 analogWrite(RedPin,random(SoundColor));
 analogWrite(GreenPin,random(SoundColor));
 analogWrite(BluePin,random(SoundColor));

우선 소리값을 그냥 color 값으로 출력한다면 소리값에 0~255사의 값이 RGB 색의 출력된 값이 되고 출력명령은 analogWrite()함수로 color를 출력하게 됩니다. 이렇게 하면 코딩이 끝났다고 생각하실꺼에요. 한번 돌려보세요 어떤 현상이 발생하는지요. 마이크사운드센서에 소리를 입력하기도 전에 3색 LED를 막 깜박일꺼에요.

그 이유가 뭘까요. 지난시간에 공부하셨으면 쉽게 찾을 수 있을꺼에요. 지난시간에 마이크사운드 감지센서는 초기값으로 일정한 전류가 발생한다고 했죠. 같은 종류라도 센서라도 초기의 센서에 흐르는 전류의 초기값은 제각각 입니다. 제가 사용하는 Sensor는 처음 46이 초기값이였고, 가변저항기를 돌려서 26정도로 맞췄습니다. 여기서 아무런 소리를 입력하기 전 초기 상태에서 이미 소리값이 일정 수치가 읽어지니깐 자연히 그 초기값의 색이 RGB LED에 출력이 되는 것이죠 제 경우 초기값이 26이라면 26에 대한 색이 출력되더군요.

처음 마이크 상태는 0에서 출발해야 하는데 그렇게 안되겠죠. 그럼 그렇게 0에서 출발하도록 만들어줘야 합니다. 바로 map()함수를 수정하시면 됩니다.

 int SoundColor = map(SoundSenser,26,1023,0,255);

입력 최소값을 26으로 잡아주면 간단히 해결됩니다. 그런데 두번째 문제가 발생합니다. 과연 뭘까요. 어제 포스트를 읽어봤다면 찾을 수 있을꺼에요. 소리가 입력되고 다시 입력이 안되면 전기신호를 감소합니다. 중요한 것은 소리가 입력될때는 전기신호가 커졌다가 입력이 안될때는 전기신호가 작아지는데 초기값보다 마이너스(-)로 빠져버리는 현상이 발생한다고 했죠. 그러면 어떻게 될까요. 그 이상한 값에 의해서 색이 출력되어버립니다. 최소값 문제가 발생합니다.

그걸 해결하기 위해서 어제 최소값 구하는 로직을 이용할꺼에요.

if(SoundSensor<26) SoundSensor=26;

만약에 26보다 작은 전기신호가 온다면 26에 수렴되게 만들면 됩니다. 그리고 또 어떤 문제가 발생할까요. 아두이노에서 사용되는 마이크사운드 감지센서를 아주 싸고 질이 떨어집니다. 결론은 소리감지에 좀 문제가 있습니다. 어느정도 큰소리라고 생각했지만 측정되는 수치는 그렇게 높지 않습니다. 거의 마이크를 두둘겨야 비로소 일정 수치까지 올라갑니다. 말하자면 대부분의 소리가 낮은 전기신호값을 가지고 있으면 일부 소리만 큰전기신호값으로 입력으로 들어옵니다. 결론은 거의 동작을 안하는 것처럼 보이고 색상도 아주 낮게 보입니다. 그래픽 이퀄라이저 효과를 뚜렷하게 표현하기 어렵다는 것이죠.

해결책으로는 낮은 값을 기준으로 색의 값을 재배열 하면 됩니다.

if(SoundSensor>300) SoundSensor=300;

이렇게 300이상의 소리는 그냥 300에 수렴하게 만들면 됩니다. 대충 아래 그래프처럼 입력값의 범위를 강제적으로 정하면 됩니다. 가로축는 마이크사운드 감지센서의 측정값이고 세로축은 소리입력허용 수치입니다. 26~300사이의 값으로 소리입력범위가 잡으시면 됩니다.


그러면, map()함수는 어떻게 될까요.

 int SoundColor = map(SoundSenser,26,300,0,255);

이렇게 해서 26~300사이의 값을 입력범위로 해서 0~255의 색상값으로 출력하겠다고 표현하시면 됩니다.
여기서, 어제 배웠던 최소값과 최대값 구한 로직을 기본소스로 우선 보여준 이유가 이 로직으로 이런 2가지의 문제를 해결하기 위해서 입니다.

이렇게하면 소리에 대한 밝기만 표현되기 때문에 그래픽 이퀄라이저라고 보기 어렵습니다. 그래서 밝기와 더불어 다양한 색을 만들어보는 시도를 해보겠습니다. 그 방법으로 random()함수를 사용하겠습니다.

void setup()
{
  randomSeed(analogRead(A1));
}
void loop(){
    random(SoundColor);
}

A0은 마이크사운드 감지센서로 사용되니깐 A1은 랜덤함수 초기값으로 세팅하겠습니다. 여기서 아날로그핀값을 읽어오기만 하는게 난수초기세팅인 이유는 뭘까요. 그 이유는 아두이노는 핀마다 미세한 전류가 자체적으로 흐르고 있습니다. 사용을 안하더라고 핀마다 작게 전류가 흘러서 그 전류는 미세하지만 랜덤한 전류의 양을 가지고 있어서 그 값을 기준으로 난수를 세팅하면 랜덤값을 추출할 수 있습니다.
그리고 random(255)함수면 255이 max값으로 임의의 난수를 만들어 냅니다. 이 값을 RGB 마다 random()함수로 색의 난수값을 만들어내면 소리에 대한 다양한 색을 만들 수 있겠죠.

종합해보면.

const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

4. 결과



음악으로 실험해야하는데 음악은 저작권 때문에 단지 손으로 마이크사운드 감지센서에 자극을 줘서 출력실험을 했네요. 보면은 터치센서인줄 착각할 수 있는 장면이네요.

참고로 위 소스대로 하시면 그렇게까지 그래픽 이퀄라이저 효과를 얻을 수 없습니다. 결함이 있는 소스입니다. 그 이유가 뭘까요. 바로 random()함수를 단순히 측정한 소리값을 max값으로 넣었기 때문입니다. 이렇게 표현하면은 소리에 따라 다양한 색을 만들어 내기는 합니다. 하지만 300의 소리가 입력되었어도 100이 나올수 있고 150의 소리가 입력되어도 100이 나올 수 있다는 것이죠. 이말은 색상을 일정한 패턴 범위에 맞추지 못했다는 결함적 소스입니다.

그런데 왜 수정을 안하고 여기서 멈추었냐면 이제부터서 여러분들이 수정해 나갈 차례입니다. 기본 베이스는 다 주어졌습니다. 소리의 초기상태를 읽을 수 있고 최소와 최대값을 정할 수 있게 되었고 map()으로 색의 값의 범위를 정할 수 있게 되었습니다. random()함수로 색을 랜덤하게 만들어 낼 수 있게 되었습니다. 여러분들은 이제 소리를 좀 더 디테일적으로 나누는 작업과 그 나누었을 때 색을 랜덤함수로 범위를 정할지 아니면 고정된 색값으로 하고 밝기만 변화를 줄지의 선택은 여러분의 몫입니다.

참고로, 이렇게 코딩을 해도 실제 돌려보면 색이 화려하지 않고 낮은 밝기와 색값으로 출력됩니다. 그것은 바로 마이크사운드 감지센서의 문제입니다. 좀 큰소리를 말해도 실질적으로 측정되는 수치는 그렇게 높지 않습니다. 그렇기 때문에 미세한 1의 변화에도 색의 밝기값을 좀 더 큰값 범위를 기준으로 해서 정해보시는 것을 추천드려요.

5. 가상시뮬레이터에서 느낌만 실험


그냥 실제로만 하면 뭔가 아쉬울 것 같아서 느낌만 살펴서 가상시뮬레이터로 실험해 해보세요. 위에 공개회로도에 링크 걸렸으니깐 돌려만 보세요.

1) 가변저항기와 3색 LED


const int RedPin = 9;
const int GreenPin = 10;
const int BluePin = 11;

void setup()
{
  Serial.begin(9600);
  randomSeed(analogRead(A1));
  pinMode(RedPin, OUTPUT); 
  pinMode(GreenPin, OUTPUT); 
  pinMode(BluePin, OUTPUT); 
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  
  Serial.println(SoundColor);
  analogWrite(RedPin,random(SoundColor));
  analogWrite(GreenPin,random(SoundColor));
  analogWrite(BluePin,random(SoundColor));
    
  delay(50); 
}

2) 가변저항기와 NeoPixel


#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, neopixelPin, NEO_GRB + NEO_KHZ800);
 
void setup()
{
  neopixel.begin();   
  randomSeed(analogRead(A1));
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  neopixel.setPixelColor(0, random(SoundColor), random(SoundColor), random(SoundColor));    
  neopixel.show();
  delay(50);    
  neopixel.clear(); 
}

3) 두가지 실험 결과

위 3색 LED와 NeoPixel을 가변저항기가 마이크사운드 감지센서라고 상상하고 실험한 영상입니다.


마무리


하다보니깐 길어졌네요. 원래는 추가적으로 NeoPixl 바 모양 향태로 음악을 들을때 사운드 그래픽 바 모양을 아실꺼에요. 그것도 추가 설명할려고 했는데 그건 못하게 되었네요. 내일 또 이여서 하면 너무 길게 연장 될 것 같아서 여러분들의 상상력으로 표현을 해보세요.

혹시, 사운드 그래픽 바를로 출력하고자 하고자 한다면 참고해주세요. NeoPixel을 예전 포스팅에 어떻게 표현을 했었죠. 배열 행태로 표현했었습니다. 그 배열을 사운드 그래픽 바의 위치라고 상상하고 소리의 크기에 따라서 배열로 몇번째 배열까지 불이 들어오게 할건지 그 간격만 정하시면 됩니다. 그리고 해당 NeoPixel의 색 값은 random()함수로 표현하시거나 고정값을 하셔도 됩니다. 쉽게 말해서, 소리가 1~10까지 있으면 3이라는 신호값이 발생하면은 NeoPixel를 3번째까지 불이 들어오게 하면 됩니다. 이렇게 소리의 크기 값에 neoPixel의 위치를 정하면 됩니다. 한번 연구를 해보세요.

오늘도 어떻게 표현하면 더 자연스럽고 화려할지를 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 마이크사운드 감지센서 제어

IOT/아두이노|2019. 4. 6. 09:00

[아두이노] 마이크사운드 감지센서 제어



오늘은 어제 설명한 Sensor 읽기에서 가상시뮬에이터에서 제공되지 않는 실제 Sensor를 다뤄보도록 하겠습니다. 실험에 사용되는 마이크사운드 감지센서는 D0, A0로 디지털신호와 아날로그신호를 출력하기 때문에 해당 핀에 맞는 함수를 사용하여 읽으시면 됩니다. 실험에서는 아날로그신호를 읽는 방식으로 진행되니깐 잘 참고를 해주세요.

1. 마이크사운드 감지센서



사진이 깔끔하게 나오지 않고 약간 좀 떨려서 찍혔네요. 좀 흐려도 자세히 보시면 대충 구분이 되실거라 믿고 진행하겠습니다. 보시면 파란색 나사이 있을꺼에요. 가변저항기로 전류의 출력을 조절하는 장치입니다. 어느쪽 방향인지 햇갈리는데 여기서 오른쪽으로 올리면 마이크센서의 초기 값이 작아지던가 아무튼 아두이노에 연결해서 Serial 통신으로 그 값을 찍어보시면 돌리는 방향에 따라 값이 커졌다 작아졌다 하니깐 초기값을 정하시면 됩니다.

그리고 오른쪽 핀 연결부분을 보시면 A0, G, +, D0으로 표시되어 있는데 A0은 아날로그 신호로 출력하는 거고, D0은 디지털 신호로 출력하는 핀입니다. +, Gnd은 전원부분으로 아두이노우노에 Vcc, Gnd로 연결하시면 되겠죠. 제가 사용하는 모델은 5V에 연결했네요 부품마도 허용치 Vcc가 다르니깐 구매 부품의 사용설명서를 참조하세요.

2. 마이크사운드 감지센서값 읽기


void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int SoundSensor =analogRead(A0);
  Serial.println(SoundSensor);    
  delay(50); 
}

대충 이 코딩으로 현재 사용하시는 마이크사운드 감지센서의 출력값을 아두이노 IDE 시리얼모니터에 출력시켜 초기값을 확인하시면 됩니다.
제 마이크사운드 감지센서의 값은 23~26사이의 아날로그 출력값을 초기값으로 출력하는데 평균 26을 주기적으로 출력하더군요.
아무것도 소리를 입력하지 않는 현재 공간의 소음상태의 신호값입니다. 위에서 설명한 가변저항기의 일자나사모양을 돌리시면 돌리는 방향에 따라서 이 수치가 커지거나 작어지거나 할꺼에요. 자신이 정한 기준에 맞춰서 세팅하시면 됩니다.

3. 회로도 구성


  • 준비물 : 마이크사운드 감지센서 1개, 아두이노우노
  • 내용 : 마이크사운드 감지센서를 아두이노에 연결하자.


마이크사운드 감지센서는 어떤 종류라도 상관 없습니다. 사용되는 핀은 아날로그 신호를 입력받기 때문에 A0핀과 전원부분인 +(Vcc), Gnd 핀을 사용하기 때문에 아날로그 출력핀은 아두이노의 원하는 아날로그핀(A0~A5)을 선택하셔서 연결하시면 되고 전원부분은 맞춰서 연결하시면 됩니다. 마이크사운드 감지센서는 종류에 따라 전원공급 허용치에 맞게 연결하시면 되니깐 그건 부품의 사용설명서를 참고해주세요.

4. 코딩


  • 사용함수 : analogRead()
  • 내용 : 간단히 마이크사운드 감지센서를 통해 소리의 최소와 최대값을 출력해 보자.

마이크사운드 감지센서값 읽기에서 최소값, 최대값 변수를 만들어서 if문 조건문을 통해서 최소값과 최대값을 저장하여 어느정도의 범위의 소리 신호를 센서가 측정할 수 있는지 살펴보도록 하겠습니다.

int m_min = 26;
int m_max = 0;
int SoundSensor =analogRead(A0);
if(SoundSensor<m_min) m_min=SoundSensor;
if(SoundSensor>m_max) m_max=SoundSensor;

첫번째 if문은 SoundSensor값이 초기 26보다 작다면 그 값을 최소값으로 한다.
두번째 if문은 SoundSensor값이 초기 0보다 크다면 그 값을 최대값으로 한다.

간단하게 최소값하고 최대값을 구하는 로직입니다. 왜 초기 최소값을 26으로 했을까요. 그냥 마이크사운드 감지센서로 읽은 값을 Serial 모니터로 출력하면 초기평균값이 나오잖아요 .그 값을 최소로 잡은 것이죠. 그냥 26을 최소값으로 하고 구지 if문으로 최소값을 찾을 이유가 없지 않느냐라고 반문할 수 있지만 실제로 아래 돌려보시면 생각했던 값이 나오지 않고 더 작은 값이 최소값으로 찍히게 됩니다. 그 이유는 결과부분에서 자세히 설명드리겠습니다.

우선은 이런식으로 최소값과 최대값을 구하는 로직을 짰는 걸로 정리해주시기 바랍니다. 그럼 완성된 코딩은 다음과 같겠죠.

int m_min = 26;
int m_max = 0;

void setup()
{
  Serial.begin(9600);
}
void loop()
{
  int SoundSensor =analogRead(A0);
    if(SoundSensor<m_min) m_min=SoundSensor;
    if(SoundSensor>m_max) m_max=SoundSensor;
    
    Serial.print(m_min);    
    Serial.print("  :  ");
  Serial.println(m_max);    
  delay(50); 
}

5. 결과


[ 초기상태 ]




[ 소리입력 후 결과 ]



두장의 사진을 보면 어떤 차이가 있나요. 첫번째 사진은 최소값이 23이고 최대값은 27로 나옵니다. 이건 처음에 마이크사운드 감지센서에 전원이 공급될때 발생하는 전류의 순간 초기 변화의 값이고 평균적으로 26의 값을 초기값으로 찍어 냅니다.

하지만 두번째 사진을 보시면 최소값이 17이고 최대값은 1005로 나옵니다. 마이크사운드 감지센서의 소리가 1005까지 증가한 신호가 측정되었네요. 하지만 이상한점은 최소값이 17로 감소한 지점이 마이크사운드 감지센서에서 발생했습니다.

그 이유가 뭘까요. 바로 소리를 측정하고 전기 신호를 발생하는 순간 측정된 만큼의 전류가 늘어나겠죠. 그리고 소리가 끊어지면 전류의 소비된 만큼이 순간 떨어지게 되고 초기 전류상태에서 아래로 전류값이 떨어지게 되는 것 같습니다. 소리의 측정된 전류가 늘었다 줄어드는 순간 초기 전류상태에서 그 아래로 떨어졌다가 다시 초기상태로 되돌아간다고 생각하면 될 듯 싶네요.

소리가 발생할때마다 전류의 신호의 파형이 증가했다가 떨어질때는 초기값 아래로 일시적으로 전류가 떨어졌다가 다시 올라고 반복된 현상을 보입니다.

이 현상을 잘 기억하셔서 다른 부품을 제어할 때 주의해서 코딩해야 합니다. 초기값이 26이니깐 그 값을 기준으로 해야지 했는데 기준값 아래로 떨어질때는 원하지 않는 값으로 다른 부품을 제어하게 됩니다. 위에서 최소값을 26으로 잡고 다시 if문을 써서 최소값을 구하는 로직을 표현했는지 그 이유를 아시겠지요. 정확히 26이란 값이 최소값이 되지 않기 때문에 그 아래 신호값이 발생할 수 있기 때문에 자신이 사용하는 마이크사운드 감지센서에 대한 정확한 측정 수치를 테스트를 해보셔야 합니다.

마무리


원래는 이 포스팅이 아니였는데 쓰다보니깐 길어져 버렸네요. 이 부품을 가지고 다른 원리를 설명하고자 했는데 의도치 않게 마이크사운드 감지센서 포스트가 되고 말았네요.
그리고 디지털 읽기로 한번 해보세요. 어떤 값이 찍히는지요. 그 값을 Serial 모니터로 출력해보세요. 그리고 이런류의 부품을 사용할때는 보정작업이 필요합니다. 초기값을 어느값으로 할지와 그 값의 범위를 정해서 그 밤위의 값으로 나누는 방법도 생각하시면 다양한 표현을 할 수 있습니다.
초기값을 정하면 그 값을 기준으로 다른 부품의 동작을 제어할 수 있습니다. 그리고 범위값을 나누면 소리의 범위별로 서로 다른 동작을 시킬 수 있습니다.

참고로 가상시뮬에이터에서는 실험 할 수 없습니다. 마이크사운드 감지센서가 존재하지 않으니깐요. 그 대안으로 가변저항기를 이용해주세요. 가변저항기를 돌리면 0~1023의 값을 입력받을 수 있습니다.

[아두이노] 가변저항을 이용하여 3색 LED 제어

이 포스트가 예제로 적당할 듯 하네요. 다음 포스팅 내용도 이 예제를 기반으로 설명을 할 예정입니다. 한번 가변저항기로 마이크사운드 감지센서가 이 값은 음역대 신호를 발생한다고 가정하고 읽는 것을 연습해보세요. 그리고 이 아날로그 신호값이 발생하면 3색 LED 제어한 것처럼 난 무엇을 해볼까 상상력을 발휘를 해보세요.

마지막으로 마이크사운드 감지센서를 가지고 여러분들은 뭘 만들고 싶을지 상상의 나래를 펼쳐보세요.

댓글()

[아두이노] Sensor 읽는 법

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

[아두이노] Sensor 읽는 법



오늘은 지금까지 다뤘던 Sensor 들에 대해서 정리하는 시간을 갖도록 하겠습니다. 가상시뮬레이터에서 제공되는 Sensor의 종류들은 극히 일부에 지나지 않습니다. 실제는 더 많은 수십 수백가지이상의 센서들이 존재합니다. 여기서 중요한 것은 종류가 많다고 해서 개별적으로 다르게 접근하는게 아니라 이제 설명하는 기본 세가지 방법으로 Sensor의 값을 읽어 올 수 있습니다. 이 세가지 방법만 알고 있으면 센서 종류만 많지 다루는데 그렇게까지 어려움이 없을 거라 생각합니다.

1. Sensor의 종류



가상시뮬레이터에서는 값을 읽는데 사용하는 부품들입니다. 몇 종류가 되지 않지만 그래도 세가지 방법을 다 실험할 수 있는 부품들로 구성되어 있습니다. 실제로 아두이노키트를 구매하시면 몇십가지의 부품들로 구성되어 판매하지만 사실 종류별로 구분하여 분류하면 종류는 그렇게 많지 않습니다. 개수만 많을뿐이죠. 실제 부품들 사진을 올려야 하는데 제 키트 사진을 찍어서 올리기 힘들고 인터넷 키트 사진을 올리는 것도 문제가 될 것 같아서 그부분은 생략하도록 하겠습니다. 관심있으신 분들은 구글 검색에서 "아두이노센서 종류"라는 키워드로 검색하시면 목록 사진들이 나열된 게시물들을 쉽게 찾을 수 있으니깐 어떤 종류의 센서들이 있는지 개별적으로 살펴보시면 되겠습니다.

2. Sensor의 읽기 구분



Sensor를 읽이 위해서는 크게 세가지 방법으로 구분한다고 했죠. 바로 디지털 읽기, 아날로그 읽기, 시간값 읽기로 나누어 살펴볼 수 있습니다.

1) 디지털 읽기 :

디지털핀에서 센서의 값을 읽는 방법입니다. 기본적으로 디지털핀은 HIGH or LOW의 상태값을 읽을 수 있고 쓸 수 있습니다. 쉽게 말해서, 디지털핀으로 읽을때 HIGH or LOW의 상태값을 읽게 됩니다. Sensor에서 디지털 상태값을 출력하는 종류가 뭐가 있을까요. PIR Sensor(인체감지센서), Tilt Sensor(기울기센서), 스위치버턴 등이 대표적으로 들 수 있겠죠. 이런 종류의 Sensor는 두가지 HIGH or LOW 상태값만 갖게 됩니다.

int val = digitalRead(디지털핀);

val이란 변수에 디지털핀에 연결된 센서의 값을 저장하게 됩니다. 우리는 두가지 상태값 HIGH or LOW의 값을 통해서 아두이노에서 특정 동작을 제어하게 됩니다.

2) 아날로그 읽기 :

아날로그핀에서 센서의 값을 읽는 방법입니다. 기본적으로 아날로그핀은 0~1023사이의 값을 읽게 됩니다. 아날로그 값을 출력하는 종류가 뭐가 있을까요. 위 사진을 보면 대충, Gas Sensor(가스센서), Photoresistor(LDR)(조도센서) 등이 있네요. 이런 종류의 센서는 전류의 값을 출력되는데 아두이노는 0~1023사이의 전류의 세기 값을 읽게 됩니다.

int val = analogRead(아날로그핀) ;

val이란 변수에 아날로그핀에 연결된 센서의 값을 저장하게 됩니다. 우리는 전류의 세기 0~1023의 값을 통해서 특정 동작을 제어하게 됩니다. 참고로 부품에 따라 아날로그 신호와 디지털 신호를 두개 다 내보내는 부품이 있는데 사용목적에 따라서 디지털 신호는 디지털 읽기로 아날로그 신호는 아날로그 읽기 함수를 사용해서 읽으시면 됩니다.

3) 시간값 읽기 :

디지털핀에서 센서의 값을 읽는데 어떤 특정한 상태를 기준으로 해서 그 상태가 유지하는 시간을 추출할 수 있습니다. 대표적으로 초음파 센서를 들 수 있습니다.

int val =pulseIn(디지털핀, HIGH/LOW);

val이란 변수에 디지털 핀에 연결된 센서의 값을 읽어오는데 상태의 값이 HIGH or LOW 중 어떤 상태값이 얼마정도 유지 되었는지 그 시간을 측정하는데 사용됩니다. 가령 pulseIn(디지털핀, HIGH) 라고 하면 디지털핀에 들어오는 HIGH값이 LOW가 될때까지의 시간값을 반환하게 됩니다. 초음파센서에서 초음파를 쏘고 되돌아올때 이때 초음파센서를 통해 HIGH에서 LOW가 될때까지의 시간값을 가지고 거리를 계산 했었죠.

원래는 디지털 읽기와 아날로그 읽기로 두가지로 구분하시면 됩니다. 시간값 읽기는 특수한 경우이고요. 다양한 부품들이 있는데 대부분 디지털 값을 출력하거나 아날로그 값을 출력하는 부품들이고 우리는 digitalRead()함수와 analogRead()함수로 센서의 값을 읽게 됩니다.

추가적으로 통신에서 '1'과 '0'의 신호값으로 읽기도 합니다. 가령 1byte의 값을 전송한다면 '00000000'의 범위의 전기신호를 보내는데 '1'이라는 값을 보낸다고 치면 '00000001'이렇게 2진수로 보낸다면 전기 신호가 'LOW' 7번, 'HIGH' 1번으로 짧게 끈어서 8bit의 신호를 보내겠죠. 통신에서는 읽을때 이 전기신호를 읽어서 다시 복호화하여 1이란 숫자로 저장하게 됩니다. 통신 부분이기 때문에 이 부분은 간단히 전기신호로 읽는 방법정도만 이해하시면 되겠습니다.

SoftwareSerial mySerial(RX,TX);
byte val = mySerial.read();

Serial 통신핀을 통해서 1byte의 신호값을 val에 저장합니다. 센서값을 읽은 기기에서 통신을 통해서 그 값을 아두이노우노에서 읽을때 사용 됩니다. 이부분은 통신 부분이니깐 별도로 알아두시기 바랍니다.

마무리


오늘은 간단히 센서의 값을 읽는법을 정리하는 시간으로 채웠습니다. 어떤 Sensor를 사용하시더라고 기본 골격은 디지털 읽기와 아날로그 읽기 뿐이 없습니다. 별도로 라이브러리리 제공 될 경우 해당 라이브러리 객체에 사용핀을 던져주기만 하면 별도의 코딩은 필요 없겠지만 실제 여러분들이 코딩을 하실 경우는 대표적으로 이 두가지 방법으로 접근이 이루어지기 때문에 두 함수만 이해하시면 사용해보지 않는 센서들일지라도 대부분 사용하실 수 있을 꺼에요.

오늘은 정리하는 시간으로 간단히 포스팅을 했네요.

댓글()

[아두이노] ATtiny 제어

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

[아두이노] ATtiny 제어



오늘은 ATtiny에 대해서 살펴보는록 하겠습니다. 아주 작은 프로그램을 이식할 수 있고 다른 부품을 제어할 수 있어서 소형 아두이노로 생각하시면 아마 편하실꺼에요. 그런데 사용하기 위해서는 프로그램을 이식해야하는데 좀 복잡한 과정을 거쳐야 하기 때문에 사용하시기 번거로울 꺼에요. 프로그램 이식과정을 무시하고 바로 가상시뮬레이터에서 동작을 시킬 수 있기 때문에 한번 사용해 보겠습니다. 사용을 해봐야 어디다 사용할지 상상을 할 수 있기 때문에 상상하기 전에 간단히 제어를 통해 ATtiny를 배워도록 하겠습니다.

1. ATtiny 구조



파워포인트로 대충 그려봤는데 잘 그려졌는지 모르겠네요. 우선 핀을 보시면 총 8개의 핀으로 구성되어 있습니다. 전원 공급핀과 리셋핀을 제외하면 대충 Pin 0,1은 PWM으로 사용되고 Pin 2,3,4은 Analog Input으로 사용되네요.

따로 설명할 필요가 없을 것 같아 이 핀들을 가지고 간단히 LED를 제어하는 실험을 해보도록 하겠습니다.

2. 회로도 구성


  • 준비물 : ATtiny 1개, coin Cell 3V Battery 1개, Red Led 1개, 저항 220옴 1개
  • 내용 : 간단히 Red Led를 깜박이기게 하자.


위 그림에서 ATtiny 칩이 동작할려면 전원을 공급해야겠죠. 작은 건전지를 Vcc, Gnd로 연결해서 전원을 공급하게 됩니다. 그리고 첫번째 Pin 0에 출력핀으로 해서 Red Led를 전류를 공급하도록 배치해 놨습니다. 제일 처음 LED 제어편에서 아두이노를 13번 핀에 연결한 Red Led를 깜박이는 예제가 생각나실 꺼에요.

아래 그림에서는 NeoPixel Ring 24 짜리는 선을 구별할 수 있지만 NeoPixel Ring 12와 NeoPixel Jewel은 우리가 NeoPixel를 지난 시간분터 계속 연결해왔기 때문에 안보여도 대충 어떤식으로 연결되는지 아실거라고 생각되기에 그 부분은 설명에서 제외하겠습니다. 아래 결과물 동영상에서 제작과정이 잘 나와 있으니깐 참고해 주세요. 그거랑 같다고 생각하시면 됩니다.

3. 코딩


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : "STEEM CODINGMAN" 글자를 만들어 출력하자.
  • 참고소스 :

함수

  • pinMode(사용핀, 입/출력모드) : 해당 사용핀을 어떤 모드로 사용할지 결정.
  • digitalWrite(사용핀, HIGH/LOW) : 해당 사용핀을 디지털 출력으로 HIGH or LOW 인지 결정.
  • delay(시간값) : 시간값만큼 대기한다.

[기본 소스]

void setup()
{
  pinMode(0, OUTPUT);
}

void loop()
{
  digitalWrite(0, HIGH);
  delay(1000); // 1초(1000) 대기
  digitalWrite(0, LOW);
  delay(1000); // 1초(1000) 대기
}

Pin 0을 출력모드로 설정 합니다. 1초 동안 HIGH(5V)를 공급 한 뒤에 1초동안 LOW(0V)상태가 됩니다. 이렇게 loop()함수는 계속 반복하게 됩니다. 그럼 1초 단위로 깜박이는 로직이 됩니다. 이미 LED 제어편에서 설명한 내용이니깐 간단히 로직에 대해서만 이야기 했네요.

4. 결과



위 그림은 간단히 Red Led가 깜박이는 장면입니다. 코딩하는 것도 아두이노랑 차이가 없고 핀번호에 대해서 구분하여 사용하시면 사용하는데 어려움이 없겠죠. 하지만 문제는 프로그램을 ATtiny 칩에 이식하는 과정입니다. 사용은 쉽고 코딩도 쉬운데 프로그램을 이식하는게 어렵습니다.

5. 아두이노에서 ATtiny로 연결



위 출처로 가시면 자세히 설명 나와 있습니다. 저도 이글을 통해서 ATtiny에 프로그램을 이식시키구나 하고 대충 의미를 이해했네요. 구글검색하시면 국내 게시물도 다양하게 공개되어 있으니깐 어느걸 보셔도 상관없습니다. 따로 제가 아는척 설명드리기는 뭐해서 링크만 걸어 놓겠습니다.


대충 가상시뮬레이터에서 그림만 그린다면 이렇게 되네요. 그리고 나서 위 참고자료에 따라 순서대로 아두이노 IDE에서 설정만 해주시면 된다고 하네요. 실제 ATtiny 칩이 없어서 실험은 해보지 못했네요. 그냥 여러분들도 가상시뮬레이터에서 실험하기 대문에 동작 제어만 한번 코딩해보고 ATtiny 칩에 응용 방법만 머리속에서 설계하시면 됩니다.

6. 추가로 찾아보셔야 할 내용



필독 내용도 꼭 읽어주시기 바랍니다. 어떻게 아두이노에서 다른 칩에 프로그램을 이식시킬 수 있는지 아두이노공식페이지에 잘 나와 있습니다.

그리고 데이터시트 경우는 구글검색 키워드 "attiny85 datasheet"로 하시면 저 게시물이 나옵니다. 해당 데이터시트를 링크를 안걸어 놓은 이유는 다운로드 PDF 파일이기 때문입니다. 오해를 살 수 있는 부분이라서 링크를 걸어놓지 않겠습니다. 다른 데이터시트를 보셔도 됩니다. 내용은 비전공자분들은 어려울 수 있습니다. 제가 소개한 데이터시트의 p59 페이지의 표만 참고하시면 됩니다. 각 핀에 대한 설명이 자세히 나와 있습니다. 그걸 참고하시고 위에서 간단히 핀 설명을 설명을 PWM, Analog Input이라고만 했는데 우선은 이정도만 이해하셔도 충분합니다. 보다 자세히 알고 싶으면 데이터시트를 꼭 참고해 주세요.

마무리


간단히 ATtiny 칩을 제어해 보았습니다. 코딩은 따로 할 것은 없고 아두이노 상에서 코딩한 것 처럼 그대로 코딩하시면 됩니다. 어떤 핀들이 있는지만 잘 이해하시면 됩니다. 명령을 내릴 수 있는 핀은 어떤 핀인지만 구별하시면 어려움은 없을 듯 하네요. 좀 어려운 부분은ATtiny에 프로그램을 이식시키는 과정이 좀 복잡하다는 점이죠.
저는 코딩을 좋아하고 코딩을 위주로 하기 때문에 전자회로쪽은 좀 약한 편입니다. 얇은 지식으로 2편을 나눠서 제가 설명하는 것보다 위에 나온 링크한 자료들을 토대로 공부하시는게 더 좋을 것 같아 포스팅을 늘리지 않겠습니다.

실제로는 다른칩에 프로그램을 이식하기 위해서는 ArduinoISP 튜토리얼을 참고하셔서 그 과정대로 세팅하고 실제 코딩해놓은 프로그램을 이식시키면 됩니다. 가상시뮬레이터에서는 프로그램을 이식시키는 과정이 필요 없기 때문에 뭘 만들지만 여러분들이 상상력을 발휘하시면 됩니다. 지금까지 배워온 여러가지 부품들을 ATtiny와 연결해서 회로도를 설계를 해보시고 동작시켜서 ATtiny의 활용도를 상상해 보세요.

마지막으로 작은 ATtiny 칩을 어디에다 쓸지 상상의 나래를 펼쳐보세요. 아마 소형으로 구현할 수 있는 기기들을 상상하시겠지요.

댓글()

[아두이노] NeoPixel 증흥적 패턴 놀이

IOT/아두이노|2019. 4. 3. 09:00

[아두이노] NeoPixel 증흥적 패턴 놀이 



오늘은 NeoPixel를 가지고 for문만을 이용해서 즉흥적으로 깜박이는 실험을 해 보겠습니다. 지난 시간에는 패턴을 만들고 그 만들어진 패턴을 변수에 저장하고 그것을 다시 불러와서 출력하는 과정이 좀 복잡하고 어렵게 느껴졌을 거라 생각되네요. 재밌는 NeoPixel를 너무 어렵게 나간것 같아서 원래 예정에 없던 추가 포스트를 작성하게 되었네요. for문 하나를 가지고 여러가지 패턴으로 움직이게 하여 NeoPixel를 가지고 놀아 보도록 합시다.


오늘 제작 할 NeoPixel의 모습입니다.

1. 회로도 구성


  • 준비물 : NeoPixel Ring 24 1개 NeoPixel Ring 12 1개, NeoPixel Jewel 1개, 아두이노우노
  • 내용 : 사용 할 NeoPixel를 핀에 맞게 간단히 연결하시오

아래 그림에서는 NeoPixel Ring 24 짜리는 선을 구별할 수 있지만 NeoPixel Ring 12와 NeoPixel Jewel은 우리가 NeoPixel를 지난 시간분터 계속 연결해왔기 때문에 안보여도 대충 어떤식으로 연결되는지 아실거라고 생각되기에 그 부분은 설명에서 제외하겠습니다. 아래 결과물 동영상에서 제작과정이 잘 나와 있으니깐 참고해 주세요.


선연결은 그리 어렵지 않을꺼에요 Vcc은 Vcc로 Gnd은 Gnd로 각각 서로 연결하시면 됩니다. 그리고 아두이노에서 출력하는 값은 3번 핀으로 Orange 선이 Neopixel의 입력핀으로 연결되고 NeoPixel의 출력핀은 다른 NeoPixel의 입력핀과 연결된다는 점만 구별해 주세요.

2. 코딩


  • 사용함수 : Adafruit_NeoPixel(), neopixel.begin(), neopixel.setPixelColor(), neopixel.show(), neopixel.clear()
  • 내용 : 증흥적 다양한 릴레이 동작을 구현해 보자.(랜덤함수로 색을 다양하게 표현)

함수

  • Adafruit_NeoPixel(NeoPixel수, NeoPixelPin, NEO_GRB + NEO_KHZ800) : 생성자함수로 NeoPixel 클래스 객체를 만들때 사용합니다. 몇개의 NeoPixel를 사용하고 어느 핀을 사용할지와 Neopixel 타입을 설정을 하는 함수입니다. 우선 가상시뮬에이터는 이 값을 기본으로 사용하세요.
  • neopixel.begin() : 라이브러릴 사용하면 꼭 객체 시작을 알리는 함수가 있죠. 이것도 마찬가지입니다. 사용하겠다고 선언.
  • neopixel.setPixelColor(위치, R, G, B) : 색을 나타냅니다. 위와 같은 Neopixel 타입을 설정했을때 RGB의 위치는 이와 같이 결정됩니다. 참고로 타입이 바뀌면 색상 위치도 바뀌니깐 햇갈리지 마세요. 쉽게말해서, 색을 세팅하는 함수입니다.
  • neopixel.show() : 색이 세팅한 값을 실제로 보여주라는 함수입니다. Orange선을 통해서 해당 위치에 색이 켜지겠지요.
  • neopixel.clear() : 지금까지 켜진 NeoPixel를 초기화하는 함수입니다. 켜진 NeoPixel를 다 꺼지겠지요.

[기본 소스]

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(43, neopixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  neopixel.begin();  
}

void loop() {  
  for(int i=0;i<43;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 
  neopixel.clear(); 
}

순서대로 NeoPixel에 불이 들어오는지 첨에 이 소스로 돌려주세요.

랜덤 함수로 NeoPixel의 색을 결정하기 위해서 두개의 함수를 더 사용합니다.

randomSeed(analogRead(A0))
random(255)

setup()함수에다가 randomSeed()함수로 A0핀을 난수를 만들 핀으로 사용하겠다고 선언해주시고요.
실제로 RGB는 각각 0~255사이의 값으로 출력되기 때문에 random(255)로 0~255사이의 난수 값을 얻게 됩니다.

[전체 소스]

#include <Adafruit_NeoPixel.h>
const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(43, neopixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  neopixel.begin();  
  randomSeed(analogRead(A0));
}
void loop() { 
  
  //순차적으로 릴레이
  for(int i=0;i<43;i++){ //1씩 증가하니깐 0~42까지 해당 위치에 NeoPixel의 색을 결정
    neopixel.setPixelColor(i, random(255), random(255), random(255));    
    neopixel.show();
    delay(50);    
  } 
  neopixel.clear(); 
  
  //홀수층 릴레이
  for(int i=0;i<43;i+=2){  //0부터 2씩 증가하니깐 홀수 위치에 색을 설정
    neopixel.setPixelColor(i, 0,255,0);
    neopixel.show();
    delay(50);    
  }  
  //neopixel.clear(); 
  
  //짝수층 릴레이
  for(int i=1;i<43;i+=2){ //1부터 2씩 증가니깐 짝수 위치가 되겠죠.
    neopixel.setPixelColor(i, 0,0,255);
    neopixel.show();
    delay(50);    
  }  
  neopixel.clear(); 
 
  //홀수 위치는 밖에서부터 짝수 위치는 안쪽부터 두가지 패턴을 한번에 세팅
  for(int i=0;i<43;i+=2){
    neopixel.setPixelColor(i, 0,255,0);
    neopixel.setPixelColor(42-i-1, 0,0,255);
    neopixel.show();
    delay(50);    
  }  
  neopixel.clear(); 
  
  //짝수 홀수 교대로 깜박이기 (위의 홀수와 짝수를 릴레이 하는걸 홀수전체 세팅, 짝수 전체세팅으로 설정
  for(int k=0;k<5;k++){ //교대로 반복 5회
    for(int i=0;i<43;i+=2){
        neopixel.setPixelColor(i, random(255),random(255),random(255));  
    }
    neopixel.show();
    delay(50);    
    neopixel.clear(); 
  
    for(int i=1;i<43;i+=2){
        neopixel.setPixelColor(i, random(255),random(255),random(255));
    }
    neopixel.show();
    delay(50);    
    neopixel.clear(); 
  }
}

위 소스만 보면 코딩이 엄청 길지만 기본소스에 원리를 그대로 적용해서 어떻게 출력할지만 표현 했을뿐 자세히 보시면 코딩이 반복되는 코딩으로 for문의 규칙을 잘 살펴보시면 별차이가 없다는 것을 느끼 실 꺼에요.

여기서, 코딩을 이해하실때 neopixel.show()함수와 neopixel.clear()함수의 의미를 의해 잘 생각 하시고 어느 위치에 있느냐에 따라서 결과가 어떻게 변하는지 구별 하실 수 있게 되면은 자기가 원하는 형태로 다양한 패턴을 만들어 낼 수 있을 거라 생각됩니다.

3. 결과


결과만 보여드릴까도 했는데 제작과정을 다 담았네요. 참고로 짝수 릴레이, 홀수 릴레이에서 동영상에서 실수로 오타가 났네요. '0,2,4,,6,8,10...' 순으로 가니깐 짝수인것처럼 한글로 짝수 릴레이라고 했는데요. 실제로는요 0이 첫번째 NeoPixel이니깐 홀수층 릴레이가 됩니다. 그리고 '1,3,5,7,9...'순으로 가니깐 한글로 홀수 릴레이라고 했는데요 이건 반대로 배열 1번은 2번째 위치입니다. 그러니 정확한 표현은 짝수층이 되겠죠. 아무튼, 동영상에서는 오타가 난거고 숫자가 짝수니깐 짝수층이고 홀수니깐 홀수층이라고 생각하시면 안됩니다. 정확히 짚고 넘어갑니다.

프로그램에서는 배열에서 a[0]은 1번째 위치고 a[1]은 2번째 위치입니다. 순서로 a[0]은 홀수 위치가 되고 a[1]은 짝수 위치가 됩니다. 이부분을 한글 오타를 정정합니다.

동영상을 급하게 대충 표현한거라 여러분들은 더 멋진 패턴으로 깜박이게 한번 해보세요.


마무리


NeoPixel은 다양한 형태로 출력할 수 있기 때문에 재밌는 부품입니다. RGB LED를 쉽게 여러개을 한번에 연결하여 제어할 수 있는 부품이라고 생각하시면 아마 될 듯 싶네요.
실제로 구매하셔서 만들어 보시는 것도 재미있을 듯 싶네요.


댓글()

[아두이노] NeoPixel로 글자패턴 만들기

IOT/아두이노|2019. 4. 2. 09:00

[아두이노] NeoPixel로 글자패턴 만들기 


오늘의 주제는 NeoPixel를 이용해서 지난 시간에 배운 3x3x3 CUBE LE와 8x8 Dot Matrix에서 사용한 코딩기법을 복습하는 시간을 가져보도록 하겠습니다. 기본 출력 로직을 회로도에 맞춰서 만들어 놓으면 나머지 패턴 글자만 만드는 작업만 좀 고생 되겠지만 이 후 코딩은 글자 패턴를 만드는 작업뿐이 없으니깐 아마 편하게 코딩 할 수 있을꺼에요. 암튼 좀 어려울 수 있는 내용이지만 코딩을 처음 어떻게 시작해서 어떻게 진화한 코딩으로 바뀌는지 느끼는 시간이 되었으면 합니다. 코딩은 처음부터 정해진 코딩으로 뚝닥 완성되는게 아니라 출발은 아래 글처럼 하나의 글자를 증흥적으로 코딩해보고 다른 방식들을 찾아보면서 최종적으로 자신이 원하는 형태의 로직 형태를 찾아서 그 결과 로직을 토대로 다양한 글자들을 만들어 아래와 같은 결과물을 얻게되는데 이 과정을 잘 살펴 보셨으면 합니다. 완성된 코딩은 그렇게 까지 의미는 없습니다. 자신이 출발은 어디서 부터이고 그 출발에서 어떻게 변화 해가는지가 중요합니다. 아래의 코딩과정을 길게 나열한 것은 그 의미를 전달하기 위해서 입니다. 이 글을 쓰는 저자가 완성된 로직을 만들기 위해서 처음에 여기서부터 출발했고 어떤 과정을 거쳐서 여기까지 왔구나 정도만 이해하시면 됩니다.

코딩한 결과가 중요한 것이 아니라 코딩하는 과정이 무척 중요합니다.

1. NeoPixel 회로도 배치 고민


처음에는 위와 같은 순서대로 회로도를 연결했습니다. 문제는 위치에 대한 공간인지가 좀 불편하실 수 있으며 나중에 2차배열로 위치값을 정해야 하는데 순번이 햇갈릴 수 있기 때문에 이경우는 초보분들에게 혼동을 야기 할 것 같아서 포기했습니다.


그래서 옆으로 눞여 봤는데 홀수 행은 배열 형태를 잘 나타나는데 짝수행은 배열의 역순 위치 값으로 표기 되기 때문에 이 또한 혼동을 야기 할 것 같아서 포기했습니다.

최종적으로 선 연결을 복잡하게 한 대신에 배열 형태를 맞춰서 디자인 했습니다. 그러면 어떻게 했는지 회로도 구성에서 살펴보도록 하겠습니다.

2. 회로도 구성


  • 준비물 : NeoPixel 35개, 아두이노우노
  • 내용 : 사용 할 NeoPixel를 간단히 연결해 보자.


회로도의 경우는 자신의 원하는 형태로 연결하시면 됩니다. 단, 연결하실 때는 어떤식으로 코딩할지에 대해서 고려하시고 연결하셔야 해요. 연결하고 나서 그 연결된 형태를 고려해서 코딩을 해도 되지만 반대로 코딩 할 로직을 어느정도 틀이 잡혀 있으면 그 틀에 맞게 회로도를 배치하는 것도 좋습니다. 제가 코딩을 소개하는 방향에서는 2차배열을 사용하기 때문에 최대한 위치에 대한 순서를 맞춰서 회로도의 선을 연결했네요.

3. 코딩 과정


  • 사용함수 : Adafruit_NeoPixel(), neopixel.begin(), neopixel.setPixelColor(), neopixel.show(), neopixel.clear()
  • 내용 : "STEEM CODINGMAN" 글자를 만들어 출력하자.
  • 참고소스 :

함수

  • Adafruit_NeoPixel(NeoPixel수, NeoPixelPin, NEO_GRB + NEO_KHZ800) : 생성자함수로 NeoPixel 클래스 객체를 만들때 사용합니다. 몇개의 NeoPixel를 사용하고 어느 핀을 사용할지와 Neopixel 타입을 설정을 하는 함수입니다. 우선 가상시뮬에이터는 이 값을 기본으로 사용하세요.
  • neopixel.begin() : 라이브러릴 사용하면 꼭 객체 시작을 알리는 함수가 있죠. 이것도 마찬가지입니다. 사용하겠다고 선언.
  • neopixel.setPixelColor(위치, R, G, B) : 색을 나타냅니다. 위와 같은 Neopixel 타입을 설정했을때 RGB의 위치는 이와 같이 결정됩니다. 참고로 타입이 바뀌면 색상 위치도 바뀌니깐 햇갈리지 마세요. 쉽게말해서, 색을 세팅하는 함수입니다.
  • neopixel.show() : 색이 세팅한 값을 실제로 보여주라는 함수입니다. Orange선을 통해서 해당 위치에 색이 켜지겠지요.
  • neopixel.clear() : 지금까지 켜진 NeoPixel를 초기화하는 함수입니다. 켜진 NeoPixel를 다 꺼지겠지요.

[기본 소스]

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(35, neopixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  neopixel.begin();  
}

void loop() {  
  for(int i=0;i<35;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 
  neopixel.clear(); 
  neopixel.show();
  delay(100);   
}

순서대로 NeoPixel에 불이 들어오는지 테스트를 하였습니다.

2) 하나의 'S' 글자패턴 만들기


위 표에서 보는 것 처럼 2차 배열이 있으면 'S'라는 글자를 만들면 해당 위치에 가운데 표에서 보는 것처럼 색이 칠해지겠죠. 그 위치값을 가지고 글자패턴을 만든다면 아래와 같이 표현이 됩니다.

const byte p[7][5]= {
        {0,1,2,3,0},
        {5,0,0,0,9},
        {10,0,0,0,0},
        {0,16,17,18,0},
        {0,0,0,0,24},
        {25,0,0,0,29},
        {0,31,32,33,0}};

즉, 배열에다가 위치값을 아예 저장하는 방식입니다. 출력을 해볼까요.

   for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(p[i][j], 255, 0, 0);    
      }     
    }
    neopixel.show();
    delay(1000);

위 코딩으로 2차원 배열로 행7과 열 5로 순차적으로 0이 아닌 위치에 neopixel.setPixelColor()함수로 색을 세팅합니다. 2차 for문이 다 돌고나면 'S'라는 글자의 위치에는 색이 다 세팅되어 있겠죠. 그리고 나서 neopixel.show()함수로 실제 NeoPixel로 출력하게 됩니다. 그러면 'S'라는 글자를 볼 수 있습니다.

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(35, neopixelPin, NEO_GRB + NEO_KHZ800);
const byte p[7][5]= {
        {0,1,2,3,0},
        {5,0,0,0,9},
        {10,0,0,0,0},
        {0,16,17,18,0},
        {0,0,0,0,24},
        {25,0,0,0,29},
        {0,31,32,33,0}};
void setup() {  
   neopixel.begin();   
}       
void loop(){        
    for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(p[i][j], 255, 0, 0);    
      }     
    }
    neopixel.show();
    delay(1000);
    neopixel.clear();
    neopixel.show();
    delay(100);  
}       

2) 글자패턴 다른식으로 접근

사실 위 코딩으로 's' 글자를 만들때 위치값을 해도 되지만 '0'과 '1'로 표현을 해보도록 하겠습니다. 글자가 들어가는 위치를 '1'로 그렇지 않는 곳은 '0'으로 표현 하겠습니다.

const byte p[7][5]= 
        {{0,1,1,1,0},
        {1,0,0,0,1},
        {1,0,0,0,0},
        {0,1,1,1,0},
        {0,0,0,0,1},
        {1,0,0,0,1},
        {0,1,1,1,0}};

그러면 처음 코딩한 곳에서 어디를 수정해야 할까요. 바로 for문안에 문장이겠죠.

   byte y=0;
   for(int i=0;i<7;i++){
      for(int j=0;j<5;j++){
          if(p[i][j]!=0)neopixel.setPixelColor(y+j, 255, 0, 0);   
      }
            y+=5; // 행위치값을 5씩 증가,
    }

그냥 i+y하면 안됩니다. 그 이유는 i가 0일때는 j에 더하면 '0,1,2,3,4'가 나오지만 i가 1일때는 '1,2,3,4,5'가 나오게 됩니다. 원래는 '5,6,7,8,9'이렇게 나와야 하는데 말이죠. 이렇게 나오게 하기 위해서 y라는 변수를 행 위치변수로 하나 만들어서 행의 위치를 나타내는 변수가 필요합니다. 행인 i가의 for문이 증가할때마다 행위치변수인 y은 5씩 증가하게 표현함으로 2차 배열의 위치를 정확히 지정할 수 있게 됩니다.

첫번째 코딩한 것에서 위 표현을 변경할 부분에 삽입하셔서 바꾸시면 됩니다. 변수선언과 for문안에 로직과 y변수 선언하시면 됩니다.

3) '0'과 '1'의 표현이면 '0B00000000'로 표현

'0'과 '1'이 나왔으면 어떤 표현이 떠오르지 않나요. 바로 byte형으로 하나의 숫자 값으로 '0B00000000'로 표현이 떠오르셨다면 지난 내용을 제대로 공부하셨던 분일꺼에요, 이 표현을 통해 열을 표현한 5개의 배열공간을 하나의 숫자로 표현이 가능해 집니다.

01110 + 000 => 0B01110000

뒤에다 '000' 붙였는지만 앞에다 붙이셔도 됩니다. 실험은 뒤에다 붙여서 실험했는데 이 표현은 제 마음이니깐요 이게 꼭 해야하는 건 아니니깐 앞이든 뒤든 상관이 없고 나중에 for문을 표현할 때 앞이냐 뒤냐에 따라서 표현이 좀 달라지기는 하지만 그렇게 큰 의미는 없는 표현입니다.

const byte p[7]= {
0B01110000,
0B10001000,
0B10000000,
0B01110000,
0B00001000,
0B10001000,
0B01110000 }

이렇게 표현되는데 아두이노라이브러리에 bit를 읽는 함수를 기억하실지 모르겠네요. bitRead(x,n)함수가 있습니다. X라는 숫자에서 n번째 위치의 bit값을 가져오는 함수입니다.

위의 패턴배열변수에서

0B01110000

이값을 가져올려면

const byte p=0B01110000;
for(int j=7;j>2;j--){
     bitRead(p, j);
}

이렇게 표현해야합니다. 읽는 위치는 역순이니깐요. 오른쪽끝이 0번째가 됩니다. 그런데 왼쪽부터 순서대로 하나식 가져올려면 7번 위치의 자리에 값부터 순서대로 읽어와야겠죠. 총 5개니깐 j는 2보다 커야하고요. 순서대로 가져오니깐 'j--'로 감소연산자로 1씩 감소시켜야 겠죠. 이렇게 표현해야 왼쪽부터 순서대로 1bit씩 가져오게 됩니다.

로직은 이러헥 되겠죠.

    byte x=0; //열위치
   byte y=0;//행위치
   for(int i=0;i<7;i++){
      for(int j=7;j>2;j--){
          if(bitRead(p[i,j)]!=0)neopixel.setPixelColor(y+j, 255, 0, 0);   
                x+=1;
      }
            x=0;
            y+=5; // 행위치값을 5씩 증가,
    }

여기서 열의 위치값을 j로 할 수 없습니다. j가 7부터 시작하기 때문이지요. 그렇기 때문에 따로 x변수로 열도 위치변수를 만들어 줘야 합니다. j가 반복수행 할 때 x를 1씩 증가하게 하고 j가 5번 반복이 끝나면 x를 초기화 해야합니다. 두번째 행의 위치를 처음부터 다시 찾아야 하니깐요.

변수 선언과 이 로직을 처음 코딩한 소스에서 삽입하고 변경하시면 됩니다. 따로 전체소스는 안올리겠습니다.

4) 'S' 글자 16진수화

0B01110000 = 0x70

const byte p[7] ={0x70,0x88,0x80,0x70,0x08,0x88,0x70};

이렇게 한줄로 대폭 줄일 수 있습니다. 단, 힘든 16진수 계산이 필요하지만요. 따로 16진수로 출력되는 프로그램을 만드셔도 됩니다. 해당 2진수 값이 저장된 배열을 for문을 이용해서 출력을 16진수로 바꾸시면 됩니다. 참고로 10진수로 변경하셔도 됩니다.

5) 'STEEM CODING MAN' 글자패턴을 만들기


글자 한개를 만드는 것도 힘들데 총 14자의 글자를 만든다고 생각하니깐 앞이 깜깜하시죠. 이제 글자를 쉽게 만드는 꼼수를 찾아냈는데 알려드리도록 하겠습니다.



위 블로그에 올리신 분께서 젤 하단에 이런 프로그램을 링크 걸어 놓았네요. 글자 패턴을 만들기 힘드신분들을 위한 배려인 듯 합니다. 저도 이런거 프로그램을 만든분 없는지 구글 검색을 통해서 찾아보다가 우연히 발견했네요. 그리고, 쉽게 글자를 만들었네요.


7행의 패턴 글자이기 때문에 마지막 8행의 16진수 값은 필요 없으니깐 지우시면 됩니다. 이렇게 글자를 만들면 다음과 같은 패턴 배열변수가 완성되었네요.

이런식으로 글자들을 14자를 완성하면 이렇게 간단히 표현됩니다.

const byte p[14][7]={ //글자 패턴이 14개임
  {0x70,0x88,0x80,0x70,0x08,0x88,0x70}, //S
    {0xF8,0x20,0x20,0x20,0x20,0x20,0x20}, //T
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x80,0x80,0x80,0x88,0x70}, //C
    {0x70,0x88,0x88,0x88,0x88,0x88,0x70}, //0
    {0xF0,0x88,0x88,0x88,0x88,0x88,0xF0}, //D
    {0x70,0x20,0x20,0x20,0x20,0x20,0x70}, //I
    {0x88,0x88,0xC8,0xA8,0x98,0x88,0x88}, //N
    {0x70,0x88,0x80,0xB8,0x88,0x88,0x70}, //G
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x88,0xF8,0x88,0x88,0x88}, //A
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88} //M
    };

이 글자가 14개의 패턴 글자가 나오니깐 기존 소스에서 14번 for문을 한번 더 돌려야 겠죠.

for(int k=0;k<14;k++){

        기존 2차for문 로직;
        y=0; // 하나의 패턴 글자가 끝나니깐 y행위치 변수값을 초기화 해야겠죠.
}

최종적으로 코딩을 하면

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(35, neopixelPin, NEO_GRB + NEO_KHZ800);


const byte p[14][7]={ //글자 패턴이 14개임
    {0x70,0x88,0x80,0x70,0x08,0x88,0x70}, //S
    {0xF8,0x20,0x20,0x20,0x20,0x20,0x20}, //T
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8}, //E
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x80,0x80,0x80,0x88,0x70}, //C
    {0x70,0x88,0x88,0x88,0x88,0x88,0x70}, //0
    {0xF0,0x88,0x88,0x88,0x88,0x88,0xF0}, //D
    {0x70,0x20,0x20,0x20,0x20,0x20,0x70}, //I
    {0x88,0x88,0xC8,0xA8,0x98,0x88,0x88}, //N
    {0x70,0x88,0x80,0xB8,0x88,0x88,0x70}, //G
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88}, //M
    {0x70,0x88,0x88,0xF8,0x88,0x88,0x88}, //A
    {0x88,0xD8,0xA8,0x88,0x88,0x88,0x88} //M
    };

void setup() {
  neopixel.begin();  
}

void loop() {
  byte x=0;
  byte y=0;
  for(int k=0;k<14;k++){ //글자 패턴의 숫자만큼 반복
    for(int i=0;i<7;i++){
        for(int j=7;j>2;j--){
            if(bitRead(p[k][i],j)!=0) neopixel.setPixelColor(y+x, 0, 255, 0);    
            x+=1;
        }
        x=0;//열 초기화
        y+=5;//열이 5칸이니 행이 증가할때마다 5가 더해져야함
    }
    y=0; //행 초기화
    neopixel.show();
    delay(100);   
    neopixel.clear(); 
    neopixel.show();
    delay(100);
  }
}

이렇게 완성이 되었습니다. 코드를 이해 못하셔도 좋습니다. 제가 처음에 어떻게 시작을 했고 어떤 과정을 통해서 여기까지 왔는지의 과정만 이해하시면 됩니다. 어떤 코딩을 할 때 처음부터 완벽한 코딩은 없습니다. 하나씩 차근 차근 원리를 코딩하고 그것을 끊임없이 다른 방법은 없는지 찾으면서 코딩을 하는게 중요합니다.

그 의미를 잘 이해해 주셨으면 합니다.

4. 결과


원래는 'STEEM CODINGMAN'이라는 14자를 출력해야 하는데 동영상 녹화를 논스톱으로 하다보니깐 'O'자를 실수로 빼먹었네요. 13자만 출력되었네요. 어쩐지 글자가 다 출력되고 다시 loop()함수가가 시작하기 전에 딜레이 시간이 생겨서 가상시뮬레이터 상에서 발생하는 렉인 줄 알았는데 13자 출력하고 반복 패턴글자 1글자 분량의 동작을 더 수행하다 보니 결과는 없고 1글자 분량의 동작은 수행만큼의 딜레이 현상처럼 보였네요. 수정할려다가 그러면 영상을 편집해야 하기 때문에 그냥 패턴글자를 'STEEM CDINGMAN'로 출력하는걸로 끝내겠습니다.

참고로, 공개된 회로도 링크 주소로 가셔서 결과를 실제 돌려보시면 정상적으로 'STEEM CODINGMAN'이란 글자가 출력되도록 수정했으니깐 참고하세요.


만드는 과정이 좀 길었네요. 다 자르고 뒤에 결과만 편집할려다가 그냥 올렸습니다. 젤 마지막의 글자 패턴 결과만 보시거나 아니면 실제로 공개된 회로도 링크 주소로 가셔서 직접 돌려보셔도 됩니다.

마무리


NeoPixel이 재밌다고 했는데 갑자기 코딩수업이 되어서 어렵게 느껴졌을 듯 싶네요. 간단한 단순 패턴을 만들어서 화려하게 꾸미고 싶었는데 글자가 너무 땡겨서 글자 출력으로 하고 말았네요. 사실 특정한 문자로 출력하는 것은 좀 코딩이 복잡하고 불편합니다. 단순하게 홀짝 출력이나 릴레이 색상출력이나 큰틀에서의 한두가지 패턴을 색상과 덧붙여서 출력하는게 더 재미 있고 화려한데 말이죠.
이부분은 이 포스팅을 읽으시는 분들의 몫으로 남겨 두겠습니다. 혹시 땡겨서 밤에 만들어 내일 올릴 수 도 있겠지만 오늘밤 땡기냐 안땡기냐로 기분에 맡겨야 겠습니다.
갑자기 코딩이 좀 어렵게 됐는데 이것 말고 간단히 색상 별 한두가지 패턴으로 지그재그로 꾸며 보세요. 그리고 위치를 달리해서 어떤 독특한 모양을 만들어 보세요.
아니면 이 출력하는 원리를 잘 생각해보셔서 어느 부품하고 연결하면 좋을지 상상의 나래를 한번 펼쳐보세요.


댓글()

[아두이노] NeoPixel 제어

IOT/아두이노|2019. 4. 1. 09:00

[아두이노] NeoPixel 제어 


오늘의 주제는 NeoPixel로 재밌는 LED 제어에 대해서 살펴보도록 하겠습니다. NeoPoxel를 제어하기 위해서 Adafruit_NeoPixel 라이브러리를 이용하는데 별로 어렵지 않게 LED 색을 만들어 내기 때문에 재미 있는 시간이 될거라 생각됩니다. 사용되는 함수는 몇개 안되지만 그래도 상상력을 얼마나 발휘하느냐에 따라서 멋진 작품들을 만들 수 있는 부품임으로 관심을 많이 가져주세요.

1. NeoPixel


NeoPixel은 여러 종류가 있습니다. 긴 띠, 한개짜리, 링형 막대형 등 다양한 부품들이 있습니다. 가상시뮬레이터에서는 아래 그림처럼 한개짜리와 원형과 여러종류의 링을 제공하고 있네요. 다루는 것은 다 동일하니깐 간단히 다뤄보도록 하겠습니다.


NeoPixel의 핀 구조는 Vcc, Gnd 핀이 각각 두개씩 있으며 Vin과 Vout 핀으로 나눠져 있습니다. Vcc, Gnd은 NeoPixel의 전원을 담당하겠죠. Vin은 해당 NeoPixel의 들어오는 입력값이고 Vout은 해당 NeoPixel에 들어왔던 입력값 중 일부를 다음 NeoPixel로 보내는 출력핀이 됩니다. 아래 그림의 Orange 선을 보시면 됩니다. Orange 선이 신호값이 들어오고 다음 NeoPixel로 보내는 흐름선으로 생각하시면 됩니다.


예를들어, 순차적으로 1초 단위로 1,2,3 NeoPixel이 켜진다고 가정해 봅시다. 신호 값은 1,2,3번에 불이 켜지라고 명령을 내리게 됩니다. 이때 데이터값이 Orange선으로 따라서 해당 위치에 불이 들어오게 합니다. 고로, Orange선은 명령값을 전달하는 통로로 생각하시면 됩니다.


다시 종합하면, 3번까지 NeoPixe에 불이 들어오라고 명령을 내리면 그 신호 값이 1번에 불이 들어오고 2번의 Neopixel에 신호를 보내고 2번에 불이들어오고 다시 Orange선을 통해서 3번 NeoPixel에 신호값이 전달되어 3번에 불이 들어오게 됩니다. NeoPixel간의 연결은 Vout과 Vin을 연결한 통로로 신호가 전달되니깐 이 점만 잘 기억해 주세요.

2. 회로도 구성


  • 준비물 : NeoPixel Ring 12 1개, NeoPixel 6개, 아두이노우노
  • 내용 : 사용 할 NeoPixel를 간단히 연결해 보자.

대충 두종류의 NeoPixel를 Vcc, Gnd 명칭을 잘 보고 연결하시면 됩니다. 그리고 Vin, Vout은 3번핀에 연결했네요. 제 블로그에 올렸던 회로도에서 약간만 수정했네요.


주의할것은 1개짜리 NeoPixel을 연결할때 두번째 라인은 회전시켜서 배치한 거라 Vout, Vin을 잘 체크하시고 연결하셔야 합니다. 그냥 드래그해서 배치한 형태에서 두번째 라인을 Vout에서 Vout로 연결하는 실수를 하시면 안됩니다.

3. 라이브러리 추가


참조 : [아두이노] LCD16x2 I2C(LiquidCrystal_I2C) 제어
라이브러리 출처 : https://github.com/adafruit/Adafruit_NeoPixel


NeoPixel 라이브러리는 여러 종류가 있습니다. 실제로 실험하실 분은 위 링크된 참조 LCD16x2 I2C 포스트 내용중에 라이브러리 추가하는 방법이 잘 나와 있으니깐 보시고 라이브러리를 추가하시면 됩니다.

그런데 가상시뮬레이터로 하시는 분들은

#include <Adafruit_NeoPixel.h>

이 문구만 있으면 됩니다.

라이브러리 올려주신 분의 github 주소입니다. 거기 가셔서 Adafruit_NeoPixel.h, Adafruit_NeoPixel.h.cpp 파일을 꼭 보시기 바랍니다.

거기 보시면 아래와 같은 코딩들이 있습니다. 타입 설정하는데 어느정도 보셔야 합니다. 실제로 구현 하실때 왜 불이 안들어오지 의도치 않게 결과가 나오는 이유가 타입 설정 때문에 그렇습니다. 하나로 고정되어 있는게 아니라 부품에 따라서 그 타입에 맞게 설정해야지 정상적으로 동작합니다.

 Constructor: number of LEDs, pin number, LED type
 
 LED type :
 NEO_KHZ800  800 KHz datastream
 EO_KHZ400  400 KHz datastream
 
 NEO_RGB  ((0 << 6) | (0 << 4) | (1 << 2) | (2))
 NEO_RBG  ((0 << 6) | (0 << 4) | (2 << 2) | (1))
 NEO_GRB  ((1 << 6) | (1 << 4) | (0 << 2) | (2))
 NEO_GBR  ((2 << 6) | (2 << 4) | (0 << 2) | (1))
 NEO_BRG  ((1 << 6) | (1 << 4) | (2 << 2) | (0))
 NEO_BGR  ((2 << 6) | (2 << 4) | (1 << 2) | (0))
 NEO_RGBW
 ...
 NEO_WRGB 
 ...
 

다른 함수들도 꼭 보시고 대충 어떤 느낌으로 흘러가는지만 살펴보세요. 나중에 색을 세팅하는 함수에서 위에 나와있는 RGB라는 글자들이 보이시죠. 위치를 나타냅니다. 색을 세팅하는 함수는 같지만 해당 위치의 인자값은 달라지게 됩니다. RGB 인자를 순서대로 넣었는데 처음 세팅을 BGR로 해버렸다면 코딩하는 사람은 RGB라고 생각하고 색 값을 넣었지만 색 세팅함수에서는 BGR로 인식해버릴 수 있으니깐 잘 살펴보셔야 해요. 그리고 함수명들만 헤더 파일에 어떤게 있는지만 한번 살펴봐 주세요.

4. 코딩


  • 사용함수 : Adafruit_NeoPixel(), neopixel.begin(), neopixel.setPixelColor(), neopixel.show(), neopixel.clear()
  • 내용 : 간단히 순차적으로 NeoPixel에 불이 들어오게 하자.
  • 참고소스 : [아두이노] 3x3x3 CUBE LED 제어 III

함수

  • Adafruit_NeoPixel(NeoPixel수, NeoPixelPin, NEO_GRB + NEO_KHZ800) : 생성자함수로 NeoPixel 클래스 객체를 만들때 사용합니다. 몇개의 NeoPixel를 사용하고 어느 핀을 사용할지와 Neopixel 타입을 설정을 하는 함수입니다. 우선 가상시뮬에이터는 이 값을 기본으로 사용하세요.
  • neopixel.begin() : 라이브러릴 사용하면 꼭 객체 시작을 알리는 함수가 있죠. 이것도 마찬가지입니다. 사용하겠다고 선언.
  • neopixel.setPixelColor(위치, R, G, B) : 색을 나타냅니다. 위와 같은 Neopixel 타입을 설정했을때 RGB의 위치는 이와 같이 결정됩니다. 참고로 타입이 바뀌면 색상 위치도 바뀌니깐 햇갈리지 마세요. 쉽게말해서, 색을 세팅하는 함수입니다.
  • neopixel.show() : 색이 세팅한 값을 실제로 보여주라는 함수입니다. Orange선을 통해서 해당 위치에 색이 켜지겠지요.
  • neopixel.clear() : 지금까지 켜진 NeoPixel를 초기화하는 함수입니다. 켜진 NeoPixel를 다 꺼지겠지요.
    참고로 clear()함수로 명령을 내렷다고 해서 NeoPixel에 들어온 불이 꺼지지는 않습니다. 내부적으로 세팅한 setPixelColor()함수의 값만 초기화 될 뿐이죠. 내부적으로 초기화 된 값에서 show()함수를 실행 시켜야 초기화된 상태로 NeoPixel이 꺼지게 됩니다. ( "왜! 안꺼져! 이런 실수를 안하셨으면 해요!")

설계

NeoPixel 라이브러리를 사용하니깐 사용할 객체를 선언해야 합니다. 한개짜리 6개와 12개짜리 링 한개로 총 18개의 NeoPixel를 제어하니깐 아래와 같이 선언하시면 되겠습니다.

const byte neopixelPin = 3;
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(18, neopixelPin, NEO_GRB + NEO_KHZ800);

순차적으로 NeoPixel로 불이 들어와야 하니깐 18개NeoPixel 기준으로 for문 한개를 사용하여 18번 반복하면 되겠죠.

for(int i=0;i<18;i++){
   NeoPixel i에 불이 들어오게하라;
}

이 로직으로 대충 돌아가야 겠죠. NeoPixel 함수는 불이 들어올려면 색을 세팅하고 그 세팅값을 NeoPixel로 보내야 합니다.

setPixelColor()함수로 색을 정하고, show()함수로 출력하면 됩니다.

  for(int i=0;i<24;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 

대충 이렇게 코딩 됩니다. i번째 위치 NeoPixel에 RGB에서 R(255)로 Red색으로 세팅하고 show()함수로 해당 위치에 NeoPixel에 불이 들어오게 됩니다. 그렇게 어렵지 않죠. NeoPixel 라이브러리로 간단히 해당 위치에 원하는 색으로 불이 들어오게 하는 함수가 이 두 함수입니다. 어떻게 로직을 짜냐에 따라서 재밌는 패턴이 만들어 지고 화려하게 불이 들어오게 할 수 도도 있게 됩니다.

딜레이는 빠르게 보기 위해서 0.1초로 했네요. 가상시뮬레이터에는 지연렉이 좀 있기 때문에 좀 빠르게 보기 위한 값이니깐 실험할 때는 원하는 시간 간격으로 돌려보세요.

그리고, 시작은 begin()함수를 사용하고 초기화는 clear()함수를 사용합니다. 꺼먹지 마세요. 그리고 clear()함수 명령을 내렸는데 왜 NeoPixel이 안꺼지지 하고 혼동하시는 분이 있는데요. 무조건 Neopixel에 명령을 보내는 것은 show()함수 입니다. 내부적으로 clear()함수로 초기화 되었어도 출력된 NeoPixel은 초기화 되지 않습니다. show()함수로 같이 쓰시면 외부의 NeoPixel까지 초기화 된다는 점을 꼭 기억해주세요.

전체적으로 코딩을 하면,

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(18, neopixelPin, NEO_GRB + NEO_KHZ800);

void setup() {
  neopixel.begin();  
}

void loop() {  
  for(int i=0;i<18;i++){
    neopixel.setPixelColor(i, 255, 0, 0);    
    neopixel.show();
    delay(100);    
  } 
  neopixel.clear(); 
  neopixel.show();
  delay(100);    
}

5. 결과




오늘은 회로도 만드는 것이 간단하니깐 전과정을 기록해 봤네요. 그리고 결과는 Red 색 하나만 순차적으로 불이 들어오게 하면 좀 그러니깐 R->G->B순으로 불이 들어오게 가상시뮬레이터에서 돌려봤네요. 어떤 결과가 나오는지 동영상을 꼭 확인 해보세요. 아니면 공개 회로도에 링크된 곳에 가셔서 가상시뮬레이터를 직접 돌려보세요.


마무리


진짜 오늘 배운 부품 소재는 재맸는 소재입니다. 아마도 한번은 보셨을지 모르겠네요. 클럽에 네온싸인 같은 것이나 할로윈 축제때 어떤 사람을 보면 몸에서 야광 LED가 깜박이는 것들을 보셨을꺼에요. 유튜브를 검색을 하시면 NeoPixel로 모니터를 만들어서 동영상을 감상하시는 분도 있고 연주가들은 악기에 붙여서 휘황찬란하게 표현하는 분도 있고 찾아보시면 스케일들이 남다른 분들의 영상물들을 많이 보실 수 있을 꺼에요. 아마도 실제로 구매해서 만들고 싶은 충동이 느껴지실 거라고 생각됩니다. 그런데 개당 가격이 좀 비싼편이라서 약간 구매해서 실험하기는 애매한 점도 있습니다. 우리에게는 가싱시뮬레이터가 있고 그걸 통해서 대리 만족을 느끼면 됩니다.

오늘은 간단한 동작으로만 채웠습니다. 그 이유는 여러분들의 상상력을 발휘할 시간을 주기 위해서 입니다. 다양한 표현을 시도해 보세요. 여러분들은 NeoPixel로 난 무엇을 만들어 볼까? 무엇을 만들 수 있을까? 계속 자신에게 질문을 던져주세요. 그리고 순차적으로 출력되는 저 for문을 가지고도 다양한 표현을 할 수 있습니다. setPixelColor()함수와 show()함수의 의미와 동작원리를 곰곰히 잘 생각해보시면 for문 한개로도 꽤 다양한 패턴을 만드실 수 있으니깐 꼭 동작 원리를 이해해 주세요.


댓글()

[아두이노] 74HC595+8x8 도트매트릭스 제어

IOT/아두이노|2019. 3. 31. 09:00

[아두이노] 74HC595+8x8 도트매트릭스 제어



이전 시간에는 8x8 LED 핀을 일일히 아두이노에 연결을 하다 보니깐 16개의 핀이 필요했습니다. 너무 많은 핀을 사용하다 보니깐 줄여보고 싶은 마음이 생기지 않나요. 우리가 7 Segment LED를 제어할 때 사용한 부품이 떠오르는 분이 있을 지 모르겠군요. 그 부품은 74HC595칩 핀으로 쉬프트레지스트를 이용하면 16개 핀에서 3개의 핀으로 줄어들게 됩니다. 74HC595 칩은 8개의 출력 핀을 제공하는데 이 칩을 직렬로 연결하면 무제한 핀을 사용할 수 있습니다. 물론 Max7219칩 하나로 16개핀을 한번에 제어가 가능합니다. 하지만 가상시뮬레이터에서는 해당 칩이 없고 74HC595 칩뿐이 없으니 그걸로 실험을 할 수 밖에 없겠죠.
그러면 74HC595 칩 2개를 이용해서 8x8 LED를 제어해보록 하겠습니다.

1. 74HC595 직렬 연결



복습의 시간으로 latchPin(12), clockPin(11), dataPin(9)을 통해서 제어합니다. 9번, 11번, 12번 핀을 연결한 선 색을 잘 보시기 바랍니다. 11, 12번핀은 서로 연결되어 있지만 9번 데이터 입력되는 핀은 Orange 선이 수평선으로 이여진게 직렬 데이터가 전송되는 연결인데 오른쪽 74HC595핀의 Q7s핀에서 왼쪽 74HC595의 dataPin에 입력으로 이여집니다.

예) 아래와 같은 입력이 이뤄질 경우74HC595 직렬 연결시 데이터는 어떻게 배치되는지 살펴 봅시다.

0B11111110 00000001


어떤 느낌으로 들어가시는지 아시겠지요.

실제로 코딩할대는 byte 배열 변수를 층과 호실로 나눠서 2개를 만들었는데요. 가능하다면 int형으로 하나로 16bit로 표현하셔도 됩니다. 이해를 돕기위해서 실험은 2개의 byte(8bit)로 실험 했습니다.

2. 회로도 구성


  • 준비물 : Yellow LED 64개, 저항 220옴 8개, 74HC595 칩 2개, 아두이노우노, 뻥판
  • 내용 : 8x8 도트매트릭스 형태를 표현하고 74HC595 칩 두개로 출력하니깐 거기에 맞게 배치해보자.


3. 코딩



복습

  • 74HC595칩 제어 :
    - latchPin, clockPin, dataPin을 pinMode(출력핀, 모드)로 선언
    - digitalWrite(결쇠핀, 열림/잠금)으로 74HC595 칩에 입력할때 열림/잠금을 제어
    - shiftOut(데이터입력핀, 클럭핀, MSBFIRST, 전송값)으로 이걸 통해서 역순으로 데이터가 배치

설계

1층만 개방은 1층에 Gnd 상태이고 나머지 층은 HIGH상태여야 하잖아요.

OB11111110

이렇게 됩니다. 오른쪽 끝이 1층이고 역순으로 순서대로 '87654321'층으로 위치가 결정됩니다.

그럼 1층1호실에 불이 들어올려면은 어떻게 해야 할까요. '0B11111110' 로 1층만 개방해야 겠죠.
1~8호실중에 1호실만 불이 들어올려면 1호실만 HIGH 상태이고 나머지는 Gnd(LOW)상태여야 겠죠.

0B00000001

1층(0B11111110)과 1호실(0B00000001)로 합쳐져서 1호실만 불이 들어오게 됩니다.
층별 개방할려면,

0B11111110,  
0B11111101,
0B11111011,
0B11110111,
0B11101111,
0B11011111,
0B10111111,  
0B01111111,

이렇게 '0'인 위치만 개방 해야겠죠

호실별 개방할려면,

0B00000001,  
0B00000010,  
0B00000100,  
0B00001000,  
0B00010000,  
0B00100000,  
0B01000000,  
0B10000000

이렇게 '1'인 위치로 호실만 전류를 보내겠다는 표현이겠죠. 각층의 8개의 호실에 불이 순서대로 깜박일려면 2중 for문을 사용해서 표현하면 됩니다.

for(int i=0;i<8;i++){
 for(int j=0;j<8;j++){
     층[i] 패턴에 호실[j=0~7]까지 순서대로 호실패턴이 대입을 수행;
 }
}

즉, 층[0] 패턴일때 호실[0]~[7]패턴이 대입된다면
'0B11111110 ' + '0B00000001 = 1층1호실 불이 들어옴
'0B11111110 ' + '0B00000010 = 1층2호실 불이 들어옴
'0B11111110 ' + '0B00000100 = 1층3호실 불이 들어옴
.......

대충 이런식으로 층[0]~[7]까지의 패턴이 있는데 각 층에 대한 호실[0]~[7]까지의 패턴이 순차적으로 대입되는 로직을 머리속에서 그리셔야 합니다. 어떤식인지 이해하셨는지 모르겠네요.

전체적으로 코딩을 하면,

byte layer[]={ //층
0B11111110,  
0B11111101,
0B11111011,
0B11110111,
0B11101111,
0B11011111,
0B10111111,  
0B01111111,
};
byte room[]={ //호실
0B00000001,  
0B00000010,  
0B00000100,  
0B00001000,  
0B00010000,  
0B00100000,  
0B01000000,  
0B10000000
};
int latchPin = 11; //ST_CP Pin
int clockPin = 12; //SH_CP Pin
int dataPin = 9; //DS Pin

void setup(){
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop(){
  for(int i=0;i<8;i++){  //순차적으로 릴레이 깜박이기    
    for(int j=0;j<8;j++){
      digitalWrite(latchPin, LOW);
      shiftOut(dataPin, clockPin, MSBFIRST, layer[i]);
      shiftOut(dataPin, clockPin, MSBFIRST, room[j]);
      digitalWrite(latchPin, HIGH);       
      delay(100);
    }
  }  
}

4. 결과


간단히 실험 결과 영상만 올렸습니다. 회로도 제작 과정은 생략합니다. 이전 74HC595 칩을 제어할때 다뤘던 내용이니 결과로 어떻게 출력되니지만 확인하시고 회로도 만드는 과정은 74HC595편을 참고하세요


마무리


오늘은 어제 실험한 내용에 오래전에 배운 74HC595칩을 결합을 해보았습니다.

내일은 LED의 꽃인 네오픽셀을 다루는 것을 해보겠습니다. LED에 관해서는 얼추 기초적인 부분은 다 끝난 것 같네요. 나머지 부분은 대부분 여러가지 부품과 결합해서 표현하는 응용편이니깐요. 암튼 네오픽셀이 가장 화려하고 재미 있을 듯 싶네요.


댓글()

[아두이노] 8x8 도트매트릭스 제어

IOT/아두이노|2019. 3. 30. 09:00

[아두이노] 8x8 도트매트릭스 제어 



이건 조이스틱 제어 한뒤에 다룰려고 했던 주제인데 3x3x3 CUBE LED랑 같기 때문에 이번에 같이 소개하는 게 좋을 것 같아서 바로 도트매트릭스에 대해서 살펴보도록 하겠습니다.

1. 8x8 도트매트릭스


제가 가지고 있는 모델입니다. 뒷면에 핀이 8x8핀으로 구성되어 있는데 그냥 이상태에서 실험하시면 딱 이 부품만 사용하면 아두이노에서 사용할 수 있는 핀은 몇개 안남습니다. 여기에 따로 모듈을 붙여서 사용핀을 줄여서 실험하셔야 다른 부품들과 같이 다양한 실험을 할 수 있습니다. 모듈화 된 제품을 구하시는게 좋고요. Max7219 칩인가 그걸로 도트매트릭스 핀을 다 제어가 가능합니다. 아니면 74HC595를 2개 사용하여 8핀씩 제어하는 방법도 있고요. 그냥 모듈화 된 것을 구매하셔서 라이브러리로 쉽게 제어하는게 편하겠지요.


오늘 다루는 내용은 사실 실제부품과는 차이가 있고 원리를 설명하며 가상시뮬레이터에서 실험을 통해거 그 결과를 보기 위한 내용으로 꾸며질 꺼에요. 그 이유는 도트매트릭스 부품이 가상시뮬레이터에서는 없기 때문이고 해당 8x8 도트매트릭스 부품은 핀 번호가 좀 복잡합니다. 단순하에 8x8핀이 있다고 해서 대충 연결해서는 원하는 결과를 얻을 수 없습니다.

실제로 실험하실려면은 아래 아두이노 튜토리얼를 참고 하시기 바랍니다.

이것만 보시면 안되고 앞쪽과 뒷쪽을 구분해야 합니다. 앞쪽에 볼록 약간 뛰어 나온 모양이 있는데 그 부분이 앞쬭인데 부품이름이 적혀있는 면일 꺼에요. 가장 중요한 핀번호를 알아야 합니다.

여기 블로그 게시물을 쓴분의 8x8 도트매트릭스 뒷면에 핀번호 사진이 있습니다. 그걸 보시고 핀번호를 확인하시면 됩니다. 그러면 아두이노 공식 홈페이지에 나온 튜토리얼 예제를 보시고 핀 연결한 뒤에 실제로 돌려보시면 아마 될꺼에요.

이걸 다 다룰려면 포스트가 또 길어지고 실제 아두이노를 다루는 것에 초점을 둔게 아니라 가상시뮬레이터 실험을 통해서 아두이노를 이해하는게 목적임으로 간단히 8x8 도트매트릭스 원리를 이해하는 내용으로 전개하겠습니다.

그러면 직접 LED로 제작해야죠. 도트매트릭스 회로도를 만들어 봅시다. 다시 노가다의 길이 시작 되었네요.

2. 8x8 도트매트릭스 회로도 구성


  • 준비물 : Yellow LED 64개, 저항 220옴 8개, 아두이노우노
  • 내용 : 8x8 도트매트릭스 형태를 표현하자.


아마도 지난시간에 3x3x3 CUBE LED를 같다고 생각하시는 분들이 있을거에요. 맞습니다. 도트매트릭스가 이런 구조로 된 부품입니다. 이 회로도를 만드는 중에 난 지금 뭐하고 있는거지 하는 생각이 들정도로 엄청 노가다 작업을 했네요. 동작 원리는 동일합니다. 층과 호실의 개념으로 접근하시면 됩니다. 이건 지난시간에 많이 설명을 해서 더이상 반복하지 않게습니다.

스피드하게 다음 코딩작업으로 갈까요. 참고로 A0~A4핀은 아날로그 입력핀인데 pinMode()를 선언하면 디지털핀 용으로 사용이 가능합니다. 그래서, 16개 핀이 필요한데 겨우 배치했네요. 참고로 0,1핀은 통신핀으로 일부러 사용하지 않았습니다. 사용할 수 있지만 나중에 시리얼통신을 할 때 적용해야 하기 때문에 습관을 0,1핀을 될 수 있으면 사용안하시는게 좋습니다.

3. 코딩



복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

int layer[8] = {10,11,12,13,A0,A1,A2,A3};
int room[8] = {2,3,4,5,6,7,8,9};
int m_layer = 8;  // 층 수
int m_room = 8; // 호실 수

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    pinMode(room[i], OUTPUT); //호실 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
    digitalWrite(room[i], LOW);  // 각호실 닫기   
  }   
}
void loop()
{
  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);
    for(int j=0;j<m_room;j++){      
      digitalWrite(room[j], HIGH);
      delay(100);
      digitalWrite(room[j], LOW);
    }
    digitalWrite(layer[i], HIGH);
  }
}  

4. 패턴글자 출력 코딩


참고소스 : [아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본


참고소스는 각 층별로 패턴을 저장했다면 이번 경우는 패턴을 기준으로 각층별의 패턴을 묶어서 저장했네요. 약간의 차이가 있습니다.
'STEEM'이란 글자를 만들기 위해서 8x8에서 'S'를 하나의 패턴으로 층별로 값을 묶었습니다. 지난시간에는 1층이면 1층만의 패턴을 하나의 배열변수에 저장했다면 이번에는 'S'자를 만드는 1~8층의 정보를 하나의 배열변수에 저장했다고 생각하시면 됩니다. 약간은 차이가 있고 이런식으로도 패턴을 저장하는구나를 보여주기 위해서 입니다. 사실, 이방식으로 패턴을 저장하는게 더 옳은 방법입니다. 지난 포스팅에 패턴은 층별 개별적인 패턴을 만들기 때문에 층별 배열변수로 저장이 이뤄졌고요. 이번에는 전체 층을 합산한 패턴을 만들기 때문에 패턴에 대한 전체층의 값을 묶어서 저장해서 좋더 효율으로 패턴을 만들고 출력할 수 있다는 점을 생각하시고 코드를 보시기 바랍니다.

int layer[8] = {10,11,12,13,A0,A1,A2,A3};
int room[8] = {2,3,4,5,6,7,8,9};
int m_layer = 8;  // 층 수
int m_room = 8; // 호실 수

unsigned int layerPattern[5][8] ={ //패턴
  //S :   
  {0x3C,0x66,0x06,0x1C,0x38,0x60,0x66,0x3C},
  //T : 
  {0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF},  
  //E : 
  {0x7E,0x7E,0x60,0x7E,0x7E,0x60,0x7E,0x7E},
  //E : 
  {0x7E,0x7E,0x60,0x7E,0x7E,0x60,0x7E,0x7E},
  //M : 
  {0xC3,0xC3,0xC3,0xC3,0xDB,0xFF,0xE7,0xC3}  
};

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    pinMode(room[i], OUTPUT); //호실 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
    digitalWrite(room[i], LOW);  // 각호실 닫기
   
  }  
 
}
void loop()
{
  for(int i=0;i<5;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<30;j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      for(int p=0;p<8;p++){ //층 별 패턴
        LEDSetting(layer[p], layerPattern[i][p]);  //1층 i번째 패턴        
      }
    }
    delay(500);
  }  
}  
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<8;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(5);
  for(int i=0;i<8;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

5. 결과


가상시뮬레이터 결과는 순차적으로 릴레이 출력과 글자 패턴을 출력하는 영상입니다. 따로 만드는 과정은 생략했고요. 그냥 어떻게 동작하는지만 살펴보세요. 그리고 글자 패턴은 'STEEM' 글자가 출력되게 했네요. 가상시물레이터가 뚜렷하게 출력되지는 않네요.

마무리


코딩에 대한 설명은 생략했습니다. 지난시간에 순차적으로 릴레이하는 코딩을 그대로 인용하였고, 패턴 만들기로 2편의 포스팅으로 대충 감을 잡으셨을꺼에요. 따로, 8x8 도트 매트릭스를 설명 드릴 것은 없네요. 3x3x3 CUBE LED나 8x8 도트매트릭스는 다 동일한 원리니깐요. 오늘은 복습 차원으로 생각하시면 되겠습니다.

사실 이 포스트는 지난 3x3x3 CUBE LED할 때 완성했는데 따로 Steem API가 재미 있어서 그쪽으로 맛보기로 포스팅하다 보니깐 이제서야 올리게 되었네요. 1일 1포스트를 하다 보니깐 뭔가 빨리 올리고 싶어서 밀리게 되네요.

아! Steem API 몇개 함수를 이용해서 또 재밌는 걸 만들려고 구상중이네요. 그건 포스팅을 안하겠지만요. 아두이노하다거 포스트 정체성이 상실하니깐 아두이노를 당분간 계속 올려야겠네요. 혹시 재밌는 표현이 완성된 것 있으면 주말에나 한번씩 포스팅을 해봐야 겠네요.


댓글()

[아두이노] 아두이노 변수란

IOT/아두이노|2019. 3. 29. 09:00

[아두이노] 아두이노 변수란 



오늘은 아두이노의 변수에 대해서 살펴보도록 하겠습니다. 사실 변수에 대한 개념 정의를 아두이노 초기에 소개했었야 할 부분입니다. 개념적 설명 부분이 전부라 우선은 가상시뮬레이터로 회로도를 만들고 결과를 출력시키는 부분에 초점을 맞춰 흥미를 전달하고자 하는데 초점을 맞췄기 때문에 지금까지 다루지 않았네요. 무척 중요한 주제인데 이제서야 설명이 들어가네요. 왜! 중요하냐면요 ATmega328 아두이노의 경우 프로그램 메모리가 32KB, 데이터메모리 2KB 밖에 사용할 수 없습니다. 데이터메모리에는 2KB(2x1024byte)는 2048byte 뿐이 기록할 수 없습니다. 그래서 변수를 함부로 막 사용해서는 안되고 효율적인 변수사용이 필요합니다. 경우에 따라서는 구지 변수로 선언하지 말고 상수로 표현해서 사용공간을 확보해야 합니다.

그래서 변수와 상수에 대해서 알아야하는 큰 이유가 됩니다. 이제 변수와 상수에 대해서 살펴보도록 하겠습니다.

1. 변수와 상수


수학시간에 많이 들었던 단어일꺼에요. 변수란 변하는 수이고 상수란 변하지 않는 수라고 기본적으로 알고 있을 겁니다. 그럼 프로그램에서 변수와 상수를 살펴보도록 하죠.

예) int i =10;



i는 변수이고 10은 상수입니다. i 공간에는 10이 저장될 수 있고 11이 저장될 수도 있기 때문에 변수라고 부릅니다. 하지만 10은 10이란 수로 고정되어 10을 11로 바꿀 수는 없기 때문에 상수라고 부릅니다. 여기서 변수란 프로그램에서는 저장공간이라는 개념을 추가해야 합니다.

프로그램에서는 어떤 값이 저장될때 메모리주소지에 저장됩니다. 그 주소지는 숫자형태로 되어 있어서 우리고 직접 주소지로 접근하기는 어렵습니다. '0x7fffad92dd80'주소지가 있다면 이 주소지공간에 10이 저장되었다고 생각해보세요.
'0x7fffad92dd80=10' 실제로 들어가겠죠. 하지만 코딩을 하는 인간 입장에서는 주소지로 해서 접근하는 건 힘이 듭니다. 그래서 변수라는 이름을 사용하여 그 주소지를 대신 접근할 수 있게 합니다.

i의 주소지가 '0x7fffad92dd80'이라 했을 때 i=10; 하면 우리가 해독할때는 "i변수공간에 10이 저장되는 구나" 라고 생각하지만 컴퓨터는 '399942732'주소지에 10이 기록된다라고 생각하시면 됩니다.

위 설명은 다 필요 없고 그냥 프로그램에서는 변수란 저장공간이라는 개념만 탑재하시면 됩니다.

예) int A[3] = {10, 20, 30};


즉, A[0]이란 변수가 주소지 '0x7fffad92dd80'을 가리키고, 이 주소지에 '10'이란 값이 저장됩니다.

2. 프로그램언어에서 상수 표현


총 3가지로 위 그림에서 보는 것 처럼 사용할 수 있습니다. 10은 상수라고 쉽게 아는데 나머지 두개는 왜 상수인지 잘 이해가 안되는 분들도 있을 겁니다.

첫번째, '#define'으로 선언된 경우의 A는 10을 가리킨다고 생각하시면 됩니다. A라는 단어 자체가 10이라고 고정시킨 이름이라고 생각하시면 돼요. A는 무조건 10이고 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

두번째, 'const' 단어가 붙으면 상수변수라고 생각하시면 됩니다. 즉, B라는 상수변수가 10을 지칭한다고 생각하시면 됩니다. 'const' 붙은 상수변수 B는 무조건 10이고 B에는 11이나 9가 절대 될 수 없는 상수라고 생각하세요.

그러면 이런걸 왜 선언하고 사용하느냐고 생각하실꺼에요. 상수 사용은 프로그램의 가독성을 높이기 위해서 입니다. 즉, 상수를 그 자체로 쓰게 되면은 사실 이 숫자가 가리키는 의미 쉽게 알 수 없습니다. 상수로 사용을 하되 그 의미를 전달하고자 할때 상수변수라는 걸 사용하게 됩니다.

그리고 '#define'과 'const'의 구별하는 법은 사실을 딱히 없습니다. 암묵적으로 대문자 표현의 경우는 '#define'으로 선언하고 'const'변수는 일반적으로 변수 표현으로 선언해서 사용하는 경우가 많습니다. 대문자형 변수명이면 '#define'으로 선언된 상수변수구나 정도로 생각할 수 있습니다. 물론 'const' 변수에서도 대문자 선언을 하는 경우도 있습니다. 그리고 일반변수와 상수변수를 차이를 두기위해서 대문자변수명과 소문자변수명으로 구별해서 쓰시면 구별하는게 아주 좋습니다.

그리고, 아두이노에서 상수변수를 많이 보셨을거에요. HIGH, LOW, OUTPUT, INPUT 등등요. HIGH은 '1'이란 값을 가리킵니다. LOW은 '0'이란 값을 가리킵니다. 여기서 아두이노는 출력표현을 할때 '1' or '0'으로 해도 출력되는 이유가 상수변수가 저장된 원래 값이 1과 0이기 때문입니다. '1' or '0'으로 표현하면 의미를 전달하기가 어렵습니다. 하지만 HIGH or LOW로 표현하면 출력시 전류를 보내는 구나 아니면 전류를 보내지 않는 구나 하고 쉽게 의미를 이해 할 수 있게 됩니다. 상수변수에 대해서 이제 어느정도 이해를 하셨을 거라 생각됩니다.

마지막으로 상수변수를 사용하면 좋은점은 '#define' 이나 'const'으로 선언된 상수변수는 데이터메모리 주소지를 사용하지 않습니다. 프로그램메모리에 기록됩니다. 그 이유는 컴파일시에 프로그램에 이식되기 때문이지요. 표현이 적절할 지 모르겠지만 프로그램 언어로 코딩된 명령문들은 그대로 아두이노가 해독하지 못합니다. 기계가 이해할 수 있는 언어로 번역해야 합니다. 그 과정이 컴파일 과정이고요. 인간의 언어에서 기계 언어로 바뀌는 과정이라고 생각하시면 됩니다. 이때 상수변수로 선언된 것들은 상수변수가 가리키는 숫자값을 그대로 상수변수가 사용된 위치에 숫자로 대입되어 컴파일을 거치게 됩니다. 그래서 데이터메모리 주소지를 사용하지 않게 됩니다. 그런 이유로 상수변수를 사용하는 이유가 되겠습니다.

즉,

int A = 10;

int형만큼의 자료공간을 데이터메모리를 차지합니다.

const int B =10;

컴파일때 B라는 위치에 10이 대입되어 컴파일 되기 때문에 데이터메모리를 차지않지 않습니다.

그럼 '#define'과 'const' 차이가 궁금하시는 분들이 있을거에요. 이 경우는 컴파일 때 차이가 납니다. '#define'은 경우는 그냥 사용된 상수의 크기 판별안하고 대입되어 컴파일 되지만 'const'은 선언된 변수의 크기에 체크하고 컴파일이 진행됩니다. 추천은 'const'를 사용하는게 좋다고 합니다.

아두이노 예제 소스들을 보면 오래된 자료들은 '#define'으로 핀 번호를 지정하는 경우가 많으나 최근 자료들은 'const' 예제들이 주로 사용됩니다.

3. 아두이노에서 변수 선언하는 이유


void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000); 
}

이전 포스팅들을 보면 위와 같은 코딩을 주로 했습니다. 그 이유는 우선 해당 함수가 몇번 핀과 연관이 되었는지 이해를 시키기 위해서였죠. 하지만 정석으로 코딩을 하면 다음과 같습니다.

const byte LedPin = 13;

void setup() {
pinMode(LedPin, OUTPUT);
}

void loop() {
digitalWrite(LedPin, HIGH);
delay(1000);
digitalWrite(LedPin, LOW);
delay(1000); 
}

대충 이런식으로 하면 되겠죠. 이렇게 코딩하면 장점이 뭘까요. 비로 setup(), loop()함수를 건들 필요가 없어집니다. 가령 13번핀에 LED 출력을 담당했는데 다른 부품을 같이 사용하면서 13번 자리를 다른 부품에게 양보하고 LED를 2번 핀으로 옮겼다고 생각해 보세요. 그럼 setup()와 loop()함수에 표현된 13이란 숫자를 전부 찾아가서 2이란 숫자로 바꿔야 겠죠. 이렇게 핀번호를 변수로 선언하면 변수가 선언된 곳에서 13을 2로 변경만 하면 자동으로 setup(), loop()함수가 2번핀으로 변경됩니다.
단적으로 보여준 예이지만 이런식으로 핀 번호를 변수로 바꾸면 핀위치가 바뀌더라고 수정이 쉽게 되는 장점을 가지고 있습니다.

또 하나는 변수는 선언된 위치에 따라 그 영역이 달라진다는 점을 주의해서 사용해야 합니다. 함수 밖에 선언되면 모든 함수에 영향권에 두게 되면서 setup()와 loop()함수에서 그 변수를 사용 가능해집니다. 하지만 변수가 setup()함수 내에나 loop()함수 내에서 선언하게 되면 그 함수내에서만 사용할 수 있고 함수가 끝나면 소멸하게 된다는 점을 꼭 기억해 주세요.

[ 변수의 영역 ]


쉽게 설명하면은, 위 그림에서 보는 것처럼 a가 선언된 변수 위치는 setup(), loop(), test()함수내에서도 사용이 가능합니다. 하지만 b변수는 setup()함수에서만 사용이 가능하고 setup()함수가 끝나면 소멸합니다. c변수는 loop()함수안에서만 사용이 가능하고 loop()함수가 끝나면 소멸합니다. 하지만 다른점은 c변수는 test(c) 명령으로 c값을 test()함수의 k변수로 넘겨주게 됩니다. 결론은 c의 값이 test()함수에서도 계속 사용이 가능하게 됩니다. 그리고 d변수는 test()함수가 끝나면 소멸합니다. 대충 변수의 영역에 대해서 살펴보았습니다. 변수를 어디에 선언을 해야할지 감이 잡히셨는지 모르겠네요.

4. 자료형


자료형이란 변수를 저장하는 공간의 크기입니다. 아래 표는 가상시뮬레이터에서 측정한 저장공간의 크기입니다. 운영체제에 따라서 컴파일러에 따라서 그 값은 달라질 수 있다는 점을 감안하시고 보세요. 쉽게 말해서 자료형은 뭘 담을 수 있는 그릇으로 생각하시면 됩니다. 예를들어, 1리터를 담을 수 있는 A 그릇과 50리터를 담을 수 있는 B그릇과 100리터를 담을 수 있는 C 그릇이 있습니다. 조건으로 20리터의 물을 담을려고 한다면 어느 그릇에 담아야 할까요? A 그릇은 1리터 밖에 담지 못하고 그릇 밖으로 쏟아지겠죠. B그릇은 20리터를 담고도 30리터를 더 담을 수 있어서 충분하겠죠. C그릇은 20리터를 담고도 80리터를 더 담을 수 있겠죠. 결론은 B, C그릇에 담으면 해결됩니다. 그런데 어느 그릇이 더 효율적인가요 정답은 B그릇이겠죠. C그릇은 20리터를 담을 수 있지만 80리터를 담을 수 있는 공간이 낭비하게 됩니다. 크기만 크고 자리는 다 차지하고 B 그릇에 비해 더 비효율적이겠죠. 프로그램언어에서도 마찬가지 입니다. 변수도 필요한 공간만 있으면 되지 구지 큰 공간이 필요 없습니다. 자원 낭비일 뿐이죠. 프로그램을 코딩할때도 보다 효율적으로 자원을 관리하는게 무척 중요합니다.

그래서 아래의 char, int, long, float, double, boolean 등 의 사용되는 용도에 따라서 그 그릇의 크기를 지정해주게 자원을 보다 효율적으로 사용이 가능해지겠죠.


제 블로그에 있던 소스인데 그대로 가져왔네요.

void setup() {
  Serial.begin(9600); 
  
  Serial.print("boolean : ");  
  Serial.println(sizeof(boolean));
  Serial.print("byte : ");  
  Serial.println(sizeof(byte));
  Serial.print("char : ");
  Serial.println(sizeof(char));
  Serial.print("short : ");
  Serial.println(sizeof(short));  
  Serial.print("int : ");
  Serial.println(sizeof(int));
  Serial.print("long : ");
  Serial.println(sizeof(long)); 
  Serial.print("float : ");
  Serial.println(sizeof(float));
  Serial.print("double : ");
  Serial.println(sizeof(double));
  Serial.print("uint8_t : ");
  Serial.println(sizeof(uint8_t));    
  Serial.print("uint16_t : ");
  Serial.println(sizeof(uint16_t));
  Serial.print("uint32_t : ");
  Serial.println(sizeof(uint32_t));  
}
void loop() {
}

가상시뮬레이터에서 체크값이네요.

보시면 생소한 uint8_t, uint16_t, unint32_t라는 자료형이 있는데 직접적으로 정의해놓은 변수인데 8bit, 16bit, 32bit 크기의 자료형이라고 생각하시면 됩니다. 아두이노 라이브러리 소스를 분석해보시면 이런 표현의 변수를 많이 볼 수 있을거에요.

5. static와 extern 변수


static 선언변수는 한번 선언되면 프로그램이 종료될때까지는 소멸하지 않는 변수입니다.


위 그림에서 보는것 처럼 'static'으로 선언된 A 값은 처음에 '10'이 출력되고 다음 loop()함수때 '20'이 출력되고 이런식으로 '10'씩 증가하고 B는 처음에 '10이 출력되고 다음 loop()함수때도 '10을 출력되고 이런식으로 계속 '10'만 출력합니다. 그 말은 B변수는 loop()함수가 끝나면 B가 B+10한 결과값 20은 자동 소멸해버린다는 의미 입니다. 하지만 'static'으로 선언된 A변수는 A+10을 해서 20이라는 값이 저장되고 loop()함수가 끝나도 소멸되지 않고 다음 loop()함수때 20값을 유지할 수 있는 변수라는 것을 의미합니다. 'static' 선언변수를 아시겠지요.

그럼 다음으로 'extern'으로 선언된 변수가 또 있습니다. 이 경우 현재 파일뿐 아니라 다른 파일에서도 접근이 가능한 변수입니다.

아두이노에서는 시간과 관련해서 이 변수선언하여 컨트롤 하는 경우가 종종 있습니다.

extern unsigned long timer0_millis;

이렇게 선언되었을 경우에 코딩 로직이 다음과 같다고 생각하시면,

val=millis();
if(val>10000) timer0_millis=0;

millis()함수는 현재 시간값을 가져옵니다. 타이머가 돌기 시작하면 계속 시간값을 커지겠죠. 그런데 10초가 지난 후 어떻게 될까요. 'extern'로 선언된 timer0_millis변수값을 '0'으로 초기화 하면 millis()의 현재시간은 '0'이 됩니다. 다시 초기화 된 상태에서 시작됩니다. 용어적으로 설명할려고 하니깐 좀 애매하고 해서 예제를 하나 들어서 설명을 했네요. 'extern'은 시간관련한 아두이노의 라이브러리파일 어딘가에 사용되는 변수인데 그 변수는 이런식으로 전역변수로 선언하게 되면 그 값을 개발자의 의도대로 변경이 가능해지게 됩니다. 우리가 건들수 없는 변수를 직접 접근할 수 있게 되는 것이죠. 'extern' 선언이 없으면 아무리 timer0_millisd를 0으로 초기화 하더라도 millis()함수안에 시간값은 초기화 되지 않습니다. timer0_millis은 현재 개발자가 코딩한 영역에서만 그 영향권에 있기 때문입니다. 하지만 'extern'로 선언하게 되면 라이브러리 maillis()안에 있는 timer0_millis보다 우선권을 갖게됩니다. 설명이 참 애매하게 됐는데요. 쉽게말해서, 라이브러리에 표현된 변수가 있고 그 변수를 개발자가 'extern'으로 선언하면 개발자가 코딩한 영역에서 해당 변수값을 변경할 수 있다고 생각하시면 됩니다.

그러면 언제 이것을 사용하느냐고 물으신다면 다음과 같은 경우에 사용합니다. 아두이노는 전원이 공급되면 타이머가 돌아가기 시작하는데 그 타이머는 unsigned loog형의 숫자만큼 증가했다가 나중에 초기화 됩니다. 시간으로 따지면 엄청나게 긴 시간인데 정확히 날짜는 기억 안나네요. 만약에 아두이노에서 시간과 관련한 정확한 컨트롤이 필요할대는 이 부분을 건들어서 제어해야겠죠. 왜냐면 아두이노를 무한으로 켜놓을 경우 일정 시기가 되면 시간이 리셋되니깐요.

마무리


오늘은 변수와 상수에 대해서 살펴보았네요. 설명이 좀 길었지만 알아두시면 아두이노를 다룰 때 유용합니다. 그리고 이해가 안되면 변수란 변하는 수이고 상수란 변하지 않는 수이다. 그리고 변수는 저장공간이다 이정도만 이해하시면 됩니다. 더이상 깊게 아실 필요는 없고요. 추가로 변수를 쓰면 좋은점 선언된 변수의 영역정도만 이해해 주세요. 'static'이나 'extern'은 잘 안쓰는 표현이기 때문에 이런게 있구나 정도로 넘어가시면 됩니다. 물론 'static'가 비슷한 효과로 아예 setup()함수와 loop()함수 밖에다 선언하면 되니깐 이 부분이 어려운 분들은 잠시 이해하는 부분을 미루셔도 됩니다.

그냥 변수와 상수라는 개념만 탑재하시면 됩니다.

댓글()

[아두이노] 릴레이 모듈+온도센서+DC모터 응용

IOT/아두이노|2019. 3. 28. 09:00

[아두이노] 릴레이 모듈+온도센서+DC모터 응용 



다른 주제로 포스팅을 끝냈는데 @ellenalee 님이 릴레이 모듈로 선풍기를 제어 해보셨다고 하셔서 이걸 한번 비슷하게 구현해보면 릴레이 모듈의 응용으로 괜찮을 것 같아서 가상시뮬레이터에서 느낌만 비슷하게 한번 구현해 보도록 하겠습니다.

1. 회로도 구성


  • 준비물 : relay DPDT 1개, DC Motor 1개, Temperature Sensor[TmP36] 1개, Power Supply, 아두이노우노
  • 내용 : 온도센서의 값에 의해서 모터가 회전할 수 있게 회로도를 구성해 보자.


대충 DC Motor가 선풍기라고 생각해 봅시다. 느낌은 그런데로 비슷해가 표현 했네요.

3. 코딩


  • 사용함수 : analogRead(), map()
  • 내용 : LED 깜박이는 소스를 이용해서 그 값을 제어값으로 해서 Light bulb 깜박이게 해보자.
  • 참고소스 : [아두이노] 온도센서(TMP36) 제어

복습

  • analogRead(아날로그핀) : 아날로그신호값을 읽음
  • map(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : 입력값이 입력범위에 기준에 맞게 출력범위의 해당값을 출력.

변형함수(만든함수)

  • fmap(입력값,입력최소값,입력최대값,출력최소값,출력최대값) : map()함수와 동일하지만 자료형 타입을 float형으로만 변경했습니다. 계산값이 실수형 값이라서 map()함수를 사용하기가 애매해서 인자들을 전부 float형으로 변경했네요.

[ 온도센서 측정 소스 ]

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void loop(){
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
   //공식
  //float V = analogRead(A0)*5.0/1023;
 
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  float F = C*9.0/5.0+32; //화씨 F = C*1.8+32;
 
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

여기서 대충 온도 C변수 값에 따라서 릴레이모듈를 제어하면 되겠죠

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
   if(C>35){    //35도 이상이면 참
     digitalWrite(2, HIGH); //스위치 켜라
  }
  else{
     digitalWrite(2, LOW);  //스위치 꺼라
  }  

끝! 간단하게 느낌만 살렸습니다.

전체적인 코드를 살펴보면,

[아두이노] 릴레이 모듈+온도센서+DC모터 응용
void setup()
{
  Serial.begin(9600);
  pinMode(7, INPUT_PULLUP);
  pinMode(2, OUTPUT);
}
void loop()
{
  
  float V =fmap(analogRead(A0),0,1023,0,5); //map함수 원리를 이용한 다이렉트 Voltage계산
  float C = (V-0.5)*100;  //섭씨 C = (F-32)*1.8;
  
  if(C>35){    
     digitalWrite(2, HIGH); 
  }
  else{
     digitalWrite(2, LOW);   
  }   
}

float fmap(long x, long in_min, long in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) /(float) (in_max - in_min) + out_min;
}

4. 결과


[ 35도 이하일때 ] : 그림에서는 25도 위치



[ 35도 이상일때 ] : 그림에서는 57도 위치


동영상을 찍어야 했지만 그냥 이미지로 간단히 차이점을 화살표로 표시 해 놨습니다. 25도일때는 0.00A로 전류가 흐르지 않는 상태이고 57도 위치에서는 79.9mA로 전류가 흐르는 상태입니다. 그 차이점으로 확인해주시기 바랍니다.

코딩에서는 35도 이상이면 모터가 회전되게 되어 있습니다. 실제 시뮬레이터로 돌아가는 장면을 보고 싶으면 공개되 회로도에서 바로 시뮬레이터를 실행 시켜서 그 결과를 확인 할 수 있습니다.


링크된 곳으로 가면 위 그림과 같은 창이 뜹니다. 거기서 1번 눌러주셔서 2번의 시뮬레이터 실행버턴의 창이 뜹니다. 그리고 2번을 누르시면 실행을 시킬 수 있습니다. 한번 온도조절을 해보시고 결과를 확인해 보세요.

마무리


상상이 어렵지만 표현은 그렇게 어렵지 않습니다. 매번 상상의 나래를 펼치라는 멘트를 날리는 이유가 바로 그 이유입니다. 온도에 따라서 선풍기를 자동을 켜볼까라는 상상력이 뭔가를 만들어 냅니다.

여러분들도 어떤 원리를 배우게 되면 그 원리에서만 멈추지 말고 상상력을 더해서 새로운 뭔가를 창조할 수 있는 능력을 키우시면 엄청난 결과를 얻을 수 있을 거에요.

언제나 그럴듯이 상상의 나래를 펼쳐 보세요.

댓글()

[아두이노] 릴레이 모듈 제어

IOT/아두이노|2019. 3. 27. 09:00

[아두이노] 릴레이 모듈 제어 



오늘은 릴레이 모듈을 제어하는 방법을 살펴보도록 하겠습니다. 아두이노는 기본적으로 5V 전류에 동작하는 싱글보드입니다. 그래서 5V이상의 전류를 다룰 수가 기본적으로 없습니다. 그걸 대신해주는 역할이 릴레이 모듈입니다. 높은 전압에 동작하는 전자기기를 아두이노가 릴레이 모듈을 사용하여 쉽게 제어가 가능합니다. 예로 선풍기, 에어컨, 남방기기, 현광등 외 높은 전압을 사용하는 전자기기를 제어할 수 있습니다. 릴레이 모듈은 동작은 기본적으로 스위치 역활을 연상하시면 됩니다. 이제 릴레이 모듈에 대해서 살펴보도록 하죠.

1. 릴레이 모듈


제가 가지고 있는 모델입니다. 아래 그림에서 주의할 점은 부품마다 아두이노 연결하는 핀 위치가 다릅니다. 이 부품은 가운데가 Vcc(5V) 이지만 어떤 부품은 가운데가 제어선인 경우도 있고요. 어떤 부품은 가운데가 Gnd핀인 경우도 있습니다. 실전에서는 사용하는 모듈 부품 정보를 토대로 주의해서 선을 연결하시면 됩니다. 그리고 제어 가능한 전류 표시는 위면에 잘 나와 있으니깐 본인이 제어하고자 하는 전류의 크기를 잘 체크하시고 사용하시면 되겠습니다.


이건 제가 사용하는 부품의 기준으로 설명을 드리겠습니다. 이부분은 다른 부품일때 위와 같이 연결하시면 작동하지 않습니다. 그냥 원리를 이해하는 수준으로 읽어주시기 바랍니다. 왼쪽은 실제 높은 전압을 사용하는 전류를 가운데 공용선에 연결해서 그 전류가 Output1, Output2로 흘러 갈지를 릴레이 모듈에서 제어를 하게 됩니다. 오른쪽은 아두이노와 연결하는데 Vcc(5V), Gnd 핀은 그대로 연결하시면 되고요. 제어선은 아두이노의 디지털핀(출력값)을 통해 릴레이 모듈을 제어하는 스위치 연활을 하게 됩니다. HIGH(5V) or LOW(0V) 값에 따라서 Output1과 Output2로 왔다 갔다 연결이 됩니다. 가령, Output1에 연결되어 전류가 공급되고 있다면 제어선값이 반대값이 입력되면 Output1의 연결이 끊어지고 Output2에 연결됩니다.


그림을 못 그리지만 대충 그림판에서 느낌만 그려봤네요. 가령 초기상태로 0V일 때 왼쪽 그림의 모습이라고 했을때 5V가 제어선으로 입력되면 선이 위쪽으로 붙게 됩니다. 즉, 0V일 때 아래선에 붙게 되고 5V일때 윗 선에 붙는다고 생각하시면 됩니다.

대충 어떤식으로 모듈이 움직이는지 아시겠지요.

[ 가상시뮬레이터에서 사용할 Relay DPDT 모듈 ]


처음 이것을 실험할 때 가장 애 먹은 주제입니다. 2쌍으로 위쪽과 아래쪽으로 선 연결이 나뉘는데요. 선 연결은 윗면의 그림 모양으로 되어 있는 데로 선을 연결하시면 됩니다.


대충 A,B가 한쌍이고, C,D가 한쌍입니다. 표기가 안된 쪽은 아두이노에 연결하시면 됩니다. 하나는 제어선 하나는 Gnd선입니다. 회로도 구성을 보시면 아마 쉽게 이해가 가실꺼에요.

2. 회로도 구성


  • 준비물 : relay DPDT 1개, Light bulb 2개, Power Supply, 아두이노우노
  • 내용 : Light bulb를 교대로 깜박이게 할 수 있도록 회로도를 구성해보자.


참고로 위면은 보시고 선은 연결하세요. 첨에 반대로 연결했다가 삽질을 했네요. 글자가 뒤집어지게 회전 시키고 선을 연결하세요. 정면은 오른쪽이 아두이노우노랑 연결해야 하거든요. Relay DPDT 모듈에 선 핀을 어떻게 연결해야할지 아시겠지요. 어느선이 전원선이고 어느선이 출력선인지요. 가상시뮬레이터에서 딱히 연결할 부품이 없어서 백열등 같은 모양의 전등을 연결해봤네요. Power Supply에서 10V의 전류로 불이 들어온다고 가정한 회로도 입니다.

3. 코딩


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : LED 깜박이는 소스를 이용해서 그 값을 제어값으로 해서 Light bulb 깜박이게 해보자.
  • 참고소스 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

따로 수정할 부분은 없습니다. 그냥 기본적인 LED 깜박이기 소스를 그대로 적용할 예정입니다.

[LED 깜박이기 기본 소스]

void setup() {
pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000); 
}

13번 핀값이 HIGH와 LOW를 1초 단위로 출력되잖아요. HIGH or LOW일 때 어떤 결과가 나오는지 보도록 하죠.

4. 결과


[ digitalWrite(13, HIGH); ]



[ digitalWrite(13, LOW); ]


[실험 촬영 영상]
위에 소개한 릴레이모듈에서 초기값(0V)이 Output2로 연결되었네요. 제어선(13번) 핀에서 HIGH(5V) 상태가 되면 Output1으로 선이 연결됩니다. 제어선(5번) 핀에 HIGH or LOW의 상태에 따라서 Output1과 Output2로 교차해서 연결되어 두개의 LED가 깜박이게 되는 효과를 보여주네요.


마무리


릴레이 모듈이 일상생활에서 꽤 유용한 부품으로 활용 분야가 넓습니다. 예를 들면은 온도 센서가 있습니다. 온도에 따라 낭방기기를 On/Off 시킬 수 있겠죠. 또는 wifi 모듈과 연동한다면 집 밖에서도 인터넷을 통해서 집안 전자기기들을 작동시킬 수도 있겠죠. 전류를 컨트롤 할 수 있다는 것은 엄청난 일을 할 수 있습니다. 단순히 On/Off라고만 생각하시면 안되고 그냥 리모콘 정도의 사고에 머물면 안되고 다른 부품들과 연동했을때 원격으로 제어 하거나 자동 제어를 할 수 있는 시스템을 구축할 수 있기 때문에 그 활용도는 무궁무진 합니다.

한번 상상의 나래를 펼쳐 보세요.


댓글()

[아두이노] 2진수 계산법 암기

IOT/아두이노|2019. 3. 26. 09:00

[아두이노] 2진수 계산법 암기 



오늘은 간단히 진법 변환에 대해 살펴보도록 하겠습니다.

1. 10진수


123(10진수) => 우리가 사용하는 정수입니다.
123의 10진수 :


2. 2진수 변환


123을 2진수로 변환 : 123을 2로 나눈 나머지를 나열하는 숫자인데 화살표 방향으로 순서대로 나열하시면 됩니다. 이 과정은 2보다 작은 숫자가 나올때까지 나누게 됩니다.


123(10진수)가 1111011(2진수)로 표현됩니다. 마지막 2보다 작은 숫자가 나올때 그 몫도 포함해서 역순으로 순차적으로 나열하시면 됩니다. 처음 나눈 나머지는 오른쪽 첫번째 입니다.

1111011(2) 값을 다시 10진수 변환


1111011(2)를 10진수로 변환을 통해 2진수 값이 정확히 표현 되었는지 확인 과정이 필요하겠죠.


2진수 숫자를 다시 10진수화를 통해 정상적으로 계산된 것을 확인 하셨죠. 여기서 숫자가 규칙적으로 증가하는 것을 느끼셨을 거에요.


2의 몇승으로 이렇게 순차적으로 증가합니다. 그래서 2진수 계산 할 때 해당 위치에 값이 1이면 2의 자릿수승값으로 계산하시면 쉽게 암산이 가능합니다.

가령, 1101(2) 수가 있으면 오른쪽 자리부터 순서대로 해당 위치값이 1인 자리에 2의 자릿수 승값을 다 더해주면 됩니다. 이렇게 '1+4+8=13' 표현 하시면 됩니다.

10진수와 2진수 변환을 이제 쉽게 할 수 있겠죠.

2진수 소수점 변환


예) 0.125(10) 10진수가 있을때 2진수로 변환은 계속 곱하기 2을 해주면 됩니다. 그리고 소수점 이상 반올림 한 숫자는 버리기고 다시 2를 곱해줍니다. '1.0'이 될때까지 계속 소숫점 수에다 2를 곱해줍니다. 그리고 반올림한 숫자가 '0' or '1'이 나오는데 순서대로 나열하시면 됩니다.


위 그림처럼 0.25에서 '0'을 0.5에서 '0'을 1.0에서 '1'을 취합니다. 정수일때는 역순으로 나열하지만 소수점일때는 정순으로 나열합니다. 그래서 '001' 이 나오게 됩니다.

0.001(2) 값을 다시 10진수로 변환



위 표처럼 계산이 이루어지고 원래의 십진수 '0.125' 가 나오는 것을 확인이 되시죠. 여기서도 숫자의 규칙이 있습니다. 아래의 표처럼 반대로 2의 -몇승으로 증가합니다.


숫자가 2의 –4승까지는 잘 외워지는데 그 담부터 햇갈리실꺼에요. 0.5에서 –1승씩 증가할 때마다 0.5를 곱해준다고 생각하시면 됩니다.

종합)

123.125라는 10진수가 있다면 이 수를 2진수로 변환 하시오?

어떻게 할까요. 소수점을 기준으로 둘을 나눠서 계산하시면 됩니다.


3. 왜! 2진수를 배워야 하느냐!


아두이노를 다루다 보면 아누이노우노에 저장할 수 있는 공간이 그리 많지 않습니다. 단순한 표현은 그냥 해도 되지만 좀 복잡한 로직을 설계하다보면 자원공간이 부족합니다. 그렇게 때문에 최대한 효율적으로 코딩이 필요할 때 사용하기 아주 좋습니다.

예를 들어, LED 8개를 제어 한다고 생각해보죠. '1'은 HIGH, '0'은 LOW라고 할때

byte LED[8] = {1,1,0,0,1,0,0,1};

이렇게 표현하면 1byte 저장공간이 총 8byte의 공간이 필요합니다. 하지만 이걸 2진수로 이용한다면 어떻게 될까요.

byte LED = 0B11001001;

이렇게 1byte로 표현이 가능해집니다. 왜! 2진수를 배워야 하는지 이해를 하셨죠.

그리고, 패턴을 길게 만들거나 코딩이 표현하기 복잡할 때 2진수로 오히려 편하게 표현이 되는 경우가 있습니다. 그럴때 활용하기 위해서 2진수를 배워두셔야 합니다. 그리고 2진수로 코딩하다보면 좀 타이핑 글자수가 늘어날 수 있는데 그게 불편할 때는 10진수나 16진수로 변환해서 표현해도 됩니다. 다 같은 값을 가리키고 저장하기 때문입니다. 아두이노가 알아서 2진수값으로 해독하니깐요. 우리가 시각적인 눈으로 10진수 형태로 보는 것일 뿐 아두이노는 2진수 형태로 전류의 on/off로 해독하기 때문에 신경 쓸 필요가 없습니다.

4. 8진수와 16진수


계산법은 동일합니다. 8의 몇승이든 16의 몇승이든 자릿수에 맞춰서 계산하면 됩니다. 여기서 16진수만 알아 두시면 좋습니다.

우선, 알아두어야 할 것은 BCD(8421) 코드표입니다. 숙지하셔야 합니다.


BCD 8421 코드표는 "0001 + 0001 = 0010" 이렇게 숫자가 덧셈으로 반올림 된다고 생각하시면 돼요.

그리고 이것을 어떻게 다 외우냐고 하실 수 있습니다. 컴퓨터관련 학과에서 처음 공부할때 암기하는 표입니다. 그냥 외우시면 안외워집니다. 간단히 자릿수 2진수 "0001, 0010, 0100, 1000" 이렇게 해당된 자리만 암기하시면 됩니다. {1, 2, 4, 8} 어디서 많이 본 숫자의 규칙이죠. 2진수의 2의 승의 차릿수입니다. 가령 8은 '1000' 앞 뒤자리는 0111과 1001이겠죠 7과 9가 됩니다. 이런식으로 4개의 2진수 규칙을 숙지하고 전후자리의 값으로 더하고 빼주면 쉽게 숫자로 표현이 가능합니다.
그리고, '1010' 부터는 'A' 알파벳 시작이라고 생각하고 몇자리 안되니깐 더해주시면 됩니다.

8진수, 16진수 변환법


00110001(2) 이진수가 있습니다. 8진수, 16진수로 변환할려면 아래 표처럼 묶어서 계산하시면 아주 쉽게 변환할 수 있습니다. 2진수로 표현하면 어느 진수로든 쉽게 변환이 가능합니다.


여기서 참고하실것은,


이렇게 진수값 앞에 기호를 표기해야지 컴퓨터가 2, 8, 16진수구나 하고 해독합니다.

결과


'0B00110001(2)', '061(8)', '0x31(16)'로 표현이 결과가 49라는 10진수 값이 출력되는 것을아래 그림에서 컴파일 된 그림으로 확인하시거나 ideone 사이트 가셔서 직접 그 결과가 나오는지 코딩해 보세요. 다른 값들로 표현해보시는 것도 좋겠죠.


마무리


오늘 포스팅은 수학시간이 된 것 같네요. 컴퓨터 전공하면 젤 먼저 배우게 되는 기초 계산법입니다. 그리고, 이 기초 계산법은 가장 많이 사용하는 계산법이기도 하고요.

아두이노에서 10진수를 2진수로 표현하면 bit로 쪼개 계산에 활용하기 좋습니다. bitRead(x,n)함수로 원하는 위치의 비트 추출을 쉽게 할 수 있기 때문이지요. 그래서 활용 범위는 무척 넓습니다. 이번에 진수 계산법을 제대로 배워 두시면 아두이노에서 꽤 유용하게 써먹을 수 있을거에요.

지난시간에 배운 3x3x3 CUBE LED에서 편하게 패턴을 만들게 활용을 했죠. 7 Segment LED에서도 잘 써먹는 방식이고요. 다른곳에서도 비트값으로 제어하는 부품에서도 잘 사용하니깐 잘 알아두시기 바랍니다.

댓글()

[아두이노] 3x3x3 CUBE LED 패턴 놀이

IOT/아두이노|2019. 3. 25. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 놀이 



3x3x3 CUBE LED로 한주를 그냥 보내 버렸네요. LED 제어에 가장 재밌는 부분이라서 설명해야 할 것이 산더미 인데 더이상 했다가는 너무 한 주제로 무리한 것 같아서 다음에 비슷한 주제 나오면 또 신나게 달려볼까 합니다. 주말이라서 오늘은 편하게 휴식하는 맘으로 지난시간에 완성한 함수를 이용해서 패턴만 만들어서 재밌게 표현하고 놀았습니다. 위에 링크된 공개된 회로도를 여러분들의 계정에 복사하셔서 패턴만 워드나 기타 문서 편집기에서 표로 만들어서 표현하고 싶은 패턴을 색칠하시고 패턴을 완성하시면 그걸 시뮬레이터로 테스트 해보세요. 그냥 오늘은 표로 책실하는 놀이나 하고 그 결과를 시뮬레이터로만 돌려 보면 휴식을 취합니다.

1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


완성 좀 더 그럴싸 하게 표현하려고 좀 노력했네요. 일자모양의 LED를 제어하니깐 느낌이 안 살고해서 큐브 모양을 한번 제대로 보여드리기 위해서 최대한 큐브모양에 가깝게 표현 했네요.

2. 패턴 만들기





위 표를 책칠한 위치의 자리를 잘 생각하셔서 2진수로 표현하고 그 것을 여기다 에 올리면 너무 길어질 것 같아서 10진수로 다시 표현했네요.

unsigned int layerPattern1[6] ={325,170,16,341,186,511}; //1층
unsigned int layerPattern2[6] ={7,56,448,73,146,292}; //2층
unsigned int layerPattern3[6] ={273,146,84,56,63,511}; //3층

세줄로 위 표를 색칠한 패턴을 완성했습니다.


3. 코드 수정


  • layerPattern1[],layerPattern2[],layerPattern2[] 이부분은 패턴이 저장된 배열변수로 베이스 소스에서 이부분만 수정

  • int m_pattern = 6; : 패턴수가 6개라서 6으로 수정

  • int m_delay[9] ={30,30,30,30,30,30}; //유지시간은 패턴 6개이고 30이라는 시간만큼 유지

끝! 지난시간의 소스에서 이부분만 수정하면 나머지 그대로 사용하시면 됩니다. 즉, 3x3x3 CUBE LED은 위 세개만 변경하시면 원하는 패턴을 다 만들 수 있습니다.


[완성 소스]

int layer[3] = {A0,A1,A2};
int room[9] = {2,3,4,5,6,7,8,9,10};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수


//패턴 정수로 표현
unsigned int layerPattern1[6] ={325,170,16,341,186,511}; //1층
unsigned int layerPattern2[6] ={7,56,448,73,146,292}; //2층
unsigned int layerPattern3[6] ={273,146,84,56,63,511}; //3층

int m_pattern = 6; //패턴수
int m_delay[9] ={30,30,30,30,30,30}; //패턴유지시간

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT); //호실 선언
  }
}
void loop()
{
  for(int i=0;i<m_pattern;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<m_delay[i];j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      LEDSetting(layer[0], layerPattern1[i]);  //1층 i번째 패턴        
      LEDSetting(layer[1], layerPattern2[i]);  //2층 i번째 패턴        
      LEDSetting(layer[2], layerPattern3[i]);  //3층 i번째 패턴        
    }  
  }   
}    
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(5);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

4. 결과


[ 가상시뮬레이터 결과 ]
가상시뮬레이터로 돌리니깐 느낌이 아직도 잘 안사네요. 실제 제작한분 있으면 거기에 이 소스를 대입하면 좀 화려할 듯 싶네요.



마무리


주말이라서 새로운 주제로 나가기 좀 그래서 휴식하는 맘으로 지난시간에 완성한 소스에서 재밌는 패턴을 만들어서 시뮬레이션하는걸로 끝냈습니다.

사실 주말에 Steem API가 땡겨서 맨땅에 해당 하면서 javascript로 데이터를 불러오는 것 까지는 성공했는데 함수들을 정확히 다 이해를 못해서 구글 검색을 통해 예제만 신나게 찾고 있네요. 함수 래퍼런스가 너무 부실해서 C언어 지식으로 대충 때려 맞추고 있네요. 라즈베리파이에 파이썬 3.5가 깔려서 Steem 라이브러리를 설치하고 정보 불러오는 것 까지도 성공했는데 파이썬으로 접근하는 함수나 명령문들이 아직은 자료가 부족해서 기본정보만 접근하는 것만 성공하고 때려치우고 javascript로 공부하느라 아두이노를 잠시 주말은 외도하고 간단히 패턴 놀이로 포스팅 합니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본

IOT/아두이노|2019. 3. 24. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 완성본



지난 시간의 포스팅으로 끝낼려고 했는데 왠지 이렇게 마무리 하면 좀 찜찜한 기분이 들어서 좀 더 완벽한 코딩을 만들어 놓고 마무리 짓는게 더 나을 것 같아서 추가로 포스팅을 합니다. 3x3x3 CUBE LED를 마무리 하려다가 문득 코딩이 순간 머리속에서 스쳐 지나가서 간단하게 완성본을 만들어 봤네요.


1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


2. 패턴 코딩 시행 착오


만든 함수

  • pattern(사용층1, 사용호수1, 유지시간)
  • pattern(사용층1, 사용층2, 사용호수1, 사용호수2, 유지시간)
  • pattern(사용층1, 사용층2, 사용층3, 사용호수1, 사용호수2, 사용호수3, 유지시간)
    이렇게 표현해서 원하는 사용층만 사용하도록 바꿨습니다.
void pattern(int layer1,int room1,int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);   
  }  
}

void pattern(int layer1,int layer2,int room1, int room2, int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);
    LEDSetting(layer2,room2); 
  }  
}

void pattern(int layer1,int layer2,int layer3,int room1,int room2, int room3,int m_delay){
  for(int i=0;i<m_delay;i++){ //for문이 delay()함수 효과    
    LEDSetting(layer1,room1);
    LEDSetting(layer2,room2);    
    LEDSetting(layer3,room3);    
  }  
}
  • LEDSetting(사용층, 사용호수) : 기본적으로 LED에 전원을 공급해서 전류가 0.01초 만큼 공급되도록 세팅했습니다.
void LEDSetting(int layer, int room){
  digitalWrite(layer, LOW); //층 개방
  digitalWrite(room, HIGH); //호실 개방 
  delay(10);
  digitalWrite(room, LOW);   //호실 닫힘
  digitalWrite(layer, HIGH); //층 닫힘  
}

위와 같은 함수를 만들었네요. 사용층 수에 따라서 인수가 달라질 것을 감안하여 오버로드 함수로 표현을 이용했습니다.

로직을 짜면,

void loop()
{
  //Red LED만 순차적 릴레이
  for(int i=0;i<9;i++){ 
    pattern(layer[0],room[i],10);    
  }     
  
  //RED, BLUE만 동시 서로 역순 순차적 릴레이
  for(int i=0;i<9;i++){ 
    pattern(layer[0],layer[1],room[i],room[8-i],20);    
  }  
  
  //RED, BLUE, GREEN LED 모두 순차적 릴레이 
  for(int i=0;i<9;i++){ 
    pattern(layer[0],layer[1],layer[2],room[8-i],room[i],room[i],5);    
  }  
}

[ 결과 ]



결과는 짧은 동영상에서 보는 것과 같이 만족스럽게 다양한 패턴으로 동작의 결과를 얻었습니다.

완성되었다 싶었는데 문제점이 발견 되더군요. 이 방식을 사용하면 동시에 제어를 하더라도 각층의 LED은 1개만 컨트롤 할 수 밖에 없다는게 문제점이네요. 여기서, 딜레이 시간을 짧게해서 연속으로 다른 호실에 불이 들어오게 하면 되는데 이렇게 되면 또, LEDSetting()함수안에 delay(10)이 같은 층이여도 반복되니깐 그 시간이 누적되면 동시에 불이 들어오는 착시현상이 사라지게 되는 문제에 봉착 됐네요. 그래서 다시 원점에서 코딩을 시작하게 되었네요.

2. 완성된 패턴 코딩 설명


배열변수에 패턴 저장

각층의 패턴을 아예 16bit로 만들었네요. '0B' 표시는 2진수라는 표현입니다. 시각적으로 불이 들어오는 위치를 '1'이고 불이 안들어오는 위치를 '0'으로 나눴네요. bit로 쪼개면 변수 크기을 최소화 하고 효율적인 코딩을 할 수 있어서 사용 하였습니다. 가령, '0'과 '1'을 개별적으로 저장 변수를 만들경우 패턴이 늘어날수록 변수저장공간이 부족한 아두이노에게는 좀 문제가 있겠죠. 그래서 bit로 쪼갠 정보를 많이 이용합니다. LED의 상태를 '0'과 '1'로만 표현이 가능하니 가장 이상적인 표현이라고 할 수 있습니다.

아래 그림만 보면 '1'이 나타내는 곳에 LED가 불이 들어오니깐요. 대충 어떤식으로 LED가 켜지는지 쉽게 알 수 가 있겠죠.

unsigned int layerPattern1[9] ={ //1층 패턴
  0B0000000000000001, 
  0B0000000000000010,
  0B0000000000000100,
  0B0000000000001000,
  0B0000000000010000,
  0B0000000000100000,
  0B0000000001000000,
  0B0000000010000000,
  0B0000000100000000
};
unsigned int layerPattern2[9] ={ //2층 패턴
  0B0000000100000000, 
  0B0000000010000000,
  0B0000000001000000,
  0B0000000000100000,
  0B0000000000010000,
  0B0000000000001000,
  0B0000000000000100,
  0B0000000000000010,
  0B0000000000000001
};
unsigned int layerPattern3[9] ={ //3층 패턴
  0B0000000100000001, 
  0B0000000010000010,
  0B0000000001000100,
  0B0000000000101000,
  0B0000000000010000,
  0B0000000000101000,
  0B0000000001000100,
  0B0000000010000010,
  0B0000000100000001
};

또는, 2진수 계산법을 아신다면 표현은 인간이 표현하기 쉬운 숫자로 표기가 가능합니다.

//1층 패턴
unsigned int layerPattern1[9] ={1,2,4,8,16,32,64,128,256};

//2층 패턴
unsigned int layerPattern2[9] ={256,128,64,32,16,8,4,2,1};

//3층 패턴
unsigned int layerPattern3[9] ={258,130,68,40,16,40,58,130,258};

2진수로 표현된 것을 10진수로 변환하면 우리가 사용하는 일반 숫자로 나타낼 수 있어요. 3x3x3 CUBE LED은 한층의 9개의 LED를 사용하기 때문에 비트 한자리씩 총 9개의 비트자리만 사용하면 됩니다. 그래서 표현 숫자는 0~511 숫자로 9개의 LED 패턴을 만들어 낼 수 있습니다. 중요한것은 어느 숫자가 어느 LED인지는 쉽게 알 수 없습니다. 10진수 정수를 2진수로 변환해야지 그 위치에 LED에 불이 들어오는 구나 생각되는 것이죠. 10진수 형태로 나타낸 패턴은 포털 검색에서 2진수 변환이라는 키워드를 치셔서 왜 저렇게 숫자로 나타내는지 찾아보세요. 진수변환이 주 목적이 아니니깐 위에 2진수 표현한 배열변수를 그대로 사용하겠습니다.

잘 이해가 안가는 분들을 위해서 아래 그림을 참고하시면 됩니다.


그림에서 아래 1부터 9까지의 숫자는 비트 자릿수 입니다. 그냥 1호실부터 9호실로 생각하시면 돼요. 참고로 역순으로 표현한 이유는 나중에 bitRead()함수를 이용해서 1비트씩 읽는데 bitRead(숫자,읽을위치)로 하는데 읽을 위치가 '0'이면 숫자의 첫번째자리의 비트를 읽어오기 때문입니다. "나는 정순으로 배치하고 '8'번째 위치부터 역순으로 읽으면 되잖아!" 하시는 분들은 그렇게 하셔도 됩니다. 이 표현은 제가 코딩하는 스타일의 편의 상 이렇게 한것뿐인점 참고해 주세요.

그런데 앞에 '0'의 갯수가 더 많은 이유가 뭐지하고 생각하시는 분들도 있을꺼에요. 1byte는 8bit로 구성되어 있고 2byte은 16bit가 됩니다. unsigned int 자료형을 2byte의 자료 공간을 갖기 때문에 표기는 전체 16bit를 다 표현해야겠죠. 그래서 9bit의 값만 사용하지만 앞에 사용하지않는 비트는 전부 '0'으로 채우게 됩니다. 쉽게 생각하시면 오른족 9bit만 3x3x3 CUBE LED에 이용된다고 생각하시면 됩니다.

그리고 패턴을 만들때 워드창에다가 표를 만들어서 위 그림처럼 색칠하기를 해보세요. 그리고 색이 칠해진 자리는 '1'로 표기하고 색이 안칠해진 자리는 '0'으로 표기하여 16bit를 표현하시면 됩니다. 어렵지 않겠죠.

1(10진수) => 0B0000000000000001(2진수)와 같습니다. 둘 다 똑같은 값인점 참고해주세요. 10진수는 인간이 이해하기 쉬운 숫자라면 2진수는 컴퓨터가 이해하기 쉬운 숫자라고 생각하시면 됩니다.

예) 표에서 녹색 1패턴은 어떻게 표현 할까요.

0B0000000100000001

이렇게 해서 패턴을 만드는 것은 어렵지 않겠죠


만든 함수

  • LEDSetting(사용층핀, 사용층패턴) : 사용층의 모든 호실의 패턴를 인수로 넘겨줘서 출력한다.

여기서 층 개방/닫힘은 위아래로 묶어준뒤에 그안에 for문으로 9개 LED의 상태를 개방하고 0.01초 동안 불이 들어오게 한뒤에 다시 for문을 통해 9개 LED를 닫습니다. LED에 불이 들어올때 착시효과 만들기 위한 LED 불이 최소한 켜져있도록 만든 기준 함수입니다. 쉽게 말해서 특정 위치에 LED가 최소한으로 전류가 공급될 시간을 주기 위한 함수라고 생각하시면 됩니다.

void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //패턴 호실 개방     
  }
  delay(10);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //모든 호실 닫힘    
  }
  digitalWrite(layer, HIGH); //층 닫힘  
  
}

아두이노 IDE에서 코딩할 때 기본적으로 라이브러리에서 제공되는 bitRead()함수를 1bit씩 추출을 쉽게 할 수 있어서 편하게 표현을 할 수 있었네요.

x은 읽을값이고 n은 읽을 위치입니다. 참고로 읽는 순서는 오른쪽에서 부터입니다. 그래서 1호실 개방하는데 '0B0000000000000001'로 표현했다면 bitRead(0B0000000000000001,0) 함수로 0번째(첫번째) 위치 1을 추출하게 됩니다.


로직을 수행하면,

int m_pattern = 9; //패턴수
int m_delay = 20; //유지시간

void loop()
{

  for(int i=0;i<m_pattern;i++){     
    for(int j=0;j<m_delay;j++){
      LEDSetting(layer[0], layerPattern1[i]);          
      LEDSetting(layer[1], layerPattern2[i]);          
      LEDSetting(layer[2], layerPattern3[i]);          
    }
  
  }
}

1층(layerPattern1[9]), 2층(layerPattern2[9]), 3층(layerPattern3[9])에 기록된 9개 패턴들을 간단히 출력하는 로직입니다.

3. 패턴 코딩 완성본


int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

unsigned int layerPattern1[9] ={ //1층 패턴
  0B0000000000000001, 
  0B0000000000000010,
  0B0000000000000100,
  0B0000000000001000,
  0B0000000000010000,
  0B0000000000100000,
  0B0000000001000000,
  0B0000000010000000,
  0B0000000100000000
};
unsigned int layerPattern2[9] ={ //2층 패턴
  0B0000000100000000, 
  0B0000000010000000,
  0B0000000001000000,
  0B0000000000100000,
  0B0000000000010000,
  0B0000000000001000,
  0B0000000000000100,
  0B0000000000000010,
  0B0000000000000001
};
unsigned int layerPattern3[9] ={ //3층 패턴
  0B0000000100000001, 
  0B0000000010000010,
  0B0000000001000100,
  0B0000000000101000,
  0B0000000000010000,
  0B0000000000101000,
  0B0000000001000100,
  0B0000000010000010,
  0B0000000100000001
};
int m_pattern = 9; //패턴수
int m_delay[9] ={20,20,20,20,20,20,20,20,20}; //패턴유지시간

void setup()
{  
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT); // 층 선언
    digitalWrite(layer[i], HIGH);  // 각층 닫기
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT); //호실 선언
  }
}

void loop()
{

  for(int i=0;i<m_pattern;i++){  //순차적으로 패턴수만큼 동작
    for(int j=0;j<m_delay[i];j++){  //m_delay 시간만큼 해당 패턴을 유지한다.
      LEDSetting(layer[0], layerPattern1[i]);  //1층 i번째 패턴        
      LEDSetting(layer[1], layerPattern2[i]);  //2층 i번째 패턴        
      LEDSetting(layer[2], layerPattern3[i]);  //3층 i번째 패턴        
    }  
  }
}
    
void LEDSetting(int layer, unsigned int state){
  digitalWrite(layer, LOW); //층 개방
  for(int i=0;i<9;i++){    
    digitalWrite(room[i],bitRead(state, i)); //호실 개방     
  }
  delay(10);
  for(int i=0;i<9;i++){     
    digitalWrite(room[i], LOW);   //호실 닫힘   
  }
  digitalWrite(layer, HIGH); //층 닫힘    
}

4. 결과


[ 가상시뮬레이터 결과 ]



[실제 아두이노 결과 ] 3층은 선이 부족해서 3층 패턴이 맘에 들어서 2층으로 옮겨왔고 1층 패턴은 그대로 뒀네요.


실제 결과는 LEDSetting()함수내 최소한 불이 켜져있을 시간을 delay(10)으로 했는데 3층의 딜레이 시간이 총 0.03초 정도가 되더군요. 그외 명령문들 수행결과까지 하면 딜레이가 좀 길어저 깜박거림이 좀 심해져 눈의 피로도를 유발하더군요. 어쩔 수 없이 delay(10)을 delay(5~7)사이로 돌렸네요. 그렇게 되면 패턴이 돌아가는 시간이 빨라져서 유지시간 m_delay[9] 배열변수의 값들을 약간씩 늘여서 깜박이는 속도를 다시 보정 하였습니다.

마무리


어제 포스팅을 올리고 나서 뭔가가 너무 아쉬워서 다른 각도로 접근하여 완성본을 만들어 봤네요.
상단에 layerPattern1[], layerPattern2[], layerPattern3[] 배열변수만 나중에 원하는 패턴을 '0'과 '1'로만 만들어 내고 패턴수와 그 패턴을 유지하는 시간만 만들어 내면 나머지는 그대로 사용하시면 됩니다. 완성본이라고 하지만 아직은 즉흥적으로 코딩한 거라 좀 아쉬운 점이 많습니다.

좀 더 코딩부분을 다뤄서 패턴 클래스를 만들고 하면 체계적으로 제어가 되지 않을까 생각이 들었는데 더이상 가는건 아닌것 같아서 너무 깊게 들어가는 것보다 다른 주제로 넘어가는게 좋을 것 같아서 여기서 멈추겠습니다.

이제는 장시간에 걸쳐서 다룬 3x3x3 CUBE LED를 마무리하도록 하겠습니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 II

IOT/아두이노|2019. 3. 23. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 II



지난 시간에 3x3x3 CUBE LED 패턴 포스팅에서 패턴을 만드는 것과 그걸 일정시간 유지하는 방법을 배웠습니다. 그리고 2가지 패턴을 교차로 LED를 깜박이게 하는 것까지 했었죠. 이제는 패턴을 좀 더 길게 만들고 중복되는 코딩 부분을 제거하여 코딩을 최소화하는 과정을 배워 보겠습니다.


1. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


2. 패턴 코딩 과정


패턴 코딩 과정의 두번째 시간입니다. 지난시간의 코드에서 연장선상에서 이여가겠습니다.

[ 1층1호실2층2호실과 1층2호실2층1호실 교차 깜박이기]

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
    //1층1호실과 2층2호실
     for(int i=0;i<30;i++){ //for문이 delay()함수 효과
         digitalWrite(layer[0], LOW);  // 1층 개방
         digitalWrite(room[0], HIGH);  // 1호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[0], LOW);  // 1호실 닫힘
         digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
         digitalWrite(layer[1], LOW);  // 2층 개방
         digitalWrite(room[1], HIGH);  // 2호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[1], LOW);  // 2호실 닫힘
         digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
    
    //1층2호실과 2층1호실
    for(int i=0;i<30;i++){ //for문이 delay()함수 효과
        digitalWrite(layer[0], LOW);  // 1층 개방
        digitalWrite(room[1], HIGH);  // 2호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[1], LOW);  // 2호실 닫힘
        digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
        digitalWrite(layer[1], LOW);  // 2층 개방
        digitalWrite(room[0], HIGH);  // 1호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[0], LOW);  // 1호실 닫힘
        digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
}

여기서, 어느게 중복되고 있나요. 바로 for문 이하의 안에 명령문들이 두번 중복되는 것을 보실거에요. 2가지여서 2번 중복되었는데 10가지라면 10번 for문을 코딩해야한다면 정말 노가다 코딩이 되겠죠.

[ 중복코드를 외부함수로 ]

이제는 그 중복되는 부분을 차라리 외부로 빼서 호출하여 명령을 수행하도록 하여 코딩하는 양을 줄이도록 하겠습니다.

void pattern(int layer1, int layer2, int room1, int room2){
  for(int i=0;i<30;i++){ //for문이 delay()함수 효과
    digitalWrite(layer1, LOW); 
    digitalWrite(room1, HIGH);  
    delay(10);
    digitalWrite(room1, LOW);  
    digitalWrite(layer1, HIGH); 
  
    digitalWrite(layer2, LOW); 
    digitalWrite(room2, HIGH);  
    delay(10);
    digitalWrite(room2, LOW);  
    digitalWrite(layer2, HIGH); 
  }  
}

새롭게 함수를 만들었습니다. 그렇게 이 로직은 사실 기존의 코딩한 로직의 연장 선상으로 표현한 것입니다. 사실은 이렇게 하면 안되고 내부 로직도 변경하고 명령문도 새롭게 만들어야 합니다. 혼동을 최소화하기 위해서 이전 코드를 그대로 인용하여 표현했다는 점을 감안하시고 보셨으면 해요.

그러면, 어떻게 loop()함수에서 코딩이 될까요.

void loop(){
    pattern(layer[0],layer[1],room[0],room[1]);  //1층1호실, 2층2호실
    pattern(layer[0],layer[1],room[1],room[0]);  //1층2호실, 2층1호실
}

  • pattern(1층,2층,1층호실,2층호실) : 2층과 2호실에 제어.

코딩이 딱 두줄로 바뀌었지요. 그리고 loop()함수만 봐도 대충 의미를 이해하실꺼에요.

좀 길게 스토리를 만들어 볼까요. 가령 1층은 1호실부터 9호실까지 순차적으로 깜박이고 2층은 9호실부터 1호실까지 순차적으로 깜박이게 명령을 내려 볼까요.

void loop(){
    pattern(layer[0],layer[1],room[0],room[8]);  
    pattern(layer[0],layer[1],room[1],room[7]);  
    pattern(layer[0],layer[1],room[2],room[6]);  
    pattern(layer[0],layer[1],room[3],room[5]);  
    pattern(layer[0],layer[1],room[4],room[4]);  
    pattern(layer[0],layer[1],room[5],room[3]);  
    pattern(layer[0],layer[1],room[6],room[2]);  
    pattern(layer[0],layer[1],room[7],room[1]);  
    pattern(layer[0],layer[1],room[8],room[0]);     
}

그런데, 또 문제가 생겼네요. 중복 제거 코딩을 했는데 또 중복이 발생했네요. 불필요한 코딩이 늘어났는데 다시 해결해 볼까요. 이번에도 순차적이니깐 for문으로 처리해 버리죠.

void loop(){
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }
}

간단히 loop()함수 내 3출 코딩으로 줄어 들었네요. 이제 더 나아가 반대로 역순으로 깜박이게 하고 싶은 충동이 안느껴지나요. 위 for문에서 room[i],room[8-i]만 서로 바꿔주면 됩니다. 그래서 아래와 같은 완성된 코드만 만들어집니다.

void loop(){
 
  //for문으로 한줄 명령으로 릴레이  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }  
    
  //역순  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[8-i],room[i]);    
  }
}

만약에 이 명령문들을 일일히 수작업으로 명령을 다 코딩했다고 생각해보세요. pattern()함수를 릴레이 패턴 for문 당 9번 총 pattern()함수 내부의 코드를 18번 일일히 수정하면서 코딩해야 합니다. pattern()함수 내부 명령문들도 많은데 그 많은 명령문을 18번이나 중복해서 코딩해야 한다고 상상해보세요. 진정한 노가맨이 되겠죠.

프로그램 문법을 좀 배우면 표현력의 한계는 없고 남들보다 더 쉬운 코딩을 할 수 있습니다.

loop()함수안의 코드는 의미전달과 최소화만 해야합니다. 뭘 하는지만 전달해주고 세부적인 명령문들은 외부함수로 빼서 제어해야합니다. 그래야 나중에 코드를 수정하거나 다른사람들이 보기에도 편해집니다. 수작업으로 모든 명령문들을 일일히 loop() 함수에 다 집어 넣게 되면은 도대체 loop()함수 내에서 무슨 동작하는지 머리만 복잡해 집니다.

위 loop()함수 내 코드만 보더라도 pattern()함수를 for문으로 해서 순차적으로 뭘 표현했구나하고 쉽게 이해할 수 있습니다. 정확히 어떤 동작인지는 모르지만 pattern()함수라는 뭔지는 모르지만 9번 순차적으로 반복이 이루어졌고 그걸 또 역순으로 9번 순차적으로 진행했구나 정도는 쉽게 loop() 함수만 보면 알 수 있습니다. loop()함수의 동작은 pattern()함수는 아직은 뭔지 모르더라도 그걸 순차적으로 반복하고 또 역으로 반복했구나 쉽게 loop()함수의 동작을 이해 할 수 있습니다.

3. 소스


int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }

  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop(){

  //for문으로 한줄 명령으로 릴레이  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[i],room[8-i]);    
  }  

  //역순  
  for(int i=0;i<9;i++){
    pattern(layer[0],layer[1],room[8-i],room[i]);    
  }
}

void pattern(int layer1, int layer2, int room1, int room2){
  for(int i=0;i<30;i++){ //for문이 delay()함수 효과
    digitalWrite(layer1, LOW); 
    digitalWrite(room1, HIGH);  
    delay(10);
    digitalWrite(room1, LOW);  
    digitalWrite(layer1, HIGH); 
  
    digitalWrite(layer2, LOW); 
    digitalWrite(room2, HIGH);  
    delay(10);
    digitalWrite(room2, LOW);  
    digitalWrite(layer2, HIGH); 
  }  
}

4. 결과


어떻게 패턴 코딩을 진행했는지 녹화를 해 놓았으니깐 살펴보시고 마지막에 시뮬레이션 결과를 확인해 보세요. 실제 아두이노에서도 실행한 장면도 들어 있으니깐 보시고 결과물이 어떻게 나왔는지 확인해 주세요.

가상시뮬레이터에서 실험한 기록 일지

실제아두이노에서 실험한 영상

마무리


지난시간의 내용을 기반으로 오늘은 연장선상에서 이여갔습니다. 사실, 오늘 코딩한 것에도 문제점이 많습니다. 그것은 강제적으로 1층과 2층에 꼭 불이 하나는 들어와야 한다는 전재 조건이 있습니다. 그 부분을 수정하자면 pattern()함수 내부를 완전 수정해야하기 때문에 그냥 수정 안하고 그 문제점을 가지고 오늘 포스팅을 했네요. 오늘은 전달하고자 하는 바는 중복코드를 제거하는 방법입니다. 그리고 loop()함수의 명령문들은 뺄 수 있으면 외부 함수로 빼내고 하나의 묶음으로 묶어서 loop()내에서는 의미 전달코딩을 하면 보다 효율적인 코딩을 할 수 있다는 것을 보여 드리는게 목적입니다.

결론, loop()함수는 최소 동작의 의미만 표현하며 중복되는 부분은 외부함수로 빼서 표현의 최소화 입니다. 지난시간에는 서로다른 층의 다른 호실의 LED에 불이 들어오게 하는 방법과 패턴 LED가 일정시간 유지될 수 있도록 하는 방법을 배웠습니다. 이 두가지를 꼭 기억하셔서 다른 곳에서도 활용해 보세요.

마지막으로 상단에 가상 시뮬레이터에서 실험한 회로도가 공개 시켰네요. 혹시 회로도 만들기 귀찮은 분들을 위해서 패턴만 만들어보시라고 공개 했네요. 가셔서 계정이 있으면 복사하시면 본인 계정으로 회로도가 복사 됩니다. 거기서 편하게 편집하시면 됩니다.


댓글()

[아두이노] 3x3x3 CUBE LED 패턴 코딩 I

IOT/아두이노|2019. 3. 22. 09:00

[아두이노] 3x3x3 CUBE LED 패턴 코딩 I



지난 시간에 3x3x3 CUBE LED 코딩을 간소화 하는 방법을 살펴보았습니다. 오늘은 좀 더 깊게 들어가서 LED를 깜박이는 패턴을 만들어보는 시간을 갖도록 하겠습니다. 그냥 순차적으로 깜박이는 것은 for문 하나면 해결되지만 서로 다른 층과 서로 다른 호실에 동시에 불이 들어오게 하는 방법은 좀 다른 관점의 코딩이기 때문입니다. 그 이유는 동시에 서로 다른 층의 다른 호실에 불이 들어오게 할 수 없습니다. 왜냐면 각 호실은 같은 층의 연결되어 있고 각층은 동일 호실과 선이 연결되어 있기 때문입니다. 만약, 1층 1호실과 2층 2호실에 불이 들어오게 하려고 1층과 2층을 동시에 개방하면 1층 1,2호실과 2층 1,2호실에 LED가 켜지게 됩니다. 이 부분에 대해서 간단히 살펴보고 그걸 해결하기 위한 방법을 설명 드리겠습니다. 사실 힘들게 뻥판 노가다로 전선을 연결했는데 다시 원위치 시키기 아쉬워서 패턴이라는 내용으로 포스팅을 한편 더 작성하네요.


1층과 2층의 LED만 사용합니다. 전선부족으로 지난시간에 2,3층 3개의 LED에 선을 연결못했는데 3층선 일부를 2층선으로 옮겨와서 1,2층 선을 모두 연결한 상태에서 실험했네요.

1. 서로 다른 층과 호실에 동시에 불을 켤 수 없는가?


1층 1호실과 2층 2호실에 동시에 불이 들어오게 한다고 가정해 봅시다. 그러면 1층과 2층을 Gnd(-)로 개방해야하잖아요. 그런데 1층(Gnd)와 1호실(Vcc)로 해서 1층 1호실에 불이 들어옵니다. 하지만 2층 2호실의 경우도 동시에 2층(Gnd)와 2호실(Vcc)을 하게 된다면 아래와 같은 현상이 발생합니다.


1층 1,2호실과 2층 1,2호실에 불이 전부 들어오게 됩니다. 선이 층을 기준으로 모든 호실이 하나로 선(Gnd)이 연결되어 있고 호실을 기준으로 모든 층이 선(Vcc)로 연결되어 있기 때문입니다. 그래서 동시에 불이 들어오게는 불가능 합니다. 그럼 해결책이 없느냐고 물으신다면 바로 딜레이 시간을 줘서 1층과 2층을 짧은 시간에 개방하고 닫고 해서 1층1호실에 불이 들어오게 한뒤에 2층 2호실에 불이 들어오게 함으로써 착시현상이 일어나게 처리하면 해결 할 수 있습니다.


위 그림에서 A동작을 수행시 불이 충분히 켜질 시간(delay) 주고 난 뒤에 바로 B동작을 수행합니다. 참고로 A동작과 B동작의 딜레이 시간은 아주 짧아야 합니다. 착시현상으로 동시에 불이 들어오는 것처럼 느끼게 됩니다.


대충 위 그림처럼 착시로 동시에 불이 들어오게 보여야 합니다. 이제 어떻게 하는지 알아보도록 하겠습니다.

2. 3x3x3 CUBE LED 회로도 구성


  • 준비물 : Red, Blue, Green LED 각각 9개, 저항 220옴 3개, 아두이노우노, 뻥판
  • 내용 : 3x3x3 CUBE LED 형태를 표현하자.


힘들게 디자인 했으니 그대로 활용하겠습니다.

3. 패턴 코딩 과정


  • 사용함수 : pinMode(), digitalWrite(), delay()
  • 내용 : 간단히 2x2x2 CUBE LED를 순차적으로 깜박이기
  • 참고 : [아두이노] LED 제어

복습

  • pinMode(사용핀, 사용모드) : 사용핀을 INPUT/OUTPUT/INPUT_PULLUP 모드중 뭘로 사용할지를 정함.
  • digitalWrite(사용핀, 출력형태) : 사용핀을 HIGH(5V) or LOW(0V) 중 출력 형태를 정함.
  • delay(1000) : 1초(1000)를 대기한다.

3x3x3 CUBE LED 순차적 깜박이는 코드

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
  
  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);  
    for(int j=0;j<m_room;j++){
      digitalWrite(room[j], HIGH);  
      delay(100);
      digitalWrite(room[j], LOW);  
    }
    digitalWrite(layer[i], HIGH);  
  }
}

여기서, 다른 부분은 그냥두고 이부분만 수정하시면 됩니다.

  for(int i=0;i<m_layer;i++){
    digitalWrite(layer[i], LOW);  
    for(int j=0;j<m_room;j++){
      digitalWrite(room[j], HIGH);  
      delay(100);
      digitalWrite(room[j], LOW);  
    }
    digitalWrite(layer[i], HIGH);  
  }

이 명령은 순차적으로 깜박이는 로직입니다. 여기서 1층 1호실과 2층 2호실에 불이 동시에 들어오게 하려면 어떻게 해야 할까요. 1,2층 개방/닫힘 명령과 1,2호실 개방/닫힘 명령이 필요합니다.

  • 층 개방 : digitalWrite(해당층핀, LOW);
  • 층 닫힘 : digitalWrite(해당층핀, HIGH);
  • 호실 개방 : digitalWrite(호실핀, HIGH);
  • 호실 닫힘 : digitalWrite(호실핀, LOW);

[ 1층1호실과 2층2호실 불 들어오게 하기 ]

위 코드에서 layer[0]은 1층, layer[1]은 2층, layer[2]은 3층이고 room[0]은 1호실, room[1]은 2호실, room[2]은 3호실, room[3]은 4호실입니다. 배열변수로 그렇게 선언했기 때문에 이점을 생각하고 코딩을 해보도록 할까요.

먼저, 1층 1호실을 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[0], LOW);  // 1층 개방
digitalWrite(room[0], HIGH);  // 1호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[0], LOW);  // 1호실 닫힘
digitalWrite(layer[0], HIGH);  // 1층 닫힘

다음 2층 2호실에 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[1], LOW);  // 2층 개방
digitalWrite(room[1], HIGH);  // 2호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[1], LOW);  // 2호실 닫힘
digitalWrite(layer[1], HIGH);  // 2층 닫힘

결과는 다음과 같습니다.


[ 소스 ]

int layer[3] = {A0,A1,A2};
int room[9] = {10,9,8,7,6,5,4,3,2};
int m_layer = 3;  // 층 수
int m_room = 9; // 호실 수

void setup()
{
  for(int i=0;i<m_layer;i++){
    pinMode(layer[i], OUTPUT);
    digitalWrite(layer[i], HIGH);    
  }
  
  for(int i=0;i<m_room;i++){
    pinMode(room[i], OUTPUT);
  }
}

void loop()
{
  digitalWrite(layer[0], LOW);  // 1층 개방
  digitalWrite(room[1], HIGH);  // 1호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[1], LOW);  // 1호실 닫힘
  digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
  digitalWrite(layer[1], LOW);  // 2층 개방
  digitalWrite(room[0], HIGH);  // 2호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[0], LOW);  // 2호실 닫힘
  digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

[ 결과 ]



[ 1층2호실과 2층1호실 불 들어오게 하기 ]

위 코드를 반대로 표현하면 됩니다.

digitalWrite(layer[0], LOW);  // 1층 개방
digitalWrite(room[1], HIGH);  // 2호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[1], LOW);  // 2호실 닫힘
digitalWrite(layer[0], HIGH);  // 1층 닫힘

다음 2층 2호실에 불이 들어오게 명령을 내려 볼까요.

digitalWrite(layer[1], LOW);  // 2층 개방
digitalWrite(room[0], HIGH);  // 1호실 개방
delay(10); //0.01초 동안 불 켜기
digitalWrite(room[0], LOW);  // 1호실 닫힘
digitalWrite(layer[1], HIGH);  // 2층 닫힘

결과는 다음과 같습니다.


[ 소스 ]

... 생략

void loop()
{
  digitalWrite(layer[0], LOW);  // 1층 개방
  digitalWrite(room[1], HIGH);  // 2호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[1], LOW);  // 2호실 닫힘
  digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
  digitalWrite(layer[1], LOW);  // 2층 개방
  digitalWrite(room[0], HIGH);  // 1호실 개방
  delay(10); //0.01초 동안 불 켜기
  digitalWrite(room[0], LOW);  // 1호실 닫힘
  digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

[ 결과 ]



[ 1층1호실과 2층2호실의 불을 일정시간 단위로 깜박이기 ]

하나의 패턴은 그냥 loop()함수로 돌리면 그 패턴의 LED만 불이 들어옵니다. 둘 이상의 경우는 해당 패턴의 모양이 일정시간 유지해야 합니다. 하지만 연속해서 표현하면 아주 짧게 딜레이를 줬기 때문에 해당 패턴모양이 원하는 시간동안 유지하지 못하는 현상이 발생합니다. 또는 두가지 패턴이 하나의 패턴으로 겹치는 현상도 발생합니다. 그래서 하나의 패턴을 일정시간 유지시키기 위해서는 딜레이 함수와 같은 효과를 코딩으로 표현해야 합니다.

딜레이를 아주 짧게 줬기 때문에 짧게 패턴을 일정횟수만큼 반복하면 반복된 시간만큼이 해당 패턴의 모양을 유지하는 시간으로 만들어 낼 수 있습니다.

가령,

 for(int i=0;i<30;i++){ //for문이 delay()함수 효과
   digitalWrite(layer[0], LOW);  // 1층 개방
   digitalWrite(room[0], HIGH);  // 1호실 개방
   delay(10); //0.01초 동안 불 켜기
   digitalWrite(room[0], LOW);  // 1호실 닫힘
   digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
   digitalWrite(layer[1], LOW);  // 2층 개방
   digitalWrite(room[1], HIGH);  // 2호실 개방
   delay(10); //0.01초 동안 불 켜기
   digitalWrite(room[1], LOW);  // 2호실 닫힘
   digitalWrite(layer[1], HIGH);  // 2층 닫힘
}

표현을 하면 1층1호실은 0.01초만큼 불이들어온 후에 2층2호실은 0.01초만큼 불이 들어오게 됩니다. 총 합산하면 0.02초만큼 1층1호실과 2층2호실의 시간이 소요됩니다. 그걸 30회 반복한다고 했죠. 보는 수치상으로 계산하면 0.6초여야 동안 같은 패턴을 유지하게 된다고 생각하시면 됩니다. 그런데 실제로 돌리시면 0.06초동안 유지되는게 아니라 명령어 수행시간도 합산하면 미세하지만 좀 늘어나겠죠. 신경 쓸 필요는 없는 부분이고요. 30번 반복이란 예를 든 것일뿐 다른 값으로 어느정도 시간이 유지되는지를 테스트 해보고 원하는 시간으로 보정을 해보세요. for문 안에 delay(10)도 그냥 제 아두이노로 실험했을때 대충 정한 값이라 이 값이 크면 착시효과가 낮으니깐 어느정도 다른 값들을 대입해보면서 원하는 딜레이시간으로 보정해 해보세요.

[ 1층1호실과 2층2호실 패턴과 1층2호실과 2층1호실 패턴을 교차 ]

위 for문에 반대패턴을 코딩한걸 삽입하면 되겠죠.

[ 소스 ]

... 생략

void loop()
{
    //1층1호실과 2층2호실
     for(int i=0;i<30;i++){ //for문이 delay()함수 효과
         digitalWrite(layer[0], LOW);  // 1층 개방
         digitalWrite(room[0], HIGH);  // 1호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[0], LOW);  // 1호실 닫힘
         digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
         digitalWrite(layer[1], LOW);  // 2층 개방
         digitalWrite(room[1], HIGH);  // 2호실 개방
         delay(10); //0.01초 동안 불 켜기
         digitalWrite(room[1], LOW);  // 2호실 닫힘
         digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
    
    //1층2호실과 2층1호실
    for(int i=0;i<3[](http://)0;i++){ //for문이 delay()함수 효과
        digitalWrite(layer[0], LOW);  // 1층 개방
        digitalWrite(room[1], HIGH);  // 2호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[1], LOW);  // 2호실 닫힘
        digitalWrite(layer[0], HIGH);  // 1층 닫힘
  
        digitalWrite(layer[1], LOW);  // 2층 개방
        digitalWrite(room[0], HIGH);  // 1호실 개방
        delay(10); //0.01초 동안 불 켜기
        digitalWrite(room[0], LOW);  // 1호실 닫힘
        digitalWrite(layer[1], HIGH);  // 2층 닫힘
    }
}

[ 결과 ]

위 그림에서 보는것처럼 교차로 깜박이게 됩니다. 이런식으로 해서 패턴을 만들고 그 패턴을 일정시간동안 유지시킬 수 있게 되면은 다양한 패턴을 만들어 낼 수 있겠죠.

마무리


이번 포스팅은 원래 패턴 코딩을 한편으로 제작할려고 했던 내용입니다. 그런데 포스팅의 글을 쓰다가 보니 아직 제대로 코딩하는 법으로 들어가지 못했는데 분량이 장난 아니게 늘어났네요. 어쩔 수 없이 다음편으로 연장해야 할 듯 싶네요. 너무 길게 쓰면은 피로감이 밀려올 수 있으니 이번 포스팅은 패턴을 만들고 그 패턴을 일정시간 유지하는 것에서 마무리 하도록 하겠습니다.

다음편에서는 이렇게 패턴을 만들고 for문을 통해서 일정시간을 유지시키는 방법으로 매 패턴들을 표현한다면 코딩량이 엄청나겠죠. 단 두가지 패턴을 코딩했는데도 이정도인데 10개이상 되는 패턴을 만든다면 노가다 코딩이 되겠죠.

그래서 중복되는 부분을 제거하는 코딩을 다음 포스팅에 연재 하도록 하겠습니다. 오늘은 이렇게 패턴을 만드는 것과 일정시간 유지하는 걸 배웠으니 어떤 패턴을 만들지 머리속에서 구상해보세요. 사실 전체과정을 동영상으로 기록하고 실제 아두이노에서도 결과까지 다 준비 됐는데 그건 다음에 올리도록 하겠습니다.

댓글()