-
Quick update
11/04/2018 at 18:05 • 0 commentsHi there. I finally managed to find some time and motivation to get back to business. Quick recap: I knew that my new code worked to some extent, but it wasn't triggering automatically and furthermore, it violated MIDI specification.
Let's analyze the second problem again (I know, I can repeat myself, but it's been a long time): in order to store more than two bits of data, I'm gonna need to split this variable into two memory addresses and do some masking and shifting. The handling of parameters is done in pretty flexible way - there's a generic handler that can use metadata provided with each parameter. This ease of use comes with some limitations though - of course it's not possible to span one parameter across multiple offsets and there's no 4-bit panel value handler too (like there's no parameter that takes values from 1 to 15, only 1-8, 0-31 and 0-63 plus some custom ones). From this point on I must consider possible routes:
a) use only 2 bits available in one adress, expand waveform memory to 4 banks, changes needed: write a 2-bit value handler (quite easy, but some memory reorganization will be necessary)
b) use the existing 3-bit handler, expand waveform memory to 8 banks, changes needed: store one bit somewhere, add custom parameter handler which will be able to read and write this bit in an adjacent address (probably hardcoded)
c) a + b - use 4 bits of memory, changes needed: custom value handler and custom parameter/memory handler
Should be doable in, well, finite time, but I'm a little bit concerned if I manage to squeeze it into available memory.
Now let's get back to the first problem: my recent hack worked only on value change. That's nice, however setting the bank number once the patch is loaded should be a no-brainer. I spent a considerable amount of time trying to track down when this happens, but without any luck. Today I tried to do it once again: I fired up my emulator and set a watchpoint on the memory area occupied by the working patch (I've probably done that before) - that's basically your buffer, each time you recall a patch from the memory, it gets copied here and each modification is stored there as long as you keep the same patch number. Well, if it gets written, then it must be also read, right? Makes sense, but I couldn't capture it. I took a closer look at the schematic again. DW6000 consists of two processor boards connected by a parallel interface. But it's TCP-like, so instead of fireing and forgetting you need to wait for an ACK signal before you can send more data, right? The ACK signal coming from the DWGS board generates an interrupt which then gets handled by our lil board. OK, that might be a clue. Let's get back to the drawing board. Uhm, to the text editor and MAME source code I mean :) I have quickly appended following code to the write handler:
if (offset == 0x4000) { logerror("[%.4X]: write to KLM-654, data=%.2X\n", m_maincpu->pc(), data); m_maincpu->set_input_line(UPD7810_INTF2, CLEAR_LINE); m_maincpu->set_input_line(UPD7810_INTF2, ASSERT_LINE); }
It's a simple mock that reacts on each write to 0x6000 (that's right, but I'm using addresses relative to 0x2000 which is the beginning of external RAM) by generating an interrupt.
And what do you know, it worked! Shortly I was able to see the whole transmission. It is basically a whole patch dump with a header (or message ID) after power on / patch change and another short message after each parameter change.
It seems that I need to put another hook before or after patch transmission, because KLM-653 doesn't give a shit about all the parameters. It only needs to know a few: assign mode and currently displayed parameter's value. And the others are read whenever they're being modified. That's it.
More to come SOON.
-
Told ya, we're not dead!
06/12/2018 at 12:29 • 0 comments...just procrastinating!
But thanks to Isa I decided to get back to work. I haven't been updating this feed for some time, but that doesn't mean, that I've been doing nothing (well... sorta).
When I found out that my modification of the DW6k's bit map (aka patch structure) will corrupt SysEx messages, I knew that there's no turning back from some heavier firmware modifications. All I need to do is to write a function which assembles bigger values out of two bits available in almost each byte of patch data. Not quite a rocket science, just some shifts and masking (this is what assembly is all about:)), but must be precisely injected into the existing code. Doing it the old way (i.e. run the code in the emulator, set some break- and watchpoints, decipher what it does, comment, wash, rinse, repeat) is highly inefficient as it requires lots of patience and concentration. What I needed was a way to graphically represent the code including calls and conditionals. There's a brilliant program called IDA Pro, but unfortunately is way too expensive. OK, I thought - maybe I don't need it with its all bells and whistles. At one point I considered writing a piece of software which parses the source file and exports it to Graphviz format, but then I decided to spent comparable amount of time to do something better. Do you know radare2? It's an open source reverse engineering suite which supports multiple processor architectures. As for upd7810, if you can write a module for it it will run just fine:) And that's what I've been doing for some time. I've re-used some code from MAME (didn't have to type in all opcodes by hand), but I didn't manage to get it fully working (disassembly works fine, but the analyzer gets crazy on branching instructions). And that's what I'm gonna do now.
-
Finally, something one can listen to
06/11/2018 at 14:14 • 0 commentsOver the course of the weekend I burned a set of WaveROMs with rather generic waveforms, plugged them into the DW, and - we have sound.
http://www.filehosting.at/file/details/744100/WaveTest.mp3That sounded promising enough to convert some other waves into an appropriate format - Some of you might recognize the wave names from $somewhere:
...this would not have been possible without the excellent information found here
http://www.buchty.net/ensoniq/And now - let's go find a useful EPROM :)
-
Hello, I'm new here...
06/08/2018 at 11:00 • 0 comments -
RTFM
09/27/2017 at 10:57 • 0 commentsJust as I thought, there must be a reason behind every piece of code. I don't know MIDI specification by heart, but I had a feeling, that it might be the reason. And (unfortunately) I was right.
This is an excerpt from the official MIDI specification. The MSB destinguishes between status and data bytes. That's why one does not simply use 8 bits for patch data. Bummer. Using only 2 bits for bank switching doesn't sound good enough to me, so I will probably rewrite the code and use 2 subsequent bytes to carry 2 bits each. Pros? Moar banks. Cons? I won't be able to reuse as much code as currently. Back to square 2.
-
Hello, world!
09/26/2017 at 20:40 • 0 commentsWhen you're developing something that lives only inside of an emulator, there's a big chance that the code, which seems to work just fine in a debugger, will fail to work on real hardware. That's exactely why I wanted to hook up a logic analyzer and see what my code is doing outside of a silicon chip. Unfortunately due to my non existing safety precautions I have released the magic smoke from the CPU and didn't have a chance to see my code on real hardware. You know the story.
This time I have taken a slightly different approach. No darn logic analyzers. Real hardware only. This is what I ended up with:
It's pretty simple: just an octal edge-triggered latch and some LEDs. The latches are connected to PB4..6 and the clock input goes to PC2. As you can see this time I have used some heavy duty tape, just in case :) OK, but will it work?
Nothing happens so far. Maybe I should change the bank number?
Nice, looks like a binary five! So it works. Kinda. I have cheated a little bit. You see, the code I wrote gets executed only if I explicitely change the bank number, but if the patch is loaded from the memory (e.g. on power on or patch change), nothing happens. I have to dig a little deeper and find another place to call my bank switching routine. But I think that I already earned a beer:)
Oh, there's one more thing that can complicate a thing or two. This piece of code:
LXI EA, 2680H ; EA = $2680 (working patch) LXI H, 2000H ; HL = $2000 (patch memory) .1AEEH: LDAX H ; A = (HL) ANI A, 7FH ; highest bit = 0 STAX H ; save back to (HL) INX H ; HL++ DEQ EA, H ; loop until EA==HL JR .1AEEH
For some strange reason the memory region which contains patches gets ANDed with 0x7F to set the highest bit to zero. If you look at the DW6000 bit map, you can see that no parameter uses the highest byte. Unfortunately my parameter violates this rule:
Is there any reason behind it? Well, there are two ways to find out: a) analyze the source code or b) disable it and see what happens. Guess which path I will take:)
-
Back on track
09/19/2017 at 12:25 • 0 commentsAfter over one month, DW6k is working again. Yay! Fortunately only the main CPU was broken and after quick (sorta) replacement, everything seems to work just fine. It took so long, because I have ordered the ICs from China and they stuck at customs.
The replacement took longer than I expected, mostly because I wanted to install the IC in a machined socket (you know, just in case), but I couldn't fit all the pins at once, so after more than one hour long struggle, I have soldered the chip directly to the PCB.
For safety's sake I have poured a ton of hot snot on all exposed mains connectors :)
After replacing the fuse I have connected the board to the PSU, powered it on and... success! It blinks again.
Another few moments later I have connected other boards and it looks like this lil one is tougher than I thought - works like back in the day.
More to come soon...
-
We're not dead - errata
08/09/2017 at 10:50 • 0 commentsSome time ago I told you, that we're not dead yet, but.. that's not entirely true. Long story short - I was sniffing the GPIOs with a logic analyzer to see how the new code works and suddenly I have shorted out mains to the chassis. Boom, flash, circuit breaker engaged, but it was too late. Logic analyzer is partially vaporized, my laptop doesn't start anymore (at least my hard drive seems to be intact) and the logic board of DW6k is dead as well... I don't know yet if the analog boards are fine, but first I will try to fix the digital one (mostly off the shelf low cost chips) and try to estimate the damage. upd7810 is fried, no clue about other logic chips. Fail of the week, huh?
-
History of 1-4
07/30/2017 at 14:27 • 0 commentsSo, it's been a long time (almost a year), but I finally got back to my little project and done some progress. If you know DW6000, or if your perception level is at least somewhere round 7, you will see that on the picture I posted yesterday I dialed in the new, non existant parameter 14. So, here's the story:
Before I even started further reverse engineering, I got in touch with Alfred Arnold (creator of the assembler I'm using) and asked him a few questions, because I wanted to get rid of any hardcoded addresses in jump tables. At the beginning I wanted to write a macro which would convert any label into either high or low byte and replace it with a nice DB. Long story short - the answer was pretty simple - all I needed to do was to use DWs instead. I didn't think about it first, because it seemed too easy:)
before:
.TBL03: TABLE JB DB 29H DB 14H
after:
.TBL03: TABLE JB DW .1429H ; that's an autogenerated label pointing ; (originally) to 0x1429h
Then I fired up my emulator again (I didn't change anything in it, it just works) and started with setting a watchpoint which would break the execution if anything tries to read the data from any of both of the tables (more on them later). That brought me somewhat closer to what I wanted to do - I ended up knowing more or less where's the code which reads the data from the tables and does the stuff with it.
Next I just started to change parameter numbers and/or parameter values (incrementing or decrementing them) and just looking into memory window searching for some patterns. And I finally found something: two offsets which are always taking the parameter's value. Another watchpoint set and... bingo! With some backtracking I have finally found the code I was looking for.
Now, a word about the data tables. There are two tables (related to synthesizer's parameters) and in order to modify the code I had to understand precisely what kind of data is stored in them. The first one is 144 bytes (48x3) long. Each entry represents a parameter number (11..16, 21..26 and so on).
The 1st byte holds the information about parameter offset (bits 3-7) relative to the beginning of each patch and its beginning bit (bits 0-2). That makes more sense if you just take a look at the DW6k's service manual (page 6, DW-6000 bit map).
The high nibble (4-7) of the second byte is a numeric value (0-9) which multiplied by 4 (size of an entry in the second table) gives an offset to the value from the second table. Just a relative pointer to say so. The low nibble holds a value between 0 and 3 which selects an appropriate display subroutine:
- 0: "normal" 2 digit (max) value (e.g. 0-31)
- 1: "normal" 2 digit (max) value, incremented (e.g. 0-31 displayed as 1-32)
- 2: value from 0 to 2 with translation -> 0=16, 1=8, 2=4 (octave selection)
- 3: value from 0 to 4 with translation -> 0=1, 1=-3, 2=3, 3=4, 4=5 (interval selection)The last byte is kind of an index of each parameter with a small twist - "local" (per pach) parameters go from 0x00 to 0x21, "global" parameters (81-83) from 0xF0 to 0xF2 and invalid parameters (like our 14) are marked with 0xFF.
The 2nd table isn't so exciting - the 1st byte is the maximum parameter's value, the second one is the bit mask to be applied on the value (after bit shifting) to get the desired value (hope you know what I mean:)). The 3rd one - I have no idea whatsoever, but it hasn't been used in any code which looks interesting to me, so let's just skip it. The last byte is another bitmask used to get or set the value.
Knowing all of that I took a look at the bit map again to find out where I could store my new parameter. Unfortunately there's no continuous 4 bit space, so I had to use only 3 bits and extend the memory by 8 banks (not 16 as I planned before). The second byte which holds the value of portamento time consumes only 5 bits, so I can use the remaining 3 for my purposes: offset = 1, start bit = 5 so my first byte is 0x0D. Then I started looking for any other 3-bit parameter that sits on the top of some other one to see which entry from the second table should I take for my purposes. The 5th one looks good. Now the display subroutine. I think that I will use the incremented one, so the resulting byte will be 0x51. Now we need to assign an index value and the next empty one is 0x22. Ok, so far so good. One more thing I needed to do was to find any instructions looking for the top patch's number and increment it to be able to select my new one.
Quick assembly, start the emulator and it looks really nice! I can dial-in 1-4 and set the value from 1 to 8, sweet!
Now it's time to handle the new parameter. That was actually quite easy - after finding a place in code which handles the other functions all I had to do was to modify it:
.11C9H: LTI E, 23H ; local parameter? (changed from 0x22 to 0x23) JRE .11F1H ; nope MVIW 0C3H, 0E0H MOV A, E STAW 0C4H ; store A (params' ID) into $26C4 NEI E, 0EH ; ID == $0E? (param 3-6, chorus) JR .11E5H ; go to chorus routine ; new code below NEI E, 22H ; ID == 22? (1-4 ,select bank) JMP .BANKSWITCH ; go to bank switch routine ; common instructions for all local parameters
Easy, huh? Let's write some code then:
.BANKSWITCH: MOV A, D ; bank number (0-7) stored in D, copy to A SLL A ; store in high nibble SLL A SLL A SLL A MOV B, A ; store in B DI ; enter critical section MOV A, PB ; get current PB ANI A, 0FH ; wipe high nibble ORA A, B ; join together MOV PB, A ; speak to hardware XRI PC, 04H ; enable latch (PC2) NOP ; wait XRI PC, 04H ; disable latch EI ; enable interrupts JMP .11E1H ; go back (label is my guess:)
No rocket science either.
The idea is to connect the memory through a latching buffer (74374) to PB[4..7] with its latch connected to PC[2]. This way I don't have to take care about any writes to PB (and that happens quite often, since it's a part of keyboard scanner), because it will be separated from the memory as long as PC[2] doesn't change from low to high (74374 is edge triggered).
Using the fact, that the existing code already provides me with the value to be set (register D), I'm just creating a copy of it (for safety's sake), shifting it to high nibble, merging it with PB's value (with interrupts disabled just to make sure that nothing will modify PB after it has been copied), writing it back and cycling PC to enable latch.
That may actually work if a) no other piece of code touches PC[2] (there's only one piece in code which may do that - if anything strange will ever happen I can easily patch it) and b) the jump address at the end is correct (I'm skipping the communication sequence between this board and KLM-654, because there's nothing to be communicated).
That's all for now. The new EEPROM is already programmed and sits in the keyboard. Next step is to connect an oscilloscope or a logic analzer and see if we're getting some data out of upd7810. We're getting really close.
-
A teaser
08/18/2016 at 21:11 • 0 commentsI told you, we're not dead :)
More to come soon!