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!
Welding Rail In South Yarra
There was a total shutdown of all lines past my apartment on the weekend of the 16th-17th November as Metro we're upgrading the signalling. It's well-needed to allow better head-ways for more services, and also to allow brand new 'high-capacity' trains to run. To do all this, they've had to cut in new isolated track joins. I assumed they'd just cut through the rails in-stu and clamp an insulated joiner over the top... but I assume the tension in the rails prevents them from doing this? Instead they've been busy removing a 20 metre length of track and welded in a new length that includes a bypassed insulated joiner!
It's bypassed as they don't want to break the track-circuit just yet. The rest of the infrastructure isn't in place yet, so a break in the circuit here would actually cause a 'blind' area on one of the sides. Hence the jumper cables. There's also a longer length of cables running down the current work area, to keep the current circuit in operation whilst the work is carried out. I don't really see the reason though, as there's a lot of protection at either end of the entire work area to prevent vehicles from entering.
The weather was reasonable, and I was half brain-dead from a cold, so I spent a good bit of time on the bridge near Cromwell Road, watching the professionals carry out their business. You could tell they'd done the job 100s of times before; their efficiency and precision was great to watch.
Step 1 - Align The Rails
The first step would probably be the most important in the whole process; misaligned rails would cause untold problems in the future and therefore a lot of time was spent getting the elevation and angle spot-on. There's a large brace/jack on the outside of the rails that was aligned first. This unit uses friction to grip all four rails and, when air pressure is applied, pneumatically draws them closer together.
The workman was constantly measuring the gap between the rails until it was within tolerance. I note that they didn't just bring them closer in one hit. The workman operating the compressor only applied pressure for short periods, maybe 3 seconds each time, and then his colleague would measure again. They'd then let the whole contraption rest for 5 seconds before applying further pressure.
I'm actually astonished that the rails even moved. It's a relatively straight section of track and where does the slack come from? I can't imagine they actually stretch the steel? They had removed around 6 rows of clips from the original rails to allow freedom of movement. I'd say the gap was about 40mm to start with, but they brought it right down to less than 20mm!
Once it was as close as required, the rails were further adjusted using (what looked like) rail spikes between the closest sleepers and the base of the rail. These were hammered in to raise or lower the rails. The worksman spent around 5 minutes doing this, making sure everything was totally level. The ruler was around a metre long, allowing him to see how much the rails tapered off on either side. You could tell he did not want to create any kind of gradient on either side of the join.
Step 2 - Build The Mould
From what I could see, the mould was made of some form of terracotta? The colour was the standard red, but it really could be made of any sort of compound. The base was removed from the packaging first and a layer of glue applied down each long edge. This was then smoothed around the edge and along the ridge to make sure that there'd be no gaps once joined. This base was then placed into the metal tray which would soon form the case that holds the mould together.
The whole base was then placed under the gap between the rails. It was held in place with clamps and, once again, adjusted once and again to make sure that it was completely square and level with the join.
The sides of the mould were then taken out of the pack. These were placed in the 'sides' of the metal case and both units were then assembled onto the rail. From here, the mould was complete, with an opening at the top where molten metal would be poured in? You could see that, at the top of the mould there was one short edge that was lower than the others; presumably this was for excess molten metal to flow over.
Once this was done, the final outer frame was dropped on and the 'drip tray' inserted on the side that had the overflow 'spout'.
From here, a large block of malleable clay (or other playdough-esque material) was split up and used to totally fill all gaps around the edges of the case and mould. The goal was to ensure that all heat, once the mould was full, was to stay trapped inside.
The entire block was applied to all facades of the mould, specifically where the case and the mould met.
Step 3 - Add Fire!
A cute little terracotta (or other substance) pot had been sitting on the back of the work truck for a while, but it was now its time for action.
To 'set the mould', a small mount was clamped to the rail and then the oxy-acetylene torch was flamed up and positioned on it, aiming the fire directly into the mould. The flame from the torch burned a strong blue, but eventually yellow flames began rising from the openings on either side of the torch. One of the worksman then grabbed the terracotta pot whilst the other pulled the torch out of the mould. Without being able to see inside the mould, one couldn't really work out what exactly was being heated, but you'd have to assume the rail ends were red-hot by this point!
The pot was placed directly on top of the whole mould and the torch was placed in the small opening on top.
I'll let the following video explain the rest...
A lot of trust placed in a set of serious gloves!
Step 4 - Clean Up
From here, there was a 10 minute break whilst the burning-box-of-death cooled down. The overflow tray on the side was thrown track-side once it could be lifted.
After a while, the outside metal case was unscrewed and whacked a few times until it fell loose. Now the mould was perfectly visible, and perfectly-solidly-formed as a single unit, welded to the rails!
To get this off, another utensil was used. The workmen brought over another flat-frame-style machine and placed it over the mould. With a few clamps, they secured it on all corners to the rail and then one of the guys started pumping a lever handle. I couldn't quite see what the action resulted in from where I was, but I assume there was a flat blade that was slowly, flush with the railhead, cutting into the mould on a horizontal plane.
The other worksmen started beating the mould with a mallet when the cutter wasn't being used. After a lot of intimidation, the mould finally started budging... but instead of a clean splice, it decided to split to pieces... pieces of 1000 degree red-hot danger.
More time was spent letting the bits cool down and then it was all moved to the rubbish pile track-side.
From here, a portable grinder-on-wheels was used to finally trim down the weld. Once complete, it finally resembled rail!
I'd actually watched the 2nd of 4 joins be welded before I had decided to get my camera and record the one above. This part, the removing of the mould, was much smoother on that one; a single knock after a slight clamp saw the whole lot just break free in one hit. There was no grinding required afterwards either! I hadn't really noticed anything done differently between each session, so I wonder how many variables come into play when it comes to doing this and how easy it is to stuff up!?
Gantry Foundations
Whilst all the welding was taking place, another vehicle had been busy drilling a column on the other side of the track. The colour of the earth was quite interesting, being somewhere between clay and red earth? A re-bar metal frame was then built up and inserted, with the square frame of pre-built bolt rigging for the base of a pylon. The alignment of this frame in the hole was actually a big thing and watching the surveyor get it correct was pretty interesting.
Turns out there was a remote surveyors camera sitting half way down the track, fixed on the location of the pylon.
And, with a remote mirror, with a very fine tip, the surveyor measured each corner of the frame. The workers around nailed, cut and hammered the external wooden frame to get the metal frame in the exactly correct position.
The surveyor was holding a handheld computer that was relaying the stats from the surveying equipment.
Good to see technology helping all departments.
Tamping And Cleaning
Whilst everything else was going on, there were also tampers and ballast cleaners working away. Turns out they were tidying up a cut-in insulated joint that had been installed the night before.
The ballast cleaner sounded like it was in pain, chewing rocks up and spitting out a lot of dust.
After all of the above, they managed to clean up and the trains were running again the next morning.
Arduino Uno WiFi + TV Remote + LED Sign
Over the weekend, whilst watching TV, I realised that I needed some kind of sign to work out when trains were passing my apartment. There's a railway line right next to me and around four-or-more services pass daily. I'd prefer to be notified in advance and, since I have data sources to tell me when trains are about to come by, I want to make the most of it. Currently that's done by RSS both on my phone and PC, but the whole point is that I'm watching TV and don't want to have to be checking my phone every 2 seconds.
After getting the red LED marquee to work on the old ISA I/O card, I decided it was time to get it plugged into the Arduino. I actually already have an Arduino Uno Wifi next to my TV, which I've recently been using to translate my TV remote control codes (similar to what I did with the old B&O TV) into Yamaha remote control codes for control of volume with one remote. Adding the LED marquee would let me know when a button was successfully pressed ... and it would also tell me when trains were coming past!
I hadn't actually worked with the WiFi on the Arduino yet, so that was a bit of an experience. The RF side of things was working fine and I had already worked out the wiring requirements for the LED display thanks to the old I/O card.
Getting the ESP8266 Online
The Arduino Uno Wifi is an Arduino Uno board with an embedded ESP8299 microcontroller. The basic idea is that the two units talk serial to each other, when configured to do so. If you want to program one, or the other, you have to adjust the serial communications path on the board via a tiny row of switches. Jaycar has a minimal instruction sheet here, but it's enough to get you started. From the information, please set your Arduino Uno Wifi's tiny switch row to: 1:OFF,2:OFF,3:OFF,4:OFF,5:ON,6:ON,7:ON,8:OFF.
I would want this unit to act by itself as both a web server and web requestor, so I needed to find code for both. Before this we need to set up the Arduino IDE for use with the ESP8266. I found a great tutorial here, but otherwise, the steps are pretty simple. Open the Arduino IDE, go to Preferences and then paste this URL into to the additional boards list: http://arduino.esp8266.com/stable/package_esp8266com_index.json.
Once done, go to the board manager and search for ESP8266...
Install the relevant row and then restart the Arduino IDE. From here, you should have a new selection list in the boards drop-down...
...and then, once selected, a hideous amount of new options to adjust the programming!
The main advice is to not touch any of them, apart from the serial port. You'll need to work out which one your Arduino is connected to, but depending on if you even had serial ports in the first place, it should be pretty obvious. I'm working on a Mac Mini and my Arduino was connected via cu.usbserial-1460. From here, open the Serial Monitor and hit reset on the Arduino.
What garbage is that? I then started from the slowest and tested each baud... turns out if you set to 74880 then you'll get the following...
What a weird port number? It seems that just the initial header information is at that BAUD rate. Afterwards, it'll be at whatever you've set in the code you upload.
Running A Web Server
So, where were we? We have a connected device, so let's grab the first example from here and compile it. After installing the ESP8266 libraries, this code should compile with zero issues. If you happen to have any, then write a comment below, providing as much detail as possible. If it's worked, then check the code and adjust the Wifi SSID and password to match your local AP. Hit upload and cross your fingers and toes... hopefully you'll get the following output:
Yes! Now, you need to switch OFF switch 7 on the little switch header on the Arduino. This pin enables the programming mode and we now want it in the run mode. Note also that we've now set the baud to 115200, so change your serial monitor to that too... Do you get the following after hitting the reset key on the Arduino?
Associated! And with a nice IP! So what does the web browser say?
WIN!
HTTPS Web Requests with the ESP8266
The basic ESP8266 examples use WiFiClient, but this only works with HTTP requests. My server runs over HTTPS, so we need to use WifiSecureClient for secure web requests. To call an HTTPS service, you'll need to get the fingerprint from the SSL certificate. Browse to the site in a regular web browser and check out the properties of the lock next to the URL in the address bar. Somewhere in there you'll find the required data.
And from there, edit the WifiClient Example to carry out an SSL request using these instructions:
#include <esp8266wifi .h> const char* ssid = "********"; const char* password = "********"; const char* host = "api.github.com"; const char* fingerprint = "CF 05 98 89 CA FF 8E D8 5E 5C E0 C2 E4 F7 E6 C3 C7 50 DD 5C"; void setup() { Serial.begin(115200); Serial.println(); Serial.printf("Connecting to %s ", ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" connected"); } void loop() { WiFiClientSecure client; Serial.print("[connecting to "); Serial.println(host); if (client.connect(host, 443)) { Serial.println("connected]"); if (client.verify(fingerprint, host)) { Serial.println("[certificate matches]"); } else { Serial.println("[certificate doesn't match]"); } Serial.println("[Sending a request]"); client.print(String("GET /") + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n" + "\r\n"); Serial.println("[Response:]"); while (client.connected() || client.available()) { if (client.available()) { String line = client.readStringUntil('\n'); Serial.println(line); } } client.stop(); Serial.println("\n[Disconnected]"); } else { Serial.println("connection failed!]"); client.stop(); } delay(5000); }
And hopefully, you'll get something similar to the following in your serial terminal.
{ll⸮⸮|⸮d⸮|⸮d⸮b|⸮⸮⸮⸮s⸮b⸮c⸮⸮'o⸮dg'⸮⸮⸮cp⸮⸮dsdrlx⸮o⸮⸮d⸮⸮co⸮|l⸮⸮c⸮⸮go⸮d⸮⸮$`⸮'o$`gs⸮⸮⸮ob⸮$;⸮⸮gc⸮l⸮⸮dl⸮⸮d`⸮⸮'⸮ Connecting ... scandone ........scandone .scandone state: 0 -> 2 (b0) state: 2 -> 3 (0) state: 3 -> 5 (10) add 0 aid 1 cnt connected with COMEGETSOME, channel 6 dhcp client start... ip:192.168.1.121,mask:255.255.255.0,gw:192.168.1.1 Connected to COMEGETSOME IP address: 192.168.1.121 mDNS responder started connecting to api.github.com Using fingerprint '59 74 61 88 13 CA 12 34 15 4D 11 0A C1 7F E6 67 07 69 42 F5' requesting URL: /repos/esp8266/Arduino/commits/master/status request sent headers received esp8266/Arduino CI has failed reply was: ========== {"state":"pending","statuses":[],"sha":"1e17dddd895883806c9bfd3dd7b7042aa813d94f","total_count":0,"repository":{"id":32969220,"node_id":"MDEwOlJlcG9zaXRvcnkzMjk2OTIyMA==","name":"Arduino..."} ========== closing connection
Nice, we can successfully pull data from a HTTPS source. Let's rig up a polling system to get the data each minute.
Polling for Data + Extra Buttons
So, we're able to pull data from the internet, but I also want this unit to be a mini webserver. If I used Delay() to spread out the web-polling calls, then I'd halt the entire unit. We can't have it wait when it needs to be handling button presses from the web interface. Instead, we'll take a snapshot of the current internal millisecond counter using millis() and then check how many milliseconds have passed to work out if we should try and grab new data.
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WiFiMulti.h> #include <ESP8266mDNS.h> #include <ESP8266WebServer.h> // Include the WebServer library ESP8266WiFiMulti wifiMulti; // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti' ESP8266WebServer server(80); // Create a webserver object that listens for HTTP request on port 80 void handleRoot(); // function prototypes for HTTP handlers void handleNotFound(); void handleRemote(); unsigned long lastMillis; const char* host = "trainfinder.otenko.com"; const int httpsPort = 443; const char fingerprint[] PROGMEM = "BC 9C 56 D5 8E 7A FE CC 0C F7 A2 21 1F C5 7B 9A C0 FA 15 41"; String last_line = ""; void setup(void) { Serial.begin(9600); delay(100); Serial.println("\n!START"); wifiMulti.addAP("COMEGETSOME", "blahblahblah"); while (wifiMulti.run() != WL_CONNECTED) { delay(250); } Serial.println(WiFi.SSID()); Serial.println(WiFi.localIP()); server.on("/", HTTP_GET, handleRoot); server.on("/buttons", HTTP_POST, handleRemote); server.onNotFound(handleNotFound); server.begin(); lastMillis = millis(); } void loop(void){ server.handleClient(); if ((millis() - lastMillis) > (60 * 1000)) { WiFiClientSecure client; client.setFingerprint(fingerprint); if (!client.connect(host, httpsPort)) { Serial.println("!FAILED"); } else { String url = "/some/cool/url"; client.print(String("GET ") + url + " HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n"); while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") break; } bool foundLastLine = false; String line = ""; while(client.available()) { line = client.readStringUntil('\n'); if (last_line.equals("") || foundLastLine) { Serial.println(line); last_line = String(line); delay(6500); //space out the data to the Arduino } if (last_line.equals(line)) { foundLastLine = true; } } if (!foundLastLine) last_line = line; } lastMillis = millis(); } } void handleRoot() { server.send(200, "text/html", "<form action='/buttons' method='POST'>" \ "<input type='submit' name='pwr_plz' value='PWR!'/>" \ "<input type='submit' name='vol_dn' value='VOL-'/>" \ "<input type='submit' name='vol_up' value='VOL+'/>" \ "</form><br/><br/>Last Line: <pre>" + last_line + "</pre>"); } void handleRemote() { if (server.hasArg("vol_up")) { Serial.println("!VOLUP"); } else if (server.hasArg("vol_dn")) { Serial.println("!VOLDN"); } else if (server.hasArg("pwr_plz")) { Serial.println("!POWER"); } server.sendHeader("Location","/"); server.send(303); } void handleNotFound(){ server.send(404, "text/plain", "404: Not found"); }
Just for fun, here's the first shot of me writing the code. Instead of 'text/html', I was still outputting 'text/plain' and, well, as expected, got the following...
After fixing that, the screenshot below was taken. Of course, it doesn't match the code above, but you get the idea.
The basic idea is to print out the raw html to the browser. From here there's a form that has the buttons which post back to the ESP8266. The controller then sees the post, sends the command via Serial to the Arduino and then redirects the user back to the main form.
Now to get the Arduino to consume it.
ESP8266 to Arduino
We've currently had the ESP8266 piped directly out the USB Serial port. Now we want it to talk to the Arduino internally. To do this, the tiny onboard switch needs to have all switches off except for 1 and 2. This then routes the RX/TX directly from the ESP8266 to the Arduino. From here though, the unit becomes a black-box. We have no idea what the ESP8266 is saying to the Arduino, and vice-versa, as the connection via the USB serial port has been severed. We can only hope that the data we're sending through is what we want!
If you check the shots above of the data the ESP8266 sends, you can see that we want to skip a bit of the earlier stuff. To do this, I defined a "!START" command which the Arduino should look for before bothering to deal with any of the data from the serial channel. From there, anything starting with a "!" is a command, whereas anything else is to be printed to the LED Display.
Oh yeah, that TV Remote bit
I previously had an amplifier that ran over ARC over HDMI and also received controls... but half the time it'd switch on to Optical and just would not switch back to ARC without a lot of screwing around. No amount of power cycling, cable switching or button pressing would get it back. So it went to the farm. Instead, I purchased a beautiful old Yamaha amp for AUD$5 and then realised I had to get up and control it by hand. Not optimal, so I search google for the remote codes.... they didn't exist, so I search eBay for a spare remote... they didn't exist... but one guy in Spain had a company that programmed third-party remotes and had one for this amp!
It arrived, and I plugged it through the same code I used for the B&O TV. Over the serial, the codes and bits were reported, showing me that it used the "NEC" protocol. I recorded the codes for the buttons I wanted. I then did the same with my Sony TV Remote for buttons that I wanted to re-purpose. The goal was to have the TV Volume buttons also trigger the amp volume. This worked nicely, via an external IR LED that sits in front of the amp.
Of course, the TV volume still shifts, so I made the amp volume shift 4 times per button press... meaning the surround is always louder than the TV. Doesn't hurt to have a little bit of tinny sound from the TV also... a second center channel?
To make everything compile, install Ken Shirrif's IRremote library, available via the Arduino GUI...
The code is in the next segment.
Hooking up the LED Sign
I've used a font found online, but it seems that Arduino has one built-in? 5x7 font. Either way, the 3 data lines and 7 row-enable pins need to find homes in the Arduino digital IO pins. Make sure you don't use digital pin 0 or 1 as that's the serial channel to the ESP8266. I found this out later below...
Once hooked up, just use a similar loop process as per any LED display: Disable all rows, send out the columns to be lit for the first row, enable that row and quickly disable it. Then do the same for the next 6 rows. As quickly as possible! More information on lighting this sign is over here.
#include <IRremote.h> #include "myfont.h" #include "digitalWriteFast.h" int RECV_PIN = 13; IRrecv irrecv(RECV_PIN); decode_results results; IRsend irsend; void setup() { Serial.begin(9600); irrecv.enableIRIn(); // Start the receiver pinModeFast(0, OUTPUT); digitalWriteFast(0, LOW); pinModeFast(2, OUTPUT); digitalWriteFast(2, LOW); pinModeFast(12, OUTPUT); digitalWriteFast(12, LOW); //matrix led row drivers for (int p = 0; p <= 11; p++) { pinModeFast(p, OUTPUT); digitalWriteFast(p, LOW); } } unsigned long last_time = millis(); unsigned long scroll_time = millis(); unsigned long scroll_pause = millis(); bool is_off = true; int loop_count = 0; String serialString = " Warming up.."; bool found_start = false; int charOffset = 0; int ccol; void loop() { if (irrecv.decode(&results)) { loop_count = 0; switch (results.value) { case 4841: case 0xA90: irsend.sendNEC(0xDE21807F, 32); if (is_off) { serialString = " Power ON!"; is_off = false; } else { serialString = " GOOD BYE!"; is_off = true; } break; case 0x490: irsend.sendNEC(0xDE21B04F, 32); delay(50); irsend.sendNEC(0xDE21B04F, 32); delay(50); irsend.sendNEC(0xDE21B04F, 32); serialString = " VOLUME UP!"; break; case 0xC90: irsend.sendNEC(0xDE21708F, 32); delay(50); irsend.sendNEC(0xDE21708F, 32); delay(50); irsend.sendNEC(0xDE21708F, 32); serialString = " VOLUME DOWN!"; break; case 0xFFFFFFFF: irsend.sendNEC(0xFFFFFFFF, 32); break; //sony teletext red case 21225: irsend.sendNEC(3726721215, 32); serialString = " INPUT!"; //sony teletext green case 13033: irsend.sendNEC(3726717135, 32); serialString = " MODE UP!"; break; //sony teletext yellow case 29417: irsend.sendNEC(3726735495, 32); serialString = " MODE DOWN!"; break; default: Serial.println(results.value); Serial.println(results.bits); break; } delay(250); irrecv.enableIRIn(); irrecv.resume(); // Receive the next value } if (charOffset == 0 && Serial.available() && ((millis() - last_time) > 4000)) { last_time = millis(); serialString = Serial.readStringUntil('\n') + '\0'; loop_count = 0; scroll_pause = millis(); } //draw LEDs for (int row = 0; row < 7; row++) { for (int col = 0; col < 90; col++) { if (((col / 5) + charOffset) < 60) { ccol = (charOffset * 5) + col; digitalWriteFast(2, ((font[serialString[ccol / 5]][ccol % 5] & (1 << (6 - row))) > 0) ? HIGH : LOW); } else { digitalWriteFast(2, LOW); } digitalWriteFast(12, HIGH); delayMicroseconds(5); digitalWriteFast(12, LOW); } digitalWriteFast(row + 5, HIGH); delayMicroseconds(75); digitalWriteFast(row + 5, LOW); } if ((millis() - scroll_pause > 2500)) { if ((serialString.length() > 18)) { if (millis() - scroll_time > 180) { charOffset++; if (charOffset > serialString.length()) { charOffset = 0; loop_count++; scroll_pause = millis(); if (loop_count > 10) serialString = ""; } scroll_time = millis(); } } else { loop_count++; if (loop_count > 100) serialString = ""; charOffset = 0; } } }
Overloading Arduino Pins
Anyone watching above, or in the previous post where I hooked the display up to the old PC, was probably screaming in their seats. It's well known that you should NOT draw too much current through the pins on any microprocessor. As soon as I started hooking up more modules onto this unit, things started going haywire and it quickly dawned on me. My seven line-enable pins were being brought LOW to enable the rows... but these were actually working as the single GND conduit for the entire bloody LED display!
I quickly referred to the previous module that ran this sign and saw there was a 74LS145 binary counter in between the Atmel Microcontroller and the 7 enable rows. A quick review of the datasheet shows that this chip can 'sink' up to 15v and is 'great for driving LEDs'. Right... they were properly routing the GND wires away from the microcontroller and using the 145 as a set of transistors. They even saved 3 data lines in the process! I only had a ULN2003 (which would still use 7 lines) and there were no 74LS145 to be found at Jaycar!
This now directed the current away from the microcontroller... but didn't enhance the brightness.
Speeding up the Refresh Rate
It occurred to me that my refresh rates weren't anywhere near as good as the original controller that the sign contained. My loop is busy checking clocks, serial ports and mucking around, so it was never going to actually go fast enough. The code above could run a single segment OK, but started to scale worse as the bit shifting got longer. One trick, which is actually already implemented in the source above is to use an external library to make the digital pin manipulation faster.
With this implemented, there was less flickering and I found that three segments (90 LEDs per row) looked good. One final note is that you shouldn't really use pins 0 or 1 if you've got the serial enabled. They actually present the TX/RX lines and the serial data and the serial data to the Arduino is visible on them. In this case, it was the serial data to/from the ESP8266. In the case below, I had the three data lines required to drive the display on digital pins 0,1,2...
Shifting those to spare pins higher...
Yessssss! It works really nicely.
Red LED Shop Display
Found this thing at the flea markets on the weekend. It's a standard LED marquee/shop display with two cables hanging out one side. One looks like a power cable, the other data with an RJ45 phone plug on the end. Manufactured by Data Signs Australia, there's no mention of this relic on their site... seems they only care about road signs now!
As you can see, it's what's on the inside that counts. At the end there seems to be a controller board with an Atmel microcontroller. This is then connected to five display modules, all on a standard bus. The build of the headers is nice as it all connects seamlessly together to give one full display. It looks like the wiring of the phone jack goes straight into a MAX232... so we can talk serial to this thing. I'll do that later though, I'm more interested in the display modules.
Each module contains six 5x7 LED matrices. These are powered by TIP125 transistors and data is driven by MC14015BCP shift registers connected in series. The data bus on the side has 2 lines at either end for VCC and GND. The pins in the middle are then Clock, Data, Reset (for the shift registers) and then enable lines for the 7 matrix rows.
Hacking started in earnest to work out the pinout...
Lighting it up
For any LED matrix, the basic idea is to send data really quickly to them and 'emulate' that all LEDs are on at one time. Whenever you try to take photos of LED lights, be that traffic lights, train destination boards, etc... you'll find that, unless your shutter speed is slow, you don't get the entire set of 'lit' LEDs visible at once. Here's a good example:
Not the clearest example... blame the snow in early November... but you can see that the LEDs in the destination board aren't of a consistent illumination. The microcontroller is still busy rendering the screen and the frame is 75% drawn.
Microcontrollers 'print' the LED signal across the matrices to get them to light up. They do this so quickly, that to the naked eye, the LEDs are always lit. We're going to have to do this here as well. I've decided to use that huge IO board I investigated recently to drive this thing.
Based on the datasheet for the MC14015BCP shift registers, as the clock signal is driven high, whatever is on the data line will be fed into the first parallel data out line. These shift registers contain two sets of 4-bit outputs. Following the wiring on the card, the final bit of the first 4-bit output is chained to the data input of the second register. The final bit of that is then chained to the next shift register IC's data in. The clocks are all tied together, meaning that any data fed in to the clock/data lines on the bus at the side of the board will eventually trickle down all the way through the board, and then to any boards attached further. Quite the shift-register-centipede!
Writing some code...
Thanks to the previous work with this IO card, the interfacing would be quite simple. Clock, Data and Reset would be pins 1,2,3 on the first block-of-eight at the base IO address. I then put the 7 row-enable pins on the second block-of-eight. This meant less bit-twiddling. QBasic allows a nice mechanism to record data that can then be read into arrays. The data happens to be the pin value of the data pin, so here "2" will set the second pin HIGH when sent to the port, meaning that the data line will be high for the shift registers and therefore illuminating the LED in that column. I then just need to enable the first pin briefly to send through that number. Of course, I'd send a 0 (or LOW) to pin 2 to send a 0 through the shift registers, thereby turning that column's LED off.
The above needs to be done for each row. i.e. I have to send out 30 values (length of one row on one module) then 'flicker' the enable row. Then I need to send the next 30 values and 'flicker' the second enable row. The speed at which the row enable pin is 'flickered' is also an issue... if it's too slow, then you just get one line of LEDs and it's very jittery... but if it's too fast the LEDs start to dim, as they're starved of power to actually illuminate!
OUTDATA# = &H410 OUTLINES# = &H411 OUT &H413, 0 OUT &H413, 0 CLOCKBIT = 1 DATA 30, 8 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 READ DATALENGTH READ DATAHEIGHT DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER FOR Y = 0 TO (DATAHEIGHT - 1) FOR X = 0 TO (DATALENGTH - 1) READ DATAPIXELARRAY(X, Y) NEXT X NEXT Y DO FOR ROWS = 0 TO (DATAHEIGHT - 1) FOR DOT = 0 TO (DATALENGTH - 1) BIT = DATAPIXELARRAY(DOT, ROWS) OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT OUT OUTLINES#, 255 - (2 ^ ROWS) FOR i = 0 TO 10 NEXT i OUT OUTLINES#, 255 NEXT ROWS LOOP UNTIL INKEY$ = "q"
The only real trickery in the code above is the two zeroes sent out to 0x413 at the start... it seemed to be required to get the IO card to treat the ports as output and pull them to ground by default. Otherwise, data is defined and loaded into the array. A non-stop loop (unless you press "q") is then started where we load a single row of dots and then trigger the row enable pin. Each row-enable pin needs to be brought low to activate the LEDs, hence the 255 - (2 ^ rownumber). Finally, that crappy little for-loop is there to provide a mini delay so that the LEDs get a chance to light up.
Let's make a marquee
Right, a display of static pixels is fine, but what if you want one of those really ugly perpetually-scrolling marquees that are seen in every $2 (100-yen) shop? Should be pretty simple, just throw in an offset and increment it at every loop. That'll work, but then once it's off the screen it'll start back at '0' which happens to be fully-on-screen. That jump is a little jarring, so instead you need to actually shift it width-of-data off the start. For now, we'll just wrap it without padding in the middle.
OUTDATA# = &H410 OUTLINES# = &H411 OUT &H413, 0 OUT &H413, 0 CLOCKBIT = 1 DATA 30, 8 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 READ DATALENGTH READ DATAHEIGHT DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER FOR Y = 0 TO (DATAHEIGHT - 1) FOR X = 0 TO (DATALENGTH - 1) READ DATAPIXELARRAY(X, Y) NEXT X NEXT Y DIM SCROLLOFFSET AS INTEGER DIM LOOPCOUNTER AS INTEGER SCROLLOFFSET = 0 LOOPCOUNTER = 0 DO FOR ROWS = 0 TO (DATAHEIGHT - 1) FOR DOT = SCROLLOFFSET TO (DATALENGTH - 1) BIT = DATAPIXELARRAY(DOT, ROWS) OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT FOR DOT = 0 TO (SCROLLOFFSET - 1) BIT = DATAPIXELARRAY(DOT, ROWS) OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT OUT OUTLINES#, 255 - (2 ^ ROWS) FOR i = 0 TO 10 NEXT i OUT OUTLINES#, 255 NEXT ROWS LOOPCOUNTER = LOOPCOUNTER + 1 IF LOOPCOUNTER > 10 THEN LOOPCOUNTER = 0 SCROLLOFFSET = SCROLLOFFSET + 1 IF SCROLLOFFSET > DATALENGTH THEN SCROLLOFFSET = 0 END IF END IF LOOP UNTIL INKEY$ = "q"
I'm sure there's some MOD function I can use to prevent the need for two loops, but it works! Of course, it only works as long as the data is the length of the screen! What happens when I hook up the other modules?...
Oh... that's no good... it's offsetting by 1 row per module? What's the go there? Let's edit the code to fill the entire row.
OUTDATA# = &H410 OUTLINES# = &H411 OUT &H413, 0 OUT &H413, 0 CLOCKBIT = 1 DIM ACTUALDISPLAYWIDTH AS INTEGER ACTUALDISPLAYWIDTH = 60 DATA 30, 8 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,2,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,2,0,0,2,2,2,0,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 READ DATALENGTH READ DATAHEIGHT DIM DATAPIXELARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER FOR Y = 0 TO (DATAHEIGHT - 1) FOR X = 0 TO (DATALENGTH - 1) READ DATAPIXELARRAY(X, Y) NEXT X NEXT Y DIM SCROLLOFFSET AS INTEGER DIM LOOPCOUNTER AS INTEGER SCROLLOFFSET = 0 LOOPCOUNTER = 0 DO FOR ROWS = 0 TO (DATAHEIGHT - 1) FOR DOT = SCROLLOFFSET TO (ACTUALDISPLAYWIDTH - 1) IF (DOT >= DATALENGTH) THEN BIT = 0 ELSE BIT = DATAPIXELARRAY(DOT, ROWS) END IF OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT FOR DOT = 0 TO (SCROLLOFFSET - 1) IF (DOT >= DATALENGTH) THEN BIT = 0 ELSE BIT = DATAPIXELARRAY(DOT, ROWS) END IF OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT OUT OUTLINES#, 255 - (2 ^ ROWS) FOR i = 0 TO 10 NEXT i OUT OUTLINES#, 255 NEXT ROWS LOOPCOUNTER = LOOPCOUNTER + 1 IF LOOPCOUNTER > 5 THEN LOOPCOUNTER = 0 SCROLLOFFSET = SCROLLOFFSET + 1 IF SCROLLOFFSET >= ACTUALDISPLAYWIDTH THEN SCROLLOFFSET = 0 END IF END IF LOOP UNTIL INKEY$ = "q"
So now we have a ACTUALDISPLAYWIDTH variable that tells us how much space we actually need to fill. Using the SCROLLOFFSET, we then start printing out the data array and, if we're past it then just zeroes. Once we've gotten to our full width, we then go back to the array and attack it from the start.
And if we increase to three panels and up the width to 90?
Gosh, it's getting slow loading that full row each time... maybe time to switch to an Arduino? Before we do that... here's a typewriter...
OUTDATA# = &H410 OUTLINES# = &H411 OUT &H413, 0 OUT &H413, 0 CLOCKBIT = 1 DIM ACTUALDISPLAYWIDTH AS INTEGER ACTUALDISPLAYWIDTH = 90 DATA 104, 8 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,2,2,2,0,2,2,0,0,2,2,2,0,2,2,0,0,2,2,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2, 2,2,0,0,2,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,2,0,0,0,2,0,0,2,2,2,0,2,2,2,0,2, 2,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,2,2 DATA 0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,0,0,2,0,0,0,2,0,0,0,2,0,2,0,0, 2,0,0,0,0,2,0,2,0,2,0,2,0,0,0,2,2,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2, 0,2,0,2,0,0,0,0,2,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,0,0,2 DATA 0,2,2,2,0,2,2,0,0,2,0,0,0,2,0,2,0,2,2,2,0,2,2,2,0,2,0,2,0,2,2,2,0,0, 2,0,0,0,0,2,0,2,2,0,0,2,0,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,2,2,0,2,0,2,0,2, 2,2,0,2,2,2,0,0,2,0,0,2,0,2,0,2,0,2,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,0 DATA 0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,0,0,2,0,0,0,2,0,2,0,2,0,2,0,0, 2,0,0,2,0,2,0,2,0,2,0,2,0,0,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,0,0,2,2,2,0,2, 2,0,0,0,0,2,0,0,2,0,0,2,0,2,0,2,0,2,0,2,2,2,0,2,0,2,0,0,2,0,0,2,0,0 DATA 0,2,0,2,0,2,2,0,0,2,2,2,0,2,2,0,0,2,2,2,0,2,0,0,0,2,2,2,0,2,0,2,0,2, 2,2,0,0,2,0,0,2,0,2,0,2,2,2,0,2,0,2,0,2,0,2,0,0,2,0,0,2,0,0,0,2,2,2,0,2, 0,2,0,2,2,2,0,0,2,0,0,2,2,2,0,0,2,0,0,2,0,2,0,2,0,2,0,0,2,0,0,2,2,2 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 READ DATALENGTH READ DATAHEIGHT DIM DATAFONTARRAY((DATALENGTH - 1), (DATAHEIGHT - 1)) AS INTEGER FOR y = 0 TO (DATAHEIGHT - 1) FOR X = 0 TO (DATALENGTH - 1) READ DATAFONTARRAY(X, y) NEXT X NEXT y DIM ACTUALPIXELBUFFER(ACTUALDISPLAYWIDTH - 1, DATAHEIGHT - 1) AS INTEGER DIM SCROLLOFFSET AS INTEGER DIM LOOPCOUNTER AS INTEGER SCROLLOFFSET = 0 LOOPCOUNTER = 0 DO FOR ROWS = 0 TO (DATAHEIGHT - 1) FOR DOT = 0 TO (ACTUALDISPLAYWIDTH - 1) BIT = ACTUALPIXELBUFFER(DOT, ROWS) OUT OUTDATA#, BIT OUT OUTDATA#, (BIT + CLOCKBIT) OUT OUTDATA#, 0 NEXT DOT OUT OUTLINES#, 255 - (2 ^ ROWS) FOR i = 0 TO 10 NEXT i OUT OUTLINES#, 255 NEXT ROWS LOOPCOUNTER = LOOPCOUNTER + 1 pressedchar$ = INKEY$ IF (LEN(pressedchar$) = 1) THEN lastchar = ASC(pressedchar$) lastchar = lastchar - 97 IF (lastchar >= 0) THEN FOR c = 0 TO 3 FOR y = 0 TO 6 ACTUALPIXELBUFFER(c + offset, y) = DATAFONTARRAY((lastchar * 4) + c, y) NEXT y NEXT c offset = offset + 4 IF ((offset + 4) >= ACTUALDISPLAYWIDTH) THEN offset = 0 END IF END IF ELSE REM lastchar = ASC(RIGHT$(pressedchar$, 1)) END IF PRINT lastchar IF LOOPCOUNTER > 200 THEN LOOPCOUNTER = 0 SCROLLOFFSET = SCROLLOFFSET + 1 END IF LOOP WHILE INKEY$ <> "."
Hah... that worked better than expected...
Apple G4 iBook
After a lovely drive into the Dandenong Ranges to check out some tulips (and eat oliebollen) (thanks Mum!), it was time to venture home... but who could resist not visiting the tip-shop along the way? After a quick look around the shop, it felt like they'd stopped stocking electrical components... but then I stumbled across this, sitting in the wrong area, but looking in really-great condition!
Check out that keyboard! It's still in perfect condition and clean? How on earth... anyway, I took it to the counter and asked for the price. The checkout-chick had to flip through a book of standard prices before responding with AUD$10. I giggled and kept shopping, hoping to find the power supply... no luck there though!
DIY Power Supply
These iBooks need a 24v DC power supply. The plug is a little adventurous on Apple's behalf. Initially it looks like an RCA composite plug, but then you realise that the central pin is actually a 2.5mm stereo jack. Without the outer shield, I can imagine it would be very easy to short the plug. Either way, it's a bit of a lets-take-two-things-off-the-shelf and combine them to make a proprietary socket that no one else can copy. Or can we? (alternative source)
Turns out that you only really need to apply VCC and GND to a standard 2.5mm jack and it'll work. The very tip is not connected, the middle band is GND and the base, closest to the wiring, is +24v DC.
Jaycar didn't have any full-metal-casing plugs, so I had to live with the cheap plastic plugs. Soldered up, I got the following...
The next morning, at the local flea market, I found this cheap and nasty power supply. It says it produces 21-24v? Seems to have a 4-pin plug, so hopefully one of the wires is the full 24v.
Oh right, that's just a two-wire cable split into pairs. So the 4-pin is a double-adaptor. Not handy... and the voltage is hardly at 22v. Will the PowerBook survive?
Sure did! And the battery even started charging!
201 Series Final Run, Osaka – June, 2019
This was officially the third final run 'seen' in one trip to Japan! First I got to see Ohmi Railway's 700 Series do its last run. Secondly, although not the final-final, I saw/rode Kumamoto Dentetsu's 200 Series during it's last month of operations. Finally, we have the Osaka Loop Line special: an orange-liveried 201-Series EMU. Starting in 2016, JR West put a lot of money into the Osaka Power Loop marketing campaign which saw new EMUs and new liveries on the Loop Line. This therefore meant a phasing out of the older rolling-stock.
Just my luck, the final run of the last running orange 201-series happened in early June. Unfortunately, I was working from the apartment and hadn't paid enough attention to when the final run would actually be! It turns out it was a single lap in the early morning peak! The train then retired to the yards near Osaka-Jo. I had actually gone out for a lap of the loop at around 3pm, waiting around Bentencho Station for the train to pass... after a while I google'd, only to find out that I was too late and therefore decided to head to the yards at Morinomiya. Before that though, there were some cool sites to be seen!
And, of course, my goal had been to catch the last-run and freight... so at least I was in one correct spot at one correct time!
The yard is located to the south of Kyobashi Station on the eastern side of the Osaka Loop Line. Morinomiya is the station directly south of the yard. The yard's entrance is on the north side and all operations are visible from the southern end of the Kyobashi Station platforms. To see parked trains though, I'd recommend walking from Morinomiya Station. It seems that everyone else had the same idea!
So, the train was in the yard and the wall was high and secure. If you look in the photo above, there was a poor little kid who'd ridden his bicycle down and tried to use it as a pedestal to see over the wall. Unfortunately, he still wasn't tall enough to take a photo with his Nintendo 3DS. I didn't ask why he was using that... but I guess he was too young to have a phone?
I took the following photo over the wall...
And then the poor kid looked up at me ... totally distraught. What else was there to do? I grabbed him and lifted him up high enough to take photos with his Nintendo! Made his day! On the way back, I stopped through Kyobashi Station as I wanted to actually check out the area. Whilst alighting, I grabbed a few shots of the local rolling-stock.
I was pretty sad... this was my second-last day of a 5-week trip.
Flea Markets, Osaka – May, 2019
There's nothing better than queuing (queueing?) up at the entrance of a Flea Market... there could be any amount of treasure inside, so you'll never know what you might find. One also doesn't want others to steal said treasure, so one must be early! In Japan, just like most other countries, there's a lust for flea markets and there's always someone selling something which piques one's interest.
Banpaku Recycle Fair - Expo Park
This flea market, named Banpaku Recycle Fair, is held twice a month at Expo Park in North Osaka. Getting there from Shin-Osaka was very easy, taking the subway and the Osaka Monorail.
After getting off at Expo Memorial Park Station, exit to the east and then cross under the monorail lines. The entrance is well organised, and really, you just need to head towards this guy to find it...
The park is used for a lot of events, even just families going for a picnic. The weather was perfect for a picnic also, but that's not what I was there for. Following the main path around to the left, you'll find the flea market.
From here, the browsing commenced! I ended up picking up a Famicom and a few n-gauge trains.
That Tower of the Sun God is ever-so-daunting.
Ohatsu Tenjin Shrine Flea Market
This one is nicely tucked away behind the busy streets of Umeda. It's a little bit south-east of the main JR Osaka Station, but within easy walking distance.
The temple itself is beautiful, a complete relic nestled in amongst a ring of skyscrapers. The area is connected to the Sonezaki Ohatsu Tenjin Dori Shopping Street (Shoutengai) which also offers some vintage and retro stores... if they're open when you're at the market!
I managed to pick up a really nice Sony Walkman-style personal recorder. It had all the right inputs and looked like it might be able to be connected to an MSX/C64/etc.. for data recording.
Don't forget to actually check out the shrine itself! Make a wish if you want!
Shi-Tennoji Flea Market
This market was huge! It's on the grounds of the Shi-Tennoji Temple and it's quite an effort to navigate the layout. You can access this market via the Tanimachi Subway Line at Shitennoji-mae Yuhigaoka Station or by walking north from JR Tennoji Station.
If you're walking from the Subway station, you'll find the residents have their own stalls in the street leading to the main market. I don't know how by-the-book this is, but they've made the most of the traffic that comes through!
Wandering around, the usual trinkets were to be seen... until I saw this!
It's a vintage model maglev Linear Shuttle! Opening the box to check the contents proved that it wasn't in the best condition.
There seems to be a small oil tube included, which makes me think that the vehicle isn't always levitating... a quick google indicated that it actually only levitates on one section of track which then propels it around the loop. The loop is also vertical, as in a loop-the-loop, and not a flat circuit. The metal was also quite corroded... so I passed on it... but I had been pretty damn keen!
Don't forget to say Hi! to the turtles in the middle of the temple yard. And the dancing monkey! I just missed the show.
Some stall holders happily dumped their wares on their tarpaulins... others were a lot more organised.
And yeah... there's a lot of the above as well... it's always fun to check out the customers of such wares!
Shin-Osaka Station - East Gate
This small market is open every Saturday morning. Markets are pretty-much always on Sundays in Australia, so it was fun to come across this randomly when heading to the station to meet friends. Fortunately, there wasn't anyting that interesting... so I didn't have to lug anything around all day.
It was also extremely hot... so anything you see above has probably already melted!
Suita Yard, Osaka – May, 2019
Thanks to the time of year, the sun was already starting to set later in the evening during May. I used the opportunities, when it wasn't raining, to venture out to the freight areas along the JR Kyoto Line. I'd visited Takatsuki the night before and realised, on the way back to Shin-Osaka, that I'd never really investigated the freight yard in Suita. The yard is officially located between Suita Station and Kishibe Station and there's a locomotive depot on the southern side of the line. I chose to proceed to Kishibe Station on train and then walk back to Suita.
Approaching from the east, I was instantly happy with my timing. The sun was setting perfectly, pointing straight at the faces of quite a lot of freight locomotives! Not only that, the variety was quite surprising. There were even some EF200s ready to be chopped up!
From the east side, there's a gate to the yard. This area provides a great vantage point to watch anything shunting around. It just so happens that a new HD300 was doing the honours with a set of KOKI flats. I don't actually remember the note of the engine as it was shunting, but for the life of me I don't think it sounded any less diesel! Shouldn't it have been more hybrid?
An EF63 then came through and parked into a free road in the yard. It had actually just come from Hirano, where I'd seen it earlier passing through!
Waaaay up the back of the yard there was also a standard YO5000 black guards van.
I continued to walk around to the other side of the yard. A lot of the length is just the side of the engine shed, in which I could here a lot of work being done, but couldn't really see it. And then there was some treasure on the side of the road... wouldn't fit in the suitcase though... might have been handy to test the famicom tho!
Things got more interesting on the other side of the yard... EF66s! My favourite!
And then, after a bit more of a walk, there was an open gate with a perfectly framed view straight into the yard.
What an awesome line up! The lighting wasn't too bad either. Finally, down the very western end, is the entrance to the offices. They've mounted a 52 Series EMU (KuMoHa 52001) in their yard!
Nice surprise! I totally recommend anyone in the area to go for a walk and check this place out.