-
Firmware Source Committed
12/12/2023 at 02:47 • 0 commentshttps://github.com/lambdamikel/picoram2090/tree/main/src/picoram_2090
I am omitting the TTS code for now though (i.e., the SPI Interface to the Epson S1V30120). -
PicoRAM 2090 got a link on the official Busch homepage!
12/05/2023 at 02:43 • 0 comments -
Final Demo Video: https://youtu.be/dFnKhUI47KU
11/26/2023 at 18:30 • 0 commentsIt's the end of the project as we know it - thanks for hanging on, folks!
-
GitHub updated! https://github.com/lambdamikel/picoram2090
11/25/2023 at 17:44 • 0 commentsThe GitHub repo and page have been updated. The repo also includes the Gerbers and firmware by now; firmware sources will come a bit later as I am still refactoring things a bit:
https://github.com/lambdamikel/picoram2090
Enjoy!
-
Final PicoRAM 2090 PCB
11/24/2023 at 05:15 • 0 commentsThe third revision of the PCB is the final one for now - I have fixed all shortcomings and am satisfied with it for now. Now, I'll make one more final demo video, and then upload of the sources and Gerbers to GitHub.
-
First PCB Assembled
11/19/2023 at 16:50 • 0 commentsThe good news is - it works!
The bad news is: I'll need one more round of PCB revisions; problems are:
- the power switch is blocking the Pico's USB port - I hence had to put in a temporary micro slide switch which is very flimsy and a bit of a hack.
- I also learned that the double throw 6pin DPDT power blue & white switches come in 7x7 and 8x8 mm; the 7x7 that I currently have don't fit the KiCAD footprints without pin bending. So I ordered the 8x8 ones.
- the silkscreen text for buttons and button legend is too small.
- the SRAM 2114 data lines are in reverse bit order - I had to change the firmware for now by including a reverse_bits on the data lines, but have fixed this already in the PCB.
- the voltage divider for the Microtronic's DISP line is 1k-1k instead of 1k-2k... I had to cut a trace and use one of the spare 2k resistors in one of the isolated resistor network DIPs with a bodge wire.
- I made a stupid mistake when I crimped the 2114 connector cable - it took me 2 hours before I finally identified the badly crimped cable shorting every adjacent pair of wires, questioning my design, as the main culprit why it wouldn't work at all at first. I had to de-solder the crimp connector from the board and messed up a trace, requiring one more bodge wire.
Well, the 2nd revision of the PCB is already in production, and this should be the final one!
Here are a few pics of the mess ups:
-
PCB Designed and in Production!
11/12/2023 at 16:19 • 0 commentsWaiting for the PCB to arrive...
-
RetroChallenge 2023/10 Log Entry #6 - Final Wrap-Up Demo Video
10/29/2023 at 07:48 • 0 commentsI achieved everything I wanted for this year's RetroChallenge 2023/10 - here is a final wrap up demo video:
So the last week I mostly spent time writing the demo programs you see in the video. Some of these graphics demos (involving trig) were created with the help of a bunch of little Lisp functions (see below) - the Microtronic cannot do trig functions (sin, cos) reasonably fast, so these were pre-computed with Lisp and materialized in the code. The Lisp-program generated Microtronic instructions were just put in a .MIC file and then put on the PicoRAM's SD card. In the end, it turned out to be a good design decision to utilize an SD card interface for memory dumps / .MIC file storage, as opposed to, say, an EEPROM - given that I can easily create and exchange .MIC programs / content with and from the PC using the SD card.
It might be a good idea to actually add extended op-codes just for trig, i.e., if I wanted to add simple Logo-turtle like graphics capabilities as op-codes, I will need fast trig from register memory as well. For now, I've run out of available vacuous op-codes, but it is of course possible to implement different sets of vacuous op-codes. There could be one set which includes op-codes for sound and speech, like the current set, and another set that utilizes the same vacuous op-codes for trig functions instead. I could use one of the PicoRAM push buttons to cycle through different op-code sets, just like I am doing now to swap memory banks.
Interesting side note: running some of these graphics demos, I noticed that the 2nd core, which is running the op-code extensions, display, user interface, SD card interface, speech, sound, .etc., wasn't fast enough during certain display line drawing operations to keep up with the Microtronic. As the PicoRAM has no way of halting the Microtronic (it only snoops the RAM bus!), if the extended op-codes such as drawing a line to the display take too long, the 2nd core might miss the next extended op-code if it's still busy executing the current operation (e.g., drawing the line). Hence resulting in (graphics) glitches.
Solution: I overclocked the Pico to 250 Mhz - all glitches are gone! This problem never occurred for the SRAM emulation running on the 1st core, only the 2nd core was problematic as it was implementing the extended op-codes, driving the display, etc. In general, the Pico is a great platform for this kind of work - fast and powerful. I don't think this would have been possible with the old AVR MCUs - the 2nd core is a blessing. And overclocking and using the 2nd core is insanely easy, even from C! It's definitely a platform / MCU I am going to use for future projects. Despite the slight annoying required level shifters, it's close to perfect.
Speaking of the op-codes - writing all these demo programs I tweaked the firmware and its op-codes here and there, just to make the programming more convenient. You don't really know how well a programming language or a set of op-codes works until you eat your own dog food! For example, I removed the original
50D SOUND PLAY FREQ <X><Y>
op-codes because I found it more useful to have an op-code that enables auto-echo to the speech synthesizer of text. With speech echo enabled, I can use the
506 DISP SHOW CHAR <LOW><HIGH>
op-code to drive the text display as well as the speech synthesizer.
The final (hah - or let's say latest, current) set of op-codes is hence the following:
0xx ENTER LITERAL DATA x 3Fx ENTER DATA FROM REG x 500 HEX DATA ENTRY MODE 501 DEC DATA ENTRY MODE 502 DISP CLEAR SCREEN 503 DISP TOGGLE UPDATE 504 DISP REFRESH 505 DISP CLEAR LINE 506 DISP SHOW CHAR 507 DISP CURSOR SET CURSOR LINE 508 DISP SET CURSOR 509 DISP PLOT 50A DISP LINE 50B DISP LINE FROM 50C DISP LINE TO 50D SOUND PLAY NOTE (SOUND OFF FIRST) 50E SPEAK DISP ECHO 50F SPEAK BYTE 70x SWITCH MEMORY BANK x
And here is the Lisp program that I used to generate some of these demo programs:
(in-package :cl-user) (defun sin-d (x) (sin (* (/ x 180) pi))) (defun cos-d (x) (cos (* (/ x 180) pi))) (defun invert (x) (let ((string (format nil "~2,'0X" x))) (format nil "~A~A" (elt string 1) (elt string 0)))) (defvar *adr* 0) (defun low-nibble (x) (let ((string (format nil "~2,'0X" x))) (format t "~2,'0X 0~A~A~%" (incf *adr*) (elt string 1) (elt string 1)))) (defun high-nibble (x) (let ((string (format nil "~2,'0X" x))) (format t "~2,'0X 0~A~A~%" (incf *adr*) (elt string 0) (elt string 0)))) (defun low-nibble0 (x) (let ((string (format nil "~2,'0X" x))) (format t "0~A~A~%" (elt string 1) (elt string 1)))) (defun high-nibble0 (x) (let ((string (format nil "~2,'0X" x))) (format t "0~A~A~%" (elt string 0) (elt string 0)))) (defun test (n) ;; polygon k-m (setf *adr* 0) (loop as a from 0 by (/ 360 n) do (when (>= a 360) (return)) (format t "~2,'0X F10~%" *adr*) (format t "~2,'0X 509~%" (incf *adr*)) (low-nibble (round (+ #x40 (* #x3f (cos-d a))))) (high-nibble (round (+ #x40 (* #x3f (cos-d a))))) (low-nibble (round (+ #x10 (* #x0f (sin-d a))))) (high-nibble (round (+ #x10 (* #x0f (sin-d a))))) (format t "~2,'0X 50B~%" (incf *adr*)) (loop as b from 0 by (/ 360 n) do (when (>= b 360) (return)) (low-nibble (round (+ #x40 (* #x3f (cos-d b))))) (high-nibble (round (+ #x40 (* #x3f (cos-d b))))) (low-nibble (round (+ #x10 (* #x0f (sin-d b))))) (high-nibble (round (+ #x10 (* #x0f (sin-d b)))))))) (defun test2 (n) ;; circle granularity n (let ((rx #x3f) (ry #x0f)) (format t "F10~%") (format t "509~%") (loop as a from 0 by (/ 360 n) do (when (> a (+ 360 (/ 360 n))) (return)) (low-nibble0 (round (+ #x40 (* rx (cos-d a))))) (high-nibble0 (round (+ #x40 (* rx (cos-d a))))) (low-nibble0 (round (+ #x10 (* ry (sin-d a))))) (high-nibble0 (round (+ #x10 (* ry (sin-d a))))) (when (> a 0) (format t "50C~%"))) (format t "F00~%"))) (defun test3 (n m) ;; with sound! and nested circles ;; not good... (let* ((rx #x3f) (ry #x0f) (ix (round (/ rx m))) (iy (round (/ ry m)))) (format t "F10~%") (dotimes (i m) (loop as a from 0 by (/ 360 n) as step from 0 by 1 do (format t "50D~%") (low-nibble0 i) (low-nibble0 step) (format t "509~%") (when (> a (+ 360 (/ 360 n))) (return)) (low-nibble0 (round (+ #x40 (* rx (cos-d a))))) (high-nibble0 (round (+ #x40 (* rx (cos-d a))))) (low-nibble0 (round (+ #x10 (* ry (sin-d a))))) (high-nibble0 (round (+ #x10 (* ry (sin-d a))))) (when (> a 0) (format t "50C~%"))) (decf rx ix) (decf ry iy)) (format t "502~%") (format t "C00~%"))) (defun test3a (n m) ;; with sound! and nested circles (let* ((rx #x3f) (ry #x0f) (ix (round (/ rx m))) (iy (round (/ ry m)))) (format t "F10~%") (dotimes (i m) (format t "50D~%") (low-nibble0 1) (low-nibble0 i) (format t "509~%") (loop as a from 0 by (/ 360 n) as step from 0 by 1 do (when (> a (+ 360 (/ 360 n))) (return)) (low-nibble0 (round (+ #x40 (* rx (cos-d a))))) (high-nibble0 (round (+ #x40 (* rx (cos-d a))))) (low-nibble0 (round (+ #x10 (* ry (sin-d a))))) (high-nibble0 (round (+ #x10 (* ry (sin-d a))))) (when (> a 0) (format t "50C~%"))) (decf rx ix) (decf ry iy)) (format t "502~%") (format t "C00~%"))) (defun test4 (n m) ;; nested circles animation (let* ((rx #x3f) (ry #x0f) (ix (round (/ rx m))) (iy (round (/ ry m)))) (format t "F10~%") (format t "503~%") (dotimes (i m) (format t "509~%") (loop as a from 0 by (/ 360 n) do (when (> a (+ 360 (/ 360 n))) (return)) (low-nibble0 (round (+ #x40 (* rx (cos-d a))))) (high-nibble0 (round (+ #x40 (* rx (cos-d a))))) (low-nibble0 (round (+ #x10 (* ry (sin-d a))))) (high-nibble0 (round (+ #x10 (* ry (sin-d a))))) (when (> a 0) (format t "50C~%"))) (format t "504~%") (decf rx ix) (decf ry iy)) (format t "502~%") (format t "503~%") (format t "C00~%"))) (defun test5 (n m step) ;; nested circle animation with step rotation for each (let* ((rx #x3f) (ry #x0f) (ix (round (/ rx m))) (iy (round (/ ry m))) (aoff 0)) (format t "F10~%") (format t "503~%") (dotimes (i m) (format t "509~%") (loop as a from 0 by (/ 360 n) do (when (> a (+ 360 (/ 360 n))) (return)) (let ((a (+ a aoff))) (low-nibble0 (round (+ #x40 (* rx (cos-d a))))) (high-nibble0 (round (+ #x40 (* rx (cos-d a))))) (low-nibble0 (round (+ #x10 (* ry (sin-d a))))) (high-nibble0 (round (+ #x10 (* ry (sin-d a)))))) (when (> a 0) (format t "50C~%"))) (format t "504~%") (decf rx ix) (decf ry iy) (incf aoff step)) (format t "502~%") (format t "503~%") (format t "C00~%"))) (defun string-to-ascii (string file) (format file "F10") (format file "~%502") (format file "~%50E") (format file "~%506") (dolist (x ;;(substitute #\Space #\Newline (coerce string 'list))) (remove #\Newline (coerce string 'list))) (let ((string (string-upcase (format nil "~2,'0X" (char-code x))))) (format file "~%0~A~A" (elt string 1) (elt string 1)) (format file "~%0~A~A" (elt string 0) (elt string 0))))) (defun page-bank-reader (pages) (let ((bank 0)) (dolist (page pages) (with-open-file (file (format nil "c:/temp/PAGE~A.MIC" bank) :direction :output :if-exists :supersede) (string-to-ascii page file) (incf bank) (format file "~%50F") (format file "~%0AA") (format file "~%000") (format file "~%FF0") (format file "~%70~X" bank) (format file "~%C00~%~%"))))) (page-bank-reader (list "Hi. Nice to meet you. I am the Microtronic 2090 Computer System." "I was created in 1981 by the ELO magazine and the company Busch." "My CPU is a 4bit T M S 1600 micro controller. And I have a RAM of" "256 12bit words. I speak a custom machine language and I am big in" "education. With my PicoRAM I can now do things that ought to be" "impossible for me. My master LambaMikel loves me. Bye bye now.")) (defun spiral (n m ) ;; draw a spiral (let* ((rx #x3f) (ry #x0f) (irx (/ rx (* n m))) (iry (/ ry (* n m)))) (format t "F10~%") (format t "509~%") (loop as a from 0 by (/ 360 n) as step from 1 to (* n m) do (low-nibble0 (round (+ #x40 (* (round rx) (cos-d a))))) (high-nibble0 (round (+ #x40 (* (round rx) (cos-d a))))) (low-nibble0 (round (+ #x10 (* (round ry) (sin-d a))))) (high-nibble0 (round (+ #x10 (* (round ry) (sin-d a))))) (when (> a 0) (format t "50C~%")) (decf rx irx) (decf ry iry)) (format t "502~%") (format t "C00~%")))
-
RetroChallenge 2023/10 Log Entry #5 - DS3231 for Microtronic
10/22/2023 at 17:12 • 0 commentsIn my original RetroChallenge 2023/10 participation announcement, I promised a battery backed-up Real Time Clock (RTC) for the Microtronic. Well, here it is! I have hooked up the DS3231 RTC.
I did not use any of the DS3231 Pico SDK C libraries found online literally, but I had used this module before in some of my previous projects (e.g., https://hackaday.io/project/177784-lambdaspeak-futuresoft-edition) and hence didn't have a lot of issues getting it to work. Most helpful I found https://github.com/bnielsen1965/pi-pico-c-rtc/blob/master/ds3231.c and I took bits and pieces of it, but heavily refactored - thanks, good code, Bryan Nielsen (https://github.com/bnielsen1965)! Nice and simple - I really dislike when people create a huge CPP framework for something that is not rocket science.So how does that work? See, the Microtronic already has an internal real time clock in its firmware, and there is even an op-code F06 ("load time") that can be used to load the current time to register memory (registers for A-B for hours, C-D for minutes, and E-F for seconds). Two built-in ROM programs (PGM 3 and PGM 4) also allow you to set and see the current time (without using the F06 op-code).
However, the Microtronic RTC is not battery backed-up, and hence if you want to use the real time in your programs, you will always have to set the clock manually when you turn it on (using the ROM program PGM 3). This of course means limited utility.
How can the DS3231 RTC help here? Well, using similar ideas as explained in my previous log entry https://hackaday.io/project/192655-picoram-2090/log/224467-retrochallenge-202310-log-entry-3, instead of adding extra side-effects / semantics to vacuous op-codes, I can also intercept a non-vacuous op-code such as F06 ("load time"), and do anything I want by switching first into a dedicated memory bank to not interfere with the current program, then execute this program with the Microtronic, and finally return to the original program. When F06 is intercepted, the Pico first retrieves the current time from the DS3231, and then materializes a set of MOVI nx = Move Immediate Value n to Register x (op-code 1nx) into the current memory bank that will, when executed by the Microtronic, write the current DS3231 time into the A to F registers, overwriting the values that F06 had already left there. After returning from the banked-in sub-program, the registers A-F contain the correct time.
Here is a demo video:
Again, the underlying mechanism that loads the DS3231 time is entirely transparent to the calling program - from a user program's perspective, there is no difference in accessing the DS3231 vs. accessing the internal Microtronic RTC - the same single op-code is used, nothing else is required. This is the program from the demo:
00 F6A # display "time" registers A..F on the LED display 01 F06 # load time from RTC 02 FF0 # wait for keyboard input 03 F08 # clear registers 04 C00 # goto 00
-
RetroChallenge 2023/10 Log Entry #4 - DEMO
10/21/2023 at 14:06 • 0 commentsHere is a first "all-in-one" demo - SD card interface, graphics, text, speech!
Here is the program:
00 F08 # clear registers 01 F20 # display registers 0 and 1 on the LED display 02 50A # enable draw line (x1, y1) - (x2, y2) mode; display (0,0) - (x7F,x1F) 03 000 # send x1 low nibble: 0 04 3F0 # send x1 high nibble: value in register 0 05 000 # send y1 low nibble: 0 06 000 # send y1 high nibble: 0 07 000 # send x2 low nibble: 0 08 3F1 # send x2 high nibble: value in register 1 09 0FF # send y2 low nibble: F 0A 011 # send y2 high nibble: 1 -> x1F (32 4*8 = 32) 0B 520 # add to to register 0 0C 980 # reg 0 = 8? 0D E0F # yes, then goto x0F 0E C02 # no, goto x02 0F 100 # reg 0 = 8: clear register 0 (to 0) 10 521 # add 2 to register 1 11 981 # reg 1 = 8? 12 E14 # yes, then goto x14 13 C02 # no, goto x02 14 50E # register 2 = 8, end of nested reg 0, 1 loop. enable speak echo mode. 15 506 # enable print ASCII char to display with speech echo 16 0DD # send ASCII codes for MICROTRONIC (x4D, x49, x43, ...); M 17 044 18 099 # I 19 044 1A 033 # C 1B 044 1C 022 # R 1D 055 1E 0FF # O 1F 044 20 044 # T 21 055 22 022 # R 23 055 24 0FF # O 25 044 26 0EE # N 27 044 28 099 # I 29 044 2A 033 # C 2B 044 2C 0AA # LF (0x0A) -> speech synth will speak MICROTRONIC 2D 000 2E F00 # end of program, halt