lynxeyedの電音鍵盤

組み込みとか電装とか

Swift Playgrounds向けアクセサリ「PlaygroundBluetooth API」を使ってBluetooth LEデバイスと通信をする

WWDC2017で発表されたPlaygroundsBluetooth API便利そう

今年最後のブログになります。
今何してあそんでるかの報告がわりに。

WWDC2017

What’s New in Swift Playgrounds - Apple WWDC 2017

iPad向けのSwift Playgroundsアクセサリとして提供されているPlaygroundBluetoothを使って見ました。CoreBluetoothベースですが、Appleが言うにはより一貫性がある(これはSwift PlaygroundsでのBluetooth通信のユーザエクスペリエンスが一貫していると言う意味だと思います)APIで、以下のコンポーネントを提供しています

PlaygroundBluetooth API

  • PlaygroundBluetoothCentralManager(および、そのDelegate)
    • Bluetooth機器との接続、通信 (CBCentralManagerに近い)
  • PlaygroundBluetoothConnectionView(および、そのDelegate)
    • Bluetooth機器の検出、接続、接続解除などのためのUI

特にPlaygroundBluetoothConnectionViewはUIを提供して検出、接続、解除までを一貫してしてくれるので、プログラマによってBluetooth検出、接続、解除時のUIが全く異なり、アプリを使うユーザが途方に暮れる…事は少なくなりそうです。
と言っても2017年12月現在、Appleのページで纏まった資料やサンプルコードが見当たりませんでした。探し方が下手なだけかも知れませんが。
2018年9月更新AppleのページにPlaygroundBluetooth Frameworkの詳細情報が掲載されています。最新情報はこちらを確認してください。

サンプルコードないかな…

唯一見つけたのが以下のサイト。
https://swiftexample.info/snippet/liveviewswift_bricklife_swift
iPhoneの電池残量が確認できるサンプルコード
なんか途中から全力で生のCoreBluetooth触りにいってるけど大丈夫かこのサンプルコード…。

カスタムプロファイルのペリフェラルとお話しする

これをベースに、カスタムプロファイルを持ったBluetoothペリフェラルに接続し、データをやり取りするターミナルっぽいやつを作りました。
iPadは終始セントラルとして動きます。

  • read
  • writeWithResponse
  • writeWithoutResponse
  • notify

キャラクタリスティックに対応します。

ペリフェラル側とその設定が必要です。
今回はセントラル(iPad)からきたデータをWrite(writeWithoutResponse)し、Readに対してはそのデータを返すようにします。(ループバックっぽい動作にする)

いきなりBLEを持ってる組み込みマイコンにさせると、バグったときに切り分けが出来なくなって詰みそうですので、今回はiPhoneにLightBlueアプリをいれて仮想的にペリフェラルとして動かしています。
VirtualPeripheralの提供するサービスは「Blank」を選択し

  • Service UUID : 0x1111
  • Characteristic UUID : 0x2222

を選択します。

PropertyはReadとWriteWithoutResponseにチェック

そうすると、iPadは以下のように動きます。

Communicate with BLE accessory using PlaygroundBluetooth API

参考資料


iOS 11 Programming

iOS 11 Programming

  • 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
  • 発行日:2017年11月16日
  • 対応フォーマット:製本版,PDF
  • PEAKSで購入する

Swift Playgrounds(Swift4.0)で横スクロールするグラフを描く

単純な横スクロールのグラフだけでいい

iOSプログラミングのお仕事がちょいちょい来てるのですが、ブランクが長すぎてiPad Proでリハビリをする毎日です。Cordova/PhoneGapも最近聞かないなぁ。
最近はReactがアツいとかなんとか聞くのですが。

iOS11になってから今まで順調に動いていたPythonista3でグラフをプロットしようとすると何かの契機にアプリごとクラッシュしてしまうようになりました。
自分がmatplotlibの扱いがよくわかってないのもあるのですが…そのうち解決するでしょうということで。

Swiftでグラフを描くのはライブラリを使うのが良いようです。たとえばGitHub - core-plot/core-plot: Core Plot source code and example applications
今回は簡単な折れ線グラフが書きたいだけなのでこのライブラリの学習コストが見合わないなと思いいろいろ探していたところ、ありました。

Swiftでシンプルなグラフ描写する ... | FiNC Developers Blog

ここで紹介されているものをお借りしました。ありがとうございます。
プロット数が多くなると、UIViewサイズに合わせてx軸のマージンがどんどん小さくなるので、UIScrollViewを使って横スクロールしてしまうようにしました。またSwift 2.0の記述だったので4.0に対応するように一部直しています。
なお、プロット間のx軸の距離は80に固定してしまっています。複数の折れ線グラフも楽々。


f:id:Lynx-EyED:20171106153539p:plain

動作してるところ


Swift ScrollView plot

感想

iPad上のSwift Playgroundsで結構しっかりしたプログラム組めるのはすごいなと思いました。電車内やカフェで殴り書きするのにちょうどいい。
でも、ガチなソフト組むとなると、StoryBoardが欲しくなるしエディタとしては機能が限定されているので、MacXCodeが必要になりますね。

コード

本家
Swiftでシンプルなグラフ描写する ... | FiNC Developers Blog
のコードと比較したほうがいいかも。急いででっち上げてしまったので…

//
//  ViewController.swift
//  SimpleGraph
//
//  Created by Yoshimi Kondo on 2015/11/19.
//  Copyright © 2015年 yoshimikeisui. All rights reserved.
//
//  fixed to use UIScrollView by lynxeyed 2017/11/05


import PlaygroundSupport
import UIKit

public class ViewController: UIViewController{
    let wd = 500
    let ht = 630
override public func viewDidLoad() {
        super.viewDidLoad()
        drawLineGraph()
    //drawBarGraph()
    }
    
    func drawLineGraph() {
        let stroke1 = LineStroke(graphPoints: [1, 3, 6, 4, 9, 12, 4, 11, 15,5,1,5,10,5])
        let stroke2 = LineStroke(graphPoints: [16,3, nil, 6, 4, 8])
        let stroke3 = LineStroke(graphPoints: [24,5, 5, 6, 4, 5])
        
        stroke1.color = #colorLiteral(red: 0.239215686917305, green: 0.674509823322296, blue: 0.968627452850342, alpha: 1.0)
        stroke2.color = #colorLiteral(red: 0.854901969432831, green: 0.250980406999588, blue: 0.47843137383461, alpha: 1.0)
        stroke3.color = #colorLiteral(red: 0.584313750267029, green: 0.823529422283173, blue: 0.419607847929001, alpha: 1.0)
        let lineGraphView = UIScrollView()
        lineGraphView.frame.size = CGSize(width: wd, height: ht)
        let graphFrame = LineStrokeGraphFrame(strokes: [stroke1, stroke2, stroke3])
        lineGraphView.isPagingEnabled = false
        lineGraphView.backgroundColor = UIColor.darkGray
        view.addSubview(lineGraphView)
        lineGraphView.addSubview(graphFrame)
        let fw = graphFrame.xAxisPointsCount * Int(graphFrame.xAxisMargin)
        lineGraphView.contentSize = CGSize(width:fw, height:ht) //スクロールビュー内のコンテンツサイズ設定
       
        
    }
    
    func drawBarGraph() {
        let bars = BarStroke(graphPoints: [nil, 1, 3, 1, 4, 9, 12, 4])
        bars.color = UIColor.green
        
        let barFrame = LineStrokeGraphFrame(strokes: [bars])
        
        let barGraphView = UIView(frame: CGRect(x: 0, y: 240, width: view.frame.width, height: 200))
        barGraphView.backgroundColor = UIColor.gray
        barGraphView.addSubview(barFrame)
        
        view.addSubview(barGraphView)
    }
    
    override public func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
}

protocol GraphObject {
    var view: UIView { get }
}

extension GraphObject {
    var view: UIView {
        return self as! UIView
    }
    
    func drawLine(from: CGPoint, to: CGPoint) {
        let linePath = UIBezierPath()
        
        linePath.move(to: from)
        linePath.addLine(to: to)
        linePath.lineWidth = 0.5
        
        let color = UIColor.white
        color.setStroke()
        linePath.stroke()
        linePath.close()
    }
}

protocol GraphFrame: GraphObject {
    var strokes: [GraphStroke] { get }
}

extension GraphFrame {
    // 保持しているstrokesの中で最大値
    var yAxisMax: CGFloat {
        return strokes.map{ $0.graphPoints }.flatMap{ $0 }.flatMap{ $0 }.max()!
    }
    
    // 保持しているstrokesの中でいちばん長い配列の長さ
    var xAxisPointsCount: Int {
        return strokes.map{ $0.graphPoints.count }.max()!
    }
    
    // X軸の点と点の幅
    var xAxisMargin: CGFloat {
        //return view.frame.size /CGFloat(xAxisPointsCount)
        return 80.0 //    }
}

class LineStrokeGraphFrame: UIView, GraphFrame {
    
    var strokes = [GraphStroke]()
    convenience init(strokes: [GraphStroke]) {
        self.init()
        self.strokes = strokes
    }
    
    override func didMoveToSuperview() {
        if self.superview == nil { return }
        //self.frame.size = self.superview!.frame.size
        let uisview = ViewController()
        
        //var frameWidth = gs.
        self.frame = CGRect(x: 0, y: 0, width: 3000, height: uisview.ht )
        self.view.backgroundColor = UIColor.clear
        
        strokeLines()
    }
    
    func strokeLines() {
        for stroke in strokes {
            self.addSubview(stroke as! UIView)
        }
    }
    
    override func draw(_ rect: CGRect) {
        drawTopLine()
        drawBottomLine()
        drawVerticalLines()
    }
    
    func drawTopLine() {
        self.drawLine(
            from: CGPoint(x: 0, y: frame.height),
            to: CGPoint(x: frame.width, y: frame.height)
        )
    }
    
    func drawBottomLine() {
        self.drawLine(
            from: CGPoint(x: 0, y: 0),
            to: CGPoint(x: frame.width, y: 0)
        )
    }
    
    func drawVerticalLines() {
        for i in 1..<xAxisPointsCount {
            let x = xAxisMargin*CGFloat(i)
            self.drawLine(
                from: CGPoint(x: x, y: 0),
                to: CGPoint(x: x, y: frame.height)
            )
        }
    }
}


protocol GraphStroke: GraphObject {
    var graphPoints: [CGFloat?] { get }
}

extension GraphStroke {
    var graphFrame: GraphFrame? {
        return ((self as! UIView).superview as? GraphFrame)
    }
    
    var graphHeight: CGFloat {
        return view.frame.height
    }
    
    var xAxisMargin: CGFloat {
        return graphFrame!.xAxisMargin
    }
    
    var yAxisMax: CGFloat {
        return graphFrame!.yAxisMax
    }
    
    // indexからX座標を取る
    func getXPoint(index: Int) -> CGFloat {
        return CGFloat(index) * xAxisMargin
    }
    
    // 値からY座標を取る
    func getYPoint(yOrigin: CGFloat) -> CGFloat {
        let y: CGFloat = yOrigin/yAxisMax * graphHeight
        return graphHeight - y
    }
}


class LineStroke: UIView, GraphStroke {
    var graphPoints = [CGFloat?]()
    var color = UIColor.white
    
    convenience init(graphPoints: [CGFloat?]) {
        self.init()
        self.graphPoints = graphPoints
    }
    
    override func didMoveToSuperview() {
        if self.graphFrame == nil { return }
        self.frame.size = self.graphFrame!.view.frame.size
        self.view.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        let graphPath = UIBezierPath()
        
        graphPath.move(
            to: CGPoint(x: getXPoint(index: 0), y: getYPoint(yOrigin: graphPoints[0] ?? 0))
        )
        
        for graphPoint in graphPoints.enumerated() {
            if graphPoint.element == nil { continue }
            let nextPoint = CGPoint(x: getXPoint(index: graphPoint.offset),
                                    y: getYPoint(yOrigin: graphPoint.element!))
            graphPath.addLine(to: nextPoint)
        }
        
        graphPath.lineWidth = 5.0
        color.setStroke()
        graphPath.stroke()
        graphPath.close()
    }
}

class BarStroke: UIView, GraphStroke {
    var graphPoints = [CGFloat?]()
    var color = UIColor.white
    
    convenience init(graphPoints: [CGFloat?]) {
        self.init()
        self.graphPoints = graphPoints
    }
    
    override func didMoveToSuperview() {
        if self.graphFrame == nil { return }
        self.frame.size = self.graphFrame!.view.frame.size
        self.view.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        for graphPoint in graphPoints.enumerated() {
            let graphPath = UIBezierPath()
            
            let xPoint = getXPoint(index: graphPoint.offset)
            graphPath.move(
                to: CGPoint(x: xPoint, y: getYPoint(yOrigin: 0))
            )
            
            if graphPoint.element == nil { continue }
            let nextPoint = CGPoint(x: xPoint, y: getYPoint(yOrigin: graphPoint.element!))
            graphPath.addLine(to: nextPoint)
            
            graphPath.lineWidth = 30
            color.setStroke()
            graphPath.stroke()
            graphPath.close()
        }
    }
}

PlaygroundPage.current.liveView = ViewController()

iPad版Swift Playgroundsでsocket通信する

ネットワークにつながる組み込み機器とスマホを繋げるネタは数多ありますが、ほとんどがAndroid機器のようです。iOSバイスがそのような所謂"低レベル通信"のほとんどをユーザーに開放してないというのが理由のようです。
あとXCode使う=Mac買わなきゃ & お布施とかそういうやつ。

W5500(サーバー側)とiPad Pro(クライアント)だけでTCP/IPソケット通信できるといいなぁと探したらありました。

perl - How to implement a socket connection using a swift playground? - Stack Overflow

上記情報が古いバージョンのSwiftでいろいろ修正が必要でしたが、動作したのでメモがてら。
iPadのSwift Playgroundsアプリだけでプログラムできます。

これを現行のSwift 3.1用に修正しました。
下記例ではサーバーアドレス192.168.0.123:4567ポートと通信しています。(IP固定)

/* 
 Socket communication on Static IP
 Works on Swift 3.1 + playgrounds support
 */
import UIKit
import PlaygroundSupport

let addr = "192.168.0.123"
let port = 4567  //PORT Num

var buffer = [UInt8](repeating: 0, count: 255)

var inp : InputStream?
var out : OutputStream?
Stream.getStreamsToHost(withName: addr, port: port, inputStream: &inp, outputStream: &out)

if inp != nil && out != nil {
    let inputStream : InputStream = inp!
    let outputStream : OutputStream = out!
    inputStream.open()
    outputStream.open()
    
    if outputStream.streamError == nil && inputStream.streamError == nil {
        let queryString = "クライアントから送信したいデータ"
        let queryData = [UInt8](queryString.utf8) 
        while true {
            UnsafePointer<UInt8>(queryData)
            outputStream.write(queryData, maxLength: queryData.count)
            var readChars: Int = inputStream.read(&buffer, maxLength: buffer.count)
            if (readChars > 0) {
                let readString: String = NSString(data: NSData(bytes:buffer, length:readChars) as Data, encoding: String.Encoding.utf8.rawValue)! as String
                print(readString) //  サーバから受信したデータ
                usleep(300 * 1000) //300ms待つ(適当
                }
             
            } else {
                print ("server closed connection")
                inputStream.close()
                outputStream.close()
                break
            }
        }
    } else {
        print ("could not create socket")
    }
} else {
    print ("could not initialize stream")
}

通信中の切断に対する適切な対応とかタイムアウトとか全然考えてないので、そこら辺はご容赦。

受信したデータをprint()するだけの用途はあまりないと思うのでちょいと応用です。
趣味で作っている透過率測定器の結果をiPadに出力しました。この測定器はクライアントから「data?\r\n」という文字列を受信すると、ダイオードアレイのADCデータを「datais?t+1234567」という形式でクライアントへ送信します(欲しいデータは+1234567)。文字列から数値だけを抽出したいので、分割します。
先ほどの

print(readString) //  サーバから受信したデータ

を下記のコードに置き換えます。

//print(readString) //  サーバから受信したデータ

                if (readString.contains("datais?t")) {
                    let strSep = readString.components(separatedBy: "?t")
                    let rval = (strSep[1] as NSString).integerValue 
                    
                    usleep(300 * 1000) //300ms
                }
                

こちらのコードも変更

//let queryString = "クライアントから送信したいデータ"
let queryString = "data?\r\n"

Swift Playgroundsの便利機能で、変数の値変化をグラフの形でスコープ出来る(データ数上限あり)ので、簡単な確認なら気合い入れてグラフ描画コード書かなくても良い。

自分はWiznet W5500とWiFiルータ経由で通信をテストしました。
最近はやりのESP32とかXbee WifiとかWiFiが使えるデバイスで試した方がいれば、動作結果など是非お聞かせ願えれば。

参考:
Real-Time Communication with Streams Tutorial for iOS

iPad Pro 10.5用カバーでApple Pencil一緒に携帯できて頑丈なやつ。

Raspberry Pi 3でUrJTAGを使う

とりあえずRasPi3でUrJTAGが動くようにする

MAX10を出先や遠隔でコンフィグしたりするときRaspiでUrJTAGが使えたらなあと調べたら使えたのでメモ
RasPiだとGPIOでもバウンダリスキャンできるらしいので便利かもしれない。

apt-get install urjtagでインストールしたらUSB Blasterがうまく動かなかったので、仕方なくビルド。
参考はこちらUniversal JTAG library, server and tools / Discussion / Using UrJTAG:raspberry pi gpio port

準備

上記を参考に必要なパッケージをインストールする

$ sudo apt-get install autoconf autopoint libtool libreadline-dev python-dev libusb-dev libusb-1.0-0-dev flex libftdi-dev bison

flex,bisonはBSDLをUrJTAGに食わせるのに共に必要なので追加しました。

D2XXのインストール。FTDIのサイトのREADMEを参考に行う。RasPi3は今のところOSが32bitなのでARMv7版を使うようだ。

$ wget http://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-arm-v7-hf-1.4.6.tgz
$ tar xvf libftd2xx-arm-v7-hf-1.4.6.tgz
$ cd release/build
$ sudo cp libftd2xx.* /usr/local/lib
$ sudo chmod 0755 /usr/local/lib/libftd2xx.so.1.4.6
$ sudo ln -sf /usr/local/lib/libftd2xx.so.1.4.6 /usr/local/lib/libftd2xx.so

ftd2xx.h,WinTypes.hがないとUrJTAGビルドでエラーになるのでコピーしておく

$ cd ../examples
$ sudo cp ftd2xx.h /usr/include/
$ sudo cp WinTypes.h /usr/include/


UrJTAGのビルド。下記サイトの内容そのまま
Universal JTAG library, server and tools / Discussion / Using UrJTAG:raspberry pi gpio port

$ cd ~/
$ git clone git://git.code.sf.net/p/urjtag/git urjtag-git
$ cd urjtag-git

ビルドの前にcmd_bfin.cの内容を修正

$ cd urjtag/src/cmd
$ nano cmd_bfin.c 

cmd_bfin.cソースコード先頭に下記を追加する

#define _SYS_UCONTEXT_H

ctrl-o,ctrl-xで保存後エディタ終了

UrJTAGのビルド

$ cd ../../
$ ./autogen.sh

Warningはちゃんと見ておくべし。
使いたい項目がyesになっているか確認する。
今回はUSB Blasterを使いたいのでD2XX関連とBSDL,SVFはyesになっているか確認。

urjtag is now configured for

  Libraries:
    libusb     : 1.0
    libftdi    : yes (no async mode)
    libftd2xx  : yes
    inpout32   : no

  Subsystems:
    SVF        : yes
    BSDL       : yes
    STAPL      : no

問題なかったらmakeする

$ make
$ sudo make install

動かん……

$ sudo jtag
jtag: error while loading shared libraries: liburjtag.so.0: cannot open shared object file: No such file or directory

共有ライブラリとして認識されてないんじゃないかな…

$ sudo ldconfig
$ sudo jtag

UrJTAG 0.10 #
Copyright (C) 2002, 2003 ETC s.r.o.
Copyright (C) 2007, 2008, 2009 Kolja Waschk and the respective authors

UrJTAG is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
There is absolutely no warranty for UrJTAG.

warning: UrJTAG may damage your hardware!
Type "quit" to exit, "help" for help.

jtag>

動いた…


RStudioで標準入出力するとなんか変

RStudioがハングする

R-3.4.0をWindows10で使っているのですが、ユーザからのコンソールを受け付けるのに以下のように書いていました

#! /usr/bin/env Rscript

#
lines = readLines("stdin")
# 以下 linesに代入された文字列の処理

ところが、RStudioそのものが動作停止し、タスクマネージャから強制的に終了しないといけなくなる顛末。
確かこれで動いてたはず…
とおもったらRStudioのリダイレクトの所為だったようです。"stdin"ではなく stdin()に書き換え。
参考:linux - Use stdin from within R studio - Stack Overflow

#! /usr/bin/env Rscript

#
while (TRUE) {
    line = readLines(stdin(), n=1); 
    if(line == "q")break
    cat(line, "\n")
}
# break後の処理
#

無事動きました

W5500でTCP/IP:send() , recv()をそれぞれ独立に行う

W5500でTCPソケット送受信をそれぞれ独立にしたい

ArduinoでもすっかりおなじみになったWiznetのSPI-PHYシリーズですが、思い出しては忘れを繰り返すので、自分への開発時短効果を期待してメモがわり。

W5100/5200/5500のioLibraryドライバ
GitHub - Wiznet/ioLibrary_Driver: Create a repository of WIZnet ioLibrary.

これを自分の使ってるマイコンに移植して、こんな感じで動かすと思います。

#include <____my_mcu_headers____.h>
#include "socket.h"
#include "loopback.h"	


/////////////////////////////////////////
// SOCKET NUMBER DEFINION for Examples //
/////////////////////////////////////////
#define SOCK_TCPS        0
// ....

///////////////////////////////////
// Default Network Configuration //
///////////////////////////////////
volatile wiz_NetInfo gWIZNETINFO = { .mac = {0x00, 0xAB, 0xCD, 0xEF, 0x01, 0x23},
                            .ip = {192,168,1,111},
                            .sn = {255,255,255,0},
                            .gw = {192, 168, 1, 1},
                            .dns = {0,0,0,0},
                            .dhcp = NETINFO_STATIC};

							
// 	(中略)
int main(void){

	int32_t ret = 0;
    uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};


// 初期化処理もろもろ

    
	for(;;) {
	
        if( (ret = loopback_tcps(SOCK_TCPS, gDATABUF, 5000)) < 0) { // Port=5000 (= 192.168.1.111:5000)
			printf("SOCKET ERROR : %ld\r\n", ret);
		}

// ......
	}
}

こんな感じでloopback_tcpsをポーリングで動かしてるとおもいます。
loopbackサンプルプログラムを動かして喜んでそのまま積み基板化することが多いのですが、
loopbackのサンプルプログラムを元にsend(), recv()を独立に動かすの、前知識なしではしんどい。

前知識:TCPの状態遷移図
基礎から学ぶWindowsネットワーク:第16回 信頼性のある通信を実現するTCPプロトコル(3) (3/4) - @IT

でもほかにリソース割かなくてはならないし読んでられないよ...何のためにWiznet(ryなので、loopback.cのloopback_tcps()を下記に示すコードに置き換えるだけでrecv(),send()のみできるプログラムを急ごしらえで作成しました。
send/recvの同時発生でバッファの内容を破壊しないような配慮はしてないので各自で書き換えてほしい。

bufに文字列がある場合、文字列をsend()します。bufが空の場合(buf[0]='\0')受信データがあるか確認し、あったらrecv()します。ループバックはしません。なので関数名がおかしくなります。はい。ちゃんと直せよ(殴

#if LOOPBACK_MODE == LOOPBACK_MAIN_NOBLCOK
int32_t loopback_tcps(uint8_t sn, uint8_t* buf, uint16_t port)
{
   int32_t ret;
   uint16_t size = 0, sentsize=0;
#ifdef _LOOPBACK_DEBUG_
   uint8_t destip[4];
   uint16_t destport;
#endif

   switch(getSn_SR(sn))
   {
      case SOCK_ESTABLISHED :
         if(getSn_IR(sn) & Sn_IR_CON)
         {
#ifdef _LOOPBACK_DEBUG_
			getSn_DIPR(sn, destip);
			destport = getSn_DPORT(sn);

			printf("%d:Connected - IP:%d.%d.%d.%d : %d\r\n",sn, destip[0], destip[1], destip[2], destip[3], destport);
#endif
			setSn_IR(sn,Sn_IR_CON);
         }
		 if((size = getSn_RX_RSR(sn)) > 0) // Don't need to check SOCKERR_BUSY because it doesn't not occur.
         {
			if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
			ret = recv(sn, buf, size);
	        if(ret <= 0){
                return ret;      // check SOCKERR_BUSY & SOCKERR_XXX. For showing the occurrence of SOCKERR_BUSY.
            }
//            sentsize = 0;
//            while(size != sentsize)
//			{
//				ret = send(sn, buf+sentsize, size-sentsize);
//				if(ret < 0)
//				{
//					close(sn);
//					return ret;
//				}
//				sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
//			}
         }else if((size = getSn_TX_FSR(sn)) > 0){
         
            size = strlen((const char*)buf);
            sentsize = 0;
			while(size != sentsize)
			{
				ret = send(sn, buf+sentsize, size-sentsize);
				if(ret < 0)
				{
					close(sn);
					return ret;
				}
				sentsize += ret; // Don't care SOCKERR_BUSY, because it is zero.
			}
        }
         break;
      case SOCK_CLOSE_WAIT :
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:CloseWait\r\n",sn);
#endif
         if((ret = disconnect(sn)) != SOCK_OK) return ret;
#ifdef _LOOPBACK_DEBUG_
         printf("%d:Socket Closed\r\n", sn);
#endif
         break;
      case SOCK_INIT :
#ifdef _LOOPBACK_DEBUG_
    	 printf("%d:Listen, TCP server loopback, port [%d]\r\n", sn, port);
#endif
         if( (ret = listen(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_CLOSED:
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:TCP server loopback start\r\n",sn);
#endif
         if((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) return ret;
#ifdef _LOOPBACK_DEBUG_
         //printf("%d:Socket opened\r\n",sn);
#endif
         break;
      default:
         break;
   }

   return 1;
}


main関数などからループで呼び出してやればOK。
send()したい文字列があればbufにそのポインタを指定し、なければ空のバッファのポインタを指定しておく
呼び出し後にbufの中身を消しておくのを忘れずに。


PQI Air Pen向けにmips-linux-gnu-gccをソースビルドした話

事の発端

年末にNTT-XストアでPQI Air Penが破格の値段でした。
これ、有線/無線でFTPサーバーになるWi-Fiアダプタなのですが、なにより、弄り倒し甲斐のあるLinuxが入ったMIPSマイコンボードな訳です。
nttxstore.jp

そして心強いmoyashi兄さんのブログ
hitoriblog.com

もう買うしかない。正月休みに遊ぶネタが出来たわけです。
飽きてもこれだったら部屋のWiFiルータにはなります。
f:id:Lynx-EyED:20170113134645p:plain

で、クロスコンパイラはSourcery CodeBench Lite Edition for MIPS GNU/Linuxがあるので別に必要ないのですが、

Ubuntu xenial(16.04LTS)はgcc-5-mips-linux-gnuが提供されているのでこれでビルドし、PQI Air Penで動作させようとすると、

~ #./a.out

FATAL: kernel too old
Bus error

~ #

致命的エラー:カーネルおじさんめっちゃ老けてるぞ

よーし、一人だけgcc野良ビルドする苦行2017選手権やるか
正月休みにおすすめしたいとても有益な時間の浪費方法です。絶対やめた方がいい。おすすめです。
Rなんとか社のRXマイコンの時は会社でも家でも散々野良rx-gccビルド大会してましたね。このブログのアクセス解析が某軽子坂からのアクセスだらけになってたのはいい思い出です。あんな共用体地獄ライブラリ付きうんこマイコン金輪際ビルドしてやらねぇ。


息をするように野良ビルドするのも考え物ですがカーネルバージョンに合わせて自分でビルドできるので、時には必要な事もあるのかも。
まず、PQI Air Pen(ファームウェアはv0.1.24に変更済み)のカーネルを確認します

~ # cat /proc/version 
Linux version 2.6.31.AirPen_V0.1.22-g5eca71a (dio@dio) (gcc version 4.3.3 (GCC) ) #324 Thu Feb 7 17:36:23 CST 2013
~ # 

なので、Kernel2.6.31か、それより以前のカーネルヘッダでクロスコンパイラを作った方が良さそうです

mips-linux-gnuのビルド

ホスト
ビルドおよび動作確認したのは以下の環境です

ubuntuはメインで使っている14.04で動くことを目指しました。(結局16.04でもビルドできた。詳細後述)
16.04LTSいろいろ難しい…

どちらも前もってインストールしておくパッケージ

$ sudo apt-get update
$ sudo apt-get install build-essential m4 gawk g++ make


GCCビルド成功した組み合わせ

GMP,MPC,MPFRを別個にwgetしてもいいのですがGCCフォルダ内の

contrib/download_prerequisites

を起動して自動取得しています。

Ubuntu 16.04LTSがbinutilsのビルドからこけやがる件

本当は根本的な解決をするべきなのでしょうが、よくわからないところで警告出してエラー終了しているので-Wno-errorで回避していまいます。

CFLAGS=-Wno-error
binutils-${BINUTILSVERSION}/configure (中略) --disable-warnings-as-errors

スクリプトかいた

出来てしまうと大したことはないのですが、すごくめんどくさいのと大したことないのは別問題なので(?)スクリプト書きました。めっちゃ適当に書いてるので、詳しい人書き直して。
ビルド前にmips-linux-gnuをすでにインストールしている場合はアンインストールするか、$PATHをunsetしておきましょう

#!/bin/bash -ev

# see https://www.linux-mips.org/wiki/Toolchains#Roll-your-own
# see http://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/
# see also http://www.hs-augsburg.de/~beckmanf/dokuwiki/doku.php?id=mips_cross_compiler

export WDIR=$HOME/tmp
export TARGET=mips-linux-gnu
export PREFIX=$HOME/mips-cross
export TARCH=mips
export PATH="${PATH}":${PREFIX}/bin

# ------------------------------
# enviroments
# ------------------------------
echo ${WDIR}
echo ${TARGET}
echo ${PREFIX}
echo ${PATH}
# ------------------------------
mkdir -p ${PREFIX}
mkdir -p $WDIR
cd $WDIR
mkdir -p ${TARGET}-toolchain 
cd ${TARGET}-toolchain


# version
BINUTILSVERSION="2.24"
GCCVERSION="4.9.2"
#GDBVERSION="7.7"
ISLVERSION="0.12.2"
CLOOGVERSION="0.18.1"
#GLIBCVERSION="2.20"
#PORTSVERSION="2.20"
GLIBCVERSION="2.19"
PORTSVERSION="2.19"
#KERNELVERSION="3.17.2"
#KERNELVERSION="2.6.27.62"
#KERNELVERSION="2.6.32.60"
KERNELVERSION="2.6.30"


# wget
if [ ! -e "binutils-${BINUTILSVERSION}.tar.bz2" ] ; then
wget http://ftp.gnu.org/gnu/binutils/binutils-${BINUTILSVERSION}.tar.bz2
fi

if [ ! -e "gcc-${GCCVERSION}.tar.bz2" ] ; then
wget http://ftp.gnu.org/gnu/gcc/gcc-${GCCVERSION}/gcc-${GCCVERSION}.tar.bz2
fi

<< COMMENTOUT
if [ ! -e "gdb-${GDBVERSION}.tar.bz2" ] ; then
wget http://ftp.gnu.org/gnu/gdb/gdb-${GDBVERSION}.tar.bz2
fi
COMMENTOUT

if [ ! -e "linux-${KERNELVERSION}.tar.xz" ] ; then
# wget https://www.kernel.org/pub/linux/kernel/v2.6/longterm/v2.6.27/linux-${KERNELVERSION}.tar.xz
# wget https://www.kernel.org/pub/linux/kernel/v2.6/longterm/v2.6.32/linux-${KERNELVERSION}.tar.xz
# wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-${KERNELVERSION}.tar.xz
wget https://www.kernel.org/pub/linux/kernel/v2.6/linux-${KERNELVERSION}.tar.xz
fi

# ----------------------
# wget kernel-headers
# ----------------------
if [ ! -d "linux-${KERNELVERSION}" ] ; then
tar -Jxvf linux-${KERNELVERSION}.tar.xz
fi

# ----------------------
# binutils
# ----------------------
if [ ! -d "binutils-${BINUTILSVERSION}" ] ; then
tar xjf binutils-${BINUTILSVERSION}.tar.bz2
fi


# ----------------------
# gcc
# ----------------------
if [ ! -d "gcc-${GCCVERSION}" ] ; then
tar xjf gcc-${GCCVERSION}.tar.bz2
fi

#-----------------------
# get infrastructure
# gmp, mpfr, gmp
#-----------------------
if [ ! -d "gmp" ] ; then
gcc-${GCCVERSION}/contrib/download_prerequisites
fi

# get isl
if [ ! -e "isl-${ISLVERSION}.tar.bz2" ] ; then
  wget  ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-${ISLVERSION}.tar.bz2
fi

if [ ! -d "isl-${ISLVERSION}" ] ; then
tar -xvjf isl-${ISLVERSION}.tar.bz2
fi

# get cloog
if [ ! -e "cloog-${CLOOGVERSION}.tar.gz" ] ; then
wget ftp://gcc.gnu.org/pub/gcc/infrastructure/cloog-${CLOOGVERSION}.tar.gz
fi

if [ ! -d "cloog-${CLOOGVERSION}" ] ; then
tar -xvf cloog-${CLOOGVERSION}.tar.gz
fi

# get glibc
if [ ! -e "glibc-${GLIBCVERSION}.tar.xz" ] ; then
wget ftp://ftp.gnu.org/gnu/glibc/glibc-${GLIBCVERSION}.tar.xz
fi


if [ ! -d "glibc-${GLIBCVERSION}" ] ; then
tar -Jxvf glibc-${GLIBCVERSION}.tar.xz
fi
<< COMMENTOUT
# get glibc-ports
if [ ! -e "glibc-ports-${PORTSVERSION}.tar.bz2" ] ; then
wget http://ftp.gnu.org/gnu/libc/glibc-ports-${PORTSVERSION}.tar.bz2
fi

if [ ! -d "glibc-ports-${PORTSVERSION}" ] ; then
tar -xvjf glibc-ports-${PORTSVERSION}.tar.bz2
fi
COMMENTOUT

#ln -sf glibc-ports-${PORTSVERSION} glibc-${GLIBCVERSION}/ports  
# cd glibc-${GLIBCVERSION}
# ln -sf ../glibc-ports-${GLIBCVERSION} ports
# cd ..
# --------------------------------------------------------
# Build step 0: make symbolic link
# 				mpfr, gmp, mpc, mpfr, isl, cloog 
# --------------------------------------------------------
cd gcc-${GCCVERSION}
ln -sf ../mpfr mpfr
ln -sf ../gmp gmp
ln -sf ../mpc mpc
ln -sf ../isl-${ISLVERSION} isl
ln -sf ../cloog-${CLOOGVERSION} cloog
cd ..

# --------------------------------------------------------
# Build step 1: make Binutils
# 		configure & make & install 
# --------------------------------------------------------
mkdir -p build-binutils
cd build-binutils

if [ ! -e "config.status" ] ; then
CFLAGS=-Wno-error ../binutils-${BINUTILSVERSION}/configure --target=${TARGET} --prefix=${PREFIX} --disable-warnings-as-errors
fi

if [ ! -e "${PREFIX}/bin/${TARGET}-ld" ] ; then
make
make install
fi

cd ..

# --------------------------------------------------------
# Build step 2: make Linux Kernel Headers
# 		make headers_install 
# --------------------------------------------------------
cd linux-${KERNELVERSION}
make ARCH=${TARCH} INSTALL_HDR_PATH=${PREFIX}/${TARGET} headers_install
cd ..

# --------------------------------------------------------
# Build step 3: Build GCC but libraries
# 		make all-gcc && make install-gcc
# --------------------------------------------------------
mkdir -p build-gcc
cd build-gcc
../gcc-${GCCVERSION}/configure --prefix=${PREFIX} --target=${TARGET} --enable-languages=c,c++ --disable-multilib
make all-gcc
make install-gcc
cd ..
# --------------------------------------------------------
# Build step 4: Standard C library Headers and Startup Files
# 		make (removed --enable-add-ons=ports,  --disable-sanity-checks--with-tls --disable-versioning --disable-profile)
# --------------------------------------------------------
mkdir -p build-glibc
cd build-glibc
../glibc-${GLIBCVERSION}/configure --prefix=${PREFIX}/${TARGET} --build=${MACHTYPE} --host=${TARGET} --target=${TARGET} --with-headers=${PREFIX}/${TARGET}/include --disable-multilib libc_cv_forced_unwind=yes
make install-bootstrap-headers=yes install-headers
make csu/subdir_lib
install csu/crt1.o csu/crti.o csu/crtn.o ${PREFIX}/${TARGET}/lib
${TARGET}-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o ${PREFIX}/${TARGET}/lib/libc.so
touch ${PREFIX}/${TARGET}/include/gnu/stubs.h
cd ..
# --------------------------------------------------------
# Build step 5: Compiler Support library 
# 		make 
# --------------------------------------------------------
cd build-gcc
make all-target-libgcc
make install-target-libgcc
cd ..

# --------------------------------------------------------
# Build step 6: Standard C library 
# 		make 
# --------------------------------------------------------
cd build-glibc
make 
make install
cd ..
# --------------------------------------------------------
# Build step 7: Standard C++ library 
# 		make 
# --------------------------------------------------------
cd build-gcc
make 
make install
cd ..

ビルドが無事終わったら忘れないうちに.bashrcにPATHを通しておいた方がいいと思います。PCはCore-i5 5200Uが乗っているWindows7ノートでやったのですが、
ビルド時間はubuntuのパッケージバージョンには関係なく
VMware環境 2時間半
デュアル環境 40分
でした。仮想環境結構遅いんだね。

#include <stdio.h>
int main(int argc, char *argv[]){

	printf("hello world.\nThis is PQI Air Pen.\n");
	return 0;

}

という何の変哲もないコードをhello.cなどと保存し、

$ mips-linux-gnu-gcc -g -O2 -Wall -pedantic -march=24kc -static -o hello hello.c

とすれば、3MB以上もある巨大バイナリが完成する。(staticビルドだからしかたない)
これをmicroSDにコピーするか、FTP経由でPQI Air Penにアップロードする。
telnetPQI Air Penにログイン

hoge@ubuntu:~$ telnet 192.168.200.1
Trying 192.168.200.1...
Connected to 192.168.200.1.
Escape character is '^]'.
(none) login: root
Password: (パスワード:pqiap)


BusyBox v1.01 (2013.02.07-09:36+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # cd /tmp/www/ftp/sda1/
/tmp/www/ftp/sda1 # ./hello
 hello world.
 This is PQI Air Pen.

/tmp/www/ftp # 

ncursesをtelnet使ってパイプする方法がわかればslコマンドでも、と思ったのですが、いかんせんよくわからず…
また時間のあるときに、Luaでもビルドしてみよう。

おしまい。


参考サイト

https://www.linux-mips.org/wiki/Toolchains#Roll-your-own MIPSロスコンパイラをビルドするのに最低限必要な内容
http://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/ Linux上で動作するクロスコンパイラをビルドのするのに必要な知識
http://www.hs-augsburg.de/~beckmanf/dokuwiki/doku.php?id=mips_cross_compiler bare-metalなMIPSコンパイラを作る時に必要な知識