I2CDriver with TC2004A 4 line x 20 character I2C based LCD display
Shortly after I got the SPIDriver, I obtained the similar I2CDriver through CrowdSupply. It’s made by the same folks, and has a very similar interface. The software provided is different though. Anyways… I managed to figure out the ‘C’ programming language interface, added a missing “i2c_stop” prototype to the header file, and get it working in test.
Anyways, modern LCD displays all seem to have a built-in I2C interface. It’s slow, but it works. More on the speed, and tricks to get around this, later.
I2C Conversion Chip
The old parallel interface appears to still be there, but there’s almost always a daughterboard attached containing a PCF8574 I2C-to-parallel conversion chip. Here’s the datasheet for the conversion chip : PCF8574 .
Display Controller
The original Hitachi HD44780 controller chip is still there, here is datasheet: HD44780 . There appears to be a second source now, the Sitronix ST7066U, here is datasheet: ST7066U_v2.4 .
The HD44780 and friends, always did have a 4/8 bit bus, based on the Motorola MC6800 bus interface – probably because Hitachi second-sourced a bunch of Motorola MC68xx parts in the 1980s. But I digress :-)
Anyway, this bus had two control lines:
RW – read/write. This is actually “read/not write” but many source code languages don’t like the embedded use of slashes in symbol names, and it’s difficult to do an above bar in most simple word processors. Anyways, this is high for read and low for write. Its state is latched on the rising edge of E and must be stable through the end of E high.
E – enable. This is an active-high strobe.
The HD44780 has one address pin called RS for register select. Like RW, this is latched on the rising edge of E and must be stable through the end of E high. When RS is low, the command/status register is accessed. When RS is high, data memory is accessed.
There are eight data lines, D0 through D7. Generally, outgoing data must be valid before the rising edge through the falling edge of E. Most devices latch the data in on the falling edge of E, although this is not always the case.
Connections from I2C Conversion Chip to Display Controller
With only 8 bits total output from the I2C conversion chip, the display only implements the 4 bit interface. The 4 bit interface is made up of only the top 4 data lines, D4 through D7.
HD44780 Input
PCF8574 Output
RS
P0
RW
P1
E
P2
D4
P4
D5
P5
D6
P6
D7
P7
To control the HD44780, the bus interface has to be emulated by bit-banging. It takes multiple I2C transfers to do a single transaction.
More than Two Lines
The original HD44780 could control one or two line character displays with lines that were up to 64 characters in width. Today, displays often have more than two lines, but generally not nearly as wide as 64 characters – most often, not even half this… so they split the lines.
The original internal display ram was 128 bytes of addressable memory. The top line at addresses 0x00 to 0x00+LINE_LENGTH-1, the lower line at addresses 0x40 to 0x40+LINE_LENGTH-1. The rest of the memory was present and could be used as scratchpad memory, with no effect on the display.
What modern displays appear to do, is split the display into multiple 2-line parts. The top two lines work just like the original, using addresses 0x00 to 0x00+LINE_LENGTH-1, and 0x40 to 0x40+LINE_LENGTH-1, as before. The third line uses addresses 0x00+LINE_LENGTH to 0x00+(2LINE_LENGTH)-1, the fourth line uses 0x40+LINE_LENGTH to 0x40+(2LINE_LENGTH)-1.
So, in my case, for a 4 line by 20 character display, the lines start at 0x00, 0x40, 0x14 (decimal 20) and 0x54 (0x80+decimal 20), respectively.
There are also devices that have larger displays – more rows, or longer lines that don’t allow them to play the “split original 2 lines” trick. For this, the common approach is to use two HD44780 controllers (or equivalent), and have 2 “E” signals.
Accessing the Display
As with the SPIDriver, there are an assortment of drivers and sample programs provided to play with. I built the little i2ccl ‘C’ program that was provided, and that showed me the basics of access. I reviewed the lcd1602.py script. That gave me everything I needed. I adapted a very old LCD access library that I wrote many years ago, do use the i2cdriver functions. I wrote a ‘C’ program called TestLcd which exercised these libraries, and found all kinds of errors (of course).
Embrace… CG Characters
One of the cool features of the HD44780 type displays is their ability to display compose characters, or CG characters. You can compose up to 8 arbitrary different 5×7 patterns, store the patterns in RAM, and access them just like regular characters. This is helpful, because, well, the character set is a little weird – having some kind of foreign symbol where backslash should be, value 0x5C – see below.
HC44780 character set with foreign symbol where backslash should be
It can also be cool for creating industry-specific compose characters too. For instance, my first exposure to the HD44780 was in the late ’80s, where we designed it into the Kodiak Scoretec scoreboard system (I developed the electronics for the remote keyboard assembly, eventually became responsible for support of the entire system). It had little numbers for periods, number of fouls, a little football for possession, special characters for bonus etc.
Extend… CG Palette
The LCD library has the concept of a CG palette. There are far more than 8 compose characters defined, but only 8 can be used at any given time. When this library tries to display a CG character, it checks to see if it’s already in the palette. If it is, it uses it. If it is not, it finds an available CG location, loads it with the desired CG character, and uses it. If there are no CG locations available, it displays a question mark ‘?’.
I added a function to this, so that if no CG locations are available, every present CG character is cross checked to every character in the display RAM, to see if that CG character is presently in use. The first CG location that’s not in use, is removed to make room for the new CG character. At the end, if there just aren’t enough CG locations, again a question mark ‘?’ is displayed.
Extinguish… Backlight and Display Clear
I changed the clear() function to use the direct clear command, instead of writing spaces out to the screen. Much faster!
I also added a function to control the backlight, seeing as it can be turned on and off using I2C.
Speed, Speed… Always Need More Speed!
It turns out that accessing the display through I2C is quite slow. Now, I found a few easy things to speed things up… and there may be places where I can save time, but are more complex. However, those may be for a later time.
I modified the library to check whether a character is already in the display, before writing it out. If it is already there, it skips the write, saving precious I/O time.
Compared to the I/O time, the processor time is nothing, so this speeds things up considerably, especially if very little changes from second to second… like maybe, in an IRIG-B decoder display, hmmmmmm?
SPI 128 x 160 display operating with SPIDriver on output from read_irig program
I bought a cool device called SPIDriver through CrowdSupply. Actually, I bought two – and got them each with a neat little SPI based 132 x 162 colour LCD display.
It’s a very neat device, appearing as a USB serial port to your system. On my LINUX system, it appears as /dev/ttyUSBn. There are several sample programs and libraries available to drive SPIDriver, including for the display.
The display operates with a Sitronix ST7735 controller – here are a couple of different version datasheets: ST7735ST7735S_v1.1 .
Anyway, I hacked the included Python program st7735s.py to do my own bidding. irig_to_st7735.py takes STDIN text, uses the PIL library to rasterize it into graphics, and paints it into the little display.
Unfortunately, it takes about 1-1/2 seconds to get it transferred onto the display – it is a full graphics display, and it is a SPI interface, after all. I added an option to read_irig to put out 8 lines of text every 2 seconds, which I then piped into the Python program. It worked fine. Not terribly useful, but fine :-)
#!/usr/bin/env python3
# coding=utf-8
#***************
# Import functions.
#---------------
import array
import getopt
import struct
import sys
import time
from PIL import Image, ImageDraw, ImageFont
from spidriver import SPIDriver
#***************
# Constants.
#---------------
Version = 0
Issue = 3
IssueDate = "2019-05-06"
DefaultDevice = "/dev/ttyUSB0"
LightBlue = (102, 255, 255)
PaleBlue = (102, 204, 255)
Red = (255, 0, 0)
Yellow = (255, 255, 0)
Lime = ( 0, 255, 0)
White = (255, 255, 255)
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
SLPIN = 0x10
SLPOUT = 0x11
PTLON = 0x12
NORON = 0x13
INVOFF = 0x20
INVON = 0x21
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RAMRD = 0x2E
PTLAR = 0x30
COLMOD = 0x3A
MADCTL = 0x36
FRMCTR1 = 0xB1
FRMCTR2 = 0xB2
FRMCTR3 = 0xB3
INVCTR = 0xB4
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC
RDID4 = 0xDD
PWCTR6 = 0xFC
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
DELAY = 0x80
#***************
# Pure Python rgb to 565 encoder for portablity
#---------------
def as565(ProcessedImage):
#print ("ProcessedImage:", ProcessedImage)
OriginalRed, OriginalGreen, OriginalBlue = [list(c.getdata()) for c in ProcessedImage.convert("RGB").split()]
def MultiplyAndShift(ColourValue, ShiftBy):
return ColourValue * (2 ** ShiftBy - 1) // 255
d565 = [(MultiplyAndShift(BlueValue, 5) << 11) | (MultiplyAndShift(GreenValue, 6) << 5) | MultiplyAndShift(RedValue, 5) for (RedValue, GreenValue, BlueValue) in zip(OriginalRed, OriginalGreen, OriginalBlue)]
d565h = array.array('H', d565)
#print ("d565h:", d565h)
d565h.byteswap()
d565s = d565h.tostring()
#print ("d565s:", d565s);
return array.array('B', d565s)
def debug565(OriginalColour):
print ("OriginalColour:", OriginalColour)
OriginalRed, OriginalGreen, OriginalBlue = OriginalColour
def MultiplyAndShift(ColourValue, ShiftBy):
return ColourValue * (2 ** ShiftBy - 1) // 255
d565 = [(MultiplyAndShift(OriginalBlue, 5) << 11) | (MultiplyAndShift(OriginalGreen, 6) <HH", x0, x1))
self.writeCommand(RASET) # Row addr set
self.writeData(struct.pack(">HH", y0, y1))
self.writeCommand(RAMWR) # write to RAM
def rect(self, x, y, w, h, color):
self.setAddrWindow(x, y, x + w - 1, y + h - 1)
self.writeData(w * h * struct.pack(">H", color))
def start(self):
self.sd.setb(0)
time.sleep(.001)
self.sd.setb(1)
time.sleep(.001)
self.cmd(SWRESET) # Software reset, 0 args, w/delay
time.sleep(.180)
self.cmd(SLPOUT) # Out of sleep mode, 0 args, w/delay
time.sleep(.180)
commands = [
(FRMCTR1, ( # Frame rate ctrl - normal mode
0x01, 0x2C, 0x2D)), # Rate = fosc/(1x2+40) * (LINE+2C+2D)
(FRMCTR2, ( # Frame rate control - idle mode
0x01, 0x2C, 0x2D)), # Rate = fosc/(1x2+40) * (LINE+2C+2D)
(FRMCTR3, ( # Frame rate ctrl - partial mode
0x01, 0x2C, 0x2D, # Dot inversion mode
0x01, 0x2C, 0x2D)), # Line inversion mode
(PWCTR1, ( # Power control
0xA2,
0x02, # -4.6V
0x84)), # AUTO mode
(PWCTR2, ( # Power control
0xC5,)), # VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
(PWCTR3, ( # Power control
0x0A, # Opamp current small
0x00)), # Boost frequency
(PWCTR4, ( # Power control
0x8A, # BCLK/2, Opamp current small & Medium low
0x2A)),
(PWCTR5, ( # Power control
0x8A, 0xEE)),
(VMCTR1, ( # VCOM control
0x0E,)),
(MADCTL, ( # Memory access control (directions)
0xC8,)), # row addr/col addr, bottom to top refresh
(COLMOD, ( # set color mode
0x05,)), # 16-bit color
(GMCTRP1, ( # Gamma + polarity Correction Characterstics
0x02, 0x1c, 0x07, 0x12,
0x37, 0x32, 0x29, 0x2d,
0x29, 0x25, 0x2B, 0x39,
0x00, 0x01, 0x03, 0x10)),
(GMCTRN1, ( # Gamma - polarity Correction Characterstics
0x03, 0x1d, 0x07, 0x06,
0x2E, 0x2C, 0x29, 0x2D,
0x2E, 0x2E, 0x37, 0x3F,
0x00, 0x00, 0x02, 0x10)),
(NORON, ()), # Normal display on
(DISPON, ()), # Main screen turn on
]
for c, args in commands:
self.cmd(c, args)
def clear(self):
self.rect(0, 0, 128, 160, 0x0000)
def writestrings(self, ListOfLineStrings):
#print ("On entry into writestrings(), ListOfLineStrings: ", ListOfLineStrings)
#print ("Starting writestrings")
BaseWidth = 160
BaseHeight = 128
# make a blank image for the text, initialized to transparent text color
TextImage = Image.new('RGB', (BaseWidth, BaseHeight), ( 16, 16, 16))
TextSize = int(BaseHeight/ 8)
LineColours = (White, Lime, Lime, PaleBlue, LightBlue, LightBlue, Yellow, Red)
#print ("Length of list of LineColours: " + "{0:d}".format(len(LineColours)))
StartColumnOffset = 0
# get a font
#FontToWrite = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', int(TextSize))
FontToWrite = ImageFont.truetype('Courier_New.ttf', int(TextSize))
# get a drawing context
DrawContext = ImageDraw.Draw(TextImage)
# draw text, full opacity
LineNumber = 1
for LineString in ListOfLineStrings:
#print ("LineString: " + LineString)
if (LineNumber > len(LineColours)):
print ("Past end of colour list, LineNumber = " + "{0:d}".format(LineNumber) + " and length of LineColours list = " + "{0:d}".format(len(LineColours)))
print ("Length of ListOfLineStrings = " + "{0:d}".format(len(ListOfLineStrings)))
print ("ListOfLineStrings: ", ListOfLineStrings )
print ()
LocalColour = White
else:
LocalColour = LineColours[LineNumber-1]
DrawContext.text((StartColumnOffset, (LineNumber-1)*TextSize), LineString, font=FontToWrite, fill=LocalColour)
LineNumber += 1
FinalImage = TextImage
# debug by saving images to disk
#print ("Output 01txt.jpg")
#SaveImage = TextImage.convert('RGB')
#SaveImage.save("01text.jpg")
#print ("Ouput 02base.jpg")
#SaveImage = BaseImage.convert('RGB')
#SaveImage.save("02base.jpg")
#print ("Ouput 03final.jpg")
#SaveImage = FinalImage.convert('RGB')
#SaveImage.save("03final.jpg")
#print ("FinalImage.size")
#print (FinalImage.size)
if FinalImage.size[0] > FinalImage.size[1]:
#print ("Rotating 90 degrees")
FinalImage = FinalImage.transpose(Image.ROTATE_90)
#w = 160 * FinalImage.size[0] // FinalImage.size[1]
#FinalImage = FinalImage.resize((w, 160), Image.ANTIALIAS)
#(w, h) = FinalImage.size
#if w > 128:
#FinalImage = FinalImage.crop((w // 2 - 64, 0, w // 2 + 64, 160))
#elif w < 128:
#c = Image.new("RGB", (128, 160))
#c.paste(FinalImage, (64 - w // 2, 0))
#FinalImage = c
st.setAddrWindow(0, 0, 127, 159)
#st.writeData(as565(FinalImage.convert("RGB")))
st.writeData(as565(FinalImage))
#***************
# Function to provide usage help.
#---------------
def usage():
print ("nTake in read_irig LCD output and display to ST7735 display attached to SPIDriver, v"+"%1d" % (Version)+"."+"%1d" % (Issue)+" "+IssueDate+" dmw")
print ("nTypical usage: "+sys.argv[0]+" [option]* ")
print ("n Options: ")
print (" -o Output device for SPIDriver (default " + DefaultDevice + ")")
print (" -v More verbose")
print ("n RCS Info:")
print (" $Header: /home/dmw/src/ntp/refclock_irig/RCS/irig_to_st7735.py,v 1.4 2019/05/17 03:37:27 dmw Exp $")
print ("n")
#***************
# Main function.
#---------------
if __name__ == '__main__':
#***************
# Get command line options. Error results in help text dump and exit.
#---------------
try:
opts, args = getopt.getopt(sys.argv[1:], "o:v")
except getopt.GetoptError as Error:
# print help information and exit:
print ("n")
print (Error) # will print something like "option -a not recognized"
print ("n------------------------------")
usage()
sys.exit(2)
#***************
# Set defaults.
#---------------
UseDevice = DefaultDevice
Verbose = False
#print ("Checking options now")
#***************
# Parse values from command line options. Error results in message and exit.
#---------------
for Option, Argument in opts:
#print ("Checking option: " + Option + ", with argument: " + Argument)
if Option in ("-o"): # Output file name.
UseDevice = Argument
#print ("nUsing device: " + UseDevice)
elif Option in ("-v"): # Turn on verbosity.
Verbose = True
else:
print ("nUnknown option "" + Option + " " + Argument + "", aborting...")
print ("n------------------------------")
usage()
sys.exit(2)
if (Verbose):
print("nLightBlue")
debug565(LightBlue)
print("nPaleBlue")
debug565(PaleBlue)
print("nRed")
debug565(Red)
print("nYellow")
debug565(Yellow)
print("nLime")
debug565(Lime)
print ("nWhite")
debug565(White)
print ()
print ("nUsing device: " + UseDevice)
print ()
#exit(0)
#***************
# Open ST7735 display through SPIDriver, initialize and clear.
#---------------
st = ST7735(SPIDriver(UseDevice))
st.start()
st.clear()
#***************
# Initial message.
#---------------
LineList = [
# 1111111
#1234567890123456
" IRIG Decoder",
"v"+"%1d" % (Version)+"."+"%1d" % (Issue)+" "+IssueDate,
" (c) 2019",
" Dean Weiten",
" Winnipeg, MB",
" (204)-888-1334",
" dmw@weiten.com"]
st.writestrings( LineList )
#***************
# Loop on input.
# Expecting up to 8 lines per frame,
# up to 16 characters per line.
# Colours are fixed sequence.
# An exception (often a ctrl-C break)
# results in clearing and exit message display.
#---------------
try:
LineIndex = 0
LineList = []
for line in sys.stdin:
FindFF = line.find('f')
if (FindFF>=0):
if (Verbose):
print ("Got FF at " + "{0:d}".format(FindFF))
print( "The line up to the FF is "" + line[:FindFF] + ""." )
LineToAppend = line[:FindFF].rstrip("rnf")
if (len(LineToAppend) > 0):
LineList.append(LineToAppend)
st.writestrings( LineList )
LineList = []
LineToAppend = line[FindFF+1:].rstrip("rnf")
if (len(LineToAppend) > 0):
#print ("First line "" + LineToAppend + "" has length " + "{0:d}".format(len(LineToAppend)) + ", so will be appended, list is at present: "", LineList, "".")
LineList.append(LineToAppend)
else:
LineToAppend = line.rstrip("rnf")
if (len(LineToAppend) > 0):
#print ("Line "" + LineToAppend + "" has length " + "{0:d}".format(len(LineToAppend)) + ", so will be appended.")
LineList.append(LineToAppend)
#print ("Length of LineList after append is " + "{0:d}".format(len(LineList)) + ".")
if (Verbose):
print( "The line list is:" )
print(LineList)
#st.writestrings(LoopNumber)
#LoopNumber += 1
finally:
st.clear()
#time.sleep(3)
LineList = [
# 1111111
#1234567890123456
" IRIG Decoder",
"v"+"%1d" % (Version)+"."+"%1d" % (Issue)+" "+IssueDate,
"",
" Exiting"]
st.writestrings( LineList )
time.sleep(4)
st.clear()
My grandfather was born before the turn of the 20th century, supposedly in Luxembourg (although that is not really important), and, by his own telling, was a child labourer in the mines somewhere in Europe during the Great War.
I seem to recall my grandfather saying, “that’s better than a shot in the ass with a scoop shovel“, or something to that effect – I don’t recall if he really did say it that way, or even if he really did say it – but I’ve taken that saying to heart, you may hear me say it often, heh heh.
I’m pretty sure that my grandfather knew what a shot in the ass with a scoop shovel felt like. I had no desire to feel it myself, and that’s one of the primary things that drove me to get an education and work in the tech sector.
I am too puny, too weak, too whimpy to man up and be a miner!
I’m a bit of a time-nut… well, not quite as adamant as some of those on that list ( Allen deviations? Seriously? Who would talk about Allen that way?!? ), but I am crazy about keeping time, accurate time (well to within the millisecond or so, anyway), and distributing time.
IRIG-B Decoder Box
Since my first encounter with IRIG-B in, oh, was that 1988 (?), doing an IRIG-B decoder box at Vansco, on contract for Manitoba Hydro, I’ve been fascinated by the IRIG-B time code. Here’s the official specification, and an unofficial site by someone trying to sell you something. Truth be told, the original MC68xx (might have been MC68701 because it had both accumulators and more than one TCAP pin) IRIG-B decoder firmware was written by Filipe Fernandes… then I took it from there, as Filipe got gobbled up by all the other stuff going on at Vansco :-) That decoder box took in modulated IRIG-B only, put out a contact for when it was valid or invalid, and had an RS-232 interface for another device to tell it the IRIG-B time. What fun!
Extensions
Then came the IEEE 1344 extensions, which defined a use for the extra IRIG-B undefined bits, giving more information that originally intended. The original IRIG-B decoder box did not support these extensions, but our next product did.
APT Power Technologies had IRIG-B
Anyways, while tangled up in all the other matters during development of the APT relay, like running the APT division, hiring staff, arranging trade shows, and designing the entire data acquisition system, I designed the hardware and ported & upgraded the firmware for the on-board MC68711 IRIG-B decoder. Oh yes, and designed the time coordination system between the multiple processors in the system, which one had the master time and when, etc. Very interesting. During that time, we added unmodulated IRIG-B input as well. The unit cycles between searching for valid modulated IRIG-B and unmodulated IRIG-B.
Getting IRIG-B for Testing
The only way that we had to generate IRIG-B was with an Arbiter 1084 satellite clock. It would put out a modulated IRIG-B stream, and an unmodulated IRIG-B stream (TTL level), and a 1 pulse-per-second (generally referred to as 1 PPS or just “PPS”) (also TTL level). There was also a serial interface – reminiscent of what we did in the original IRIG-B decoder box – but we rarely used it. It could change settings on the IRIG-B interface signal, but we generally had models with the front panel buttons and display, so we changed parameters using that.
Satellite clocks were expensive back then. Well, industrial grade ones still are – but we needed IRIG-B signals for testing – in product development, but also in manufacturing test. So, we had one central satellite clock, and distributed IRIG-B through the building using CAT5 twisted pair, with specially marked RJ45 drops at developer’s desks, in production, in customer support, etc.
Experimenting with various formats was a pain. We had to get access to the satellite clock (it was in the locked server room), change the settings, then get other users’ acceptance of the change… oops, did I get that out of sequence? Ha ha, I did get that out of sequence several times. It turned out that most locations only had a single drop, so if I changed from unmodulated to modulated when users weren’t expecting it, ugh it caused us some heartache. To the point where production got two separate direct wire drops. Then, we had loading issues, so production got its own clock. It didn’t have to be accurate, just functional, so I’m not sure that it was even always satellite locked.
Rudimentary Test Recordings
Sometimes, we recorded IRIG-B signals and just played them back. After all, modulated IRIG-B was developed in the 50s (or was it 60s?) and was intended to be recorded on multi-track audio recorders and strip chart recorders for the telemetry of various military tests (think nuclear weapons testing). I experimented with cassette tape recorderings in the early days, then with MP3 players like my little Samsung YP-U3 that we used on our trip to England & Scotland in 2005. It had mixed results. Modulated worked quite well, but unmodulated was unreliable – since it’s not really audio, and requires DC offset to be maintained, or restored, or something.
Enter tg
Around 2005, I found out that the NTP project had a program called tg in its “utils” subdirectory. tg stands for “Timecode Generator”, and it was designed to put out modulated simulated IRIG-B or WWV through the audio output port of a Sun workstation computer (I suspect that this was the original target for the NTP executable). I could be wrong, maybe it was a different *NIX system, but anyway, it didn’t work on the LINUX that I was running at the time, probably Mandrake LINUX. So, I hacked it to use OSS to make it run on my computer. What fun!
tg isn’t good enough for really accurate time sync! It’s just good enough to test the decoder, test edge cases, etc.
Deano went a-hacking
Of course, I couldn’t leave well enough alone, so I Weiten-ified it, as Michael Miller used to say. I added a slew of options and tweaks. I added optional unmodulated IRIG-B output, tweaked its time code generation, added IEEE 1344 extension support, added the ability to start at an arbitrary specified time & date, added the ability to insert and remove leap seconds on demand, and much more. I found that the audio card output sample rate wasn’t quite good enough over long term testing to keep accurate time – the output sample rate of 8,000 samples per second and the sinewaves were precisely, you know, 8 samples or 80 samples (IRIG or WWV) per cycle, so if the output sample rate were off by 0.1%, the frequency was off by 0.1%, and this would accumulate to eventually cause time drift.
So, I arranged to insert or remove a single cycle (1 mSec or 10 mSec) at what I decided was an innocuous part of the time code – at least for my decoders, heh heh. The program compared the time to the LINUX real-time clock (which often is sync’d to global standard using NTP, how cool is that), and if the amount became detectable, it would slip in an extra cycle, or remove an extra cycle per second, to get back in sync.
I also added options to create file output instead of audio, so could be played back later (through audio port, as before, or through an Arbitrary Waveform Generator).
The Fork Less Travelled
So anyways, when I was done, I was quite proud of my work, so I sent it back to the NTP project – specifically to Dr. David Mills, chief NTP guy , and author of the book on network time sync – and I’m sure he was absolutely horrified at what I had done to the program. In retrospect, I didn’t read nor respect the code formatting guidelines, I hacked and slashed the original audio driver code, yikes. Dr. Mills was very polite, but wouldn’t accept my changes to tg on top of tg itself, but instead called it tg2. If you download NTP today, you will see util/tg2 in there, waiting to be hacked some more :-)
Around 2006, I re-mastered a Knoppix CD with tg2 built in, so I could demo all its features on a live CD. At the time, Jason Fuith and I were operating our own business called Elecsys Solutions (and starving, at least financially, while doing it), and we wanted to maybe sell it to folks like Krish Narendra, who was now in charge of Product Development at ERLPhase, and still faced all the problems of satellite clocks, IRIG-B distribution etc.
The idea that we had wasn’t necessarily to sell the code, but to sell support of it – maybe install it on custom embedded hardware, or put in specific customization, etc.
tg2 + Knoppix = NAN
Although interesting, it didn’t sweep Krish off his feet, and even at a modest cost, he could not justify the expenditure. Well, I gave him the CD anyway, and I don’t think it went any further.
After Krish left ERLPhase, I worked out of his office for a while in 2018, and I stumbled across that CD, heh heh. I gave it to Mark Poole, as he is now ERLPhase’s IRIG-B guy – and he was quite interested. Mark and I had a long chat, and my next “hobby” project was born! More on that later.
Meanwhile, Back doing Real Work
I tinkered with tg2 from time to time, per above, but didn’t do much with it. In the meantime, Elecsys was purchased by Norscan Instruments and I went to work there as Product Development Manager for 3-1/2 years. They didn’t have any specific interest in IRIG-B, NTP, or tg2 – they needed to make some money to pay our wages, imagine that!
Then back to ERLPhase for a bit, a great year at GE Multilin in Markham, back in Winnipeg for a short time, and off to Phoenix to work for Alstom Grid Digital Instrument Transformers, or Alstom Grid DIT (now GE Grid Solutions DIT).
Optical IRIG
Now, at DIT, they used satellite clocks all right – Arbiter 1084s, in fact, among others (like Reason, now GE, ones). There was one important difference though – DIT used the now-standard orange 62.5/125 multimode fibre with 820 nm pulsed light (using the Avago/Broadcom HFPL1414TZ transmitter). On the Arbiter 1084, this option is called “option 20”, and the single ST output can be configured to output IRIG-B unmodulated (of course :-) ), IRIG-B modified manchester coding, or 1 PPS. DIT used the 1 PPS, because its primary use was to synchronize sampling / output of its product – what’s now known in the industry as a “primary converter merging unit” – so that it can be synchronized in time to other merging units in the system.
We did switch our satellite clocks back and forth between 1 PPS and IRIG-B unmodulated, when we were synchronizing with Reason (now GE) merging units. I was intrigued.
Dean Scores! ( an Arbiter 1084C satellite clock )
I always wanted a satellite clock, but, as I said before, they are expensive. I started trolling for one in late 2015. They are still expensive – generally selling for US$700 used, and up to US$2100 if new in box. I just happened to find one selling at BMI Surplus for something like US$250, talked them down to US$236 inc shipping and tax, and it was mine, yay! As a bonus, it was the Arbiter 1084C model, which has the big seven segment LED display on the front, way cool :-)
Arbiter 1084C Satellite Clock
It didn’t come with option 20, so I (carefully!) disassembled one of DIT’s 1084 clocks, made a parts list and took pictures, ordered what I needed, and installed them in my clock. Now I have option 20, heh heh heh.
This came in handy for testing of the Reason merging unit with RogoFlex at PowerTech in Vancouver in August 2017, when I took my own 1084 clock to sync the systems together.
What to Do with a Satellite Clock?
My satellite clock didn’t come with a GPS antenna, so I got one through Amazon, of course, put that up with the many other antennas above my house, and got the clock working.
I had a lot of fun with the Arbiter 1084C and my oscilloscope – marvelling at the time code, watching the different signals, cross referencing the different formats (yes, I am that weird).
The clock was affected by a rollover bug. I ordered new EPROMs for US$60 from Arbiter. Easy peasy, replaced those, just like in my early days.
GPS Module Troubles
At one point, the clock stopped working properly. Sad day! The GPS module had died. It seems that it’s writing something to the flash on the GPS module constantly, and it just wore out.
The module, an old Motorola OnCore unit, is of course obsolete. There are non-drop-in replacements, but they aren’t binary compatible. Arbiter will upgrade your unit – as I recall, it’s about US$300 – more than I paid for the satellite clock in the first place! Now that was a very sad day.
I bought a SMT soldering/desoldering station, flash programmer and some spare flash parts, pulled the old flash off the board and rolled it out to disk… programmed the new part, but could not get it installed back on the board properly. Seems as though I damaged the board removing the parts, so sad.
Fortunately, I found a (less) used GPS module of the exact same model, at some place in the far east. I received it, plugged it in, and was back in business.
Oh, and the backup battery had of course died – being nickel-cadmium and probably 25 years old! But, it had also corroded its connections badly. I repaired the board and replaced the battery, and all is good again. The battery is actually optional, probably should have just left it off.
Now What to do with a Satellite Clock?
The satellite clock sat running on my shelf for a year or so, before I got to thinking that I should use the IRIG-B signal output. At first, I wanted to time sync a local computer and create my own (effectively) stratum 1 NTP time reference. I don’t recall, I might have even had it working briefly, but what do I need a stratum 1 NTP time reference for? That’s kind of a dead-end project, at least for me.
If You Can’t Generate ‘Em, Decode ‘Em?
Back to that conversation with Mark Poole, he mused that it would be nice to have a tool that could display the details of IRIG-B signals, so Customer Service, Applications and Product Development personnel, could trouble shoot issues, including various clocks that apparently give wrong or conflicting information, get the parity wrong, use reversed time zone-to-UTC offset etc.
So I started playing with NTP’s refclock_irig, not to sync my computer’s clock (in fact, I neutered it, so it would not), but to fully decode and display the signal. Along the way, I added support for unmodulated IRIG-B as well – that was a challenge, since an audio input port is used to read the signal, and it does not maintain DC levels. In fact, I did a lot of work on DC restoration à la NTSC TV signal recovery, back in the day. That was fraught with peril, eventually causing a numerical overflow or a drifting DC baseline, so I eventually settled on a much simpler “look for fast shifts” solution, and that worked well. I made the printout of the time code optional, using “fudge” tweak configuration bits already in the code, to dump output to the existing log, then tail -f in a separate console to display them. I also generated some (huge) CSVs from time to time, both raw analog data (do you see what I see?) and various decode internals (do you decode what I decode?). A lot of work to keep putting debug in, take it out, etc. There are only 4 fudge tweak bits, and 3 of them are already in use… I pulled one back, so I could use 2, but still, too much hassle to tweak it inside the NTP framework.
Stand-Alone Decoder Program
Eventually, I undertook an effort to break my refclock_irig code away from NTP, creating read_irig. This program stands alone, and supports too many options:
Read and decode modulated/unmodulated IRIG-B/modulated IRIG-E from audio signal, v0.43, 2019-05-08 dmw
RCS Info:
$Header: /home/dmw/src/ntp/refclock_irig/RCS/read_irig.c,v 1.57 2019/05/08 06:07:07 dmw Exp $
Usage: read_irig [option]*
Options: -b Disable automatic input gain control
-c Put out CSV file of bunch of internal values for portion of time
-e Repeat full header every this many lines (default 20)
-f Force only format:
0 = Any (default)
1 = Normal Modulated
2 = Normal Unmodulated
3 = Inverted Unmodulated
4 = Normal or Inverted Unmodulated
-g Initial gain setting (default 50), from 10 to 100
-i Input file instead of audio input
-j Delay in samples for "-c" CSV output after startup (default 8000)
-m Enable IRIG-B modulated bandpass filter
-n Non-inverting input stream (normally input is inverted)
-o Put out CSV file of just raw input for as long as program runs
-r Bitmap of outputs:
bit 0 = decoded data
bit 1 = raw data
bit 2 = do not repeat full header while in low verbosity
bit 3 = 8 lines x 16 chars/line flat panel display output (disables all others)
(default value shows raw and decoded data, and repeats full header periodically)
-s Run this many seconds then exit (default forever), must be greater than 7
-t Change timeout on successful decode, must be greater than 4.0 sec
-v Increase verbosity of output
-x Use 2nd stream (right? left?) instead of 1st
-y Invert raw_signal_csv_out from "-o" option
-z Unmodulated edge detect threshold amount (default +/-0.20) in +/- amount of max
Notes: 1. Starting with " Normal Unmodulated" format, default time out after about 64,000 samples
(about 8.0 sec) without successful decode, then will cycle through " Normal Modulated",
" Normal Unmodulated", and "Inverted Unmodulated", with about 8.0 sec timeout on each format.
2. Setting timeout on successful decode to less than about 8 sec may produce intermittent lock or
failure to lock on modulated IRIG carrier.
3. It can take a few seconds to lock onto a signal, so output may not be frames.
4. Modulated bandpass filter for IRIG-B tends to "shmoosh" the input signal and make it difficult
to decode, and most IRIG-B signals are very clean, so it's not generally necessary.
5. Default outputs include both raw and decoded data. If both are turned off using "-r" option,
decoded data is turned on alone.
This software licenced under the GPL, derived from refclock_irig, changes made 2018, 2019 by Dean Weiten
Contact: Dean Weiten, Winnipeg, MB, Canada, ph (204)-888-1334, E-mail dmw@weiten.com
The output, I think, is way cool:
#
#--------------------------------------------------------------------------------------------------||------------------------------------------------------------|
# Inverted Unmodulated IRIG-B Raw (inverted audio) (Gain 100 giving -12.9 dB) || Inverted Unmodulated IRIG-B (inv , gain 100 -> -12.9 dB) |
# StrtBinSecs (SBS) | Control Bits | Year | Day of Year | Hours | Minutes |Seconds ||Day| Date and Time |SBS hex|Leap| DST |Offset|Qual|Par |
# ........ .........|......... .........|.... ....| .. .... ....| .. ....| ... ....|........||...|.... .. .. .. .. .. | . ....|....|.....|......| ..|....|
# | | | | | | || | | | | | | | |
.001010000.000011001.000100000.010111000.000101001.000000001.001100000.000100001.001000011.00000101. 130 2019-05-10 11:23:05 0_A019 !lsp DST UTC-05 00 1 ok
.001010000.000011010.000100000.010111000.000101001.000000001.001100000.000100001.001000011.00000110. 130 2019-05-10 11:23:06 0_A01A !lsp DST UTC-05 00 1 ok
.001010000.000011011.000000000.010111000.000101001.000000001.001100000.000100001.001000011.00000111. 130 2019-05-10 11:23:07 0_A01B !lsp DST UTC-05 00 0 ok
.001010000.000011100.000000000.010111000.000101001.000000001.001100000.000100001.001000011.00001000. 130 2019-05-10 11:23:08 0_A01C !lsp DST UTC-05 00 0 ok
...
The raw and inverted decode sections can be enabled independently, and the program can auto-detect (slowly, mind you) modulated IRIG, unmodulated IRIG, and inverted unmodulated IRIG.
I’m still a sad case… I can sit and watch the codes go by, constantly. It turns in a separate TTY on my computer, in the background, all the time. Heh heh heh.
My father told me a story about my grandfather, Michael Weiten. The story goes like this: my grandfather lived on a hard scrabble farm just south of Makinak, Manitoba, which just east of the boundary for Riding Mountain National Park. Now, during the 30s and 40s, apparently there were plenty of wildlife in the area, especially up near the park. Don’t tell anyone, but my grandfather and the other hard-scrabble settlers in the area, would hunt near, and sometimes in, the park (shhhhh!).
My grandfather and his buddy, whom I think was Archie, used to hunt together. Archie had an old wobbly Ford, probably a Model “A”, but quite possibly a Model “T” – life was pretty hard in the area. My grandfather quite likely, had no vehicle.
Did I mention it was hard scrabble? He used his back to clear land, build railroad, and build road. Ugh, I am tired, just thinking of it!
Anyway, Archie and my grandfather had been out hunting (probably poaching) one day, and they were grinding their way back in the old Ford, bouncing down the rutted, muddy rural road. They are talking about cars, the amazing technology they contained, and how it made life so much better than walking, quicker and more convenient even than horseback.
Archie pats his trusty Ford, leans over to my grandfather and says:
Yeah, Mike, they can’t improve on this much, can they?
Bwa-ha-ha-ha!
For Those of You Who Need The Humour Explained…
Yes, that was probably 1935 or so. The car was possibly a model “T”. The roads in rural Manitoba up near Skane’s Crossing were probably mostly mud. Maybe, just maybe, things have improved somewhat in the 80-plus years since then.
Waxing Philosophically for a Moment
I sometimes think of this story when I’m cruising in my DTS at 80 MPH down the Interstate, sipping my Diet Pepsi(tm) and listening to old Robert Johnson delta blues music. Damn, grandfather would have appreciated the good life! Because of his effort, I am able to live it.
I need to encrypt the contents of my Drobo – at least the Drobo partitions that are going to contain my personal data. The Drobo is too old to natively support encryption.
To do LUKS, you need cryptsetup package. Use apt-get or apt command.
Configure LUKS partition
WARNING! The following command will remove all data on the partition that you are encrypting. You WILL lose all your information! So make sure you backup your data to an external source such as NAS or hard disk before typing any one of the following command.
In this example, I’m going to encrpt /dev/sdj1. Type the following command:
# cryptsetup -y -v luksFormat /dev/sdj1
Sample outputs:
WARNING!
========
This will overwrite data on /dev/sdj1 irrevocably.
Are you sure? (Type uppercase yes): YES
Enter LUKS passphrase:
Verify passphrase:
Command successful.
This command initializes the volume, and sets an initial key or passphrase. Please note that the passphrase is not recoverable so do not forget it.
Switching to UUID Instead of Block Device Name
At this point, you can switch to using UUID reference, so the mapping won’t change if, say, your block devices show up in a different sequence. To find the UUID for a given partition, do the following:
Now, wherever you would see “/dev/sdj1”, you put UUID=.
Open the Crypto Block Device
Type the following command create a mapping:
# cryptsetup luksOpen /dev/sdj1 crypt_drobo2-5
Or, using UUID:
# cryptsetup luksOpen UUID=85af2419-bde3-49e7-939a-2f231532a8b2 crypt_drobo2-5
Sample outputs:
Enter passphrase for /dev/sdj1:
You can see a mapping name /dev/mapper/crypt_drobo2-5 after successful verification of the supplied key material which was created with luksFormat command extension:
# ls -l /dev/mapper/crypt_drobo2-5
First, you need to write zeros to /dev/mapper/crypt_drobo2-5 encrypted device. This will allocate block data with zeros. This ensures that outside world will see this as random data i.e. it protect against disclosure of usage patterns:
# dd if=/dev/zero of=/dev/mapper/crypt_drobo2-5
The dd command may take many hours to complete. I suggest that you use pv command to monitor the progress:
# pv -tpreb /dev/zero | dd of=/dev/mapper/crypt_drobo2-5 bs=128M
Sample outputs:
dd: error writing '/dev/mapper/crypt_drobo2-5': No space left on device ]
200GiB 0:16:47 [ 203MiB/s] [ ]
1600+1 records in
1599+1 records out
214746267648 bytes (215 GB, 200 GiB) copied, 1008.19 s, 213 MB/s
To mount the new filesystem at /backup2, enter:
# mkdir /mnt/drobo2-5</p>
<h1>mount /dev/mapper/crypt_drobo2-5 /mnt/drobo2-5</h1>
<h1>df -H</h1>
<h1>cd /mnt/drobo2-5</h1>
<h1>ls -l
Closing the Paritition
Type the following commands:
# <a href="https://www.cyberciti.biz/faq/tag/umount-command/">umount</a> /mnt/drobo2-5</p>
<h1>cryptsetup luksClose backup2
Re-Opening the Paritition
Type the following command:
# cryptsetup luksOpen /dev/sdj1 backup2</p>
<h1>mount /dev/mapper/crypt_drobo2-5 /mnt/drobo2-5</h1>
<h1>df -H</h1>
<h1>mount
Sample outputs:
Fig.01: Encrypted partition mounted on /mnt/drobo2-5
Type the following command
### see key slots, max -8 i.e. max 8 passwords can be setup for each device ####</p>
<h1>cryptsetup luksDump /dev/sdj1</h1>
<h1>cryptsetup luksAddKey /dev/sdj1
Enter any passphrase:
Enter new passphrase for key slot:
Verify passphrase:
Remove or delete the old password:
# cryptsetup luksRemoveKey /dev/sdj1
Please note that you need to enter the old password / passphrase.
Using the Encrypted Partition
This article outlines how to run fsck on an encrypted partition.
Yeah, in the machinations around my phones and breaking them, I wanted to put a few files onto the microSD card from the phone. I put it into an adapter, put the adapter into my computer, and… filesystem exFAT not recognized?!? Hmm.
I guess when I loaded up Ubuntu 18.04 onto this computer, I didn’t put the exFAT utilities onto it.
It turns out that it’s as simple as:
sudo su -
apt-get update
apt-get install exfat-utils
Of course, you need to enter the root password when you “sudo su”.
It has been a minor annoyance for as long as I can remember. When using LINUX, or more specifically X-Windows on LINUX, clicking the mouse centre button would perform a paste wherever the cursor is right now. When you are using the scroll-wheel to scroll through a document, often the centre button has a hair-trigger and will perform a click (and therefore a paste) without even realizing it – and, boom! You have random (well, arbitrary) text in the middle of your document. Fortunately, if it’s source code, the compile or execution will almost always fail… so you can fix it. But, when it’s a word processing document… ugh!Continue reading “Centre Button BullSomething on LINUX”
When we last left our hero (me? ha ha), he had installed the latest LineageOS installed on my second T-Mobile Samsung S5 (actually my original from T-Mobile). I like it! It’s not quite as pretty as the stock O/S (OK OK, stock ROM), but it works well, more quickly, and, most importantly (related to the quickness), uses much, much less flash!
I had to (re-re-) install all of my programs, of course, and that was a pain. Worse, several of my “go to” programs are now “abandonware”. Sigh. Notably, “Profiles”, which gave me one-touch change of settings – I used this to turn off BlueTooth, dim display, and, in earlier phones, change volumes etc. However, in the S5, even with the T-Mobile ROM, I had to use “do not disturb” to prevent overnight E-mail & text notifications.
Anyway, so now the phone works… mostly. Still adding programs…
My use case is to leave the phone plugged in, face down, on my dresser overnight. Often my tablet is sitting there as well, plugged in, because I like to read the news, devotions, etc., before going to sleep. Well, what do you know, the tablet is large and thin, so I put it on the dresser first, then the phone on top, face down.
The past two mornings, the phone is working – I can see notifications in my web-based “WhatsApp” and text messages on “PushBullet” – but it seems to be locked up! The clock is present on a blue background in the top half of the screen, but the bottom half of the screen is black :-( It’s doing the haptic feedback on button presses (including the “soft” buttons), but won’t change screens for anything. If you plug it into the charger, the blue background changes to yellow, whoo hoo. My only recourse was to open it up, remove the battery, and reboot the phone that way.
Well, it turns out that this is a known problem with the Galaxy S5 – well, actually, a feature! Ugh. Apparently, there are some cases out there (or “covers” as they are sometimes called) that have a magnet in them, so that the S5 knows when the face is closed. It’s called a “flip flap”. Well, when the face is closed for a period of time, it automatically goes into “flipflap” mode, which shows the clock on the solid background, per above, through a lightly tinted upper window in the cover.
There were posts on this issue from a year ago – see https://www.reddit.com/r/LineageOS/comments/6yzpn1/how_to_disable_flip_flap_klte/ . Apparently there was a setting to disable this so-called “feature” (does my disdain show here?), but it would keep re-enabling itself “automagically”, as one post put it. You have to use “debloat” under “Magisk”. Now, here is a journey down a rabbit hole.
Installing Terminal Emulator
Go to the Google Play store and install Terminal Emulator from Jack Palevich. It will eventually need root privileges, but that should be automatic. Just remember that.
Installing Magisk
There are two parts of Magisk – the actual program, which you download as a ZIP archive and install in TWRP Recovery, and the manager program, which is an Android program but is no longer in the Google Play store, so you have to download the APK and sideload it. Here is the installation guide. To perform the sideload, you download the APK, find it in your file explorer and click on it to “open” it. I use ES Explorer. This will ask if you want to sideload this ultra-dangerous application, to which you reply “yes”, and there you have it!
Optionally Check for Root
The next part requires root access, so you can check for root with Root Checker from joeykrim. This may fail the first time, because Magisk has to wake up and ask to give it root privileges, which, if you want it to detect root privileges, you will say “yes” to. Ha ha.
Installing Debloater
Unfortunately, you are just getting started. You may have to reboot to get Magisk Manager fully loaded. Open Magisk Manager, click the upper left menu, go to Modules. It will be empty. OK then, click the upper left menu, go to Downloads. Install Bash for Android. Do not install Debloater from here, unless you are on Magisk 19 or later – this is a bug in my opinion. Instead, that thread leads to a scary filesharing site to get old version. Now go back to Modules, click the bottom middle plus, navigate and select the downloaded older debloat ZIP archive, and it will install.
Now, reboot.
Doing the Deed in Terminal Emulator
Once I got Terminal Emulator running, I just:
su – up to superuser mode
debloat – if it says “module not found” then you have too new of debloat, go to this thread which leads to a scary filesharing site to get old version
2 System Priv-Apps – which will list available applications to be removed / masked by debloat
25 – FlipFlap program!
exit – to get out of “su” mode
exit – to exit the terminal emulator
– now, reboot the phone to apply changes
Load Terminal Emulator again, do “su”, “debloat”, “2 System Priv-Apps” again, and “FlipFlap” is no longer in the list, yay!
Blank Terminal Emulator Screen
I goofed and uninstalled Bash for Android instead of the too-new version of Debloater. ( hint: there’s no double check, it just does it – so be careful! )
After I reinstalled Bash for Android, Terminal Emulator just gave a blank screen! I’m not sure if it was related. Maybe I hit the “X” to close a window, rather than typing “exit”.
Anyway, it did come in after some fiddling. Not sure what I did. I waited a long time, I went to “windows” and tried to add one (killed the program?), and I tried to add a window using the big plus on the screen. I uninstalled and reinstalled Terminal Emulator.
If this does happen, be patient. Click things, try things, it will eventually work.
Better, be careful when clicking things in Magisk.
Shall see if this fixes the problem. Maybe will find out tomorrow morning :-)
Well, the Galaxy S5 was a nice phone when I bought it from T-Mobile in 2015 in Phoenix. Not a fire breather, by any stretch, but nice. Unfortunately, it has become a dog. I’m not sure that it’s the phone’s fault – with Samsung and T-Mobile putting all that “crapware” on it, taking way too much memory all the time. In addition to all that, T-Mobile is responsible for the base Android firmware updates, even though I’ve moved back to Canada and put it on Bell-MTS… and they haven’t updated it in over 18 months. They probably will never update it again.
So, now I’m thinking that I’ll have to do what I did on my trusty old Motorola Atrix phone when it started to dog out some 6 years ago – root it and put Cyanogenmod, or equivalent, on it. No crapware (nothing that you can’t delete anyway), only what you want, and up-to-date Android firmware. Cyanogenmod has morphed into Lineage OS. I don’t recall what the reason for the morph was, but I think it was that the Cyanogen corporation that was formed to maintain & support the O/S, got carried away and did some dumb things (including trying to make their own phone and compete with Apple/Samsung/LG etc.). Anyway, it’s Lineage OS now.
I actually have two identical phones, so I can modify one and not take the hit when I goof up. So I’m actually changing my original T-Mobile S5 phone, and my “newer” phone still has the original firmware. Ugh, it’s so slow that I can barely use it.
The T-Mobile Galaxy S5 is model SM-G900T. Lineage calls it “klte” and firmware for it is at this location on the LineageOS website. Note that it’s not an ARM64 but just ARM architecture.
You first need to root the phone. Go to this article in Tom’s Guide which will show you how to root your phone with a program called Odin. You should be able to boot the original Android firmware, but it will moan and groan about being rooted. Good!
Next you install TeamWin Recovery Program “TWRP”, which is a middle stage loader that allows you to change OSes (“ROMs” in Android parlance). To install TWRP, you use Odin again. You power down your phone, power up with a specific key combination to get into “recovery”, and then blast the TWRP image onto the phone. See this TWRP page to get the image, which right now is version twrp-3.2.3-0-klte.img.tar .
It seemed to take forever for the phone to boot, the first time after installing Lineage, GApps and the SU Addon – but it did boot. Have patience.
Once you log in to your Google account, you can restore your applications, etc. In may case, I “restored” it from what was saved by the other phone (grin). I had to re-delete some of the crapware, but that’s a small price to pay.