feat(usb): 添加USB CDC功能模块支持
- 在CMakeLists.txt中添加usb_cdc_module、usb_cdc_test_module和 usb_device_module源文件 - 添加usb_cdc_rx_event、usb_cdc_tx_event、usb_device_state_event、 usb_function_ready_event和usb_prepare_event事件定义 - 实现USB CDC串口通信功能,包括接收和发送数据处理 - 添加USB设备状态管理,支持连接、断开、激活等状态变化 - 配置设备树中的USB端点数量以支持CDC ACM功能 - 创建USB设备模块用于管理USB堆栈初始化和状态监控 - 添加USB功能就绪事件以协调不同USB功能的准备状态
This commit is contained in:
464
src/usb_cdc_module.c
Normal file
464
src/usb_cdc_module.c
Normal file
@@ -0,0 +1,464 @@
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
|
||||
#define MODULE usb_cdc_module
|
||||
#include <caf/events/module_state_event.h>
|
||||
#include <caf/events/power_event.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#include "usb_cdc_rx_event.h"
|
||||
#include "usb_cdc_tx_event.h"
|
||||
#include "usb_function_ready_event.h"
|
||||
#include "usb_prepare_event.h"
|
||||
#include "usb_device_module.h"
|
||||
#include "usb_device_state_event.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define USB_CDC_RX_RING_BUF_SIZE 256
|
||||
#define USB_CDC_TX_RING_BUF_SIZE 256
|
||||
#define USB_CDC_RX_CHUNK_SIZE 32
|
||||
#define USB_CDC_MAX_LINE_SIZE 96
|
||||
#define USB_CDC_CONTROL_POLL_INTERVAL K_MSEC(100)
|
||||
#define USB_CDC_EXPECTED_BAUDRATE 115200U
|
||||
|
||||
static const struct device *const cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
|
||||
|
||||
static uint8_t rx_ring_buffer[USB_CDC_RX_RING_BUF_SIZE];
|
||||
static uint8_t tx_ring_buffer[USB_CDC_TX_RING_BUF_SIZE];
|
||||
static struct ring_buf rx_ringbuf;
|
||||
static struct ring_buf tx_ringbuf;
|
||||
static struct k_work rx_work;
|
||||
static struct k_work_delayable control_work;
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
static bool usb_active;
|
||||
static bool usb_function_prepared;
|
||||
static bool dtr_ready;
|
||||
static bool rx_enabled;
|
||||
static bool rx_line_overflow;
|
||||
static uint8_t rx_line_buf[USB_CDC_MAX_LINE_SIZE];
|
||||
static size_t rx_line_len;
|
||||
|
||||
static void submit_usb_cdc_rx_event(const uint8_t *data, size_t len)
|
||||
{
|
||||
struct usb_cdc_rx_event *event = new_usb_cdc_rx_event(len);
|
||||
|
||||
memcpy(event->dyndata.data, data, len);
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
static void submit_usb_function_ready_event(void)
|
||||
{
|
||||
struct usb_function_ready_event *event = new_usb_function_ready_event();
|
||||
|
||||
event->function_mask = USB_FUNCTION_CDC_ACM;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
static void reset_ring_buffers(void)
|
||||
{
|
||||
unsigned int key = irq_lock();
|
||||
|
||||
ring_buf_init(&rx_ringbuf, sizeof(rx_ring_buffer), rx_ring_buffer);
|
||||
ring_buf_init(&tx_ringbuf, sizeof(tx_ring_buffer), tx_ring_buffer);
|
||||
rx_line_len = 0U;
|
||||
rx_line_overflow = false;
|
||||
|
||||
irq_unlock(key);
|
||||
}
|
||||
|
||||
static void disable_uart_io(void)
|
||||
{
|
||||
uart_irq_rx_disable(cdc_dev);
|
||||
uart_irq_tx_disable(cdc_dev);
|
||||
rx_enabled = false;
|
||||
dtr_ready = false;
|
||||
reset_ring_buffers();
|
||||
}
|
||||
|
||||
static void kick_tx(void)
|
||||
{
|
||||
if (!running || !usb_active || !dtr_ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
uart_irq_tx_enable(cdc_dev);
|
||||
}
|
||||
|
||||
static void validate_line_coding(void)
|
||||
{
|
||||
uint32_t baudrate = 0U;
|
||||
int err;
|
||||
|
||||
err = uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
|
||||
if (err) {
|
||||
LOG_WRN("Failed to get CDC baudrate (%d)", err);
|
||||
} else {
|
||||
LOG_INF("CDC baudrate %u", baudrate);
|
||||
if (baudrate != USB_CDC_EXPECTED_BAUDRATE) {
|
||||
LOG_WRN("Expected CDC baudrate %u, got %u",
|
||||
USB_CDC_EXPECTED_BAUDRATE, baudrate);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
|
||||
{
|
||||
struct uart_config cfg;
|
||||
|
||||
err = uart_config_get(cdc_dev, &cfg);
|
||||
if (err) {
|
||||
LOG_WRN("uart_config_get failed (%d)", err);
|
||||
} else {
|
||||
LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u",
|
||||
cfg.data_bits, cfg.stop_bits, cfg.parity,
|
||||
cfg.flow_ctrl);
|
||||
if ((cfg.data_bits != UART_CFG_DATA_BITS_8) ||
|
||||
(cfg.stop_bits != UART_CFG_STOP_BITS_1) ||
|
||||
(cfg.parity != UART_CFG_PARITY_NONE) ||
|
||||
(cfg.flow_ctrl != UART_CFG_FLOW_CTRL_NONE)) {
|
||||
LOG_WRN("Expected CDC line coding 115200 8N1 no flow control");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void process_rx_byte(uint8_t byte)
|
||||
{
|
||||
if ((byte == '\r') || (byte == '\n')) {
|
||||
if (rx_line_overflow) {
|
||||
LOG_WRN("Drop oversized CDC line");
|
||||
rx_line_overflow = false;
|
||||
rx_line_len = 0U;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rx_line_len > 0U) {
|
||||
submit_usb_cdc_rx_event(rx_line_buf, rx_line_len);
|
||||
rx_line_len = 0U;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rx_line_overflow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rx_line_len >= sizeof(rx_line_buf)) {
|
||||
rx_line_overflow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
rx_line_buf[rx_line_len++] = byte;
|
||||
}
|
||||
|
||||
static void rx_work_handler(struct k_work *work)
|
||||
{
|
||||
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
||||
|
||||
ARG_UNUSED(work);
|
||||
|
||||
while (true) {
|
||||
uint32_t len;
|
||||
unsigned int key = irq_lock();
|
||||
|
||||
len = ring_buf_get(&rx_ringbuf, buffer, sizeof(buffer));
|
||||
irq_unlock(key);
|
||||
|
||||
if (len == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
process_rx_byte(buffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void control_work_handler(struct k_work *work)
|
||||
{
|
||||
uint32_t dtr = 0U;
|
||||
int err;
|
||||
|
||||
ARG_UNUSED(work);
|
||||
|
||||
if (!running || !usb_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_DTR, &dtr);
|
||||
if (err) {
|
||||
LOG_WRN("Failed to get CDC DTR (%d)", err);
|
||||
goto reschedule;
|
||||
}
|
||||
|
||||
if (dtr && !dtr_ready) {
|
||||
dtr_ready = true;
|
||||
LOG_INF("CDC DTR set");
|
||||
validate_line_coding();
|
||||
|
||||
err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1);
|
||||
if (err) {
|
||||
LOG_WRN("Failed to set DCD (%d)", err);
|
||||
}
|
||||
|
||||
err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1);
|
||||
if (err) {
|
||||
LOG_WRN("Failed to set DSR (%d)", err);
|
||||
}
|
||||
|
||||
if (!rx_enabled) {
|
||||
uart_irq_rx_enable(cdc_dev);
|
||||
rx_enabled = true;
|
||||
}
|
||||
|
||||
kick_tx();
|
||||
} else if (!dtr && dtr_ready) {
|
||||
LOG_INF("CDC DTR cleared");
|
||||
disable_uart_io();
|
||||
}
|
||||
|
||||
reschedule:
|
||||
k_work_reschedule(&control_work, USB_CDC_CONTROL_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
static void cdc_interrupt_handler(const struct device *dev, void *user_data)
|
||||
{
|
||||
ARG_UNUSED(user_data);
|
||||
|
||||
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
||||
int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer));
|
||||
|
||||
if (recv_len < 0) {
|
||||
LOG_ERR("Failed to read CDC RX FIFO");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (recv_len > 0) {
|
||||
uint32_t written;
|
||||
unsigned int key = irq_lock();
|
||||
|
||||
written = ring_buf_put(&rx_ringbuf, buffer,
|
||||
(uint32_t)recv_len);
|
||||
irq_unlock(key);
|
||||
|
||||
if (written < (uint32_t)recv_len) {
|
||||
LOG_WRN("Drop %d CDC RX bytes", recv_len - (int)written);
|
||||
}
|
||||
|
||||
k_work_submit(&rx_work);
|
||||
}
|
||||
}
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
||||
uint32_t len;
|
||||
int sent_len;
|
||||
unsigned int key = irq_lock();
|
||||
|
||||
len = ring_buf_get(&tx_ringbuf, buffer, sizeof(buffer));
|
||||
irq_unlock(key);
|
||||
|
||||
if (len == 0U) {
|
||||
uart_irq_tx_disable(dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
sent_len = uart_fifo_fill(dev, buffer, len);
|
||||
if (sent_len < 0) {
|
||||
LOG_ERR("Failed to write CDC TX FIFO");
|
||||
uart_irq_tx_disable(dev);
|
||||
} else if ((uint32_t)sent_len < len) {
|
||||
LOG_WRN("Drop %u CDC TX bytes",
|
||||
(unsigned int)(len - (uint32_t)sent_len));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int module_init(void)
|
||||
{
|
||||
if (!device_is_ready(cdc_dev)) {
|
||||
LOG_ERR("CDC ACM device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
reset_ring_buffers();
|
||||
k_work_init(&rx_work, rx_work_handler);
|
||||
k_work_init_delayable(&control_work, control_work_handler);
|
||||
uart_irq_callback_set(cdc_dev, cdc_interrupt_handler);
|
||||
usb_function_prepared = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int module_start(void)
|
||||
{
|
||||
if (running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running = true;
|
||||
if (usb_active) {
|
||||
k_work_reschedule(&control_work, K_NO_WAIT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void module_pause(void)
|
||||
{
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
k_work_cancel_delayable(&control_work);
|
||||
disable_uart_io();
|
||||
running = false;
|
||||
}
|
||||
|
||||
static bool handle_usb_prepare_event(const struct usb_prepare_event *event)
|
||||
{
|
||||
ARG_UNUSED(event);
|
||||
|
||||
if (!running || usb_function_prepared) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usb_function_prepared = true;
|
||||
submit_usb_function_ready_event();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_usb_device_state_event(const struct usb_device_state_event *event)
|
||||
{
|
||||
bool new_usb_active = (event->state == USB_DEVICE_STATE_ACTIVE);
|
||||
|
||||
if (new_usb_active == usb_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usb_active = new_usb_active;
|
||||
|
||||
if (!usb_active) {
|
||||
k_work_cancel_delayable(&control_work);
|
||||
disable_uart_io();
|
||||
} else if (running) {
|
||||
k_work_reschedule(&control_work, K_NO_WAIT);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_usb_cdc_tx_event(const struct usb_cdc_tx_event *event)
|
||||
{
|
||||
uint32_t written;
|
||||
unsigned int key;
|
||||
|
||||
if (!running || !usb_active || !dtr_ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
key = irq_lock();
|
||||
written = ring_buf_put(&tx_ringbuf, event->dyndata.data,
|
||||
(uint32_t)event->dyndata.size);
|
||||
irq_unlock(key);
|
||||
|
||||
if (written < event->dyndata.size) {
|
||||
LOG_WRN("Drop %zu CDC TX bytes", event->dyndata.size - written);
|
||||
}
|
||||
|
||||
if (written > 0U) {
|
||||
kick_tx();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool app_event_handler(const struct app_event_header *aeh)
|
||||
{
|
||||
if (is_usb_device_state_event(aeh)) {
|
||||
return handle_usb_device_state_event(cast_usb_device_state_event(aeh));
|
||||
}
|
||||
|
||||
if (is_usb_prepare_event(aeh)) {
|
||||
return handle_usb_prepare_event(cast_usb_prepare_event(aeh));
|
||||
}
|
||||
|
||||
if (is_usb_cdc_tx_event(aeh)) {
|
||||
return handle_usb_cdc_tx_event(cast_usb_cdc_tx_event(aeh));
|
||||
}
|
||||
|
||||
if (is_module_state_event(aeh)) {
|
||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||
|
||||
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||
int err;
|
||||
|
||||
if (!initialized) {
|
||||
err = module_init();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
err = module_start();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (initialized) {
|
||||
module_pause();
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_wake_up_event(aeh)) {
|
||||
if (initialized) {
|
||||
int err = module_start();
|
||||
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, usb_prepare_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, usb_device_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, usb_cdc_tx_event);
|
||||
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||
Reference in New Issue
Block a user