added usb - can bridge (loopback)
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.debug
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
SET(CMAKE_CXX_COMPILER /opt/zephyr-sdk/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc)
|
||||||
|
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
|
||||||
|
project(usb_cdc_can_fd_bridge C CXX)
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
33
boards/nucleo_h755zi_q_stm32h755xx_m7.overlay
Normal file
33
boards/nucleo_h755zi_q_stm32h755xx_m7.overlay
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/ {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
&zephyr_udc0 {
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
cdc_acm_uart0: cdc_acm_uart0 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
label = "CDC_ACM_0";
|
||||||
|
};
|
||||||
|
|
||||||
|
cdc_acm_uart1: cdc_acm_uart1 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
label = "CDC_ACM_1";
|
||||||
|
};
|
||||||
|
|
||||||
|
cdc_acm_uart2: cdc_acm_uart2 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
label = "CDC_ACM_2";
|
||||||
|
};
|
||||||
|
|
||||||
|
cdc_acm_uart3: cdc_acm_uart3 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
label = "CDC_ACM_3";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&fdcan1 {
|
||||||
|
bitrate = <1000000>;
|
||||||
|
bitrate-data = <4000000>;
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
15
config.yml
Normal file
15
config.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
sample:
|
||||||
|
description: USB CDC - CAN FD Bridge
|
||||||
|
name: ros2_node
|
||||||
|
common:
|
||||||
|
min_ram: 2
|
||||||
|
min_flash: 16
|
||||||
|
tags: usb_cdc
|
||||||
|
integration_platforms:
|
||||||
|
- nucleo_h563zi
|
||||||
|
- nucleo_h755zi_q
|
||||||
|
harness: console
|
||||||
|
harness_config:
|
||||||
|
type: one_line
|
||||||
|
regex:
|
||||||
|
- "Hello World! (.*)"
|
||||||
43
prj.conf
Normal file
43
prj.conf
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# c version
|
||||||
|
CONFIG_MAIN_STACK_SIZE=32768
|
||||||
|
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192
|
||||||
|
CONFIG_ISR_STACK_SIZE=2048
|
||||||
|
CONFIG_USBD_THREAD_STACK_SIZE=4096
|
||||||
|
CONFIG_CPP=y
|
||||||
|
CONFIG_NEWLIB_LIBC=y
|
||||||
|
CONFIG_NEWLIB_LIBC_NANO=n
|
||||||
|
CONFIG_PICOLIBC=n
|
||||||
|
CONFIG_MINIMAL_LIBC=n
|
||||||
|
CONFIG_POSIX_API=y
|
||||||
|
|
||||||
|
#threads
|
||||||
|
CONFIG_INIT_STACKS=y
|
||||||
|
CONFIG_THREAD_STACK_INFO=y
|
||||||
|
CONFIG_DEBUG=y
|
||||||
|
CONFIG_DEBUG_THREAD_INFO=y
|
||||||
|
|
||||||
|
CONFIG_CONSOLE=y
|
||||||
|
|
||||||
|
# logging
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_LOG_MODE_IMMEDIATE=y
|
||||||
|
CONFIG_STDOUT_CONSOLE=y
|
||||||
|
CONFIG_PRINTK=y
|
||||||
|
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||||
|
|
||||||
|
# Enable CDC ACM (virtual COM port)
|
||||||
|
CONFIG_SERIAL=y
|
||||||
|
CONFIG_UART_LINE_CTRL=y
|
||||||
|
CONFIG_USBD_CDC_ACM_CLASS=y
|
||||||
|
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||||
|
CONFIG_USBD_CDC_ACM_WORKQUEUE=y
|
||||||
|
CONFIG_UDC_WORKQUEUE=y
|
||||||
|
CONFIG_UDC_STM32=y
|
||||||
|
CONFIG_UDC_STM32_STACK_SIZE=2048
|
||||||
|
CONFIG_UDC_WORKQUEUE_STACK_SIZE=2048
|
||||||
|
|
||||||
|
# can FD
|
||||||
|
CONFIG_CAN=y
|
||||||
|
CONFIG_CAN_FD_MODE=y
|
||||||
|
CONFIG_CAN_STM32_FDCAN=y
|
||||||
|
|
||||||
12
src/CMakeLists.txt
Normal file
12
src/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
target_include_directories(app PRIVATE ./)
|
||||||
|
|
||||||
|
add_subdirectory(./bridges)
|
||||||
|
add_subdirectory(./canfd)
|
||||||
|
add_subdirectory(./devices)
|
||||||
|
add_subdirectory(./usb)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
7
src/bridges/CMakeLists.txt
Normal file
7
src/bridges/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
target_include_directories(app PRIVATE ./)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
bridge_usb_cdc_acm_canfd.cpp
|
||||||
|
)
|
||||||
175
src/bridges/bridge_usb_cdc_acm_canfd.cpp
Normal file
175
src/bridges/bridge_usb_cdc_acm_canfd.cpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#include <zephyr/drivers/uart.h>
|
||||||
|
#include <zephyr/drivers/can.h>
|
||||||
|
|
||||||
|
#include "bridge_usb_cdc_acm_canfd.hpp"
|
||||||
|
#include "devices.hpp"
|
||||||
|
|
||||||
|
#define USB_THREAD_STACK_SIZE 1024
|
||||||
|
K_THREAD_STACK_DEFINE(bridge_stack, USB_THREAD_STACK_SIZE);
|
||||||
|
struct k_thread bridge_thread_data;
|
||||||
|
K_FIFO_DEFINE(usb_fifo_0);
|
||||||
|
K_FIFO_DEFINE(usb_fifo_1);
|
||||||
|
K_FIFO_DEFINE(usb_fifo_2);
|
||||||
|
K_FIFO_DEFINE(usb_fifo_3);
|
||||||
|
|
||||||
|
struct uart_msg {
|
||||||
|
void *fifo_reserved;
|
||||||
|
uint8_t len;
|
||||||
|
uint8_t *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t get_can_id_from_usb_dev(const struct device *dev) {
|
||||||
|
if (dev == cdc0) {
|
||||||
|
return 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc1) {
|
||||||
|
return 0x20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc2) {
|
||||||
|
return 0x30;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc3) {
|
||||||
|
return 0x40;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct k_fifo *get_fifo_from_dev(const struct device *dev) {
|
||||||
|
if (dev == cdc0) {
|
||||||
|
return &usb_fifo_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc1) {
|
||||||
|
return &usb_fifo_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc2) {
|
||||||
|
return &usb_fifo_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == cdc3) {
|
||||||
|
return &usb_fifo_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct device *get_usb_dev_from_can_id(uint8_t can_id) {
|
||||||
|
switch (can_id) {
|
||||||
|
case 0x10:
|
||||||
|
return cdc0;
|
||||||
|
case 0x21:
|
||||||
|
return cdc1;
|
||||||
|
case 0x31:
|
||||||
|
return cdc2;
|
||||||
|
case 0x41:
|
||||||
|
return cdc3;
|
||||||
|
default:
|
||||||
|
return NULL; // unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t get_lenght_from_dlc(uint8_t dlc) {
|
||||||
|
static const uint8_t dlc_to_len[16] = {
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dlc < 16) {
|
||||||
|
return dlc_to_len[dlc];
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void uart_rx_handler(const struct device *dev, void *user_data)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(user_data);
|
||||||
|
|
||||||
|
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||||
|
printk("tx: %i, rx: %i\r\n", uart_irq_tx_ready(dev), uart_irq_rx_ready(dev));
|
||||||
|
if (uart_irq_rx_ready(dev)) {
|
||||||
|
uint8_t buffer[64] = {0};
|
||||||
|
|
||||||
|
int recv_len = uart_fifo_read(dev, buffer, 64);
|
||||||
|
if (recv_len > 0) {
|
||||||
|
struct can_frame counter_frame = {
|
||||||
|
.id = get_can_id_from_usb_dev(dev),
|
||||||
|
.dlc = can_bytes_to_dlc(recv_len),
|
||||||
|
.flags = CAN_FRAME_FDF | CAN_FRAME_BRS,
|
||||||
|
.data = {0},
|
||||||
|
};
|
||||||
|
memcpy(counter_frame.data, buffer, recv_len);
|
||||||
|
|
||||||
|
can_send(fdcan1, &counter_frame, K_NO_WAIT, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uart_irq_tx_ready(dev) >= 64) {
|
||||||
|
k_fifo* fifo = get_fifo_from_dev(dev);
|
||||||
|
if (fifo == NULL) {
|
||||||
|
uart_irq_tx_disable(dev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct uart_msg *msg = (uart_msg*) k_fifo_get(fifo, K_MSEC(10));
|
||||||
|
if(msg){
|
||||||
|
printk("sending to UART: %s / %i\n\r", msg->data, msg->len);
|
||||||
|
uart_fifo_fill(dev, msg->data, msg->len);
|
||||||
|
k_free(msg->data);
|
||||||
|
k_free(msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printk("Nothing to do\n\r");
|
||||||
|
uart_irq_tx_disable(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_rx_handler(const struct device *dev, struct can_frame *frame, void *user_data)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(user_data);
|
||||||
|
|
||||||
|
const struct device *uart_dev = get_usb_dev_from_can_id(frame->id);
|
||||||
|
if (!uart_dev) return;
|
||||||
|
|
||||||
|
struct uart_msg *msg = (uart_msg*)k_malloc(sizeof(uart_msg));
|
||||||
|
if (!msg) return;
|
||||||
|
|
||||||
|
msg->len = get_lenght_from_dlc(frame->dlc);
|
||||||
|
printk("msg size: %i / %i\n\r", msg->len, frame->dlc);
|
||||||
|
|
||||||
|
msg->data = (uint8_t*)k_malloc(sizeof(uint8_t) * msg->len);
|
||||||
|
if (!msg->data) return;
|
||||||
|
|
||||||
|
memset(msg->data, 0, msg->len);
|
||||||
|
memcpy(msg->data, frame->data, msg->len);
|
||||||
|
|
||||||
|
printk("sending to Queue: %s / %s / %i / %i\n\r", frame->data, msg->data, msg->len, frame->dlc);
|
||||||
|
k_fifo_put(get_fifo_from_dev(uart_dev), msg);
|
||||||
|
uart_irq_tx_enable(uart_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void bridges_init() {
|
||||||
|
uart_irq_callback_set(cdc0, uart_rx_handler);
|
||||||
|
uart_irq_rx_enable(cdc0);
|
||||||
|
uart_irq_callback_set(cdc1, uart_rx_handler);
|
||||||
|
uart_irq_rx_enable(cdc1);
|
||||||
|
uart_irq_callback_set(cdc2, uart_rx_handler);
|
||||||
|
uart_irq_rx_enable(cdc2);
|
||||||
|
uart_irq_callback_set(cdc3, uart_rx_handler);
|
||||||
|
uart_irq_rx_enable(cdc3);
|
||||||
|
|
||||||
|
struct can_filter filter = {
|
||||||
|
.id = 0, // match all IDs
|
||||||
|
.mask = 0, // accept everything
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
can_add_rx_filter(fdcan1, can_rx_handler, NULL, &filter);
|
||||||
|
}
|
||||||
3
src/bridges/bridge_usb_cdc_acm_canfd.hpp
Normal file
3
src/bridges/bridge_usb_cdc_acm_canfd.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void bridges_init(void);
|
||||||
7
src/canfd/CMakeLists.txt
Normal file
7
src/canfd/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
target_include_directories(app PRIVATE ./)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
can_fd.cpp
|
||||||
|
)
|
||||||
15
src/canfd/can_fd.cpp
Normal file
15
src/canfd/can_fd.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include <zephyr/drivers/can.h>
|
||||||
|
|
||||||
|
#include "can_fd.hpp"
|
||||||
|
#include "devices.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
void canfd_init() {
|
||||||
|
if (!can_set_mode(fdcan1, CAN_MODE_LOOPBACK | CAN_MODE_FD)) {
|
||||||
|
printk("CAN0 set to CAN FD\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_start(fdcan1) < 0) {
|
||||||
|
printk("CAN0 not enabled\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/canfd/can_fd.hpp
Normal file
3
src/canfd/can_fd.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void canfd_init(void);
|
||||||
7
src/devices/CMakeLists.txt
Normal file
7
src/devices/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
target_include_directories(app PRIVATE ./)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
devices.cpp
|
||||||
|
)
|
||||||
40
src/devices/devices.cpp
Normal file
40
src/devices/devices.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "devices.hpp"
|
||||||
|
|
||||||
|
const struct device *cdc0 = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0));
|
||||||
|
const struct device *cdc1 = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart1));
|
||||||
|
const struct device *cdc2 = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart2));
|
||||||
|
const struct device *cdc3 = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart3));
|
||||||
|
|
||||||
|
const struct device *fdcan1 = DEVICE_DT_GET(DT_NODELABEL(fdcan1));
|
||||||
|
|
||||||
|
|
||||||
|
bool verify_are_devices_available(void) {
|
||||||
|
bool devices_ready = true;
|
||||||
|
|
||||||
|
if (!device_is_ready(cdc0)) {
|
||||||
|
printk("cdc0 not ready\n");
|
||||||
|
devices_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(cdc1)) {
|
||||||
|
printk("cdc1 not ready\n");
|
||||||
|
devices_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(cdc2)) {
|
||||||
|
printk("cdc2 not ready\n");
|
||||||
|
devices_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(cdc3)) {
|
||||||
|
printk("cdc3 not ready\n");
|
||||||
|
devices_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(fdcan1)) {
|
||||||
|
printk("fdcan1 not ready\n");
|
||||||
|
devices_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices_ready;
|
||||||
|
}
|
||||||
15
src/devices/devices.hpp
Normal file
15
src/devices/devices.hpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
|
||||||
|
|
||||||
|
extern const struct device *cdc0;
|
||||||
|
extern const struct device *cdc1;
|
||||||
|
extern const struct device *cdc2;
|
||||||
|
extern const struct device *cdc3;
|
||||||
|
|
||||||
|
extern const struct device *fdcan1;
|
||||||
|
|
||||||
|
|
||||||
|
bool verify_are_devices_available(void);
|
||||||
20
src/main.cpp
Normal file
20
src/main.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#include "devices.hpp"
|
||||||
|
#include "can_fd.hpp"
|
||||||
|
#include "usb_cdc_acm.hpp"
|
||||||
|
#include "bridge_usb_cdc_acm_canfd.hpp"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
if (!verify_are_devices_available()) {
|
||||||
|
printk("Not all devices ready. Aborting ...\r\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
canfd_init();
|
||||||
|
usb_init();
|
||||||
|
bridges_init();
|
||||||
|
|
||||||
|
k_thread_suspend(k_current_get());
|
||||||
|
}
|
||||||
7
src/usb/CMakeLists.txt
Normal file
7
src/usb/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
target_include_directories(app PRIVATE ./)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
usb_cdc_acm.cpp
|
||||||
|
)
|
||||||
38
src/usb/usb_cdc_acm.cpp
Normal file
38
src/usb/usb_cdc_acm.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#include <zephyr/usb/usbd.h>
|
||||||
|
|
||||||
|
#include "usb_cdc_acm.hpp"
|
||||||
|
|
||||||
|
USBD_DEVICE_DEFINE(usb0, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), 0x2fe3, 0xffff);
|
||||||
|
USBD_DESC_LANG_DEFINE(usb0_lang);
|
||||||
|
USBD_DESC_MANUFACTURER_DEFINE(usb0_mfr, "Ohmman & Co. KG");
|
||||||
|
USBD_DESC_PRODUCT_DEFINE(usb0_prod, "USB Test");
|
||||||
|
|
||||||
|
USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration");
|
||||||
|
USBD_CONFIGURATION_DEFINE(usb0_conf, USB_SCD_SELF_POWERED, 100, &fs_cfg_desc);
|
||||||
|
|
||||||
|
static const char *const usb0_blocklist[] = {
|
||||||
|
"dfu_dfu",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void usb_init(void) {
|
||||||
|
usbd_add_descriptor(&usb0, &usb0_lang);
|
||||||
|
usbd_add_descriptor(&usb0, &usb0_mfr);
|
||||||
|
usbd_add_descriptor(&usb0, &usb0_prod);
|
||||||
|
//usbd_add_descriptor(&usb0, &usb0_sn);
|
||||||
|
|
||||||
|
if (usbd_add_configuration(&usb0, USBD_SPEED_FS, &usb0_conf)) {
|
||||||
|
printk("Failed to add Full-Speed configuration\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbd_register_all_classes(&usb0, USBD_SPEED_FS, 1, usb0_blocklist)) {
|
||||||
|
printk("Failed to register usb classes\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
usbd_device_set_code_triple(&usb0, USBD_SPEED_FS, USB_BCC_APPLICATION, 0x02, 0x01);
|
||||||
|
|
||||||
|
usbd_init(&usb0);
|
||||||
|
usbd_enable(&usb0);
|
||||||
|
}
|
||||||
3
src/usb/usb_cdc_acm.hpp
Normal file
3
src/usb/usb_cdc_acm.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void usb_init(void);
|
||||||
Reference in New Issue
Block a user