반응형

아두이노에서의 아날로그와 PWM

 

PWM: Pulse-width modulation

 

간단하게 얘기해서 아날로그의 선형 변화를 디지털 0과 1의 방식을 사용하여 흉내내는 것이다. 아날로그의 표현에 있어 대표적인 sine wave를 살펴보겠다. 

 

전압을 -10V 에서 10V까지 조정할 수 있는 가변 저항을 사용한다고 가정하였을 때 가변 저항을 일정한 시간에 맞춰 일정속도로 변화를 준다면 전압의 변화는 아래와 같은 sine wave의 형태를 띄게 되고 가변저항의 조정을 7.5V에서 멈춘다면 7.5V의 전압이 계속해서 출력되게 된다. 

 

 

디지털의 세계는 0과 1로 이루어져 있다. 즉 꺼진상태(0)나 켜진상태(1)로 표현 할 수 있는 것이다.

꺼진 상태에서는 0V를 출력하고 켜진상태에서는 10V를 출력하는 디지털 핀이 있다고 가정하고 10V에서 최대 밝기인 LED를 사용한다고 가정해 보자. 매 10초를 주기를 기준으로 2.5초 동안 켜고 7.5초동안 끈다고 하면 아래 도표와같이 25% Duty Cycle로 표현된다. 또한, 7.5초 동안 켜고 2.5초동안 끈다고 하면 아래 도표와같이 75% Duty Cycle로 표현된다. 시간이 10초 단위이므로 우리는 LED가 최대 밝기로 시간에 맞춰 깜박이는 것을 상상해 볼 수 있다. 

 

더나아가, 최대 밝기로 10초 동안 켜져있는 밝기의 총량을 100이라고 가정 해보자. 이럴 경우, 75% Duty Cycle일 때 7.5초동안 켜지고 2.5초 동안 꺼지게 되므로 이 때의 밝기 총량은 75가 되고 아날로그로 전압을 7.5V로 조절하고 10초동안 켰을 때와의 총량과 같게됨을 알 수 있다. 하지만 아날로그를 이용하면 7.5V의 밝기로 계속해서 켜져 있게되고 디지털의 75% Duty Cycle을 이용하는 경우에는 10V의 밝기로 깜박이게 되어 전혀 비슷하다고 느낄수가 없게 된다. 하지만, 이 상황에서 Duty Cycle의 기준이 되는 시간을 10초에서 10밀리초로 바꾸면 우리는 LED가 깜박이는 것 자체를 인식할 수 없게 되지만 10초 동안 발산하는 밝기의 총량은 그대로 유지되게 된다. LED가 깜박이는 것을 느끼지 못하고 단위 시간당 발산하는 밝기의 총량이 같기 때문에 우리는 아날로그 전압 7.5V 밝기로 LED를 켠것과 같게 느끼는 것이다. 이러한 원리를 적용하여 디지털 핀을 사용해 아날로그의 값 변동을 흉내내게 되는 것이고 이를 PWM이라 부른다. 

 

 

LED 뿐만 아니라 서보모터에서도 PWM 신호를 사용하는데, 서보 모터에서는 PWM의 펄스폭(Duty Cycle)을 모터의 위치값으로 변환하여 사용한다.

 

아두이노에서 PWM으로 제어를 한다는 의미는 상기의 PWM 변환을 하드웨어적으로 지원하는 전용핀의 사용과 적정 프로그램 또는 라이브러리를 활용한다는 것으로 이해해 볼 수 있다.  

PWM 핀(~ 표시)와 analogWrite() 함수가 대표적이고 analogWrite(val) 함수에 val값으로 숫자를 입력함으로써 아두이노는 PWM 신호를 출력하게 되는 것이다. 

 

그렇다면 시리얼 통신과 PWM제어는 어떻게 이해 해야할까? 간단하게 말해 PWM에 사용할 값을 전송하는 것으로 이해 하면된다. 디지털 핀값을 전송하는 것과 다를게 없어 보이지만 디저털 핀 값은 켜거나 끄는것에 대한 값을 보내는 것이고, PWM제어를 위해서는 일정 범위내의 숫자를 임의대로 보내거나 방향성을 갖는 숫자를 연속으로 해서 보낸다는 차이점이 있다.  

 

아두이노에서 PWM 제어의 기본 예제로 사용하는 LED의 밝기를 순차적으로 조절하는(점점 밝게 하고 다시 어둡게 하는, LED Fade in/out) 예제를 살펴 보겠다. 

 

 

 

 

 

밝기를 조절하기 위해 프로그램으로 하는 방법과 가변저항을 이용하는 방법을 주로 사용한다. 

 

 

프로그램으로는 analogWrite(pwm) 함수의 pwm값을 0 -> 255 ->0 순으로 0분터 1씩 증가 시키고 다시 255가 되면 1씩 감소시키는 코드를 사용한다. 

fade_in_out.zip
0.00MB

int ledPin = 6; // 아두이노 pwm핀(~ 핀)

int pwm = 0; // pwm 값

int fade = 1; // 증가값, 초기 '+', 255 도달시 '-' 변경, 0 도달시 '+'변경

 

void setup() {

  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);

}

 

void loop() {

  analogWrite(ledPin, pwm); 

  delay(10); // 10밀리초

  pwm += fade;  // pwm 값에 fade값을 더해서 pwm에 저장

  if(pwm <= 0 || pwm >=255) { // pwm이 0이거나 255이면

    fade = -fade;  // '+', '-' 부호변경 

  }

  Serial.println(pwm);

}

 

 

코드를 업로드 하면 LED가 밝아졌다 어두워졌다를 반복하는것을 확인 할 수 있다. 

 

 

가변저항을 사용할 경우 ADC(analog digital converter) 핀을 통해 입력되는 전압을  analogRead() 함수를 통해 읽고, 그 값을 PWM 제어에 맞는 범위의 숫자(0 ~ 255)로 변환하여 analogWrite(pwm) 함수에 다시 입력함으로써 PWM 제어를 하게 된다. 

 

아두이노 사이트에 있는 참조 페이지의 analogRead() 설명을 살펴보자

 

지정한 아날로그 핀에서 값을 읽습니다. 아두이노 보드는 6채널(미니와 나노는 8채널, 메가는 16채널), 10비트의 아날로그-디지털 변환기를 가지고 있습니다. 이는, 0에서 5V 사이의 입력 전압을 0에서 1024 사이의 정수 값으로 대응시키는 것을 뜻합니다. 이는 해상도가 5V/1024 혹은 0.0049V (0.49mV) 단위라는 것을 뜻합니다. 입력 범위와 해상도는 analogReference() 를 사용해서 바꿀 수 있습니다.

 

아날로그 입력을 읽는 데는 약 100마이크로초(0.0001초)가 걸리므로, 최대 읽기 속도는 1초에 약 1만 번입니다.

 

 

아날로그 읽기의 해상도가 10비트(0 ~ 1023)라고 했다. 입력 전압이 5V일때 가변저항에의해 조정된 전압을 읽어 0.0049V를 1로 환산하여 값을 출력하게 된다. 읽은 전압이 3V 일경우 약 612(3 / 0.0049)의 값이 되는 것이다. 이는 아두이노 우노에 해당되는 얘기이고 보드마다 지원하는 해상도가 다를 수 있다. Esp32 보드의 경우 12bit ADC 분해능: 4096(0 ~ 4095)을 지원한다.

 

 

아두이노 사이트에 있는 참조 페이지의 analogWrite() 설명을 살펴보자

 

아날로그 값(PWM 파)을 핀에 출력합니다. LED를 다양한 밝기로 켜거나 다양한 속도로 모터를 돌리는 데 쓸 수 있습니다. analogWrite() 를 호출하면, 해당 핀에 다음 analogWrite() (또는 digitalRead() 또는 digitalWrite() )가 불릴 때까지 특정 듀티 사이클의 구형파를 발생시킵니다. 대부분 핀에서 PWM 신호의 주파수는 약 490Hz입니다. Uno, 혹은 비슷한 보드에서, 5, 6번 핀은 약 980Hz의 주파수를 가집니다.

대부분의 아두이노 보드(ATmega168 또는 ATmega328P가 장착된)에서, 이 함수는 3, 5, 6, 9, 10, 11번 핀에서 동작합니다.

 

analogWrite(pin, value); // value: 듀티 사이클: 0 (언제나 꺼짐)과 255 (언제나 켜짐) 사이.

 

 

PWM을 제어하기 위한 값으로 0 ~ 255까지의 값을 사용한다. 이는 0일경우 0% Duty Cycle, 255일경우 100% Duty Cycle라는 얘기이다. 191이면 75% Duty Cycle이 되고 PWM핀의 출력전압이 5V일 경우 약 3.75V의 PWM 전압을 출력하고 PWM핀의 출력전압이 3.3V일 경우 약 2.47V의 PWM 전압을 출력한다. 

 

아두이노에 아래 코드를 업로드 하고 가변저항을 조정해 가면서 읽은 값들을 확인해 보자. 

potentiometer.zip
0.00MB

int val; // ADC 핀에서 전압을 읽고 변환한 값을 저장하는 변수

int pwm; // PWM 제어에 사용할 값 저장 변수

float duty; // Duty Cycle %값 저장 변수

float voltage; // V 환산값 저장 변수

 

void setup() {

  Serial.begin(9600);

}

  

void loop() {

  val = analogRead(A0);

  Serial.print("analogRead : ");  Serial.println(val);

  pwm = val/4;

  Serial.print("pwm : ");  Serial.println(pwm);

  duty = (float)pwm / 255.0 * 100.0;

  Serial.print("duty cycle : ");  Serial.print(duty);  Serial.println(" %");

  voltage = 5.0/1023.0 * (float)val; // 아두이노 1024 - 10 bit ADC 분해능

  Serial.print("voltage : ");  Serial.print(voltage);  Serial.println(" V");

  delay(1000);

 

}

 

아래와 같은 값을 시리얼 모니터에서 확인 할 수 있다. 

analogRead : 767

pwm : 191

duty cycle : 74.90 %

voltage : 3.75 V

 

 

 

가변저항으로 입력된 전압을 PWM 제어를 위한 값으로 변환하고 그 값을 analogWrite()함수에 입력하여 LED의 밝기를 조절해 보자  
int ledPin = 11; // 아두이노 pwm핀(~ 핀)
int val; // ADC 핀에서 전압을 읽고 변환한 값을 저장하는 변수
int pwm = 0; // pwm 값
 
void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}
 
void loop() {
  val = analogRead(A0);
  Serial.print("read: "); Serial.println(val);
  pwm = val/4;
  analogWrite(ledPin, pwm); 
  Serial.println(pwm);
  delay(100);
}
 

위의 함수를 업로드후 가변저항을 조절해 보면 시리얼 모니터에는 pwm 제어값 0 ~ 255 사이의 값이 표시되고 그에 따라 밝기가 조절되는 것을 볼 수 있다.

 

만약 블루투스 모듈을 아두이노에 연결해 놓았다면, 대기 상태의 빨간색 LED가 점멸하고 있을 것이다. 이 때에는 가변저항의 analogRead(A0) 값을 0에 맞추어 놓아도 블루투스의 작동 상태(대기모드)에 맞춰 Read 값이 0에서 최대 30까지 순간적으로 튀면서 그에따라 LED가 점멸하는 것을 볼 수 있다. 블루투스 모듈의 작동 상태에 따라 가변저항의 GND로 잡음이 들어 오는거 같다. 블루투스가 대기상태가 아니고 연결된 상태이면 상태는 나아진다. 하지만 과감히 if (val <= 30) val = 0; 코드를 추가하여 Read값의 30까지는 무시하도록 하자. 

potentiometer_led_control_2.zip
0.00MB

void loop() {

  val = analogRead(A0);

  Serial.print("read: "); Serial.println(val);

  pwm = val/4;

  if (val <= 30) val = 0;

  analogWrite(ledPin, pwm); 

  Serial.println(pwm);

  delay(100);

 

}

 

위의 코드에서 가변저항은 항상 연결되어 있어 아두이노는 메인 루프가 회전할 때마다 그 아날로그 값을 읽는다. 그렇게 읽은 값들은 가변저항을 조정하지 않는 이상 같은 값이 될 것인데, 매번 PWM제어 함수에 그 값을 입력하여 실행하는 것은 프로그램이 의도하는 바는 아닐 것이다. 이를 방지하기 위해 사용자가 가변저항을 조정했을때, 즉 앞선 값이 현재값과의 차이가 있을 경우 코드를 실행하도록 해보자. 

 

아래 코드를 추가해 주었다. 

 

int old_pwm = 0; // 비교용 변수 선언

if (old_pwm != pwm) { // 비교용 변수와 현재 pwm의 값이 같지 않으면 

  analogWrite(ledPin, pwm);  // PWM 제어 함수 실행

  old_pwm = pwm;             // 현재 pwm 값을 비교용 변수에 저장

  Serial.print("pwm: "); Serial.println(pwm);

}

그리고 delay(100);은 지워주자

potentiometer_led_control_compare.zip
0.00MB

int ledPin = 11; // 아두이노 pwm핀(~ 핀)

int val; // ADC 핀에서 전압을 읽고 변환한 값을 저장하는 변수

int pwm = 0; // pwm 값

int old_pwm = 0; // 비교용 변수 선언

 

void setup() {

  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);

}

 

void loop() {

  val = analogRead(A0);

  if (val <= 30) val = 0;

  pwm = val/4;

  if (old_pwm != pwm) {

    analogWrite(ledPin, pwm); 

    old_pwm = pwm;

    Serial.print("pwm: "); Serial.println(pwm);

  }

}

 

 

위의 코드를 업로드 하고 가변저항을 조절해보자.

 

 

코드의 의도는 가변저항을 조절해서 pwm 값이 변하게 되었을 때만 analogWrite() 함수가 실행되도록 하는 것이다. 블루투스의 연결을 제거한 상태에서는 가변저항을 건드려서 값이 변하게 될 경우 시리얼 모니터상에 pwm 값이 표시 되는것을 볼 수있다. 하지만 블루투스가 연결된 상태에서는 가변저항이 0일경우(이미 앞서 블루투스 잡음을 제거하기 위해 val의 30까지는 무시하기로 했기에 pwm의 0의 범위가 확장된 상태)는 시리얼 모니터 상에 어떠한 출력도 없다가 0을 넘어가는 순간(정확히는 val이 30을 넘어가는 순간) 가변저항을 조정하지 않고 그대로 놓아두어도 시리얼 모니터에 pwm 값 출력이 연속으로 되는 것을 볼 수있다. 이것 역시 블루투스의 잡음이 반영된 것으로 사용자가 가변저항을 조정하지 않았더라도 analogRead() 함수를 통해 읽은 값이 0 ~ 30 사이의 범위에서 수시로 변하게 되어 결과적으로 pwm의 값이 1 ~ 2정도 계속해서 변하게 되고 시리얼 모니테에 pwm값이 계속 출력되게 된다. 이는 블루투스와 가변저항이 연결된 아두이노와의 사이에 하드웨어적으로 GND를 공유함으로써 발생하는것 같다. 위 코드를 추가함으로써 블루투스가 연결된 상태에서는 완벽하진 않지만 그 실행 빈도를 어느정도 낮출수 있게 되었다. 하지만, 여전히 실행되는 횟수가 너무 많다.

 

millis()함수를 사용하여 1초에 10번만 analogRead함수를 읽고 코드를 실행하도록 아래의 함수를 추가하였다.

 

unsigned long read_now = 0; // millis() 값 비교용 변수선언, 변수 타입은 unsigned long 으로 해주는게 좋다.

if (millis() - read_now > 100) { // 현재의 millis()값 - read_now 값이 100보다 크면 

  read_now = millis(); // 현재의 millis()값을 read_now에 저장

  실행코드

 

if (val <= 30) val = 0; 를 if (val <= 15) val = 0; 로 변경해 주었다.

potentiometer_led_control_compare_delay.zip
0.00MB

int ledPin = 11; // 아두이노 pwm핀(~ 핀)

int val; // ADC 핀에서 전압을 읽고 변환한 값을 저장하는 변수

int pwm = 0; // pwm 값

int old_pwm = 0; // 비교용 변수 선언

unsigned long read_now = 0;

 

void setup() {

  Serial.begin(9600);

  pinMode(ledPin, OUTPUT);

}

 

void loop() {

  if (millis() - read_now > 100) {

    read_now = millis();

    val = analogRead(A0);

    Serial.print("val: "); Serial.println(val);

    if (val <= 15) val = 0;

    pwm = val/4;

    if (old_pwm != pwm) {

      analogWrite(ledPin, pwm); 

      old_pwm = pwm;

      Serial.print("pwm: "); Serial.println(pwm);

    }

  }

}

 

위의 코드를 입력하고 시리얼 모니터에 출력되는 값을 살펴보면 블루투스 작동에 의해 발생하던 잡음이 적어진 것처럼 보인다. 물론, 적어진 것은 아니다. 단지 앞선 코드 에서는 delay() 함수 없이 코드를 작성했으므로 메인 루프가 빠르게 돌아갈 때마다 값을 표시하고 들어온 잡음도 모두 표시되고(대부분 같은 값을 표시하고 잡음이 들어온 값의 빈도는 상대적으로 적다) 그렇게 잡음이 반영된 값이 모두 PWM 출력 함수에 반영이 되어 많게 느껴졌던 것이다. 읽는 빈도수를 줄이게 되면 읽은 값의 대부분을 차지하는 같은 값을 읽어올 확률이 높아져 잡음을 어느정도 제거한 것과 같은 효과가 나타나게 되었다. 

 

 

 

가변저항을 통한 PWM제어를 살펴 보았으니 블루투스 앱을 통하여 LED의 밝기를 조절해 보자.

 

arduino bluetooth controller PWM - 아두이노 원격제어 안드로이드 앱 버전 3.5 다운로드

arduino bluetooth controller PWM 매뉴얼

(*** 상기앱의 블루투스 연결은 안드로이드10 이하 버전에서만 작동합니다 ***)

아날로그 입력이라함은 그 값이 선형적인 변화를 갖는다고 볼 수 있다. 가변저항을 사용하여 일정한 속도로 일정 방향으로 조절 할 경우 시간 변화에 따라 0, 1, 3, 6, 10, 20, 50...  같이 값의 변화가 일정 방향으로 연속되게 변화 하게 된다. 이는 20다음에 10으로 변하지 않는다는 얘기이기도 하다.

 

가변저항의 경우 전선으로 아두이노 ADC핀에 연결되어 변경된 전압을 ADC핀에 공급하게 된다. 그렇게 공급된 전압값을 analogRead()함수를 이용하여 값을 읽고 PWM 제어에 사용할 값으로 변경하여 analogWrite() 함수에 적용하여 PWM 제어를 하는 것이다. 

 

시리얼 통신을 사용하게 된다면 시리얼 통신은 가변저항과 아두이노를 연결하는 전선에 해당 할 수 있고 블루투스 앱은 가변저항에 해당하는 PWM 제어용 입력기가 되는 것이다. PWM이 아날로그의 출력을 흉내 내듯이 블루투스 앱의 PWM 제어용 입력기는 가변저항을 흉내낼 수 있으면 된다. 또한, 가변저항과 같이 할 수 있다는 것은 순차적인 연속된 값을 보내어 LED의 밝기 조절을 할 수 있다는 것을 의미한다.

 

블루투스 앱에서 PWM값 입력기인 슬라이드를 이동시키게되면 "0xF3+1byte number+2byte number+0xF1"의 데이터를 전송하도록 프로그램 되어 있다. 데이터는 블루투스 모듈을 통해 아두이노로 전송되게 된다. 

 

"0xF3+1byte number+2byte number+0xF1" 프로토콜의 세부 의미는 다음과 같다.

 

0xF3(16진수) : 243(십진수), 11110011(이진수) - PWM값에대한 데이터임을 표시하는 헤더값

1byte number : 블루투스 앱 슬라이드 입력기의 고유번호

2byte number : PWM값 (0 ~ 65,535)

0xF1(16진수) : 241(십진수), 11110001(이진수) - 데이터의 종료를 표시

 

슬라이드 바를 이동시켜 값의 변화가 인지되면 프로그램은 위와 같이 총 5byte의 데이터를 전송하게 된다. 

2byte number 넘버의 사용은 PWM값의 최대 입력범위를 255 이상으로 설정할 경우 그 범위 내의 값을 전송하기 위해 사용하였다. 그리고 아두이노의 시리얼 통신은 1byte씩 받는다고 했었다. 그럼 아두이노에서 2byte number(16bit)를 수신할 때 어떤 순서로 들어올까? 16bit중 LSB영역(8bit)먼저 들어오고 그 다음으로 MSB영역(8bit)가 들어온다.

 

     0000 0000     0000 0000     - 2Byte(16bit)

  |   MSB 영역  |   LSB  영역  |

 

디지털 핀에서는 블루투스 앱에서 수신시 같은 프로토콜을 사용하여 LED핀의 상태변화를 표시 했었다. 

PWM 제어시에는 블루투스 앱에서 수신시  프로토콜 "0xF4+2byte number+0xF1" 를 추가하여 사용하게 된다. 나중에 자세히 다루겠다. 

 

전편의 디지털 핀 제어 코드와 비슷하며, 아래의 코드를 작성해 주었다. 

 

bool get_pwm_val = false; // 들어온 데이터에 PWM 헤더값이 있을경우 PWM값을 확인하는 코드 진입용 플래그 

uint8_t pwm_val_count = 0; // 들어온 4byte 데이터를 1byte, 2byte, 1byte 순으로 구분하여 받기 위한 카운터

uint8_t pwm_a[5] = {0, }; // 5byte 데이터를 저장하기 위한 배열

uint16_t pwm1 = 0;  // 블루투스 PWM값 입력기에 대응해서 PWM값을 저장하는 변수

uint16_t pwm2 = 0;  // 입력기가 총 3개 이므로 3개 선언

uint16_t pwm3 = 0;  

 

if (get_pwm_val != true && btSerial.peek() == 0xF3) get_pwm_val = true;

// PWM 값을 확인하는 코드에 진입하지 않은 상태에서 시리얼 버퍼에 PWM 헤더값이 있다면 PWM값을 확인하는 코드로 진입해라.

if (get_pwm_val == true) { 실행코드1 } else { 실행코드2 } // 코드 진입 플래그가 참이면 실행코드1, 아니면 실행코드2

 

실행코드1(메인루프가 1회전 할때마다 카운트를 1씩 증가시켜 5byte를 순차적으로 받는 코드)

if (pwm_val_count == 0) { pwm_a[0] = btSerial.read(); pwm_val_count++; }

// 카운터값이 0이면(0일 때에만) 핀값 배열 인덱스 0에 1byte값 저장하고 카운트 1증가

 

else if (pwm_val_count > 0 && pwm_val_count < 4) { // 카운트가 1 ~ 3일 경우 진입하고 데이터값 저장

  if (pwm_val_count == 1) { pwm_a[1] = btSerial.read(); pwm_val_count++; } // 1일때 슬라이드 고유번호 저장

  else if (pwm_val_count == 2) { pwm_a[2] = btSerial.read(); pwm_val_count++; } // 2일 때 PWM값 LSB 저장

  else if (pwm_val_count == 3) { pwm_a[3] = btSerial.read(); pwm_val_count++; } // 3일 때 PWM값 MSB 저장

}

 

else if (pwm_val_count == 4) { // 카운트가 4일 경우 진입

  pwm_a[4] = btSerial.read(); // 종료문자 0xF1 저장

  if (pwm_a[0] == 0xF3 && pwm_a[4] == 0xF1) { // 헤더와 종료문자값의 검증이 완료되면

    uint16_t temp = pwm_a[3];                         // PWM값 MSB를 16비트 임시 변수에 저장

    temp = temp << 8 | pwm_a[2];  // 임시 변수(PWM값 MSB)를 왼쪽으로 8비트만큼 쉬프트하고 LSB값과 비트연산

    if (pwm_a[1] == 1) { pwm1 = temp; analogWrite(ledPin, pwm1); Serial.print("pwm1: "); Serial.println(pwm1); }

    // 슬라이드 값이 1이면 비트연산한 PWM값을 pwm1 변수에 저장하고 PWM 제어 함수 실행

    else if (pwm_a[1] == 2) { pwm2 = temp; Serial.print("pwm2: "); Serial.println(pwm2); } // 슬라이 값이 2일때 코드

    else if (pwm_a[1] == 3) { pwm3 = temp; Serial.print("pwm3: "); Serial.println(pwm3); } // 슬라이 값이 3일때 코드

  }

  pwm_val_count = 0; // 5byte를 모두 받았으므로 다음 데이터를 받을 수 있도록 카운트 초기화

  get_pwm_val = false; // PWM값 확인이 완료 되었으므로 다음 데이터를 받을 수 있도록 코드 진입 플래그 초기화 

}

 

실행코드2 : 들어온 데이터를 그대로 시리얼 모니터에 출력

char text = btSerial.read();  // text value for some purpose.

Serial.write(text);      // print text to Serial Monitor

slide_led_control_bt.zip
0.00MB

위 코드를 업로드 하고 블루투스 앱의 슬라이드를 변경해 보면 슬라이드의 변경에 따라 시리얼 모니터에는 pwm값이 표시되면서 LED의 밝기가 조절됨을 확인해 볼 수 있다. 

 

 

슬라이드 변경은 아날로그 입력기인 가변저항을 흉내내는 것이지만 앱자체는 디지털로서 특정값을 입력할 수 있게된다.  

슬라이드 옆에 있는 텍스트 입력란에 원하는 값을 넣고 우측의 버튼을 클릭하면 해당 값에 따르는 밝기로 LED 밝기가 변경됨을 볼 수 있다. 

 

 

가변저항을 이용해 아날로그 값을 읽고 그값을 PWM제어 함수를 통하여 LED의 밝기를 조절하고 블루투스 앱을 통하여 LED 밝기를 조절하도록 코딩해 보았다.  이번에는 두개의 프로그램을 합쳐 가변저항으로도 조절하고 블루투스 앱에서도 조절 할수 있게끔 코딩을 해보자.

 

우선 코딩에 앞서 생각해 볼 사항이 있다. 

가변저항 및 블루투스 두 아날로그값 입력기 모두 하나의 장치와 연결되고 그 장치를 조종하게 된다. 블루투스에서 입력된 값은 블루투스에서 전송되어질때에만 아두이노가 수신하고 코드상 PWM값을 반영하게 되지만 가변저항의 경우 블루투스가 연결된 상태에서는 사용자가 조정하지 않아도 항상 pwm값이 조금씩 변동이 있어 의도치 않게 PWM 제어 함수가 실행 되어졌던 예제를 앞서 살펴 보았었다. 

 

가변저항으로 PWM 제어함수를 실행 시키고 있을때에 발생하는 의도치 않은 입력값은 현재 입력하고 있는 값과 차이가 작아 무시할 수 있을 정도 이지만 블루투스 앱의 슬라이드로 입력하고 있을 때에는 의도치 않게 유입되는 값과의 차이가 커질 수 있어 무시할 수 없게 된다. 

 

블루투스로 조정하고 있을때 가변저항의 값이 의도치 않게 유입되게 되면 블루투스 앱에서 보낸 값과는 다른값으로 갑자기 PWM 제어 함수를 실행한다는 의미이다. 즉 블루투스 슬라이드를 이용하여 255쪽으로 이동을 하고 있을때 가변저항의 값 변동으로 인해 50의 값으로 실행 될 수 있다는 의미이다. 

 

 

이러한 이유로 블루투스 앱을 통해 PWM을 제어할 경우에는 가변저항의 입력을 방지하도록 코드를 짜야 할 것이다. 

 

상기의 예제 코드에 아래의 코드를 추가해 주었다.

 

int val;                   // ADC 핀에서 전압을 읽고 변환한 값을 저장하는 변수

int h_pwm = 0;       // 가변저항 pwm 값

int old_h_pwm = 0;  // 비교용 변수 선언

 

bool bt_pwm_use = false; // 블루투스 제어 사용시 가변저항 입력방지 플래그

uint16_t bt_pwm = 0;      // 비교용 블루투스 값 저장 변수 선언

bool hand_over = false;   // 가변저항으로 제어권 넘겼다는 표시 플래그

 

if (millis() - read_now > 100) { // 1초에 10번씩 analogRead(A0); 실행 - millis() 함수 범위를 줄여 주었다.

    read_now = millis();

    val = analogRead(A0);     // 가변저항 값 읽기

    if (val <= 15) val = 0;     // 읽은 값이 30이하이면 0으로 변경

    h_pwm = val/4;              // analogRead() 값을 PWM 제어 값으로 변경

}

 

if (bt_pwm_use == false) { 실행코드1 } else { 실행코드2 } // 블루투스 앱에서 제어하지 않을경우 실행코드1

  

실행코드1

if (old_h_pwm != h_pwm) { // 앞선 값과 비교하여 값이 같지 않을경우 코드 실행

      analogWrite(ledPin, h_pwm); 

      old_h_pwm = h_pwm;

      Serial.print("h_pwm: "); Serial.println(h_pwm);

}

 

실행코드2

if (abs(old_h_pwm - h_pwm) > 10) { // 가변저항 PWM의 앞선 값과 현재값과의 차이가 10보다 크면

  hand_over = true; // 제어권을 가져오는 코드를 실행해라. (가변저항을 일정범위 이상 움직이면)

}

if (hand_over == true) { // 제어권 획득 코드 진입 (가변저항을 움직여)

  Serial.println(h_pwm);

  if (abs(h_pwm - bt_pwm) < 10) { // 가변저항 PWM값과 블루투스 앱 PWM값의 차이가 10보다 작으면

    bt_pwm_use = false;              // (가변저항의 값이 블루투스 값에 근접하면) 가변저항으로 컨트롤

    btSerial.print("potentiometer control");  // 제어권 변경 표시

    Serial.print("potentiometer control"); 

  }

}

 

if (bt_pwm_use == false) { // 가변저항에 제어권이 있는 상태에서 블루투스 앱에서 PWM 값이 전송되어 올 경우 

   old_h_pwm = h_pwm; // 현재의 가변저항 값을 저장하여 가변저항 제어권 획득여부 비교에 사용 

   hand_over = false;     // 제어권 획득 코드 진입 플래그 초기화 하여 제어권 획득 가능하도록 준비

   btSerial.print(" ");      // 제어권 변경 표시 삭제

   bt_pwm_use = true;  //블루투스 PWM값 사용 선언과 가변저항 값 사용방지

}

bt_pwm = pwm1; // 가변저항값과의 비교를 위한 최신값을 변수에 저장(블루투스 PWM값이 변경될 때마다)

slide_potentiometer_led_control_bt.zip
0.00MB

위 코드를 업로드 하고 가변저항을 조정하면 밝기가 조절되는 것을 볼 수 있고 블루투스 앱으로도 밝기 조절이 잘되는것을 확인 할 수 있다. 가변저항값이 100인 상태에서 블루투스 앱으로 PWM 값을 200으로 조정을 하여 제어권이 블루투스 앱에 있다고 할 때 가변저항을 움직이기 시작한 후 가변저항 변동값이 10을 넘어갈 때부터 시리얼 모니터에 가변저항 값이 표시되고 그 값이 190에 도달 했을때 "potentiometer control" 메세지를 보게되고 이 때 부터는 가변저항으로 LED 밝기 조절을 할 수 있게 된다. 

 

 

 

 

 

이제, 가변저항으로 LED 밝기를 조절할때 그 조절된 값에 맞게 블루투스 앱의 슬라이드와 값이 표시되도록 해보자.

 

블루투스 앱에서 PWM 값에 대한 수신 프로토콜은 헤더값을 0xF3, 0xF4로 설정하여 두가지로 되어 있다. 

 

1. "0xF3+1byte number+2byte number+0xF1" : 블루투스에서 입력시 입력값 echo 용도 -> 변경값을 받아 버튼에만 표시

2. "0xF4+1byte number+2byte number+0xF1" : 가변저항으로 조절시 슬라이드와 버튼값 변경

 

위 프로토콜의 차이는 슬라이드 값의 변경 유무이다. 안드로이드 앱에서는 슬라이드의 값이 변경될 경우 단지 슬라이드의 상태표시만 변경하는게 아니라 버튼을 클릭한 것과 같은 역할을 하게 된다. 만약 블루투스로 입력을 할 때 슬라이드 버튼의 상태를 표시 하게하게 되는 프로토콜을 echo로 사용한다면 입력한 값과 수신된 값의 시간차에의해 값이 수신된 시점에 과거에 입력했던 위치로 슬라이드가 뒤로 점프하게 되며 터치 스크린에의해 감지된 위치로 다시 복귀하여 값을 보내는 것을 반복하게 되는 의도치 않는 현상이 발생하게 된다.  따라서 블루투스 앱에서 입력한 값의 확인을 위한 용도로는 위의 1번 프로토콜을 사용해야 하며 가변저항의 변화를 표시하는 용도로는 2번 프로토콜을 사용해야만 한다. 

 

앞선 예제의 코드에 아래의 코드를 추가해 주었다. 

if (old_h_pwm != h_pwm) {

  analogWrite(ledPin, h_pwm); 

  old_h_pwm = h_pwm;

  btSerial.write(0xF4); // 슬라이더 상태표시 용도 헤더값 

  btSerial.write(1);    // 슬라이더 번호

  btSerial.write(h_pwm); // PWM 제어값이 0 ~ 255 이므로 LSB값만 보내주고 

  uint8_t temp = 0;  // btSerial.write(0);은 입력이 안된다. 0 입력시 테이터크기(16bit? 8bit?)가 명확하지 않아 그런거같다.

  btSerial.write(temp);   // MSB는 0으로 보내준다.

  btSerial.write(0xF1);

  Serial.print("h_pwm: "); Serial.println(h_pwm);

}

slide_potentiometer_led_control_bt_state.zip
0.00MB

위의 코드를 업로드하고 가변저항을 조절하면 조절된 값에 따라 안드로이드 블루투스 앱의 슬라이드 바와 그 값이 변동됨을 확인할 수 있다. 

 

슬라이드로 조정한 값을 echo로 다시 받아 값 표시 부분에 표시 하고 싶다면 앞서 언급한 0xF3 헤더를 사용한 프로토콜을 이용하면 된다. 

 

블루투스 수신부의 analogWrite(ledPin, pwm1); 코드에 아래 코드를 추가해 보자.

btSerial.write(0xF3); // 슬라이더 상태표시 용도 헤더값 

btSerial.write(1);    // 슬라이더 번호

btSerial.write(pwm1);

uint8_t temp = 0;

btSerial.write(temp);

btSerial.write(0xF1);

slide_potentiometer_led_control_bt_state_echo.zip
0.00MB

위 코드를 업로드 해주면 블루투스 앱의 슬라이드 변경시 오렌지 색으로 값이 표시되고 echo를 수신하게 되면 갈색으로 변경되는 것을 확인 할 수 있다. 

 

*상기의 PWM 값 전송시 그 값에 대한 echo의 사용은 슬라이드 바를 이용한 PWM 값 전송시 슬라이드바를 빠르게 움직여 전송되는 값이 많아질 경우 전송/수신의 원활한 동작을 방해하게 된다.  PWM값의 echo가 꼭 필요한 경우가 아니라면 사용하지 않는게 좋다.

 

슬라이드 바를 빠르게 움직일 경우 짧은 시간에 연속된 값을 계속해서 보내는 중에 가끔씩 수신 데이터에 오류가 발생하는것 확인 하였으며(오류가 있는 데이터는 헤더 및 종료문자 검증으로 걸러짐) 블루투스 4.0 BLE의 경우는 아두이노에서 딜레이 없이 연속해서 두 함수를 실행하여 휴대폰으로 데이터를 전송할 경우 두번째 함수의 데이터를 수신하지 못하게 됨을 확인했다. 

 

btSerial.print("test1"); 

btSerial.print("test2");  // 두번째 메세지를 받지 못함.

 

btSerial.print("test1"); 

delay(10);

btSerial.print("test2");  // 두번째 메세지 수신.

 

다음편에는 LED를 이용하여 가변저항의 제어권 획득을 표시해보겠다.

 

최신 업데이트 유료앱(모든 안드로이드 버전 블루투스 연결 지원)

https://postpop.tistory.com/175

 

ADUCON - Arduino wireless remote control application

Arduino remote control app using a wireless module available in Arduino. Bluetooth 2.0 Classic / 4.0 BLE : HC-05, HC-06, HM-10, AT-09, BT05, ESP32, etc. Wi-Fi : ESP01, ESP8266 NodeMcu, ESP32, etc. https://play.google.com/store/apps/details?id=com.tistory.p

postpop.tistory.com

 

관련 글

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 1편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 2편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 3편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 4편

[arduino] - 아두이노 - 안드로이드를 이용한 무선 원격제어 그리고 시리얼 통신 - 5편

[arduino] - 블루투스 4.0 BLE 이용 아두이노 및 ESP32 원격제어

 

[arduino] - 아두이노 - 시리얼통신 주요함수와 예제, String class

[arduino] - 아두이노 - ESP01 wifi 모듈 무선 원격제어 그리고 시리얼 통신 - 6편

[arduino] - ESP8266 - NodeMcu 1.0 와이파이 이용 원격제어(soft AP, wifi)

[arduino] - ESP32 - Dev Module 와이파이 이용 원격제어(soft AP, wifi)

 

 

+ Recent posts