Synaptic Spiral and Fractals

Synaptic Spiral and Fractals

I have been a bit of an AI skeptic for the last few years. Sure, it helped me polish an email to a customer or two, but aside from that I didn’t get the hype. That changed a couple of months ago with the new models from Anthropic and Claude Code… There’s still a huge amount of AI hype, but there is now a huge amount of value here as well. I wanted to learn more about how good it was, so I vibe coded some n8n workflows for work – just for extra AI-bro street cred.

Then one weekend I saw this trend of people testing AI coding tools by getting them to generate single page web apps with 3D visualisations. Pretty cool. But it made me think – I’ve always wanted a visualizer that I could control more than the commercial ones. So I tried generating an animated fractal, and with a bit of effort, that was running. Then the building bug bit me, and I added more features and more… I spent the entire weekend on it and all my spare time for the next week. Claude Code is mad addictive. As a friend said “dopamine++.” That ability to spin up a new feature in an hour instead of a day or week… Madness.

Anyway, with out further commentary, I give to you my fractal based visualizer – Synaptic Spiral.nz

Final build of the Pine A64 TRS-80 Model 100

I have a few posts on various details of this project, but this is is something of a summary/conclusion post as it’s just been finished up.

Hardware Summary

  • Pine A64 LTS SBC. This includes the capability to charge and run directly off lithium batteries, and I have some details on that here.
  • 4 x 18650 cells in parallel that runs everything for 6+ hours
  • 1920×480 LCD from Aliexpress. The height works, but it’s a little too wide. Given I’ll be running almost entirely in the terminal, this was solvable with a “stty cols 170”
  • Various switches/wires/etc.

Build Notes

  • The 5V supply (including for the USB ports) is constantly active while the battery is connected, and when the SBC is shut down, it sits at the battery voltage (somewhere around 3.1-3.8V) rather then 5V. These two attributes meant I needed to add a switch to the screen power supply – I ran this off the 5V pins on the Pi header, and the switch fits nicely into the old power switch location on the Model 100. I also replaced the old DC jack with another one so I could keep the old motherboard stock.
Right hand side of case.
  • I could really do with a shorter HDMI cable, but this one still fits. Battery pack in the bottom left.
  • The screen I attached with 3M strips that I could get it close to the original distance from the screen cover – looks good. I also installed a new LED into the old “Low Battery” spot, running off another GPIO pin and indicating to me… Surprise! When the battery is low 🙂 You can see the wires coming off the right hand side of the screen in the image below.
  • The ports on the A64 line up OK with the rear ports on the back of the Model 100 so I can still access the USB ports (or at least one of them) and the power button. I was considering breaking everything out and installing new buttons in the holes, but my “workshop” is currently very limited and that would have taken more time than I wanted to spend.

Usability

With tmux and two terminals side by side, this is incredibly functional for me. I spend a lot of time on the CLI of various network devices. I still need to add a couple of characters to the keyboard script so this is 100% usable, but otherwise the feel is great and there’s no distractions. Great battery life also.

A note on modification of old devices

I do appreciate that for some people, hacking on a 37 year old device would be considered sacrilegious. I have some sympathy. FYI I have a second Model 100 which I’m not touching, and now I have parts to keep that running. Also, my new device is something I’ll use every week, whereas a stock Model 100 is not hugely useful to most people (including me), even if you spend a lot of time on the CLI like I do. So to me, my modified Model 100 is actually a much better tribute as something I can regularly use. I can appreciate opinions might differ – if you want to restore and preserve a Model 100, there seems to always plenty on Ebay 🙂

GPIO Based Keyboard in Python

Since I was trying to utilise the existing keyboard in my TRS-80 Model 100, I needed some way to connect it to the Pine A64. While I could have added something like an Arduino Pro Micro to run QMK, this would have added more complexity. Instead I decided to connect the keyboard directly to the GPIO pins on the Pine A64 and poll them using Python. This would also be applicable to the Raspberry Pi – same GPIO pinout and I’m using a Raspberry Pi GPIO library. I found some code here which was a useful start on using UInput, but the logic behind that didn’t make much sense to me and also didn’t work for me – I think maybe an early/non-working version of the script was uploaded by accident. I have made some updates and resolved the issues, as well as adding something to reduce the polling rate if the keyboard isn’t touched for a while – just saving battery life 🙂 By the way, not saying I am a coding master by any means, but someone may find this useful. Still need to add a second layer for a couple of keys that are missing from the Model 100 keyboard.

#!/usr/bin/python3
import RPi.GPIO as GPIO
from time import sleep
from evdev import UInput, ecodes as e
import logging

# TODO:
# Add an exception for handling Shift + [
# Add a new keymap for when CODE is held. Need to add chars: \ | ` ~ { }



#logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
#logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')

ui = UInput(name = "TRS-80 Model 100 Keyboard", vendor = 0x01, product = 0x01)

# TRS-80 Keyboard pin to GPIO pin map
# 1   :   11,     11  :   0,
# 2   :   12,     12  :   23,
# 3   :   13,     13  :   29,
# 4   :   15,     14  :   31,
# 5   :   16,     15  :   32,
# 6   :   18,     16  :   33,
# 7   :   19,     17  :   35,
# 8   :   21,     18  :   36,
# 9   :   22,     19  :   37,
# 10  :   0,      20  :   0

cols = [11,12,13,15,16,18,19,21,22]
rows = [23,29,31,32,33,35,36,37]

keymap = [
    e.KEY_Z,    e.KEY_A,    e.KEY_Q,    e.KEY_O,           e.KEY_1,    e.KEY_9,      e.KEY_SPACE,     e.KEY_F1,    e.KEY_LEFTSHIFT,
    e.KEY_X,    e.KEY_S,    e.KEY_W,    e.KEY_P,           e.KEY_2,    e.KEY_0,      e.KEY_BACKSPACE, e.KEY_F2,    e.KEY_LEFTCTRL,
    e.KEY_C,    e.KEY_D,    e.KEY_E,    e.KEY_LEFTBRACE,   e.KEY_3,    e.KEY_MINUS,  e.KEY_TAB,       e.KEY_F3,    e.KEY_LEFTALT,
    e.KEY_V,    e.KEY_F,    e.KEY_R,    e.KEY_SEMICOLON,   e.KEY_4,    e.KEY_EQUAL,  e.KEY_ESC,       e.KEY_F4,    e.KEY_FN,
    e.KEY_B,    e.KEY_G,    e.KEY_T,    e.KEY_APOSTROPHE,  e.KEY_5,    e.KEY_LEFT,   e.KEY_GRAVE,     e.KEY_F5,    e.KEY_NUMLOCK,
    e.KEY_N,    e.KEY_H,    e.KEY_Y,    e.KEY_COMMA,       e.KEY_6,    e.KEY_RIGHT,  e.KEY_COPY,      e.KEY_F6,    e.KEY_CAPSLOCK,
    e.KEY_M,    e.KEY_J,    e.KEY_U,    e.KEY_DOT,         e.KEY_7,    e.KEY_UP,     e.KEY_CLEAR,     e.KEY_F7,    e.KEY_RESERVED,
    e.KEY_L,    e.KEY_K,    e.KEY_I,    e.KEY_SLASH,       e.KEY_8,    e.KEY_DOWN,   e.KEY_ENTER,     e.KEY_F8,    e.KEY_PAUSE,
]

GPIO.setmode(GPIO.BOARD)

for row in rows:
    logging.info(f"Setting pin {row} as an output")
    GPIO.setup(row, GPIO.OUT)

for col in cols:
    logging.info(f"Setting pin {col} as an input")
    GPIO.setup(col, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

pressed = set()
sleep_time = 1/60
polls_since_press = 0

while True:
    sleep(sleep_time)
    syn = False
    for i in range(len(rows)):
        #logging.debug(f"Setting row {i} high, pin {rows[i]}")
        GPIO.output(rows[i], GPIO.HIGH)
        for j in range(len(cols)):
            keycode = i * (len(rows) + 1) + j
            #logging.debug(f"Checking column {j}, pin {cols[j]} which results in key {keymap[keycode]}")
            newval = GPIO.input(cols[j]) == GPIO.HIGH
            if  newval and not keycode in pressed:
                pressed.add(keycode)
                logging.info(f"Pressed {keycode} which results in key {e.KEY[keymap[keycode]]} Column {i} Row {j}")
                ui.write(e.EV_KEY, keymap[keycode], 1)
                syn = True
            elif not newval and keycode in pressed:
                pressed.discard(keycode)
                logging.info(f"Released {keycode} which results in key {e.KEY[keymap[keycode]]}")
                ui.write(e.EV_KEY, keymap[keycode], 0)
                syn = True
        GPIO.output(rows[i], GPIO.LOW)
    if syn:
        ui.syn()
        polls_since_press = 0
        sleep_time = 1/60
    else:
        polls_since_press = polls_since_press + 1

    if polls_since_press == 600:
        logging.info(f"Reducing polling rate")
        sleep_time = 1/10
    elif polls_since_press == 1200:
        logging.info(f"Reducing polling rate again")
        sleep_time = 1/5

I then threw a quick start script into /etc/rc.local so it starts on boot.

Testing the GPIO Keyboard

NTS Dreamwriter T400

While I was wandering around Ebay looking for TRS-80 Model 100’s, I came across this device… I couldn’t find much info on it, but there were a bunch going fairly cheaply (I guess a school was getting rid of them) so I picked one up. Now while Rule 35 Of The Internet has a fairly specific focus in it’s subject matter, I tend to think that if information on something doesn’t exist on the internet, it should be added… Hence this post.

The magnificent Dreamwriter T400

The T400 is in the same vein as the Tandy WP-2 or Alphasmart devices – it’s a wordprocessor which is basically a keyboard and screen, it runs on 4xAA batteries (more on that shortly) and supposedly has a pretty good battery life. These devicees seem to have been made for school use – they actually have terminals on them so you can charge them from some sort of charging station, and mine came with a NiCd battery pack installed (which was of course completely dead)

That’s OK though, mine also came with a power adaptor – standard wall wart switched mode power supply which I plugged in an, and amazingly, everything came to life. Sweet, it lives! And then… It did not. Stopped working. I checked the power supply… Quite warm and only outputting 1V when it needs to be 6V. Then I noticed the power supply was 120V only, and I had just plugged it into our 240V power. Ouch. I’ve gotten so used to PSUs auto-ranging I forgot that old PSUs often didn’t do this. So that’s dead anyway, but luckily the Dreamwriter lived on.

The NiCd pack was not easily replaceable with standard AA batteries. For one, it’s just marginally too short (the NiCds in the pack don’t have the button top we’re used to on the positive terminal of a AA, so are much shorter) and for another there is no terminal in one end of the battery tray, and the pack was soldered/welded together. Hmm.

Ah well. Nothing that chipping out a small piece of plastic and installing a small piece of wire can’t fix.

It may not be pretty, but it works surprisingly well.

The device comes apart pretty easily and simply. You can see some interior shots below. There’s a nice spot under the batteries where I am hoping to install a Raspberry Pi Zero W.

My Dreamwriter also came with a manual, and I couldn’t find it online, so if needed it is here:

As mentioned, my goal is to install a RasPi Zero W inside and use the Dreamwriter as a serial console to the Zero. Unfortunately at this stage this doesn’t work that well – I guess the terminal program is very basic. I’m going to continue working on this one though.

Some Pine A64 Power Stuff

I’ve built up a battery pack out of 4 x protected 18650 cells to eventually be used in the terminal, and have been testing how the A64 performs on battery when driving my chosen screen. The good news is, I got over 6.5hrs of battery time with low usage (“top” running on the screen, an SSH session over wifi doing minimal work). The bad news is that the charger capability is a bit lower than I would like – effectively it won’t charge if I’m using it.

Building the battery pack

Probably the only real note to add here is that the Pine expects three wires for your battery back – VBAT, GND and TS. TS is for a thermistor to measure battery temperature and control charging accordingly. The TS line needs to be tied to GND via a 10k resistor if you don’t have a thermistor, otherwise it won’t charge beyond the trickle charge rate (which is apparently 10% of peak, so 120mA.) I have protected cells and the pack is quite high capacity compared to the charge rate, so I wasn’t too concerned about overheating.

Discharge test

A few graphs below based on polling /sys/class/power_supply data once per minute. I unplugged the power at 12:15pm (though logging starts later – took me a few minutes to write the logging script) and the Pine shut down at 6:52pm

If I was getting roughly 5.3W from the batteries for 6.5hrs I guess that’s 34.45Wh, meaning the batteries gave me about 9.3Ah @3.7V. They should be 3400mAh and there’s 4… I got about 2300mAh of power from each. Seems about right for napkin math.

Pin A64 LTS Power Jumpers

There are a pair of jumpers on the A64 LTS board that are marked “Batt” and “DC5V.” The only documentation I could find for these was in the schematic:

Now, while this doesn’t actually explain what they do, I thought that at least it tells me what I need to do – since I will be using the Pine on both battery and mains power, I should have no jumper connected. The problem in this mode is that when both power and battery are connected, the USB ports don’t seem to have enough power to drive my screen (won’t come on at all). So I don’t think that’s correct.

So what if we switch to battery mode? Well here everything works correctly on both DC5V and battery, however it seems like the power chip can only barely push enough current to run the board and the screen at the same time – with any load on the board and the DC5V plugged in, it starts slowly discharging the battery. Not a huge problem I guess, but means that if the battery is discharged, it would not really charge if I was still using it when I got the device to a power point.

What about in DC5V mode? Well the USB power wasn’t stable with battery and DC5V plugged in.

What are these jumpers actually doing? To be honest, I’m not exactly sure. The only threads I’ve found about it say it’s about how the 5V USB power line is driven, but my electronics-fu is not sufficient to figure out much more from the schematic.

So I’ll be leaving it in “Battery” mode, and I guess if I expect it to charge I need to have the screen switched off (better yet I can shutdown the whole machine, as the battery charging continues even when the system is shut down – that’s pretty cool.)

Increasing AXP803 charger current

The default maximum charging current for the AXP803 chip is 1.2A, but according to the data sheet, it supports up to 2.8A. Since my system seems to flutter around the threshold of whether a 1.2A rate is charging or discharging, even if I could raise this charging current to 1.5A I would get a lot more headroom and my charge rate would increase dramatically when in use. I’ve had trouble determining how to do this though. I tried echoing new values into /sys/class/power_supply/axp20x-battery/constant_charge_current and /sys/class/power_supply/axp20x-battery/constant_charge_current_max, and the values did change, but the system stayed putting out a little under 1.2A.

One thread suggested changing settings for the power management system in the device tree, but at least on my setup, there were no relevant settings in the device tree to change…

Some other references suggested that you should be able to set and get registers directly via I2C (at least on other platforms using this power chip.) I poked into this a bit but it looks like maybe this is not possible on the A64 – I found two devices on the I2C bus, but neither of them responded to requests in the way an AXP803 should.

So might be out of luck on this front… Or maybe I need to try something different to Armbian. To be considered still.

UPDATE: Solved the charging rate issue! I decided to try the Debian based Armbian just in case there was something different in the device tree or anything. There wasn’t, but I noticed that the 1.6A charging rate that I had set in the Ubuntu Armbian was still in place – that meant the setting had been pushed to the AXP803. Which is a win – I thought maybe it was just a visual change. I had a theory that there was some kind of total power limit on the chip that I was hitting, and found it in /sys/class/power_supply/axp813-ac/input_current_limit. By default this is set to 1.5A, but it supports up to 4A max according to the data sheet. Interestingly it looks like you can only set this in 0.5A increments, so I’ve set this now to 2A, and the charging current has gone up to follow like it should. Nice!

At the current level I can run the screen and still charge at 500mA, and even run all CPUs full speed with the screen and not discharge the battery. The power chip also gets pretty hot though – luckily I have a spare heatsink that fits it.

Armbian Setup Notes

Just starting to get everything plugged in and configured, so making some notes here…

In order to rotate the screen, the needed command is:

echo 1 | sudo tee /sys/class/graphics/fbcon/rotate_all

And the command “armbian-config” gives a lot of the basic config functions. For some reason running the firmware update breaks my wifi… Might be something to resolve later.

The screen resolution is a bit high for the default console font size, so you can run:

sudo dpkg-reconfigure console-setup

I set the font to termiusbold and the size to 10×20. Makes things much more readable for my eyes.

GPIO Support

At least initially for the keyboard I’m wanting to use some python code intended for the Raspberry Pi, so I need a library so the interface is the same on the Pine64 as it is on the Pi. This one seems like it will do the trick:

https://github.com/swkim01/RPi.GPIO-PineA64

This library seems to not work correctly with python3 CLI sadly. I needed to:

sudo apt-get install python python-dev

Since python2 isn’t installed in the Focal version of Armbian by default. Then run the installer from the RPi.GPIO-PineA64 folder:

sudo python setup.py install

Then you can test to see if it’s working (sudo python required):

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(38, GPIO.OUT)
GPIO.output(38, True)
GPIO.output(38, False)

You should see pin 38 go high then low.

Also useful to have a lookup table of RPI GPIO vs that on the Pine64… I’m using this:

http://joey.hazlett.us/pine64/pine64_pins.html

TRS-80 Model 100 Components

I was considering treating the TRS-80 simply as a serial console, but I think the device in that case would mostly sit on my shelf unused. So I started poking around at hardware that could be replaced to make it more useful. I came across the “HSD088IPW1-B00” 8.8 inch LCD screen (1920×480 resolution – nice) on Aliexpress, which is only slightly larger in each dimension than the the current Model 100 screen. That certainly made me think. Actually it would be much more interesting (and power efficient) to use the existing screen, however I’m thinking that writing a driver for that may be beyond my capabilities at this stage.

I’m going to pair that with a Pine A64 SBC for brains. The great thing about this SBC is that it has built in LiPo battery management – it can run off a 3.7V LiPo (probably some 18650 cells in parallel in this case) and charge them when it’s powered up, along with being able to poll battery voltage from the CLI. Pretty sweet actually. The A64 will also power the display driver, and has enough GPIO to plug the keyboard in directly. Will have to spend some time making sure this will all work as expected – luckily the Model 100 has a service manual available so it’s actually possible to figure this stuff out without having to fully reverse engineer everything.

The next project: TRS-80 Model 100

I’ve always had a bit of a thing for retro tech, and also for buying old tech that I wanted when I was a kid but couldn’t buy at the time… I bought an N64 in 2013 and loved it. I bought a Nissan 300zx around the same time (not exactly tech, but still something I loved as a kid) when it was an aging turbocharged sports car. Not my most effective use of money ever. But then neither was building a motorised couch.

A couple of years ago I bought my partner an Alphasmart Neo, which has been out of production for about 10 years:

Not a TRS-80 Model 100

This is an absolutely kick ass writing tool for her – long battery life, solid keyboard, and no distractions. Also, the design choices mean it can have a long useful lifetime – it runs on AA batteries instead of proprietary battery packs, and for uploading text it pretends to be a USB keyboard… Just open a document on your computer and it quickly “types” everything you have saved into the document. Such a great design, and it will continue to be functional for as long as we have USB keyboards and AA batteries.

I spend a lot of my time sitting on a CLI and using text based interfaces, so I started trying to figure out if I could use this in some way for my work – at minimum maybe I could use it as a serial terminal connected to a Raspberry Pi or some other device. Turned out not to really be so possible with the AlphaSmart – it does its one thing, and it does it well.

I kept pondering on this though every so often over the last few years. /r/cyberdeck gave my some inspiration. Maybe I could custom build something with a great keyboard and a small screen for the CLI? But I’m lacking in a workshop currently which makes full custom challenging. The tiny laptops now available from GPD/OneNetbook/Chuwi etc. were also tempting, but lacked a little style.

I started hunting around Reddit for devices that could act as a dumb terminal, and came across q whole family of devices similar to the AlphaSmart, but with a critical feature… A serial port that I could use for CLI access to another device (like a Raspberry Pi) and from there, I don’t need to worry about whether or not the device I’m using can run SSH… The other device can do the heavy lifting. The various devices which look good for this on my list:

I also came across an oddball in the form of the NTS Dreamwriter T400. There seems to be very little information on these, but they are going cheap on ebay currently, so I picked one up and we’ll see how it goes once it arrives. The Amstrad NC100 seems like it would also be interesting as it can run a version of CP/M, but it seems not so possible to get unless you’re in the UK, and postage to my freight forwarder in EU is challenging at the moment due to COVID… Maybe I’ll pick one up eventually.

The one that is really interesting though is the TRS-80 Model 100.

This has an Alps mechanical keyboard which should be quite nice to type on (aside from a few interesting key placements), and it appears to be quite spacious inside should I decide to go down the hardware hacks route and replace the guts. Quite looking forward to trying this one out when it arrives.

Literature review

You have to see what others have done before you start right? Turns out, I’m not 100% original (who knew?)

We shall see where things go from here…

Couch Update 2020

Just remembered I had this blog actually. A lot has happened to the couch in the nearly 6 years since the last update. The control boxes as two separate devices (one for each motor) made it… Interesting to drive. Though it was also definitely a bonding experience with friends as the cables also weren’t long enough for one person to control both sides at the same time if two people were riding the couch. So in Jan 2016 the next upgrade was adding an Arduino Uno connected to a wireless Xbox 360 controller. Yes, the couch is now remote controlled!

That made life a lot easier when trying to drive it around people. It also tripped quite a lot of people out as I drove friends around on it with apparently no one in control…

Test setup before everything got tided up. Note the big red “Oh fuck” button. Got to have one of those.

For 2017 most of the work was in fixing the drive train… Making it easier to control meant we snapped axles more frequently. Turns out those motors have quite some torque.

2018 was time for an upgrade… It was finally time to add some shade by adding a roof, which was amazing… Especially when it rained. No pics sadly.

2019 it became clear that the drive train was just too much of a problem and needed to be solved…

2020… Time for a full rebuild. Out with the electric bike motors and chain driven two stage gear reduction we built from wood and hand tools. Out with the electric scooter wheels. Out with 2WD. In with… 4 x electric wheelbarrow wheels. And 4WD.

New wheels on the left…

We had to shift a few struts to make room for the new wider wheels, but everything came together pretty well in the end.

New hotness

Unfortunately, the previous control circuitry won’t drive the new motor controllers… The input seems to be lower impedance, and the arduino PWM can’t drive it. Sad face. Ran out of time the summer to add the needed buffer circuitry, but that’s in progress. Not looking likely that festivals will feature much this summer though…

Couch Construction Retrospective – January 2015

At the end of Jan 2015 construction recommenced with a vengeance – Rainbow Serpent Festival was the end of this month.

We decided to move up from a 36V system to a 48V system for more power, got a  new motor controller (after I fried one in November) and finally got things up and running.

I had grand plans for the control system, but ended up using two Kelly Controller test boxes, much like this one:

KBS ControlBox

So one hand on each throttle knob…And the wires weren’t quite long enough so you could either sit in the middle of the couch with one arm stretched out to either side, or have one person controlling the left motor and one controlling the right…

The first attempt at driving down the driveway should have been videoed for posterity bad sadly it wasn’t… Imagine some 300KG+ of couch, batteries and people heading very quickly into some bushes… Still, no harm done.

We ended up having to turn the max speed on the controllers to the lowest setting – 30% I think it was. At that setting we would still move at a fast walk, but with a lot of communication between the two of us we could drive pretty well. Brakes are a problem though. The tiny band brakes built into the wheels do not work well enough. There’s a fair bit of power, but steeper inclines were still a challenge. It’s currently using a 14 tooth pinion and a 90 tooth drive sprocket – future plans for a second stage of gear reduction.

Here’s some video at 50% max speed:

And a picture of the couch in action at the festival (Thanks to Mojo Film for taking this shot):

10958716_739482546147696_8439380944141177066_n

Tech Specs

4 x 12V 60AH SLA Batteries

2 x Cyclone 2kW 48V brushless motors

2 x Kelly Controller KBS48101X (40A, 24-48V)

14:90 gear reduction, 12 1/2″ wheels.