Information wants to be free...

SSD1306 Wi-Fi Status

Here is another use of the SSD1306 miniature OLED display. Instead of loading an image like my previous project, this code contains character data and can display text. I have combined this with some simple hacks to retrieve and display Wi-Fi status information from the host. This is also meant to be connected to a Raspberry Pi 3.

Take a look:

#!/usr/bin/python
import smbus
import subprocess
import re
import time

chardata = dict()
chardata['@'] = "\x3c\x66\x6e\x6e\x60\x62\x3c\x00"
chardata['A'] = "\x18\x3c\x66\x7e\x66\x66\x66\x00"
chardata['B'] = "\x7c\x66\x66\x7c\x66\x66\x7c\x00"
chardata['C'] = "\x3c\x66\x60\x60\x60\x66\x3c\x00"
chardata['D'] = "\x78\x6c\x66\x66\x66\x6c\x78\x00"
chardata['E'] = "\x7e\x60\x60\x78\x60\x60\x7e\x00"
chardata['F'] = "\x7e\x60\x60\x78\x60\x60\x60\x00"
chardata['G'] = "\x3c\x66\x60\x6e\x66\x66\x3c\x00"
chardata['H'] = "\x66\x66\x66\x7e\x66\x66\x66\x00"
chardata['I'] = "\x3c\x18\x18\x18\x18\x18\x3c\x00"
chardata['J'] = "\x1e\x0c\x0c\x0c\x0c\x6c\x38\x00"
chardata['K'] = "\x66\x6c\x78\x70\x78\x6c\x66\x00"
chardata['L'] = "\x60\x60\x60\x60\x60\x60\x7e\x00"
chardata['M'] = "\x63\x77\x7f\x6b\x63\x63\x63\x00"
chardata['N'] = "\x66\x76\x7e\x7e\x6e\x66\x66\x00"
chardata['O'] = "\x3c\x66\x66\x66\x66\x66\x3c\x00"
chardata['P'] = "\x7c\x66\x66\x7c\x60\x60\x60\x00"
chardata['Q'] = "\x3c\x66\x66\x66\x66\x3c\x0e\x00"
chardata['R'] = "\x7c\x66\x66\x7c\x78\x6c\x66\x00"
chardata['S'] = "\x3c\x66\x60\x3c\x06\x66\x3c\x00"
chardata['T'] = "\x7e\x18\x18\x18\x18\x18\x18\x00"
chardata['U'] = "\x66\x66\x66\x66\x66\x66\x3c\x00"
chardata['V'] = "\x66\x66\x66\x66\x66\x3c\x18\x00"
chardata['W'] = "\x63\x63\x63\x6b\x7f\x77\x63\x00"
chardata['X'] = "\x66\x66\x3c\x18\x3c\x66\x66\x00"
chardata['Y'] = "\x66\x66\x66\x3c\x18\x18\x18\x00"
chardata['Z'] = "\x7e\x06\x0c\x18\x30\x60\x7e\x00"
chardata['['] = "\x3c\x30\x30\x30\x30\x30\x3c\x00"
chardata[']'] = "\x3c\x0c\x0c\x0c\x0c\x0c\x3c\x00"
chardata[' '] = "\x00\x00\x00\x00\x00\x00\x00\x00"
chardata['!'] = "\x18\x18\x18\x18\x00\x00\x18\x00"
chardata['"'] = "\x66\x66\x66\x00\x00\x00\x00\x00"
chardata['#'] = "\x66\x66\xff\x66\xff\x66\x66\x00"
chardata['$'] = "\x18\x3e\x60\x3c\x06\x7c\x18\x00"
chardata['%'] = "\x62\x66\x0c\x18\x30\x66\x46\x00"
chardata['&'] = "\x3c\x66\x3c\x38\x67\x66\x3f\x00"
chardata['\''] = "\x06\x0c\x18\x00\x00\x00\x00\x00"
chardata['('] = "\x0c\x18\x30\x30\x30\x18\x0c\x00"
chardata[')'] = "\x30\x18\x0c\x0c\x0c\x18\x30\x00"
chardata['*'] = "\x00\x66\x3c\xff\x3c\x66\x00\x00"
chardata['+'] = "\x00\x18\x18\x7e\x18\x18\x00\x00"
chardata[','] = "\x00\x00\x00\x00\x00\x18\x18\x30"
chardata['-'] = "\x00\x00\x00\x7e\x00\x00\x00\x00"
chardata['.'] = "\x00\x00\x00\x00\x00\x18\x18\x00"
chardata['/'] = "\x00\x03\x06\x0c\x18\x30\x60\x00"
chardata['0'] = "\x3c\x66\x6e\x76\x66\x66\x3c\x00"
chardata['1'] = "\x18\x18\x38\x18\x18\x18\x7e\x00"
chardata['2'] = "\x3c\x66\x06\x0c\x30\x60\x7e\x00"
chardata['3'] = "\x3c\x66\x06\x1c\x06\x66\x3c\x00"
chardata['4'] = "\x06\x0e\x1e\x66\x7f\x06\x06\x00"
chardata['5'] = "\x7e\x60\x7c\x06\x06\x66\x3c\x00"
chardata['6'] = "\x3c\x66\x60\x7c\x66\x66\x3c\x00"
chardata['7'] = "\x7e\x66\x0c\x18\x18\x18\x18\x00"
chardata['8'] = "\x3c\x66\x66\x3c\x66\x66\x3c\x00"
chardata['9'] = "\x3c\x66\x66\x3e\x06\x66\x3c\x00"
chardata[':'] = "\x00\x00\x18\x00\x00\x18\x00\x00"
chardata[';'] = "\x00\x00\x18\x00\x00\x18\x18\x30"
chardata['<'] = "\x0e\x18\x30\x60\x30\x18\x0e\x00"
chardata['='] = "\x00\x00\x7e\x00\x7e\x00\x00\x00"
chardata['>'] = "\x70\x18\x0c\x06\x0c\x18\x70\x00"
chardata['?'] = "\x3c\x66\x06\x0c\x18\x00\x18\x00"
chardata['a'] = "\x00\x00\x3c\x06\x3e\x66\x3e\x00"
chardata['b'] = "\x00\x60\x60\x7c\x66\x66\x7c\x00"
chardata['c'] = "\x00\x00\x3c\x60\x60\x60\x3c\x00"
chardata['d'] = "\x00\x06\x06\x3e\x66\x66\x3e\x00"
chardata['e'] = "\x00\x00\x3c\x66\x7e\x60\x3c\x00"
chardata['f'] = "\x00\x0e\x18\x3e\x18\x18\x18\x00"
chardata['g'] = "\x00\x00\x3e\x66\x66\x3e\x06\x7c"
chardata['h'] = "\x00\x60\x60\x7c\x66\x66\x66\x00"
chardata['i'] = "\x00\x18\x00\x38\x18\x18\x3c\x00"
chardata['j'] = "\x00\x06\x00\x06\x06\x06\x06\x3c"
chardata['k'] = "\x00\x60\x60\x6c\x78\x6c\x66\x00"
chardata['l'] = "\x00\x38\x18\x18\x18\x18\x3c\x00"
chardata['m'] = "\x00\x00\x66\x7f\x7f\x6b\x63\x00"
chardata['n'] = "\x00\x00\x7c\x66\x66\x66\x66\x00"
chardata['o'] = "\x00\x00\x3c\x66\x66\x66\x3c\x00"
chardata['p'] = "\x00\x00\x7c\x66\x66\x7c\x60\x60"
chardata['q'] = "\x00\x00\x3e\x66\x66\x3e\x06\x06"
chardata['r'] = "\x00\x00\x7c\x66\x60\x60\x60\x00"
chardata['s'] = "\x00\x00\x3e\x60\x3c\x06\x7c\x00"
chardata['t'] = "\x00\x18\x7e\x18\x18\x18\x0e\x00"
chardata['u'] = "\x00\x00\x66\x66\x66\x66\x3e\x00"
chardata['v'] = "\x00\x00\x66\x66\x66\x3c\x18\x00"
chardata['w'] = "\x00\x00\x63\x6b\x7f\x3e\x36\x00"
chardata['x'] = "\x00\x00\x66\x3c\x18\x3c\x66\x00"
chardata['y'] = "\x00\x00\x66\x66\x66\x3e\x0c\x78"
chardata['z'] = "\x00\x00\x7e\x0c\x18\x30\x7e\x00"

class SSD1306(object):
    def __init__(self, bus=1, address=0x3c):
        self._address = address
        self._bus = smbus.SMBus(bus)

        self._command(0xae) # Display off.

        self._command(0xa8) # Multiplex ratio...
        self._command(0x3f) # ...63
        self._command(0xd3) # Display offset...
        self._command(0x00) # ...0
        self._command(0x40) # Display start line at 0.
        self._command(0xa1) # Segment Re-map with column 127 mapped to SEG0.
        self._command(0xc8) # Remapped mode, scan from COM[N-1] to COM0.
        self._command(0xda) # COM pins hardware configuration...
        self._command(0x32) # ...Alternative and Left/Right
        self._command(0xa4) # Entire display ON.
        self._command(0xa6) # Inverse display mode.
        self._command(0xd5) # Display clock...
        self._command(0x80) # ...No clock divide ratio and max frequency.
        self._command(0x8d) # Charge pump...
        self._command(0x14) # ...Enabled.
        self._command(0x20) # Memory addressing mode...
        self._command(0x20) # ...Horizontal.

        self._command(0xaf) # Display on.
    
    def _command(self, command_byte):
        self._bus.write_byte_data(self._address, 0x00, command_byte)

    def _data(self, data_byte):
        self._bus.write_byte_data(self._address, 0x40, data_byte)

    def reset_cursor(self):
        self._command(0x21) # Column address...
        self._command(0x00) # ...start at 0...
        self._command(0x7f) # ...end at 127.
        self._command(0x22) # Page address...
        self._command(0x00) # ...start at 0...
        self._command(0x07) # ...end at 7.

    def putc(self, char):
        if char in chardata:
            for column in range(0, 8):
                byte  = ((ord(chardata[char][0]) >> (7 - column)) & 1) << 1
                byte += ((ord(chardata[char][1]) >> (7 - column)) & 1) << 0
                byte += ((ord(chardata[char][2]) >> (7 - column)) & 1) << 3
                byte += ((ord(chardata[char][3]) >> (7 - column)) & 1) << 2
                byte += ((ord(chardata[char][4]) >> (7 - column)) & 1) << 5
                byte += ((ord(chardata[char][5]) >> (7 - column)) & 1) << 4
                byte += ((ord(chardata[char][6]) >> (7 - column)) & 1) << 7
                byte += ((ord(chardata[char][7]) >> (7 - column)) & 1) << 6
                self._data(byte)

    def puts(self, string):
        for char in string:
            self.putc(char)

class WiFiStatus(object):
    def __init__(self, interface):
        self._interface = interface

    def _get_status(self):
        iwconfig = subprocess.check_output("iwconfig %s" % (self._interface), shell=True)
        ifconfig = subprocess.check_output("ifconfig %s" % (self._interface), shell=True)

        match = re.search(r'inet ([^\ ]+?) ', ifconfig, flags=re.MULTILINE)
        if match:
            inet_address = match.group(1)
        else:
            inet_address = ""

        match = re.search(r'RX packets ([^\ ]+?) ', ifconfig, flags=re.MULTILINE)
        if match:
            rx_packets = match.group(1)
        else:
            rx_packets = ""

        match = re.search(r'TX packets ([^\ ]+?) ', ifconfig, flags=re.MULTILINE)
        if match:
            tx_packets = match.group(1)
        else:
            tx_packets = ""

        match = re.search(r'ESSID:"([^"]+?)"', iwconfig, flags=re.MULTILINE)
        if match:
            essid = match.group(1)
        else:
            essid = ""

        match = re.search(r'Link Quality=(\d+/\d+)', iwconfig, flags=re.MULTILINE)
        if match:
            link_quality = match.group(1)
        else:
            link_quality = ""

        match = re.search(r'Signal level=(-\d+ dBm)', iwconfig, flags=re.MULTILINE)
        if match:
            signal_level = match.group(1)
        else:
            signal_level = ""

        match = re.search(r'Bit Rate=([\.\d]+ Mb/s)', iwconfig, flags=re.MULTILINE)
        if match:
            bit_rate = match.group(1)
        else:
            bit_rate = ""

        match = re.search(r'Tx-Power=(\d+ dBm)', iwconfig, flags=re.MULTILINE)
        if match:
            tx_power = match.group(1)
        else:
            tx_power = ""

        return (inet_address, rx_packets, tx_packets, essid, link_quality, signal_level, bit_rate, tx_power)

    def loop(self):
        display = SSD1306()

        while True:
            (ia, rx, tx, si, lq, sl, br, tp) = self._get_status()
            display.reset_cursor()
            display.puts((si + "                ")[:16])
            display.puts((ia + "                ")[:16])
            display.puts(("Link: " + lq + "          ")[:16])
            display.puts(("Sig: " + sl + "           ")[:16])
            display.puts(("Rate: " + br + "          ")[:16])
            display.puts(("Pow: " + tp + "           ")[:16])
            display.puts(("Rx: " + rx + "            ")[:16])
            display.puts(("Tx: " + tx + "            ")[:16])
            time.sleep(3)

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print "Usage: %s <interface>" % (sys.argv[0])
        sys.exit(1)

    ws = WiFiStatus(sys.argv[1])
    ws.loop()
          


Topic: Scripts and Code, by Kjetil @ 05/10-2019, Article Link

RSS Feed

Moving to external web hosting has made it possible to finally create an RSS feed for this site. Note that the content itself is not part of the feed, only the headers and a corresponding link back to this site.

Link: RSS Feed

Topic: General, by Kjetil @ 01/09-2019, Article Link

New Web Hosting

After 12 years of hosting this site on a server in my own house I have decided to finally move it to an external web hosting site. Hopefully this will provide better uptime, as it does not rely on my own Internet connection.

Direct Link: http://kobolt.website/infocenter/

Topic: General, by Kjetil @ 22/08-2019, Article Link

OpenVPN Setup for Android

There are probably many ways to do this, but this is what worked for me in the end, after several trials and errors. I ended up making a "standalone" server solution based on running in GNU Screen to avoid messing too much with my existing server.

I started by downloading the EasyRSA scripts to help generating certificates and such. Then ran the following commands:

./easyrsa init-pki
./easyrsa build-ca
./easyrsa build-server-full server
./easyrsa build-client-full client
./easyrsa gen-dh
          

You will have to enter a CA key passphrase and PEM passphrase, keep those for later.

Once the files are created, copy them into a new location where everything will be stored, in my case the "openvpn" directory under my home directory:

mkdir ~/openvpn
cp pki/ca.crt ~/openvpn/
cp pki/dh.pem ~/openvpn/
cp pki/issued/client.crt ~/openvpn/
cp pki/issued/server.crt ~/openvpn/
cp pki/private/ca.key ~/openvpn/
cp pki/private/client.key ~/openvpn/
cp pki/private/server.key ~/openvpn/
          


The OpenVPN server configuration file must be created manually, at ~/openvpn/server.cfg with the following contents:

ca ca.crt
cert server.crt
key server.key
dh dh.pem
dev tun
ifconfig 10.8.0.1 10.8.0.2
tls-server
port 1194
proto udp
comp-lzo
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "ifconfig 10.8.0.2 10.8.0.1"
mode server
verb 4
client-config-dir ccd
          


Create a new directory "ccd" under the directory structure and create the file ~/openvpn/ccd/client with the following single line:

iroute 10.8.0.0 255.255.255.0
          


To be able to start things easily and open the necessary parts of the firewall a script like this can be used, placed at ~/openvpn/start.sh:

#!/bin/sh
screen -S openvpn -d -m sudo openvpn server.cfg

sudo iptables -A INPUT -p udp --dport 1194 -i eth0 -j ACCEPT
sudo iptables -A INPUT -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -d 10.8.0.0/255.255.255.0 -j ACCEPT
          

This particular server already has iptables setup for NAT and such, so that is not present in this configuration.

Finally, the Android OpenVPN application requires a matching "ovpn" file with the client configuration. I had to make this one by manually looking something like this:

client
dev tun                             
proto udp
remote my.openvpn.server.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo
verb 3
<cert>
-----BEGIN CERTIFICATE-----
<contents of client.crt file>
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN ENCRYPTED PRIVATE KEY-----
<contents of client.key file>
-----END ENCRYPTED PRIVATE KEY-----
</key>
<ca>
-----BEGIN CERTIFICATE-----
<contents of ca.crt file>
-----END CERTIFICATE-----
</ca>
          


Topic: Configuration, by Kjetil @ 18/08-2019, Article Link

SSD1306 PBM Viewer

I recently bought a SSD1306 miniature OLED display. This communicates through I2C and can be connected to various boards, like the Raspberry Pi 3 which I have used.

As an initial experiment, I have made a Python script that will load a 2-color PBM image file and display it on the OLED. The image has to be in binary (P4) format and exactly 128x64 which is the same as the resolution. I have re-used the same initialization commands as mentioned here to get up and running quickly.

Enjoy:

#!/usr/bin/python
import smbus

class SSD1306(object):
    def __init__(self, bus=1, address=0x3c):
        self._address = address
        self._bus = smbus.SMBus(bus)

        self._command(0xae) # Display off.

        self._command(0xa8) # Multiplex ratio...
        self._command(0x3f) # ...63
        self._command(0xd3) # Display offset...
        self._command(0x00) # ...0
        self._command(0x40) # Display start line at 0.
        self._command(0xa1) # Segment Re-map with column 127 mapped to SEG0.
        self._command(0xc8) # Remapped mode, scan from COM[N-1] to COM0.
        self._command(0xda) # COM pins hardware configuration...
        self._command(0x32) # ...Alternative and Left/Right
        self._command(0xa4) # Entire display ON.
        self._command(0xa6) # Inverse display mode.
        self._command(0xd5) # Display clock...
        self._command(0x80) # ...No clock divide ratio and max frequency.
        self._command(0x8d) # Charge pump...
        self._command(0x14) # ...Enabled.
        self._command(0x20) # Memory addressing mode...
        self._command(0x20) # ...Horizontal.

        self._command(0xaf) # Display on.
    
    def _command(self, command_byte):
        self._bus.write_byte_data(self._address, 0x00, command_byte)

    def _data(self, data_byte):
        self._bus.write_byte_data(self._address, 0x40, data_byte)

    def reset_cursor(self):
        self._command(0x21) # Column address...
        self._command(0x00) # ...start at 0...
        self._command(0x7f) # ...end at 127.
        self._command(0x22) # Page address...
        self._command(0x00) # ...start at 0...
        self._command(0x07) # ...end at 7.

    def pbm(self, filename):
        fh = open(filename, "r")
        if not fh.readline().startswith("P4"):
            raise Exception("Not a binary PBM image!")
        header = fh.readline()
        while header.startswith("#"):
            header = fh.readline() # Ignore comments.
        if not header.startswith("128 64"):
            raise Exception("Dimensions must be 128x64!")
        data = fh.read()
        fh.close()

        if len(data) != 1024:
            raise Exception("Size of data is not 1024 bytes!")

        self.reset_cursor()
        for row_offset in [0, 128, 256, 384, 512, 640, 768, 896]:
            for column in range(0, 128):
                byte  = ((ord(data[row_offset + (column / 8)      ]) >> (7 - (column % 8))) & 1) << 1
                byte += ((ord(data[row_offset + (column / 8) + 16 ]) >> (7 - (column % 8))) & 1) << 0
                byte += ((ord(data[row_offset + (column / 8) + 32 ]) >> (7 - (column % 8))) & 1) << 3
                byte += ((ord(data[row_offset + (column / 8) + 48 ]) >> (7 - (column % 8))) & 1) << 2
                byte += ((ord(data[row_offset + (column / 8) + 64 ]) >> (7 - (column % 8))) & 1) << 5
                byte += ((ord(data[row_offset + (column / 8) + 80 ]) >> (7 - (column % 8))) & 1) << 4
                byte += ((ord(data[row_offset + (column / 8) + 96 ]) >> (7 - (column % 8))) & 1) << 7
                byte += ((ord(data[row_offset + (column / 8) + 112]) >> (7 - (column % 8))) & 1) << 6
                self._data(byte ^ 0xff)

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print "Usage: %s <128x64 binary PBM image>" % (sys.argv[0])
        sys.exit(1)

    display = SSD1306()
    display.pbm(sys.argv[1])
          


And here is how it looks with an image loaded:

SSD1306 in action.


Topic: Scripts and Code, by Kjetil @ 21/07-2019, Article Link

Commodore 64 Game Cheats

I recently tried out the same tricks from my older article about DOS games, but for a Commodore 64 game instead. This time using the VICE emulator for Blagger by Alligata, a game I could never finish as a kid.

I present some of the steps I used to create the "cheat" patch for infinite lives.
1) Start the game (should start with 5 lives) and go into the VICE monitor.
2) Dump the memory and state of the C64 with the "dump" command.
3) Continue the game, and just loose 1 life.
4) Re-enter the monitor and dump the memory and state again with the "dump" command.
5) Convert the dumps to hex and do a diff on them, to see what has changed. It may take a while to find what you are looking for, which may require additional dumps.
Eventually I found this suspicious memory area, where two values were decremented twice:

101c101
< 00000740  20 20 20 20 1e 57 57 1e  57 57 1e 7a 3a 3b 42 43  |    .WW.WW.z:;BC|
---
> 00000740  20 20 20 20 1e 57 57 1e  57 57 1e 7a 3a 3b 44 45  |    .WW.WW.z:;DE|
          

Although this is at address 0x74e and 0x74f in the dump file, the actual memory addresses are 0x6ca and 0x6cb due to the format of the dump.
6) Now, with an address to look at, set a watch point on this in the VICE monitor.
7) Continue the game again and loose another life.
8) The monitor should now stop execution automatically if done correctly. In my case it displayed:

(C:$1102) w $06ca
WATCH: 1  C:$06ca  (Stop on load store)
(C:$1102) x
#1 (Stop on  load 06ca)  076 050
.C:0de6  AE CA 06    LDX $06CA      - A:01 X:44 Y:00 SP:f6 ..-....C  184810550
.C:0de9  CA          DEX            - A:01 X:44 Y:00 SP:f6 ..-....C  184810550
(C:$0de9)
          

This assembly instruction (DEX) means the memory area is being decremented, exactly what we are looking for.
9) After investigation of the assembly code in that area I found two decrement instructions. These could be patched with NOP (No Operation) instructions (6502 machine code 0xEA) from the VICE monitor like so:

(C:$0e0c) > $0de9 ea
(C:$0e0c) > $0dea ea
          

10) Now, when continuing to play the game, any lost life is simply ignored.

To apply a patch like this permanently, do it on the file (in this case a PRG file) instead of directly in memory of course. The code location will be different, so it must be searched for manually.

Topic: Configuration, by Kjetil @ 02/07-2019, Article Link

CDROM Drive Test Report

Here is yet another article on hardware testing. Similar to the one on floppy drives and hard disk drives from earlier. Now for my collection of CDROM (and DVD) drives, to filter out which ones still work and which ones doesn't. I have compiled a complete (LaTeX typesetted) report on the test results here. A total of 14 drives were investigated, where 10 were OK, 3 were untestable and 1 was broken.

There are tests on the following hard drive models in the report:
* Aztech CDA 268-01A
* Aztech CDA 468-02I
* Creative CR-587-B
* Goldstar CRD-8160B
* HP XJ-HD166S
* Matsushita CR-504-B
* Philips CDD3600/51
* Pioneer DR-A12X
* Plextor PX-712A
* Plextor PX-716SA
* Samsung TS-H352
* Sony AD-5260S
* Sony CRX320A
* Toshiba XM-5302B

Collection:

CDROM drive collection


Topic: Mundane, by Kjetil @ 09/06-2019, Article Link

Hard Drive Test Report

Here's another article on old hardware testing, like the one on floppy drives from before. This time I decided to go through my collection of hard drives test those, again to filter out which ones still work and which ones doesn't. Once again, I have compiled a complete (LaTeX typesetted) report on the test results here. A total of 13 drives were investigated, where 10 were OK and 3 were broken.

There are tests on the following hard drive models in the report:
* Quantum ProDrive ELS
* Quantum ProDrive LPS (x2)
* Seagate ST-1144A
* Seagate ST-1239A
* Seagate ST-157A
* Seagate ST-3096A
* Seagate ST-3144A
* Seagate ST-3195A
* Seagate ST-351AX (x2)
* Conner CFA270A
* Conner CP-3104

Collection:

Hard drive collection


Topic: Mundane, by Kjetil @ 01/05-2019, Article Link

Floppy Drive Test Report

I decided to go through my collection of old 3.5" floppy drives and test them all, in order to filter out which ones still work or not. I compiled a complete (LaTeX typesetted) report on the test results here. A total of 20 drives were investigated. In short, 13 were OK, 4 were broken and 3 were untestable.

There are tests on the following floppy drive models in the report:
* Citizen LR102061 A.L.
* JPN ZFDD1.44M
* Mitsubishi MF355C-252M
* NEC FD1138H
* Panasonic JU-256A276P
* Panasonic JU-257-204P
* Panasonic JU-257A604P
* Panasonic JU-257A606P (x2)
* Sony MPF920 (x4)
* Sony Model MPF40W-00
* Sony Model MPF520-1
* TEAC FD-235HF
* TEAC FD-235HG
* TEAC FD-335HF
* Y-E Data YD-702B-6039B (x2)

Collection:

Floppy drive collection


Topic: Mundane, by Kjetil @ 02/04-2019, Article Link

64-Bit Number Conversion Tool

Around 10 years ago I posted about a number conversion tool. Several improvements have made it into the tool in those years, so it's time for an update. The most notable change is the support for 64-bit resolution on the numbers (also when compiled on 32-bit systems). In addition there is support for more operators like and (&), or (|), multiplication (*), division (/) and modulus (%).

Here is the updated code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h> /* __WORDSIZE */

static uint64_t power_of_two(uint64_t n)
{
  if (n == 0)
    return 1;
  else
    return 2 * power_of_two(n - 1);
}

static char *convert_to_bin(uint64_t integer)
{
  static char buffer[65]; /* Should not be longer than 64-bits + NULL. */
  int i, first_one;

  buffer[64] = '\0'; /* Always terminate. */

  first_one = 63;
  for (i = 63; i >= 0; i--) {
    if (integer >= power_of_two(i)) {
      buffer[63 - i] = '1';
      if (first_one > (63 - i))
      {
        first_one = (63 - i);
      }
      integer -= power_of_two(i);
    } else {
      buffer[63 - i] = '0';
    }
  }

  return &buffer[first_one];
}

static uint64_t convert_from_bin(char *number)
{
  uint64_t value;
  int n;
  char *p;

  value = 0; 
  n = strlen(number) - 3; /* Also minus "0b". */
  p = &number[2];
  do {
    if (*p == '0') {
      n--;
    } else if (*p == '1') {
      value += power_of_two(n);
      n--;
    }
  } while (*p++ != '\0');

  return value;
}

static uint64_t convert_to_int(char *number)
{
  uint64_t integer;

  if (strncmp(number, "0x", 2) == 0) {
    integer = strtoull(number, NULL, 16);
  } else if (strncmp(number, "0b", 2) == 0) { 
    integer = convert_from_bin(number);
  } else if (strncmp(number, "-", 1) == 0) {
    integer = atoll(number); /* Handle signed integers. */
  } else {
    integer = strtoull(number, NULL, 10);
  }
  
  return integer;
}

static uint64_t biggest_int(uint64_t n1, uint64_t n2, uint64_t n3)
{
  /* NOTE: Does not handle signed integers, so padding will be off. */
  if (n1 > n2 && n1 > n3) {
    return n1;
  } else if (n2 > n3) {
    return n2;
  } else {
    return n3;
  }
}

static void display_int(uint64_t integer, char *prefix, uint64_t biggest)
{
  int pad;

  if (prefix != NULL)
  {
    printf("%s", prefix);
  }

#if (__WORDSIZE == 64)
  pad = snprintf(NULL, 0, "%ld", biggest)
      - snprintf(NULL, 0, "%ld", integer);
#else /* __WORDSIZE == 32 */
  pad = snprintf(NULL, 0, "%lld", biggest)
      - snprintf(NULL, 0, "%lld", integer);
#endif /* __WORDSIZE */
  while (pad-- > 0) {
    printf(" ");
  }

#if (__WORDSIZE == 64)
  printf("%ld ", integer);
#else /* __WORDSIZE == 32 */
  printf("%lld ", integer);
#endif /* __WORDSIZE */

  printf("0x");
#if (__WORDSIZE == 64)
  pad = snprintf(NULL, 0, "%lx", biggest)
      - snprintf(NULL, 0, "%lx", integer);
#else /* __WORDSIZE == 32 */
  pad = snprintf(NULL, 0, "%llx", biggest)
      - snprintf(NULL, 0, "%llx", integer);
#endif /* __WORDSIZE */
  while (pad-- > 0) {
    printf("0");
  }

#if (__WORDSIZE == 64)
  printf("%lx ", integer);
#else /* __WORDSIZE == 32 */
  printf("%llx ", integer);
#endif /* __WORDSIZE */

  printf("0b");
  pad = strlen(convert_to_bin(biggest))
      - strlen(convert_to_bin(integer));
  while (pad-- > 0) {
    printf("0");
  }

  printf("%s\n", convert_to_bin(integer));
}

int main(int argc, char *argv[])
{
  uint64_t n1, n2, result, biggest;
  char *prefix;

  if (argc == 2) { /* Just display the number in different forms. */
    n1 = convert_to_int(argv[1]);
    display_int(n1, NULL, n1);

  } else if (argc == 4) { /* Perform extra operation. */
    n1 = convert_to_int(argv[1]);
    n2 = convert_to_int(argv[3]);
    if (argv[2][0] == '+') {
      result = n1 + n2;
      prefix = " + ";
    } else if (argv[2][0] == '-') {
      result = n1 - n2;
      prefix = " - ";
    } else if (argv[2][0] == '^') {
      result = n1 ^ n2;
      prefix = " ^ ";
    } else if (argv[2][0] == '&') {
      result = n1 & n2;
      prefix = " & ";
    } else if (argv[2][0] == '|') {
      result = n1 | n2;
      prefix = " | ";
    } else if (argv[2][0] == '*') {
      result = n1 * n2;
      prefix = " * ";
    } else if (argv[2][0] == '/') {
      result = n1 / n2;
      prefix = " / ";
    } else if (argv[2][0] == '%') {
      result = n1 % n2;
      prefix = " % ";
    } else {
      fprintf(stderr, "%s: error: invalid operator.\n", argv[0]);
      return -1;
    }
    biggest = biggest_int(n1, n2, result);
    display_int(n1, "   ", biggest);
    display_int(n2, prefix, biggest);
    display_int(result, " = ", biggest);

  } else {
    fprintf(stderr, "Usage: %s <number> [<operator> <number>]\n", argv[0]);
    fprintf(stderr, "  number can be integer decimal, hex (0x) or binary (0b)\n");
    fprintf(stderr, "  valid operators: + - ^ & | * / %%\n");
    return -1;
  }
   
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 09/03-2019, Article Link

Older articles

Newer articles