Woot! This is my first time coding-up a FLASH writing algorithm... Kinda been wanting to do-so for a while... sorta thing I'd add to #commonCode (not exclusively for AVRs).
(Huh, how's this stuff fit in with that? We're writing ASM, here, that's C for the sake of portability. Hmmm... At some point I might like to use that stuff on here, too... talking sdcc at that point, and coding on a separate compy... I dunno yet. Maybe this'd be a good time to document those things' functionality for the sake of easy porting to other languages, or something)
I looked into some flash-writing code one of my coworkers wrote long ago... I remember A) It wasn't the same as writing SRAM, and B) it looked complicated, enough-so that maybe it couldn't even be done with a standard memory-interface bus... oh, and C) it seems to differ based on the device; manufacturer, amount, whatnot?
I think I lucked-out choosing the AT29C020 I had in my parts-bin... because this one is far easier than I remember. And because it apparently just happens to be the same one TI used during devel.
Things that make it easy:
It does use standard bus transactions (/WR at an address). Made a bit more difficult by the apparent lack of /OE from the T6A43 z80 VLSI, and the apparently somewhat universal /WE-overrides-/OE on other memories but not this FLASH. But easy-enough to workaround... and, again, right back to standard bus transactions (ld (hl), a).
It does require one to write a sector (256 bytes) at a time, but doesn't require a particularly complicated way to initiate that... By default, you just start writing bytes where you want them.. not exactly Random-Access. Instead, you've gotta write 256 bytes back-to-back, all in the same "sector" (which is selected by Address bits above A7). Interestingly, they /don't/ have to be written in order, which I thought was kinda excessive for them to implement until I started coding.
ld b, 0
ld (hl), b <whatever, just testing... i used b>
This is kinda like I'd usually do in C:
for(b=0; b<256; b++)... but, for that to work, b has to store 256, which an 8bit value can't, so b would have to be uint16
I've always thought this stupid, but never bothered to figure out a workaround until seeing it in ASM, recently. The equivalent of that ASM in C is:
uint8_t *hl = 0x8000;
uint8_t b = 0;
*hl = b;
} while (b!=0);
Now you iterate through all 256 values of b, *without* needing an int16 to do-so. Awesome!
BUT: it doesn't go in order.
so, my flash writing experiment shows 0 @ 0x8000, 255 @ 0x8001, 254@0x8002...1@0x80ff.
SO, if I was smarter, I think I coulda used bc for both the address /and/ the counter(!!!)
djnz only works with the 8bit b, I think, not the 16bit bc...
ld (bc), b
I think would do it... loading 0x8000 with 0, 0x8001 with 1...0x80ff with 0xff
BUT: it would do-so in reverse order AND with address zero first, 255 next, and end at address 1.
(Only thing... is c the high byte in bc?)
My point is: it's actually quite handy that in writing all the bytes in a sector, the chip doesn't care what order you send them! It's like they thought about this! Not particularly easy to implement, internally, either, as it means 256 8 bit registers have to each be individually addressable. A 256byte Static RAM, where they coulda just daisychained 256 8bit registers. Glad they thought of it.
OK, what was hard(er):
Apparently this chip has another feature to prevent undesired sector-rewrites... especially, e.g. when the CPU is in brown-out or reset, and plausibly /WE and /CE are measured active (and /OE inactive)... so, IF this software-protection feature is activated, then a normal sector write gets ignored. Instead, to (temporarily) deactivate this soft-protection, you first have to send the deactivation-key. Which consists of writing three values at three very different addresses (not in the same sector). These bus-writes Do Not trigger a flash-sector-write... until /after/ the unlock key completes. Now... coding to handle this with an unknown chip would be an ordeal, because, if I understand correctly, if that feature is /not/ active, the first write in the unlock sequence would begin a sector-write in whatever page that address is in. And an early-terminated sector-write would result in only that byte being written with any even remotely valid/recognizable data, and all the others would be random. Hah! So, if you tried to write, say, sector 0, by first writing the unlock key on an unlocked device, I think you'd wind-up writing garbage to a completely different page.
Handily, I tried the easiest case first. It resulted in nothing written, so then I coded up the unlock key. But, doing that process in software (determining whether to use the unlock key, without accidentally overwriting garbage to another sector) means quite a bit more code.
Then there's /another/ lock feature, boot-block protection, which at least apparently wasn't active on the sector I wrote (though could be on another sector).
I'm starting to see why my coworker's FLASH-programming code was so gnarly.
And, frankly, I chose this chip over a much nicer one in an identical package because this one's flash-programming routines seemed far simpler.
Now, it seems, that explains why somewhat generic operating systems (e.g. Linux) running on somewhat standard architectures (e.g. ARM, with a common memory-layout) still need friggin' drivers for a simple boot FLASH wired up like any other... and you might see "identifying FLASH... AT29C020" in dmesg. Crazy! All that for a memory interfaced like a standard memory to a standard memory-bus!
Now, turns out the addresses in the unlock key span multiple z80 pages, which means a lot of math to determine which FLASH page the address is in, map in that page, then remember that that is now mapped at 0x8000... so, e.g. one of those addresses is 0xAAAA on the FLASH, but that's FLASH page 1 (not 0), map page 1 to 0x8000, whoops, forgot writing requires adding 16 pages... open page 16 at 0x8000, but it's a FLASH page, so we need to set /CS3, so really we need to send 0xD1 to the memorymapper port, then... well, it's 14 bits that come from the z80, and bits 14 and 15 (and higher) come from the mapper, so we shift D1 two places to the right, OR that with the first 14 bits, so... uh... 0xAAAA becomes 0x9AAA on memory-map 0xD1, or something.
Now it's time to start thinking of something along the lines of TI's "Absolute Addressing" and the functions that decode it. Though, oddly, they /didn't/ math it, and instead used IF-THENs, so it doesn't work with memories other than they built into the TI-86. (And, in fact, it doesn't really work with the ROM, either, and in fact, it doesn't really correspond to the actual address bits on the actual bus. Hah!
So, if I code this up (which, frankly, I'm surprised I could even wrap my head around doing by hand the three addresses I needed, nevermind trying to generalize that), at least it could be used for /all/ the devices. /CE0=ROM is at 0x000000 (on the physical bus) /CE1=RAM is at 0x100000, /CE2 (unused) at 0x200000, /CE3=myFLASH at 0x300000. Really, not at all complicated externally. Just converting those to memory-mapping-port values and then to addresses in the z80's 64k... it'll happen. Kinda has to.
The FLASH chip has some data in it, at the higher pages. Oddly, the lower 3 pages are blank. I'm guessing this was some sort of boot rom for something which boots from its higher addresses (e.g. timeline-wise, maybe a 486's BIOS, or a 14.4k modem). For some reason these days I have a hard time just wiping things like this. Maybe it was some stupid modem, or maybe it was something vintage I scrapped for parts but now realize would be wothwhile to reassemble... (Oh, suddenly I vaguely recall a PLCC socket on my P150 laptop which I bought spare parts for planning to turn it into a pizza-box webserver... that was a nicely hackable system, real parallel port, and docking station, to boot!). So, not knowing where it's from, I might try to back it up... 256KB at 100bytes-per-second. HAH! (my write-attempts, so far, have been limited to the blank pages). (Yes, I have a chip programmer in storage, no, I'm not bouts to desolder this, nor plan to head that direction any time soon). That also means learning a bit more about TI's variables... which isn't a horrible thing. Though I'd planned to avoid it, the fact is there've been other reasons it could be handy, even though I might boot my own code from FLASH (via a boot-select switch swapping /CE0 and /CE3) "bare metal." Doing so might result in more than 96k of assembly libraries, etc... what'd I do then? Heh! Still using TI-OS for ZAC and such... maybe learning the TI variable system would be handy.
Also, I'm having some mental hurdles with my full RAM imaging idea... that won't restore things like all the z80 registers, etc. And even if I did that, the process of doing it would probably be quite difficult to do /without/ modifying other memory in the process (stack pushes, etc.)... AND ... a full restore would put the executing program's state back where it was when the backup was occurring... in other words, when trying to run the "restore" program, it would 'start up' mid backup, rather than at the beginning of the restore. Heh. Brain is not quite there, lately.
...I'm definitely feeling the "concept proven, time to move on to another project" vibe inching in... and there are some that are "pressing" (like what's this rattle in my engine, or the popping? Ugh, so far out of my realm.). Or, months ago my old boss asked for help with fixing one of our really old projects, which I couldn't do then due, nail-in-coffin, to lack of functioning computer, among /many/ other factors which this project was to hopefully help me whittle away at... and may've succeeded to some extent.
To keep this one going, I think I'd better think of some groovy hardware to add at /CS2, soon. (BTW, 1MB... that's enough for any 8-bit ISA card... hmmm... LOL, I might-could even plop this into the CPU socket on a PC/XT clone ala #Improbable AVR -> 8088 substitution for PC/XT )
Though, really, this was to be a start to getting back into "project mode" for a slew of other projects which have been backburnered. So, if this goes that way, it's still a win.
Oh yeah... a heart stopping moment yesterday at the end of HOURS of coding...
Stupidly just minutes before packing up, I tried to run my flash-writing program outside ASMIDE, and... it just froze solid, it seemed. Requiring battery removal. I thought I'd lost everything I'd written that day, (ASMIDE provides crash prevention and memory recovery!), thankfully I didn't. Still, I'm not wrapping my head around why it works fine in the IDE, but not outside.
Nothing doing until after I run a backup.