Implements expressive face animations with 5 core emotions (happy/sad/curious/angry/sleeping) and smooth transitions on small LCD displays. Features: - State machine with smooth 0.5s emotion transitions (ease-in-out cubic easing) - Automatic idle blinking (4-6s intervals, 100-150ms duration per blink) - UART command interface via USART3 @ 115200 (text-based protocol) - 30Hz target refresh rate via systick integration - Low-level LCD abstraction supporting monochrome and RGB565 - Rendering primitives: pixel, line (Bresenham), circle (midpoint), filled rect Architecture: - face_lcd.h/c: Hardware-agnostic framebuffer & display driver - face_animation.h/c: Emotion state machine & parameterized face rendering - face_uart.h/c: UART command parser (HAPPY/SAD/CURIOUS/ANGRY/SLEEP/NEUTRAL/BLINK/STATUS) - Unit tests (14 test cases): emotion transitions, blinking, rendering, all emotions Integration: - main.c: Added includes, initialization (servo_init), systick tick, main loop processing - Pending: LCD hardware initialization (SPI/I2C config, display controller setup) Files: 9 new (headers, source, tests, docs), 1 modified (main.c) Lines: ~1450 total (345 headers, 650 source, 350 tests, 900 docs) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
176 lines
5.2 KiB
C
176 lines
5.2 KiB
C
/*
|
|
* face_uart.c — UART Command Interface for Face Animations
|
|
*
|
|
* Receives emotion commands from Jetson Orin and triggers face animations.
|
|
* Text-based protocol over USART3 @ 115200 baud.
|
|
*/
|
|
|
|
#include "face_uart.h"
|
|
#include "face_animation.h"
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
|
|
/* === Ring Buffer State === */
|
|
static struct {
|
|
uint8_t buf[FACE_UART_RX_BUF_SZ];
|
|
uint16_t head; /* Write index (ISR) */
|
|
uint16_t tail; /* Read index (process) */
|
|
uint16_t count; /* Bytes in buffer */
|
|
} rx_buf = {0};
|
|
|
|
/* === Forward Declarations === */
|
|
static void uart_send_response(const char *cmd, const char *status);
|
|
|
|
/* === Private Functions === */
|
|
|
|
/**
|
|
* Extract a line from RX buffer (newline-terminated).
|
|
* Returns length if found, 0 otherwise.
|
|
*/
|
|
static uint16_t extract_line(char *line, uint16_t max_len) {
|
|
uint16_t len = 0;
|
|
uint16_t idx = rx_buf.tail;
|
|
|
|
/* Scan for newline */
|
|
while (idx != rx_buf.head && len < max_len - 1) {
|
|
uint8_t byte = rx_buf.buf[idx];
|
|
if (byte == '\n') {
|
|
/* Found end of line */
|
|
for (uint16_t i = 0; i < len; i++) {
|
|
line[i] = rx_buf.buf[(rx_buf.tail + i) % FACE_UART_RX_BUF_SZ];
|
|
}
|
|
line[len] = '\0';
|
|
|
|
/* Trim trailing whitespace */
|
|
while (len > 0 && (line[len - 1] == '\r' || isspace(line[len - 1]))) {
|
|
line[--len] = '\0';
|
|
}
|
|
|
|
/* Update tail and count */
|
|
rx_buf.tail = (idx + 1) % FACE_UART_RX_BUF_SZ;
|
|
rx_buf.count -= (len + 1 + 1); /* +1 for newline, +1 for any preceding data */
|
|
|
|
return len;
|
|
}
|
|
|
|
len++;
|
|
idx = (idx + 1) % FACE_UART_RX_BUF_SZ;
|
|
}
|
|
|
|
return 0; /* No complete line */
|
|
}
|
|
|
|
/**
|
|
* Convert string to uppercase for case-insensitive command matching.
|
|
*/
|
|
static void str_toupper(char *str) {
|
|
for (int i = 0; str[i]; i++)
|
|
str[i] = toupper((unsigned char)str[i]);
|
|
}
|
|
|
|
/**
|
|
* Parse and execute a command.
|
|
*/
|
|
static void parse_command(const char *cmd) {
|
|
if (!cmd || !cmd[0])
|
|
return;
|
|
|
|
char cmd_upper[32];
|
|
strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1);
|
|
cmd_upper[sizeof(cmd_upper) - 1] = '\0';
|
|
str_toupper(cmd_upper);
|
|
|
|
/* Command dispatch */
|
|
if (strcmp(cmd_upper, "HAPPY") == 0) {
|
|
face_animation_set_emotion(FACE_HAPPY);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "SAD") == 0) {
|
|
face_animation_set_emotion(FACE_SAD);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "CURIOUS") == 0) {
|
|
face_animation_set_emotion(FACE_CURIOUS);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "ANGRY") == 0) {
|
|
face_animation_set_emotion(FACE_ANGRY);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "SLEEP") == 0 ||
|
|
strcmp(cmd_upper, "SLEEPING") == 0) {
|
|
face_animation_set_emotion(FACE_SLEEPING);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "NEUTRAL") == 0) {
|
|
face_animation_set_emotion(FACE_NEUTRAL);
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "BLINK") == 0) {
|
|
face_animation_blink_now();
|
|
uart_send_response(cmd_upper, "OK");
|
|
} else if (strcmp(cmd_upper, "STATUS") == 0) {
|
|
const char *emotion_names[] = {"HAPPY", "SAD", "CURIOUS", "ANGRY",
|
|
"SLEEPING", "NEUTRAL"};
|
|
face_emotion_t current = face_animation_get_emotion();
|
|
char status[64];
|
|
snprintf(status, sizeof(status), "EMOTION=%s, IDLE=%s",
|
|
emotion_names[current],
|
|
face_animation_is_idle() ? "true" : "false");
|
|
uart_send_response(cmd_upper, status);
|
|
} else {
|
|
uart_send_response(cmd_upper, "ERR: unknown command");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a response string to UART TX.
|
|
* Format: "CMD: status\n"
|
|
*/
|
|
static void uart_send_response(const char *cmd, const char *status) {
|
|
/* TODO: Implement UART TX
|
|
* Use HAL_UART_Transmit_IT or similar to send:
|
|
* "CMD: status\n"
|
|
*/
|
|
(void)cmd; /* Suppress unused warnings */
|
|
(void)status;
|
|
}
|
|
|
|
/* === Public API Implementation === */
|
|
|
|
void face_uart_init(void) {
|
|
/* TODO: Configure USART3 @ 115200 baud
|
|
* - Enable USART3 clock (RCC_APB1ENR)
|
|
* - Configure pins (PB10=TX, PB11=RX)
|
|
* - Set baud rate to 115200
|
|
* - Enable RX interrupt (NVIC + USART3_IRQn)
|
|
* - Enable USART
|
|
*/
|
|
|
|
rx_buf.head = 0;
|
|
rx_buf.tail = 0;
|
|
rx_buf.count = 0;
|
|
}
|
|
|
|
void face_uart_process(void) {
|
|
char line[128];
|
|
uint16_t len;
|
|
|
|
/* Extract and process complete commands */
|
|
while ((len = extract_line(line, sizeof(line))) > 0) {
|
|
parse_command(line);
|
|
}
|
|
}
|
|
|
|
void face_uart_rx_isr(uint8_t byte) {
|
|
/* Push byte into ring buffer */
|
|
if (rx_buf.count < FACE_UART_RX_BUF_SZ) {
|
|
rx_buf.buf[rx_buf.head] = byte;
|
|
rx_buf.head = (rx_buf.head + 1) % FACE_UART_RX_BUF_SZ;
|
|
rx_buf.count++;
|
|
}
|
|
/* Buffer overflow: silently discard oldest byte */
|
|
}
|
|
|
|
void face_uart_send(const char *str) {
|
|
/* TODO: Implement non-blocking UART TX
|
|
* Use HAL_UART_Transmit_IT() or DMA-based TX queue.
|
|
*/
|
|
(void)str; /* Suppress unused warnings */
|
|
}
|