Arduino, 스텝모터

스텝모터 (Step Motor)

  • 펄스 모양의 전압에 의해 일정 각도(스텝 수) 만큼 회전하는 모터.
  • 회전 각도는 입력 펄스 신호의 수에 비례하고, 회전 속도는 입력 펄스 신호의 주파수에 비례한다.

스텝모터 28BYJ-48

  • 정격전압: 5VDC

  • 기어비: 1/64
    $$
    \frac {32}{9} \times \frac {22}{11} \times \frac {26}{9} \times \frac {31}{10} = 63.68395 \fallingdotseq 64
    $$

  • 스트라이드 각도
    • 스펙상 5.625º / 64 = 0.087890625
      • 그러므로, 360º / 0.087890625 = 4096 스텝
    • 실제로는 11.25º / 64 = 0.17578125
      • 그러므로, 360º / 0.17578125 = 2048 스텝으로 1회전 (이 값이 중요함!)
  • Frequency: 100Hz
  • 토크: 34.3mN,m

모터드라이브 ULN2003


schematic

Pin Map

모터드라이브 아두이노
INT1 11
INT2 10
INT3 9
INT4 8
GND GND
VCC 5V

sketch: 예제1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <Stepper.h>

// 2048:한바퀴(360도), 1024:반바퀴(180도)...
// datasheet를 통해 스트라이드 각도를 계산한 값을 사용
const int stepsPerRevolution = 2048;

// 모터 드라이브에 연결된 핀 IN1, IN3, IN2, IN4
Stepper myStepper(stepsPerRevolution, 11, 9, 10, 8);

void setup() {
myStepper.setSpeed(14);
}

void loop() {
// 시계 반대 방향으로 한바퀴 회전
myStepper.step(stepsPerRevolution);
delay(500);

// 시계 방향으로 한바퀴 회전
myStepper.step(-stepsPerRevolution);
delay(500);
}
  • Stepper myStepper(stepsPerRevolution, 11, 9, 10, 8);
    • 모터 드라이브에 연결되는 순서에 유의한다! (IN1, IN3, IN2, IN4 순서임!)
  • myStepper.setSpeed(14);
    • 아두이노 보드의 5V 전원 사용시 16이 최대값.
    • 16 이상은 안정적으로 회전이 안되며, 17을 넣으면 한바퀴 돌고 정지. 18을 넣으면 돌지 않음

sketch: 예제2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <Stepper.h>

// 2048:한바퀴(360도), 1024:반바퀴(180도), 64:11.25도
const int stepsPerRevolution = 64;

// 모터 드라이브에 연결된 핀 IN1, IN3, IN2, IN4
Stepper myStepper(stepsPerRevolution, 11, 9, 10, 8);

void setup() {
myStepper.setSpeed(16);
}

void loop() {

// 시계 반대 방향으로 한바퀴 회전
// 64 * 32 = 2048 한바퀴 => 11.25도씩 32번 회전 (360도)
for(int i=0; i<32; i++) {
myStepper.step(stepsPerRevolution);
}
delay(500);

// 시계 방향으로 한바퀴 회전
// -64 * 32 = 2048 한바퀴 => -11.25도씩 32번 회전 (-360도)
for(int i=0; i<32; i++) {
myStepper.step(-stepsPerRevolution);
}
delay(500);
}

Arduino, 서보모터

서보모터 SG-90

  • 모터드라이버, 회전센서, 모터, 제어회로가 내장된 기어 박스를 포함하고 있는 형태의 모터로, 스텝모터보다 힘이 강함
  • 보통 선이 3가닥이며, 회전수와 각도 등의 움직임 제어가 가능
  • 아두이노에서 많이 다루는 SG90 제품의 경우 0~180˚ 제어가 가능
  • 아두이노UNO의 USB 전류량이 500mA 정도가 한계이므로 별도의 전원을 사용하지 않으면 1~2개의 서보모터만 사용 가능

라이브러리 추가하기

서보모터를 사용하기 위한 Servo 라이브러리는 아두이노 IDE에 기본으로 포함되어 있다.

  • 스케치> 라이브러리 포함하기> Servo 를 선택하면,
  • #include <servo.h> 라이브러리가 스케치에 추가 된다.

schematic

SG90 Brown Red Orange
Arduino GND 5V 10

sketch : 서보모터 180º 회전 왕복하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <Servo.h>

Servo servomotor; // servomotor 선언
int position = 0;

void setup() {
servomotor.attach(10); // servomotor 핀 설정
}

void loop() {
for(position = 0; position < 180; position++)
{
servomotor.write(position); // pos값의 위치로 이동
delay(15);
}
for(position = 180; position > 0; position--) // 같은 방법으로 역회전
{
servomotor.write(position);
delay(15);
}
}
  • servomotor.write(숫자)를 사용할때, 숫자는 현재 위치에서의 ‘회전각’을 의미하는 것이 아니다!
  • 초기 위치를 0º로 기준삼아, 표시된 숫자의 각도 위치로 이동하라는 의미이다. 예를들어서 servomotor.write(90)와 servomotor.write(30)을 연속으로 실행하면,
    • 90도를 회전한 뒤, 추가로 30도를 회전하여 120도 위치에 있다 → ×
    • 90도의 위치로 회전한 뒤, 역회전하여 (처음위치를 기준으로) 30도의 위치로 이동한다. → ○
  • 단, servo.h 라이브러리를 사용하면, 스케치 내에서 analogWrite를 사용할 수 없다는 단점도 있다.

sketch: 시리얼모니터에서 각도값을 입력받아 회전시키기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <Servo.h>
Servo servomotor;
int a = 0;

void setup() {
servomotor.attach(10);
Serial.begin(9600); // Serial 통신을 설정
while(!Serial);
Serial.println("Servor Mortor");
}

void loop() {
if(Serial.available()){ // Serial 모니터창에 어떤 값이 들어오면 실행
a = Serial.parseInt(); // Serial 모니터창에서 받은 값을 a에 넘겨줌
if(a>=0 && a<=180)
{
Serial.print("angle : ");
Serial.println(a);
servomotor.write(a); // a값에 해당하는 각도의 위치로 이동
delay(15);
}
}
}


서보모터 SG-90: 라이브러리 없이 사용하기

for SG90 with Arduino

  • Frequency of that signal should be 50hz.
  • Its range is 544-2450 micro seconds for 0-180 degree angle.
  • Again its a little complex.
  • For example 0 degree signal will be something like 5 volts for 544 micro seconds and 0 volt for 19465.
  • highDelay
    • the time in which we want to keep voltage high on pin 10 and this unit is in micro seconds.
  • lowDelay
    • the time in which want to keep voltage zero across pin 10 again this unit is going to be in micro seconds
  • only HIGH part of signal wont operate servo. we have to send complete HIGH and LOW signal to make servo work.
  • deg_factor
    • signal range (2450 - 544) for SG90 divided by 180 which is equal to 10.6 something that we make 11 as an integer.
  • Loop = 20000;
    • 20000 micro seconds or 20 milli seconds is signal cycle or u can say total signal should be equal to 20 milli seconds including HIGH and LOW input. (50Hz)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int servoPin = 10;        // servo is connected to pin 10.
int initialDelay = 544;
int highDelay, lowDelay;

int deg;
int deg_factor = 10;
int Loop = 20000;

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

void loop() {
for (deg = 0;deg <= 180; deg++) {
servoWrite(servoPin, deg);
}
for (deg = 180;deg >= 0; deg--) {
servoWrite(servoPin, deg);
}
}

void servoWrite(int servo, int duty)
{
highDelay = initialDelay + (duty * deg_factor); // setting angle

digitalWrite(servo, HIGH);
delayMicroseconds(highDelay);

lowDelay = Loop - highDelay;
digitalWrite(servo, LOW);
delayMicroseconds(lowDelay);

delay(500);
}

시리얼 모니터에 입력한 문자로 서보모터 회전시키기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int servoPin = 10;        // servo is connected to pin 10.
int initialDelay = 544;
int highDelay, lowDelay;

int deg = 90; // servo initial degree
int deg_factor = 10;
int Loop = 20000;

void setup() {
Serial.begin(9600);
pinMode (servoPin, OUTPUT);
}

void loop() {
servoWrite(servoPin, deg);

while(Serial.available() > 0)
{
char flag=Serial.read();
delay(2);

if(flag=='l') {
deg = deg - 5;
if(deg < 0) {
deg = 0;
}
}

if(flag=='r') {
deg = deg + 5;
if(deg > 180) {
deg = 180;
}
}
}

void servoWrite(int servo, int duty)
{
highDelay = initialDelay + (duty * deg_factor); // setting angle

digitalWrite(servo, HIGH);
delayMicroseconds(highDelay);

lowDelay = Loop - highDelay;
digitalWrite(servo, LOW);
delayMicroseconds(lowDelay);

delay(500);
}
  • l 을 입력하면, 5도 좌회전하며, ll을 입력하면 10도 좌회전한다.
  • r 을 입력하면, 5도 우회전하며, rr을 입력하면 10도 우회전한다.


서보모터와 인터럽트 동시에 사용하기

서보모터가 180도 회전하는 동안, 스위치를 누르면 13번 LED가 토글되도록 만들어보자.


schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int servoPin = 10;        // servo is connected to pin 10.
int initialDelay = 544;
int highDelay, lowDelay;

int deg;
int deg_factor = 10;
int Loop = 20000;

#define swPin 2
#define ledPin 13
#define debounceTime 500 // Set debounce Time (unit ms) 버튼을 누른 후 0.5sec 이내에는 버튼 체크하지 않음

boolean state = false;

void setup() {
pinMode(servoPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(swPin), swInterrupt, FALLING);
}

void loop() {
for (deg = 0;deg <= 180; deg++) {
servoWrite(servoPin, deg);
}
for (deg = 180;deg >= 0; deg--) {
servoWrite(servoPin, deg);
}
}

void servoWrite(int servo, int duty)
{
highDelay = initialDelay + (duty * deg_factor); // setting angle

digitalWrite(servo, HIGH);
delayMicroseconds(highDelay);

lowDelay = Loop - highDelay;
digitalWrite(servo, LOW);
delayMicroseconds(lowDelay);

delay (15);
}

void swInterrupt() {
static unsigned long lastTime = 0;
unsigned long now = millis();

if((now-lastTime) > debounceTime) {
state=!state;
}
lastTime = now;

digitalWrite(ledPin, state);
}
  • 스위치를 눌렀다 뗄 때, 물리적 요인에 의해 다시 스위치가 눌리는 효과가 나타나기도 함 (chattering)
  • Chattering 효과를 방지하기 위해, 하드웨어적으로 콘덴서를 붙이거나, 소프트웨어적으로 debounce 시간을 만들어 해결해야 한다.


레이저 모듈 추가: 서보모터 날개에 부착

schematic


sketch

13번 핀에 레이저모듈을 연결하므로 위 스케치를 그대로 이용



조도센서 추가

반대편에 조도센서를 놓고, 레이저 모듈이 움직이는 동안 버튼을 눌러 조도센서를 정확하게 맞추면 점수가 올라가는 게임을 만들어보자.



Keypad에서 값을 입력받아 회전시키기

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <Servo.h>
#include <Keypad.h>

Servo servomotor;
int pos = 0;

const byte ROWS = 4; // 키패드 배열 선언
const byte COLS = 4;

char keys[ROWS][COLS] = { // 키패드 정의
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'},
};

byte rowPins[ROWS] = {9, 8, 7, 6}; // 키패드 연결 핀 설정
byte colPins[COLS] = {5, 4, 3, 2};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

void setup() {
servomotor.attach(10);
}

void loop() {
char keyValue = keypad.getKey();

if(keyValue) {
switch(keyValue) {
case '0' : // 시리얼 모니터에 0을 입력하면
servomotor.write(0); // 0도 위치로 이동
delay(15);
break;
case '1' : // 시리얼 모니터에 1을 입력하면
servomotor.write(15); // 15도 위치로 이동
delay(15);
break;
case '2' :
servomotor.write(30); // 30도 위치로 이동
delay(15);
break;
case '3' :
servomotor.write(45); // 45도 위치로 이동
delay(15);
break;
case '4' :
servomotor.write(60); // 60도 위치로 이동
delay(15);
break;
case '5' :
servomotor.write(75); // 75도 위치로 이동
delay(15);
break;
case '6' :
servomotor.write(90); // 90도 위치로 이동
delay(15);
break;
case '7' :
servomotor.write(120); // 120도 위치로 이동
delay(15);
break;
case '8' :
servomotor.write(150); // 150도 위치로 이동
delay(15);
break;
case '9' :
servomotor.write(180); // 180도 위치로 이동
delay(15);
break;
default :
break;
}
}
delay(10);
}


비밀번호 확인하여 서보모터 회전시키기

서보모터의 날개를 금고의 개폐장치로 사용하여, 비밀번호 금고를 만들어 보자.


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <Keypad.h>
#include <Servo.h>

int tru=0; // 비밀번호가 맞는지 확인
int count=0; // 4자리수 카운트
char PW[4]={'1','2','3','A'}; //비밀번호

Servo servomotor;

// 키패드 설정 시작
const byte ROWS = 4;
const byte COLS = 4;

byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

char keys[ROWS][COLS] = {
{'1','2','3', 'A'},
{'4','5','6', 'B'},
{'7','8','9', 'C'},
{'*','0','#', 'D'}
};

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
// 키패드 설정 끝

void setup() {
Serial.begin(9600);
servomotor.attach(10); // 서보모터 핀 설정
}

void loop() {
char key = keypad.getKey(); // 키패드 입력 변수

if (key) {
Serial.println(key);
if(key==PW[count]) { // 입력번호와 비밀번호가 맞으면
count++; // 자리수 카운트 1증가
tru++; // 맞은수 값 1증가
}
else if(key!=PW[count]) { // 입력번호와 비밀번호가 틀리면
count++; // 자리수 카운트 값만 1증가
}

if(key=='#') // #을 누르면
re(); // 초기화
if(count==4) { // 4자리 입력받은 값이
if(tru==4) // 모두 맞으면
Su(); // 성공
else // 그렇지 않으면(4자리는 입력했는데, 모두 맞지는 않았으면)
Fa(); // 실패
tru=0; // tru, count값 초기화
count=0;
}
}
}

void Su() { // 성공했을 때 실행
servomotor.write(90); // 90도 돌려서 도어락 열림
Serial.println("open the door");
}

void Fa() { // 실패한 경우는 도어락 잠금 유지
servomotor.write(0);
Serial.println("close the door");
}

void re() { // 비밀번호 입력중 #을 입력하면 초기화하고 도어락 잠금 유지
tru=0;
count=0;
servomotor.write(0);
Serial.println("password reset");
}


Joystick을 사용하여 2개의 서보모터 제어하기

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#define JSTICKPIN A0      // joystick is connected to pin 11

int servoPin = 10; // servo is connected to pin 10
int initialDelay = 544;
int highDelay, lowDelay;

int deg, Jstick, duty;
int deg_factor = 10;
int Loop = 20000;

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

void loop() {
PrintValue();

Jstick = analogRead(JSTICKPIN);
deg = map(Jstick, 0, 1023, 0, 180);
servoWrite(servoPin, deg);
}

void servoWrite(int servo, int duty)
{
highDelay = initialDelay + (duty * deg_factor); // setting angle

digitalWrite(servo, HIGH);
delayMicroseconds(highDelay);

lowDelay = Loop - highDelay;
digitalWrite(servo, LOW);
delayMicroseconds(lowDelay);

delay (15);
}

void PrintValue() {
Serial.print(analogRead(JSTICKPIN));
Serial.println (" Joystick Value");
}

Arduino, 피에조 부저

Buzzer의 종류

Active Buzzer (능동형 부저) Passive Buzzer (수동형 부저)
有源蜂鸣器 (KY-012) 无源蜂鸣器 (KY-006)
회로가 내장되어 있어, 전원만 인가하면 소리가 나므로 프로그램 제어가 편리함 내부에 진동원이 없어, 스케치에 주파수를 지정하여야만 소리를 냄
원하는 주파수의 소리를 낼 수 없음 원하는 주파수의 소리를 만들어낼 수 있음
패시브 부저에 비해 비쌈 (모듈 1개 0.9위안) 액티브 부저에 비해 저렴 (모듈 1개 0.8위안)
  • 모듈로 판매하는 제품의 경우 겉모양으로는 구별이 어렵다. Active Buzzer의 경우 초기 판매시 스티커를 붙여서 판매하지만, 스티커가 떨어져 나간 상태라면 Passive Buzzer와 구별하기 어렵다. 또한 반드시 스티커가 붙은 형태로 판매하는 것도 아니다.
  • Active Buzzer의 경우 스티커로 사용전압을 표시하기도 한다. 빨간색은 3V, 파란색은 5V, 보라색은 12V용이다.
  • Active Buzzer 모듈의 경우 低电平触发(혹은 Low Level) / 高电平触发(혹은 High Level) 2가지 형태로 표기가 되어 있다. 低电平触发은 DigitalWrite에서 HIGH일 때 소리가 나며, 高电平触发은 LOW일 때 소리가 난다.
  • 모듈 형태가 아닌 단일 부품의 형태라면 연결 핀이 나와 있는 아랫면을 보면 구별할 수 있다. 일반적으로 Active Buzzer는 검은색 실리콘으로 밀폐되어 있으며, Passive Buzzer는 핀의 접합부위가 그대로 노출되어 있다.
  • Active Buzzer와 Passive Buzzer를 구별하는 가장 좋은 방법은 사용전압에 해당하는 전압(낮은 전압의 건전지도 가능)의 건전지를 연결시켜보는 것이다. 소리가 나면 Active, 나지 않으면 Passive라고 생각하면 된다.


Active Buzzer

schematic


sketch

1
2
3
4
5
6
7
8
9
10
void setup () {
pinMode (11, OUTPUT); // 11번 핀을 출력으로 설정
}

void loop () {
digitalWrite (11, HIGH); // 11번 핀 부저 ON
delay (1000);
digitalWrite (11, LOW); // 11번 핀 부저 OFF
delay (1000);
}

과제

tact 스위치를 누르면 액티브 부저 소리 울리기



Passive Buzzer 사용

주파수

  • 0옥타브(C0B0): 1631(Hz) (초저주파)
  • 1옥타브(C1B1): 3362(Hz)
  • 2옥타브(C2B2): 65123(Hz)
    • 중저음
  • 3옥타브(C3B3): 131247(Hz)
    • 기본 음계에 해당하는 옥타브
  • 4옥타브(C4B4): 262494(Hz)
    • 일반적으로 고음이라고 불리는 영역
    • 일반적인 남자의 경우 G4~B4음이 최고음
    • 음악의 기준음인 **가온 라(A4)(440Hz)**가 속한 영역
  • 5옥타브(C5B5): 523988(Hz)
    • 일반적인 여성의 경우 D5의 음까지는 낼 수 있고, 고음을 잘 내는 경우 E5까지는 무난함
  • 6옥타브(C6B6): 10471976(Hz)
  • 7옥타브(C7B7): 20933951(Hz)
  • 8옥타브(C8B8): 41867902(Hz)
  • 9옥타브(C9B9): 837215804(Hz)
  • 10옥타브(C10B10): 1674431608(Hz) (초음파)

  • 한 옥타브 내려가면 주파수가 1/2배, 올라가면 2배가 되고, 한 음계 올라가면 2^(1/12) 배가 된다.

  • 0옥타브 중간쯤(20Hz)부터나, 10옥타브 중간쯤(20kHz)부터는 소리가 들리지 않는다. 사람의 가청 영역을 벗어나기 때문. 이를 각각 초저주파, 초음파라고 한다. 어릴 수록 고음이 잘 들리기 때문에, “선생님은 못 듣는 벨소리”같은 것을 만들수 있다.

0 1 2 3 4 5 6 7 8
C(도) 16 33 65 131 262 523 1046.5 2093 4186
C♯ 17 35 69 139 277 554 1109 2217.5 4435
D(레) 18 37 73 147 294 587 1175 2349 4699
D♯ 20 39 78 156 311 622 1244.5 2489 4978
E(미) 21 41 82 165 330 659 1318.5 2637 5274
F(파) 22 44 87 175 349 698.5 1397 2794 5588
F♯ 23 46 92.5 185 370 740 1480 2960 5920
G(솔) 25 49 98 196 392 784 1568 3136 6272
G♯ 26 52 104 208 415 831 1661 3322.5 6645
A(라) 28 55 110 220 440 880 1760 3520 7040
A♯ 29 58 116.5 233 466 932 1865 3729 7459
B(시) 31 62 123.5 247 494 988 1975.5 3951 7902

Pin Map

Arduino Active Buzzer Passive Buzzer
11 S S
5V 표기없음 표기없음
GND (-) (-)

sketch : Passive Buzzer

tone() 사용방법
  • tone 함수는 3개의 매개변수(핀, 주파수, 음길이)를 사용하며, 마지막 매개변수(음길이)는 생략 가능하다.

    1
    tone(11, 262, 500);   // 11번 핀에 연결된 부저에 C3음을 0.5초 연주
  • 마지막 매개변수를 사용하지 않고 tone을 연속으로 사용하는 경우 주의가 필요하다. 예를들어,

    1
    2
    tone(11, 262);   // 11번 핀에 연결된 부저에 C3음을 0.5초 연주
    tone(11, 294); // 11번 핀에 연결된 부저에 D3음을 0.5초 연주

    이렇게 tone을 연속으로 사용하면 C3음을 소리낸 이후에, D4음을 내는 것이 아니라 C3음이 계속되므로, noTone함수를 사용하여 소리 출력을 멈추어야 한다.

    1
    2
    3
    tone(11, 262);   // 11번 핀에 연결된 부저에 C3음을 0.5초 연주
    noTone(11);
    tone(11, 294); // 11번 핀에 연결된 부저에 D3음을 0.5초 연주
  • 마지막 매개변수를 사용할 때, 중간에 음을 멈추는 구간을 만들려면, delay를 사용한다.

    1
    2
    3
    tone(11, 262, 500);   // 11번 핀에 연결된 부저에 C3음을 0.5초 연주
    delay(600);
    tone(11, 294, 500); // 11번 핀에 연결된 부저에 D3음을 0.5초 연주

    C3음을 0.5초 소리낸 후 delay(600) 에 의해 0.6초를 쉬는 것이 아니라, delay(600) 의 시간 중, 0.5초는 C3음을 소리내는 데 사용하고, 나머지 0.1초를 쉰다.


예시

도레미파솔라시도
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const int BUZZER_PIN = 11;

const int C_4 = 261; // 도
const int D_4 = 294; // 레
const int E_4 = 330; // 미
const int F_4 = 349; // 파
const int G_4 = 392; // 솔
const int A_4 = 440; // 라
const int B_4 = 494; // 시

void setup() {
}

void loop() {
tone(BUZZER_PIN, C_4);
delay(500);

tone(BUZZER_PIN, D_4);
delay(500);

tone(BUZZER_PIN, E_4);
delay(500);

tone(BUZZER_PIN, F_4);
delay(500);

tone(BUZZER_PIN, G_4);
delay(500);

tone(BUZZER_PIN, A_4);
delay(500);

tone(BUZZER_PIN, B_4);
delay(500);

noTone(BUZZER_PIN);
}

노래 연주: Super Mario Theme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/* Arduino Mario Bros Tunes With Piezo Buzzer and PWM

by : ARDUTECH
Connect the positive side of the Buzzer to pin 11,
then the negative side to a 1k ohm resistor. Connect
the other side of the 1 k ohm resistor to
ground(GND) pin on the Arduino.
*/


#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978

#define melodyPin 11

//Mario main theme melody
int melody[] = {
NOTE_E7, NOTE_E7, 0, NOTE_E7,
0, NOTE_C7, NOTE_E7, 0,
NOTE_G7, 0, 0, 0,
NOTE_G6, 0, 0, 0,

NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,

NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0,

NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,

NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0
};

//Mario main theme tempo
int tempo[] = {
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,

12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,

9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,

12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,

9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
};

//Underworld melody
int underworld_melody[] = {
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0, NOTE_DS4, NOTE_CS4, NOTE_D4,
NOTE_CS4, NOTE_DS4,
NOTE_DS4, NOTE_GS3,
NOTE_G3, NOTE_CS4,
NOTE_C4, NOTE_FS4, NOTE_F4, NOTE_E3, NOTE_AS4, NOTE_A4,
NOTE_GS4, NOTE_DS4, NOTE_B3,
NOTE_AS3, NOTE_A3, NOTE_GS3,
0, 0, 0
};

//Underwolrd tempo
int underworld_tempo[] = {
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
6, 18, 18, 18,
6, 6,
6, 6,
6, 6,
18, 18, 18, 18, 18, 18,
10, 10, 10,
10, 10, 10,
3, 3, 3
};

void setup(void)
{
pinMode(melodyPin, OUTPUT); // buzzer
pinMode(13, OUTPUT); // led indicator when singing a note

}

void loop()
{
//sing the tunes
sing(1);
sing(1);
sing(2);
}

int song = 0;

void sing(int s) {
// iterate over the notes of the melody:
song = s;
if (song == 2) {
Serial.println(" 'Underworld Theme'");
int size = sizeof(underworld_melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {

// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / underworld_tempo[thisNote];

buzz(melodyPin, underworld_melody[thisNote], noteDuration);

// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);

// stop the tone playing:
buzz(melodyPin, 0, noteDuration);

}

} else {

Serial.println(" 'Mario Theme'");
int size = sizeof(melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {

// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / tempo[thisNote];

buzz(melodyPin, melody[thisNote], noteDuration);

// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);

// stop the tone playing:
buzz(melodyPin, 0, noteDuration);
}
}
}

void buzz(int targetPin, long frequency, long length) {
digitalWrite(13, HIGH);
long delayValue = 1000000 / frequency / 2; // calculate the delay value between transitions

//// 1 second's worth of microseconds, divided by the frequency, then split in half since
//// there are two phases to each cycle

long numCycles = frequency * length / 1000; // calculate the number of cycles for proper timing

//// multiply frequency, which is really cycles per second, by the number of seconds to
//// get the total number of cycles to produce

for (long i = 0; i < numCycles; i++) { // for the calculated length of time...
digitalWrite(targetPin, HIGH); // write the buzzer pin high to push out the diaphram
delayMicroseconds(delayValue); // wait for the calculated delay value
digitalWrite(targetPin, LOW); // write the buzzer pin low to pull back the diaphram
delayMicroseconds(delayValue); // wait again or the calculated delay value
}
digitalWrite(13, LOW);
}

노래 연주: 학교종이 땡땡땡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const int BUZZER_PIN = 11;

const int C_4 = 261; // 도
const int D_4 = 294; // 레
const int E_4 = 330; // 미
const int F_4 = 349; // 파
const int G_4 = 392; // 솔
const int A_4 = 440; // 라
const int B_4 = 494; // 시

typedef struct {
int tone;
unsigned long delay;
} TAD;//Tone And Delay

TAD music[] =
{
{G_4, 100}, {G_4, 100}, {A_4, 100}, {A_4, 100}, {G_4, 100}, {G_4, 100}, {E_4, 200},
{G_4, 100}, {G_4, 100}, {E_4, 100}, {E_4, 100}, {D_4, 200}, {G_4, 100}, {G_4, 100},
{A_4, 100}, {A_4, 100}, {G_4, 100}, {G_4, 100}, {E_4, 200}, {G_4, 100}, {E_4, 100},
{D_4, 100}, {E_4, 100}, {C_4, 200}
};

int musicLen;

void setup() {
musicLen = sizeof(music) / sizeof(TAD);
}

void loop() {
for(int i = 0; i < musicLen; i++) {
tone(BUZZER_PIN, music[i].tone);
delay(music[i].delay * 5);

noTone(BUZZER_PIN);
delay(30);
}

noTone(BUZZER_PIN);
delay(1000);
}

과제

  • 크리스마스 캐롤 만들어보기
  • 크리스마스 캐롤 연주시에, 음이 바뀔때마다 신호등 LED점멸 바꿔보기


미니 피아노 만들기

패시브 부저와 스위치를 사용하여 미니 피아노를 만들어보자.


schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const int BUZZER_PIN = 11;

// 도 레 미 파 솔 라 시
// C4 D4 E4 F4 G4 A4 B4
int octave_4[9] = {0, 0, 261, 294, 330, 349, 392, 440, 494};

void setup() {
// tact switch pin 2~8 for Keyboard
for(int i=2; i<=8; i++) {
pinMode(i, INPUT_PULLUP);
}
Serial.begin(9600);
}

void loop() {
for(int i=2; i<=8; i++) {
if(digitalRead(i) == HIGH) {
tone(BUZZER_PIN, octave_4[i]);
delay(1);
}
}
noTone(BUZZER_PIN);
}

과제

23명의 학생이 모여 23 옥타브를 사용하는 피아노 연주곡 만들어보기



Arduino, 키패드 사용하기

4×4 매트릭스 키보드

키패드 입력값을 시리얼 모니터로 출력해보자.


schematic


Pin Map

Arduino Keypad 일부 Keypad의 경우
2 1 8
3 2 7
4 3 6
5 4 5
6 5 4
7 6 3
8 7 2
9 8 1

주의 : Keypad 제조회사에 따라 통상적인 배선 순서와는 반대로 해야하는 경우도 있다. 키패드의 단자에 단자번호가 적혀있지 않으므로, 일단 통상적인 배선순서에 따라 연결해보고 시리얼 모니터를 통해 출력되는 값을 보고 확인해야 한다.



라이브러리 추가하기

Keypad 라이브러리는 아두이노 IDE에 기본으로 포함되어 있지 않으므로, 컴파일 전에 라이브러리를 추가한다.

1. 스케치> 라이브러리 포함하기> 라이브러리 관리> 라이브러리 매니저

  • 검색창에서 keypad 검색
  • Keypad by Mark Stanley, Alexander Brevig 라이브러리 설치

2. 스케치> 라이브러리 포함하기 에서 Keypad를 선택하면

1
#include <Keypad.h>
  • 라이브러리가 스케치에 추가된다.


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <Keypad.h>

const byte ROWS = 4; //배열
const byte COLS = 4; //배열

char keys[ROWS][COLS] = { //배열
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; //핀 지정
byte colPins[COLS] = {5, 4, 3, 2}; //핀 지정

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

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

void loop(){
char key = keypad.getKey(); //읽어온 값을 key에 저장
if (key){
Serial.println(key); //key값을 시리얼 모니터에 출력한다
}
}

> 시리얼 모니터(혹은 Ctrl-Shift-M) 에서 확인한다.

Arduino, IR 리모컨

생활속에서 가장 밀접하게 쓰이는 IR(Infra Red, 적외선) 리모컨의 사용에 대해서 알아본다. 리모컨 작동을 위해서는 기본적으로 IR 신호를 내보내는 리모컨본체와 이 본체에서 내보내는 신호를 받아들이는 IR 리시버가 필요하다.


기본 사용법

schematic

위 그림에서 IR 리시버에 있는 핀은, IR모듈이 아닌 단일 부품형태의 리시버 일때 핀의 순서이다.


IR 리시버 모듈을 사용하는 경우

아두이노 학습용으로 판매하는 IR 리시버는 브레드보드에 바로 붙일 수 있도록 조그만 PCB가 달린 모듈의 형태로 판매되는 경우가 많으며, 이 경우에는 PCB보드에 써있는 글씨를 보고 각 핀에 맞게 아두이노에 연결해야한다. 문제는 PCB에 어느 핀인지를 나타내는 글자가 잘 안보인다는 것!

  • Signal : PCB에 S라고 표기되어 있는 쪽
  • VCC : 대부분 가운데에 있는 경우가 많았음
  • GND : S라고 표기된 부분의 반대쪽이라고 생각하면 된다. (-) 마이너스 표기가 작게 되어 있음

Pin Map

Arduino IR Reciever 모듈 사용시 IR Reciever 단일부품
2 S (Signal) (둥근 모양을 마주보고) 왼쪽
5V 표기 되어 있지 않음 (둥근 모양을 마주보고) 오른쪽
GND - (둥근 모양을 마주보고) 가운데

라이브러리 준비하기

  • 스케치> 라이브러리 포함하기> 라이브러리 관리> 라이브러리 매니저

    • 검색어 irremote 입력

    • “IRremote by Armin Joachimsmeyer” 라이브러리 설치/업데이트

  • 스케치> 라이브러리 포함하기> IRremote 를 선택하면, IR remote 기능을 사용하는데 필요한 라이브러리가 스케치에 include 된다.
1
#include <IRremote.h>

리모컨 버튼의 고유값 알아내기

  • old MSB-first 32bit IR data code
  • new LSB-first 32bit IR data code

리모컨 코드값은 위의 2가지 형태로 구분된다. 여기서는 기본적으로 MSB-first 방식을 사용하도록 한다. 아래 스케치를 이용하면 간단하게 MSB-first code 값만 출력된다. (시리얼 모니터 Baudrate 115200)


sketch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <IRremote.h>

int RECV_PIN = 2; // IR 리시버 핀 설정
IRrecv irrecv(RECV_PIN); // IR 리시버 선언
decode_results results; // 수신결과 저장

void setup() {
Serial.begin(115200);
irrecv.enableIRIn(); // IR 리시버 시작
}

void loop() {
if(irrecv.decode(&results)) { // 리시버가 받은 값이 있으면
Serial.print("0x");
Serial.println(results.value, HEX); // 버튼 HEX값을 콘솔에 출력
delay(500);

irrecv.resume(); // 다음 값을 받기 위해 준비
}
}

serial monitor

출력값 중에서 앞부분의 0x를 제외한 6자리의 값(아래 그림에서 FFA25D)이 방금 누른 버튼의 고유 값이다.


리모컨 고유값

보유하고 있는 리모컨(XA5-14) 제품의 경우, 리모컨 타입은 NEC타입이며 각 버튼별 HEX값은 아래와 같다.

Remote Button unsigned int data
1 0xFFA25D
2 0XFF629D
3 0XFFE21D
4 0XFF22DD
5 0XFF02FD
6 0XFFC23D
7 0XFFE01F
8 0XFFA857
9 0XFF906F
0 0XFF9867
* 0XFF6897
# 0XFFB04F
0XFF18E7
0XFFA4B5
0XFF10EF
0XFF5AA5
OK 0XFF38C7


리모컨 버튼값 시리얼모니터에 출력하기

스케치에서 리모컨 버튼 정의하기

각 버튼의 Code값을 알아냈으면 스케치에서 리모컨의 버튼을 정의한다. 예를 들어, 리모컨 버튼 중에서 “1”을 눌렀을 때, 출력되는 HEX값이 “0xFFA25D” 였다면,

1
#define BTN_1 0xFFA25D

리모컨에 있는 모든 버튼을 위와 같은 방법으로 정의한다. 버튼이 보통 10~20개 정도 되므로 상당히 귀찮은 작업이지만 사용하려는 버튼은 모두 정의를 해야한다. 배열을 통해 정의할 수도 있다. 또한 별도의 리모컨이 없으면 집에서 사용하는 리모컨을 사용해도 된다.


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <IRremote.h>
#include <ir_Lego_PF_BitStreamEncoder.h>

#define BTN_CH_M 0xFF6897 // CH- Button
#define BTN_CH 0xFFB04F // CH Button
#define BTN_CH_P 0xFF18E7 // CH+ Button
#define BTN_PREV 0xFF4AB5 // PREV Button
#define BTN_NEXT 0xFF10EF // NEXT Button
#define BTN_PLAY_PAUSE 0xFF5AA5 // PLAY PAUSE Button
#define BTN_M 0xFF38C7 // - (VOL-) Button
#define BTN_P 0xFF38C8 // + (VOL+) Button
#define BTN_EQ 0xFF38C9 // EQ Button
#define BTN_100P 0xFF38CA // 100+ Button
#define BTN_200P 0xFF38CB // 100- Button

#define BTN_0 0xFF9867
#define BTN_1 0xFFA25D
#define BTN_2 0xFF629D
#define BTN_3 0xFFE21D
#define BTN_4 0xFF22DD
#define BTN_5 0xFF02FD
#define BTN_6 0xFFC23D
#define BTN_7 0xFFE01F
#define BTN_8 0xFFA857
#define BTN_9 0xFF906F

int recvPin = 2;
IRrecv irrecv(recvPin);

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

void loop() {
decode_results results;

if(irrecv.decode(&results)) {
switch(results.value) {
case BTN_CH_M :
Serial.println("CH-");
break;
case BTN_CH :
Serial.println("CH");
break;
case BTN_CH_P :
Serial.println("CH+");
break;
case BTN_PREV :
Serial.println("PREV");
break;
case BTN_NEXT :
Serial.println("NEXT");
break;
case BTN_PLAY_PAUSE :
Serial.println("PLAY/PAUSE");
break;
case BTN_M :
Serial.println("-");
break;

case BTN_P :
Serial.println("+");
break;
case BTN_EQ :
Serial.println("EQ");
break;
case BTN_100P :
Serial.println("100+");
break;
case BTN_200P :
Serial.println("200+");
break;

case BTN_0 :
Serial.println("0");
break;
case BTN_1 :
Serial.println("1");
break;
case BTN_2 :
Serial.println("2");
break;
case BTN_3 :
Serial.println("3");
break;

case BTN_4 :
Serial.println("4");
break;
case BTN_5 :
Serial.println("5");
break;
case BTN_6 :
Serial.println("6");
break;
case BTN_7 :
Serial.println("7");
break;

case BTN_8 :
Serial.println("8");
break;
case BTN_9 :
Serial.println("9");
break;
}
delay(500);
irrecv.resume();
}
}

과제: 리모컨 계산기 만들기



리모컨으로 서보모터 회전시키기

3개의 리모컨 버튼을 정의하여 각 버튼을 통해 서버모터를 회전시켜 본다.

  • 초기 위치 90도
  • Left 버튼을 누르면 5도 감소
  • Right 버튼을 누르면 5도 증가
  • OK 버튼을 누르면 90도 위치로 이동

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <Servo.h>
Servo servomotor;
int servomotorPin = 9;
int a = 90;

#include <IRremote.h>
#include <ir_Lego_PF_BitStreamEncoder.h>

#define BTN_1 0xFFA25D
#define BTN_2 0xFF629D
#define BTN_3 0xFFE21D
#define BTN_4 0xFF22DD
#define BTN_5 0xFF02FD
#define BTN_6 0xFFC23D
#define BTN_7 0xFFE01F
#define BTN_8 0xFFA857
#define BTN_9 0xFF906F
#define BTN_0 0xFF9867
#define BTN_A 0xFF6897 // * Asterisk Button
#define BTN_P 0xFFB04F // # Pound Button
#define BTN_U 0xFF18E7 // Up Button
#define BTN_D 0xFF4AB5 // Down Button
#define BTN_L 0xFF10EF // Left Button
#define BTN_R 0xFF5AA5 // Right Button
#define BTN_O 0xFF38C7 // OK Button

int recvPin = 2; // IR signal 핀
IRrecv irrecv(recvPin);

void setup() {
servomotor.attach(servomotorPin);
irrecv.enableIRIn();
}

void loop() {
decode_results results;

if(irrecv.decode(&results)) {
switch(results.value) {
case BTN_O : // OK버튼을 누르면 90도 위치로
a = 90;
break;
case BTN_L :
if(a > 5) {
a = a - 5; // L버튼을 누르면 서버모터 5도씩 감소
}
else
{
a = 0;
}
break;
case BTN_R :
if(a < 175) {
a = a + 5; // R버튼을 누르면 서버모터 5도씩 증가
}
else
{
a = 180;
}
break;
}
servomotor.write(a);
delay(500);
irrecv.resume();
}
}


LSB-first 32bit IR data code를 사용하려면

LSB-first 형태의 디코더 데이터를 사용해야하는 경우에는 라이브러리에서 제공하는 설명서(IRremote: IRremote Arduino Library)를 우선 숙지하여 사용토록한다. 여기서는 간단한 사용방법만 덧붙인다.

  1. 예제> IRremote> SimpleReceiver를 선택하여 파일을 불러온다.
    • SimpleReceiver.ino의 앞부분에, 사용하려는 리모컨의 프로토콜 타입을 선택할 수 있는 부분이 있다.
    • 아두이노 실습용으로 판매되는 리모컨은 대부분 NEC타입이므로 변경할 필요가 없다.
    • 삼성이나 LG 등 집에서 사용하는 리모컨의 HEX값을 알아내려면, 사용하려는 제품의 제조사에 따라 주석처리되는 부분을 적절히 수정한다.
SimpleReceiver.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//#define DECODE_DENON        // Includes Sharp
//#define DECODE_JVC
//#define DECODE_KASEIKYO
//#define DECODE_PANASONIC // the same as DECODE_KASEIKYO
//#define DECODE_LG
#define DECODE_NEC // Includes Apple and Onkyo
//#define DECODE_SAMSUNG
//#define DECODE_SONY
//#define DECODE_RC5
//#define DECODE_RC6

//#define DECODE_BOSEWAVE
//#define DECODE_LEGO_PF
//#define DECODE_MAGIQUEST
//#define DECODE_WHYNTER

//#define DECODE_DISTANCE // universal decoder for pulse width or pulse distance protocols
//#define DECODE_HASH // special decoder for all protocols
  1. SimpleReceiver을 열면 PinDefinitionsAndMore.h가 동시에 열린다.
    • 이 파일은 사용하는 보드에 따라 Signal핀을 연결하는 핀 번호를 정의하고 있다.
    • 아두이노 UNO의 경우 DEFAULT/AVR 플랫폼으로 정의되어 있으므로 IR Input핀은 2번을 사용한다. (아래 3번과정 참고: 시리얼 모니터를 열면, 연결된 보드에 따라 몇번 핀에 연결해야하는지 출력이 되므로, 이 과정에서 Signal을 어디에 연결해야하는지 확인할 필요는 없다.)
    • ESP32의 경우 IR Input이 GPIO 15번으로 지정되어 있다.

  1. 스케치를 업로드 한 후, 시리얼 모니터를 열고 Baud rate를 115200으로 설정한다. (Signal이 연결된 핀의 번호가 맞는지 확인)

  1. 리모컨의 버튼을 IR수신부를 향하여 누르면, 3줄이 출력됨
    • 첫번째 라인에 출력되는 Raw-Data 부분에서 앞부분의 0x를 제외한 8자리의 값(아래 그림에서 BA45FF00)이 방금 누른 버튼의 고유 값이다.
    • 각 버튼을 차례대로 누르면서 버튼의 고유값을 기록한다. 다운로드

Arduino, 1602 LCD (I2C)

1602 LCD (I2C)

schematic


1602 LCD SDA SCL VCC GND
Arduino A4 A5 5V GND

라이브러리 설치하기

스케치> 라이브러리 포함하기> 라이브러리 관리

  • Wire : 기본 내장 라이브러리
  • LiquidCrystal I2C (by Frank de Brabander) 검색하여 설치

sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// I2C 1602 LCD (연결핀을 바꿀 수 없음)
// VCC-5V / GND-GND / SDA-A4 / SCL-A5
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// i2c address 는 칩에 따라 0x20~0x27 혹은 0x3F값을 가짐
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
lcd.init(); // lcd 초기화
lcd.backlight();

lcd.print("I Love Steam!!"); // LCD창에 메시지 출력
delay(1000);
}

void loop() {
// 문자열의 길이 13개를 왼쪽으로 스크롤
for(int pCount = 0; pCount < 13; pCount++) {
lcd.scrollDisplayLeft(); // 왼쪽으로 스크롤
delay(700);
}
// 문자열 길이 13열 + 기본 16열 = 29개 위치를 오른쪽으로 스크롤
for(int pCount = 0; pCount < 29; pCount++) {
lcd.scrollDisplayRight(); // 오른쪽으로 스크롤
delay(700);
}
// 왼쪽으로 16개 위치 스크롤하여 처음 위치로 이동
for(int pCount = 0; pCount < 16; pCount++) {
lcd.scrollDisplayLeft(); // 왼쪽으로 스크롤
delay(700);
}

delay(1000);
}


주의사항

  • I2C 모듈에 점퍼선을 뺏다가 다시 끼우는 경우, 정상적인 상황에서도 LCD가 작동하지 않는 경우가 있음 (주로 첫번째라인에 ■■■■■■■■■■■■■■■■ 형태로 출력되는 오류가 발생됨)
  • 이런 경우에는 배선을 모두 완료한 상태에서 스케치 업로드를 다시 하면 대부분 해결됨


여러 가지 문자열 출력해보기

  • 첫 번째 행에 I Love Steam!!
  • 두 번째 행에 본인의 영문이름을 써서 스크롤 해보기
  • 두 번째 행에 출력하는 방법 : lcd.setCursor(0,1);


1602 LCD에 DS18b20 온도센서 값 출력하기

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// DS18b20 Temperature Sensor
#include <OneWire.h>
#include <DallasTemperature.h>

#define TEMP_PIN 2

OneWire oneWire(TEMP_PIN);
DallasTemperature sensors(&oneWire);
float celciusTemperature;

// I2C 1602 LCD (연결핀을 바꿀 수 없음)
// VCC-5V / GND-GND / SDA-A4 / SCL-A5
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// i2c address 는 칩에 따라 0x27, 0x3F값을 가짐
LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
Serial.begin(9600);
Serial.println("DallasTemperature IC Control");

// Start up the Temperature Sensor library
sensors.begin();

// lcd 초기화
lcd.init();
lcd.backlight();
}

void loop() {
sensors.requestTemperatures();
celciusTemperature = sensors.getTempCByIndex(0);
Serial.print("Temperature is: ");
Serial.println(celciusTemperature);

lcd.setCursor(0, 0);
lcd.print("Temperature : ");
lcd.setCursor(0, 1);
lcd.print(celciusTemperature);
lcd.print("*C");
delay(2000);
}

Arduino, TM1637 모듈 사용하기

TM1637


schematic

TM1637 CLK DIO VCC GND
Arduino 4 7 5V GND

라이브러리 추가하기

  1. 파일> 새파일>
  2. 스케치> 라이브러리 포함하기> 라이브러리 관리>


  1. 라이브러리 매니저 창이 뜨면, 검색란에 “tm1637”를 적어 넣음. tm1637관련 라이브러리가 5~6개정도 검색됨. TM1637 by Avishay Orpaz 라이브러리를 선택하여 설치



TM1637 숫자 표기 방법

for TM1637 by Avishay Orpaz library 
   *  A  *
   F     B
   *  G  *
   E     C
   *  D  *  DP

sketch

“8.8.8.8.” → “ “ → “0123” → “dOnE”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

// The amount of time (in milliseconds) between tests
#define TEST_DELAY 2000

const uint8_t SEG_DONE[] = {
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O
SEG_C | SEG_E | SEG_G, // n
SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E
};

TM1637Display display(CLK, DIO);

void setup()
{
}

void loop()
{
uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };
uint8_t blank[] = { 0x00, 0x00, 0x00, 0x00 };

display.setBrightness(15); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);

// All segments on
display.setSegments(data); // "8.8.8.8."
delay(TEST_DELAY);

// Selectively set different digits // "0123"
data[0] = display.encodeDigit(0);
data[1] = display.encodeDigit(1);
data[2] = display.encodeDigit(2);
data[3] = display.encodeDigit(3);
display.setSegments(data);
delay(TEST_DELAY);

// Done!
display.setSegments(SEG_DONE); // "dOnE"
delay(TEST_DELAY);
}


0000~9999까지 나타내기


for() 문을 이용하는 방법

먼저 가장 간단하게 for문을 사용하여 0~9999까지의 숫자를 표시해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

uint8_t data[] = { 0x00, 0x00, 0x00, 0x00 }; // 초기 출력값 0000
int seg3, seg2, seg1, seg0;

TM1637Display display(CLK, DIO);

void setup() {
display.setBrightness(15); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);

Serial.begin(9600);
}

void loop() {
for(int digitValue = 0; digitValue <= 9999; digitValue++) { // 0~9999까지 1씩 증가시킴
seg0 = (digitValue / 1000) % 10; // 4자리에서 1000의 자리 숫자 저장
seg1 = (digitValue / 100) % 10; // 4자리에서 100의 자리 숫자 저장
seg2 = (digitValue / 10) % 10; // 4자리에서 10의 자리 숫자 저장
seg3 = digitValue % 10; // 4자리에서 1의 자리 숫자 저장

data[0]=display.encodeDigit(seg0); // 첫번째 FND에 1000의 자리 숫자배열
data[1]=display.encodeDigit(seg1); // 두번째 FND에 100의 자리 숫자 배열
data[2]=display.encodeDigit(seg2); // 세번째 FND에 10의 자리 숫자 배열
data[3]=display.encodeDigit(seg3); // 네번째 FND에 1의 자리 숫자 배열

display.setSegments(data);
Serial.println(digitValue);
delay(10); // 10ms 마다 카운트
}
}


millis() 함수를 이용하는 방법

millis() 함수를 이용하여 시간카운트를 통해 0~9999까지 카운트 해보는 방법도 있습니다.

※ 주의: seg0, seg1, seg2, seg3 등의 변수값이 1015사이의 값을 갖게되면 16진수(AF)로 표현되므로, 각 변수는 10보다 작은 값을 갖도록 해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
extern volatile unsigned long timer0_millis;   // millis() 리셋을 위한 변수

#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

unsigned long previousTime, currentTime; // 현재시간, 현재시간(ms)
int timeValue;
uint8_t data[] = { 0x00, 0x00, 0x00, 0x00 }; // 초기 출력값 0000
int seg3, seg2, seg1, seg0;

TM1637Display display(CLK, DIO);

void setup() {
display.setBrightness(15); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);

Serial.begin(9600);
}

void loop() {
currentTime = millis(); // 현재 시간값을 측정

/*
if(currentTime - previousTime >= 1000) { // 현재 시간값을 1s마다 1000씩 증가시킴
previousTime = currentTime;
timeValue = currentTime / 1000; // 1s 마다 1000씩 증가된 숫자를 1000으로 나누어 줌.
// (결과적으로 1s마다 1씩 증가된 숫자가 timeValue에 저장됨)
*/
if(currentTime - previousTime >= 100) { // 현재 시간값을 0.1s마다 100씩 증가시킴
previousTime = currentTime;
timeValue = currentTime / 100; // 0.1s 마다 100씩 증가된 숫자를 100으로 나누어 줌.
// (결과적으로 0.1s마다 1씩 증가된 숫자가 timeValue에 저장됨)
/*
if(currentTime - previousTime >= 10) { // 현재 시간값을 0.01s마다 10씩 증가시킴
previousTime = currentTime;
timeValue = currentTime / 10; // 0.01s 마다 10씩 증가된 숫자를 10으로 나누어 줌.
(결과적으로 0.01s마다 1씩 증가된 숫자가 timeValue에 저장됨)
*/
seg0 = (timeValue / 1000) % 10; // 현재 시간값 4자리에서 1000의 자리 숫자 저장
seg1 = (timeValue / 100) % 10; // 현재 시간값 4자리에서 100의 자리 숫자 저장
seg2 = (timeValue / 10) % 10; // 현재 시간값 4자리에서 10의 자리 숫자 저장
seg3 = timeValue % 10; // 현재 시간값 4자리에서 1의 자리 숫자 저장

data[0]=display.encodeDigit(seg0); // 첫번째 Segment에 1000의 자리 숫자 배열
data[1]=display.encodeDigit(seg1); // 두번째 Segment에 100의 자리 숫자 배열
data[2]=display.encodeDigit(seg2); // 세번째 Segment에 10의 자리 숫자 배열
data[3]=display.encodeDigit(seg3); // 네번째 Segment에 1의 자리 숫자 배열

display.setSegments(data);
Serial.println(timeValue);
}

if(currentTime>=1000000) { // 1,000,000ms(1000초) 후 millis() 리셋
timer0_millis = 0;
previousTime = 0;
}
}


millis() 함수를 이용하여 (분:초) 시계 만들기

※ 주의: seg0, seg1, seg2, seg3 등의 변수값이 1015사이의 값을 갖게되면 16진수(AF)로 표현되므로, 각 변수는 10보다 작은 값을 갖도록 해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
extern volatile unsigned long timer0_millis;   // millis() 리셋을 위한 변수

#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

unsigned long previousTime, currentTime; // 현재시간, 현재시간(ms)
int timeValue;
uint8_t data[] = { 0x00, 0x00, 0x00, 0x00 }; // 초기 출력값 0000
uint8_t segto;
int seg3, seg2, seg1, seg0;
int initTime = 5948; // 초기 시간 59분 48초

TM1637Display display(CLK, DIO);

void setup() {
display.setBrightness(7); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);

seg0 = (initTime / 1000) % 10; // 초기 시간값 4자리에서 1000의 자리 숫자 저장
seg1 = (initTime / 100) % 10; // 초기 시간값 4자리에서 100의 자리 숫자 저장
seg2 = (initTime / 10) % 10; // 초기 시간값 4자리에서 10의 자리 숫자 저장
seg3 = initTime % 10; // 초기 시간값 4자리에서 1의 자리 숫자 저장

Serial.begin(9600);
}

void loop() {
currentTime = millis(); // 현재 시간값을 측정

if(currentTime - previousTime >= 1000) { // 현재 시간값을 0.1s마다 100씩 증가시킴
previousTime = currentTime;
timeValue = currentTime / 1000; // 0.1s 마다 100씩 증가된 숫자를 100으로 나누어 줌.
// (결과적으로 0.1s마다 1씩 증가된 숫자가 timeValue에 저장됨)
seg3++;
if(seg3 == 10) {
seg3 = 0;
seg2++;
}
if(seg2 == 6 && seg3 == 0) {
seg2 = 0;
seg1++;
}
if(seg1 == 10) {
seg1 = 0;
seg0++;
}
if(seg0 == 6 && seg1 == 0) {
seg0 = 0;
seg1 = 0;
seg2 = 0;
seg3 = 0;
}

data[0]=display.encodeDigit(seg0); // 첫번째 Segment에 1000의 자리 숫자 배열
data[1]=display.encodeDigit(seg1); // 두번째 Segment에 100의 자리 숫자 배열
data[2]=display.encodeDigit(seg2); // 세번째 Segment에 10의 자리 숫자 배열
data[3]=display.encodeDigit(seg3); // 네번째 Segment에 1의 자리 숫자 배열

// 0.5초마다 콜론 깜박이기
segto = 0x80 | display.encodeDigit(seg1); // 분과 초사이의 콜론(:) 표시
display.setSegments(&segto,1,1);
delay(500); // 깜박임 딜레이
display.setSegments(data);
delay(500); // 깜박임 딜레이
}

if(currentTime>=86400000) { // 86,400,000ms(86,400초 = 24시간*60분/시간*60분/초) 후 millis() 리셋
timer0_millis = 0;
previousTime = 0;
}
}


다양한 표현방법 익히기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

// The amount of time (in milliseconds) between tests
#define TEST_DELAY 2000

const uint8_t SEG_DONE[] = {
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O
SEG_C | SEG_E | SEG_G, // n
SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E
};

TM1637Display display(CLK, DIO);

void setup()
{
}

void loop()
{
int k;
uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };
uint8_t blank[] = { 0x00, 0x00, 0x00, 0x00 };

display.setBrightness(15); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);

// All segments on
display.setSegments(data);
delay(TEST_DELAY);

// Selectively set different digits
data[0] = display.encodeDigit(0);
data[1] = display.encodeDigit(1);
data[2] = display.encodeDigit(2);
data[3] = display.encodeDigit(3);
display.setSegments(data);
delay(TEST_DELAY);

/*
for(k = 3; k >= 0; k--) {
display.setSegments(data, 1, k);
delay(TEST_DELAY);
}
*/

display.clear();
display.setSegments(data+2, 2, 2);
// __23 -> data+2: data배열 0123에서 앞에 두 자리 건너 띄운 후,
// 2: 남은 숫자 중 2자리를 잘라서, 2: 앞의 2칸을 띄운 후 출력
delay(TEST_DELAY);

display.clear();
display.setSegments(data+2, 2, 1);
// _23_ -> data+2: data배열 0123에서 앞에 두 자리 건너 띄운 후,
// 2: 남은 숫자 중 2자리를 잘라서, 1: 앞의 1칸을 띄운 후 출력
delay(TEST_DELAY);

display.clear();
display.setSegments(data+1, 3, 1);
// _123 -> data+1: data배열 0123에서 앞에 한자리 건너 띄운 후,
// 3: 남은 숫자 중 3자리를 잘라서, 1: 앞의 1칸을 띄운 후 출력
delay(TEST_DELAY);

display.clear();
display.setSegments(data+3, 1, 2);
// __3_ -> data+2: data배열 0123에서 앞에 두 자리 건너 띄운 후,
// 1: 남은 숫자 중 1자리를 잘라서, 2: 앞의 2칸을 띄운 후 출력
delay(TEST_DELAY);

display.clear();
display.setSegments(data+3, 2, 1);
// _3__ -> data+2: data배열 0123에서 앞에 두 자리 건너 띄운 후,
// 2: 남은 숫자 중 2자리를 잘라서(3이 마지막 자리이므로 1자리만 자름)
// 2: 앞의 2칸을 띄운 후 출력
delay(TEST_DELAY);

// Show decimal numbers with/without leading zeros
display.showNumberDec(0, false); // Expect: ___0
delay(TEST_DELAY);
display.showNumberDec(0, true); // Expect: 0000
delay(TEST_DELAY);
display.showNumberDec(1, false); // Expect: ___1
delay(TEST_DELAY);
display.showNumberDec(1, true); // Expect: 0001
delay(TEST_DELAY);
display.showNumberDec(301, false); // Expect: _301
delay(TEST_DELAY);
display.showNumberDec(301, true); // Expect: 0301
delay(TEST_DELAY);
display.clear();
display.showNumberDec(14, false, 2, 1); // Expect: _14_
delay(TEST_DELAY);
display.clear();
display.showNumberDec(4, true, 2, 2); // Expect: __04
delay(TEST_DELAY);
display.showNumberDec(-1, false); // Expect: __-1
delay(TEST_DELAY);
display.showNumberDec(-12); // Expect: _-12
delay(TEST_DELAY);
display.showNumberDec(-999); // Expect: -999
delay(TEST_DELAY);
display.clear();
display.showNumberDec(-5, false, 3, 0); // Expect: _-5_
delay(TEST_DELAY);
display.showNumberHexEx(0xf1af); // Expect: f1Af
delay(TEST_DELAY);
display.showNumberHexEx(0x2c); // Expect: __2C
delay(TEST_DELAY);
display.showNumberHexEx(0xd1, 0, true); // Expect: 00d1
delay(TEST_DELAY);
display.clear();
display.showNumberHexEx(0xd1, 0, true, 2); // Expect: d1__
delay(TEST_DELAY);

// Run through all the dots
for(k=0; k <= 4; k++) {
display.showNumberDecEx(0, (0x80 >> k), true);
delay(TEST_DELAY);
}

display.showNumberDecEx(3141, (0x80 >> 0), true);
// 3.141 -> 0x80: dot출력, 0x40: 콜론출력, >>0: 첫번째 도트출력,
// true: 빈자리 0으로 채우기
delay(TEST_DELAY);

display.showNumberDecEx(243, (0x80 >> 1), false);
// _2.43 -> 0x80: dot출력, 0x40: 콜론출력, >>1: 두번째 도트출력,
// true: 빈자리 0으로 채우기
delay(TEST_DELAY);

display.showNumberDecEx(314, (0x80 >> 2), false);
// _31.4 -> 0x80: dot출력, 0x40: 콜론출력, >>2: 세번째 도트출력,
// false: 빈자리 채우지 않기
delay(TEST_DELAY);

display.showNumberDecEx(52, (0x80 >> 3), false);
// __52. -> 0x80: dot출력, 0x40: 콜론출력, >>3: 네번째 도트출력,
// false: 빈자리 채우지 않기
delay(TEST_DELAY);

// Brightness Test
for(k = 0; k < 4; k++)
data[k] = 0xff; // 모두 segment를 키는 data배열 선언
for(k = 0; k < 7; k++) {
display.setBrightness(k); // 밝기를 7단계에 걸쳐 점점 밝게 출력
display.setSegments(data);
delay(TEST_DELAY);
}

// On/Off test
for(k = 0; k < 4; k++) {
display.setBrightness(7, false); // Turn off
display.setSegments(data);
delay(TEST_DELAY);
display.setBrightness(7, true); // Turn on
display.setSegments(data);
delay(TEST_DELAY);
}

// Done!
display.setSegments(SEG_DONE);

// while(1);
}

Arduino, 온도 및 습도 측정 (DHT22)

온습도 센서 DHT22

DHT22 센서를 이용하여 온도와 습도를 측정한다.

센서 DHT11 DHT22 (AM2302)
사진 DHT11 DHT22
온도 0 ~ 50℃ (오차범위 ±2℃) -40 ~ 100℃ (오차범위 ±0.5℃) (해상도 0.1℃)
습도 20 ~ 90% (오차범위 ±5%) 0 ~ 100% (오차범위 ±2~5%) (해상도 0.1%)

Pinout

DHT22 DAT VCC GND
Arduino 8 5V GND

schematic



라이브러리 준비하기

Case1: 위 사진과 같은 형태의 DHT22 모듈을 사용하는 경우
  • 라이브러리 매니저검색창에 검색어로 ‘AM2302’를 입력

    • Groove Temperature And Humidity Sensor (by Seeed Studio) 설치


Case2: 위 라이브러리로 동작하지 않는 경우

2개의 라이브러리가 필요합니다.

  • 먼저 DHT sensor library (by Adafruit)를 검색하여 설치를 클릭합니다.

  • 그러면 Dependencies for library DHT sensor library 창이 나타나는데, 이때 Install all을 클릭하면 2개의 라이브러리가 동시에 설치됩니다.

두 개의 라이브러리를 설치하기만 하면 되기때문에, 순서가 바뀌어도 관계없습니다.



sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <DHT.h>

#define DHTPIN 8 // data pin
#define DHTTYPE DHT22 // change to DHT11 if you're using the DHT11
// AM2301(DHT21) -> DHT21, AM2302(DHT22) -> DHT22
DHT dht(DHTPIN, DHTTYPE);

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

void loop() {
delay(2000);

// float temperature = dht.readTemperature();
// float humidity = dht.readHumidity();
float temp_hum_val[2] = {0};

if(!dht.readTempAndHumidity(temp_hum_val)){
Serial.print("Temperature: ");
Serial.print(temp_hum_val[1]);
Serial.print(" *C\t,\t");
Serial.print("Humidity: ");
Serial.print(temp_hum_val[0]);
Serial.println(" %");
}
else{
Serial.println("Failed to get temprature and humidity value.");
}
}


1602 LCD에 온도, 습도 출력하기

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <DHT.h>

#define DHTPIN 8 // data pin
#define DHTTYPE DHT22 // change to DHT11 if you're using the DHT11
// AM2301(DHT21) -> DHT21, AM2302(DHT22) -> DHT22
DHT dht(DHTPIN, DHTTYPE);

// I2C 1602 LCD (연결핀을 바꿀 수 없음)
// VCC-5V / GND-GND / SDA-A4 / SCL-A5
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// i2c address 는 칩에 따라 0x27, 0x3F값을 가짐
LiquidCrystal_I2C lcd(0x27, 16, 2);

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

// lcd 초기화
lcd.init();
lcd.backlight();
}

void loop() {
delay(2000);

// float temperature = dht.readTemperature();
// float humidity = dht.readHumidity();
float temp_hum_val[2] = {0};

if(!dht.readTempAndHumidity(temp_hum_val)){
Serial.print("Temperature: ");
Serial.print(temp_hum_val[1]);
Serial.print(" *C\t,\t");

lcd.setCursor(0, 0);
lcd.print("Temp. : ");
lcd.print(temp_hum_val[1]);
lcd.print("*C");

Serial.print("Humidity: ");
Serial.print(temp_hum_val[0]);
Serial.println(" %");

lcd.setCursor(0, 1);
lcd.print("Humi. : ");
lcd.print(temp_hum_val[0]);
lcd.print(" %");
}
else{
Serial.println("Failed to get temprature and humidity value.");
}
}

Arduino, 온도측정(DS18b20)

DS18b20 온도센서

DS18b20 온도 센서를 이용하여 온도를 측정한다.

  • one-wire 버스 통신
  • 공급전압: 3.0V ~ 5.5V
  • 작동온도: -55ºC ~ +125ºC
  • 오차: +/-0.5 ºC (-10ºC ~ 85ºC 범위에서)

Pinout


schematic

DS18b20 센서는 두 가지 연결방법을 제공하는데, 하나는 VCC를 5V에 연결하는 Normal Mode이고 다른 하나는 VCC를 GND에 연결하는 Parasite Mode이다. 두가지 방법 모두 지원되지만 (경험상) Normal를 추천하며 (원인은 잘 모르겠지만) Parasite Mode에서는 온도센서가 작동이 되지 않는 경우도 가끔 있었다. 회로 구성을 위해 4.7㏀ 저항 1개가 필요하며 Normal Mode 구성을 위해 다음 그림과 같이 연결한다.

  • 3가닥의 선에 브레드보드에 연결할 수 있는 핀을 납땜하고, 노란색선과 빨간색선의 연결부위에 4.7㏀ 저항을 추가로 납땜하여 연결한다.

  • 수축튜브를 사용하여 연결부위를 절연한 뒤,

  • 구경이 조금 더 큰 수축튜브를 사용하여 3가닥을 하나로 감싸 마무리한다.


Normal Mode

DS18b20 Yellow Red Black
Arduino 2 5V GND
4.7㏀ O O

Parasite Mode

DS18b20 Yellow Red Black
Arduino 5V 2 GND GND
4.7㏀ O O

sketch

라이브러리 준비하기
  • OneWire (by Jim Studt etc.)
  • DallasTemperature (by Miles Burton)

sketch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <OneWire.h>
#include <DallasTemperature.h>

#define TEMP_PIN 2

OneWire oneWire(TEMP_PIN);
DallasTemperature sensors(&oneWire);

void setup() {
Serial.begin(9600);
Serial.println("DallasTemperature IC Control");

// Start up the library
sensors.begin();
}

void loop() {
sensors.requestTemperatures();

float temperatureC = sensors.getTempCByIndex(0);
float temperatureF = sensors.getTempFByIndex(0);
Serial.print(temperatureC);
Serial.println("*C");
Serial.print(temperatureF);
Serial.println("*F");
delay(2000);
}


TM1637에 온도 표시하기


schematic: Normal Mode


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <Arduino.h>
#include <TM1637Display.h>

// Module connection pins (Digital Pins)
#define CLK 4
#define DIO 7

TM1637Display display(CLK, DIO);
uint8_t data[] = { 0xff, 0xff, 0xff, 0xff };
int seg3, seg2, seg1, seg0;

// DS18b20 Temperature Sensor Setting
#include <OneWire.h>
#include <DallasTemperature.h>
#define TEMP_PIN 2

OneWire oneWire(TEMP_PIN);
DallasTemperature sensors(&oneWire);

float celciusTemperature; // Celcius Temperature
int tempTemperature;

void setup() {
Serial.begin(9600);
Serial.println("DallasTemperature IC Control");

// DS18b20 Temperature Sensor Initialize
sensors.begin();

// TM1637 Initialize
display.setBrightness(15); // 0 ~ 15 (15가 가장 밝음)
// display.setBrightness(0x0f);
}

void loop() {
sensors.requestTemperatures();
celciusTemperature = sensors.getTempCByIndex(0);
Serial.print("Temperature is: ");
Serial.println(celciusTemperature);
tempTemperature = celciusTemperature * 100;

seg0 = (tempTemperature / 1000) % 10; // 4자리에서 1000의 자리 숫자 저장
seg1 = (tempTemperature / 100) % 10; // 4자리에서 100의 자리 숫자 저장
seg2 = (tempTemperature / 10) % 10; // 4자리에서 10의 자리 숫자 저장
seg3 = tempTemperature % 10; // 4자리에서 1의 자리 숫자 저장

data[0]=display.encodeDigit(seg0); // 첫번째 FND에 1000의 자리 숫자배열
data[1]=display.encodeDigit(seg1); // 두번째 FND에 100의 자리 숫자 배열
data[2]=display.encodeDigit(seg2); // 세번째 FND에 10의 자리 숫자 배열
data[3]=display.encodeDigit(seg3); // 네번째 FND에 1의 자리 숫자 배열

// 온도가 100도보다 높으면 소수점 첫째자리까지 출력
if (celciusTemperature >= 100) {
display.showNumberDecEx(tempTemperature, (0x80 >> 2), true);
}

// 온도가 100도보다 낮으면 소수점 둘째자리까지 출력
else {
display.showNumberDecEx(tempTemperature, (0x80 >> 1), true);
}

delay(2000);
}

Arduino 스위치, 풀다운저항, 풀업저항

Tact Switch


Pull-down

동작 Arduino
스위치를 누르면 ON HIGH
스위치에서 손을 떼면 OFF LOW

schematic

저항(10k 이상)을 GND에 연결한 경우: Pull-down 회로


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const int switch_R = 2;

void setup() {
pinMode(switch_R, INPUT);
Serial.begin(9600);
}

void loop() {
int i = digitalRead(switch_R);
if(i == HIGH) { // 스위치를 누르면
Serial.println("1");
}
else { // 스위치에서 손을 떼면
Serial.println("0");
}
}

Pull-up

동작 Arduino
스위치를 누르면 OFF LOW
스위치에서 손을 떼면 ON HIGH

schematic

저항(10k 이상)을 VCC(5V)에 연결한 경우: Pull-up 회로


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const int switch_R = 2;

void setup() {
pinMode(switch_R, INPUT);
Serial.begin(9600);
}

void loop() {
int i = digitalRead(switch_R);
if(i == HIGH) { // 스위치를 누르면
Serial.println("1");
}
else { // 스위치에서 손을 떼면
Serial.println("0");
}
}


스위치를 사용할 때, 풀다운 or 풀업 저항을 구성해야 하는 이유

출처: 풀업(Pull-up)저항, 풀다운(Pull-down)저항 (악보쓰는 프로그래머)

Floating 문제

5V에 연결한 경우 GND에 연결한 경우
스위치를 누르면 HIGH, 스위치를 떼면 LOW ? 스위치를 누르면 LOW, 스위치를 떼면 HIGH ?
실제로는 스위치를 누른 상태에는 HIGH이지만, 스위치를 뗀 상태에는 HIGH와 LOW가 섞여서 들어간다 실제로는 스위치를 누른 상태에는 LOW이지만, 스위치를 뗀 상태에는 LOW와 HIGH가 섞여서 들어간다.

즉, 스위치를 누른 상태에서는 어떤 값을 가져야 하는지 확실하지만, 스위치를 뗀 상태에서는 어떤 값을 가져야 하는지 불확실하다는 문제가 발생하는데, 이것이 플로팅이다.


Floating의 해결과 또 다른 문제

  • (아래 그림대로 연결 절대 금지!) 위 그림 2개를 조합하여, 회로를 만든다고 가정하면

스위치를 누르면 HIGH, 스위치를 누르지 않으면 LOW ?

  • (그림대로 연결 절대 금지!) 그러나 위 그림과 같이 연결하면 또다른 심각한 문제가 발생한다.

  • 스위치를 누르는 순간, 5V와 GND가 직접 연결되어 전기적으로 합선(쇼트)이 되기때문에 아두이노 보드가 망가지거나 심한 경우, 아두이노가 연결된 컴퓨터가 망가질 수도 있다.

  • 이 그림은 마치 건전지 양극을 도선으로 그대로 연결한 것과 마찬가지인 상황이며, 실제 건전지에 이와 같은 방법으로 선을 연결하면 건전지가 과열되어 녹거나 심하면 불이 날수도 있다.


문제 해결 방법

  • 이런 문제를 해결하려면, 양극의 사이에 저항을 하나 넣어서 전류의 흐름을 방해하면 된다.


  • 이를 응용하여 스위치의 한쪽에 저항을 하나 추가로 연결하는 것이고, 이것이 바로 풀다운 저항 혹은 풀업 저항을 구성해야하는 이유이다.


아두이노 보드에 내장된 풀업저항 사용하기

  • 스위치를 사용하는 경우, 풀다운 저항 혹은 풀업 저항을 사용해야 하므로. 스위치를 구성할 때마다 10k~100kΩ 정도의 저항을 별도로 준비해서 회로를 구성해야 한다. 매우 중요한 문제이지만, 매우 귀찮기도 하다.

  • 그래서…친절하게도 아두이노 보드는 자체에 풀업 저항을 내장해두었다. 당연한 이야기이지만 아두이노 보드에 내장된 풀업 저항을 사용한다면, 별도의 저항을 준비하지 않아도 되기때문에 회로를 간단하게 구성할 수 있다.

  • 기본적으로 풀업 회로이므로 스위치가 눌리지 않은 상태가 HIGH이며, 스위치가 눌린 상태에서 LOW값을 갖습니다.

  • pinMode 선언시, INPUT_PULLUP을 사용합니다.

    1
    pinMode (2, INPUT_PULLUP);

schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const int switch_R = 2;

int R;

void setup() {
pinMode(switch_R, INPUT_PULLUP);
Serial.begin(9600);
}

void loop() {
R = digitalRead(switch_R);
if(R == LOW) { // 스위치를 누르면
Serial.println("0");
}
else { // 스위치에서 손을 떼면
Serial.println("1");
}
}


스위치를 눌러 LED 켜기

위에서 구성한 풀업 회로에 RGB LED를 추가하여, 각 스위치에 연결된 LED가 점등되도록 구성해보자.


schematic


sketch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int switch_R = 2;
const int switch_G = 3;
const int switch_B = 4;

const int pin_ledR = A0;
const int pin_ledG = A1;
const int pin_ledB = A2;

int R, G, B;

void setup() {
pinMode(switch_R, INPUT_PULLUP);
pinMode(switch_G, INPUT_PULLUP);
pinMode(switch_B, INPUT_PULLUP);

pinMode(pin_ledR, OUTPUT);
pinMode(pin_ledG, OUTPUT);
pinMode(pin_ledB, OUTPUT);

Serial.begin(9600);
}

void loop() {
R = digitalRead(switch_R);
G = digitalRead(switch_G);
B = digitalRead(switch_B);

if(R == LOW) { // R 스위치를 누르면
Serial.println("R=1");
digitalWrite(pin_ledR, HIGH);
}
else { // R 스위치에서 손을 떼면
Serial.println("R=0");
digitalWrite(pin_ledR, LOW);
}

if(G == LOW) { // G 스위치를 누르면
Serial.println("G=1");
digitalWrite(pin_ledG, HIGH);
}
else { // G 스위치에서 손을 떼면
Serial.println("G=0");
digitalWrite(pin_ledG, LOW);
}

if(B == LOW) { // B 스위치를 누르면
Serial.println("B=1");
digitalWrite(pin_ledB, HIGH);
}
else { // B 스위치에서 손을 떼면
Serial.println("B=0");
digitalWrite(pin_ledB, LOW);
}
}