NXP

通过网络远程控制ARM系统GPIO应用实例

2019-07-12 13:46发布

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). 硬件准备 a). Colibri VF61核心板模块以及Colibri开发板载板 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 详细的关于管脚定义和GPIO号码请参考VF61手册,Colibri开发板手册以及GPIO号码映射. 3). 软件配置 a). Embedded Linux系统为Toradex官方发布image V2.5, 也可以参考这里基于OpenEmbedded框架自己编译系统image. b). 在VF61 Embedded Linux中安装node.js并验证运行正常 ------------------- root@colibri-vf:~# opkg update root@colibri-vf:~# opkg install nodejs root@colibri-vf:~# node                                                         > process.exit ()                                                                root@colibri-vf:~# ------------------- c). 在VF61 Embedded Linux中安装nodemon, Express和body-parse ------------------- root@colibri-vf:~# opkg install tar root@colibri-vf:~# curl -L https://www.npmjs.com/install.sh | sh root@colibri-vf:~# npm install express root@colibri-vf:~# npm install body-parser        root@colibri-vf:~# npm install -g nodemon ------------------- 4). GPIO操作 详细Embedded Linux GPIO操作请见这里,下面只用一个GPIO-63举例 a). 初始化GPIO-63并设置为Input ------------------- root@colibri-vf:/sys/class/gpio# echo 63 > /sys/class/gpio/export               root@colibri-vf:/sys/class/gpio# echo "in" > /sys/class/gpio/gpio63/direction ------------------- b). 查看当前GPIO-63电平 ------------------- root@colibri-vf:/sys/class/gpio/gpio63# cat /sys/class/gpio/gpio63/value        0 ------------------- c). 按下GPIO连接的Button同时查看电平 ------------------- root@colibri-vf:/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’代码如下 -------------------                   Colibri VF61 node.js webserver                                      

Access to the VF61 GPIO using Node.js and AJAX

        
                                              
------------------- 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’, 启动服务 ------------------- root@colibri-vf:~# 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框架修改显示效果适配移动设备 -------------------                            Colibri VF61 node.js webserver                                                                 
                  

Access to the VF61 GPIO using Node.js and AJAX

                  
                           
                                                                
                  
                  

LEDs

                  
                           
                                                                
                           
                                                                
                  
                  

Switches

                  
                           
                                                                
                           
                                                                
                  
        
------------------- 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查看