// 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 , which the // umbrella deliberately does NOT pull in. #include #include #include #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; }