This allows us to write to an AT28 EEPROM from within collapse os.pull/10/head
@@ -0,0 +1,24 @@ | |||
; at28w - Write to AT28 EEPROM | |||
; | |||
; Write data from the active block device into an eeprom device geared as | |||
; regular memory. Implements write polling to know when the next byte can be | |||
; written and verifies that data is written properly. | |||
; | |||
; Optionally receives a word argument that specifies the number or bytes to | |||
; write. If unspecified, will write until max bytes (0x2000) is reached or EOF | |||
; is reached on the block device. | |||
; *** Requirements *** | |||
; blkGetC | |||
; parseArgs | |||
; | |||
; *** Includes *** | |||
#include "user.h" | |||
#include "err.h" | |||
.org USER_CODE | |||
.equ AT28W_RAMSTART USER_RAMSTART | |||
jp at28wMain | |||
#include "at28w/main.asm" |
@@ -0,0 +1,76 @@ | |||
; *** Consts *** | |||
; Memory address where the AT28 is configured to start | |||
.equ AT28W_MEMSTART 0x2000 | |||
; Value mismatch during validation | |||
.equ AT28W_ERR_MISMATCH 0x10 | |||
; *** Variables *** | |||
.equ AT28W_MAXBYTES AT28W_RAMSTART | |||
.equ AT28W_RAMEND AT28W_MAXBYTES+2 | |||
; *** Code *** | |||
at28wMain: | |||
ld de, .argspecs | |||
ld ix, AT28W_MAXBYTES | |||
call parseArgs | |||
jr z, at28wInner | |||
; bad args | |||
ld a, SHELL_ERR_BAD_ARGS | |||
ret | |||
.argspecs: | |||
.db 0b111, 0b101, 0 | |||
at28wInner: | |||
ld hl, (AT28W_MAXBYTES) | |||
ld b, h | |||
ld c, l | |||
ld hl, AT28W_MEMSTART | |||
call at28BCZero | |||
jr nz, .loop | |||
; BC is zero, default to 0x2000 (8x, the size of the AT28) | |||
ld bc, 0x2000 | |||
.loop: | |||
call blkGetC | |||
jr nz, .loopend | |||
ld (hl), a | |||
ld e, a ; save expected data for verification | |||
; initiate polling | |||
ld a, (hl) | |||
ld d, a | |||
.wait: | |||
; as long as writing operation is running, IO/6 will toggle at each | |||
; read attempt. We know that write is finished when we read the same | |||
; value twice. | |||
ld a, (hl) | |||
cp d | |||
jr z, .waitend | |||
ld d, a | |||
jr .wait | |||
.waitend: | |||
; same value was read twice. A contains our final value for this memory | |||
; address. Let's compare with what we're written. | |||
cp e | |||
jr nz, .mismatch | |||
inc hl | |||
dec bc | |||
call at28BCZero | |||
jr nz, .loop | |||
.loopend: | |||
; We're finished. Success! | |||
xor a | |||
ret | |||
.mismatch: | |||
ld a, AT28W_ERR_MISMATCH | |||
ret | |||
at28BCZero: | |||
xor a | |||
cp b | |||
ret nz | |||
cp c | |||
ret | |||
@@ -23,6 +23,7 @@ disabled. | |||
This recipe is for installing a minimal Collapse OS system on the RC2014. There | |||
are other recipes related to the RC2014: | |||
* [Writing to a AT28 from Collapse OS](eeprom/README.md) | |||
* [Accessing a MicroSD card](sdcard/README.md) | |||
* [Assembling binaries](zasm/README.md) | |||
@@ -0,0 +1,10 @@ | |||
TARGET = os.bin | |||
ZASM = ../../../tools/zasm.sh | |||
KERNEL = ../../../kernel | |||
APPS = ../../../apps | |||
.PHONY: all | |||
all: $(TARGET) | |||
$(TARGET): glue.asm | |||
$(ZASM) $(KERNEL) $(APPS) < $< > $@ | |||
@@ -0,0 +1,83 @@ | |||
# Writing to a AT28 from Collapse OS | |||
## Goal | |||
Write in an AT28 EEPROM from within Collapse OS so that you can have it update | |||
itself. | |||
## Gathering parts | |||
* A RC2014 Classic that could install the base recipe | |||
* An extra AT28C64B | |||
* 1x 40106 inverter gates | |||
* Proto board, RC2014 header pins, wires, IC sockets, etc. | |||
## Building the EEPROM holder | |||
The AT28 is SRAM compatible so you could use a RAM module for it. However, | |||
there is only one RAM module with the Classic version of the RC2014 and we | |||
need it to run Collapse OS. | |||
You could probably use the 64K RAM module for this purpose, but I don't have one | |||
and I haven't tried it. For this recipe, I built my own module which is the same | |||
as the regular ROM module but with `WR` wired and geared for address range | |||
`0x2000-0x3fff`. | |||
If you're tempted by the idea of hacking your existing RC2014 ROM module by | |||
wiring `WR` and write directly to the range `0x0000-0x1fff` while running it, | |||
be aware that it's not that easy. I was also tempted by this idea, tried it, | |||
but on bootup, it seems that some random `WR` triggers happen and it corrupts | |||
the EEPROM contents. Theoretically, we could go around that my putting the AT28 | |||
in write protection mode, but I preferred building my own module. | |||
I don't think you need a schematic. It's really simple. | |||
## Building the kernel | |||
For this recipe to work, we need a block device for the `at28w` program to read | |||
from. The easiest way to go around would be to use a SD card, but maybe you | |||
haven't built a SPI relay yet and it's quite a challenge to do so. | |||
Therefore, for this recipe, we'll have `at28w` read from a memory map and we'll | |||
upload contents to write to memory through our serial link. | |||
`at28w` is designed to be ran as a "user application", but in this case, because | |||
we run from a kernel without a filesystem and that `pgm` can't run without it, | |||
we'll integrate `at28w` directly in our kernel and expose it as an extra shell | |||
command (renaming it to `a28w` to fit the 4 chars limit). | |||
For all this to work, you'll need [glue code that looks like this](glue.asm). | |||
Running `make` in this directory will produce a `os.bin` with that glue code | |||
that you can install in the same way you did with the basic RC2014 recipe. | |||
If your range is different than `0x2000-0x3fff`, you'll have to modify | |||
`AT28W_MEMSTART` before you build. | |||
## Writing contents to the AT28 | |||
The memory map is configured to start at `0xd000`. The first step is to upload | |||
contents at that address as documented in ["Load code in RAM and run it"][load]. | |||
You have to know the size of the contents you've loaded because you'll pass it | |||
as at argument to `a28w`. You can run: | |||
Collapse OS | |||
> bsel 0 | |||
> seek 00 0000 | |||
> a28w <size-of-contents> | |||
It takes a while to write. About 1 second per byte (soon, I'll implement page | |||
writing which should make it much faster). | |||
If the program doesn't report an error, you're all good! The program takes care | |||
of verifying each byte, so everything should be in place. You can verify | |||
yourself by `peek`-ing around the `0x2000-0x3fff` range. | |||
Note that to write a single byte to the AT28 eeprom, you don't need a special | |||
program. You can, while you're in the `0x2000-0x3fff` range, run `poke 1` and | |||
send an arbitrary char. It will work. The problem is with writing multiple | |||
bytes: you have to wait until the eeprom is finished writing before writing to | |||
a new address, something a regular `poke` doesn't do but `at28w` does. | |||
[load]: ../../../doc/load-run-code.md | |||
@@ -0,0 +1,73 @@ | |||
; classic RC2014 setup (8K ROM + 32K RAM) and a stock Serial I/O module | |||
; The RAM module is selected on A15, so it has the range 0x8000-0xffff | |||
.equ RAMSTART 0x8000 | |||
.equ RAMEND 0xffff | |||
.equ ACIA_CTL 0x80 ; Control and status. RS off. | |||
.equ ACIA_IO 0x81 ; Transmit. RS on. | |||
jp init | |||
; interrupt hook | |||
.fill 0x38-$ | |||
jp aciaInt | |||
#include "err.h" | |||
#include "core.asm" | |||
#include "parse.asm" | |||
.equ ACIA_RAMSTART RAMSTART | |||
#include "acia.asm" | |||
.equ MMAP_START 0xd000 | |||
#include "mmap.asm" | |||
.equ BLOCKDEV_RAMSTART ACIA_RAMEND | |||
.equ BLOCKDEV_COUNT 1 | |||
#include "blockdev.asm" | |||
; List of devices | |||
.dw mmapGetC, mmapPutC | |||
.equ STDIO_RAMSTART BLOCKDEV_RAMEND | |||
#include "stdio.asm" | |||
.equ AT28W_RAMSTART STDIO_RAMEND | |||
#include "at28w/main.asm" | |||
.equ SHELL_RAMSTART AT28W_RAMEND | |||
.equ SHELL_EXTRA_CMD_COUNT 5 | |||
#include "shell.asm" | |||
; Extra cmds | |||
.dw a28wCmd | |||
.dw blkBselCmd, blkSeekCmd, blkLoadCmd, blkSaveCmd | |||
#include "blockdev_cmds.asm" | |||
init: | |||
di | |||
; setup stack | |||
ld hl, RAMEND | |||
ld sp, hl | |||
im 1 | |||
call aciaInit | |||
ld hl, aciaGetC | |||
ld de, aciaPutC | |||
call stdioInit | |||
call shellInit | |||
xor a | |||
ld de, BLOCKDEV_SEL | |||
call blkSel | |||
ei | |||
jp shellLoop | |||
a28wCmd: | |||
.db "a28w", 0b011, 0b001 | |||
ld a, (hl) | |||
ld (AT28W_MAXBYTES+1), a | |||
inc hl | |||
ld a, (hl) | |||
ld (AT28W_MAXBYTES), a | |||
jp at28wInner | |||