Vintage Trams In Amsterdam – December, 2019
Thanks to Saint Nicholas hanging around, there's a lot to see on the rails in the main center of Amsterdam. Electrische Museumtramlijn Amsterdam is located in the south of Amsterdam, but it still connected to the cities tram lines. Good news for us as this means that, at certain times of the year, they can run their vintage stock around town! Over Christmas, this happened to be from 12-5pm on the 26-30th of December.
The vintage tram's route was from the Dam itself, anti-clockwise down Rozengracht, left at Marnixstraat, straight through to Fredriksplein, north to Rembrantplein and back to the Dam. Thanks to the winter sun, there weren't too many opportunities near the Dam to take shots, but after viewing the consist, I headed west to the first corner for a photo.
A two-car consist had already been hiding in the shadows in the Dam, waiting in the Dam for passengers to board. How can one even get a good shot of it whilst it's half in the shade? I'd thought there'd only be one set running, but it turns out that whilst one set was doing the lap, the other was paused to advertise the route and take on passengers.
Along the way to rembrantplein, the lighting was not much better...
But down in the plein itself, the sun was still trying to shine!
From there, it was home time. Fortunately, we ended up in town the next day, so I tried some full nighttime shots.
Nicely, they were running another consist... the blue and grey was a really nice combination!
And that was about it. Great to see them running and they all happened to be full when I was watching. I'll try and check out the actual museum itself this trip; there's even a model railway shop near it!
Apeldoorn Steam Trains – December, 2019
Back to Apeldoorn, but this time to check out heritage equipment operated by the De Veluwsche Stoomtrein Maatschappij. Again, it was a very early start via Amsterdam Centraal, which coincided with the Thalys arriving from Belgium.
A standard Intercity was taken through to Amersfoort with a small transfer over to another service through to Apeldoorn. It was quite the dreary day, so apologies that all shots hence-forth are in low-light... the temperature was exactly as it looks!
The picture above is the northern side of the station and via this exit you can get to the town center. I had travelled there on December 26 and there was absolutely no point of doing so... there are no boxing day sales in regional towns in Holland! After a quick lap of the town (even McDonalds was shut), I ventured to the southern side and then to the east with a plan to intersect with the branch line that the steam engine runs along. From the station it's about a 15 minute walk.
I walked from the first level-crossing down to the next along the line in search of photo opportunities. Thanks to the weather, the sun was hidden... otherwise it'd be head-on into the lens! Regardless, there wasn't much chop, apart from straight-on photos down the line, or side-on from across the canal. Instead, I wandered back to the bridge at the first crossing and lined up a 30-degree angle with the track. This also let me see the two 'white' signals down the line.. of which I hoped would indicate occupancy, or at least triggered crossings... but did neither with the up service.
I was happily kept amused by the wildlife, watching my clock as the minutes clicked past the expected times on the schedule. Running around 15 minutes late, the steamer arrived and paused at the crossing right next to me!
The train had stopped as the crossing next to me required manual activation. The guard quickly alighted, unlocked the control box and triggered the gates.
The consist then rolled on through to Apeldoorn Station. I noted the steam pipes running through the entire length of the train, presumably for heating? Once it passed and the crossing cleared I light-footed it back as well. As I was running to make it to the opposite platform, the same gas/oil train rolled through as happened last time I was in Apeldoorn. In fact, that was the only freight I saw all day... which is really only one more than I saw last time I was here!
The steamer had also already run around and was ready to roll away. After being 15 late, it still left on time! I managed to format my card before I remembered that I had a video of it on there. Whoops. After all the fun was over, I stopped for a burger at the restaurant in the station building.
And that was a day... it was cold enough to not want to hang around!
Emmerich – December, 2019
After visiting Geldermalsen, a station near the junction with the Betuworoute in the south of the Netherlands, I decided it was better to find the very start of the line. Sure, not all freights would traverse the entire length of the line, but at least some would have to enter/exit from the German side?
The line starts at Zevenaar, an eastern bordertown in the Netherlands that sits next to the locality of Emmerich in Germany. Getting here from Amsterdam was easy enough with a few transfers along the way.
Amsterdam Centraal
Whilst waiting at Amsterdam Centraal, the usual thing happened whilst waiting for my train; a freight passed when I wasn't ready. Even funnier that it happened whilst I was discussing the fact that there's lots of freight movements in europe, just not when you want them. It was also too bloody dark to take good photos.
From here, an Intercity was caught to Arhnem Centraal. It was still dark for the entire trip, with the sun rising at around 0830 each morning in Europe in winter. Something I'm still really not fond of!
At Arnhem I needed to change to a private railway. Thanks to the great setup of the ticket machines, this is a very easy task. Below you'll see the ticket validators for both companies sitting side by side. All one has to do is touch off NS and touch on Arriva to transfer. No need to exit the station!
Before long the diesel was humming away and we were heading east to Zevenaar. The transfer here wasn't as easy as above as we were now crossing international borders. Of course, being all in fhe EU, there's no actual passport checkpoints... You could just walk across if you wanted to. I did need a new ticket, and this was as simple as going to the DB machine. Emmmerich-Elten wasnt a valid destination, for an unknown reason, so I chose Emmerich itself.
I then had to turn around and bolt to the train as it had just arrived from Arhnem Centraal, ready to take everyone into the Motherland.
Emmerich
There's a big bi-directional staging yard here where freights lay over before or after crossing the border. A lot of westbound freights lined up side by side whilst I was loitering, with the german drivers alighting to let another pair take the consists further on their journeys.
From the platform, the views are therefore very side-on and, honestly, not the best. Every so often a freight would pass straight through, but most of them went into the yard. Fortunately, there was a diesel shunter that had just detached three tankers from a freight for delivery to a local factory. I'd arrived just in time to see it run around and take the tank wagons away.
Something interesting then happened. A coal (or possibly any other commodity) train passed through... though the carriages were eerily familiar. Was this the same train that passed me in Amsterdam Centraal? It sure looked like it... but there's probably 100 trains of similar consist on the line at any point.
A few ICE trains passed through...
And then I was off to the next destionation.
Emmmerich-Elten
My initial plan had always been to get to this station as it looked, from google maps, that all trains would pass through at full speed. I caught the next train westbound to check it out.
Unfortunately, the sun chose to come out just as I arrived, so all the westbound freights were hard to photograph. There were also no eastbound freights to be seen, so I quickly headed off into the small town for coffee and a pitstop.
Praest
With the initial plan not proving effective, and seeing that all the westbound freights were coming through to the yard, I chose then to head east past Emmerich to Praest.
It didn't take long for freights to pass... but I must say there weren't as many as earlier in the morning.
This little side-platform station was beautiful. Lush foliage with tiny platforms and a level crossing to provide forewarning of impending trains. Actually, on that note, there were three level crossings in the vacinity and they all went down at the same time... At least 8 minutes prior to a train coming! I'm not exaggerating... 8 bloody minutes!
After an hour, I was ready to head back to the big smoke, but I hadnte realised the obtuse timing of the crossing gates and ended up stuck on the wrong platform right when my train passed through! Blessing in disguise though... The shunter I'd observed earlier came in the opposite direction and in perfect light.
With the light coming from the south, I chose the southern platform where possible to take photos, but the trains all seemed to be coming from the west, meaning they were easily too close to the lens.
Thanks to missing my train, I had another hour to kill. Pretty easy though as there were numerous freighters and ICE trains passing.
Geldermalsen – December, 2019
The goal was to get close enough to a junction with the Betuweroute, the freight-exclusive railway line, of which I can't even pronounce, that links Germany to Rotterdam. Trains either run the entire length of the railway to get to the port of Rotterdam, or they exit at one of the many off-ramps onto the standard NS railway lines. Geldermalsen is one of these locations as there's a junction with the Betuweroute just a few KM south.
's Hertogenbosch
Geldermalsen is a lesser station and therefore only served by NS' Sprinter services. You can get there from either side: Utrecht or 's Hertogenbosch, another name I can't pronounce. I chose the latter.
The main station building is quite beautiful. There's a ton of throw-backs to the previous factories, or maybe even the previous station structure; I couldn't quite tell. There was also a cute little shunter on the side, not doing much at all... I wonder if the station receives freight? I didn't hang around for long though as the connections were great to get the next northbound sprinter.
Geldermalsen
This station is pretty much in the middle of nowhere, but still well patronised and very clean and tidy. There's a really handy pedestrian bridge crossing all lines, but with caternary, it doesn't provide the best view. The station has three roads per direction, including a passing lane as the final road. Further below you'll see how the freight use it to let the express trains pass.
The express trains didn't stop. They are scheduled with 30-minute head-ways and so there's always one heading in one direction or the other.
It wasn't long before a southbound freight train arrived. The consist entered the far road and held back until another express passed through.
There's actually a branch line to the north-west that heads off to Dordrecht. This is run by smaller consists that park at the far end of platform 1. There's then a crossover that allows the sprinters to use the same platform, but at a more convenient location, closer to the station building.
Another freight train passed through northbound, but also had to wait in the third road for a northbound express.
Culemborg
It was bloody cold, so I jumped on the next northbound sprinter; there was family to enjoy out the front o' the Rijksmuseum ... ice skating! I had a few more minutes and so I stopped at the next station north for a quick hop. Culemborg is a stanfard country station with no passing lanes. It has a great waiting room, of which I failed to take a photo.
The expresses bolt through, but there was no time to hang for a sprinter after the one that was approaching; I jumped on and returned to Amsterdam.
Check that last shot out! Google translate app working real-time translating whatever my phone camera was pointed at!
Amersfoort and Apeldoorn – December, 2019
It was time for another family adventure to Amsterdam, but this time for Christmas! I was just about ready for Australian summer before being shipped over into the depths of winter in The Netherlands. Lots of research had been completed: I'd found sites for photography spots, live maps of the passenger services and freight timetables. Unfortunately, the latter has been taken down in the last 4 days... can you believe it? Milliseconds before I travelled to the country, the information disappears! Despite this disappointment, I decided to trek out to the regional towns to hunt down freight.
The trip started from Amsterdam North and, thanks to the now-open Northern Metro, it was a quick 5 minutes to get there. I was then very happy to realise that my services would all be run by Koplopers (my total favourite)!
That last shot is cute... the poor onboard information system had a disk failure and tried to boot... the weirdest part being that the display starts off upside-down!?
Amersfoort
This town is located south-east of Amsterdam and, via the intercity services, only about 40 minutes away. From the station, there's a large yard to the north-west which hosts infrastructure testing equipment, a train simulator for NS staff, and the shunting yard for the automotive branch. The automotive branch runs south-east from the station, down to a few factories that process half-completed cars from Germany. The goal was to exit the station and loiter near the start of the branch, near a level-crossing, to try and see something run through. Fortunately, as I was approaching the station, a small diesel was already at the head of a long rake of new cars, ready to be taken down the line.
But first... the station... some Koplopers... and the yard...
That shot above was a total accident... turns out you can shoot and focus at the same time and get some pretty weird effects. The top was the only one of 5 that turned out, not that I was even trying. It was a total mistake.
So many Koplopers, but then the yard... at the eastern end there's a stash of vintage locomotives in various liveries and owned by various companies?
One thing I need to admit is that the lighting at this time of year in the northern hemisphere is dismal! I mean, it's beautiful for an hour or so when it's 'dusk', but that's 3pm! The sun is gone by 4!
Wandering to the end of platform 5, I could see the car-carrier rolling towards the station. I then consulted maps and realised that it'd been shunting backwards, whilst I was arriving, to access platform 2/3, which would then let it take the branch. I was obviously in the wrong spot, at the wrong time, and therefore bolted to platform 2. Fortunately, Amersfoort is beautifully designed and there's stairs at both ends of the platform. Before-long, it rolled through...
Quite a few Porsches on the end! An Intercity was coming in through next, bound for Berlin, and so I exited the station to get a shot of it along the line to the east.
The NS loco-hauled services look very similar to the older SNCF locomotives in France. I wonder if they're the same manufacturer? I'll do a little research when I'm sleeping on a french train later in the month. From here, I returned to the station to check out what else was going on. Prior to actually entering, you have to cross the branch where the auto-train went...
The line looks like it's heavy-railed, but the smaller diesel indicates otherwise... then again, they might just be saving fuel. Out the very front of the station, there's a strange clock that seems to be offsetting the current datetime for something relevant to somewhere else?
And one can't forget an obligatory front-on shot of the station.
From here, things got awkward. I had time and decided I'd do a round-trip to Apeldoorn and so I approached platform 2 once more. From the yard, an oil train approached without warning. Fortunately, my camera was at the ready... but after the second shot, my camera seemed to stick in half-focused mode. I turned it off and on again, but as soon as it powered up, it did a focus, as if I had my finger on the trigger, half-pressed.
And so, the train passed... and I had a dead camera. I jumped on the east-bound service to Apeldoorn.
Apeldoorn
As we rolled away, I sat down and started pulling apart my camera. After having the lens, battery and memory card out, the camera finally snapped back into action. Funnily enough, as we were heading towards Apeldoorn, the conductor came over the PA and mentioned something sounding like an apology... this, of course, is a guess as my Dutch is non-existent. The guess was that we were dawdling thanks to the freighter taking its time in front of us. Soon enough, we were accelerating again and I saw a glimpse of a train in out the right windows. Turns out there's a passing loop near Stroe and the freighter had been put away... I'd get to see it again in Apeldoorn?
Amazing. Another fluke... playing leap-frog with freight trains in the middle of The Netherlands. Apeldoorn is also a branch for the Arriva Zutphen line. This is run by two-car diesels which are remarkably similar, in layout, to DERMs from the Victorian railways.
Anyway... after that mild success, I headed back to Amersfoort. I'd bought an initial return ticket from Amsterdam to Amersfoort and then another return from Amersfoort to Apeldoorn, so I had to exit the station to keep all my tickets valid. Another point, I took a loco-hauled intercity like the one above. Even though these are ICE carriages from DB, it seems that they operate as standard intercity services once in The Netherlands. Although I didn't actually get my ticket checked.
Back at Amersfoort
Whilst coming back in, I saw something interesting. Past a building, down near the auto branch, was a stuffed-and-mounted wooden EMU? The building seems to be an old station building, is a Gibson store/function-center, and the loco must be from Austria or Switzerland?
Very nice actually, but the sun was already ready to set. Actually, the EMU reminded me of the railway line from Palma to Port De Soller.
I then did a lap of the whole station and wandered over to the northern side of the yard. There's a building that used to be carriageworks that I thought was a bar, but turned out to just be a function center. One good thing was that it allowed access to the other side of the stored locomotives...
Finally, on the way back into Amersfoort station, I stumbled past a beautiful piece of stained-glass art. That's totally a Koploper!
And that was then it for the day... jetlag was kicking in and although a nice beer was had at De Wagenmeester on the southern side, including a great view of the tracks!,...
It was home time.
More (Bit)Bang For Your Buck
Arduinos are a great hobbyist platform for projects, but you'll often encounter speed and memory issues when you try to squeeze in too many features. Fortunately, being an open source system, one can easily bypass the standard libraries and push these little units as hard as possible. Of course, once you're past the warranties and disclaimers, you're out on your own.
I've previously had to jump out of the IDE and use 'faster' code whilst using a Dreamcast Controller for my Model Railway, but that was a little more advanced than what we're about to do here. Today's goal is to bitbang the data stream to the Red LED Sign as quickly as possible. If you check that post, you'll see I'm already using digitalWriteFast, but it seems that we can go even quicker with the SPI library.
What's SPI, you ask? To steal from Arduino: Serial Peripheral Interface (SPI) is a synchronous serial data protocol used by microcontrollers for communicating with one or more peripheral devices quickly over short distances. As it turns out, that's exactly what we want to do! We want the display to be a slave and, well, we're just going to hack the right RX/TX lines together to get the data to flow. A fellow Arduino-er has done this here whilst interfacing with a 74HC595 and provides a few hints as to problems we might run in to.
Actually, now that I think of it, the data is to be sent out in bytes, but my screen is no where near the order of a clean multiplication of bytes... will have to work on that. Also... the data I read is the top 'line' of each letter, it's not a clean char in any memory array. Hmmm...
Test Patterns
Without pulling apart my currently-hacked-together-and-functional-display, I used another Uno I had lying around and hooked it up to the two spare LED panels. The whole display came with 5 panels, but I wasn't able to smoothly run them all at once, so these two were not in use.
The hook-up is pretty straight forward. The Wikipedia article on SPI gives a little more information on the bus and, more specifically, dictates which wires we need to use. We'll need the data output wire and the clock, known as MOSI, or Master Out-Slave In, and SCLK, the serial clock. Note that some devices read the data pin when the clock transitions from LOW to HIGH, and some vice-versa. Fortunately, this can all be configured via the SPI library.
With the following code, I managed to get a test pattern on the display...
#include <SPI.h> void setup() { for (int p = 0; p <= 12; p++) { pinMode(p, OUTPUT); digitalWrite(p, LOW); } SPI.begin(); SPI.setDataMode(SPI_MODE2); SPI.setBitOrder(LSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV128); } bool isEvenRow = false; void loop() { isEvenRow = false; for (int row = 0; row < 7; row++) { if (isEvenRow) { SPI.transfer(B01010101); SPI.transfer(B01010101); SPI.transfer(B01010101); SPI.transfer(B01010101); } else { SPI.transfer(B10101010); SPI.transfer(B10101010); SPI.transfer(B10101010); SPI.transfer(B10101010); } digitalWrite(row + 3, HIGH); delayMicroseconds(150); digitalWrite(row + 3, LOW); isEvenRow = !isEvenRow; } }
Well, neat! It worked? Playing with the SPI_CLOCK_DIv variable made the loop even quicker, down to the point where I had to up the delay at the end so that the LEDs would illuminate for a longer timeframe. SPI_CLOCK_DIV determines the speed at which the data is sent out, dividing the main 16mhz clock on the Arduino. DIV2 halves it (8mhz), DIV4 = 1/4 = 4mhz, DIV8 = 1/8 = 2mhz, DIV16 = 1/16 = 1mhz, etc...
Anyway, the test pattern was great... but I needed to output text, in rows of bytes!?
What About Text?
So this is where it gets interesting. My font array has 7 rows of 5 pixels per font. I then have up to 6 characters per LED display unit, equalling a total of 30 bits each. Of course, there's 8 bits to a byte, so the neatly rounded-up value would be 32 bits. I could therefore send out 4 bytes to get each row transmitted over the wire. But do be warned, if you start the data on the first bit out, it'll 'run off the edge' of the screen as the screen is only 30 bits wide...
Bytes | 00 | 01 | 02 | 03 | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pixels | -- | -- | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Row 0 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 1 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 2 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 3 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 4 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 5 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 6 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
As you can see, the text to be displayed is "TEST!" in the cute Arduino 5x7 font. Our display is 30 bits wide, so there's two columns of blank to the left. After the 2 pixel offset, we start throwing out each row of data. This won't be overly-easy either, as we need to get the rows of bits per character per font.
So, based on the current pixel to be pushed out, firstly get the character from the string array. We then need to look up the font pixel array for this character, only getting the relevant row that we're trying to display. Once we have that, we can then just send the bits out to a temporary array of bytes. As that we'll only have 5 bits per font line row, we'll actually need to merge multiple characters into each byte of the array. If we then want to scroll text, we'll have to put a little more effort in. This isn't overly different from when we were sending the data out bit-by-bit; we're just stuffing it into bytes now, making sure we get everything into the correct byte in the output array.
I jammed this calculation into one row below and the output was actually quite surprising! Mainly in the fact that it actually worked!
#include <SPI.h> #include "myfont.h" String serialString = "TEST! "; const int BUFFER_LENGTH = 4; const int DISPLAY_LENGTH = 30; byte* datarow = new byte[BUFFER_LENGTH]; void setup() { for (int p = 0; p <= 12; p++) { pinMode(p, OUTPUT); digitalWrite(p, LOW); } SPI.begin(); SPI.setDataMode(SPI_MODE2); SPI.setBitOrder(LSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV128); } void loop() { for (int row = 0; row < 7; row++) { for (int col = 0; col < DISPLAY_LENGTH; col++) { datarow[((col + ((BUFFER_LENGTH * 8) - DISPLAY_LENGTH)) / 8)] |= ((font[serialString[col / 5]][col % 5] & (1 << (6 - row))) ? 1 : 0) << ((col) % 8); } for (int col = 0; col < BUFFER_LENGTH; col++) SPI.transfer(datarow[col]); digitalWrite(row + 3, HIGH); delayMicroseconds(500); digitalWrite(row + 3, LOW); } }
Everything was actually working nicely... but then I tried to extend it out to 4 displays. 120 pixels out, even at the fastest SPI, caused severe flickering. It dawned on me that I was doing some pretty hefty lifting in my display loop, presumably slowing down the drawing. It's actually a critical loop and it seems that any processing will slow down the line drawing, causing the flickering I was seeing.
With this problem in mind, it became apparent that I'd need to move the 'buffer' calculation out of the main loop. I quickly tested this theory by moving it to a once-off in the setup procedure.
#include <SPI.h> #include "myfont.h" String serialString = "TEST! "; const int BUFFER_LENGTH = 4; const int DISPLAY_LENGTH = 30; const int DISPLAY_HEIGHT = 7; byte* datarow = new byte[DISPLAY_HEIGHT][BUFFER_LENGTH]; void setup() { for (int p = 0; p <= 12; p++) { pinMode(p, OUTPUT); digitalWrite(p, LOW); } SPI.begin(); SPI.setDataMode(SPI_MODE2); SPI.setBitOrder(LSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV128); for (int row = 0; row < DISPLAY_HEIGHT; row++) { for (int col = 0; col < DISPLAY_LENGTH; col++) { datarow[row][((col + ((BUFFER_LENGTH * 8) - DISPLAY_LENGTH)) / 8)] |= ((font[serialString[col / 5]][col % 5] & (1 << ((DISPLAY_HEIGHT - 1) - row))) ? 1 : 0) << ((col) % 8); } } } void loop() { for (int row = 0; row < DISPLAY_HEIGHT; row++) { for (int col = 0; col < BUFFER_LENGTH; col++) SPI.transfer(datarow[row][col]); digitalWrite(row + 3, HIGH); delayMicroseconds(500); digitalWrite(row + 3, LOW); } }
My text was static, so I now only built the buffer once. Would you believe it? The rendering was beautiful! I couldn't even notice a flicker. This was then short-lived when I realised I needed to scroll text. Again, there was no need to rebuild the buffer on each loop, so I wrote a procedure and only called it when there was text to move; defined by a delayed scroll rate.
This worked nicely, but there was an obvious jarring delay as the text shifted along. You could feel it re-processing in-between the character movements. I was still happy with it! I went ahead and reconfigured my initial display setup to use two Arduinos: one for the remocon+wifi and the other to receive serial data and light this dispalay.
But Wait, Why Don't I Build The Entire Text Buffer?
Whilst writing this up, it occurred to me that I was only rebuilding the buffer for the text that was visible on the display, not the entire string. If I had the full text buffer already created somewhere in memory, I then would only need to get SPI to send out the data from a specific bit offset. But, how do you start sending data via SPI from half-way through a byte? My fonts/characters were 5 bits wide and each byte had 1.5ish characters in them! I considered just shifting the pointer along the bytes, but the scrolling would be horrible.
So, I want to send out data from half way through a byte? Let's just borrow that table from above again:
Bytes | 00 | 01 | 02 | 03 | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pixels | -- | -- | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Row 0 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 1 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 2 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 3 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 4 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 5 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 6 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Officially, on the 3rd loop of scrolling, I would want to send out the green bits. I'd also then want to send out five zeroes at the other end so that the scrolling text trails off and doesn't wrap around. I can't tell SPI to start at the 4th bit of a byte? So I'll need to create a new buffer and populate this as quickly as possible, line by line, just as we're about to render it. Thanks to the glory of the internet, there's always someone else who wants to do something similar. Effectively we have an array of bytes (unsigned chars, meaning we get all 8 bits) and we'll need to shift the first one and append the next one. It'll be very much like a catepillar crawling along.
So, if we build the full text buffer array when the string comes in, we can then push the relevant chunk into the display buffer. There'll be no font calculations, just bit for bit copying, and this will hopefully still be fast enough. We can even make sure we only do it line by line, and only as required... if we're on a byte boundary, then we don't need to shift anything!
I mentioned shifting above, and that's what we'll need to do to get the chunks of bytes into the final display buffer. If you look at the table above, you'll notice I coloured the cells in light green and dark green. The first 3 bits are light green and come from the 'end' of the first byte of the text buffer. The next 5 bits are from the 'start' of the second byte in the text buffer. This chain then continues on as we shift the bits. The >> and << C operators will assist us with this task as that's exactly what they're designed to do: shift bits in a variable in a certain direction by a certain amount. Let's try some pseudo-code first....
define a string buffer that has the string to display: "TEST!"; build a buffer for this string: stringBitBuffer[7][4]; define a display buffer that is enough for 1 panel: displayBuffer = byte[7][4]; if our scroll offset is a multiple of 8 foreach stringBitBuffer[scrolloffset /8] SPI.writeRow(); write extra zeroes at the end. else if our scroll offset is NOT a multiple of 8 foreach byte in stringBitBuffer starting from (scrollOffset/8) shift the first block of the text buffer right scrollOffset times make anything to the left of the text in that byte zero. move the resulting value into the current displayBuffer byte. get the next text buffer byte. take the first (8 - scrollOffset) bits and add them to the current displayBuffer byte next end if
Here's an animation showing what bits we want where...
Bytes | 00 | 01 | 02 | 03 | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pixels | -- | -- | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Row 0 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 1 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 2 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 3 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 4 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 5 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Row 6 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
TB | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 02 | 02 | 02 | 02 | 02 | 02 | 02 | 02 | 03 | 03 | 03 | 03 | 03 | 03 | 03 | 03 |
Bytes | 00 | 01 | 02 | 03 |
The table above shows the 8 frames that need to be copied over to the framebuffer between bytes. Once the data has shifted a whole byte to the left, then the drawing routine just needs to start from the next byte, there's no need to send out the initial bytes if they're offscreen. We need to copy the bits in the main area of the table into the buffer bytes listed on top. I've added a new row at the bottom which indicates the first bit/byte of the text buffer (TB). With the duplicated Byte row underneath, you can see how we'll need a part of one text buffer and the rest of the next to fill the gaps.
Back to that forum post I mentioned above, and the description I gave about bit shifting, we can build up our buffer. We need to, based on the scroll offset, take the first (8 - offset) bits from every byte and then the 'offset' bits from the following byte. i.e. if the offset was 3, we want the last 5 bits of byte 1 and the first three of byte 2. If we're on the last byte, then we just fill the end with zeroes. The AND masks for row 2 are shown below. Row 1 is nearly all bits set... so wouldn't be a good test case.
Bytes | 00 | 01 | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pixels | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 |
Row 0 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 | 01 |
Row 1 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 01 | 00 | 00 | 00 | 00 | 01 | 00 | 00 | 00 |
AND #1 | 00 | 00 | 00 | 01 | 01 | 01 | 01 | 01 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
AND #2 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 01 | 01 | 01 | 00 | 00 | 00 | 00 | 00 |
And then we just need to shift the first 5 bits to the left... as we're stored in MSB.
#include <SPI.h> #include "myfont.h" int ccol = 0; byte datarow[7][60]; int displayLength = 120; int bytesRequired = (displayLength + (displayLength % 8) + 8) / 8; int totalPixels = bytesRequired * 8; byte frameBuffer[7][16]; unsigned long last_time = millis(); unsigned long scroll_time = millis(); unsigned long scroll_pause = millis(); unsigned long next_text_time = millis(); int charOffset = 0, loop_count = 0, charcol = 0; String displayString; void buildTextBuffer() { bytesRequired = ((displayString.length() * 5) / 8) + ((displayString.length() * 5) % 8 == 0 ? 0 : 1); totalPixels = bytesRequired * 8; for (int row = 0; row < 7; row++) { for (int b = 0; b < bytesRequired; b++) datarow[row][b] = 0; for (int col = 0; col < totalPixels; col++) { ccol = col + 8; datarow[row][ccol / 8] |= ((font[displayString[col / 5]][col % 5] & (1 << (6 - row))) ? 1 : 0) << (ccol % 8); } } } void buildFrameBuffer(int offset = 0) { int tOffset = offset / 8; int cOffset = offset % 8; int bb = 0; for (int row = 0; row < 7; row++) { for (int b = 0; b < 16; b++) { bb = b+tOffset; frameBuffer[row][b] = (datarow[row][bb] >> cOffset); if (cOffset > 0) { if (bb <= bytesRequired) { frameBuffer[row][b] |= (datarow[row][bb + 1] << (8 - cOffset)); } } } } } void setup() { Serial.begin(9600); for (int p = 0; p <= 12; p++) { pinMode(p, OUTPUT); digitalWrite(p, LOW); } SPI.begin(); SPI.setDataMode(SPI_MODE3); SPI.setBitOrder(LSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV64); displayString = "Warming up ... "; buildTextBuffer(); buildFrameBuffer(0); } String newString = ""; void getNextIndex() { for (int i = 0; i < 7; i++) for (int x = 0; x < 60; x++) datarow[i][x] = 0; if (Serial.available()) { newString = Serial.readStringUntil('\n'); if (!newString.equals(displayString)) { displayString = newString; next_text_time = millis(); } } else { displayString = ""; } if (displayString.length() > 0) buildTextBuffer(); buildFrameBuffer(0); } void loop() { for (int row = 0; row < 7; row++) { for (int col = 0; col < 16; col++) SPI.transfer(frameBuffer[row][col]); digitalWrite(row + 3, HIGH); delayMicroseconds(150); digitalWrite(row + 3, LOW); } if ((millis() - scroll_pause > 2500)) { if ((displayString.length() > (displayLength / 5))) { if (millis() - scroll_time > 30) { charOffset++; if ((charOffset / 5) > displayString.length()) { charOffset = 0; loop_count++; scroll_pause = millis(); if (millis() - next_text_time > 5000) getNextIndex(); } scroll_time = millis(); buildFrameBuffer(charOffset); } } else { loop_count++; if (millis() - next_text_time > 5000) { getNextIndex(); loop_count = 0; } charOffset = 0; } } }
And it all actually worked... beautiful scrolling.
Further Possible Optimisations
We're sending the text into this Arduino from another Arduino over SoftSerial. Why send text when I could send the framebuffer bits instead? I wonder if sending (string-length * 5 * 7 * 8) bits would be quicker than the initial string and calculations on the secondary Arduino's side? Should I test it? Maybe later...
Atari Mouse – Replacing Microswitches
I'd recently acquired an Atari 520STFM (more about that later) and the package came with two mice. One is actually optical and has a switch that allows it to also be used on an Amiga, but it really hated the kitchen bench. I do remember, back in the day, that first-generation optical mice couldn't deal with glossy single-coloured surfaces. The other mouse was the original Atari 2-Button Ball-Mouse. It tracked nicely, but the buttons were as soggy as a wet week?
Open 'Er Up
Very easy to do... two screws up the top-end where the cable enters. Lift at the front, up near the buttons and pull forward to clear the latch at the bottom end.
From here, the microswitches are your PCB-mounted standard, easily available from Jaycar. I toddled off and purchased some of the exact-same replacements, but also two slightly-taller switches. The latter had a much clickier click. The standard replacements were also clickier than the existing switches, but I wanted moar.
A quick look at the circuit board underneath saw that only one side of both microswitches was in use. You can see, per four-hole-mounting of each switch at the top left and top-right of the board, that only the bottom left and bottom right pins are used respectively. The top two are bridged, but the 4th pin on each goes nowhere.
Just because things change over the decades, I quickly checked that my microswitches contacted on the same dimension...
A simple de-soldering saw the old switches out. With the solder removed from the holes in the PCB, I could trial the clearance with the taller switches...
The case was easily re-assembled to test...
But, as you can see, the case wouldn't even close. Testing the buttons, with the case half-ajar, saw them super clicky... maybe toooo-clicky. So I switched in the new same-sized switches (SP0600 from Jaycar) and these were nice!
Although they're not that clicky when they're just in your fingertips in the shop, they're great once mounted in the mouse!
Testing...
All soldered up and plugged in, it was time to test it all out. Previously I had to mash the left-mouse-button a few times to get one click, so I was happy to see that a single click working perfectly!
And yeah, straight into Railroad Tycoon... damn that Grasshopper is a slow first train!