joeylib2/examples/serial/serial.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;
}