278 lines
8.5 KiB
C
278 lines
8.5 KiB
C
// Serial add-on demo: an echo terminal + loopback self-test with no text
|
|
// assets. It opens the DEFAULT serial port at 9600 8N1 and each frame:
|
|
// - drains received bytes (jlSerialPoll + jlSerialRead), echoes each one
|
|
// straight back out the port, and shows the most recent byte as eight
|
|
// lit/unlit bit cells (MSB left) plus a green RX blink;
|
|
// - sends the ASCII for any letter/digit key pressed, or a banner string on
|
|
// SPACE, with a yellow TX blink.
|
|
//
|
|
// With a loopback plug (TX tied to RX) it is a self-test: press SPACE and the
|
|
// banner streams back in, driving the RX blink and the bit cells. Without one,
|
|
// TX still blinks and typing still transmits -- wire a real device to the port
|
|
// to see its replies. ESC quits.
|
|
//
|
|
// Serial is an OPT-IN add-on: it is reached through <joey/serial.h>, which the
|
|
// umbrella <joey/joey.h> deliberately does NOT pull in.
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <joey/joey.h>
|
|
#include <joey/serial.h>
|
|
|
|
#define CELL_W 28
|
|
#define CELL_H 40
|
|
#define CELL_GAP 4
|
|
#define CELLS_X 8
|
|
#define BITS_ORIGIN_X 12
|
|
#define BITS_ORIGIN_Y 30
|
|
|
|
#define LAMP_W 52
|
|
#define LAMP_H 24
|
|
#define LAMP_GAP 8
|
|
#define LAMP_Y 110
|
|
#define LAMP_TX_X 12
|
|
#define LAMP_RX_X (LAMP_TX_X + LAMP_W + LAMP_GAP)
|
|
#define LAMP_LINK_X (LAMP_RX_X + LAMP_W + LAMP_GAP)
|
|
|
|
#define BLINK_FRAMES 6
|
|
|
|
#define COLOR_BACKGROUND 0
|
|
#define COLOR_BIT_OFF 1
|
|
#define COLOR_BIT_ON 2
|
|
#define COLOR_TX_ON 3
|
|
#define COLOR_RX_ON 4
|
|
#define COLOR_LAMP_IDLE 5
|
|
#define COLOR_LINK_OPEN 6
|
|
#define COLOR_LINK_SHUT 7
|
|
|
|
#define SERIAL_BAUD 9600u
|
|
#define SERIAL_DATABITS 8u
|
|
#define SERIAL_STOPBITS 1u
|
|
|
|
#define RX_CHUNK 32
|
|
|
|
static const char gBanner[] = "Hello from JoeyLib serial!\r\n";
|
|
|
|
// Keys that transmit, paired with the ASCII byte each one sends.
|
|
typedef struct KeySendT {
|
|
jlKeyE key;
|
|
char ch;
|
|
} KeySendT;
|
|
|
|
static const KeySendT gKeySend[] = {
|
|
{ KEY_A, 'A' }, { KEY_B, 'B' }, { KEY_C, 'C' }, { KEY_D, 'D' },
|
|
{ KEY_E, 'E' }, { KEY_F, 'F' }, { KEY_G, 'G' }, { KEY_H, 'H' },
|
|
{ KEY_I, 'I' }, { KEY_J, 'J' }, { KEY_K, 'K' }, { KEY_L, 'L' },
|
|
{ KEY_M, 'M' }, { KEY_N, 'N' }, { KEY_O, 'O' }, { KEY_P, 'P' },
|
|
{ KEY_Q, 'Q' }, { KEY_R, 'R' }, { KEY_S, 'S' }, { KEY_T, 'T' },
|
|
{ KEY_U, 'U' }, { KEY_V, 'V' }, { KEY_W, 'W' }, { KEY_X, 'X' },
|
|
{ KEY_Y, 'Y' }, { KEY_Z, 'Z' },
|
|
{ KEY_0, '0' }, { KEY_1, '1' }, { KEY_2, '2' }, { KEY_3, '3' },
|
|
{ KEY_4, '4' }, { KEY_5, '5' }, { KEY_6, '6' }, { KEY_7, '7' },
|
|
{ KEY_8, '8' }, { KEY_9, '9' },
|
|
{ KEY_RETURN, '\r' }
|
|
};
|
|
|
|
#define KEY_SEND_COUNT ((int16_t)(sizeof(gKeySend) / sizeof(gKeySend[0])))
|
|
|
|
static int16_t gLastByte = -1; // most recent received byte, -1 = none yet
|
|
static int16_t gTxTimer = 0; // frames the TX lamp stays lit
|
|
static int16_t gRxTimer = 0; // frames the RX lamp stays lit
|
|
|
|
static void buildPalette(jlSurfaceT *screen);
|
|
static void drawBits(jlSurfaceT *screen, int16_t value);
|
|
static void drawLamp(jlSurfaceT *screen, int16_t x, bool active, uint8_t activeColor);
|
|
static void drawLinkLamp(jlSurfaceT *screen, bool open);
|
|
static int16_t pollKeySend(void);
|
|
static void sendBytes(const char *bytes, uint16_t len);
|
|
|
|
|
|
static void buildPalette(jlSurfaceT *screen) {
|
|
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
|
colors[i] = 0x0000;
|
|
}
|
|
colors[COLOR_BACKGROUND] = 0x0000; // black
|
|
colors[COLOR_BIT_OFF] = 0x0114; // dim blue
|
|
colors[COLOR_BIT_ON] = 0x00FF; // bright cyan
|
|
colors[COLOR_TX_ON] = 0x0FF0; // yellow
|
|
colors[COLOR_RX_ON] = 0x00F0; // green
|
|
colors[COLOR_LAMP_IDLE] = 0x0333; // gray
|
|
colors[COLOR_LINK_OPEN] = 0x00C0; // dim green
|
|
colors[COLOR_LINK_SHUT] = 0x0C00; // dim red
|
|
|
|
jlPaletteSet(screen, 0, colors);
|
|
}
|
|
|
|
|
|
// Eight cells, MSB at the left. value < 0 draws all cells "off".
|
|
static void drawBits(jlSurfaceT *screen, int16_t value) {
|
|
int16_t i;
|
|
|
|
for (i = 0; i < CELLS_X; i++) {
|
|
int16_t x = (int16_t)(BITS_ORIGIN_X + i * (CELL_W + CELL_GAP));
|
|
bool set = (value >= 0) && ((value & (0x80 >> i)) != 0);
|
|
|
|
jlFillRect(screen, x, BITS_ORIGIN_Y, CELL_W, CELL_H, set ? COLOR_BIT_ON : COLOR_BIT_OFF);
|
|
}
|
|
}
|
|
|
|
|
|
static void drawLamp(jlSurfaceT *screen, int16_t x, bool active, uint8_t activeColor) {
|
|
jlFillRect(screen, x, LAMP_Y, LAMP_W, LAMP_H, active ? activeColor : COLOR_LAMP_IDLE);
|
|
}
|
|
|
|
|
|
static void drawLinkLamp(jlSurfaceT *screen, bool open) {
|
|
jlFillRect(screen, LAMP_LINK_X, LAMP_Y, LAMP_W, LAMP_H, open ? COLOR_LINK_OPEN : COLOR_LINK_SHUT);
|
|
}
|
|
|
|
|
|
// Return the ASCII byte for the first transmit key pressed this frame, or -1.
|
|
static int16_t pollKeySend(void) {
|
|
int16_t i;
|
|
|
|
for (i = 0; i < KEY_SEND_COUNT; i++) {
|
|
if (jlKeyPressed(gKeySend[i].key)) {
|
|
return (int16_t)(uint8_t)gKeySend[i].ch;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void sendBytes(const char *bytes, uint16_t len) {
|
|
uint16_t sent;
|
|
|
|
sent = 0u;
|
|
// jlSerialWrite may report a short count under flow control; retry the
|
|
// remainder for a few frames' worth of spins so the banner is not clipped.
|
|
while (sent < len) {
|
|
uint16_t n = jlSerialWrite((const uint8_t *)bytes + sent, (uint16_t)(len - sent));
|
|
|
|
if (n == 0u) {
|
|
break;
|
|
}
|
|
sent = (uint16_t)(sent + n);
|
|
}
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
jlConfigT config;
|
|
jlSurfaceT *screen;
|
|
jlSerialConfigT serialCfg;
|
|
bool linkOpen;
|
|
|
|
config.codegenBytes = 8 * 1024;
|
|
config.audioBytes = 64UL * 1024;
|
|
|
|
if (!jlInit(&config)) {
|
|
fprintf(stderr, "jlInit failed: %s\n", jlLastError());
|
|
return 1;
|
|
}
|
|
|
|
screen = jlStageGet();
|
|
if (screen == NULL) {
|
|
fprintf(stderr, "jlStageGet returned NULL\n");
|
|
jlShutdown();
|
|
return 1;
|
|
}
|
|
|
|
serialCfg.baud = SERIAL_BAUD;
|
|
serialCfg.dataBits = SERIAL_DATABITS;
|
|
serialCfg.stopBits = SERIAL_STOPBITS;
|
|
serialCfg.parity = JL_SERIAL_PARITY_NONE;
|
|
serialCfg.flow = JL_SERIAL_FLOW_NONE;
|
|
serialCfg.unit = 0u;
|
|
linkOpen = jlSerialOpen(JL_SERIAL_DEFAULT, &serialCfg);
|
|
|
|
buildPalette(screen);
|
|
jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
|
jlSurfaceClear(screen, COLOR_BACKGROUND);
|
|
drawBits(screen, gLastByte);
|
|
drawLamp(screen, LAMP_TX_X, false, COLOR_TX_ON);
|
|
drawLamp(screen, LAMP_RX_X, false, COLOR_RX_ON);
|
|
drawLinkLamp(screen, linkOpen);
|
|
jlStagePresent();
|
|
jlInputPoll();
|
|
|
|
for (;;) {
|
|
uint8_t rx[RX_CHUNK];
|
|
uint16_t got;
|
|
int16_t keyByte;
|
|
bool txFired;
|
|
bool dirty;
|
|
|
|
jlInputPoll();
|
|
if (jlKeyPressed(KEY_ESCAPE)) {
|
|
break;
|
|
}
|
|
jlSerialPoll();
|
|
|
|
txFired = false;
|
|
dirty = false;
|
|
|
|
// Transmit: a banner on SPACE, otherwise the pressed key's ASCII.
|
|
if (jlKeyPressed(KEY_SPACE)) {
|
|
sendBytes(gBanner, (uint16_t)(sizeof(gBanner) - 1u));
|
|
txFired = true;
|
|
} else {
|
|
keyByte = pollKeySend();
|
|
if (keyByte >= 0) {
|
|
char c = (char)keyByte;
|
|
|
|
sendBytes(&c, 1u);
|
|
txFired = true;
|
|
}
|
|
}
|
|
|
|
// Receive: show the last byte, echo the whole chunk back out.
|
|
got = jlSerialRead(rx, (uint16_t)RX_CHUNK);
|
|
if (got > 0u) {
|
|
gLastByte = (int16_t)rx[got - 1u];
|
|
drawBits(screen, gLastByte);
|
|
dirty = true;
|
|
gRxTimer = BLINK_FRAMES;
|
|
sendBytes((const char *)rx, got); // echo
|
|
}
|
|
|
|
if (txFired && gTxTimer == 0) {
|
|
drawLamp(screen, LAMP_TX_X, true, COLOR_TX_ON);
|
|
dirty = true;
|
|
}
|
|
if (txFired) {
|
|
gTxTimer = BLINK_FRAMES;
|
|
}
|
|
|
|
// Age the blink lamps, redrawing only on the lit->idle transition.
|
|
if (gTxTimer > 0) {
|
|
gTxTimer--;
|
|
if (gTxTimer == 0) {
|
|
drawLamp(screen, LAMP_TX_X, false, COLOR_TX_ON);
|
|
dirty = true;
|
|
}
|
|
}
|
|
if (gRxTimer > 0) {
|
|
if (got > 0u) {
|
|
drawLamp(screen, LAMP_RX_X, true, COLOR_RX_ON);
|
|
dirty = true;
|
|
}
|
|
gRxTimer--;
|
|
if (gRxTimer == 0) {
|
|
drawLamp(screen, LAMP_RX_X, false, COLOR_RX_ON);
|
|
dirty = true;
|
|
}
|
|
}
|
|
|
|
if (dirty) {
|
|
jlStagePresent();
|
|
}
|
|
}
|
|
|
|
jlSerialClose();
|
|
jlShutdown();
|
|
return 0;
|
|
}
|