Node.js + 라즈베리파이로 원격 모터 제어하기

Node.js + 라즈베리파이로 원격 모터 제어하기

구글링해보면 라즈베리파이로 모터제어하는 글보다 아두이노로 하는게 많고, 라즈베리파이로 한다치다손 파이썬을 쓰는 경우가 많다. 하지만 나는 Node.js + Ra-Pi가 좋아서 이 조합으로 모터제어를 하였다.

목차

  1. 준비물
  2. Node.js 설치
  3. ngrok
  4. L298n 분석
  5. 배선
  6. 모터 제어
  7. 원격 제어

준비물

  1. 라즈베리파이3 B+ (너무 옛날것만 아니면 다른 모델도 가능할것 같다)
  2. DC모터
  3. 모터 드라이버 – L298n
  4. 건전지 홀더 – AA건전지 4구
  5. AA건전지 4개
  6. 안경 드라이버(꼭필요!)

Node.js 설치

nodesource 사이트에 가보면, installation instructions 부분이 있다. 자신이 원하는 Node.js 버전과 라즈베리파이에 설치된 운영체제를 바탕으로 코드를 가져다 쓰면된다. 참고로 나는 아래와 같이 작성하였다.

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs

ngrok

라즈베리파이는 외부에서 접근할 수 있는 공인 IP주소를 가질 수 없다. 그래서 집 밖에서는 라즈베리파이와 통신이 불가능 하다. 이때 ngrok을 쓰면 라즈베리파이에 공인 IP나 도메인이 부여된것처럼 된다. 그래서 집 밖에서도 라즈베리파이에 부여된 도메인으로 HTTP통신을 할 수 있게된다. 설정 방법은 이 글에서 ngrok부분을 보고 따라하면 된다.

L298n

L298n모듈은 모터 드라이버다. 간단히 말해서, 2개의 모터를 편하게 컨트롤하는데 도움을 준다. 이걸쓰면 모터의 방향전환, 속도 조절이 쉽게된다. 물론 이거 말고도 다른 모터 드라이버들도 많다. 다만 이게 저렴해서 대부분 이 모듈을 많이 쓰는것 같다.

각 부분의 역할

  1. Output A : 여기로 전기가 나가니까 이 부분에 모터의 + / 를 연결해 주면 된다.
  2. +12V Power : 모터를 돌릴때 라즈베리파이의 전력으로 돌리게 되면 라즈베리파이에 손상이 온다고 한다. 그래서 이 부분에 건전지의 +를 연결해준다.
  3. Power GND : 건전지의 부분과 라즈베리파이의 GND를 같이 연결해 준다.
  4. +5V Power : +12V처럼 건전지를 연결해 주면 되긴 하는데, 나같은 경우 저기에 연결하니까 모터가 안돌았다. 그냥 +12V 핀만 쓰면 될것같다.
  5. A Enable : 저게 모양이 이상하게 생겼는데, 사실은 캡이 씌어져있는거다. 저 캡을 제거하면 일반 핀처럼 툭 튀어나온게 보인다. 여기에 핀을 연결해서 Duty Cycle값을 보낸다. 다시 말하자면, Duty Cycle을 조절해서 Output A로 나가는 전압을 조하는데 쓰인다.
  6. Input : 총 4개의 핀이 있다. 위쪽 2개는 A모터의 방향전환을 위해 쓰이는거고 아래쪽 2개는 B모터의 방향전환을 위해 쓰인다. 위쪽부터 In1, In2, In3, In4가되는데, In1 => High, In2 => Low 를 보내면 모터가 정방향으로 돌고, 반대로 하면 반대 방향으로 돌아간다. 만약 모터를 멈추고 싶다면 둘다 Low를 조절하면 된다. In3, In4도 마찬가지이다.
  7. B Enable : Output B로 나가는 전압을 조절하는데 쓰인다.
  8. 5V Enable : 정확히는 모르겠지만, 아마 건전지로 들어오는 전압이 커도(5V이상) 5V로 유지되도록 Regulator를 작동시키는(?) 역할을 하는것 같다.

배선

모터는 하나만 연결했다. 핵심은

  1. 건전지를 12V에 꽂아야 한다(5V에 꽂았다가 계속 안되서 고생했다..)
  2. ENA(Enable A)와 In1, In2에 GPIO 핀을 연결했다

아래에 나올 코드에 맞춰서 배선을 했기 때문에 직접 만들어 보면서 이 글을 읽고있다면 위의 배선과 똑같이 해야한다.

참고로, L298n에 꽂을때는 드라이버로 나사 풀어서 선 끼고 다시 나사를 꽉 조여야 한다.

모터 제어

pigpio-l298n

Python과는 달리, javascript로 L298n을 사용한 예제가 많지 않아서 npm 모듈을 찾아 직접 소스코드를 읽어보기로 했다. 그래서 찾은게 pigpio-l298n 모듈이다. 이 모듈은 node.js용 pigpio 라이브러리를 기반으로 한다. 또 node.js용 pigpio는 C언어로 만들어진 pigpio를 기반으로 한다. 그래서 먼저 pigpio를 라즈베리파이에 설치해 줘야하고,

sudo apt-get update
sudo apt-get install pigpio

그 다음, pigpio를 기반으로 하는 node.js용으로 wrapping한 pigpio를 설치해 줘야한다.

npm install pigpio

이제 마지막으로 pigpio-l298n모듈을 설치하면 된다.

npm install pigpio-l298n

pigpio를 쓰는 이유

Python에 RPi.GPIO모듈이 있다면, Node.js에서는 onoff라는 유명한 모듈이 있다. 근데 이 모듈은 하드웨어적으로 Duty Cycle 을 조절하는 기능에는 특화되어있지 않은것 같다. 그래서 onoff document에도 아래와 같은 말이 나와있다.

결국, 우리는 모터를 섬세하게 제어해야 하기 때문에 하드웨어적으로(?) Duty Cycle을 조정해야해서 pigpio를 사용하는것이다. (사실 정확하게는 잘 모르겠다..)

코드 살펴보기

물론 pigpio-l298n 모듈을 그냥 가져다 써도 문제는 없겠지만, 코드가 심플하기 때문에 내용물을 보면서 어떻게 작동하는지 확인해보자.

// PinWrite.js

const Gpio = require('pigpio').Gpio;

const LOW = 0;
const HIGH = 1;

// 여기 pin이 바로 L298n의 in1, in2로 연결되는 gpio pin이다.
// 다시말해서 이 pin으로 HIGH나 LOW가 보내지게 되고, 모터의 회전 방향이 결정된다.
function PinWrite(pin) {
    this.pin = pin;
    this.gpio = new Gpio(pin, {mode: Gpio.OUTPUT});
}

Object.assign(PinWrite.prototype, {
    HIGH : function () {
        this.gpio.digitalWrite(HIGH);
    },
    LOW : function () {
        this.gpio.digitalWrite(LOW);
    },
    value : function () {
        return this.gpio.digitalRead();
    },
});
module.exports = PinWrite;
// PinPWM.js

const Gpio = require('pigpio').Gpio;

// Duty Cycle의 최소~최대 범위이다.
const MIN = 0;
const MAX = 255;

// 이 pin은 L298n의 Enable A,B에 연결될 gpio pin이다.
function PinPWM(pin) {
    this.pin = pin;
    this.gpio = new Gpio(pin, {mode: Gpio.OUTPUT});
}
Object.assign(PinPWM.prototype, {
    setSpeedPercent : function (p) {
        if (p < 0 || p > 100) {
            console.log("Arg out of range(0-100%).");
            return;
        }
        let dutyCycle = (MAX - MIN) * p / 100 + MIN;

        // 위에서 설정한 dutyCycle에 맞춰서 Pulse Width를 생성한다. 이는 결국 0~5V사이의 전압을 뿜어낼것이다.
        // 다만, 이 전압가지고는 모터를 움직일 수 없기 때문에, 우리는 건전지를 따로 또 연결해준것이다.
        this.gpio.pwmWrite(parseInt(dutyCycle));
    }
});
module.exports = PinPWM;
// l298n.js

const PinPWM = require('./PinPWM.js');
const PinWrite = require('./PinWrite.js');

exports.NO1 = 0; // 1번 모터
exports.NO2 = 1; // 2번 모터
exports.FORWORD = 1;
exports.BACKWORD = -1;
let deviceList = []; // 끽해야 2개 들어간다
//bcm code
function initDevice(dNum,en,in1,in2) {
    deviceList[dNum] = {
        en:en, // enable A/B
        in1:in1, // 방향조절하는 in1
        in2:in2,
        enGpio : new PinPWM(en),
        in1Gpio : new PinWrite(in1),
        in2Gpio : new PinWrite(in2),
    };
}

function enPort(dNum) {
    return deviceList[dNum].enGpio;
}

function in1Port(dNum) {
    return deviceList[dNum].in1Gpio;
}

function in2Port(dNum) {
    return deviceList[dNum].in2Gpio;
}

function L298N(enableA,in1,in2,enableB,in3,in4) {
    if (enableA !== null) {
        initDevice(this.NO1,enableA,in1,in2);
    }
    if (enableB !== null) {
        initDevice(this.NO2,enableB,in3,in4);
    }
}
Object.assign(L298N.prototype, {
    // PWM으로 속도 조절
    setSpeed : function(dNum, speed) {
        enPort(dNum).setSpeedPercent(speed);
    },

    // 앞으로 가는건 in1 => 1 / in2 => 0
    forward : function(dNum) {
        in1Port(dNum).HIGH();
        in2Port(dNum).LOW();
    },

    // 뒤로 가는건 in1 => 0 / in2 => 1
    backward : function(dNum) {
        in1Port(dNum).LOW();
        in2Port(dNum).HIGH();
    },

    // 멈추는건 in1 => 0 / in2 => 0
    stop : function(dNum) {
        in1Port(dNum).LOW();
        in2Port(dNum).LOW();
    },
    PinPWM : PinPWM,
    PinWrite : PinWrite,
});
module.exports = L298N;

예제코드

아래는 pigpio-l298n 모듈 안에 있는 예제 코드이다. 참고로 핀연결은 BCM기준이고 이 이미지를 참고하면 된다.

// index.js

// 커멘드 창에서 모터를 조정해야하니까,readline 모듈을 import한다
const readline = require('readline');
const L298N = require('../l298n.js');

//bcm code
// L298n 모터 드라이버와 라즈베리파이를 연결한 배선대로 핀넘버를 설정
let l298n = new L298N(17,27,22,null,null,null);
// 속도를 20%로 한다
l298n.setSpeed(l298n.NO1,20);

const rl = readline.createInterface({
	    input: process.stdin,
	    output: process.stdout
});
rl.on('line', function (input) {
    if (input === 'quit()') {
        rl.close();
    } else if (input === 'f') {
	    l298n.forward(l298n.NO1);
    } else if (input === 'b') {
	    l298n.backward(l298n.NO1)
    } else if (input === 't') {
	    l298n.stop(l298n.NO1);
    } else {
	    l298n.setSpeed(l298n.NO1,parseInt(input));
    }
});

// Ctrl + C로 프로그램을 멈추면 아래의 callback function이 호출된다
process.on("SIGINT", function(){
    l298n.stop(l298n.NO1);
    console.log('shutdown!');
    process.exit(0);
});

위의 index.js를 node index.js 이렇게 실행하고 f를 입력하면 모터가 앞으로 간다!

원격제어

위에서는 단순히 커맨드 창에서 모터를 제어했는데, 한걸음 나아가서 HTTP로 모터를 제어해보자. 위에서 라즈베리파이에 ngrok 설정은 끝났으니까, express설치해서 노드 서버만 하나 만들면 된다. 코드는 아래와 같다.

// server.js

// server setting
const express = require('express');
const app = express();

// mortor setting
const L298N = require('./l298n.js');
let l298n = new L298N(17,27,22,null,null,null);
l298n.setSpeed(l298n.NO1,80);

app.get('/forward', async (req, res) => {
  l298n.forward(l298n.NO1);
  return res.status(201).send({ message: '앞으로 갑니다' });
});

app.get('/backward', async (req, res) => {
  l298n.backward(l298n.NO1);
  return res.status(201).send({ message: '뒤로 갑니다' });
});

app.get('/stop', async (req, res) => {
  l298n.stop(l298n.NO1);
  return res.status(201).send({ message: '멈추겠습니다' });
});

app.get('/', (req, res) => {
  return res.status(201).send({ message: '모터제어 페이지' });
});

var server = require('http').Server(app);
let PORT = 8081;
server.listen(PORT, 'localhost');

위의 ngrok으로 라즈베리파이에 도메인을 연결시켜 놓았다면, 핸드폰이나 다른 컴퓨터의 브라우저에서 주소창에 http://my-car.ngrok.io/forward 라고 치면 모터가 앞으로 회전한다. (나는 놀랍고 기분이 좋았다 😆​)

참고자료

  1. http://www.makeshare.org/bbs/board.php?bo_table=arduinomotor&wr_id=12
  2. https://www.youtube.com/watch?v=bNOlimnWZJE
  3. http://blog.naver.com/PostView.nhn?blogId=chandong83&logNo=221084688966&parentCategoryNo=&categoryNo=44&viewDate=&isShowPopularPosts=true&from=search
  4. https://www.instructables.com/id/Control-DC-and-stepper-motors-with-L298N-Dual-Moto/
  5. http://www.makeshare.org/bbs/board.php?bo_table=arduinomotor&wr_id=12
  6. https://www.youtube.com/watch?v=AZSiqj0NZgU

마지막으로

읽어주셔서 감사합니다 😀
질문 및 지적은 댓글로 부탁드립니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Up Next:

Network의 개수 구하기

Network의 개수 구하기