2014年10月14日 星期二

用php透過usb和rs232串連控制MPU電路板

設備:
電路板+電源
MPU晶片
板子上rs232的線
rs232轉usb接頭線

需求:
web server透過rs232送一串hex code給MPU晶片,MPU晶片會輸出hex code給web server,讓使用者透過網頁介面操控板子上的功能

尋找裝置:
透過dmesg得知插入板子的usb線後是 /dev/ttyUSB0 這個裝置

設定stty:
# stty -F /dev/ttyUSB0 raw speed 9600 -crtscts cs8 -parenb -cstopb #設定,指定device,注意:重新開機後要重新設定stty 不然收到的output會有錯誤(空白或斷掉)
# stty -F /dev/ttyUSB0 -a  # 檢查,注意:要指定device

簡單的測試:
cat /dev/ttyUSB0 > rs232.log  # 因為板子傳hex編碼回來,把他倒到log檔再用sublime以hexadecimal開啟
# echo -en '\x01\x01\xCA\x60\x00\x03\xA9' > /dev/ttyUSB0 #送指令到板子上,output輸出到rs232.log

使用minicom監控:
安裝minicom後
# minicom -s
選Serial port setup(會依你使用者的設定檔而出現不同語言的介面),設定Serial Device和Bps/Par/Bits成9600 8N1(依你板子通訊協議而異),然後Save setup as dfl => Exit
因為板子要送指令才會有output,在另一個視窗送指令到板子,這邊才會顯示output,但因板子送回來的是hex code,這邊顯示是亂碼,故不使用minicom監控
minicom的操作方式
ctrl+a - z 顯示說明
ctrl+a - x 離開minicom
因操作指令和screen衝突且在我的screen設定下minicom介面會跑板,所以不在screen下操作

用php讀取hex code的檔案:
$handle = @fopen("rs232.log", "r");
if ($handle) {
    while (!feof($handle)) {
        $hex = bin2hex(fread ($handle , 4 ));
        print $hex."\n";
    }
    fclose($handle);

}

使用PHP-Serial控制:
https://github.com/Xowap/PHP-Serial
以examples/dummy.php為例:
include 'PhpSerial.php';

// Let's start the class
$serial = new PhpSerial;

// First we must specify the device. This works on both linux and windows (if
// your linux serial device is /dev/ttyS0 for COM1, etc)
$serial->deviceSet("/dev/ttyUSB0"); #改為你device的位置

// We can change the baud rate, parity, length, stop bits, flow control
$serial->confBaudRate(9600); # 改頻率
$serial->confParity("none"); # 8N1的N
$serial->confCharacterLength(8); # 8N1的8
$serial->confStopBits(1); # 8N1的1
$serial->confFlowControl("none");

// Then we need to open it
$serial->deviceOpen();

// To write into
$serial->sendMessage("\x01\x02\xCA\x60\x00\x03\x01\x03\x02\xAA"); # PHP的 "\x01" 直接是16進制字串,要使用\x01的字串要跳脫"\\x01",可從vim的highlight看出來。device直接吃hex code,所以這邊這樣寫。

// Or to read from
$read = $serial->readPort();

$hex = bin2hex($read); #將output從binary轉成hex顯示到螢幕
echo "$hex\n";

// If you want to change the configuration, the device must be closed
$serial->deviceClose();

// We can change the baud rate
$serial->confBaudRate(9600); #改成你device的頻率

不能打開device:
錯誤訊息:
PHP Warning:  Unable to open the device in php_serial.class.php on line 159
...
PHP Warning:  Device must be opened to read it in php_serial.class.php on line 476
解法:
php.ini設定open_basedir 加入 /dev/

當兩個script同時去讀寫device時,output會發生互相影響的錯誤:
解法: 使用flock()
在PhpSerial.php
$this->_dHandle = @fopen($this->_device, $mode); #在後面加入:
if (flock($this->_dHandle, LOCK_EX|LOCK_NB)) {
    $this->_lock = true;
    print "\nNo problems, I got the lock, now I'm going to sit on it.";
} else {
    $this->_lock = false;
    print "\nDidn't quite get the lock. Quitting now. Good night.";
}
把sendMessage改成:
if($this->_lock){
    $this->_buffer .= $str;

    if ($this->autoflush === true) $this->flush();

    usleep((int) ($waitForReply * 1000000));
}
Class新增屬性:
var $_lock = false;

將"00" 轉成hex code "\x00":
pack('H*',"00"); // hex code

使用C存取:
read.c:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>  
#include <string.h>

int main() {
        unsigned char byte;
        char ctl[] = "\x01\x01\xCA\x60\x00\x03\xA9";
        //  for(int i=0;i<strlen(ctl);i++)
        //      printf("%x",ctl[i]);
        int fd = open("/dev/ttyUSB0", O_RDWR);
        printf("fd=%d\n",fd);
        write(fd, ctl, 8);
        puts("Writed");
        int i;
        for(i =0;i<10;i++){
            int size = (int)read(fd, &byte, 1);
            if(size<1)break;
            printf("Read %d bytes hex=%X c=%c\n",(int)size,byte,byte);
        }
    return 0;
}

# make read
# ./read
output:
fd=3
Writed
Read 1 bytes hex=1 c=
Read 1 bytes hex=1 c=
Read 1 bytes hex=CA c=�
Read 1 bytes hex=60 c=`
Read 1 bytes hex=0 c=
Read 1 bytes hex=3 c=
Read 1 bytes hex=81 c=�
Read 1 bytes hex=0 c=
Read 1 bytes hex=2 c=
Read 1 bytes hex=2A c=*

網頁無法執行PhpSerial,但能在console執行
ex.
# php dummy.php
0102ca600003aa

http://domain/dummy.php
Fatal error: No stty availible, unable to run.
(把PhpSerial _exec()裡面的$out印出來的錯誤是:
Array ( [0] => [1] => stty: standard input: Inappropriate ioctl for device ) )
方法1. 指定stty絕對位置
結果:失敗,/bin/stty 和 /usr/bin/stty都出現一樣錯誤
方法2.  加一下 system(' ls /dev/ttyUSB0  -l');
PHP:
system('which stty');
echo "<br />";
system(' ls /dev/ttyUSB0  -l');
output:
/usr/sbin/stty
crw-rw---- 1 http http 188, 0 Oct 20 11:07 /dev/ttyUSB0
看起來正常
方法3. 修改PhpSerial.php
在PhpSerial()中
- if ($this->_exec("stty") === 0) {
+if ($this->_exec("stty -F /dev/ttyUSB0") === 0) { #將這段拿掉或指定-F /dev/ttyUSB0
    register_shutdown_function(array($this, "deviceClose"));
} else {
    trigger_error(
        "No stty availible, unable to run.",
        E_USER_ERROR
    );
}
結論:apache找不到stty設定(http@xxx $ stty ,出來是空白的),要指定設備才行
相關問題:
http://forum.arduino.cc/index.php?topic=38818.0;wap2
added the apache user (www-data) to the dialout group and it now works OK!! (但是我測試機是Arch Linux,沒有dialout,無法驗證。)

使用apache(httpd)執行command line:
修改/etc/passwd
http:x:33:33:http:/srv/http:/bin/false => http:x:33:33:http:/srv/http:/bin/bash
$ sudo su http #就能切換成http執行指令了

把http使用者增加到root群組:
# gpasswd -a http root
Adding user http to group root



沒有留言:

張貼留言