[아두이노] Differential Wheeled RC카 만들기 #1-3 "신호 스케일링"

    스케일 (scale)

    '스케일'이라는 단어를 처음 접하는 사람들에게는 조금 생소할 수 있습니다.

    예를 들어 60분을 기준으로 하였을 때, 46분은 0~60분에 대하여 몇 %일까요?

    (46 / 60) * 100 = 약 77% 입니다.

    z그림으로 대충 나타내자면 이렇게 되겠죠.

    이렇게 1차 함수로 이해하시면 편합니다.

    이를 좀 더 프로그램을 짠다는 입장에서 생각해보면 아래와 같습니다.

    함수를 제작할 때 정의해야 할 매개 변수(parameter)로 5개의 매개 변수가 나올 수 있습니다.

    • in_min : 입력 값의 최소값
    • in_max : 입력 값의 최대값
    • out_min : 출력 값의 최소값
    • out_max : 출력 값의 최대값
    • in_val : 스케일링 할 입력값

    이제 위의 매개 변수를 가지고 함수를 만들면 되는데, 놀랍게도 아두이노에는 이미 간단한 스케일링 함수가 만들어져 있습니다!

    www.arduino.cc/reference/en/language/functions/math/map/

     

    map() - Arduino Reference

    Description Re-maps a number from one range to another. That is, a value of fromLow would get mapped to toLow, a value of fromHigh to toHigh, values in-between to values in-between, etc. Does not constrain values to within the range, because out-of-range v

    www.arduino.cc

    위에서 정의한 매개 변수대로라면 map(in\_val, in\_min, in\_max, out\_min, out\_max) 라고 사용하면 되겠군요.

     

    단, 정수형 연산만 가능하기 때문에 정밀한 스케일링 값을 원한다면 직접 함수를 제작하여 사용해야 한다네요.

    (결국 실수형 연산은 직접 제작을 해야하는 소리 입니다)


    굳이 스케일링 하려는 이유

    밑도 끝도 없이 갑자기 스케일링 함수를 사용하려는 이유는 가독성과 사용의 편리함에 있습니다.

    (그냥 내가 알아보기 쉽게 하려고..)

    앞서 PWM 신호를 받아 노이즈를 제거한 평균 신호값을 받았습니다.

    각 채널의 PWM 신호값은 988, 1783 이런식으로 들어오는데, 이 신호를 보고 "아하, CH1의 신호값이 1283이니 이는 -32.6%의 값이구나!"라고 단번에 인식할 수 있다면 스케일링 함수를 사용하지 않아도 됩니다.

     

    아마 전기나 계측쪽에서 일하시는 분들은 계장 신호들에 대해 많이 들어보셨을 겁니다.

    type 신호의 범위
    0% 25% 50% 75% 100%
    공기압 신호 0.2 kg/cm² 0.4 kg/cm²  0.6 kg/cm² 0.8 kg/cm² 1.0 kgf/cm²
    전압 신호 1 Vdc 2 Vdc 3 Vdc 4 Vdc 5 Vdc
    전류 신호 4 mAdc 8 mAdc 12 mAdc 16 mAdc 20 mAdc

    예를 들어 배관이나 탱크의 압력을 측정하기 위해 압력 센서를 사용한다고 해봅시다.

    사용할 센서의 측정 범위는 20Bar, 출력 신호는 4-20mA 전류 신호입니다.

    센서의 측정 범위를 알고 있기 때문에 센서의 출력 신호가 16mA일 때의 압력은 15Bar 라는 것을 알 수 있습니다.

     

    앞으로 PWM 신호를 적절히 가공하여 모터에 명령을 보내줄 MIXER를 만들어야 하는데, 백분율로 나타낸 채널 입력값이 있으면 가공할 때 더 편리할 것입니다.

    저는 반드시 백분율로 받아보고 싶습니다.


    map() 사용

      Minimum Idle Maximum
    CH1 (steering) 992
    (Left end)
    1489 1981
    (Right end)
    CH2 (throttle) 985
    (pulled trigger)
    1485 1967
    (pushed trigger)
    CH3 (toggle button) 992
    (Reset)
    - 1984
    (Set)

    각 채널별로 최소값, 최대값, 중간값(idle)을 측정하여 표기했습니다.

    이런식으로 1개의 채널만 살려두고 나머지는 코멘트 처리하여 각 채널의 값을 모니터링 했습니다. 각 채널의 조작부는 반드시 끝까지 위치시켜 10초 가량 모니터링 하며 최대/최소값을 보고 적었습니다.

    평균 함수라는 일종의 필터를 거쳐 나온 값이지만 그래도 1~2 정도 흔들림은 있었습니다.

    이제 각 채널의 최대/최소 범위를 알았으니 map()함수를 사용하여 PWM 입력 신호값을 정수형이 아닌 백분율(%)로 모니터링 해보겠습니다.

    이전의 코드는 건들지 않고 메인 loop()문에 시리얼 출력만 작성하여 단순하게 '확인'만 해보았습니다.

    void loop()
    {
      // get average of 10 times of received PWM signals.
      valueCH1 = getAvg(recvCH1, 10);
      valueCH2 = getAvg(recvCH2, 10);
      valueCH3 = getAvg(recvCH3, 10);
    
      // scaling with map().
      Serial.print("CH1: "); Serial.print(map(valueCH1, 992, 1981, -100, 100)); Serial.print("%\t");
      Serial.print("CH2: "); Serial.print(map(valueCH2, 985, 1967, 100, -100)); Serial.print("%\t");
      Serial.print("CH3: "); Serial.print(map(valueCH3, 992, 1984, -100, 100)); Serial.println("%");
    }

    전/후진 throttle을 담당하는 CH2의 값만 output 범위를 (-100, 100)이 아닌 (100, -100)으로 바꿨습니다.

    왜냐하면 대부분 방아쇠를 당겼을 때 전진, 방아쇠를 밀었을 때 후진을 한다고 인지하기 때문입니다.

    기존에 정의되어 있는 함수이기 때문에 실행은 깔끔하게 잘 되지만 연산이 0.5초에 1번 이루어 지네요.. (2Hz)

    혹시 몰라서 1개 채널만 읽어들여 봤는데 약 6Hz로 시리얼 출력됩니다.

    10회 평균을 내는 1개 채널이 1초에 6번 시리얼 출력을 하며, 3개 채널을 동시에 사용할 때 1초에 2번 출력되는 것을 보니 연산 속도때문에 그런 것 같습니다.


    스케일 함수 제작

    위에서 사용했던 map() 함수는 오직 정수형 연산에만 사용된다고 했습니다.

    하지만 저는 실수형 값을 받아보고 싶습니다.

    어차피... 같은 원리로 제작된 함수일테니 직접 만들어 보겠습니다.

    저는 중학교 수학 시간에 배운 것 같은데, 직선의 방정식을 구하는데 필요한 조건이 몇 가지 있습니다.

    • 두 점의 좌표를 알 때
    • 한 점과 직선의 기울기를 알 때
    • y 절편과 직선의 기울기를 알 때
    • x 절편과 y 절편을 알 때

    등등...

    나는 여기까지, 계산은 계산기가.

    저는 두 번째 경우를 사용하여 계산했는데요, 사실 어느것을 선택해도 상관이 없습니다.

    왜냐하면 결국 다 같은 공식이며, 가장 중요한 것은 이 계산 자체를 제가 하는것이 아니라 아두이노가 할 것이기 때문입니다.

    #define recvCH1 9   // PWM signal from receiver CH1: Throttle
    #define recvCH2 10  // PWM signal from receiver CH2: Rudder
    #define recvCH3 11  // PWM signal from receiver CH3: Toggle Button
    
    #define motorLeft_EN 3    // Left Motor: ENA pin
    #define motorLeft_OUT1 2  // Left Motor: IN1 pin
    #define motorLeft_OUT2 1  // Left Motor: IN2 pin
    #define motorRight_EN 5   // Right Motor: ENB pin
    #define motorRight_OUT1 7 // Right Motor: IN3 pin
    #define motorRight_OUT2 6 // Right Motor: IN4 pin
    
    #define ACT 4   // Linear Actuator enable signal output pin
    
    unsigned long valueCH1;
    unsigned long valueCH2;
    unsigned long valueCH3;
    
    int getAvg(int channel, int count) {
      int sum = 0;
      int cnt = 0;
      int avg;
    
      while(cnt < count) {
        sum = sum + pulseIn(channel, HIGH, 50000);
        cnt++;
      }
    
      avg = sum / count;
    
      return avg;
    }
    
    float getScale
      (int in_val, int in_min, int in_max, float out_min, float out_max) {
      float result = ((out_max-out_min)/(in_max-in_min)) * (in_val-in_max) + out_max;
      Serial.print(result, 1); Serial.println("%");
      return result;
    }
    
    void setup()
    {
      Serial.begin(115200);
      pinMode(recvCH1, INPUT);
      pinMode(recvCH2, INPUT);
      pinMode(recvCH3, INPUT);
    }
    
    void loop()
    {
      // get average of 10 times of received PWM signals.
      valueCH1 = getAvg(recvCH1, 10);
      valueCH2 = getAvg(recvCH2, 10);
      valueCH3 = getAvg(recvCH3, 10);
    
      // call scaler
      getScale(valueCH1, 992, 1981, -100.0, 100.0);
      getScale(valueCH2, 985, 1967, 100.0, -100.0);
      getScale(valueCH3, 992, 1984, -100.0, 100.0);
    }

    정말 계산했던 식 그대로 무식하게 때려 넣어 PWM 신호를 실수형 백분율로 다시 계산해줄 getScale() 함수를 제작했습니다.

    제작했다는 표현이 맞나 모르겠네요.

    그냥 수학 문제를 풀으라고 아두이노에게 시킨 것 뿐인데.

    float getScale
      (int in_val, int in_min, int in_max, float out_min, float out_max) {
      return float result = ((out_max-out_min)/(in_max-in_min)) * (in_val-in_max) + out_max;
    }

    시리얼 모니터 출력 없이 간단하게 사용하려면 위와 같이 사용해도 됩니다.

    실행 결과는 아래와 같습니다.

    모두 idle 상태에서의 값이고, 잘 동작합니다.

     

    여기서 몇 가지 짚고 넘어가야 할 부분이 있습니다.

    위의 스크린샷에서 빨간 상자를 보면 CH1 값은 0.5%로, 중간값 상태라는 점을 감안했을 때 꽤 0에 가깝다고 생각할 수 있습니다.

    하지만 CH2는 -1.8%로 중간값에서 어느정도 벗어나 있습니다.

    이는 나중에 모터 출력 신호를 만들어줄 때 deadzone을 만들어 없앨 수 있습니다.

    Deadzone에 관해서는 나중에 모터 구동 부분에서 다시 다룰 것 같습니다.

     

    또 신호 유실에 관한 문제가 있습니다.

    아래 스크린샷은 RC 송신기의 전원을 OFF하였을 때의 값입니다.

    PWM 신호가 0값이 들어오기 때문에 -300%가 틀린 계산은 아닐 겁니다.

    단지 원치 않는 결과일 뿐입니다.

    이는 RC 신호 유실 상태에 대해 분명히 정의를 해야 할 필요가 있다는 증거인 것 같습니다.

    댓글

    Designed by JB FACTORY