Browse Source

Add at28w app and recipe

This allows us to write to an AT28 EEPROM from within collapse os.
pull/10/head
Virgil Dupras 5 years ago
parent
commit
817636242a
6 changed files with 267 additions and 0 deletions
  1. +24
    -0
      apps/at28w/glue.asm
  2. +76
    -0
      apps/at28w/main.asm
  3. +1
    -0
      recipes/rc2014/README.md
  4. +10
    -0
      recipes/rc2014/eeprom/Makefile
  5. +83
    -0
      recipes/rc2014/eeprom/README.md
  6. +73
    -0
      recipes/rc2014/eeprom/glue.asm

+ 24
- 0
apps/at28w/glue.asm View File

@@ -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"

+ 76
- 0
apps/at28w/main.asm View File

@@ -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


+ 1
- 0
recipes/rc2014/README.md View File

@@ -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)



+ 10
- 0
recipes/rc2014/eeprom/Makefile View File

@@ -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) < $< > $@


+ 83
- 0
recipes/rc2014/eeprom/README.md View File

@@ -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


+ 73
- 0
recipes/rc2014/eeprom/glue.asm View File

@@ -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



Loading…
Cancel
Save