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 | This recipe is for installing a minimal Collapse OS system on the RC2014. There | ||||
are other recipes related to the RC2014: | are other recipes related to the RC2014: | ||||
* [Writing to a AT28 from Collapse OS](eeprom/README.md) | |||||
* [Accessing a MicroSD card](sdcard/README.md) | * [Accessing a MicroSD card](sdcard/README.md) | ||||
* [Assembling binaries](zasm/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 | |||||