Add firmware CDC protocol module

This commit is contained in:
2026-04-11 12:54:51 +08:00
parent 1b2fe79b5d
commit 9ba44586b3
2 changed files with 246 additions and 1 deletions

View File

@@ -92,5 +92,30 @@ Implemented behavior:
## Planned next nodes
- CDC transport module
- GATT transport module
### Node 4: CDC transport module
Files added in this step:
- `src/modules/usb_cdc_proto_module.c`
Design notes:
- use the CDC ACM UART abstraction from Zephyr
- keep CDC transport separate from protocol details by delegating encode/decode
to `keyboard_proto`
- report private outgoing events back to host over CDC
Implemented behavior:
- receive raw CDC bytes by UART IRQ
- accumulate bytes into a stream buffer
- extract protobuf `CdcFrame`
- decode host messages and dispatch them
- send:
- `HelloRsp`
- `Ack`
- `Error`
- `FunctionKeyEvent`
- `LedState`

View File

@@ -0,0 +1,220 @@
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <app_event_manager.h>
#define MODULE usb_cdc_proto
#include <caf/events/module_state_event.h>
#include "function_key_event.h"
#include "keyboard_led_event.h"
#include "keyboard_proto.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define CDC_RX_BUFFER_SIZE 256
#define CDC_TX_BUFFER_SIZE KEYBOARD_PROTO_MAX_FRAME_SIZE
struct usb_cdc_proto_ctx {
const struct device *dev;
struct k_work rx_work;
uint8_t rx_buffer[CDC_RX_BUFFER_SIZE];
size_t rx_len;
bool ready;
};
static struct usb_cdc_proto_ctx g_usb_cdc = {
.dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart),
};
static uint32_t keyboard_proto_packet_type_from_body(
const struct keyboard_cdc_CdcPacketBody *body)
{
switch (body->which_body) {
case 1U:
return 0x01U;
case 2U:
return 0x02U;
case 3U:
return 0x10U;
case 4U:
return 0x20U;
case 5U:
return 0x21U;
case 6U:
return 0x30U;
case 7U:
return 0x31U;
case 8U:
return 0x7EU;
case 9U:
return 0x7FU;
default:
return 0U;
}
}
static bool usb_cdc_proto_send_body(
const struct keyboard_cdc_CdcPacketBody *body,
void *user_data)
{
struct usb_cdc_proto_ctx *ctx = user_data;
uint8_t tx_buffer[CDC_TX_BUFFER_SIZE];
size_t tx_size = 0U;
if ((ctx == NULL) || !ctx->ready || (ctx->dev == NULL)) {
return false;
}
if (!keyboard_proto_encode_cdc_frame(
keyboard_proto_packet_type_from_body(body),
body,
tx_buffer,
sizeof(tx_buffer),
&tx_size)) {
return false;
}
for (size_t i = 0; i < tx_size; ++i) {
uart_poll_out(ctx->dev, tx_buffer[i]);
}
return true;
}
static void usb_cdc_proto_process_rx_work(struct k_work *work)
{
struct keyboard_cdc_CdcFrame frame;
struct keyboard_cdc_CdcPacketBody body;
ARG_UNUSED(work);
while (keyboard_proto_try_take_cdc_frame(
g_usb_cdc.rx_buffer,
&g_usb_cdc.rx_len,
&frame)) {
if (!keyboard_proto_decode_body(frame.payload.bytes,
frame.payload.size,
&body)) {
LOG_WRN("Drop invalid CDC body");
continue;
}
(void)keyboard_proto_handle_host_body(
&body,
KEYBOARD_PROTO_TRANSPORT_CDC,
usb_cdc_proto_send_body,
&g_usb_cdc);
}
}
static void usb_cdc_proto_irq_handler(const struct device *dev, void *user_data)
{
uint8_t buffer[64];
ARG_UNUSED(user_data);
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
if (!uart_irq_rx_ready(dev)) {
continue;
}
int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer));
if (recv_len <= 0) {
continue;
}
if ((g_usb_cdc.rx_len + (size_t)recv_len) > sizeof(g_usb_cdc.rx_buffer)) {
LOG_WRN("CDC RX overflow, drop buffer");
g_usb_cdc.rx_len = 0U;
continue;
}
memcpy(&g_usb_cdc.rx_buffer[g_usb_cdc.rx_len], buffer, recv_len);
g_usb_cdc.rx_len += (size_t)recv_len;
k_work_submit(&g_usb_cdc.rx_work);
}
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
return false;
}
if (!device_is_ready(g_usb_cdc.dev)) {
LOG_ERR("CDC ACM device not ready");
module_set_state(MODULE_STATE_ERROR);
return false;
}
k_work_init(&g_usb_cdc.rx_work, usb_cdc_proto_process_rx_work);
uart_irq_callback_set(g_usb_cdc.dev, usb_cdc_proto_irq_handler);
uart_irq_rx_enable(g_usb_cdc.dev);
g_usb_cdc.ready = true;
module_set_state(MODULE_STATE_READY);
return false;
}
static bool handle_function_key_event(const struct function_key_event *event)
{
struct keyboard_cdc_CdcPacketBody body;
if (!g_usb_cdc.ready) {
return false;
}
if (!keyboard_proto_build_function_key_event_body(event->usage,
event->pressed,
&body)) {
return false;
}
(void)usb_cdc_proto_send_body(&body, &g_usb_cdc);
return false;
}
static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
{
struct keyboard_cdc_CdcPacketBody body;
if (!g_usb_cdc.ready) {
return false;
}
if (!keyboard_proto_build_led_state_body(
keyboard_led_event_get_mask(event),
&body)) {
return false;
}
(void)usb_cdc_proto_send_body(&body, &g_usb_cdc);
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
return handle_module_state_event(cast_module_state_event(aeh));
}
if (is_function_key_event(aeh)) {
return handle_function_key_event(cast_function_key_event(aeh));
}
if (is_keyboard_led_event(aeh)) {
return handle_keyboard_led_event(cast_keyboard_led_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, function_key_event);
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);