Thank you!
Sorry - been totally overloaded with work for the past few weeks, dropping by for a quick update & some bragging.
V15 board assemblyWhile waiting for 4004 LCD from AliExpress, I was lucky to find a used one at local bulletin board for ~5 USD. So now I had everything I needed for a final assembly! Here's what it turned out like.
I've ordered a new board from my local vendor. This one cost me ~30 USD and took 3 days to make, and as I mentioned - it's a 15-minute drive from my home, so I'm really lucky. The blue silk screen looks so much nicer than the previous green one:
Attachment:
v15_pcb_irl.jpg [ 220.18 KiB | Viewed 66909 times ]
I'm a sucker for those pristine looks of a fresh PCB with basic sockets soldered:
Attachment:
v15_pcb_irl2.jpg [ 194.19 KiB | Viewed 66909 times ]
Soldering finished, taking one last look on those beautiful chips before I hide them beneath the LCD:
Attachment:
v15_final_naked.jpg [ 222.06 KiB | Viewed 66908 times ]
All done, trying to pose the board with some better lighting (I suck at taking photos):
Attachment:
v15_irl3.jpg [ 169.53 KiB | Viewed 66909 times ]
And - done. BIG SCREEN!
Attachment:
v15_final.jpg [ 236.78 KiB | Viewed 66909 times ]
As you may have noticed - I eventually ditched the Aries low-profile ZIF socket in favor of a standard bulky ZIF. Main reason - Aries has a really nice hi-tech look and is extra-small, which is great for PCBs with limited space. However it's painfully inconvenient to use, especially when constantly re-flashing my EPROM, and also makes it much easier to misplace a chip and bend some legs. Standard ZIF, on the other hand, is much more convenient: I simply throw the chip on it, and it always lands perfectly.
Those damn Chinese "standards"I had to use taller standoffs (~0.8") since the pinout of LCD I have doesn't match the one I'm still waiting for. So I had to bend the legs as such:
Attachment:
v15_legs.jpg [ 130.51 KiB | Viewed 66909 times ]
This stole around 0.4" of space, but hey - I'm good with it for now. I still think I can go down to 0.4" with a properly matching LCD footprint.
Another pain was LCD pins: some 4004 LCDs have 18 pins, and some have 16 (backlight power is on the opposite side of the LCD PCB, as seen in my photos above).
What's even worse -
some Chinese LCDs have pins 1-8 correspond to D0..D7, and some - to D7..D0. This is absolutely terrible.
Luckily, all other pins match. So here's where 4-bit LCD interface shines: I can simply connect pins 1-4 to pins 8-5! Since I had only half of data lines connected (obviously, the wrong half), all I had to do was to add some jumpers to mitigate this:
Attachment:
v15_lcd_mirror.png [ 30.1 KiB | Viewed 66909 times ]
This way the socket will handle any ordering, so I can buy all the LCDs (unless the manufacturers mess things up even more!)
"Life is too short to write in Assembly"Don't get me wrong, I've always loved assembly. However sometimes I really want to do some quick prototyping with good old C. Some would argue that life, in fact, is too short to write even in C!
I had to tinker for few days in order to make a proper config file for CA65 & CL65, and most importantly - to make kernel's routines properly callable from C programs. Took me a while to realize that CC65 actually uses software stack.
I ended up reusing a single CC65 config file for both the ROM and the C programs:
Code:
FEATURES {
STARTADDRESS: default = $1000;
}
MEMORY {
ZP: start = $0000, size = $0100;
RAM: start = $0200, size = $7E00; # 32K - zeropage
PROGRAM: start = $1000, size = $6000; # C programs are loaded here, since $0100-$1000 is reserved for kernel - video memory, FAT16, etc.
STACK: start = $7000, size = $1000, define = yes; # For C programs
LOROM: start = $8000, size = $4000, fill=yes, fillval=$EA, file="rom.bin"; # Used by "OS" - MicroREPL
N_C: start = $C000, size = $1000, fill=yes, file="rom.bin";
IO: start = $D000, size = $1000, fill=yes, file="rom.bin";
HIROM: start = $E000, size = $1FF0, fill=yes, fillval=$EA, file="rom.bin"; # Used by kernel
VECTORS: start = $FFF0, size = $0010, fill=yes, fillval=$EA, file="rom.bin";
}
SEGMENTS {
ZEROPAGE: load = ZP, type = zp;
RAM: load = RAM, type = bss; # First $0E00 bytes used by kernel
CODE: load = PROGRAM, type = ro; # C programs
RODATA: load = PROGRAM, type = ro; # C programs
DATA: load = PROGRAM, type = rw; # C programs
BSS: load = PROGRAM, type = rw; # C programs
IO: load = IO, type = bss, align=$100; # VIA & ACIA
SYSTEM: load = LOROM, type = ro, align=$100;
KORE: load = HIROM, type = ro, align=$100;
VECTORS: load = VECTORS, type = ro;
}
Additionally, I wanted my kernel (which I called "Kore", like another popular kernel with a "typo" in its name) to own the zeropage, including 26 bytes that CC65 needs for software stack, pointers, etc.
Eventually I've managed to glue everything together, so now I can write programs in C, compile them with CL65 (which automatically includes all the "magic" `libsrc` functions for stack/comparison/arithmetics, etc), copy to my FAT16-formatted SD Card, stick it into my SBC, and run everything! All my code is on GitHub as always: `rom` folder contains the kernel ("Kore") and the "OS" ("MicroREPL"), and `sdcard` code contains C programs.
Also, now I don't need to reboot my SBC after the program terminates, because it's using its own zeropage section, plus ~32 KB of RAM that's not used by the kernel (minus 160 bytes of 4004 LCD "video memory", several hundred bytes for FAT cache, etc), so the application can simply return back to my MicroREPL "OS" and I can then run more applications. This gives me some "DOS" look-and-feel, where applications cooperate with OS, instead of yeeting the latter out of existence and requiring a reboot to get back into the OS.
Of course, I couldn't resist writing a simple Snake game to celebrate this:
https://youtu.be/boeysL1Isg4Great success!
EDIT: The major downside of writing in C for 6502 is that programs end up being huge: the 200-line C code of my (terribly unoptimized) Snake game compiles into a 3.5 (!) KB binary. Of course, most of it is juggling with software stack. Also, I could probably reduce it a bit by moving all helper routines (pushax, etc) into kernel and link them during C program compilation.
So yeah, CL65 is awesome, but it's nowhere near as good as a human writing in actual 6502 assembly. Looks like C only shines when it has plenty of large CPU registers to deal with.
I've also been looking at Forth recently. It looks like the best candidate for writing both high-level and size-friendly code for 6502, since it doesn't need many registers and heavily relies on stack. I might give it a try in future.
Thanks for reading!
/Andrew