Added Stream-PDM client.

The program requires an Enhanced //e or a IIgs with an Uthernet II. It connects to an HTTP server and streams PDM audio data.
This commit is contained in:
Oliver Schmidt 2020-05-09 23:49:05 +02:00
parent 2d7e911fbe
commit 6334ca5e1b
8 changed files with 2168 additions and 0 deletions

555
linenoise.c Normal file
View File

@ -0,0 +1,555 @@
/* linenoise.c -- guerrilla line editing library against the idea that a
* line editing lib needs to be 20,000 lines of C code.
*
* You can find the latest source code at:
*
* http://github.com/antirez/linenoise
*
* Does a number of crazy assumptions that happen to be true in 99.9999% of
* the 2010 UNIX computers around.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ------------------------------------------------------------------------
*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <conio.h>
#ifdef __APPLE2__
#include <apple2_filetype.h>
#endif
#include "linenoise.h"
#define LINENOISE_HISTORY_MAX_LEN 20
#define LINENOISE_MAX_LINE 256
static char buf[LINENOISE_MAX_LINE];
static linenoiseCompletionCallback *completionCallback = NULL;
static int history_len = 0;
static char **history = NULL;
/* The linenoiseState structure represents the state during line editing.
* We pass this state to functions implementing specific editing
* functionalities. */
struct linenoiseState {
char *buf; /* Edited line buffer. */
size_t buflen; /* Edited line buffer size. */
const char *prompt; /* Prompt to display. */
size_t plen; /* Prompt length. */
size_t pos; /* Current cursor position. */
size_t oldpos; /* Previous refresh cursor position. */
size_t len; /* Current edited line length. */
size_t cols; /* Number of columns in terminal. */
int history_index; /* The history index we are currently editing. */
};
enum KEY_ACTION{
CTRL_A = 1, /* Ctrl+a */
CTRL_B = 2, /* Ctrl-b */
CTRL_C = 3, /* Ctrl-c */
CTRL_D = 4, /* Ctrl-d */
CTRL_E = 5, /* Ctrl-e */
CTRL_F = 6, /* Ctrl-f */
CTRL_N = 14, /* Ctrl-n */
CTRL_P = 16 /* Ctrl-p */
};
static void refreshLine(struct linenoiseState *l);
/* ======================= Low level terminal handling ====================== */
/* Try to get the number of columns in the current terminal, or assume 80
* if it fails. */
static int getColumns() {
unsigned char cols, rows;
screensize(&cols,&rows);
return cols;
}
#ifdef __APPLE2__
#pragma code-name (push, "LC")
#endif
/* Beep, used for completion when there is nothing to complete or when all
* the choices were already shown. */
static void linenoiseBeep(void) {
putchar('\a');
}
/* ============================== Completion ================================ */
/* Free a list of completion option populated by linenoiseAddCompletion(). */
static void freeCompletions(linenoiseCompletions *lc) {
size_t i;
for (i = 0; i < lc->len; i++)
free(lc->cvec[i]);
if (lc->cvec != NULL)
free(lc->cvec);
}
/* This is an helper function for linenoiseEdit() and is called when the
* user types the <tab> key in order to complete the string currently in the
* input.
*
* The state of the editing is encapsulated into the pointed linenoiseState
* structure as described in the structure definition. */
static int completeLine(register struct linenoiseState *ls) {
linenoiseCompletions lc = { 0, NULL };
int nwritten;
char c = 0;
completionCallback(ls->buf,&lc);
if (lc.len == 0) {
linenoiseBeep();
} else {
size_t stop = 0, i = 0;
while(!stop) {
/* Show completion or original buffer */
if (i < lc.len) {
struct linenoiseState saved;
saved = *ls;
ls->len = ls->pos = strlen(lc.cvec[i]);
ls->buf = lc.cvec[i];
refreshLine(ls);
ls->len = saved.len;
ls->pos = saved.pos;
ls->buf = saved.buf;
} else {
refreshLine(ls);
}
c = cgetc();
switch(c) {
case '\t': /* tab */
i = (i+1) % (lc.len+1);
if (i == lc.len) linenoiseBeep();
break;
case CH_ESC: /* escape */
/* Re-show original buffer */
if (i < lc.len) refreshLine(ls);
stop = 1;
break;
default:
/* Update buffer and return */
if (i < lc.len) {
nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
ls->len = ls->pos = nwritten;
}
stop = 1;
break;
}
}
}
freeCompletions(&lc);
return c; /* Return last read character */
}
/* Register a callback function to be called for tab-completion. */
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
completionCallback = fn;
}
/* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
* understand example. */
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
size_t len = strlen(str);
char *copy, **cvec;
copy = malloc(len+1);
if (copy == NULL) return;
memcpy(copy,str,len+1);
cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
if (cvec == NULL) {
free(copy);
return;
}
lc->cvec = cvec;
lc->cvec[lc->len++] = copy;
}
/* =========================== Line editing ================================= */
/* Low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content,
* cursor position, and number of columns of the terminal. */
static void refreshLine(struct linenoiseState *l) {
char tmp;
unsigned char y = wherey();
size_t plen = strlen(l->prompt);
char *buf = l->buf;
size_t len = l->len;
size_t pos = l->pos;
while((plen+pos) >= l->cols) {
buf++;
len--;
pos--;
}
while (plen+len > l->cols) {
len--;
}
/* Cursor to left edge */
gotox(0);
/* Write the prompt */
cputs(l->prompt);
/* Write the current buffer content */
tmp = buf[len];
buf[len] = '\0';
cputs(buf);
buf[len] = tmp;
/* Erase to right */
if (wherex()) {
cclear(l->cols-wherex());
}
/* Move cursor to original position. */
gotoxy((unsigned char)(pos+plen),y);
}
/* Insert the character 'c' at cursor current position. */
void linenoiseEditInsert(register struct linenoiseState *l, char c) {
if (l->len < l->buflen) {
if (l->len == l->pos) {
l->buf[l->pos] = c;
l->pos++;
l->len++;
l->buf[l->len] = '\0';
if ((l->plen+l->len < l->cols)) {
/* Avoid a full update of the line in the
* trivial case. */
cputc(c);
} else {
refreshLine(l);
}
} else {
memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
l->buf[l->pos] = c;
l->len++;
l->pos++;
l->buf[l->len] = '\0';
refreshLine(l);
}
}
}
/* Move cursor on the left. */
void linenoiseEditMoveLeft(struct linenoiseState *l) {
if (l->pos > 0) {
l->pos--;
refreshLine(l);
}
}
/* Move cursor on the right. */
void linenoiseEditMoveRight(struct linenoiseState *l) {
if (l->pos != l->len) {
l->pos++;
refreshLine(l);
}
}
/* Move cursor to the start of the line. */
void linenoiseEditMoveHome(struct linenoiseState *l) {
if (l->pos != 0) {
l->pos = 0;
refreshLine(l);
}
}
/* Move cursor to the end of the line. */
void linenoiseEditMoveEnd(struct linenoiseState *l) {
if (l->pos != l->len) {
l->pos = l->len;
refreshLine(l);
}
}
/* Substitute the currently edited line with the next or previous history
* entry as specified by 'dir'. */
#define LINENOISE_HISTORY_NEXT 0
#define LINENOISE_HISTORY_PREV 1
void linenoiseEditHistoryNext(register struct linenoiseState *l, int dir) {
if (history_len > 1) {
/* Update the current history entry before to
* overwrite it with the next one. */
free(history[history_len - 1 - l->history_index]);
history[history_len - 1 - l->history_index] = strdup(l->buf);
/* Show the new entry */
l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
if (l->history_index < 0) {
l->history_index = 0;
return;
} else if (l->history_index >= history_len) {
l->history_index = history_len-1;
return;
}
strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
l->buf[l->buflen-1] = '\0';
l->len = l->pos = strlen(l->buf);
refreshLine(l);
}
}
/* Delete the character at the right of the cursor without altering the cursor
* position. Basically this is what happens with the "Delete" keyboard key. */
void linenoiseEditDelete(register struct linenoiseState *l) {
if (l->len > 0 && l->pos < l->len) {
memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
l->len--;
l->buf[l->len] = '\0';
refreshLine(l);
}
}
/* Backspace implementation. */
void linenoiseEditBackspace(register struct linenoiseState *l) {
if (l->pos > 0 && l->len > 0) {
memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
l->pos--;
l->len--;
l->buf[l->len] = '\0';
refreshLine(l);
}
}
/* This function is the core of the line editing capability of linenoise.
*
* The resulting string is put into 'buf' when the user type enter.
*
* The function returns the length of the current buffer. */
static int linenoiseEdit(const char *prompt)
{
struct linenoiseState l;
/* Populate the linenoise state that we pass to functions implementing
* specific editing functionalities. */
l.buf = buf;
l.buflen = LINENOISE_MAX_LINE;
l.prompt = prompt;
l.plen = strlen(prompt);
l.oldpos = l.pos = 0;
l.len = 0;
l.cols = getColumns();
l.history_index = 0;
/* Buffer starts empty. */
l.buf[0] = '\0';
l.buflen--; /* Make sure there is always space for the nulterm */
/* The latest history entry is always our current buffer, that
* initially is just an empty string. */
linenoiseHistoryAdd("");
cputs(prompt);
while(1) {
char c;
c = cgetc();
/* Only autocomplete when the callback is set. It will return the
* character that should be handled next. */
if (c == '\t' && completionCallback != NULL) {
c = completeLine(&l);
/* Read next character when 0 */
if (c == 0) continue;
}
switch(c) {
case CH_ENTER:
history_len--;
free(history[history_len]);
return (int)l.len;
case CTRL_C:
errno = EAGAIN;
return -1;
case CH_DEL: /* backspace */
linenoiseEditBackspace(&l);
break;
case CTRL_D: /* ctrl-d, remove char at right of cursor. */
if (l.len > 0) {
linenoiseEditDelete(&l);
}
break;
case CTRL_B: /* ctrl-b */
case CH_CURS_LEFT:
linenoiseEditMoveLeft(&l);
break;
case CTRL_F: /* ctrl-f */
case CH_CURS_RIGHT:
linenoiseEditMoveRight(&l);
break;
case CTRL_P: /* ctrl-p */
case CH_CURS_UP:
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
break;
case CTRL_N: /* ctrl-n */
case CH_CURS_DOWN:
linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
break;
case CTRL_A: /* Ctrl+a, go to the start of the line */
linenoiseEditMoveHome(&l);
break;
case CTRL_E: /* ctrl+e, go to the end of the line */
linenoiseEditMoveEnd(&l);
break;
default:
if (isprint(c)) linenoiseEditInsert(&l,c);
break;
}
}
return l.len;
}
#ifdef __APPLE2__
#pragma code-name (pop)
#endif
/* The high level function that is the main API of the linenoise library. */
char *linenoise(const char *prompt) {
unsigned char oldcursor;
int count;
oldcursor = cursor(1);
count = linenoiseEdit(prompt);
cursor(oldcursor);
if (count == -1) return NULL;
return buf;
}
/* ================================ History ================================= */
/* Reseet the history. */
void linenoiseHistoryReset(void) {
if (history) {
int j;
for (j = 0; j < history_len; j++)
free(history[j]);
free(history);
history_len = 0;
history = NULL;
}
}
/* This is the API call to add a new entry in the linenoise history.
* It uses a fixed array of char pointers that are shifted (memmoved)
* when the history max length is reached in order to remove the older
* entry and make room for the new one, so it is not exactly suitable for huge
* histories, but will work well for a few hundred of entries.
*
* Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
char *linecopy;
/* Initialization on first call. */
if (history == NULL) {
history = malloc(sizeof(char*)*LINENOISE_HISTORY_MAX_LEN);
if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*LINENOISE_HISTORY_MAX_LEN));
}
/* Don't add duplicated lines. */
if (history_len && !strcmp(history[history_len-1], line)) return 0;
/* Add an heap allocated copy of the line in the history.
* If we reached the max length, remove the older line. */
linecopy = strdup(line);
if (!linecopy) return 0;
if (history_len == LINENOISE_HISTORY_MAX_LEN) {
free(history[0]);
memmove(history,history+1,sizeof(char*)*(LINENOISE_HISTORY_MAX_LEN-1));
history_len--;
}
history[history_len] = linecopy;
history_len++;
return 1;
}
/* Save the history in the specified file. On success 0 is returned
* otherwise -1 is returned. */
int linenoiseHistorySave(const char *filename) {
FILE *fp;
int j;
#ifdef __APPLE2__
unsigned char oldfiletype = _filetype;
_filetype = PRODOS_T_TXT;
#endif
fp = fopen(filename,"w");
#ifdef __APPLE2__
_filetype = oldfiletype;
#endif
if (fp == NULL) return -1;
for (j = 0; j < history_len; j++)
fprintf(fp,"%s\n",history[j]);
fclose(fp);
return 0;
}
/* Load the history from the specified file. If the file does not exist
* zero is returned and no operation is performed.
*
* If the file exists and the operation succeeded 0 is returned, otherwise
* on error -1 is returned. */
int linenoiseHistoryLoad(const char *filename) {
FILE *fp = fopen(filename,"r");
if (fp == NULL) return -1;
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
char *p;
p = strchr(buf,'\r');
if (!p) p = strchr(buf,'\n');
if (p) *p = '\0';
linenoiseHistoryAdd(buf);
}
fclose(fp);
return 0;
}

59
linenoise.h Normal file
View File

@ -0,0 +1,59 @@
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LINENOISE_H
#define __LINENOISE_H
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
void linenoiseHistoryReset(void);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
#endif /* __LINENOISE_H */

193
stream-pwm.c Normal file
View File

@ -0,0 +1,193 @@
#include <ctype.h>
#include <fcntl.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ip65.h>
#include "w5100.h"
#include "w5100_http.h"
#include "linenoise.h"
// Both pragmas are obligatory to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
#pragma optimize (on)
#pragma static-locals (on)
void streamafterexit(uint8_t eth_init);
void error_exit(void)
{
printf("- %s\n", ip65_strerror(ip65_error));
exit(EXIT_FAILURE);
}
void confirm_exit(void)
{
printf("\nPress any key ");
cgetc();
}
bool match(const char *filter, const char *string)
{
while (*filter)
{
if (!*string)
{
return false;
}
if (toupper(*filter++) != toupper(*string++))
{
return false;
}
}
return true;
}
void completion(const char *line, linenoiseCompletions *lc)
{
if (match(line, "http://"))
{
linenoiseAddCompletion(lc, "http://");
}
if (match(line, "http://www."))
{
linenoiseAddCompletion(lc, "http://www.");
}
}
void main(void)
{
uint8_t eth_init = ETH_INIT_DEFAULT;
char *url;
videomode(VIDEOMODE_80COL);
{
int file;
printf("\nSetting slot ");
file = open("ethernet.slot", O_RDONLY);
if (file != -1)
{
read(file, &eth_init, 1);
close(file);
eth_init &= ~'0';
}
}
#if 1
printf("- %d\n\nInitializing %s ", eth_init, eth_name);
if (ip65_init(eth_init))
{
error_exit();
}
// Abort on Ctrl-C to be consistent with Linenoise
abort_key = 0x83;
printf("- Ok\n\nObtaining IP address ");
if (dhcp_init())
{
error_exit();
}
printf("- Ok\n\n");
linenoiseHistoryLoad("stream.urls");
while (true)
{
linenoiseSetCompletionCallback(completion);
url = linenoise("URL? ");
if (!url || !*url)
{
putchar('\n');
return;
}
linenoiseHistoryAdd(url);
printf("\n\nProcessing URL ");
if (!url_parse(url))
{
break;
}
printf("- %s\n\n", ip65_strerror(ip65_error));
}
linenoiseHistorySave("stream.urls");
linenoiseHistoryReset();
printf("- Ok\n\n");
// Copy IP config from IP65 to W5100
w5100_config(eth_init);
{
char buffer[0x1000];
if (!w5100_http_open(url_ip, url_port, url_selector, buffer, sizeof(buffer)))
{
return;
}
}
#else
url = "http://www.myhost.com/myfile.stream";
#endif
clrscr();
gotoxy(0, 20);
cprintf("%.160s", url);
gotoxy(0, 23);
cprintf("Initializing...");
gotoxy(60, 23);
cprintf("Oliver Schmidt, 2020");
{
int y;
// Switch to split screen with low res graphics
*(char*)0xC056 = 0;
*(char*)0xC053 = 0;
*(char*)0xC050 = 0;
// Clear graphics part of screen
for (y = 0; y < 20; ++y)
{
gotoy(y);
memset(*(char**)0x28, 0x00, 40);
}
// Show upper divider line
gotoy(0);
memset(*(char**)0x28, 0x0F, 40);
// Show big arrow consisting of dark blue, blue, light blue and white
for (y = 0; y < 15; ++y)
{
int x;
int d = 8 - abs(7 - y);
gotoy(2 + y);
for (x = 0; x < 4; ++x)
{
uint8_t color[] = {0x22, 0x66, 0x77, 0xFF};
memset(*(char**)0x28 + d + (x * 8), color[x], 8);
}
}
// Show lower divider line
gotoy(18);
memset(*(char**)0x28, 0xF0, 40);
}
// Streaming overwrites almost all of the C program so do first a clean
// shutdown of the C program and initialize the streaming afterwards
streamafterexit(eth_init);
}

708
stream.s Normal file
View File

@ -0,0 +1,708 @@
.export _streamafterexit
.import done, abort_key
.include "apple2.inc"
.macpack apple2
.pc02
src := $00
dst := $02
rd_count := $04
wr_count := $05
scroll_row := $06
scroll_col := $07
scroll_opc := $08
program := $100B ; adressed via jmp_table !
buffer := $9000
zp_save := $9100
px = $8F ; force page crossing
silence = $FF
skew = 3
end_mark = $FF
rd_mark = $FE
wr_mark = $FD
max_row = 15
max_column = 39
scroll_ldx = $AE
scroll_ldy = $AC
scroll_stx = $8E
scroll_sty = $8C
_streamafterexit:
asl
asl
asl
asl
tax
clc
adc #px
sta slot
txa
ora #%10001111
sta slot_mask
lda abort_key
sta quit_key
lda #<stream
ldx #>stream
sta done+1
stx done+2
rts
.segment "LOWCODE"
slot:
.res 1
slot_mask:
.res 1
quit_key:
.res 1
key_state:
.res 1
jmp_table:
.byte $0B, $10, $10 ; second byte used both as high and low !
lowres_lo:
.byte $00, $80, $00, $80, $00, $80, $28, $A8
.byte $28, $A8, $28, $A8, $28, $A8, $50
lowres_hi:
.byte $05, $05, $06, $06, $07, $07, $04, $04
.byte $05, $05, $06, $06, $07, $07, $04
streaming:
scrcode "Streaming... [Space] - Pause [Esc] - Stop"
.byte $00
pausing:
scrcode "Pausing... [Space] - Stream"
.byte $00
waiting:
scrcode "Waiting... "
.byte $00
update_status:
sta update_status+9
stx update_status+10
ldx #$00
chr:lda a:$0000,x ; patched
bne :+
rts
: tay
txa
lsr ; even cols go into aux mem
tax
tya
bcs :+
bit $C055 ; aux mem
: sta $07D0,x
bcs :+
bit $C054 ; main mem
: txa
rol
tax
inx
bra chr
stream:
; Save zero page
ldx #$00
: lda $00,x
sta zp_save,x
inx
bne :-
; Crate unrolled loop program
jsr unroll
; Create socket 0 physical read memory lookup table in zero page
ldx #$00
: txa
and #>$1FFF
ora #>$6000
sta $00,x
inx
bne :-
; Init part of ring buffer that is played before first data is available
ldx #skew-1
lda #silence
: sta buffer,x
dex
bpl :-
; Show status 'Streaming'
lda #<streaming
ldx #>streaming
jsr update_status
; Run unrolled loop program
jsr program
; Restore zero page
ldx #$00
: lda zp_save,x
sta $00,x
inx
bne :-
; Switch to text screen
bit $C051
; Clear screen
jsr $FC58 ; HOME
; Check ProDOS system bit map
lda $BF6F ; protection for pages $B8 - $BF
cmp #%00000001 ; exactly system global page is protected
beq :+
jmp DOSWARM
; Quit to ProDOS dispatcher
: jsr $BF00 ; MLI call entry point
.byte $65 ; quit
.word par
; MLI parameter list for quit
par:.byte $04 ; param_count
.byte $00 ; quit_type
.word $0000 ; reserved
.byte $00 ; reserved
.word $0000 ; reserved
disconnect:
ldx slot
lda #$04 ; socket 0
sta $C085-px,x
lda #$01 ; command register
sta $C086-px,x
lda #$08 ; DISCON
sta $C087-px,x
rts
check_connect:
; Show status 'Waiting'
lda #<waiting
ldx #>waiting
jsr update_status
; Check if still connected at all
ldx slot
chk:lda #$04 ; socket 0
sta $C085-px,x
lda #$03 ; status register
sta $C086-px,x
lda $C087-px,x
cmp #$17 ; SOCK_ESTABLISHED
beq :+
pla ; completely ...
pla ; quit ...
rts ; stream
; Check if at least one page available
: lda #$04 ; socket 0
sta $C085-px,x
lda #$26 ; received size register
sta $C086-px,x
lda $C087-px,x ; high byte
beq :+
; Show status 'Streaming'
lda #<streaming
ldx #>streaming
jsr update_status
ldx #$04 ; socket 0
rts ; return to stream
; Check keyboard
: lda $C000
bpl chk
bit $C010
cmp quit_key
beq :+
cmp #$9B ; Esc
beq :+
bra chk
: pla ; completely ...
pla ; quit ...
jmp disconnect ; stream
check_keyboard:
stz key_state
key:bit $C010 ; keyboard strobe
cpx quit_key
beq :+
cpx #$9B ; Esc
beq :+
cpx #$A0 ; Space
beq :++
ldx key_state
bne get
rts ; return to stream
: pla ; completely ...
pla ; quit ...
jmp disconnect ; stream
: lda key_state
beq :+
; Show status 'Streaming'
lda #<streaming
ldx #>streaming
jmp update_status ; return to stream
; Show status 'Pausing'
: lda #<pausing
ldx #>pausing
jsr update_status
; Wait for keypress
get:ldx $C000 ; keyboard
bpl :-
inc key_state
bra key
unroll:
lda #<program
ldx #>program
sta dst
stx dst+1
stz rd_count
lda #skew
sta wr_count
lda #<recv_prolog
ldy #>recv_prolog
jsr copy
ldx #$0100 / 7
: lda #<recv_loop
ldy #>recv_loop
jsr copy
dex
bne :-
lda #<recv_epilog
ldy #>recv_epilog
jsr copy
stz scroll_row
lda #max_column
sta scroll_col
lda #scroll_ldx
sta scroll_opc
ldx #$07
scr:cpx #$07
bne :+
lda #<speaker_load
ldy #>speaker_load
bra :++
: jsr scroll_patch
lda #<speaker
ldy #>speaker
: jsr copy
dex
bpl :+
ldx #$07
: lda scroll_row
cmp #max_row
bne scr
fil:cpx #$07
bne :+
lda #<speaker_load
ldy #>speaker_load
bra :++
: lda #<speaker_nop4
ldy #>speaker_nop4
: jsr copy
dex
bpl :+
ldx #$07
: lda rd_count
bne fil
cpx #$01
bne fil
lda #<speaker_loop
ldy #>speaker_loop
jsr copy
rts
copy:
sta src
sty src+1
ldy #$00
nxt:lda (src),y
cmp #end_mark
bne :++
tya
clc
adc dst
sta dst
bcc :+
inc dst+1
: rts
: cmp #rd_mark
bne :+
lda rd_count
inc rd_count
bra put
: cmp #wr_mark
bne :+
lda wr_count
inc wr_count
bra put
: cmp #$F5 ; slot mask
bcc put
and slot_mask
put:sta (dst),y
iny
bne nxt
inc src+1
inc dst+1
bra nxt
scroll_patch:
lda scroll_opc
sta speaker ; opcode
ldy scroll_row
lda lowres_lo,y
clc
adc scroll_col
sta speaker+1 ; operand low
lda lowres_hi,y
sta speaker+2 ; operand high
lda scroll_opc
cmp #scroll_ldx
bne :+
dec scroll_col
lda #scroll_ldy
sta scroll_opc
rts
: cmp #scroll_ldy
bne :+
inc scroll_col
lda #scroll_sty
sta scroll_opc
rts
: cmp #scroll_sty
bne :++
dec scroll_col
beq :+
dec scroll_col
lda #scroll_ldy
sta scroll_opc
rts
: lda #scroll_stx
sta scroll_opc
rts
: lda #scroll_ldx
sta scroll_opc
lda #max_column
sta scroll_col
inc scroll_row
rts
; 2 bytes, 2 cycles
.macro nop2
bit #$00
.endmacro
; 2 bytes, 3 cycles
.macro nop3
bit $00
.endmacro
; 3 bytes, 4 cycles
.macro nop4
bit a:$0000
.endmacro
; 13 bytes, 9 cycles
.macro spkr
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
.res 4
: bit $C030 ; 4
:.endmacro
; 9 bytes, 9 cycles
.macro spkr_short
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
: bit $C030 ; 4
:.endmacro
; 14 bytes, 9 cycles
.macro spkr_long
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
.res 5
: bit $C030 ; 4
:.endmacro
; 13 bytes, 9 cycles
.macro spkr_jsr addr
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
.res 1
jsr addr
: bit $C030 ; 4
:.endmacro
; 16 bytes, 13 cycles
.macro spkr_load
lda buffer | rd_mark ; 4
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
.res 4
: bit $C030 ; 4
:.endmacro
; 22 bytes, 20 cycles (= 2 * 13 - 6 used by speaker_loop)
.macro spkr_init
nop4 ; 4 $100B (bit 7 is 0)
bra :+ ; 3
nop3 ; 3 $1010 (bit 7 is 1)
bit $C030 ; 4
: lda buffer | rd_mark ; 4
lsr a ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
: bit $C030 ; 4
:.endmacro
recv_prolog:
spkr_init ; bit 0
ldx #$04 ; socket 0
nop
spkr_short ; bit 1
stx $C0F5 ; socket 0
spkr ; bit 2
ldy #$26 ; received size register
nop
spkr ; bit 3
sty $C0F6 ; received size register
spkr ; bit 4
ldy $C0F7 ; high byte
spkr ; bit 5
dey ; at least one page available
bmi *+9 ; branch to jsr
spkr_jsr check_connect ; bit 6
stx $C0F5 ; socket 0
spkr ; bit 7
spkr_load ; bit 0
ldx #$28 ; read pointer register
nop
spkr ; bit 1
stx $C0F6 ; read pointer register
spkr ; bit 2
ldx $C0F7 ; high byte
spkr ; bit 3
ldy $00,x ; high byte -> physical
spkr_long ; bit 4
ldx $C0F7 ; low byte
spkr ; bit 5
sty $C0F5 ; read addr high
spkr ; bit 6
stx $C0F6 ; read addr low
spkr ; bit 7
.byte end_mark
recv_loop:
spkr_load ; bit 0
ldx $C0F7 ; data
spkr ; bit 1
stx buffer | wr_mark
spkr ; bit 2
ldx $C0F7 ; data
spkr ; bit 3
stx buffer | wr_mark
spkr ; bit 4
ldx $C0F7 ; data
spkr ; bit 5
stx buffer | wr_mark
spkr ; bit 6
ldx $C0F7 ; data
spkr ; bit 7
spkr_load ; bit 0
stx buffer | wr_mark
spkr ; bit 1
ldx $C0F7 ; data
spkr ; bit 2
stx buffer | wr_mark
spkr ; bit 3
ldx $C0F7 ; data
spkr ; bit 4
stx buffer | wr_mark
spkr ; bit 5
ldx $C0F7 ; data
spkr ; bit 6
stx buffer | wr_mark
spkr ; bit 7
.byte end_mark
recv_epilog:
; $0100 % 7 = 4 -> 4 data bytes left
spkr_load ; bit 0
ldx $C0F7 ; data
spkr ; bit 1
stx buffer | wr_mark
spkr ; bit 2
ldx $C0F7 ; data
spkr ; bit 3
stx buffer | wr_mark
spkr ; bit 4
ldx $C0F7 ; data
spkr ; bit 5
stx buffer | wr_mark
spkr ; bit 6
ldx $C0F7 ; data
spkr ; bit 7
spkr_load ; bit 0
stx buffer | wr_mark
; Commit recv
spkr ; bit 1
ldx #$04 ; socket 0
nop
spkr ; bit 2
stx $C0F5 ; socket 0
spkr ; bit 3
ldy #$28 ; received size register
nop
spkr ; bit 4
sty $C0F6 ; received size register
spkr ; bit 5
ldy $C0F7 ; high byte
spkr ; bit 6
iny ; commit one page
nop2
spkr ; bit 7
spkr_load ; bit 0
stx $C0F5 ; socket 0
spkr ; bit 1
ldx #$28 ; received size register
nop
spkr ; bit 2
stx $C0F6 ; received size register
spkr ; bit 3
sty $C0F7 ; high byte
spkr ; bit 4
ldx #$04 ; socket 0
nop
spkr ; bit 5
stx $C0F5 ; socket 0
spkr ; bit 6
ldx #$01 ; command register
nop
spkr ; bit 7
spkr_load ; bit 0
stx $C0F6 ; command register
spkr ; bit 1
ldx #$40 ; RECV
nop
spkr ; bit 2
stx $C0F7 ; RECV
spkr ; bit 3
; Check keyboard
ldx $C000 ; keyboard
spkr ; bit 4
inx ; prepare for N flag
bit #$00 ; 2 byte nop
spkr ; bit 5
dex ; set N flag
bmi *+9 ; branch to jsr
spkr_jsr check_keyboard ; bit 6
; Filler
nop4
spkr ; bit 7
.byte end_mark
; 16 bytes, 13 cycles
speaker:
.res 3 ; patched
spkr
.byte end_mark
; 16 bytes, 13 cycles
speaker_load:
spkr_load
.byte end_mark
; 16 bytes, 13 cycles
speaker_nop4:
nop4
spkr
.byte end_mark
; 14 bytes, 13 cycles (+ 6 cycles)
speaker_loop:
lsr a ; 2 bit 6
tax ; 2 bit 7
nop ; 2
bcs :+ ; 2 3
nop ; 2
bra :++ ; 3
: bit $C030 ; 4
: jmp (jmp_table,x) ; 6
.byte end_mark

338
w5100.c Normal file
View File

@ -0,0 +1,338 @@
/******************************************************************************
Copyright (c) 2015, Oliver Schmidt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
// The W5100 has the undocumented feature to wrap around the Address Register
// on an Auto-Increment at the end of physical address space to its beginning.
//
// However, the only way to make use of that feature is to have only a single
// socket that uses all of the W5100 physical address space. But having only
// a single socket by defining SINGLE_SOCKET comes with downsides too:
//
// One mustn't call into IP65 network functions anymore after w5100_config().
// Additionally the program doesn't support 'W5100 Shared Access' anymore
// (https://github.com/a2retrosystems/uthernet2/wiki/W5100-Shared-Access).
#ifdef SINGLE_SOCKET
#define SOCK_REG(offset) (0x0400 | (offset))
#else // SINGLE_SOCKET
#define SOCK_REG(offset) (0x0500 | (offset))
#endif // SINGLE_SOCKET
// Both pragmas are obligatory to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
#pragma optimize (on)
#pragma static-locals (on)
#include "../inc/ip65.h"
#include "w5100.h"
#define MIN(a,b) (((a)<(b))?(a):(b))
static volatile uint8_t* w5100_mode;
static volatile uint8_t* w5100_addr_hi;
static volatile uint8_t* w5100_addr_lo;
volatile uint8_t* w5100_data;
static uint16_t addr_basis[2];
static uint16_t addr_limit[2];
static uint16_t addr_mask [2];
static void set_addr(uint16_t addr)
{
// The variables are necessary to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
uint8_t addr_hi = addr >> 8;
uint8_t addr_lo = addr;
*w5100_addr_hi = addr_hi;
*w5100_addr_lo = addr_lo;
}
static uint8_t get_byte(uint16_t addr)
{
set_addr(addr);
return *w5100_data;
}
static void set_byte(uint16_t addr, uint8_t data)
{
set_addr(addr);
*w5100_data = data;
}
static uint16_t get_word(uint16_t addr)
{
set_addr(addr);
{
// The variables are necessary to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
uint8_t data_hi = *w5100_data;
uint8_t data_lo = *w5100_data;
return data_hi << 8 | data_lo;
}
}
static void set_word(uint16_t addr, uint16_t data)
{
set_addr(addr);
{
// The variables are necessary to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
uint8_t data_hi = data >> 8;
uint8_t data_lo = data;
*w5100_data = data_hi;
*w5100_data = data_lo;
}
}
static void set_quad(uint16_t addr, uint32_t data)
{
set_addr(addr);
{
// The variables are necessary to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
uint8_t data_1 = data;
uint8_t data_2 = data >> 8;
uint8_t data_3 = data >> 16;
uint8_t data_4 = data >> 24;
*w5100_data = data_1;
*w5100_data = data_2;
*w5100_data = data_3;
*w5100_data = data_4;
}
}
void w5100_config(uint8_t eth_init)
{
w5100_mode = (uint8_t*)(eth_init << 4 | 0xC084);
w5100_addr_hi = w5100_mode + 1;
w5100_addr_lo = w5100_mode + 2;
w5100_data = w5100_mode + 3;
#ifdef SINGLE_SOCKET
// IP65 is inhibited so disable the W5100 Ping Block Mode.
*w5100_mode &= ~0x10;
#endif // SINGLE_SOCKET
// Source IP Address Register
set_quad(0x000F, cfg_ip);
// Subnet Mask Register
set_quad(0x0005, cfg_netmask);
// Gateway IP Address Register
set_quad(0x0001, cfg_gateway);
{
bool do_send;
for (do_send = false; do_send <= true; ++do_send)
{
static uint16_t reg[2] = {0x001A, // RX Memory Size Register
0x001B}; // TX Memory Size Register
static uint16_t addr[2] = {0x6000, // RX Memory
0x4000}; // TX Memory
static uint16_t size[4] = {0x0400, // 1KB Memory
0x0800, // 2KB Memory
0x1000, // 4KB Memory
0x2000}; // 8KB Memory
#ifdef SINGLE_SOCKET
set_byte(reg[do_send], 0x03);
// Set Socket 0 Memory Size to 8KB
addr_basis[do_send] = addr [do_send];
addr_limit[do_send] = addr_basis[do_send] + size[0x03];
addr_mask [do_send] = size[0x03] - 1;
#else // SINGLE_SOCKET
uint8_t sizes = get_byte(reg[do_send]);
// Get Socket 1 Memory Size
addr_basis[do_send] = addr [do_send] + size[sizes & 0x03];
addr_limit[do_send] = addr_basis[do_send] + size[sizes >> 2 & 0x03];
addr_mask [do_send] = size[sizes >> 2 & 0x03] - 1;
#endif // SINGLE_SOCKET
}
}
}
bool w5100_connect(uint32_t addr, uint16_t port)
{
// Socket x Mode Register: TCP
set_byte(SOCK_REG(0x00), 0x01);
// Socket x Source Port Register
set_word(SOCK_REG(0x04), ip65_random_word());
// Socket x Command Register: OPEN
set_byte(SOCK_REG(0x01), 0x01);
// Socket x Status Register: SOCK_INIT ?
while (get_byte(SOCK_REG(0x03)) != 0x13)
{
if (input_check_for_abort_key())
{
return false;
}
}
// Socket x Destination IP Address Register
set_quad(SOCK_REG(0x0C), addr);
// Socket x Destination Port Register
set_word(SOCK_REG(0x10), port);
// Socket x Command Register: CONNECT
set_byte(SOCK_REG(0x01), 0x04);
while (true)
{
// Socket x Status Register
switch (get_byte(SOCK_REG(0x03)))
{
case 0x00: return false; // Socket Status: SOCK_CLOSED
case 0x17: return true; // Socket Status: SOCK_ESTABLISHED
}
if (input_check_for_abort_key())
{
return false;
}
}
}
bool w5100_connected(void)
{
// Socket x Status Register: SOCK_ESTABLISHED ?
return get_byte(SOCK_REG(0x03)) == 0x17;
}
void w5100_disconnect(void)
{
// Socket x Command Register: Command Pending ?
while (get_byte(SOCK_REG(0x01)))
{
if (input_check_for_abort_key())
{
return;
}
}
// Socket x Command Register: DISCON
set_byte(SOCK_REG(0x01), 0x08);
}
uint16_t w5100_data_request(bool do_send)
{
// Socket x Command Register: Command Pending ?
if (get_byte(SOCK_REG(0x01)))
{
return 0;
}
{
uint16_t size = 0;
uint16_t prev_size;
// Reread of nonzero RX Received Size Register / TX Free Size Register
// until its value settles ...
// - is present in the WIZnet driver - getSn_RX_RSR() / getSn_TX_FSR()
// - was additionally tested on 6502 machines to be actually necessary
do
{
prev_size = size;
{
static uint16_t reg[2] = {SOCK_REG(0x26), // Socket x RX Received Size Register
SOCK_REG(0x20)}; // Socket x TX Free Size Register
size = get_word(reg[do_send]);
}
}
while (size != prev_size);
if (!size)
{
return 0;
}
{
static uint16_t reg[2] = {SOCK_REG(0x28), // Socket x RX Read Pointer Register
SOCK_REG(0x24)}; // Socket x TX Write Pointer Register
// Calculate and set physical address
uint16_t addr = get_word(reg[do_send]) & addr_mask [do_send]
| addr_basis[do_send];
set_addr(addr);
#ifdef SINGLE_SOCKET
// The W5100 has the undocumented feature to wrap around the Address Register
// on an Auto-Increment at the end of physical address space to its beginning.
return size;
#else // SINGLE_SOCKET
// Access to *w5100_data is limited both by ...
// - size of received / free space
// - end of physical address space
return MIN(size, addr_limit[do_send] - addr);
#endif // SINGLE_SOCKET
}
}
}
void w5100_data_commit(bool do_send, uint16_t size)
{
{
static uint16_t reg[2] = {SOCK_REG(0x28), // Socket x RX Read Pointer Register
SOCK_REG(0x24)}; // Socket x TX Write Pointer Register
set_word(reg[do_send], get_word(reg[do_send]) + size);
}
{
static uint8_t cmd[2] = {0x40, // Socket Command: RECV
0x20}; // Socket Command: SEND
// Socket x Command Register
set_byte(SOCK_REG(0x01), cmd[do_send]);
}
// Do NOT wait for command completion here, rather
// let W5100 operation overlap with 6502 operation
}

82
w5100.h Normal file
View File

@ -0,0 +1,82 @@
/******************************************************************************
Copyright (c) 2014, Oliver Schmidt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
#ifndef _W5100_H_
#define _W5100_H_
#ifndef __APPLE2ENH__
#error W5100 auto-increment register access requires 65C02.
#endif
#include <stdint.h>
#include <stdbool.h>
uint16_t w5100_data_request(bool do_send);
void w5100_data_commit(bool do_send, uint16_t size);
// After w5100_receive_request() every read operation returns the next byte
// from the server.
// After w5100_send_request() every write operation prepares the next byte
// to be sent to the server.
extern volatile uint8_t* w5100_data;
// Configure W5100 Ethernet controller with additional information from IP65
// after the IP65 TCP/IP stack has been configured.
void w5100_config(uint8_t eth_init);
// Connect to server with IP address <server_addr> on TCP port <server_port>.
// Return true if the connection is established, return false otherwise.
bool w5100_connect(uint32_t addr, uint16_t port);
// Check if still connected to server.
// Return true if the connection is established, return false otherwise.
bool w5100_connected(void);
// Disconnect from server.
void w5100_disconnect(void);
// Request to receive data from the server.
// Return maximum number of bytes to be received by reading from *w5100_data.
#define w5100_receive_request() w5100_data_request(false)
// Commit receiving of <size> bytes from server. <size> may be smaller than
// the return value of w5100_receive_request(). Not commiting at all just
// makes the next request receive the same data again.
#define w5100_receive_commit(size) w5100_data_commit(false, (size))
// Request to send data to the server.
// Return maximum number of bytes to be send by writing to *w5100_data.
#define w5100_send_request() w5100_data_request(true)
// Commit sending of <size> bytes to server. <size> is usually smaller than
// the return value of w5100_send_request(). Not commiting at all just turns
// the w5100_send_request() - and the writes to *w5100_data - into NOPs.
#define w5100_send_commit(size) w5100_data_commit(true, (size))
#endif

185
w5100_http.c Normal file
View File

@ -0,0 +1,185 @@
/******************************************************************************
Copyright (c) 2020, Oliver Schmidt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include "../inc/ip65.h"
#include "w5100.h"
#include "w5100_http.h"
// Both pragmas are obligatory to have cc65 generate code
// suitable to access the W5100 auto-increment registers.
#pragma optimize (on)
#pragma static-locals (on)
bool w5100_http_open(uint32_t addr, uint16_t port, const char* selector,
char* buffer, size_t length)
{
printf("Connecting to %s:%d ", dotted_quad(addr), port);
if (!w5100_connect(addr, port))
{
printf("- Connect failed\n");
return false;
}
printf("- Ok\n\nSending request ");
{
uint16_t snd;
uint16_t pos = 0;
uint16_t len = strlen(selector);
while (len)
{
if (input_check_for_abort_key())
{
printf("- User abort\n");
w5100_disconnect();
return false;
}
snd = w5100_send_request();
if (!snd)
{
if (!w5100_connected())
{
printf("- Connection lost\n");
return false;
}
continue;
}
if (len < snd)
{
snd = len;
}
{
// One less to allow for faster pre-increment below
const char *dataptr = selector + pos - 1;
uint16_t i;
for (i = 0; i < snd; ++i)
{
// The variable is necessary to have cc65 generate code
// suitable to access the W5100 auto-increment register.
char data = *++dataptr;
*w5100_data = data;
}
}
w5100_send_commit(snd);
len -= snd;
pos += snd;
}
}
printf("- Ok\n\nReceiving response ");
{
uint16_t rcv;
bool body = false;
uint16_t len = 0;
while (!body)
{
if (input_check_for_abort_key())
{
printf("- User abort\n");
w5100_disconnect();
return false;
}
rcv = w5100_receive_request();
if (!rcv)
{
if (!w5100_connected())
{
printf("- Connection lost\n");
return false;
}
continue;
}
if (rcv > length - len)
{
rcv = length - len;
}
{
// One less to allow for faster pre-increment below
char *dataptr = buffer + len - 1;
uint16_t i;
for (i = 0; i < rcv; ++i)
{
// The variable is necessary to have cc65 generate code
// suitable to access the W5100 auto-increment register.
char data = *w5100_data;
*++dataptr = data;
if (!memcmp(dataptr - 3, "\r\n\r\n", 4))
{
rcv = i + 1;
body = true;
}
}
}
w5100_receive_commit(rcv);
len += rcv;
// No body found in full buffer
if (len == sizeof(buffer))
{
printf("- Invalid response\n");
w5100_disconnect();
return false;
}
}
// Replace "HTTP/1.1" with "HTTP/1.0"
buffer[7] = '0';
if (memcmp(buffer, "HTTP/1.0 200", 12))
{
if (!memcmp(buffer, "HTTP/1.0", 8))
{
char *eol = strchr(buffer,'\r');
*eol = '\0';
printf("- Status%s\n", buffer + 8);
}
else
{
printf("- Unknown response\n");
}
w5100_disconnect();
return false;
}
}
return true;
}

48
w5100_http.h Normal file
View File

@ -0,0 +1,48 @@
/******************************************************************************
Copyright (c) 2020, Oliver Schmidt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
#ifndef _W5100_HTTP_H_
#define _W5100_HTTP_H_
#ifndef __APPLE2ENH__
#error W5100 auto-increment register access requires 65C02.
#endif
#include <stdint.h>
#include <stdbool.h>
// Connect to server with IP address <server_addr> on TCP port <server_port>,
// then HTTP GET <selector> and consume HTTP response header. Provide feedback
// on progress to the user via STDOUT. After returning from w5100_http_open()
// the connection is ready to consume the HTTP body.
// Return true if the connection is established, return false otherwise.
bool w5100_http_open(uint32_t addr, uint16_t port, const char* selector,
char* buffer, size_t length);
#endif