@@ -0,0 +1,7 @@ | |||
# Arduino Uno | |||
The Arduino Uno is a very popular platform based on the ATMega328p. While | |||
Collapse OS doesn't run on AVR MCUs (yet?), the Arduino can be a handy tool, | |||
which is why we have recipes for it here. | |||
* [Writing to a AT28 EEPROM from a modern environment](at28/README.md) |
@@ -0,0 +1,23 @@ | |||
PROGNAME = at28wr | |||
USBDEV = /dev/cuaU0 | |||
BAUDS = 115200 | |||
AVRDUDEARGS = -F -V -c arduino -P $(USBDEV) -b $(BAUDS) | |||
AVRDUDEMCU = atmega328p | |||
TARGET = $(PROGNAME).hex | |||
AVRA = avra | |||
# Rules | |||
.PHONY: send all clean | |||
all: $(TARGET) | |||
@echo Done! | |||
send: $(PROGNAME).hex | |||
avrdude $(AVRDUDEARGS) -p $(AVRDUDEMCU) -U flash:w:$< | |||
$(TARGET): at28wr.asm | |||
$(AVRA) -o $@ at28wr.asm | |||
clean: | |||
rm -f $(TARGET) *.S.eep.hex *.S.obj |
@@ -0,0 +1,40 @@ | |||
# Writing to a AT28 EEPROM from a modern environment | |||
In this recipe, we'll build ourselves an ad-hoc EEPROM holder which is designed | |||
to be driven from an Arduino Uno. | |||
## Gathering parts | |||
* An Arduino Uno | |||
* A AT28C64B | |||
* 2 '164 shift registers | |||
* Sockets, header pins, proto board, etc. | |||
* [avra][avra] (will soon rewrite to Collapse OS' ASM) | |||
* avrdude to send program to Arduino | |||
## Schema | |||
(there will soon be an image here, but I have yet to scan my schema) | |||
This is a rather simple circuit which uses 2 chained '164 shift register to | |||
drive the AT28 address pins and connects CE, WE, OE and the data pins directly | |||
to the Arduino. Pins have been chosen so that the protoboard can plug directly | |||
on the Arduino's right side (except for VCC, which needs to be wired). | |||
PD0 and PD1 are not used because they're used for the UART. | |||
## Software | |||
The software in at28wr.asm listens to the UART and writes every byte it receives | |||
to the AT28, starting at address 0. It expects tty-escaped content (see | |||
`/tools/ttysafe`). | |||
After having written the byte, it re-reads it from the EEPROM and spits it back | |||
to the UART, tty-escaped. | |||
## Usage | |||
After you've build and sent your binary to the Arduino with `make send`, you | |||
can send your (tty-safe!) content to your EEPROM using `/tools/pingpong`. | |||
[avra]: http://avra.sourceforge.net/ |
@@ -0,0 +1,213 @@ | |||
; *** EEPROM write *** | |||
; Listen to UART expecting tty-escaped "pingpong" (from tools/) communication. | |||
; | |||
; Each of those received bytes is written to the EEPROM, starting at addr 0. | |||
; that byte is then re-read and sent back to the UART, tty-escaped. | |||
; | |||
; Addr selection is done through 2 chained '164, data in/out is done directly | |||
; with PD7:2 for bits 7:2 and PB1:0 for bits 1:0 (PD1 and PD0 are used for | |||
; UART). | |||
; | |||
; *** Register Usage *** | |||
; | |||
; r0: holds whether last received char was tty-escaped (0 = no, 1=yes) | |||
; r16: generic tmp | |||
; r17: generic tmp | |||
; r20: Byte to send to the "data" SR. Wired to D0-D7. | |||
; r21: "high" byte, to send to the "addr" SR. Wired to A8-15 | |||
; r22: "low" byte, to send to the "addr" SR. Wired to A0-7 | |||
; r23: tmp value to use for sending to the "addr" SR | |||
.include "m328Pdef.inc" | |||
; *** Pins *** | |||
.equ SRCP = PORTB2 | |||
.equ SRDS = PORTB1 | |||
.equ FLWE = PORTB3 | |||
.equ FLOE = PORTB4 | |||
.equ FLCE = PORTB5 ; WARNING: same as LED | |||
; *** Consts *** | |||
.equ BAUD_PRESCALE = 103 ; 9600 bauds at 16mhz | |||
rjmp main | |||
; *** Code *** | |||
; Waits until a char is read, then put it in R20 | |||
; Perform TTY-escape transparently. | |||
uartrd: | |||
lds r16, UCSR0A | |||
sbrs r16, RXC0 ; RXC0 is set? skip rjmp and fetch char. | |||
rjmp uartrd | |||
lds r20, UDR0 | |||
; is this the escape char? | |||
cpi r20, 0x20 | |||
brne uartrd_0 | |||
; escape char | |||
; We "pong" the escape right away. | |||
rcall uartwr | |||
inc r0 | |||
rjmp uartrd | |||
uartrd_0: | |||
; should we escape? | |||
tst r0 | |||
breq uartrd_1 | |||
; yes | |||
andi r20, 0x7f | |||
uartrd_1: | |||
ret | |||
; Sends char in r20 to UART | |||
; Perform TTY-escape transparently. | |||
uartwr: | |||
lds r16, UCSR0A | |||
sbrs r16, UDRE0 ; wait until send buffer is empty | |||
rjmp uartwr | |||
; should we escape? | |||
tst r0 | |||
breq uartwr_0 | |||
; we need to escape | |||
ori r20, 0x80 | |||
clr r0 | |||
uartwr_0: | |||
sts UDR0, r20 | |||
ret | |||
; send r23 to addr shift register. | |||
; We send highest bits first so that Q7 is the MSB and Q0 is the LSB | |||
sendaddr: | |||
ldi r16, 8 ; we will loop 8 times | |||
cbi PORTB, SRDS | |||
sbrc r23, 7 ; if latest bit isn't cleared, set SER_DP high | |||
sbi PORTB, SRDS | |||
; toggle SRCP, not waiting between pulses. The CD74AC164 at 5V has a | |||
; 5.9ns CP min pulse width. We can't match that at 16mhz. No need to | |||
; wait. | |||
cbi PORTB, SRCP | |||
sbi PORTB, SRCP | |||
lsl r23 ; shift our data left | |||
dec r16 | |||
brne sendaddr+1 ; not zero yet? loop! (+1 to avoid reset) | |||
ret | |||
; send r20 to EEPROM's I/O7:0 through PD7:2 and PB1:0 | |||
writedata: | |||
; send bits 7:2 | |||
mov r16, r20 | |||
andi r16, 0xfc | |||
in r17, PORTD | |||
andi r17, 0x03 | |||
or r16, r17 | |||
out PORTD, r16 | |||
; send bits 1:0 | |||
mov r16, r20 | |||
andi r16, 0x03 | |||
in r17, PORTB | |||
andi r17, 0xfc | |||
or r16, r17 | |||
out PORTB, r16 | |||
ret | |||
; push r20 to the rom and increase the memory counter | |||
pushdata: | |||
; first, set up addr | |||
mov r23, r21 | |||
rcall sendaddr | |||
mov r23, r22 | |||
rcall sendaddr | |||
inc r22 | |||
brne pushdata_0 ; no overflow? skip | |||
inc r21 | |||
pushdata_0: | |||
; addr is latched on WE falling edge | |||
cbi PORTB, FLWE | |||
; now, lets set up data. Plenty enough instructions to ensure a 100ns | |||
; minimum delay. | |||
rcall writedata | |||
; data is latched on rising edge | |||
sbi PORTB, FLWE | |||
ret | |||
; wait until I/O7 stops toggling | |||
waitio7: | |||
cbi PORTB, FLOE | |||
in r16, PIND | |||
sbi PORTB, FLOE | |||
andi r16, 0xfc | |||
cbi PORTB, FLOE | |||
in r17, PIND | |||
sbi PORTB, FLOE | |||
andi r17, 0xfc | |||
cp r16, r17 | |||
brne waitio7 | |||
ret | |||
; read EEPROM's I/O7:0 through PD7:2 and PB1:0 and put result in r20. | |||
readdata: | |||
cbi PORTB, FLOE | |||
; read bits 7:2 | |||
in r20, PIND | |||
andi r20, 0xfc | |||
; read bits 1:0 | |||
in r16, PINB | |||
andi r16, 0x03 | |||
or r20, r16 | |||
sbi PORTB, FLOE | |||
ret | |||
; Set PD7:2 and PB1:0 to output | |||
ioout: | |||
ldi r16, 0xfc ; PD7:2 | |||
out DDRD, r16 | |||
ldi r16, 0x3f ; PB5:0 (WE, OE and CE too) | |||
out DDRB, r16 | |||
ret | |||
; Set PD7:2 and PB1:0 to input | |||
ioin: | |||
ldi r16, 0x03 ; PD7:2 | |||
out DDRD, r16 | |||
ldi r16, 0x3c ; PB1:0 | |||
out DDRB, r16 | |||
ret | |||
main: | |||
ldi r16, low(RAMEND) | |||
out SPL, r16 | |||
ldi r16, high(RAMEND) | |||
out SPH, r16 | |||
; We begin with WE and OE disabled (high), but CE stays enabled (low) | |||
; the whole time. | |||
sbi PORTB, FLWE | |||
sbi PORTB, FLOE | |||
cbi PORTB, FLCE | |||
; Clear counters and flags | |||
clr r0 | |||
clr r21 | |||
clr r22 | |||
; Setup UART | |||
ldi R16, low(BAUD_PRESCALE) | |||
sts UBRR0L, r16 | |||
ldi r16, high(BAUD_PRESCALE) | |||
sts UBRR0H, r16 | |||
ldi r16, (1<<RXEN0) | (1<<TXEN0) | |||
sts UCSR0B, r16 | |||
loop: | |||
rcall uartrd | |||
rcall ioout | |||
rcall pushdata | |||
rcall ioin | |||
rcall waitio7 | |||
rcall readdata | |||
rcall uartwr | |||
rjmp loop | |||
@@ -31,13 +31,15 @@ static void mempty(int fd) | |||
} | |||
} | |||
static void mexpect(int fd, char ec) | |||
int mexpect(int fd, unsigned char ec) | |||
{ | |||
char c; | |||
unsigned char c; | |||
mread(fd, &c, 1); | |||
if (c != ec) { | |||
fprintf(stderr, "Expected %d but got %d\n", ec, c); | |||
fprintf(stderr, "Expected %x but got %x\n", ec, c); | |||
return 0; | |||
} | |||
return 1; | |||
} | |||
void readprompt(int fd) | |||
@@ -1,6 +1,7 @@ | |||
void sendcmd(int fd, char *cmd); | |||
void sendcmdp(int fd, char *cmd); | |||
void mread(int fd, char *s, int count); | |||
int mexpect(int fd, char ec); | |||
void readprompt(int fd); | |||
int set_interface_attribs(int fd, int speed, int parity); | |||
void set_blocking(int fd, int should_block); | |||
@@ -11,11 +11,17 @@ | |||
* to indicate EOF to the receiving end. | |||
* | |||
* It is recommended that you send contents that has gone through `ttysafe`. | |||
* | |||
* If "delayus" is specified, this will be the delay we wait between the moment | |||
* we write the "ping" and the moment where we fetch the "pong". | |||
*/ | |||
int main(int argc, char **argv) | |||
{ | |||
if (argc != 3) { | |||
fprintf(stderr, "Usage: ./pingpong device fname\n"); | |||
int delayus = 1000; | |||
if (argc == 4) { | |||
delayus = atoi(argv[3]); | |||
} else if (argc != 3) { | |||
fprintf(stderr, "Usage: ./pingpong device fname [delayus] \n"); | |||
return 1; | |||
} | |||
FILE *fp = fopen(argv[2], "r"); | |||
@@ -23,24 +29,18 @@ int main(int argc, char **argv) | |||
fprintf(stderr, "Can't open %s.\n", argv[2]); | |||
return 1; | |||
} | |||
int fd = open(argv[1], O_RDWR|O_NOCTTY|O_NONBLOCK); | |||
printf("Press a key...\n"); | |||
getchar(); | |||
int fd = ttyopen(argv[1]); | |||
unsigned char c; | |||
// empty the recv buffer | |||
while (read(fd, &c, 1) == 1) usleep(1000); | |||
int returncode = 0; | |||
while (fread(&c, 1, 1, fp)) { | |||
putchar('.'); | |||
fflush(stdout); | |||
write(fd, &c, 1); | |||
usleep(1000); // let it breathe | |||
unsigned char c2; | |||
while (read(fd, &c2, 1) != 1); // read echo | |||
if (c != c2) { | |||
usleep(delayus); // let it breathe | |||
if (!mexpect(fd, c)) { | |||
// mismatch! | |||
unsigned int pos = ftell(fp); | |||
fprintf(stderr, "Mismatch at byte %d! %d != %d.\n", pos, c, c2); | |||
fprintf(stderr, "Mismatch at byte %d!\n", pos); | |||
returncode = 1; | |||
break; | |||
} | |||
@@ -50,6 +50,9 @@ int main(int argc, char **argv) | |||
write(fd, &c, 1); | |||
printf("Done!\n"); | |||
fclose(fp); | |||
if (fd > 0) { | |||
close(fd); | |||
} | |||
return returncode; | |||
} | |||