Sending commands

Sending commands to CH32V003 over serial from the computer.

#include "debug.h"
#include <string.h>

/**
 * CH32V003 - Full USART1 Serial Input/Output Example
 *
 * Wiring:
 *   PD5 -> TX  (connect to your USB-serial adapter RX)
 *   PD6 -> RX  (connect to your USB-serial adapter TX)
 *
 * Behavior:
 *   - Echoes every character back as you type
 *   - When Enter is pressed, prints the full line back with a prefix
 *   - Demonstrates ring buffer + ISR-driven RX, polling TX
 *
 */

/* ─── Ring Buffer ──────────────────────────────────────────────── */
#define RX_BUF_SIZE 128  // Must be power of 2

typedef struct {
    volatile uint8_t  buf[RX_BUF_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} RingBuf;

static RingBuf rxBuf = {0};

static inline void rb_push(RingBuf *rb, uint8_t byte) {
    uint16_t next = (rb->head + 1) & (RX_BUF_SIZE - 1);

    if (next != rb->tail) {          // drop byte if full
        rb->buf[rb->head] = byte;
        rb->head = next;
    }
}

static inline int rb_pop(RingBuf *rb, uint8_t *out) {
    if (rb->tail == rb->head) {
        return 0;  // empty
    }

    *out = rb->buf[rb->tail];

    rb->tail = (rb->tail + 1) & (RX_BUF_SIZE - 1);

    return 1;
}

static inline int rb_available(RingBuf *rb) {
    return rb->head != rb->tail;
}

/* ─── USART1 ISR ───────────────────────────────────────────────── */
void USART1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        uint8_t byte = (uint8_t)USART_ReceiveData(USART1); // also clears RXNE
        rb_push(&rxBuf, byte);
    }
    // Clear overrun if it occurred, otherwise RX stalls
    if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET) {
        (void)USART_ReceiveData(USART1);
    }
}

/* ─── USART Helpers ────────────────────────────────────────────── */
void USART1_Init(uint32_t baud) {
    // 1. Clocks
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1, ENABLE);

    // 2. GPIO: PD5=TX, PD6=RX
    GPIO_InitTypeDef gpio = {0};

    gpio.GPIO_Pin   = GPIO_Pin_5;          // TX
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &gpio);

    gpio.GPIO_Pin  = GPIO_Pin_6;           // RX
    gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOD, &gpio);

    // 3. USART peripheral
    USART_InitTypeDef usart = {0};
    usart.USART_BaudRate            = baud;
    usart.USART_WordLength          = USART_WordLength_8b;
    usart.USART_StopBits            = USART_StopBits_1;
    usart.USART_Parity              = USART_Parity_No;
    usart.USART_Mode                = USART_Mode_Tx | USART_Mode_Rx;
    usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART1, &usart);

    // 4. Enable RXNE interrupt
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    // 5. NVIC
    NVIC_SetPriority(USART1_IRQn, 0);
    NVIC_EnableIRQ(USART1_IRQn);

    // 6. Enable peripheral
    USART_Cmd(USART1, ENABLE);
}

// Send a single byte (blocking)
void USART1_SendByte(uint8_t byte) {
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, byte);
}

// Send a null-terminated string
void USART1_SendStr(const char *s) {
    while (*s) USART1_SendByte((uint8_t)*s++);
}

// Send string + CR+LF
void USART1_SendLine(const char *s) {
    USART1_SendStr(s);
    USART1_SendByte('\r');
    USART1_SendByte('\n');
}

/* ─── Read a line from the ring buffer ────────────────────────── */
// Returns 1 when a full line (terminated by \r or \n) is ready in buf.
// Call repeatedly in your main loop; non-blocking between characters.
int USART1_ReadLine(char *buf, uint16_t maxLen, uint16_t *pos) {
    uint8_t byte;
    while (rb_available(&rxBuf)) {
        rb_pop(&rxBuf, &byte);

        // Echo character back to terminal
        USART1_SendByte(byte);

        if (byte == '\r' || byte == '\n') {
            buf[*pos] = '\0';
            *pos = 0;
            if (buf[0] != '\0') {   // ignore blank lines
                USART1_SendByte('\r');
                USART1_SendByte('\n');
                return 1;
            }
        } else if (byte == 0x7F || byte == '\b') {
            // Backspace / DEL
            if (*pos > 0) {
                (*pos)--;
                USART1_SendStr("\b \b");  // erase on terminal
            }
        } else if (*pos < maxLen - 1) {
            buf[(*pos)++] = (char)byte;
        }
    }
    return 0;
}

typedef void (*CmdHandler)(void);

typedef struct {
    const char   *name;
    CmdHandler    handler;
} Command;

// --- handlers ---
void cmd_hello(void) { USART1_SendLine("Hello to you too!"); }
void cmd_ver(void)   { USART1_SendLine("CH32V003 Demo v1.1"); }
void cmd_clock(void) { printf("SystemClk: %d\r\n", SystemCoreClock); }
void cmd_id(void)    { printf("ChipID: %08x\r\n", DBGMCU_GetCHIPID()); }

// --- table ---
static const Command commands[] = {
    { "hello", cmd_hello },
    { "ver",   cmd_ver   },
    { "clock", cmd_clock },
    { "id",    cmd_id    },
};
#define CMD_COUNT (sizeof(commands) / sizeof(commands[0]))

// --- dispatch ---
void handle_command(const char *input) {
    for (uint8_t i = 0; i < CMD_COUNT; i++) {
        if (strcmp(input, commands[i].name) == 0) {
            commands[i].handler();
            return;
        }
    }
    USART1_SendLine("Unknown command.");
}

/* ─── main ─────────────────────────────────────────────────────── */
int main(void) {
    SystemInit();           // sets up 48 MHz HSI PLL
    USART1_Init(115200);

    USART1_SendLine("CH32V003 USART ready. Type something and press Enter:");

    char    lineBuf[64] = {0};
    uint16_t linePos    = 0;

    while (1) {
        if (USART1_ReadLine(lineBuf, sizeof(lineBuf), &linePos)) {
            // USART1_SendStr("You typed: ");
            // USART1_SendLine(lineBuf);

            handle_command(lineBuf);
        }
    }
}

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.