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.