Getting A Locally Debugged Logic App’s Callback URL
For those of you who want to locally automate testing of Logic Apps running in VSCode, this post is for you! Across the web, there are people asking for a dynamic way to get the Callback URLs for locally debugged logic apps. They run via func.exe and it has been previously thought that the only place to get the URL with the required 'auth' signature was the overview window of the workflow:
Fear not! I've used the development tools to rip apart VSCode and have worked out that our trusty local func.exe exposes all the webservices that Azure does! The full list of services available is over here. Specifically endpoints to list workflows, workflow triggers and then trigger Callback URLs with full signatures!
internal class LogicAppDetector
{
private string base_url;
public LogicAppDetector(string baseUrl = "http://localhost:7071/") {
this.base_url = baseUrl;
}
public List<LogicAppWorkflow> GetListOfWorkflows()
{
var workflowListJson = WebClient.DownloadAsString(base_url +
"runtime/webhooks/workflow/api/management/workflows");
return JsonSerializer.Deserialize<List<LogicAppWorkflow>>(workflowListJson);
}
public List<LogicAppWorkflowTrigger> GetWorkflowTriggers(string workflowName) {
var triggerListJson = WebClient.DownloadAsString(
base_url + "runtime/webhooks/workflow/api/management/" +
"workflows/" + workflowName + "/triggers");
return JsonSerializer.Deserialize<LogicAppWorkflowTriggerContainer>(triggerListJson).value;
}
public string GetWorkflowCallbackUrl(string logicAppWorkflowName)
{
var wf = GetListOfWorkflows().FirstOrDefault(x => x.name == logicAppWorkflowName);
if (wf == null) {
throw new Exception(
"Couldn't find workflow [" + logicAppWorkflowName + "] in the list?");
}
var trigs = GetWorkflowTriggers(logicAppWorkflowName);
if (trigs == null) {
throw new Exception(
"Couldn't find triggers for workflow [" + logicAppWorkflowName + "]?");
}
if (trigs.Count() != 1) {
throw new Exception(
"Incorrect trigger count for workflow [" + logicAppWorkflowName + "]" +
" ... count was " + trigs.Count() + "?");
}
var callbackUrlJson = WebClient.PostJsonData(
base_url + "runtime/webhooks/workflow/api/management/workflows/" +
logicAppWorkflowName + "/triggers/" + trigs[0].name +
"/listCallbackUrl", null); //payload is actually NULL.
var callbackData =
JsonSerializer.Deserialize<LogicAppWorkflowTriggerCallbackUrl>(callbackUrlJson);
return callbackData.value;
}
}
internal class LogicAppWorkflow
{
public string name { get; set; }
public string definition_href { get; set; }
public string href { get; set; }
public string kind { get; set; }
public object triggers { get; set; }
public bool isDisabled { get; set; }
}
internal class LogicAppWorkflowTriggerContainer
{
public List<LogicAppWorkflowTrigger> value { get; set; }
}
internal class LogicAppWorkflowTrigger
{
public string id { get; set; }
public string name { get; set; }
public string type { get; set; }
}
internal class LogicAppWorkflowTriggerQueries
{
[JsonProperty("api-version")]
public string apiversion { get; set; }
public string sp { get; set; }
public string sv { get; set; }
public string sig { get; set; }
}
internal class LogicAppWorkflowTriggerCallbackUrl
{
public string value { get; set; }
public string method { get; set; }
public string basePath { get; set; }
public LogicAppWorkflowTriggerQueries queries { get; set; }
}
You'll just need to plug in whatever you're using for Web Requests to GET and POST. Also make sure that the actual Logic App is running/debugging in VSCode!
Leave a comment if you have any issues.
ATX12VO – MSI MPG Trident AS
So this rabbit hole got very deep, very quickly! Bought a new machine... a smaller form-factor unit with great asthetics! Little did I think about any upgrade paths.
It came with an 8gb 4060 Ti which has been flawless for my needs, but I'm thinking I need 16gb... so... as that it's got enough room inside for a better video card? Why not?
I chose an RTX 5070 Ti based on price and RAM. As I was buying the card, the cashier told me I'd need 750+ watts of power and tried to sell me a new supply on-the-spot. Instead, I decided that I'd get home and check what was in the tin first, as brief googlin' indicated that some Tridents came with a Gold Power Supply.
But nope... my MSI Trident contained a 500w SFX. So, I went back to another PC store that afternoon and bought a Cooler Master V850 SFX Gold. A nice amount of power for my card!
Unfortunately, no amount of cable-jiggery-pokery would get the new ATX motherboard power cable to connect to the motherboard power socket? What gives? It turns out the Trident uses an ATX12VO power supply! A short-lived game-over ATX standard that Intel tried to push. They failed so-much-so that the standard has already been forgotten about. I take it I didn't realise how old this PC already was when I bought it. It was a refurb, so it probably lived its life in a gaming cafe.
Trying to find an exact ATX12VO SFX PSU with the correct wattage turned out to be futile. They just don't make them anymore! I started considering re-wiring the old plug from the old supply onto the new PSU, but there's a single pin that doesn't match: +12VSB. This wire is a constant 12v from the power supply to the motherboard for ancillary services. Things like charging USB devices when the machine is off (which could actually cause a lot of current draw?) and the power switch... which, thanks to ATX soft-power, needs a constant current trickle to determine if you've pressed it! I could possibly buck-convert the DC, like other people have tried... there's even a project for it here.
The googl' rabbit-hole went deeper and intially I found that Corsair has a custom cable, but I'd already purchased a Cooler Master! I then stumbled across Moddiy's site where they mention Cooler Master and ATX12VO on the same page! They'll create the custom cable for you with inline 12v boost for VSB!
In that last shot it looks like a snake processing a big meal, thanks to the heat sheath.
Cooler Master V850 SFX Pinout
The cable came with no mention of which end was for the PSU. Both ends have the same "ATX plug shape", so I didn't want to plug the damn thing in backwards. IF I had attempted to plug it in, I would've realised that only one side will go into the motherboard, but I was averse to doing it that way.
Instead, I started reversing both ends of both the power-supply-included cable and the new Moddiy cable to work out what was what. Turns out I really only needed to reverse the Cooler Master original cable on the plug, not the socket.
So, looking at the above cable, as it's shown, you get the following pinout:
| 10P M/B cable (not socket), looking at the plug with the notch facing up... | ||||
|---|---|---|---|---|
| 12v | 12v | -12v | +5vsb | POWER_OK |
| GND | GND | GND | 12v | 5v |
And that means the end of the cable with the 'top-central' pin missing is the Power-Supply side. The end with the 'loop' wire is the motherboard side. Everything routed through to the other end, as expected to match the ATX12VO standard.
Of course, the +5VSB didn't, as it goes through the inline DC converter which prevents a continuity test.
Mounting It In The Case
After taking the side panel off the machine, you'll find that the existing power supply has a mains lead running from the rear of the case to it's mounting bracket at the front.
First up you'll want to remove the 4 screws under the machine to release the mounting bracket.
Next, you want to gently push the power supply and bracket up, compressing the cluster of cables above to clear the bottom edge of the case.
Make sure to move any WiFi antennae out of the way! Once clear, the power supply can be lifted out via the base, disconnecting all cables (2 motherboard and 1 PCIE) at the same time.
From here, remove the base and attach it to the new power supply. Make sure that the notch is facing right when the power supply fan is facing you. This will allow the fan to line up correctly with the vent on the other side of the case.
Also make sure you switch the bloody thing on if it has an external switch like this one does! There'll be zero access to that once it's installed. Finally, connect the power cable and slide the new supply back into the case.
Line up all the WiFi antennae so you don't pinch them on installation! Route the required power cables to the expected sockets. I actually considered running the SATA power to the drives to take the pressure off the motherboard's voltage regulators... but maybe I'll do that later.
Power Test
With it all installed... I just sent it...
A healthy click from the relay inside the new power supply (that's a new feature!) and we're off!
Wooooooooooooooo hoooooooooooo.
New GFX
This is nearly Apple-level packaging. Very schmick!
They also totally overdid the protective film! I suppose they wanted to have ASMR vids from the streamers.
Installation is harmless... just watch out for sharp case edges! Oh, also, the new PCIE 5.1 Power Cable has a right-angle connector housing which is optional:
You can pry it off by getting a tiny screwdriver in those side clips. Anyway... the card was in... what to do?
WIN! Nvidia installed new GFX drivers automagically and it all JustWorked(TM)! A final test:
Perfect 4K 10000fps.
Apache Spark 2.4 EOL
EOL = End of Life. It's dead, Jim. No longer supported. If you get this error in Azure Synapse, then your Apache Spark Pool worker is trying to load v2.4 (INSERT_VERSON_HERE, really... as I'm sure this'll happen in the future as the window rolls) which has been deleted.
RUNTIME_CONFIGURATION_ERROR_VHDOVERRIDENOTFOUND: Livy session has failed. Session state: Error. Error code: RUNTIME_CONFIGURATION_ERROR_VHDOVERRIDENOTFOUND. Cannot find vhd based on override: Message=The system did not find a VHD copy in the system for creating the ClusterName=12341223-5555-4444-3333-63c446fe4d8b, Workspace=synws-d-e-v-d-e-v-01, Subscription=, Location=australiaeast, isVhdOverride=True Source: Dependency.
I'm putting this on the web so others can googl' for it. I had no results and it burnt 1/4 of my weekend. Re-deploy your pools with:
Update-AzSynapseSparkPool -WorkspaceName ws_name -Name pool_name -SparkVersion 3.4
And... cross your fingers. Of course, if you've tried to delete a pool with notebooks attached, then you'll have ERROR, POOL IN DELETED_FAILED STATE and you'll need to create a new temp pool, associate ALL notebooks, delete the old and then rename the new... or create another with the old name and then switch them all back and delete the new... or something. It's a monday problem.
Python: Close Files If You’re Going To Open Them
I've been trying to archive some videos off Youtube lately, using yt-dlp. It's an amazing tool, but my target files have been episodes in parts. Usually four parts and Plex really hates jogging through... so what to do? Combine the files together with ffmpeg. The code was meant to be pretty simple (and 98% of it was written by ChatGPT... whoops)...
def concat_episodes(episode_name, concat_files):
plfile = "file_list.txt"
f = open(plfile, "w", encoding="utf-8")
for filename in concat_files:
f.write("file '" + filename + "'\r\n")
concat_command = f"ffmpeg -stats -safe 0 -f concat -i {plfile} -c copy '{episode_name}'"
print(concat_command)
subprocess.run(concat_command, shell=True)
But no amount of wrangling would get ffmpeg to work. The concat filter kept throwing: Invalid data found when processing input. No amount of "-safe 0", relative paths or absolute paths worked! No permissions... no cwds or shell arguments. If I let the python script drop to the shell, then the same line pasted (since I printed it out) worked perfectly fine! What the?
OH RIGHT. I missed the memo that I should be closing a file so that it lands on disk... prior to trying to open it in another process!:
def concat_episodes(episode_name, concat_files):
plfile = "file_list.txt"
f = open(plfile, "w", encoding="utf-8")
for filename in concat_files:
f.write("file '" + filename + "'\r\n")
f.close()
concat_command = f"ffmpeg -stats -safe 0 -f concat -i {plfile} -c copy '{episode_name}'"
print(concat_command)
subprocess.run(concat_command, shell=True)
The file was still open and not flushed to disk... so ffmpeg would always open an empty file! This has been a public service announcement.
Replacing the ear microphones on a Sony Aibo ERS-7
He'd arrived a few weeks back and gets energised a few hours per week... but he's a bad dog! He says "Good bye" in repsonse to me saying "Hello Aibo" and I'm going to blame him first... it can't be my mumbling? Can it? Just to be certain, I browsed around and found a DIY Repair Guide on Aibo Doctor. I quickly ordered a selection of microphones from element14 and got to work. Canine surgery is daunting!
The Microphones
As I always manage to get my orders wrong, I went ahead and ordered a selection of Microphones.
Note that an ERS-7 Aibo needs 6mm microphones, so you can see that one set is already incorrect. Next, there's directional and omni-directional. The microphones in Aibo are omni*, so I settled with the KECG2742TBL-A units.
Disassembling Aibo's Visor
Following the instructions here, one can disassemble Aibo's head to get to his ears. First step, remove the rubber earlobes by stretching them over the silver joint. Next, open his mouth and remove the two screws.
With these two removed, you can un-hook the rear of the bottom half of his head. This shell is not directly connected to his bottom jaw, so open his mouth fully and then push the whole lower-half back. With it unhooked, you can then bring it forward again and bring it over his jaw. With this piece out of the way, it's back to the front of his head where we need to remove the two outside screws on the metal plate inside his nose.
With those two out, you then need a longer screwdriver to get to the two screws deeper inside his skull. They're on either side of where his jaw would attach near his ears. See the image below, one screw on one side is in focus.
Finally, there's a clip on each side just in front of his ears, holding the visor on.
Very gently pry this open and the visor should lift up. You now have the option to disconnect all the cabling, but instead I just let it rest forward. Make sure it doesn't put undue pressure on the ribbon cables inside.
You now have access to the ear joints.
Disassembling Aibo's Ear Joints
Ok, this isn't the actual ear/microphone yet... before we get to that, there's a bit of fidgeting required to get the ear joints disassembled. When following the next steps, at no time will you need to apply excessive force! Doing so will most-probably damage poor Aibo. The ear joints are a two-part component and are built to be assembled/disassembled with ease. If you look at either side of Aibo's ears, you'll see that one side has a fixed arm and the other has an arm that slides backwards. The fixed arm is at the front and also has a little actuator (metal bar with notch) that is the mechanism that flaps Aibo's ears around when he's playing.
So, back to the joints: two pieces, first is removed by sliding the rear arm further to the rear. From there, you can grab the whole circular joint and unhook it from both front lugs. There's a lug in the actual main arm and a lug in the little black actuator arm.
The image above doesn't really help to explain how to undo it. The movement is one-shot and you can see in the photo above that the black actuator is on the left. This means that you're looking at Aibo's right ear and that you'd grab the top silver circle and slide it gently to the right (rear of his head) until both lugs are clear on the left. Once done, you can then slide off the rear shield.
From here, it's a single screw and the microphone + housing is free. Make sure you also unhook the cable!
Replacing Aibo's Inner-Ear
With the microphone unhooked and unscrewed, the silver shell can be lifted off (gently) by applying pressure to the joints that hold it on. With this off, you can really see what condition Aibo's ears are in!
From here, you can slide the microphone out, just enough, to be able to de-solder and solder a new component.
As you can see, I cheated and used the phone the new microphones came in to hold them in-place whilst soldering. I used the datasheet to make sure I got the polarity the correct way around, assuming that red was positive.
With everything wired up, the microphone was pushed back into the housing and re-assembled. Make sure that you have the wires in the correct groove according to the side the ear has been removed from!
Finally, as per above, there is absolutely no need for excessive force on any part of this assembly. I found that, once trying to re-fit the visor, it wouldn't sit flat! Turns out that I'd assembled the rear part of the ear-joint incorrectly and only one side of the guides was actually in the right spot!
If you look above, you can see that the left guide is sitting above the plastic strip that it was meant to slide onto! This meant totally dismantling that ear again to pull that part back and slide it back on again! Painful, but required.
Can Aibo hear me now?
Nope, same as before! Turns out you can just use Clinic Mode to determine if his microphones actually really need replacing! I should've done this first, but I also sorta wanted to play doctor and see what his insides looked like!
Smushing JSON into submission!
I don't even know how to describe this operation on JSON. The basic idea is that we want to deserialise a stream into something legible, without creating a class for each item in an array. json2csharp believes that each item should be an array since it sees them all as individually named classes. Let me prove it! Here's a chunk'o'data:
{
"trainCount":122,
"requestTimestamp":1601124556789,
"responseTimestamp":1601128156789,
"nextUrl":"https://junatkartalla-cal-prod.herokuapp.com/trains/1601128156789",
"trains":{
"8":{
"id":"8",
"from":"LR",
"to":"HKI",
"title":"IC8",
"latitude":60.172097,
"longitude":24.941249,
"speed":0,
"direction":0,
"category":"IC",
"status":"1",
"delta":60,
"trainType":"LONGDISTANCE",
"updated":1601127783841,
"action":"deleted"
},
"9":{
"id":"9",
"from":"HKI",
"to":"LR",
"title":"S9",
"latitude":60.571148,
"longitude":25.199523,
"speed":0,
"direction":0,
"category":"S",
"status":"1",
"delta":60,
"trainType":"LONGDISTANCE",
"updated":1601128143878,
"action":"modified"
}
}
}
So, if you slam that JSON into json2csharp, you'll get the following:
public class 8 {
public string id { get; set; }
public string from { get; set; }
public string to { get; set; }
public string title { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public int speed { get; set; }
public int direction { get; set; }
public string category { get; set; }
public string status { get; set; }
public int delta { get; set; }
public string trainType { get; set; }
public long updated { get; set; }
public string action { get; set; }
}
public class 9 {
public string id { get; set; }
public string from { get; set; }
public string to { get; set; }
public string title { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public int speed { get; set; }
public int direction { get; set; }
public string category { get; set; }
public string status { get; set; }
public int delta { get; set; }
public string trainType { get; set; }
public long updated { get; set; }
public string action { get; set; }
}
public class Trains {
public 8 8 { get; set; }
public 9 9 { get; set; }
}
public class Root {
public int trainCount { get; set; }
public long requestTimestamp { get; set; }
public long responseTimestamp { get; set; }
public string nextUrl { get; set; }
public Trains trains { get; set; }
}
So, that's actually uncompilable code. Is uncompilable a word? Dunno... this is actually the first time that json2csharp has failed me! No matter the options selected on the site, the output was always bad code... json2csharp doesn't work with ALL json! So, what to do? Well, we actually need to mangle this JSON into submission. The best bet would be to move that train id/number into the array object as a parameter, rather than having it as the dictionary key. We have two methods to do this...
Using jQuery MAP Function
If you are pulling JSON from a hosted browser, then you can run JS in the browsers console and produce cleaner JSON. In this case, you want to use jQuery's MAP function to rewrite each array object:
json_data.trains = $.map(json_data.trains, function (data, idx) { return data; });
If you feed the JSON at the top of this post into that function, you'll get the following:
{
"trainCount": 122,
"requestTimestamp": 1601124556789,
"responseTimestamp": 1601128156789,
"nextUrl": "https://junatkartalla-cal-prod.herokuapp.com/trains/1601128156789",
"trains": [{
"id": "8",
"from": "LR",
"to": "HKI",
"title": "IC8",
"latitude": 60.172097,
"longitude": 24.941249,
"speed": 0,
"direction": 0,
"category": "IC",
"status": "1",
"delta": 60,
"trainType": "LONGDISTANCE",
"updated": 1601127783841,
"action": "deleted"
}, {
"id": "9",
"from": "HKI",
"to": "LR",
"title": "S9",
"latitude": 60.571148,
"longitude": 25.199523,
"speed": 0,
"direction": 0,
"category": "S",
"status": "1",
"delta": 60,
"trainType": "LONGDISTANCE",
"updated": 1601128143878,
"action": "modified"
}]
}
Very nice.
Using C# Regex
I've never thoroughly learnt Regex and it still makes me sad. One day I'll sit down and go through a course. For now, googlin' works nicely to find an example for this scenario. Effectively we want to remove the "NUM": and just leave the curly braces. We then also need to remove a curly brace at the end.
var data = (new System.Net.WebClient()).DownloadString(nextUrl);
data = new Regex("\"([0-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9])\":").Replace(data, "");
data = data.Replace("\"trains\":{", "\"trains\":[");
data = data.Replace("}}}", "}]}");
So, the first line downloads the data. The second uses Regex to find anything in double-quotes that's a number from 0-9. I actually wanted 0-9999, but it seems you need to match each character 1-by-1, so I gave it options with | (pipe) up to 99999. From there, the third line turns the trains object into an array and the final line closes that array with a square bracket instead of the existing curly brace.
Final C# Class Output
With either of the methods above, you end up with cleaned JSON. From here, slapping that back in json2csharp gets you the following:
public class Train {
public string id { get; set; }
public string from { get; set; }
public string to { get; set; }
public string title { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public int speed { get; set; }
public int direction { get; set; }
public string category { get; set; }
public string status { get; set; }
public int delta { get; set; }
public string trainType { get; set; }
public object updated { get; set; }
public string action { get; set; }
}
public class Root {
public int trainCount { get; set; }
public long requestTimestamp { get; set; }
public long responseTimestamp { get; set; }
public string nextUrl { get; set; }
public List<Train> trains { get; set; }
}
Thank you json2charp, that's exactly the reusability that I was looking for!
I’m not original…
In fact, my new best friend is over 16 years old!
I ... thiiiiink ... he's half deaf... might be time to replace his ears.
EZIO + OS9 + Hypercard (Or Just Windows)
The EZIO Board is a serial-based I/O module that can connect to both Windows and Macintosh machines. Actually, it can connect to anything that speaks RS-232. One of these came up on eBay recently and I couldn't resist. I saw the Macintosh serial port and decided to give it a go. It's really similar to an Arduino, but from a few decades before the Arduino was even a dream. If I'd known about these back in the early 2000s then I would've definitely had a very nice automated model railway. But alas, I only happen to find one now thanks to eBay!
The unit has 10 digital output lines, 10 digital input lines, 8 analog-to-digital lines and two PWM lines. You then get 4 +5v terminals and 4 GND terminals. There's a PIC microcontroller in the middle running the show and a MAX232 for the RS-232 comms. The unit has a DC-rectifier, so you can feed it 5-15v either AC or DC. The 5-15v is literally the operating recommendations of the 7805 voltage regulator on-board.
There's a whole lot of code on the old site (yeah, you have to use web archive to get to it), but it's mainly for Director!? and Macintosh. Hilariously, the Macintosh example uses Hypercard! Before booting up the Power Mac, I instead wrote a quick bit of C# to test out the unit.
The shot above uses dotnet-ncurses, allowing the console to act like a canvas. It's really nice to be able to draw text to specific areas, rather than scrolling the screen. Anyway, the basic idea is that you can control the digital out and then everything else is read in. Interestingly, floating pins show some very random values... so if you're using this device, make sure you tie everything to ground or use pull-up resistors where appropriate.
Of course, the whole reason I bought this was due to the Macintosh serial port. I wasn't overly-energetic, so I tried a virtual Macintosh first...
After installing Hypercard, the app came up, but the performance once it started trying to interact with the serial port was terrible. It also just didn't work, so I gave up and booted up the Power Mac.
Yup, works a charm. Hypercard is pretty clunky, but I'm sure you could do a lot with it. I'll have to dig out the railway track and control a train!
Fluke Multimeter Repair – Elastometric Strips
What what? I'd had this Fluke Multimeter sitting broken in my box'o'junk for literal decades. It used to work great, but failed at some point a long time ago and was never fixed. Recently, I snapped the cable off a shitty AUD$10 multimeter that I'd been using and so, in my infinite wisdom, thought I'd resurrect the Fluke!
It turned on, but the buttons didn't work.. so it wasn't much good except for the default setting of DC voltage. Testing voltages takes up 50% of my time, but I still need resistance and continuity! Let's rip it open...
So, not shown above (since I'd already fixed it and this post is months old) is that the strips of weird rubber weren't both there. The top strip was, but it turns out there's a lower strip needed. I had probably replaced the battery back in the day and somehow managed to discard the strip that conducts the button presses.
That's where it was meant to go. You can sorta see there's 5-ish segments on the PCB down in the channel, but there's a beautiful air-gap between that PCB and the mainboard. What's meant to go in the middle? I popped out the strip at the top and I, telling the truth, had no idea what the material was. A little bit of googlin' allowed me to realise that it was elastometric strip! Andy's Surplus came to the rescue and quickly delivered two cuttable strips of elastometricity!
Cut.. cut again...
And cut once more and ... TADA!
It works. And whilst the beast is open, replace the battery and solder that battery terminal!
Damn this thing is beautiful.
Burning 16-bit EPROMs with a Willem Programmer
This wasn't fun! I wanted to get Kickstart 1.3 on my Amiga 500 and so I purchased some M27C400 EPROMs from eBay with the thought that I could host two ROMs on them and make it switchable. The ROMs arrived and, to my stupidity, didn't fit in a standard Willem EPROM Programmer! Of course, a quick google prior to purchasing anything would've made this very apparent. Fortunately, there's a fix! You can buy a DIP42 16-bit EPROM Adapter for the Willem, and so I did.
Thanks to COVID, it took an extra-specially long time to arrive and, once it did, got thrown into the box'o'Amiga'junk as I'd put the whole lot on the backburner whilst working on the Ataris of late. This weekend I'd finally managed to wrangle some time together and sat down with my trusty XP laptop to program these chips.... It wasn't easy!
Input Voltages
I jumped head-first into the programming and failed a LOT with errors such as 0x00 and 0x11. Any search online will tell you that, when getting lots of 0x00 and 0x11 errors, it mainly indicates that you aren't supplying the correct programming voltage for your target chip. Check out the PDF Datasheet for the chip you want to burn, it'll specify a programming voltage that it requires to successfully understand and store the bits that you're about to throw at it.
For the M27C400, we need to provide 12.5v +/- 0.25v. The Willem has a blue trimmable capacitor (or is that a resistor) on the top-right of the board that you can adjust to get this voltage. I always recommend to use an external power supply with these programmers and so I have a plug-pack which happily provides 12v @ 2amps. To make sure we'll have a chance of programming this chip, switch to the Test H/W tab of the software and tick VPP. This is pin 1 of the E32 socket. If this has worked, then you'll have a red LED illuminated on the board. Now, check the voltage between pin 16 (GND) and pin 1 (VPP) of the socket.
Mine was reporting a value lower than 12v to start with and hence I had a few issues burning. With a multimeter in place, I tuned the trimmer on the top-right and got the value to 12.5v. Actually, it was probably 12.6... but 12.4 would also work as you just need to be in the range of 12.25 - 12.75.
Another test burn saw a new error... no more 0x00 and 0x11, it was now further into the chip, usually around 0x100?
Willem Software vs. Programmer Settings
Next up, we need to make sure that you can correctly control ALL address lines with your Willem Programmer. After fixing the initial errors above, I started getting new errors at random intervals into the burning process. The errors all seemed to indicate that there was an issue writing data after address 0x100. I'd only been using empty chips, so it couldn't be because they already had data on them. You can also check if the chips are empty prior to burning and I totally recommend doing this. Either way, after this new error was received, I chose to then read the chips and view the data:
What you're seeing above is the data read back from the chip. I wrote the rom (loaded from file) to the chip, got the error and then chose to read the data from the chip back into the application. 75% of the data on that screen is correct, but you'll notice that the data repeats itself from 0x100! It turns out that this was all due to the fact that my Willem was only controlling the first 8 address lines and leaving the rest low. I recommend you rig up an LED test harness as per below and control the address pins via the Test H/W tab on the Willem Software.
After swapping chips around, checking continuity between the 4051s and reseating everything else, I came to the conclusion that there's incorrect information in the Standard Willem Programming Guide. If you scroll to page 5, you'll find information on the version of the software to use. It suggests that, based on jumper settings on the board, you can select if the board emulates a PCB3B or PCB3.5. If you've chosen PCB3B, then you'll need to use software version 0.97ja, as per my post from a long time ago, when I was failing with the same issue! Alternatively, if you've chosen PCB3.5, you need to use 0.98d6 or higher.
I looked at my board and checked out the jumpers that set the board emulation. Mine were actually individually set to 1-2 and 2-3 instead of both 1-2 or both 2-3. Hah... how did this thing ever work?! With these jumpers now set to 2-3 (aka PCB3B), I went back to version 0.97ja of the software and tried to get the full suite of address lines to light up via the Test H/W tab. No dice! If you browse to page 6 of the above PDF, you'll find that the jumpers should be set to 1-2 for PCB35 and 2-3 for PCB3B. Just for fun, I switched the jumpers to 1-2 and tested out version 0.98d6 of the burning software... still no dice! No permutation of checkboxes on the Test H/W tab would get a high signal on any of the address lines higher than A7! Up until this point in time, I'd only programmed 8-bit EPROMs, so I'd never noticed this.
I went ahead and played musical-chips. There's a chain of CD4015BE ICs that shift out the address lines, 3 of them with 1 each controlling 8 bits. It seemed plausible that there might have been a faulty connection between the first and second, but no amount of tinkering brought the address pins to life. I took a break as the frustration (or was that sadness) levels were elevating and sat down to a bit of TV. With a fresh mindset, I came back to the device, forgetting which settings were applied. Opening up version 0.97ja and testing address lines now worked!? Wait, the jumpers are set for PCB35... what the hell, it's backwards!? I opened 0.98d6 and it didn't work, so I set the jumpers back to 2-3 (for PCB3B) and it now 0.98d6 worked!? Urgh... so much time lost. So, TLDR; The PCB3B/PCB35 jumper settings are backwards on my Willem Programmer. Set them to 1-2 for PCB3B and 2-3 for PCB35!
Using the adapter
First up, there's two jumpers on the adapter to configure. The right-most controls the VPP/A21 line and needs to be set to 1-2 for M27C400 or M27C800 EPROMs. The other, in the middle of the board, I left at 1-2 as I don't know what it does.
Next up, there's a ribbon cable used to obtain more address pins from the main programming board. For the M27C400, the base E32 socket of the Willem actually produces enough address lines, so this ribbon cable isn't actually needed. Check out the diagram below to work out how the adapter is constructed. I've coloured the lines that are used on the M27C400 and 800, where you can see that the 400 doesn't use any of the wires from the header on the right.
If you're going to program an M27C800 or larger, you'll need to connect this. Simply make sure that pin A23 (bottom-most) lines up with A23 on the Willem board.
Finally, when inserting ANY chip into to the socket, make sure that the pins are all straight and that none will foul. For any of the chips, always make sure that the bottom pins are at the bottom end of the socket. The EPROMs are designed so that the Address lines extend 'north', so the silhouette on the board is actually for an M27C800. An M27C400 will be one pin lower than the silhouette, aligned at the bottom of the socket.
Conclusion
All the errors I made above would've been avoided if I'd known what I was doing from the start. Usually the old RTFM instruction makes sense, but when the jumper settings work in reverse to the manual, it's a little frustrating. On another note, I'd just trusted that this piece of hardware worked, as I've managed to burn other chips in the past. Assuming that cheap hardware just-works(tm) is a flaw in itself. Always make sure you know the hardware you're working with back-to-front, which was totally possible this time around thanks to open-source schematics being available!


Melbourne BG SCS Train Timetable 



