Add firmware CDC protocol module
This commit is contained in:
@@ -92,5 +92,30 @@ Implemented behavior:
|
|||||||
|
|
||||||
## Planned next nodes
|
## Planned next nodes
|
||||||
|
|
||||||
- CDC transport module
|
|
||||||
- GATT 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`
|
||||||
|
|||||||
220
src/modules/usb_cdc_proto_module.c
Normal file
220
src/modules/usb_cdc_proto_module.c
Normal 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);
|
||||||
Reference in New Issue
Block a user