Raspberry PI Kernel Building (2)

You can cross-compile the source, say, on a Linux platform, but dealing with an SD card with a virtual machine is slightly tedious.

I preferred to continue local building by throwing away unnecessary packages like X11 related ones.

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

With the option -j4, the CPU temperature gets rather high, so I used a small fan to cool the device.

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

And finally, you will get:

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 Kernel Building

First, you save the current SD image.

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

Then, you build the sources, but..

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% /

UI for WebSDR

Changing the user interface for my WebSDR step by step.

Select the BFO, either A or B, and click on the waterfall to tune into your desired signal. The sliders are for changing the CW pitches.

Raspberry Pi and WebSDR (4)

The program index.js works as an HTTP server, and first send a file index.html to your Web browser when a connection is requested.

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);

The file index.html contains a JavaScript code for displaying the waterfall image, and for receiving and playing the audio signals.

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 and WebSDR (3)

Node.js is a server-side run-time environment for executing JavaScript code.

We have two programs running on a Raspberry PI, and one of the two is index.js, a JavasSript program that communicates with your IC-7410 and your Web browser.

// 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 and WebSDR (2)

Dual watch is now implemented in a more simple and symmetric way.

You can control the two BFO frequencies independently. The CW pitches are set to 400Hz and 700Hz for the left and right audio channels, respectively, so that you can distinguish the signals even with a monaural sound system.