Raspberry PIとカーネルビルド(2)

あなたはソースをたとえばLinuxプラットフォームの上でクロスコンパイルすることもできますが、仮想マシンを使った場合は、SDカードの取り扱いが少しだけ面倒です。

私は、X11関連とかの不要なパッケージを捨てることにより、ローカルビルドを続けることにしました。

pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       5.8G  2.1G  3.4G  38% /

pi@raspberrypi:~/Zkernel/linux $ time make -j4 zImage modules dtbs
real	65m26.604s
user	235m23.420s
sys	11m3.260s

オプション-j4を用いるとCPU温度が結構高くなるので、デバイスを冷やすために小さなファンを用いました。

pi@raspberrypi:~ $ vcgencmd measure_temp
temp=60.5'C

そして、最後にこのようになります。

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.9.30-v7+ #1 SMP Mon May 29 07:24:28 UTC 2017 armv7l GNU/Linux

pi@raspberrypi:~ $ cat /proc/version
Linux version 4.9.30-v7+ (pi@raspberrypi) (gcc version 4.9.2 (Raspbian 4.9.2-10) ) #1 SMP Mon May 29 07:24:28 UTC 2017

pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       5.8G  3.6G  2.0G  66% /

Raspberry PIとカーネルビルド

最初に現在のSDカードのイメージを保存しておきます。

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.9.25-v7+ #994 SMP Fri Apr 28 16:56:00 BST 2017 armv7l GNU/Linux

pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       5.8G  4.3G  1.2G  79% /
devtmpfs        458M     0  458M   0% /dev
tmpfs           462M     0  462M   0% /dev/shm
tmpfs           462M  6.4M  456M   2% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           462M     0  462M   0% /sys/fs/cgroup
/dev/mmcblk0p6   65M   21M   45M  33% /boot
tmpfs            93M     0   93M   0% /run/user/1000
/dev/mmcblk0p5   30M  398K   28M   2% /media/pi/SETTINGS

Mac-mini:Zraspberry $ sudo dd if=/dev/rdisk4 bs=1m of=raspberry20170529.img
7695+0 records in
7695+0 records out
8068792320 bytes transferred in 397.093440 secs (20319631 bytes/sec)

Mac-mini:Zraspberry $ ls -l
total 15759360
-rw-r--r--  1 root  staff  8068792320 May 29 08:03 raspberry20170529.img

そして、ソースをビルドしますが、しかし・・。

pi@raspberrypi:~/Zkernel $ git clone --depth=1 https://github.com/raspberrypi/linux

pi@raspberrypi:~/Zkernel $ cd linux
pi@raspberrypi:~/Zkernel/linux $ KERNEL=kernel7
pi@raspberrypi:~/Zkernel/linux $ make bcm2709_defconfig
pi@raspberrypi:~/Zkernel/linux $ time make -j4 zImage modules dtbs
scripts/kconfig/conf  --silentoldconfig Kconfig

...fatal error: error writing to /tmp/cc4wpGOn.s: No space left on device...

pi@raspberrypi:~/Zkernel/linux $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       5.8G  5.5G   32K 100% /

WebSDRのユーザーインターフェース

WebSDRのユーザーインターフェースを少しづつ改良しています。

BFOのAかBかを選択してからウォーターフォール画面上をクリックすることにより、希望信号に同調することができます。スライダーは、CWピッチを変更するためのものです。

Raspberry PiでWebSDR(4)

index.jsというプログラムはHTTPサーバとして動作し、 接続が要求された時にあなたのWebブラウザにindex.htmlファイルを送ります。

index.js

 16 var http  = require('http');
 17 var fs    = require('fs');
 18 var index = fs.readFileSync(__dirname + '/index.html');
 19 var app   = http.createServer(function(req, res) {
 20                 res.writeHead(200, {'Content-Type' : 'text/html'});
 21                 res.end(index);
 22               })
 23               .listen(3000);

index.htmlファイルは、ウォーターフォール画面を表示したりオーディオ信号を受信して再生するためのJavaScriptのコードを含んでいます。

index.html

<!doctype html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing : border-box; }
body { font: 13px Helvetica, Arial; }
form { background: # 000; padding: 3px; position: fixed; bottom: 0; width: 100% ; }
form input { border: 0; padding: 10px; width: 80% ; margin-right : .5% ; }
form button { width: 15% ; background: rgb(143, 188, 143); border: none; padding: 10px; }
#messages{list-style-type : none; margin : 0; padding : 0; }
#messages li{padding : 5px 10px; }
#messages li : nth-child(odd){background : #eee; }
#messages {margin-bottom : 40px }
#messages {font-size : 2em }
#btn1 {position : fixed; top : 10px; left :  10px; font-size : 2em; background-color: darkseagreen }
#btn2 {position : fixed; top : 10px; left :  60px; font-size : 2em; background-color: darkseagreen }
#btn3 {position : fixed; top : 10px; left : 150px; font-size : 2em; background-color: cornsilk }
#btn4 {position : fixed; top : 10px; left : 230px; font-size : 2em; background-color: cornsilk }
#btn5 {position : fixed; top : 10px; left : 310px; font-size : 2em; background-color: paleturquoise }
#btn6 {position : fixed; top : 10px; left : 390px; font-size : 2em; background-color: paleturquoise }
#btn7 {position : fixed; top : 10px; left : 470px; font-size : 2em; background-color: thistle }
#btn8 {position : fixed; top : 10px; left : 550px; font-size : 2em; background-color: thistle }
#mycanvas0 {position : fixed; top :  70px; left :  10px; font-size : 2em }
#mycanvas1 {position : fixed; top :  90px; left :  10px; font-size : 2em }
#mycanvas2 {position : fixed; top : 620px; left :  10px; font-size : 2em }
</style>
</head>

<body>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>
    <script src='/socket.io/socket.io.js'></script>

    <button id="btn1"><font size="5">CQ</font></button>
    <button id="btn2"><font size="5">QRZ?</font></button>
    <button id="btn3"><font size="5">VFO+</font></button>
    <button id="btn4"><font size="5">VFO-</font></button>
    <button id="btn5"><font size="5">BFO+</font></button>
    <button id="btn6"><font size="5">BFO-</font></button>
    <button id="btn7"><font size="5">BFO+</font></button>
    <button id="btn8"><font size="5">BFO-</font></button>

    <div id="messages"></div>
    <form action="">
      <input id="m" autocomplete="off"/><button>Send</button>
    </form>
    <canvas id="mycanvas0" width="512" height= "20" style="background:white;"></canvas>
    <canvas id="mycanvas1" width="512" height="512" style="background:white;"></canvas>
    <canvas id="mycanvas2" width="280" height= "60" style="background:cornsilk;"></canvas>

<script>

function colormap(charcode) {  // 0x00~0xff
  var tmp = charcode / 255.0;  // 0.0~1.0
  var val;
  var r, g, b;
  if (tmp < 0.50) {
    r = 0.0;
  } else if (tmp > 0.75) {
    r = 1.0;
  } else {
    r = 4.0 * tmp - 2.0;
  }

  if (tmp < 0.25) {
    g = 4.0 * tmp;
  } else if (tmp > 0.75) {
    g = -4.0 * tmp + 4.0;
  } else {
    g = 1.0;
  }

  if (tmp < 0.25) {
    b = 1.0;
  } else if (tmp > 0.50) {
    b = 0.0;
  } else {
    b = -4.0 * tmp + 2.0;
  }

  rgb[1] = 255.0 * g;
  rgb[0] = 255.0 * r;
  rgb[2] = 255.0 * b;
}

function waterFall(myarray) {
  ctx1.putImageData(imgData1, 0, 1);
  imgData1 = ctx1.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    colormap(myarray[j]);
    imgData1.data[0 + j * 4] = rgb[0];
    imgData1.data[1 + j * 4] = rgb[1];
    imgData1.data[2 + j * 4] = rgb[2];
    imgData1.data[3 + j * 4] = 255;
  }
}

function showBpf(ctx0, imgData0, bfo) {
  var cwmarker0 = Math.floor( bfo[0]  / (16000 / 2048) );
  var cwmarker1 = Math.floor( bfo[1]  / (16000 / 2048) );
  console.log('bfo = ', bfo);
  
  for (i = 0; i < 20; i++) {
    for (j = 0; j < 512; j++) {
      var r, g, b, a;
      if(j == cwmarker0 || j == cwmarker1) {
        r = 0; g = 0; b = 0;
      } else if ( j >= cwmarker0-20 && j <= cwmarker0+20 && i > 10) {
        r = 135; g = 206; b= 235;
      } else if ( j >= cwmarker1-20 && j <= cwmarker1+20 && i > 10) {
        r = 216; g = 191; b= 216;
      } else {
        r = 255; g = 255; b= 255;
      }
      imgData0.data[0 + j * 4 + i * imgData0.width * 4] =   r;
      imgData0.data[1 + j * 4 + i * imgData0.width * 4] =   g;
      imgData0.data[2 + j * 4 + i * imgData0.width * 4] =   b;
      imgData0.data[3 + j * 4 + i * imgData0.width * 4] = 255;
    }
  }
  ctx0.putImageData(imgData0, 0, 0);
}

function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  var mx   = e.clientX - rect.left;
  var my   = e.clientY - rect.top;
  console.log('mouse clicked: ', mx, my);
  socket.emit('message77', mx);
}

function playAudioStream(ctx, audio_f32) {
    var audio_buf    = ctx.createBuffer(1, audio_f32.length, 16000);
    var audio_src    = ctx.createBufferSource();
    var current_time = ctx.currentTime;

    audio_buf.getChannelData(0).set(audio_f32);

    audio_src.buffer = audio_buf;
    audio_src.connect(ctx.destination);

    if (current_time < scheduled_time) {
        audio_src.start(scheduled_time);
        scheduled_time += audio_buf.duration;
    } else {
        audio_src.start(current_time);
        scheduled_time = current_time + audio_buf.duration;
    }
}

// main

var socket  = io();

var myarray = new Array();
var canvas0, canvas1, canvas2;
var ctx0, ctx1;
var imgData0, imgData1;
var rgb = new Array(3);

canvas0 = document.getElementById('mycanvas0');
canvas1 = document.getElementById('mycanvas1');
ctx0    = canvas0.getContext('2d');
ctx1    = canvas1.getContext('2d');

canvas1.addEventListener('click', onClick, false);

imgData0 = ctx0.createImageData(512,  20);
imgData1 = ctx1.createImageData(512, 512);

var stage = new createjs.Stage("mycanvas2");
var t     = new createjs.Text("IC-7410", "26px serif", "DarkRed");
t.x       = 10;
t.y       = 10;
stage.addChild(t);
stage.update();

var bfo      = [1000.0, 2000.0];
showBpf(ctx0, imgData0, bfo);

for (i = 0; i < 512; i++) {
  for (j = 0; j < 512; j++) {
    imgData1.data[0 + j * 4 + i * imgData1.width * 4] = j % 256;
    imgData1.data[1 + j * 4 + i * imgData1.width * 4] = i % 256;
    imgData1.data[2 + j * 4 + i * imgData1.width * 4] = 128;
    imgData1.data[3 + j * 4 + i * imgData1.width * 4] = 255;
  }
}
ctx1.putImageData(imgData1, 0, 0);

var audioCtx = new AudioContext;
var scheduled_time = 0;

socket.on('your message', function(msg) {
  $('#messages').append($('<div>').text(msg));
  window.scrollTo(0, document.body.scrollHeight);
});

socket.on('waterfall', function(data) {
  waterFall(data);
});

socket.on('sound'    , function(data) {
  playAudioStream(audioCtx, new Float32Array(data));
});

socket.on('freqmsg', function(msg) {
  t.text = msg;
  stage.update();
});

$('#btn1').click(function() { socket.emit('message1'); console.log('message1'); });
$('#btn2').click(function() { socket.emit('message2'); console.log('message2'); });
$('#btn3').click(function() { socket.emit('message3'); console.log('message3'); });
$('#btn4').click(function() { socket.emit('message4'); console.log('message4'); });
$('#btn5').click(function() { socket.emit('message5'); bfo[0]+=100; showBpf(ctx0, imgData0, bfo);});
$('#btn6').click(function() { socket.emit('message6'); bfo[0]-=100; showBpf(ctx0, imgData0, bfo);});
$('#btn7').click(function() { socket.emit('message7'); bfo[1]+=100; showBpf(ctx0, imgData0, bfo);});
$('#btn8').click(function() { socket.emit('message8'); bfo[1]-=100; showBpf(ctx0, imgData0, bfo);});

$('form').submit(function() {
  socket.emit('your message', $('#m').val());
  $('#m').val('');
  return false;
});
</script>
</body>
</html>

Raspberry PiでWebSDR(3)

Node.jsはJavaScriptプログラムのサーバーサイドでの実行環境です。

私たちはRaspberry PIの上で2つのプログラムを走らせますが、そのうちの1つがindex.jsです。これは、あなたのIC-7410とあなたのウェブブラウザの両方と通信を行います。

// file name = index.js
// (c) 2017 JH1OOD/Mike

const bufa = new Buffer('fefe80e017'            , 'hex'); // preamble
const bufz = new Buffer('fd'                    , 'hex'); // postamble
const buf1 = new Buffer('fefe80e0174351fd'      , 'hex'); // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd'  , 'hex'); // QRZ?
const buf3 = new Buffer('fefe80e003fd'          , 'hex'); // read freq
var   buf4 = new Buffer('fefe80e0050000000000fd', 'hex'); // send freq
 
var SerialPort = require('serialport');
var serial     = new SerialPort(
    '/dev/ttyUSB0',
    {baudrate : 19200, parser : SerialPort.parsers.byteDelimiter([ 0xfd ])});
 
var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');
var app   = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);
var io    = require('socket.io').listen(app);

var freqHz  = 0;          // VFO-A frequency
var ndata   = 512 + 2048; // waterfall_data[512] + sound_data[2048]
var pos     = 0;
var myarray = new Array();
var mywater = new Array();
var mysound = new Array();
 
// -- water fall and Sound --
 
process.stdin.on('readable', function() { // from spinor_audio
  var buf = process.stdin.read();
  if (buf !== null) {
      for(var i=0;i<buf.length;i++) {
        myarray[pos++] = buf[i];
        if (pos == ndata) {
          for(var j=0;j<512;j++) {     // waterfall_data
            mywater[j] = myarray[j];
          }
          for(var j=0;j<2048;j++) {    // sound_data
            var tmp = myarray[j+512];
            if (tmp>128) tmp -=256;
            mysound   [j] = tmp / 128.0; // signed char to -1.0~+1.0
          }
          io.emit('waterfall', mywater);
          io.emit('sound'    , mysound);
          pos = 0;
        }
      }
  }
});
 
// -- serial for IC-7410 --
 
serial.on('open',
          function() { console.error('serial port /dev/ttyUSB0 is opened.'); });
 
serial.on('data', function(data) {
  if (!(data[0] == 0xfe & data[1] == 0xfe)) {
    console.error('-- received serial data format error');
    }
  if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
    var f10   = data[5] >> 4 & 0x0f;
    var f1    = data[5] & 0x0f;
    var f1k   = data[6] >> 4 & 0x0f;
    var f100  = data[6] & 0x0f;
    var f100k = data[7] >> 4 & 0x0f;
    var f10k  = data[7] & 0x0f;
    var f10m  = data[8] >> 4 & 0x0f;
    var f1m   = data[8] & 0x0f;
    var freq  = f10m.toString() + f1m.toString() + "," + f100k.toString() +
                f10k.toString() + f1k.toString() + "." + f100.toString()  +
                f10.toString()  + f1 + " kHz";
    freqHz = f10m * 10000000 + f1m * 1000000 + f100k * 100000 + f10k * 10000 +
             f1k * 1000 + f100 * 100 + f10 * 10 + f1;
    io.emit('freqmsg', 'VFO A: ' + freq);
  }
});
 
serial.on('error', function(err) { console.error('Error: ', err.message); })
 
// -- set frequency --
 
    function setfreq(f) {
      var f10m  = Math.floor(f / 10000000); f -= f10m  * 10000000;
      var f1m   = Math.floor(f / 1000000);  f -= f1m   * 1000000;
      var f100k = Math.floor(f / 100000);   f -= f100k * 100000;
      var f10k  = Math.floor(f / 10000);    f -= f10k  * 10000;
      var f1k   = Math.floor(f / 1000);     f -= f1k   * 1000;
      var f100  = Math.floor(f / 100);      f -= f100  * 100;
      var f10   = Math.floor(f / 10);       f -= f10   * 10;
      var f1    = Math.floor(f / 1);
 
      var data = new Array(
          [ 0xfe, 0xfe, 0x80, 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd ]);
      buf4[5] = f10   << 4 | f1;
      buf4[6] = f1k   << 4 | f100;
      buf4[7] = f100k << 4 | f10k;
      buf4[8] = f10m  << 4 | f1m;
      buf4[9] = 0;
      serial.write(buf4);
    }
 
// -- WebSocket --
 
io.on('connection', function(socket) {
 
  socket.on('message1', function() { serial.write(buf1); });
  socket.on('message2', function() { serial.write(buf2); });
 
  socket.on('message3', function() {
    var newfreq = freqHz + 2000;
    setfreq(newfreq);
  });
 
  socket.on('message4', function() {
    var newfreq = freqHz - 2000;
    setfreq(newfreq);
  });
 
  socket.on('message5', function() {
    console.log('0'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message6', function() {
    console.log('1'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message7', function() {
    console.log('2'); // should be stdout to reach sprig_audio
  });
 
  socket.on('message8', function() {
    console.log('3'); // should be stdout to reach sprig_audio
  });
 
  var cwpitch = 650.0;
  socket.on('message77', function(mx) {
    var newfreq = freqHz - (16000/2048 * mx - cwpitch);
    setfreq(newfreq);
  });

  socket.on('your message', function(msg) {
    serial.write(bufa);
    serial.write(msg );
    serial.write(bufz);
    io.emit('your message', msg);
  });
 
});
 
// -- request freq --
 
function sendTime() {
  serial.write(buf3);
  // console.error('Freq asked..');
}

setInterval(sendTime, 100);

// -- EOF --

Raspberry PiでWebSDR(2)

デュアルウォッチ機能を、より単純で対称的な方法で実装してみました。

あなたは、2つのBFO周波数を独立して制御することができます。CWピッチは、左右のチャンネルに対してそれぞれ400Hzと700Hzとに設定されているので、モノラルなオーディオシステムでも信号を区別することができます。