1. 程式人生 > >通過網路遠端控制ARM系統GPIO應用例項

通過網路遠端控制ARM系統GPIO應用例項

1). 簡介

本文著重展示基於Embedded Linux快速開發Web應用程式訪問ARM系統以便於遠端操控硬體GPIO或監控其狀態.文中演示例項所採用的ARM系統硬體為Toradex 基於NXP Vybrid的產品級ARM核心板Colibri VF61配合其Colibri開發板;軟體框架為使用Node程式碼通過filesystem來訪問操控硬體GPIO,建立一個簡單UI並使用Express開發框架搭建一個最簡web伺服器並通過AJAX呼叫和客戶端進行通訊,最後在客戶端使用Javascript配合HTML+CSS/jQuery/AJAX開發一個客戶端應用程式.

Node.js是一個用於基於Javascript開發應用的伺服器端的runtime環境,自帶有管理檔案系統模組,而Linux作業系統支援使用檔案系統訪問硬體裝置的特性,因此可以基於Node開發應用來訪問操控系統硬體GPIO裝置.不過儘管Node也提供瞭如HTTP等模組可以用來搭建Web伺服器,本文還是採用更為簡單的利用

Express開發框架來搭建.

客戶端應用使用Javascript程式語言開發,通過瀏覽器執行.配合HTML和CSS, Javascript可以實現使用者互動的網頁開發,也就是響應式設計. 不過直接使用Javascrit進行開發需要一定週期,本文采用一個跨瀏覽器JavaScript庫jQuery來快速實現如DOM操作,事件處理以及AJAX方法(和伺服器非同步通訊達到動態改變頁面內容的一系列技術)使用等

2). 硬體準備

b). GPIO以及對應Button和LED設定如下所示, VF61最多支援99個GPIO管腳, 本文使用4個GPIO分別連線Colibri載板上面的兩個Button和兩個LED.

SW5, X21 Pin9 <-> GPIO-63(SODIMM_106), X11 Pin39

SW6, X21 Pin11 <-> GPIO-89(SODIMM_135), X11 Pin40

LED1, X21 Pin2 <-> GPIO-88(SODIMM_133), X11 Pin41

LED2, X21 Pin4 <-> GPIO-68(SODIMM_127), X11 Pin42

3). 軟體配置

a). Embedded Linux系統為Toradex官方釋出image V2.5, 也可以參考這裡基於OpenEmbedded框架自己編譯系統image.

b). 在VF61 Embedded Linux中安裝node.js並驗證執行正常

-------------------

[email protected]:~# opkg update

[email protected]:~# opkg install nodejs

[email protected]:~# node                                                        

> process.exit ()                                                               

[email protected]:~#

-------------------

c). 在VF61 Embedded Linux中安裝nodemon, Express和body-parse

-------------------

[email protected]:~# opkg install tar

[email protected]:~# curl -L https://www.npmjs.com/install.sh | sh

[email protected]:~# npm install express

[email protected]:~# npm install body-parser       

[email protected]:~# npm install -g nodemon

-------------------

4). GPIO操作

詳細Embedded Linux GPIO操作請見這裡,下面只用一個GPIO-63舉例

a). 初始化GPIO-63並設定為Input

-------------------

[email protected]:/sys/class/gpio# echo 63 > /sys/class/gpio/export              

[email protected]:/sys/class/gpio# echo "in" > /sys/class/gpio/gpio63/direction

-------------------

b). 檢視當前GPIO-63電平

-------------------

[email protected]:/sys/class/gpio/gpio63# cat /sys/class/gpio/gpio63/value       

0

-------------------

c). 按下GPIO連線的Button同時檢視電平

-------------------

[email protected]:/sys/class/gpio/gpio63# cat /sys/class/gpio/gpio63/value       

1

-------------------

5). Nodedemo1  - 使用Node.js操控GPIO

完整程式碼請見這裡, 需建立’server.js’檔案, 核心部分列出如下:

a). Filesystem 模組

-------------------

/* Modules */

var fs = require('fs'); //module to handle the file system

var debug = require('debug')('myserver'); //debug module

/* VF61 GPIO pins */

const         SW5 = '63', // PTD31, 106(SODIMM)

         SW6 = '89', // PTD10, 135(SODIMM)

        LED1 = '88', // PTD9, 133(SODIMM)

         LED2 = '68', // PTD26, 127(SODIMM)

/* Constants */

const HIGH = 1, LOW = 0;

-------------------

b). GPIO配置和操作模組

-------------------

function cfGPIO(pin, direction){......}  //export pin if not exported and configure the pin direction

function rdGPIO(pin){......}  //read GPIO value and return it

function wrGPIO(pin, value){......}  //write value to corresponding GPIO

-------------------

c). LED根據Button狀態顯示模組

-------------------

function copySwToLed(){......} //Copy the SW values into the LEDs

-------------------

主函式模組,使用setinterval迴圈函式,第二個引數為迴圈間隔

-------------------

setImmediate(function cfgOurPins(){

         cfGPIO(LED1, 'out'); //call cfGPIO to configure pins

         cfGPIO(LED2, 'out');

         cfGPIO(SW5, 'in');

         cfGPIO(SW6, 'in');

         setInterval(copySwToLed, 50); //poll the GPIO and copy switches status to LEDs

});

-------------------

d). 在VF61上面執行’server.js’, 並通過按鍵來驗證LED顯示

-------------------

node server.js

-------------------

6). Nodedemo2 - 建立webserver和最簡web UI來遠端操控GPIO

完整程式碼請見這裡

a). 修改’server.js’, 建立Webserver

-------------------

/* Modules */

var express = require('express'); //webserver module

var bodyParser = require('body-parser'); //parse JSON encoded strings

var app = express();

/* Constants */

const HIGH = 1, LOW = 0, IP_ADDR = '10.20.1.108', PORT_ADDR = 3000;

//Using Express to create a server

app.use(express.static(__dirname));

var server = app.listen(PORT_ADDR, IP_ADDR, function () {

    var host = server.address().address;

    var port = server.address().port;

    var family = server.address().family;

    debug('Express server listening at http://%s:%s %s', host, port, family);

});

-------------------

b). 修改’server.js’, 增加HTTP POST接收並處理返回資料部分

-------------------

app.use(bodyParser.urlencoded({ //to support URL-encoded bodies, must come before routing

         extended: true

}));

app.route('/gpio') //used to unite all the requst types for the same route

.post(function (req, res) { //handles incoming POST requests

        var serverResponse = {status:''};

        var btn = req.body.id, val = req.body.val; // get the button id and value

        if(val == 'on'){ //if button is clicked, turn on the leds

           wrGPIO(LED1, HIGH);

           wrGPIO(LED2, HIGH);

                debug('Client request to turn LEDs on');

           serverResponse.status = 'LEDs turned on.';

           res.send(serverResponse); //send response to the server

        }

        else{ //if button is unclicked, turn off the leds

           wrGPIO(LED1, LOW);

            wrGPIO(LED2, LOW);

            debug('Client request to turn LEDs off');

            serverResponse.status = 'LEDs turned off.';

            res.send(serverResponse); //send response to the server

        }

});

setImmediate(function cfgOurPins(){

         ......

    setInterval(copySwToLed, 50); //poll the GPIO and copy switches status to LEDs

});

-------------------

c). 在當前資料夾建立’index.html’

./ 請從這裡下載jQuery庫並放在同一資料夾

./ ‘index.html’程式碼如下

-------------------

<!DOCTYPE html>

<html>

<head>

         <meta charset="UTF-8">

         <title>Colibri VF61 node.js webserver</title>

         <!-- Add jQuery library -->

         <script src="jquery-1.12.3.js"></script>

         <script type="text/javascript" src="client.js"></script>

</head>

<body>

         <h1>Access to the VF61 GPIO using Node.js and AJAX</h1>

         <form>

                   <input type="checkbox" class="btn" id="btn1">

                   <label for="btn1" id="btn1l" style="color:red">OFF</label>

         </form>

</body>

</html>

-------------------

d). 在當前資料夾建立’client.js’, 用來處理客戶端操作

-------------------

$(function(){

         $(".btn").click(function clickHandling(){ //if element of class "btn" is clicked

                   var btn_status = {id:"", val:""}; //data to be sent to the server

                   if(this.checked){ //check whether button is pressed or not

                            $(this).siblings().html("ON").css("color","green"); //changes label and color

                            btn_status.id = $(this).attr("id"); //get which button was clicked

                            btn_status.val = "on"; //tell the server the button is clicked

                   }

                   else{ //if button was unclicked

                            $(this).siblings().html("OFF").css("color","red"); //changes label and color

                            btn_status.id = $(this).attr("id"); //get which button was clicked

                            btn_status.val = "off"; //tell the server the button is unclicked

                   }

                   $.post("/gpio", btn_status, function (data, status){ //send data to the server via HTTP POST

                            if(status == "success"){ //if server responds ok

                                     console.log(data);//print server response to the console

                            }

                   },"json"); //server response shuld be in JSON encoded format

         });

});

-------------------

e). 在VF61執行’server.js’, 啟動服務

-------------------

[email protected]:~# DEBUG=myserver node server.js                               

  myserver Starting VF61 GPIO control +0ms                                     

  myserver Express server listening at http://10.20.1.108:3000 IPv4 +711ms     

  myserver Configuring GPIO88 as out +46ms                                     

  myserver Configuring GPIO68 as out +9ms                                      

  myserver Configuring GPIO63 as in +3ms                                        

  myserver Configuring GPIO89 as in +2ms

-------------------

f). 在客戶端瀏覽器檢視

登入http://10.20.1.108:3000,在開啟的頁面操作checkbox來控制LED燈


7). Nodedemo3 - 升級友好介面客戶端UI來遠端操控GPIO

完整程式碼請見這裡

a). 修改’index.html’, 使用Bootstrap框架修改顯示效果適配移動裝置

-------------------

<html>

<head>

         <meta charset="UTF-8">

         <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- mobile first -->

         <title>Colibri VF61 node.js webserver</title>

         <!-- Add jQuery library -->

         <script src="jquery-1.12.3.js"></script>

         <!-- Using bootstrap -->

         <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">

         <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

         <script type="text/javascript" src="client.js"></script>

</head>

<body>

         <div class="container">

                   <h1>Access to the VF61 GPIO using Node.js and AJAX</h1>

                   <div class="row">

                            <div class="col-sm-12">

                                     <input type="button" class="btn btn-block btn-info" id="MSG" value="Application running">

                            </div>

                   </div>

                   <h2>LEDs</h2>

                   <div class="row">

                            <div class="col-sm-4">

                                     <input type="button" class="btn btn-block true_btn" id="LED1">

                            </div>

                            <div class="col-sm-4">

                                     <input type="button" class="btn btn-block true_btn" id="LED2">

                            </div>

                   </div>

                   <h2>Switches</h2>

                   <div class="row">

                            <div class="col-sm-4">

                                     <input type="button" class="btn btn-block" id="SW1">

                            </div>

                            <div class="col-sm-4">

                                     <input type="button" class="btn btn-block" id="SW2">

                            </div>

                   </div>

         </div>

</body>

</html>

-------------------

b). 如下修改’server.js’

-------------------

const GPIO = {   LED1:'88', LED2:'68',

                                     SW5:'63', SW6:'89'};

app.route('/gpio') //used to unite all the requst types for the same route

.post(function (req, res) { //handles incoming POST requests

    var serverResponse = {};

    serverResponse['gpio'] = {};

    var btn = req.body.id, val = req.body.val; // get the button id and value

    if(btn == "getGPIO"){ //if client requests GPIO status

    serverResponse['status'] = 'readgpio';

    for(io in GPIO){ //iterate through all GPIO

               serverResponse.gpio[io] = +rdGPIO(GPIO[io]); //and get its current status as a string

    }

    }

    else{ //otherwise change GPIO status

    serverResponse.status = 'changegpio'; //echo which button was pressed

        if(val == 1){ //if button is clicked, turn on the leds

           serverResponse.gpio[btn] = HIGH;

           wrGPIO(GPIO[btn], HIGH);

        }

        else{ //if button is unclicked, turn off the leds

           serverResponse.gpio[btn] = LOW;

           wrGPIO(GPIO[btn], LOW);

        }

    }

    res.send(serverResponse); //echo the changes made to GPIO

});

setImmediate(function cfgOurPins(){

         for(io in GPIO){ //for every GPIO pin

                   if(io.indexOf('LED') != -1){ //if it is a LED

                            cfGPIO(GPIO[io], 'out'); //configure it as output

                   }

                   else if(io.indexOf('SW') != -1){ //make sure it is a switch

                            cfGPIO(GPIO[io], 'in'); //configure it as input

                   }

         }

});

-------------------

c). 修改’client.js’如下, 處理客戶端獲取GPIO狀態顯示並響應點選事件

./ 主程式

-------------------

$(function main(){ //wait for the page to be fully loaded

         curr_led = "LED1"; //global variable pointing the currently selected LED

         last_led = "LED2"; //tells how many LEDs are there

         getGPIO(updateCurrentStatus); //update the GPIO status after loading the page

         timerHandler = setInterval(function(){

                   getGPIO(swAction)//updates the GPIO status periodically

         }, 200); //the update interval is set to every 200ms=0,2s

         $(".true_btn").click(btnHandling); //if element of class "true_btn" is clicked     

         $("#MSG").click(resumeApp); //if element of id "MSG" is clicked

});

-------------------

./ LED點選事件處理

-------------------

function btnHandling(){

         /* Check which button was pressed and change button and LED state based on button state */

         var id, val ; //data to be sent to server

         if($(this).attr("class").indexOf("success") != -1){ //check whether button is pressed or not

                   $(this).removeClass("btn-success").addClass("btn-danger").val($(this).attr("id") + ":OFF"); //changes label and color

                   id = $(this).attr("id"); //get which button was clicked

                   val = 0; //tell the server the button is clicked

         }

         else{ //if button was unclicked

                   $(this).removeClass("btn-danger").addClass("btn-success").val($(this).attr("id") + ":ON"); //changes label and color

                   id = $(this).attr("id"); //get which button was clicked

                   val = 1; //tell the server the button is unclicked

         }

         changeLedState(id, val); //send data to the server to change LED state

}

function changeLedState(led, new_state){

         /* Send request to the server to change LED state */

         var btn_status = {id:led, val:new_state}; //data to be sent to the server

         $.post("/gpio", btn_status, function (data, status){ //send data to the server via HTTP POST

                   if(status == "success"){ //if server responds ok

                            console.log(data);//print server response to the console

                   }

         },"json"); //server response shuld be in JSON encoded format

}

function getGPIO(callback){

         /* Gets the current GPIO status*/

         $.post("/gpio", {id:'getGPIO'}, function (data, status){ //send data to the server via HTTP POST

                   if(status == "success"){ //if server responds ok

                            callback(data);

                   }

         },"json"); //server response shuld be in JSON encoded format

}

function updateCurrentStatus(gpio_status){

         /* Updates the page GPIO status*/

         if(gpio_status.status == 'readgpio'){ //if all ok on the server-side

                   for (next_pin in gpio_status.gpio){ //iterate through all GPIO

                            if(next_pin.indexOf("SW") != -1){ //if it is a switch

                                     gpio_status.gpio[next_pin] = !gpio_status.gpio[next_pin]; //then inverts the logic, for 0 means pressed

                            }

                            if(gpio_status.gpio[next_pin]){ //if state is ON (or HIGH)

                                     $("#" + next_pin).attr("class","btn btn-block btn-success").val(next_pin + ":ON"); //changes label and color

                            }

                            else{ //if state is OFF (or LOW)

                                     $("#" + next_pin).attr("class","btn btn-block btn-danger").val(next_pin + ":OFF"); //changes label and color

                            }

                            if(next_pin == curr_led){ //set as active to tell that this is the selected LED

                                     $("#" + next_pin).addClass("active");

                            }

                   }

         }

}

-------------------

./ SW 按鍵點選事件處理, SW1切換當前LED焦點, SW2改變當前LED狀態

-------------------

function swAction(gpio_current){

         /* Handles the switches actions based on their state */

         var value, led_index ; //tmp variables

         if(gpio_current.status == 'readgpio'){ //if all ok on the server-side

           for (next_pin in gpio_current.gpio){ //iterate through all GPIO

                   if(gpio_current.gpio[next_pin]){ //if current GPIO was pressed

                            switch(next_pin){ //for every switch there is an action

                           case "SW1": //when pushbutton 1 is pressed

                                               if(curr_led == last_led){ //points to the first LED

                                                        curr_led = "LED1";

                                               }

                                               else{ //otherwise points to the next LED

                                                        led_index = parseInt(curr_led.replace("LED","")) + 1;//get the current LED index and increment its value

                                                        curr_led = "LED" + led_index; //concatenate string to value, e.g. "LED2"

                                               }

                                               break; //this should be removed if button priority is not desired

                                     case "SW2": //when pushbutton 2 is pressed

                                               changeLedState(curr_led,  (1-gpio_current.gpio[curr_led])); //invert currently selected LED state

                                               break;//this should be removed if button priority is not desired

                                     default: break; //this GPIO was not a switch

                                     }

                            }

                   }

         }

         updateCurrentStatus(gpio_current);

}

-------------------

d). 在VF61上面同上執行server.js啟動,在客戶端瀏覽器登入http://10.20.1.108:3000檢視