Kenny's Open DSKY Software
- KennysOpenDSKY
- Table Of Contents
- Description
- Acknowledgments
- Features
- Files
- Memory Usage
- Dependencies
- Compiling for Arduino Nano
- Compiling for Linux
- Compiling for Windows/MacOS
- Running the Curses Simulator
- DSKY Usage
- Using the Assembler
- Assembly Language
- Code Walk Through
- Green Acrylic Modification
- Review of the Open DSKY Kit
- Author
This project contains C/C++ source code for the Arduino nano which will control the Open DSKY kickstarter kit.
This project also contains source code for Linux/Windows/MacOS which
runs as a ncurses
text-based simulator of the arduino software. This
will be referred to as the curses simulator.
The curses simulator allows for testing and debugging the complete software without having to upload to the Arduino nano.
This is an original implementation of code needed to drive the Open DSKY hardware. However, I derived many ideas and code from these sources:
-
S&T Geotronics James Sanderson and Marc Tessier for creating the Open DSKY kit. http://www.stgeotronics.com
-
Scott Pavlovec github project. https://github.com/scottpav/OpenDSKY. This contained the reference implementation which I used to talk to the hardware.
-
The functionality offered was inspired by the functionality offered by the Apollo 50th Anniversary project which came pre-installed with the Open DSKY kit. (Apollo Education Experience Program, Cumming Georgia). Thank you Bill Walker for an incredible software creation and user manual. Github: https://github.com/ApolloExperience/OpenDSKY. Website 1: http://apolloexperience.com. Website 2: https://www.gofundme.com/apollo-education-experience-project.
-
The audio clips provided here are taken from the Apollo 50th Anniversary project SD card.
-
The following website has information about the virtual apollo guidance computer: http://www.ibiblio.org/apollo/.
-
An online Apollo DSKY emulator https://svtsim.com/moonjs/agc.html. I used this great tool to tweak my code to better reflect how an actual Apollo DSKY worked.
-
Keyboard debouncing logic comes from James Sanderson's code mentioned in this youtube video: https://www.youtube.com/watch?v=zMDSz0iKxIs&t=1726s This is an improvement over Scott Pavlovec's keyboard reading code.
-
Ron Diamond for his GPS reading code which removes the dependency on TinyGPS++. https://github.com/rondiamond/OpenDSKY
- A virtual machine and byte code interpreter.
- Two threads of control: One thread is a background task, which runs major modes (PROGs). The other thread is a foreground task which runs VERBs & NOUNs.
- Ability to enter values using the keypad instead of +/- keys.
- More accurately reproduces the Apollo DSKY interface.
- Curses Simulator - test and develop your code on your computer before uploading it to the Arduino.
- Better keyboard debounce logic.
- Allows for easy modifications to add custom apps to the DSKY.
- Strives to have minimal dependencies on Arduino libraries.
The main source code is in KennysOpenDSKY.cpp
and kennysagc.asm
.
KennysOpenDSKY.cpp
- the main source code file C/C++.kennysagc.asm
- the assembly code for the verb/noun/prog programs.kennysagc.h
- the assembled code. produced by runningassembler.py
.KennysOpenDSKY.ino
- an empty file to satisfy the Arduino CLI sketch requirements.KennysOpenDSKY.dump
- a dump of the AVR nano assembly code for the Arduino version.assembler.py
- Python 3 program which assembles the assembly code into byte codes.Makefile
- A simple makefile to compile on Linux and also compile/upload the sketch using the Arduino CLI tools.dsky
- The curses simulator executable produced on Linux.dsky_debug
- The curses simulator executable produced on Linux with debugging symbols (-g).log.txt
- a log file for debugging when running the curses simulator.persist.txt
- emulates the EEPROM and RTC RAM area when using the curses simulator.audio
- directory containing the SD card audio files for the MP3 player.images
- directory containing images used for documentation purposes. Also contains apdf
andpages
document for use with the Green Acrylic Modification.
The current build uses the following memory on the Arduino:
$ make sketch
Sketch uses 26440 bytes (86%) of program storage space. Maximum is 30720 bytes.
Global variables use 1115 bytes (54%) of dynamic memory, leaving 933 bytes for local variables.
Maximum is 2048 bytes.
Used library Version Path
Adafruit NeoPixel 1.12.0 /M/kjs/ARDUINO/Arduino/libraries/Adafruit_NeoPixel
LedControl 1.0.6 /M/kjs/ARDUINO/Arduino/libraries/LedControl
Wire 1.0 /M/kjs/ARDUINO/.arduino15/packages/arduino/hardware/avr/1.8.6/libraries/Wire
EEPROM 2.0 /M/kjs/ARDUINO/.arduino15/packages/arduino/hardware/avr/1.8.6/libraries/EEPROM
Used platform Version Path
arduino:avr 1.8.6 /M/kjs/ARDUINO/.arduino15/packages/arduino/hardware/avr/1.8.6
This section describes the third party libraries and components needed to compile the sketch for Arduino.
- Wire - standard arduino library. I2C communications.
- EEPROM - standard arduino library for reading/writing the 2K eeprom memory.
- Adafruit NeoPixel - library used to illuminate the neo pixels.
- LedControl - library used to talk to 7-segment LEDs.
- Arduino CLI tools for Linux - I don't use the Arduino IDE. I use
arduino-cli
tool. - python3 - this is needed to run
assembler.py
. The assembler is a simple text only python program which should work on most python installations.
This section describes the notable libraries & components needed to compile the curses simulator on Linux/MacOS/windows.
-
ncurses - this is a library for drawing simple text based user interfaces. It is widely available, and comes installed by default on most Linux distros.
-
sigaction() - this is capability of most unixes. It is included on all Linux operating systems. Special porting may be needed for Windows or MacOS. It is used to implement the 100ms timer.
-
getrandom() - this is a library function provdided by Linux. It is used to provide random numbers to the code for blinking the Uplink Acty and Comp Acty lights. As well as the random number assembly instruction.
-
python3 - this is needed to run
assembler.py
. The assembler is a simple text only python program which should work on most python installations.
I use the Arduino Command Line Interface. I run this under Linux on a Raspberry Pi.
(See https://github.com/arduino/arduino-cli)
The included Makefile
shows the commands needed to compile the sketch and
upload to your Open DSKY kit.
To assemble the assembly code use:
$ assembler.py kennysagc.asm
This will produce a file called kennysagc.h
which is included by KennysOpenDSKY.cpp
.
To compile the sketch use:
$ arduino-cli compile -e --fqbn arduino:avr:nano KennysOpenDSKY
To upload the sketch use:
$ arduino-cli upload -p /dev/ttyUSB0 --fqbn arduino:avr:uno KennysOpenDSKY
Replace /dev/ttyUSB0 with whatever is required on your system.
The provided simple Makefile
encapsulates these commands. This builds the sketch:
$ make sketch
This uploads the sketch to your Arduino Nano:
$ make upload
To compile on Linux gcc
is used. The program itself is written in simple C/C++.
(Does not use any fancy features of C++ beyond class
).
To assemble the assembly code use:
$ assembler.py kennysagc.asm
This produces a file caled kennysagc.h
which is included by KennysOpenDSKY.cpp
.
This builds the dsky
executable:
$ gcc -DCURSES_SIMULATOR -lncurses KennysOpenDSKY.cpp -o dsky
This builds the dsky_debug
executable (containing debug symbols):
$ gcc -DCURSES_SIMULATOR -DDSKY_DEBUG -g -lncurses KennysOpenDSKY.cpp -o dsky_debug
The provided simple Makefile builds both executables with this command:
$ make
To run the executable simply run it as follows (no command line arguments are needed):
$ ./dsky
or,
$ ./dsky_debug
or (when debugging),
$ gdb ./dsky_debug
I don't know how to compile for these platforms. Make sure
you have the ncurses
library available. Make sure you have sigaction() available.
Make sure you have getrandom(). Make sure you have python3. The compilation should
be pretty straight forward.
The curses simulator is a text based 'ncurses' application. You run the program from any text terminal and you will see a simple text screen that represents the DSKY display and DSKY keyboard.
Run with this command,
$ ./dsky
The keys map to your keyboard thusly. Only lower case keys are accepted.
0
...9
- Digits+
- Plus-
- Minusv
- VERBn
- NOUNp
- PRO (Proceed)c
- CLR (Clear)k
- KEY REL (Key Release)e
or[return]
- ENTR (Enter)r
- RSET (Reset)q
- Quit the DSKY simulator
The simulated keyboard at the bottom will highlight the most recent key that was pressed by the user.
The behavior of this program should be identical to the behavior it will have when run on the Arduino Open DSKY hardware. The GPS and IMU devices are simulated with fake data. The MP3 player only shows audio as a text line at the bottom of the window. The audio shows how many seconds remain in the audio clip. But no sound will play! The Real Time clock (RTC) is simulated but it uses the Linux date and time to initialize itself. The real time clock can be set by the user to a different date/time in the simulator.
Also emulated is the EEPROM storage and the real time clock 56 byte RAM area.
These values will be stored in the file persist.txt
so that they will
be remembered between runs of the curses simulator.
When running the curses simulator the file ./log.txt
is produced.
It is used for debugging purposes. Also contains the memory sizes of some data structures.
The file ./persist.txt
contains a simulated EEPROM storage and simulated RTC clock RAM.
This allows the curses simulator to retain information between runs of the program.
When this file does not exist then these memory areas are initialized to 0xFF
.
This section describes the general usage of the DSKY. The interface was modeled by my experimentation with a faithful DSKY simulator (See https://svtsim.com/moonjs/agc.html).
-
ENTR not required after each verb or noun entry. Pressing ENTR causes the DSKY to take action on the currently showing verb/noun fields.
-
KEY REL - When the KEY REL light is blinking, then the KEY REL button can be pressed to cancel the entry of a noun/verb.
-
OPR ERR - When OPR ERR light is blinking then the user should press RSET to reset this.
-
You cannot launch a program using the PRO key. Instead you must enter
V37
ENTR
and then type in a two digit program number which appears in the noun field. ThenENTR
again to launch the program. -
When decimal input is expected, the
+
or-
sign is required for the first input key. -
When octal input is expected, neither the
+
or-
sign is allowed. Only the digits0
-7
will be accepted. -
Pressing RSET will clear all the caution and warning lights (except KEY REL).
-
The CLR key can be pressed while entering values to clear the current entry and start over.
This section documents the available VERB/NOUN combinations and the PROGRAM's.
(updated 4/20/2024) Not all verb/nouns in the list are implemented yet. This is my list of what I wish to implement eventually. These items come from the Apollo 50th Anniversary project. I will update this section as I finish items.
The footnote [1] indicates items which have not been implemented yet.
VERB | Description |
---|---|
V01 | view memory |
V02 | test verb. enter R2 in decimal |
V03 | test verb. enter R3 in octal |
V04 | test verb incrementing/decrementing values |
V05 | clears DSKY, end verb, cleans stack |
V06 | Display Selected Value |
V09 | Calculate (Perform Math operation) |
V10 | Convert Radix |
V16 | Monitor Selected Values |
V21 | Enter value (R1 only) |
V22 | Enter value (R2 only) |
V25 | Enter values (R1 + R2 + R3) |
V26 | Load values from external source |
V35 | LAMP TEST |
V36 | Fresh Start |
V37 | Execute Major PROGRAM |
V69 | Force Restart |
V82 | Monitor Orbital Parameters1 |
NOUN | Description |
---|---|
N17 | IMU Linear Acceleration values (XXXX, YYYY, ZZZZ) |
N18 | IMU Gyro acceleration values (ROLL, PITCH, YAW) |
N19 | RTC DATE, TIME, IMU TEMP |
N31 | Time from AGC initialization |
N32 | Time from Perigee1 |
N34 | Timer count from event (HH, MM, SS.01)1 |
N35 | Timer count to Event (HH, MM, SS.01)1 |
N36 | RTC Time (HH, MM, SS.01) |
N37 | RTC Date (YYYY, MM, DD) |
N38 | GPS Time (HH, MM, SS.01) |
N39 | GPS Date (YYYY, MM, SS.01) |
N43 | GPS Longitude, Latutude (DD, MM SS.01) |
N44 | Orbital Parameters (Apocenter, Pericenter, time to free fall)1 |
N65 | MET (HH, MM, SS.01)1 |
N68 | Lunar Powered Decent1 |
N87 | IMU Accel values with random 1202 errors |
N98 | Audio Track and Index Adj (TTTTT, NNNNN) |
VERB-NOUN | Description |
---|---|
V01 N02 | View memory (R3=Address 0000-4067, R1=Value 000-377) octal |
V02 | test verb. enter R2 in decimal |
V03 | test verb. enter R3 in octal |
V04 | test verb. incrementing/decrementing values |
V05 | clears DSKY, end verb, cleans stack |
V06 N17 | Display IMU linear accel values |
V06 N18 | Display IMU gyro accel values |
V06 N19 | Display RTC Date/Time and IMU temp |
V06 N31 | Display time from AGC Init |
V06 N32 | Display time to perigee1 |
V06 N36 | Display RTC time |
V06 N37 | Display RTC date |
V06 N38 | Display GPS time |
V06 N39 | Display GPS date |
V06 N43 | Display GPS coordinates (press ENTR to switch coordinate) |
V06 N65 | Display met1 |
V09 N01 | Add stellar zeta angles. R3 = R1+R2 |
V09 N02 | Multiply Einstein gravitational cofactors. R3 = R1*R2 |
V09 N03 | Divide Feynman croc scalars. R3 = R1/R2 |
V09 N04 | Remainder R3 = R1 % R2 |
V10 N01 | Decimal to octal conversion. R2 = Octal(R1) |
V10 N02 | Octal to decimal conversion. R2 = Decimal(R1) |
V16 N17 | Monitor IMU linear accel values |
V16 N18 | Monitor IMU Gyro accel values |
V16 N19 | Monitor RTC Date/Time and IMU temp |
V16 N31 | Monitor time from agc init |
V16 N34 | Monitor/Stop Time From event1 |
V16 N35 | Monitor/Stop timer count to event1 |
V16 N36 | Monitor RTC Time |
V16 N37 | Monitor RTC Date |
V16 N38 | Monitor GPS time |
V16 N39 | Monitor GPS date |
V16 N43 | Monitor GPS coordinates |
V16 N44 | Monitor Orbital Parameters1 |
V16 N65 | Monitor MET1 |
V16 N68 | A11 Lunar Landing simulation1 |
V16 N87 | Monitor IMU linear accel values (with random 1202 alarms) |
V16 N98 | Play selected audio clip R1=clip, R2=index adj factor |
V21 N02 | Edit Memory (R3=address 0000-4067, R1=Value 000-377) octal |
V21 N98 | Select audio clip number to play |
V22 N98 | Enter index adj. factor |
V25 N34 | Set/Start timer count from event1 |
V25 N35 | Set/Start timer count to event1 |
V25 N36 | Set RTC Clock Manually |
V25 N37 | Set RTC Date Manually |
V26 N36 | Set RTC Clock from GPS |
V26 N37 | Set RTC Date from GPS |
V35 | Lamp test |
V36 | Fresh Start |
V37 N00 | P00 enter idle mode |
V37 N01 | P01 A11 Launch Simulation1 |
V37 N06 | P06 Simulates putting AGC into standby mode |
V37 N11 | P11 Monitor IMU Accel values |
V37 N42 | "blinky" - randomly illuminate caution and warning lights |
V37 N61 | P61 playback JFK i believe |
V37 N62 | P62 playback JFK We choose |
V37 N68 | P68 playback A8 Genesis |
V37 N69 | P69 playback A11 eagle has landed |
V37 N70 | P70 playback A11 we have a problem |
V69 | Force restart |
V82 | Monitor Orbital parameters1 |
To launch a program type: V37 ENTR
. The verb and noun fields will
begin blinking. Now enter one of these program numbers.
I.e. To run program P61 you would enter: VERB 3 7 ENTR 6 1 ENTR
PROG | Description |
---|---|
P00 | Poo |
P01 | Apollo 11 Launch Simulation1 |
P06 | Simulate putting AGC into standby mode |
P11 | Display IMU linear acceleration values (same as V16 N18) |
P42 | "blinky" - randomly illuminate caution and warning lights |
P61 | play short version of JFK "I believe" |
P62 | play short version of JFK "we choose" |
P68 | play short version of Apollo 8 genesis clip |
P69 | play Apollo 11 the eagle has landed clip |
P70 | play short version of Apollo 13 "problem" clip |
Enter octal address in R3. The byte value for the address will be displayed in R1. The 2K EEPROM memory area is accessed using the address range 0000 - 3777 (octal). The 56 byte RTC RAM area is accessed using the address range 4000 - 4067.
Enter octal address in R3. Then enter a byte value (0-377 octal) in R1. The byte value for the address will be written to the memory area. The 2K EEPROM memory area is accessed using the address range 0000 - 3777 (octal). The 56 byte RTC RAM area is accessed using the address range 4000 - 4067.
V16 N43 will query the GPS device and report the Latitude and Longitude. R1 will contains the degrees. R2 will contain minutes. R3 will contain hundred of seconds. Any running PROG will be terminated. The PROG field be used to indicate which coordinate is showing (latitude or longitude). When PROG reads 24 the latitude is showing. After 5 seconds longitude will be shown and PROG will read 25.
Conversion Formulas:
R1 contains degrees ddd (latitude: -90 ... +90, longitude: -180 ... +180)
R2 contains minutes mm (0 ... 59)
R3 contains hundreds of seconds ssss (0 ... 5999)
Convert to decimal degrees (ddd.dddddd):
If degrees are positive use: +ddd + mm/60 + ssss/360000
If degrees are negative use: -ddd - mm/60 - ssss/360000
Convert to degrees-decimal minutes (dddmm.mmmm):
If degrees are positive use: +ddd*100 + mm + ssss/6000
If degrees are negative use: -ddd*100 - mm - ssss/6000
Convert from decimal degrees:
given: ddd.dddddd Ie. -071.123456
DEGREES = ddd
MINUTES = round(dddddd/1000000 * 60)
HUNDREDS_OF_SECONDS = round( (dddddd/1000000 * 60 - MINUTES) * 6000 )
Negative Latitude indicates South "S"; positive means North "N".
Negative Longitude indicates West "W"; positive means East "E".
This function will display the GPS Time. The VERB/NOUN fields will blink 50 25. Pressing PRO will cause the showing values (assuming they are not 0) to be used to set the RTC Time. To cancel changes enter a different verb/noun. After pressing PRO the DSKY will show V16 N36.
This function will display the GPS Date. The VERB/NOUN fields will blink 50 25. Pressing PRO will cause the showing values (assuming they are not 0) to be used to set the RTC Time. To cancel changes enter a different verb/noun. After pressing PRO the DSKY will show V16 N37.
The program assembler.py
is a simple one pass assembler written in python.
You will need python3 to recompile the assembly. This github repository
however contains a pre-compiled version of the assembly.
$ assembler.py filename.asm
Kenny's OpenDSKY Apollo Guidance Computer Assembler
Little-endian encoding will be used.
Assembling 'filename.asm'.
Finished assembling 'filename.asm' -> 'filename.h'.
The output is filename.h
. This file contains the bytecodes in a Program[]
array.
This is the code that the virtual CPUs will be executing.
If your target architecture is a big endian computer (for example a Solaris or
IBM/AIX unix machine) you will need to switch the byte ordering to big endian.
The default is little endian. To enable big endian byte ordering use the -b
command
line option. Little endian is the appropriate byte ordering to use for
Linux machines and Arduino.
This section documents the virtual machine and its machine instructions.
Here is the CPU architecture of the virtual machine:
The CPU consists of three 32-bit registers called A, B and C. Different instructions operate on different registers. The C register has special indirect addressing modes associated with it, so it can be thought of as the "index" register. The A register can be thought of as the "accumulator", although many of the same instructions also work with the B register. These registers are 32-bits wide.
The stack pointer SP points into the RAM area. The RAM area consists of 128 memory locations each capable of storing a 32-bit value.
The program counter PC points into the Program[] array which contains the byte codes that were assembled using the assembler.
The program has access to a bank of RAM locations which are 32-bits wide. There are 128 RAM locations. This is to hold variables for the assembly programs. It also contains a small stack for each cpu.
The program byte codes are read-only. You can store data tables in this memory area. It
mostly contains the subroutines. VERBs are written in this assembly language. The
starting location for each VERB is stored in a Verbs[]
dispatch table in KennyOpenDSKY.cpp
.
Handling of the NOUNs is done by the code for each VERB. Each VERB is responsible for
checking the NOUN field and acting appropriately.
There are two CPU's defined. cpu 0 is the background thread. cpu 1 is the foreground thread. Both threads run concurrently. The background thread is designed to run major modes (or PROGs). The foreground task runs VERB/NOUN combinations.
The foreground task can be paused and a new foreground task stacked on top of the currently
running foreground task. For example, If V16 N36
is running to show the current time.
If the user runs the V35
(LAMP TEST) verb, then this will pause the current VERB/NOUN and
run the LAMP TEST code. When the LAMP TEST finishes the previous Verb/Noun will be resumed.
The following sections cover the assembly language syntax:
Use C++ //
style comments in the assembly code. I.e.,
Loop:
BRANCH Loop // loop forever
Labels are symbols which appear at the beginning of a line and followed by a colon :
.
CLR_A
LD_B_IMM8 100
AGAIN: ADD_A_IMM8 34 // A = A + 34
DEC_B
BRANCH_B_GT_IMM8 0 AGAIN
ENCODE_A_TO_DEC
MOV_A_R3
This code has one label AGAIN
. It is used as a branch target.
This code adds 34 to the A register one hundred times. Then
the contents of the A register are encoded into Binary Coded Decimal (BCD)
and written to DSKY display field R3.
The curly braces {
and }
are scope brackets. They must appear on a line
by themselves. Any labels defined within scoped brackets are removed from
the symbol table after the end scope bracket. This allows reusing symbols
within the body of a function.
VERB_34:
{ // begin scope
Loop:
GOTO Loop
} // end scope
VERB_35:
{ // begin scope
Loop: ADD_A_B
BRANCH Loop
} // end scope
The symbol Loop
was reused becuse it appears in seperate scope blocks.
The symbols VERB_34
and VERB_35
are not inside of scope brackets. These
symbols will be global (and can be referenced in the KennysOpenDSY.cpp file to
add to the Verbs[] dispatch table).
Scope brackets allow for the use of common label names in many places without having to invent different label names that are unique.
Scope brackets cannot be nested.
The assembler supports three directives: DATA8
, DATA16
and DEFINE
.
DEFINE MaxVal = 100
DEFINE JUNK = 0x45
DATA8 0xFF
DATA8 0x1F
DATA8 -120
Foo: DATA8 111
DATA16 SomeLabel
DATA16 0x1234
DATA16 65000
DATA8 MaxVal
The DATA
directives compile raw data into the Program instruction stream.
This can be used to implement lookup tables. The DEFINE
directive associates
a value with a symbol. For example instead of using 100
in your assembly code you
can say MaxVal
. DATA
directives may be preceeded by a label. I.e., Foo.
The instruction mnemonics use these suffixes to indicate the addressing modes/arguments:
_IMM8
- Takes an immediate 8-bit value_IMM16
- Takes an immediate 16-bit value_IMM32
- Takes an immediate 32-bit value_DIRECT
- Takes an 8-bit value which represents a RAM memory location_CINDIRECT
- The C register contains the address to RAM[C] or Program[C]._U
- the instruction operates in the unsigned domain_OCT
- the instruction operates using Octal radix instead of decimal
These are the types of arguments instructions can have:
<offset>
- An 8-bit signed value which is added to the program counter when the branch is taken.<imm8>
- An 8-bit value provided immediately in the Program byte code stream<imm16>
- A 16-bit value provided immediately in the Program byte code stream<imm32>
- A 32-bit value provided immediately in the Program byte code stream<addr>
- An 8-bit unsigned value which refers to a RAM location.<addr16>
- A 16-bit address to a Program[] location. Used for CALL and GOTO instructions.
In the assembly syntax, arguments are seperated by whitespace. Do not use commas or other punctuation in the assembly code.
Numeric literals can use signed decimal notation or unsigned hex notation. I.e.,
-4 // decimal literal 8-bits
1234 // decimal literal 16-bits
1234999 // decimal literal 32-bits
0x4E // hex literal (8-bit)
0x1f2b // hex literal (16-bit)
0x4E001F2F // 32-bit hex literal
The assembler knows the bit size the operands are supposed to be. So the numeric literal will be converted to the correct width. Each of the following instructions load A with the number 12. But the Program[] array will contain different sized immediate values.
LD_A_IMM32 12 // generates a 32-bit immediate value
LD_A_IMM16 12 // generates a 16-bit immediate value
LD_A_IMM8 12 // generates a 8-bit immediate value
The most space efficient encoding is the last instruction.
Here are all the assembly instructions:
Mnemonic | Arguments | Description |
---|---|---|
MOV_R1_A | Move the BCD encoded contents of DSKY register R1 into A | |
MOV_R2_A | Move the BCD encoded contents of DSKY register R2 into A | |
MOV_R3_A | Move the BCD encoded contents of DSKY register R3 into A | |
MOV_A_R1 | Move the BCD encoded contents of register A into DSKY register R1 | |
MOV_A_R2 | Move the BCD encoded contents of register A into DSKY register R2 | |
MOV_A_R3 | Move the BCD encoded contents of register A into DSKY register R3 | |
DECODE_A_FROM_OCT | Convert BCD encoding of octal value in A into a INTEGER | |
DECODE_A_FROM_DEC | Convert BCD encoding of decimal into a INTEGER | |
ENCODE_A_TO_OCT | Encode A INTEGER contents as BCD encoded octal | |
ENCODE_A_TO_DEC | Encode A INTEGER contents as BCD encoded decimal (plus/minus sign) | |
ENCODE_A_TO_UDEC | Encode Positive decimal value (No plus sign) into BCD | |
MOV_A_B | Move contents of A into B | |
MOV_A_C | Move contents of A into C | |
MOV_B_A | Move contents of B into A | |
MOV_B_C | Move contents of B into C | |
MOV_C_A | Move contents of C into A | |
MOV_C_B | Move contents of C into B | |
LD_A_DIRECT | addr | Load into the A register the contents of RAM at addr |
LD_B_DIRECT | addr | Load into the B register the contents of RAM at addr |
LD_C_DIRECT | addr | Load into the C register the contents of RAM at addr |
LD_A_IMM32 | imm32 | Load into the A register the immediate 32-bit value given by imm32 |
LD_B_IMM32 | imm32 | Load into the B register the immediate 32-bit value given by imm32 |
LD_C_IMM32 | imm32 | Load into the C register the immediate 32-bit value given by imm32 |
LD_A_IMM16 | imm16 | Load into the A register the immediate 16-bit value given by imm16 |
LD_B_IMM16 | imm16 | Load into the B register the immediate 16-bit value given by imm16 |
LD_C_IMM16 | imm16 | Load into the C register the immediate 16-bit value given by imm16 |
LD_A_IMM8 | imm8 | Load into the A register the immediate 8-bit value given by imm8 |
LD_B_IMM8 | imm8 | Load into the B register the immediate 8-bit value given by imm8 |
LD_C_IMM8 | imm8 | Load into the C register the immediate 8-bit value given by imm8 |
LD_A_CINDIRECT | Load A register with contents of RAM at address given by C | |
LD_B_CINDIRECT | Load B register with contents of RAM at address given by C | |
ST_A_DIRECT | addr | Store A register to the RAM location given by addr |
ST_B_DIRECT | addr | Store B register to the RAM location given by addr |
ST_C_DIRECT | addr | Store C register to the RAM location given by addr |
ST_A_CINDIRECT | Store A register to the RAM location given by C | |
ST_B_CINDIRECT | Store B register to the RAM location given by C | |
CLR_A | Clear the A register to a value of 0 | |
CLR_B | Clear the B register to a value of 0 | |
CLR_C | Clear the C register to a value of 0 | |
INC_A | Increment the A register by 1 | |
INC_B | Increment the B register by 1 | |
INC_C | Increment the C register by 1 | |
DEC_A | Decrement the A register by 1 | |
DEC_B | Decrement the B register by 1 | |
DEC_C | Decrement the C register by 1 | |
PUSH_A | Store the A register in RAM at address given by SP. Decrement SP | |
PUSH_B | Store the B register in RAM at address given by SP. Decrement SP | |
PUSH_C | Store the C register in RAM at address given by SP. Decrement SP | |
POP_A | Increment SP. Load the A register from RAM address given by SP | |
POP_B | Increment SP. Load the B register from RAM address given by SP | |
POP_C | Increment SP. Load the C register from RAM address given by SP | |
CALL | addr16 | Call subroutine at addr16. Push PC+3 on the stack |
RET | Pop program counter from the stack | |
GOTO | addr16 | Store addr16 into the program counter PC |
BRANCH | offset | Add signed 8-bit value to program counter |
BRANCH_A_GT_B | offset | If A > B then add the branch offset to PC. Else next instruction |
BRANCH_A_GE_B | offset | If A >= B then add the branch offset to PC. Else next instruction |
BRANCH_A_LE_B | offset | If A <= B then add the branch offset to PC. Else next instruction |
BRANCH_A_LT_B | offset | If A < B then add the branch offset to PC. Else next instruction |
BRANCH_A_EQ_B | offset | If A == B then add the branch offset to PC. Else next instruction |
BRANCH_A_NE_B | offset | If A != B then add the branch offset to PC. Else next instruction |
BRANCH_A_GT_DIRECT | addr offset | If A > RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_GE_DIRECT | addr offset | If A >= RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_LE_DIRECT | addr offset | If A <= RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_LT_DIRECT | addr offset | If A < RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_EQ_DIRECT | addr offset | If A == RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_NE_DIRECT | addr offset | If A != RAM[addr] then add offset to PC. Else next instr. |
BRANCH_A_GT_IMM8 | imm8 offset | If A > imm8 then add offset to PC. Else next instr. |
BRANCH_A_GE_IMM8 | imm8 offset | If A >= imm8 then add offset to PC. Else next instr. |
BRANCH_A_LE_IMM8 | imm8 offset | If A <= imm8 then add offset to PC. Else next instr. |
BRANCH_A_LT_IMM8 | imm8 offset | If A < imm8 then add offset to PC. Else next instr. |
BRANCH_A_EQ_IMM8 | imm8 offset | If A == imm8 then add offset to PC. Else next instr. |
BRANCH_A_NE_IMM8 | imm8 offset | If A != imm8 then add offset to PC. Else next instr. |
BRANCH_A_GT_IMM16 | imm16 offset | If A > imm16 then add offset to PC. Else next instr. |
BRANCH_A_GE_IMM16 | imm16 offset | If A >= imm16 then add offset to PC. Else next instr. |
BRANCH_A_LE_IMM16 | imm16 offset | If A <= imm16 then add offset to PC. Else next instr. |
BRANCH_A_LT_IMM16 | imm16 offset | If A < imm16 then add offset to PC. Else next instr. |
BRANCH_A_EQ_IMM16 | imm16 offset | If A == imm16 then add offset to PC. Else next instr. |
BRANCH_A_NE_IMM16 | imm16 offset | If A != imm16 then add offset to PC. Else next instr. |
BRANCH_B_GT_DIRECT | addr offset | If B > RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_GE_DIRECT | addr offset | If B >= RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_LE_DIRECT | addr offset | If B <= RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_LT_DIRECT | addr offset | If B < RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_EQ_DIRECT | addr offset | If B == RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_NE_DIRECT | addr offset | If B != RAM[addr] then add offset to PC. Else next instr. |
BRANCH_B_GT_IMM8 | imm8 offset | If B > imm8 then add offset to PC. Else next instr. |
BRANCH_B_GE_IMM8 | imm8 offset | If B >= imm8 then add offset to PC. Else next instr. |
BRANCH_B_LE_IMM8 | imm8 offset | If B <= imm8 then add offset to PC. Else next instr. |
BRANCH_B_LT_IMM8 | imm8 offset | If B < imm8 then add offset to PC. Else next instr. |
BRANCH_B_EQ_IMM8 | imm8 offset | If B == imm8 then add offset to PC. Else next instr. |
BRANCH_B_NE_IMM8 | imm8 offset | If B != imm8 then add offset to PC. Else next instr. |
BRANCH_B_GT_IMM16 | imm16 offset | If B > imm16 then add offset to PC. Else next instr. |
BRANCH_B_GE_IMM16 | imm16 offset | If B >= imm16 then add offset to PC. Else next instr. |
BRANCH_B_LE_IMM16 | imm16 offset | If B <= imm16 then add offset to PC. Else next instr. |
BRANCH_B_LT_IMM16 | imm16 offset | If B < imm16 then add offset to PC. Else next instr. |
BRANCH_B_EQ_IMM16 | imm16 offset | If B == imm16 then add offset to PC. Else next instr. |
BRANCH_B_NE_IMM16 | imm16 offset | If B != imm16 then add offset to PC. Else next instr. |
BRANCH_NOT_TIMER1 | offset | branch if timer1 (100ms) hasn't triggered, else next instruction |
BRANCH_NOT_TIMER2 | offset | branch if timer2 (200ms) hasn't triggered, else next instruction |
BRANCH_NOT_TIMER3 | offset | branch if timer3 (300ms) hasn't triggered, else next instruction |
BRANCH_NOT_TIMER4 | offset | branch if timer4 (1s) hasn't triggered, else next instruction |
BRANCH_NOT_TIMER5 | offset | branch if timer5 (2s) hasn't triggered, else next instruction |
SWAP_A_B | Swap registers A and B | |
SWAP_A_C | Swap registers A and C | |
SWAP_B_C | Swap registers B and C | |
ADD_A_B | A = A + B | |
ADD_B_A | B = B + A | |
SUB_A_B | A = A - B | |
SUB_B_A | B = B - A | |
MUL_A_B | A = A * B | |
MUL_B_A | B = B * A | |
DIV_A_B | A = A / B | |
DIV_B_A | B = B / A | |
MOD_A_B | A = A % B | |
MOD_B_A | B = B % A | |
AND_A_B | A = A & B | |
AND_B_A | B = B & A | |
OR_A_B | A = A | B | |
OR_B_A | B = B | A | |
OR_A_IMM32 | imm32 | A = A | imm32 |
OR_B_IMM32 | imm32 | B = B | imm32 |
AND_A_IMM32 | imm32 | A = A & imm32 |
AND_B_IMM32 | imm32 | B = B & imm32 |
LSHIFT_A_IMM8 | imm8 | A = A << imm8 |
LSHIFT_B_IMM8 | imm8 | B = B << imm8 |
RSHIFT_A_IMM8 | imm8 | A = A >> imm8 |
RSHIFT_B_IMM8 | imm8 | B = B >> imm8 |
NOT_A | A = ~A | |
NOT_B | B = ~B | |
NEG_A | A = -A | |
NEG_B | B = -B | |
MOV_A_VERB | Move LSB byte of register A into VERB DSKY field (assumed to be BCD encoded) | |
MOV_A_NOUN | Move LSB byte of register A into NOUN DSKY field (assumed to be BCD encoded) | |
MOV_A_PROG | Move LSB byte of register A into PROG DSKY field (assumed to be BCD encoded) | |
MOV_NOUN_A | Move NOUN DSKY field into LSB byte of register A | |
MOV_VERB_A | Move VERB DSKY field into LSB byte of register A | |
MOV_PROG_A | Move PROG DSKY field into LSB byte of register A | |
BLINK_VERB | imm8 | 1 or 0. enable/disable blinking of VERB digits |
BLINK_NOUN | imm8 | 1 or 0. enable/disable blinking of NOUN digits |
BLINK_PROG | imm8 | 1 or 0. enable/disable blinking of PROG digits |
BLINK_KEYREL | imm8 | 1 or 0. enable/disable blinking of KEYREL status light |
BLINK_OPRERR | imm8 | 1 or 0. enable/disable blinking of OPR ERR status light |
BLINK_R1 | imm8 | 1 or 0. enable/disable blinking of DSKY R1 digits |
BLINK_R2 | imm8 | 1 or 0. enable/disable blinking of DSKY R2 digits |
BLINK_R3 | imm8 | 1 or 0. enable/disable blinking of DSKY R3 digits |
LT_UPLINK_ACTY | imm8 | 1 or 0. turn on/turn off the UPLINK ACTY status light |
LT_NO_ATT | imm8 | 1 or 0. turn on/turn off the NO ATT status light |
LT_STBY | imm8 | 1 or 0. turn on/turn off the STBY status light |
LT_OPR_ERR | imm8 | 1 or 0. turn on/turn off the OPR ERR status light |
LT_KEY_REL | imm8 | 1 or 0. turn on/turn off the KEY REL status light |
LT_NA1 | imm8 | 1 or 0. turn on/turn off the NA1 status light |
LT_NA2 | imm8 | 1 or 0. turn on/turn off the NA2 status light |
LT_TEMP | imm8 | 1 or 0. turn on/turn off the TEMP status light |
LT_GIMBAL_LOCK | imm8 | 1 or 0. turn on/turn off the GIMBAL LOCK status light |
LT_PROG_ALRM | imm8 | 1 or 0. turn on/turn off the PROG ALRM status light |
LT_RESTART | imm8 | 1 or 0. turn on/turn off the RESTART status light |
LT_TRACKER | imm8 | 1 or 0. turn on/turn off the TRACKER status light |
LT_ALT | imm8 | 1 or 0. turn on/turn off the ALT status light |
LT_VEL | imm8 | 1 or 0. turn on/turn off the VEL status light |
LT_COMP_ACTY | imm8 | 1 or 0. turn on/turn off the COMP ACTY light |
LT_VERB | imm8 | 1 or 0. turn on/turn off the VERB light |
LT_NOUN | imm8 | 1 or 0. turn on/turn off the NOUN light |
LT_PROG | imm8 | 1 or 0. turn on/turn off the PROG light |
LT_ALL | imm8 | 1 or 0. turn on/turn off ALL status lights |
UPLINK_PROB_IMM8 | imm8 | set UPLINK ACTY random blink probability to imm8 0=off, 255=always on |
COMPACTY_PROB_IMM8 | imm8 | set COMP ACTY random blink probability to imm8 0=off, 255=always on |
GPS_READ_DIRECT | addr | Read GPS data into addr+0 ... addr+7 |
BRANCH_TIMESTAMP_LT | addr1 addr2 offset | branch if timestamp1 less than timestamp2 |
TIMESTAMP_DIFF_A | addr1 addr2 | diff timestamp1 in addr1 and timestamp2 in addr2 put result into A |
RTC_TIMESTAMP_DIRECT | addr | store entire RTC timestamp to addr+0 and addr+1 |
RTC_DAY_A | Read RTC and place DAY field into A register (BCD encoded) | |
RTC_YEAR_A | Read RTC and place YEAR field into A register (BCD encoded) | |
RTC_MON_A | Read RTC and place MONTH field into A register (BCD encoded) | |
RTC_HH_A | Read RTC and place HOURS field into A register (BCD encoded) | |
RTC_MM_A | Read RTC and place MINUTES field into A register (BCD encoded) | |
RTC_SS_A | Read RTC and place SECONDS field into A register (BCD encoded) | |
RTC_MEM_A_CINDIRECT | Read RTC RAM address [C] and place into A register | |
RTC_A_MEM_CINDIRECT | Write LSB of A register into RTC RAM address [C] | |
IMU_READ_DIRECT | addr | store IMU data into (addr+0) ... (addr+6) locations |
MP3_PLAY_A | play track number indicated by register A | |
EEPROM_WRITE_A_CINDIRECT | write A register byte to EEPROM[C] | |
EEPROM_READ_A_CINDIRECT | read into A register byte from EEPROM[C] | |
WAIT1 | pause CPU until 100ms timer triggers, then proceed to next instr. | |
WAIT2 | pause CPU until 200ms timer triggers, then proceed to next instr. | |
WAIT3 | pause CPU until 300ms timer triggers, then proceed to next instr. | |
WAIT4 | pause CPU until 1s timer triggers, then proceed to next instr. | |
WAIT5 | pause CPU until 2s timer triggers, then proceed to next instr. | |
INPUT_NOUN | Read a NOUN from the keyboard. A=value means good. A=-1 means bad | |
INPUT_R1 | Read a signed decimal number into R1. A=value good. A=-1 Bad | |
INPUT_R2 | Read a signed decimal number into R2. A=value good. A=-1 Bad | |
INPUT_R3 | Read a signed decimal number into R3. A=value good. A=-1 Bad | |
INPUT_R1_OCT | Read an octal value into R1. A=value good. A=-1 Bad | |
INPUT_R2_OCT | Read an octal value into R2. A=value good. A=-1 Bad | |
INPUT_R3_OCT | Read an octal value into R3. A=value good. A=-1 Bad | |
INPUT_PROCEED | Wait for PRO key (Proceed) to be pressed. A=0 PRO pressed. A=-1 Cancelled | |
INPUT_REQ_PROCEED | Wait for PRO key (Proceed) to be pressed. A=0 PRO pressed. (Required) | |
PROG8_A_CINDIRECT | A = Program[C] read byte from program memory | |
PROG16_A_CINDIRECT | A = Program[C] read 16-bit word from program memory C, C+1 | |
PROG32_A_CINDIRECT | A = Program[C] read 32-bit word from program memory C, C+1, C+2, C+3 | |
ADD_A_IMM8 | imm8 | Add signed byte imm8 to A register |
ADD_B_IMM8 | imm8 | Add signed byte imm8 to B register |
ADD_C_IMM8 | imm8 | Add signed byte imm8 to C register |
EMPTY_STACK | Reset the stack to be empty for the CPU core | |
RUN_PROG_A | cause cpu core 0 to run program located at 16-bit address in A. Reset stack | |
RUN_MINOR_A | cause cpu core 1 to run program located at 16-bit address in A. Reset stack | |
CALL_CINDIRECT | call a subroutine whose address is in the C register | |
PUSH_DSKY | push DSKY state on stack | |
POP_DSKY | pop DSKY state on stack | |
RANDOM_A | Populate A register with a random 16-bit value |
This is an example of what the assembly code looks like.
This is VERB_37
from the assembly file.
//
// Run major mode
//
VERB_37:
{
LD_A_IMM8 0xAA
MOV_A_NOUN // clear noun field
BLINK_VERB 1
BLINK_NOUN 1
INPUT_NOUN
LD_B_IMM16 0x00FF
AND_A_B
MOV_A_B
BRANCH_B_LT_IMM8 0 Error
LD_C_IMM16 Programs
Next: PROG8_A_CINDIRECTC
BRANCH_A_EQ_IMM8 -1 NotFound
INC_C
ST_A_DIRECT PROG_TMP1
PROG16_A_CINDIRECT
ADD_C_IMM8 2
BRANCH_B_EQ_DIRECT PROG_TMP1 Found
BRANCH Next
Programs: DATA8 0x00
DATA16 PROG_00
DATA8 0x01
DATA16 PROG_01
DATA8 0x02
DATA16 PROG_02
DATA8 0x60
DATA16 PROG_60
DATA8 0x61
DATA16 PROG_61
DATA8 0x62
DATA16 PROG_62
DATA8 0x68
DATA16 PROG_68
DATA8 0x69
DATA16 PROG_69
DATA8 0x70
DATA16 PROG_70
DATA8 -1
Found:
SWAP_A_B
// ENCODE_A_TO_DEC
MOV_A_PROG
SWAP_A_B
RUN_PROG_A
BRANCH Done
NotFound:
Error:
BLINK_OPRERR 1
Done:
BLINK_VERB 0
BLINK_NOUN 0
RET
}
The main function is called apollo_guidance_computer()
.
The main data structure that is manipulated is the variable Agc
.
The Agc
variable contains the DSKY state (both current and previous). It
also contains the two CPU cores and the RAM area. Plus some additional flags.
static APOLLO_GUIDANCE_COMPUTER Agc;
This is the main routine for the whole thing:
void apollo_guidance_computer()
{
agc_init();
for(;;)
{
agc_execute_cpu(0); // background thread
agc_execute_cpu(1); // foreground thread
ch = readKeyboard();
input = MAP_INPUT(ch);
new_state = StateMachine[Agc.state][input];
Agc.state = new_state;
switch(Agc.state) {
case S_ST:
...
}
// timer 1 action - blinking behavior and 'COMP ACTY' and 'UPLINK ACTY' flicker.
// timer 2 action NOT USED
// timer 3 action NOT USED
// timer 4 action NOT USED
// timer 5 action NOT USED
dsky_redraw(&Agc.dsky, &Agc.prev);
}
}
This function performs these steps:
- The
Agc
is initialized by the call toagc_init()
. - A forever loop that is the main event-display loop
- Execute the two CPU cores for one instruction.
agc_execute_cpu()
runs the CPU core for one instruction. - Check the keyboard. If a keyboard character has been recieved then
apply the new input to the state machine.
Agc.state
is the current state the DSKY is in. - Check the timer bits to see if the 100ms timer has triggered. If it has then:
- Handle blinking behavior for various DSKY elements that can be set to blink.
- Handle the flickering of the COMP ACTY and UPLINK ACTY lights.
- Redraw the DSKY state.
The way the display is redrawn is via the function dsky_redraw()
.
Two copies of the DSKY display state are stored. Agc.prev
contains
the DSKY display state most recently drawn. Agc.dsky
contains the
new DSKY display state that we wish to display.
dsky_redraw()
compares the old and new state. If nothing has changed
then nothing needs to be redrawn. Otherwise a series of if
statements
are executed to see what parts of the DSKY has changed.
The last step of dsky_redraw()
is to assign the current DSKY state to
the previous DSKY state.
Binary Coded Decimal (BCD) is used to show numbers on the DSKY display. BCD encoding is a number encoding format that differs from normal binary encoding of integers. Binary Coded Decimal uses 4-bit fields to encode each digit. The ten digits 0 through 9 are encoded into 4-bit fields. Since 4-bits can encode 16 unique values there are additional values that are not mapped to digits. For the DSKY I extended the BCD encoding to include three more symbols: SPACE, PLUS, MINUS
Bit Pattern | Hex | Dec | DSKY Symbol |
---|---|---|---|
0000 | 0x00 | 0 | zero '0' |
0001 | 0x01 | 1 | one '1' |
0010 | 0x02 | 2 | two '2' |
0011 | 0x03 | 3 | three '3' |
0100 | 0x04 | 4 | four '4' |
0101 | 0x05 | 5 | five '5' |
0110 | 0x06 | 6 | six '6' |
0111 | 0x07 | 7 | seven '7' |
1000 | 0x08 | 8 | eight '8' |
1001 | 0x09 | 9 | nine '9' |
1010 | 0x0A | 10 | SPACE ' ' |
1011 | 0x0B | 11 | plus '+' |
1100 | 0x0C | 12 | minus '-' |
1101 | 0x0D | 13 | not used |
1110 | 0x0E | 14 | not used |
1111 | 0x0F | 15 | not used |
A useful aspect of this encoding is the fact that HEX literals are also BCD literals. In other words, to represent 12345 for display on the DSKY the following hex literal can be used: 0x12345. This is a nice feature because it means BCD encoded literals are easy to read when they appear in the code.
Desired DSKY display | Hex Literal |
---|---|
" 06 22" | 0xA06A22 |
"+05930" | 0xB05930 |
"16" | 0x16 |
"-00451" | 0xC00451 |
" 23" | 0xAAAA23 |
" " | 0xAAAAAA |
The BCD symbols "+" (0xB) and "-" (0xC) are only allowed for the most signifigant digits of the R1, R2 and R3 DSKY fields. NOTE: Technically, the "-" symbol could be rendered for all DSKY digits but the code currently does not allow this.
This code reads the contents of DSKY register R1 and converts it to binary (two's compliment):
MOV_R1_A // move the 6 BCD encoded digits into the A register
DECODE_A_FROM_DEC // convert BCD encoding to two's compliment
This code writes the contents of the A register into the DSKY register R3:
LD_A_IMM16 -451 // load A with the binary value '-451'
ENCODE_A_TO_DEC // convert A to BCD encoding: 0xC00451
MOV_A_R3 // display -00451 in the DSKY display register R3
Octal conversion is also supported. When octal encoding/decoding is used only these BCD symbols are used: ' ', '0', '1', '2', '3', '4', '5', '6', '7'. These are the octal encoding/decoding instructions: DECODE_A_FROM_OCT, ENCODE_A_TO_OCT.
Using these instructions plus the bit masking and bit shifting instructions it is possible to craft various BCD encoded bit patterns for displaying numbers on the DSKY.
To add a new instruction to the virtual machine:
- Add your new instruction to
enum AGC_INSTRUCTION
inKennysOpenDSKY.cpp
. - Add your new instruction to the debug string array
Mnemonics[]
. - Add your new instruction to the associative array
InstructionTable
inassembler.py
. - Add a new case to the switch statement in the routine,
agc_execute_cpu()
. - And of course update the
README.md
file.
There is an array called Verbs[]
(in KennysOpenDSKY.cpp
) which contains all the
verbs that can be entered.
static const DISPATCH_ENTRY Verbs[] PROGMEM = {
{ 0x35, LBL_VERB_35 },
{ 0x01, LBL_VERB_01 },
{ 0x37, LBL_VERB_37 },
{ 0x02, LBL_VERB_02 },
{ 0x03, LBL_VERB_03 },
{ 0x04, LBL_VERB_04 },
{ 0x05, LBL_VERB_05 },
{ 0x16, LBL_VERB_16 },
{ 0x36, LBL_VERB_36 },
{ 0x69, LBL_VERB_69 },
};
To add a new verb, add a new entry to this table. The identifier LBL_VERB_03
is created
from the assembly files: kennysagc.asm
and kennysagc.h
. In this table you provide
the BCD encoded verb number and the starting location for the VERB code.
Nouns and Programs are not stored in any dispatch table. These features are all handled by the assembly code. Each VERB must handle the set of NOUNs it knows about.
Programs are launched by using the VERB 37 verb.
To add a new program edit, kennysagc.asm
and modify the subroutine VERB_37
.
In there is a table of program labeled Programs
which you can extend.
Now write your code as a seperate subroutine. This program can now be invoked
by entering the correct V37 Nxx
command.
Programs run in cpu core 0 concurrently with whatever is running in cpu core 1. cpu core 1 runs the user entered VERBs/NOUNs. This allows for the running of two seperate "jobs"; one in cpu 0 and another one in cpu 1.
This section describes my modification to the Open DSKY. I decided I didn't like how fuzzy the LED digits were. I decided to use a green acrylic window to make the LED region nicer to look at and avoid the fuzzyness and dimness of the original kit.
I bought this product from amazon.com:
Transparent Green Acrylic Sheet (12" x 20", 1/16" / 1.5mm)
(MakerStock Store)
Make sure the thickness is 1.5mm.
I measured the desired size and used a utility knife to cut the plastic. I used a metal ruler to maintain a straight line.
After 10 to 12 cuts I broke the plastic along the straight edge of a table. The plastic broke cleanly along the line I had scored with the knife. I fine tuned the size of the piece by shaving off plastic from the sides using the knife.
I did the same cutting operation to the provided clear plastic that came with the Open DSKY kit.
NOTE: I used duct tape to adhere my green acrylic sheet to the table I was working on as well as the ruler so that it wouldn't move.
I cut in half the sticker that came with the Open DSKY kit. I retained the left hand side of the sticker for the caution and warning lights pane. But I threw away the right hand side.
Using my laser printer I printed out screen_stuff.pdf
.
Then I used scissors to cut out a the black region plus VERB/NOUN/PROG text. I also cut out the block of plus symbols.
This I carefully positioned over the VERB/NOUN/PROG lights. I also overlayed the block of plus symbols over the plus 7-segment LEDs.
I used a small piece of scotch tape to hold these overlays in place while I assembled the display and bezel. Best results are achieved if the black ink covers anything inside that might reflect light.
This section contains my thoughts on the Open DSKY kit. The specific product I ordered was the DSKY Slim Kit.
Overall I loved it. It requires a lot of detail work for final assembly. However, this is a kit and as one of the creators says in a YouTube video: "A big part of the DIY is the Y. Do it yourself, not do it for me." https://www.youtube.com/watch?v=xPJfywL251g&t=2251s [at 7 minutes 30 seconds]
Final assembly required me to use a dremel grinding tool to carve out plastic from the back in order to ensure the front cover fits snuggly onto the cicruit board.
I didn't like the "sticker" which you use to cover over the beautiful 7-segment LED's, so I customized my device (see the section Green Acrylic Modification).
The electronics and provided circuit board were excellent. All the parts were well labeled. This kit makes for a great platform to play with the Arduino and program different devices: GPS, IMU, MP3 player and Real Time Clock.
Little details included with the Kit were also very nice. There were two stickers that you affix to the case which look like official NASA tracking signage. There was also a tiny little 3d-printed DSKY.
The assembly instructions were a little sparse. But this increased my feeling of satisfaction when I successfully built the thing. How to perform final assembly was not well documented. Having said that, the instructables website was pretty good with lots of pictures of each step. The instructions became sparse when the assembly of the case was concerned. The kit offered several logical points to test the device before final soldering of all components. For example you could test the lamps early on. Then the button lights, then the 7-segment LEDs.
Be careful with the buttons. Using wire cutters to cut off the plastic bump on all the buttons can result in inadvertently cutting the wire lead that illuminates the button. Thankfully they provide an extra button if you screw one of them up.
Make sure the GPS unit is flush with the circuit board when you solder it on.
All the components came in individual plastic and were labeled. There was a inventory sheet showing all the components. The kit arrived in good packaging. I definately felt the kit was worth the $600.
There are good online schematics. The sample source code is pretty gross, but also pretty easy to reverse engineer. Unfortunately, there lacks a schematic showing how the MP3 player was wired up.
In summary, I am very happy with the kit and feel I got my money's worth. I am an Apollo space nut, so a DSKY was always something I wanted. I am pleased that my Kenny's Open DSKY software will give me a way to experiment with this kit and build cool apps for it. It was fun trying to write software for a such a constrained system (The arduino Nano only has 30KB of program memory and 2KB of RAM; and it is an 8-bit CPU).
Ken Stauffer
New York, NY.
2/14/2024