First
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
build
|
||||||
|
tmp
|
||||||
|
boards
|
||||||
Vendored
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug with pyOCD (RP2350)",
|
||||||
|
"type": "cortex-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"servertype": "pyocd",
|
||||||
|
"serverpath": "${workspaceFolder}/../venv/bin/pyocd",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"executable": "${workspaceFolder}/build/zephyr/zephyr.elf",
|
||||||
|
"interface": "swd",
|
||||||
|
"runToEntryPoint": "main",
|
||||||
|
// "preLaunchTask": "build",
|
||||||
|
"armToolchainPath": "/usr/bin",
|
||||||
|
"gdbPath": "/usr/bin/arm-none-eabi-gdb",
|
||||||
|
"serverArgs": [
|
||||||
|
"--target=rp2350",
|
||||||
|
"--core=0"
|
||||||
|
],
|
||||||
|
"showDevDebugOutput": "raw"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
project(usb_commands)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c)
|
||||||
|
target_sources(app PRIVATE ${app_sources})
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Copyright (c) 2023 Nordic Semiconductor ASA
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
source "Kconfig.zephyr"
|
||||||
|
|
||||||
|
menu "USB options"
|
||||||
|
depends on USB_DEVICE_STACK_NEXT
|
||||||
|
|
||||||
|
config USBD_MANUFACTURER
|
||||||
|
string "USB device manufacturer string"
|
||||||
|
default "Zephyr Project"
|
||||||
|
help
|
||||||
|
USB device manufacturer string.
|
||||||
|
|
||||||
|
config USBD_PRODUCT
|
||||||
|
string "USB device product string"
|
||||||
|
default "USBD"
|
||||||
|
help
|
||||||
|
USB device product stringa.
|
||||||
|
|
||||||
|
config USBD_VID
|
||||||
|
hex "USB device Vendor ID"
|
||||||
|
default 0x2fe3
|
||||||
|
help
|
||||||
|
USB device Vendor ID. The default id (0x2fe3) is associated to
|
||||||
|
Zephyr Project, you must use your own VID and applications
|
||||||
|
outside of Zephyr Project.
|
||||||
|
|
||||||
|
config USBD_PID
|
||||||
|
hex "USB device Product ID"
|
||||||
|
default 0x0001
|
||||||
|
help
|
||||||
|
USB device Product ID. You must use your own PID
|
||||||
|
and applications outside of Zephyr Project.
|
||||||
|
|
||||||
|
config USBD_SELF_POWERED
|
||||||
|
bool "USB device Self-powered attribute"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Set the Self-powered attribute in the configuration.
|
||||||
|
|
||||||
|
config USBD_REMOTE_WAKEUP
|
||||||
|
bool "USB device Remote Wakeup attribute"
|
||||||
|
help
|
||||||
|
Set the Remote Wakeup attribute in the configuration.
|
||||||
|
|
||||||
|
config USBD_MAX_POWER
|
||||||
|
int "USB device bMaxPower value"
|
||||||
|
default 125
|
||||||
|
range 0 250
|
||||||
|
help
|
||||||
|
bMaxPower value in the configuration in 2 mA units.
|
||||||
|
|
||||||
|
config USBD_20_EXTENSION_DESC
|
||||||
|
bool "Use default USB 2.0 Extension Descriptor"
|
||||||
|
depends on USBD_BOS_SUPPORT
|
||||||
|
help
|
||||||
|
Set bcdUSB value to 0201 and use default USB 2.0 Extension Descriptor.
|
||||||
|
|
||||||
|
endmenu
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Zephyr USB Command Handler
|
||||||
|
|
||||||
|
This repository contains the implementation of a USB command handler for the Zephyr RTOS. The system allows devices to communicate via USB using custom command messages.
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
1. **command_handler.c/h**: Handles incoming commands, processes them, and performs actions based on the command type.
|
||||||
|
2. **command_message.c/h**: Manages the creation, validation, and logging of command messages, including CRC calculation.
|
||||||
|
3. **usb.c/h**: Manages USB initialization, data reception via UART, and sending responses. Includes a USB thread.
|
||||||
|
4. **usb_conf.c/h**: Configures the USB device.
|
||||||
|
|
||||||
|
### Command Structure
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct command_message_t {
|
||||||
|
uint8_t prefix; // 0x69
|
||||||
|
uint8_t length;
|
||||||
|
uint8_t id;
|
||||||
|
uint8_t command;
|
||||||
|
uint8_t crc;
|
||||||
|
uint8_t data[160];
|
||||||
|
} __attribute__((packed));
|
||||||
|
```
|
||||||
|
|
||||||
|
### CRC Calculation Function
|
||||||
|
|
||||||
|
```c
|
||||||
|
uint8_t calculate_crc(struct command_message_t *msg) {
|
||||||
|
uint32_t sum = 0;
|
||||||
|
uint8_t crc = 0;
|
||||||
|
|
||||||
|
uint8_t *byte_ptr = (uint8_t *)msg;
|
||||||
|
|
||||||
|
for (int i = 0; i < (sizeof(struct command_message_t) - sizeof(msg->data) + msg->length) - 1; i++) {
|
||||||
|
sum += byte_ptr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
crc = 0x100 - (sum & 0xff);
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python test script (AI generated)
|
||||||
|
|
||||||
|
**Command Usage:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/led_blink.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This script sends LED toggle commands to the device connected via USB.
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zephyr,console = &cdc_acm_uart0;
|
||||||
|
zephyr,shell-uart = &cdc_acm_uart0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&zephyr_udc0 {
|
||||||
|
cdc_acm_uart0: cdc_acm_uart0 {
|
||||||
|
compatible = "zephyr,cdc-acm-uart";
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
CONFIG_GPIO=y
|
||||||
|
CONFIG_PWM=y
|
||||||
|
|
||||||
|
# Serial
|
||||||
|
CONFIG_SERIAL=y
|
||||||
|
CONFIG_CONSOLE=y
|
||||||
|
CONFIG_UART_CONSOLE=y
|
||||||
|
CONFIG_STDOUT_CONSOLE=y
|
||||||
|
CONFIG_UART_LINE_CTRL=y
|
||||||
|
|
||||||
|
# USB
|
||||||
|
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||||
|
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
|
||||||
|
# CONFIG_CDC_ACM_SERIAL_PRODUCT_STRING="USB CDC ACM"
|
||||||
|
# CONFIG_CDC_ACM_SERIAL_PID=
|
||||||
|
CONFIG_USBD_VID=0xffff
|
||||||
|
CONFIG_USBD_PID=0x0420
|
||||||
|
CONFIG_USBD_MANUFACTURER="Zephyr Project"
|
||||||
|
CONFIG_USBD_PRODUCT="USBD sample"
|
||||||
|
CONFIG_USBD_SELF_POWERED=y
|
||||||
|
# CONFIG_USBD_REMOTE_WAKEUP=
|
||||||
|
CONFIG_USBD_MAX_POWER=125
|
||||||
|
|
||||||
|
# LOG
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_USBD_CDC_ACM_LOG_LEVEL_OFF=y # This removes a pointless warning
|
||||||
|
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||||
|
CONFIG_LOG_MODE_IMMEDIATE=y
|
||||||
|
# CONFIG_USBD_LOG_LEVEL_ERR=y
|
||||||
|
# CONFIG_UDC_DRIVER_LOG_LEVEL_ERR=y
|
||||||
|
|
||||||
|
# DEBUG
|
||||||
|
CONFIG_DEBUG_THREAD_INFO=y
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
PORT = "/dev/ttyACM0"
|
||||||
|
BAUDRATE = 115200
|
||||||
|
|
||||||
|
COMMAND_PREFIX = 0x69
|
||||||
|
|
||||||
|
COMMAND_ERROR = 0
|
||||||
|
COMMAND_ACK = 1
|
||||||
|
COMMAND_NACK = 2
|
||||||
|
LED = 3
|
||||||
|
|
||||||
|
DEVICE_ID = 0
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_crc(msg: bytes) -> int:
|
||||||
|
s = sum(msg) & 0xFF
|
||||||
|
return (-s) & 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
def make_packet(command: int, data: bytes = b"") -> bytes:
|
||||||
|
length = len(data)
|
||||||
|
|
||||||
|
pkt = bytearray()
|
||||||
|
pkt.append(COMMAND_PREFIX)
|
||||||
|
pkt.append(length)
|
||||||
|
pkt.append(DEVICE_ID)
|
||||||
|
pkt.append(command)
|
||||||
|
pkt.append(0) # CRC placeholder
|
||||||
|
pkt.extend(data)
|
||||||
|
|
||||||
|
pkt[4] = calculate_crc(pkt[:4] + pkt[5:])
|
||||||
|
|
||||||
|
return bytes(pkt)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_crc(packet: bytes) -> bool:
|
||||||
|
crc = packet[4]
|
||||||
|
calc = calculate_crc(packet[:4] + packet[5:])
|
||||||
|
return crc == calc
|
||||||
|
|
||||||
|
|
||||||
|
def packet_size(buf: bytes):
|
||||||
|
if len(buf) < 2:
|
||||||
|
return None
|
||||||
|
return 5 + buf[1]
|
||||||
|
|
||||||
|
|
||||||
|
def reader(ser):
|
||||||
|
rx = bytearray()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = ser.read(64)
|
||||||
|
if not data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rx.extend(data)
|
||||||
|
|
||||||
|
while rx:
|
||||||
|
# Binary packet?
|
||||||
|
if rx[0] == COMMAND_PREFIX:
|
||||||
|
size = packet_size(rx)
|
||||||
|
if size is None or len(rx) < size:
|
||||||
|
break
|
||||||
|
|
||||||
|
pkt = bytes(rx[:size])
|
||||||
|
del rx[:size]
|
||||||
|
|
||||||
|
if not verify_crc(pkt):
|
||||||
|
print("RX: Bad CRC:", pkt.hex())
|
||||||
|
continue
|
||||||
|
|
||||||
|
length = pkt[1]
|
||||||
|
dev_id = pkt[2]
|
||||||
|
cmd = pkt[3]
|
||||||
|
|
||||||
|
if cmd == COMMAND_ACK:
|
||||||
|
print(f"<-- ACK (device={dev_id})")
|
||||||
|
|
||||||
|
elif cmd == COMMAND_NACK:
|
||||||
|
print(f"<-- NACK (device={dev_id})")
|
||||||
|
|
||||||
|
elif cmd == COMMAND_ERROR:
|
||||||
|
print(f"<-- ERROR (device={dev_id})")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"<-- Command {cmd} len={length}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# ASCII log output
|
||||||
|
idx = rx.find(b'\n')
|
||||||
|
if idx == -1:
|
||||||
|
break
|
||||||
|
|
||||||
|
line = rx[:idx + 1]
|
||||||
|
del rx[:idx + 1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[LOG]", line.decode().rstrip())
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print("[RAW]", line.hex())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
|
||||||
|
|
||||||
|
threading.Thread(target=reader, args=(ser,), daemon=True).start()
|
||||||
|
|
||||||
|
for i in range(10):
|
||||||
|
print(f"--> Sending LED command {i + 1}")
|
||||||
|
ser.write(make_packet(LED))
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#include "command_handler.h"
|
||||||
|
#include "led.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(command_handler, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
int command_handler(struct command_message_t *msg) {
|
||||||
|
if (msg == NULL) {
|
||||||
|
LOG_ERR("Received NULL message pointer");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Processing command: %d, length: %d", msg->command, msg->length);
|
||||||
|
|
||||||
|
switch (msg->command) {
|
||||||
|
case COMMAND_ERROR: {
|
||||||
|
LOG_WRN("Received COMMAND_ERROR");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LED: {
|
||||||
|
// Toggle LED with the LED command
|
||||||
|
led_toggle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
LOG_WRN("Unknown command received: %d", msg->command);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef COMMAND_HANDLER_H
|
||||||
|
#define COMMAND_HANDLER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "command_message.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process received command message
|
||||||
|
*
|
||||||
|
* @param msg Command message to process
|
||||||
|
* @return 0 on success, negative errno on failure
|
||||||
|
*/
|
||||||
|
int command_handler(struct command_message_t *msg);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // COMMAND_HANDLER_H
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include "command_message.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(command_message, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
void command_message_init(struct command_message_t *msg) {
|
||||||
|
memset(msg, 0, sizeof(struct command_message_t));
|
||||||
|
msg->prefix = COMMAND_PREFIX;
|
||||||
|
msg->id = COMMAND_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[160]) {
|
||||||
|
// Ensure length doesn't exceed available space
|
||||||
|
if (length > sizeof(msg->data) - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_message_init(msg);
|
||||||
|
msg->length = length;
|
||||||
|
msg->command = command;
|
||||||
|
|
||||||
|
// Copy the data
|
||||||
|
if (data != NULL) {
|
||||||
|
for (int i = 0; i < msg->length; i++) {
|
||||||
|
msg->data[i] = data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg->crc = command_calculate_crc(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t command_calculate_crc(struct command_message_t *msg) {
|
||||||
|
uint32_t sum = 0;
|
||||||
|
uint8_t crc = 0;
|
||||||
|
|
||||||
|
uint8_t *byte_ptr = (uint8_t *)msg;
|
||||||
|
|
||||||
|
for (int i = 0; i < (sizeof(struct command_message_t) - sizeof(msg->data) + msg->length) - 1; i++) {
|
||||||
|
sum += byte_ptr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
crc = 0x100 - (sum & 0xff);
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void command_create_ack(struct command_message_t *msg) {
|
||||||
|
command_create_message(msg, 0, COMMAND_ACK, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void command_create_nack(struct command_message_t *msg) {
|
||||||
|
command_create_message(msg, 0, COMMAND_NACK, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void command_log(struct command_message_t *msg) {
|
||||||
|
if (msg->length > sizeof(msg->data) - 1) {
|
||||||
|
LOG_ERR("Message length too long: %d", msg->length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INF("Prefix: %d\n\r", msg->prefix);
|
||||||
|
LOG_INF("Length: %d\n\r", msg->length);
|
||||||
|
LOG_INF("COMMAND_ID: %d\n\r", msg->id);
|
||||||
|
LOG_INF("Command: %d\n\r", msg->command);
|
||||||
|
LOG_INF("Data:\n\r");
|
||||||
|
|
||||||
|
for (int i = 0; i < msg->length; i++) {
|
||||||
|
LOG_INF("%d", msg->data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INF("CRC: %d\n\r", msg->crc);
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef COMMAND_MESSAGE_H
|
||||||
|
#define COMMAND_MESSAGE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define COMMAND_PREFIX 0x69
|
||||||
|
#define COMMAND_ID 0x00
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COMMAND_ERROR,
|
||||||
|
COMMAND_ACK,
|
||||||
|
COMMAND_NACK,
|
||||||
|
LED,
|
||||||
|
} commands_e;
|
||||||
|
|
||||||
|
struct command_message_t {
|
||||||
|
uint8_t prefix;
|
||||||
|
uint8_t length;
|
||||||
|
uint8_t id;
|
||||||
|
uint8_t command;
|
||||||
|
uint8_t crc;
|
||||||
|
uint8_t data[160];
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize command message to default state
|
||||||
|
*
|
||||||
|
* @param msg Message to initialize
|
||||||
|
*/
|
||||||
|
void command_message_init(struct command_message_t *msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create command message with data and CRC
|
||||||
|
*
|
||||||
|
* @param msg Message to populate
|
||||||
|
* @param length Data length in bytes
|
||||||
|
* @param command Command type
|
||||||
|
* @param data Data payload
|
||||||
|
*/
|
||||||
|
void command_create_message(struct command_message_t *msg, uint8_t length, commands_e command, uint8_t data[160]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate CRC for command message
|
||||||
|
*
|
||||||
|
* @param msg Message to calculate CRC for
|
||||||
|
* @return CRC value
|
||||||
|
*/
|
||||||
|
uint8_t command_calculate_crc(struct command_message_t *msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create ACK command message
|
||||||
|
*
|
||||||
|
* @param msg Message to populate
|
||||||
|
*/
|
||||||
|
void command_create_ack(struct command_message_t *msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create NACK command message
|
||||||
|
*
|
||||||
|
* @param msg Message to populate
|
||||||
|
*/
|
||||||
|
void command_create_nack(struct command_message_t *msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print the command with LOG
|
||||||
|
*
|
||||||
|
* @param msg Message to calculate CRC for
|
||||||
|
*/
|
||||||
|
void command_log(struct command_message_t *msg);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // COMMAND_MESSAGE_H
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include "led.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(led, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define LED0_NODE DT_ALIAS(led0)
|
||||||
|
static const struct gpio_dt_spec led0_gpio = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
|
||||||
|
static int led0_state = 0;
|
||||||
|
|
||||||
|
int led_init() {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device_is_ready(led0_gpio.port)) {
|
||||||
|
LOG_ERR("LED0 GPIO device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = gpio_pin_configure_dt(&led0_gpio, GPIO_OUTPUT_INACTIVE);
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("Failed to configure LED0 GPIO: %d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn it off
|
||||||
|
ret = gpio_pin_set_dt(&led0_gpio, 0);
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("Failed to initialize LED");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INF("LED driver initialized (LED0: GPIO %d)", led0_gpio.pin);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int led_set(int state) {
|
||||||
|
led0_state = state ? 1 : 0;
|
||||||
|
|
||||||
|
int ret = gpio_pin_set_dt(&led0_gpio, led0_state);
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("Failed to set LED0: %d", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int led_toggle() {
|
||||||
|
return led_set(!led0_state);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef LED_H
|
||||||
|
#define LED_H
|
||||||
|
|
||||||
|
|
||||||
|
int led_init();
|
||||||
|
int led_set(int state);
|
||||||
|
int led_toggle();
|
||||||
|
|
||||||
|
|
||||||
|
#endif // LED_H
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#include "led.h"
|
||||||
|
#include "usb.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = usb_init();
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("Failed to enable USB");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = led_init();
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERR("Failed to enable LED");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
#include "usb.h"
|
||||||
|
#include "usb_conf.h"
|
||||||
|
#include "command_handler.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/uart.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG);
|
||||||
|
|
||||||
|
// DEVICE
|
||||||
|
const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
|
||||||
|
static struct usbd_context *usb_context;
|
||||||
|
|
||||||
|
// THREAD
|
||||||
|
static struct k_thread usb_thread_data;
|
||||||
|
static k_tid_t usb_thread_id = NULL;
|
||||||
|
#define USB_THREAD_STACK_SIZE 2048
|
||||||
|
K_THREAD_STACK_DEFINE(usb_thread_stack, USB_THREAD_STACK_SIZE);
|
||||||
|
|
||||||
|
// RX BUFFER
|
||||||
|
#define RX_BUF_SIZE 4
|
||||||
|
struct rx_buffer_t {
|
||||||
|
int current_read;
|
||||||
|
int current_write;
|
||||||
|
struct k_sem semaphore;
|
||||||
|
struct command_message_t buffer[RX_BUF_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct rx_buffer_t rx_buf;
|
||||||
|
|
||||||
|
|
||||||
|
static void interrupt_handler(const struct device *dev, void *user_data) {
|
||||||
|
ARG_UNUSED(user_data);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
uart_irq_update(dev);
|
||||||
|
|
||||||
|
if (uart_irq_is_pending(dev) <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uart_irq_rx_ready(dev)) {
|
||||||
|
size_t len = sizeof(struct command_message_t);
|
||||||
|
|
||||||
|
int recv_len = uart_fifo_read(dev, (uint8_t *)&rx_buf.buffer[rx_buf.current_write], len);
|
||||||
|
if (recv_len < 0) {
|
||||||
|
LOG_ERR("Failed to read UART FIFO");
|
||||||
|
recv_len = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
rx_buf.current_write += 1;
|
||||||
|
|
||||||
|
if (rx_buf.current_write >= RX_BUF_SIZE) {
|
||||||
|
rx_buf.current_write = 0;
|
||||||
|
rx_buf.current_read = (RX_BUF_SIZE - 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rx_buf.current_read = (rx_buf.current_write - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give semaphore for usb read thread
|
||||||
|
k_sem_give(&rx_buf.semaphore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_thread(void *p1, void *p2, void *p3) {
|
||||||
|
ARG_UNUSED(p1);
|
||||||
|
ARG_UNUSED(p2);
|
||||||
|
ARG_UNUSED(p3);
|
||||||
|
|
||||||
|
LOG_INF("USB command processing thread started");
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
// Wait forever for the rx semaphore
|
||||||
|
k_sem_take(&rx_buf.semaphore, K_FOREVER);
|
||||||
|
|
||||||
|
int read_index = rx_buf.current_read;
|
||||||
|
|
||||||
|
// Check the prefix
|
||||||
|
if (rx_buf.buffer[read_index].prefix != COMMAND_PREFIX) {
|
||||||
|
LOG_ERR("COMMAND_PREFIX does not match: %d", rx_buf.buffer[read_index].prefix);
|
||||||
|
|
||||||
|
// Send NACK
|
||||||
|
struct command_message_t nack_buf;
|
||||||
|
command_create_nack(&nack_buf);
|
||||||
|
usb_send_command(&nack_buf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the CRC
|
||||||
|
uint8_t calculated_crc = command_calculate_crc(&rx_buf.buffer[read_index]);
|
||||||
|
if (calculated_crc != rx_buf.buffer[read_index].crc) {
|
||||||
|
LOG_ERR("CRC does not match:");
|
||||||
|
LOG_ERR("Calculated CRC: %d", calculated_crc);
|
||||||
|
LOG_ERR("Received CRC: %d", rx_buf.buffer[read_index].prefix);
|
||||||
|
|
||||||
|
// Send NACK
|
||||||
|
struct command_message_t nack_buf;
|
||||||
|
command_create_nack(&nack_buf);
|
||||||
|
usb_send_command(&nack_buf);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = command_handler(&rx_buf.buffer[read_index]);
|
||||||
|
if (ret == 0) {
|
||||||
|
// Send ACK
|
||||||
|
struct command_message_t ack_buf;
|
||||||
|
command_create_ack(&ack_buf);
|
||||||
|
usb_send_command(&ack_buf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Send NACK
|
||||||
|
struct command_message_t nack_buf;
|
||||||
|
command_create_nack(&nack_buf);
|
||||||
|
usb_send_command(&nack_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INF("USB command processing thread exiting");
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_init() {
|
||||||
|
rx_buf.current_read = 0;
|
||||||
|
rx_buf.current_write = 0;
|
||||||
|
k_sem_init(&rx_buf.semaphore, 1, 1);
|
||||||
|
memset(rx_buf.buffer, 0, sizeof(rx_buf.buffer));
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device_is_ready(uart_dev)) {
|
||||||
|
LOG_ERR("CDC ACM device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_context = usb_device_init(NULL);
|
||||||
|
if (usb_context == NULL) {
|
||||||
|
LOG_ERR("Failed to initialize USB device");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usbd_can_detect_vbus(usb_context)) {
|
||||||
|
ret = usbd_enable(usb_context);
|
||||||
|
if (ret) {
|
||||||
|
LOG_ERR("Failed to enable device support");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k_msleep(100);
|
||||||
|
|
||||||
|
uart_irq_callback_set(uart_dev, interrupt_handler);
|
||||||
|
uart_irq_rx_enable(uart_dev);
|
||||||
|
|
||||||
|
usb_thread_id = k_thread_create(
|
||||||
|
&usb_thread_data,
|
||||||
|
usb_thread_stack,
|
||||||
|
K_THREAD_STACK_SIZEOF(usb_thread_stack),
|
||||||
|
usb_thread,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
K_NO_WAIT
|
||||||
|
);
|
||||||
|
|
||||||
|
if (usb_thread_id == NULL) {
|
||||||
|
LOG_ERR("Failed to create USB thread");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_send_command(struct command_message_t *msg) {
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!device_is_ready(uart_dev)) {
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message size: prefix + length + id + command + crc + data
|
||||||
|
size_t msg_size = 5 + msg->length;
|
||||||
|
uint8_t *msg_bytes = (uint8_t *)msg;
|
||||||
|
|
||||||
|
/* uart_poll_out blocks until sent, ensuring data integrity */
|
||||||
|
for (size_t i = 0; i < msg_size; i++) {
|
||||||
|
uart_poll_out(uart_dev, msg_bytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef USB_H
|
||||||
|
#define USB_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "command_message.h"
|
||||||
|
|
||||||
|
|
||||||
|
int usb_init();
|
||||||
|
int usb_send_command(struct command_message_t *msg);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // USB_H
|
||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
#include "usb_conf.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/usb/usbd.h>
|
||||||
|
#include <zephyr/usb/bos.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(usb_conf, LOG_LEVEL_DBG);
|
||||||
|
|
||||||
|
/* By default, do not register the USB DFU class DFU mode instance. */
|
||||||
|
static const char *const blocklist[] = {
|
||||||
|
"dfu_dfu",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Instantiate a context named my_usb_context using the default USB device
|
||||||
|
* controller, the Zephyr project vendor ID, and the sample product ID.
|
||||||
|
* Zephyr project vendor ID must not be used outside of Zephyr samples.
|
||||||
|
*/
|
||||||
|
USBD_DEVICE_DEFINE(my_usb_context,
|
||||||
|
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
|
||||||
|
CONFIG_USBD_VID, CONFIG_USBD_PID);
|
||||||
|
|
||||||
|
USBD_DESC_LANG_DEFINE(my_usb_lang);
|
||||||
|
USBD_DESC_MANUFACTURER_DEFINE(my_usb_mfr, CONFIG_USBD_MANUFACTURER);
|
||||||
|
USBD_DESC_PRODUCT_DEFINE(my_usb_product, CONFIG_USBD_PRODUCT);
|
||||||
|
IF_ENABLED(CONFIG_HWINFO, (USBD_DESC_SERIAL_NUMBER_DEFINE(my_usb_serial)));
|
||||||
|
|
||||||
|
USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration");
|
||||||
|
USBD_DESC_CONFIG_DEFINE(hs_cfg_desc, "HS Configuration");
|
||||||
|
|
||||||
|
static const uint8_t attributes = (IS_ENABLED(CONFIG_USBD_SELF_POWERED) ?
|
||||||
|
USB_SCD_SELF_POWERED : 0) |
|
||||||
|
(IS_ENABLED(CONFIG_USBD_REMOTE_WAKEUP) ?
|
||||||
|
USB_SCD_REMOTE_WAKEUP : 0);
|
||||||
|
|
||||||
|
/* Full speed configuration */
|
||||||
|
USBD_CONFIGURATION_DEFINE(sample_fs_config,
|
||||||
|
attributes,
|
||||||
|
CONFIG_USBD_MAX_POWER, &fs_cfg_desc);
|
||||||
|
|
||||||
|
/* High speed configuration */
|
||||||
|
USBD_CONFIGURATION_DEFINE(sample_hs_config,
|
||||||
|
attributes,
|
||||||
|
CONFIG_USBD_MAX_POWER, &hs_cfg_desc);
|
||||||
|
|
||||||
|
#if CONFIG_SAMPLE_USBD_20_EXTENSION_DESC
|
||||||
|
/*
|
||||||
|
* This does not yet provide valuable information, but rather serves as an
|
||||||
|
* example, and will be improved in the future.
|
||||||
|
*/
|
||||||
|
static const struct usb_bos_capability_lpm bos_cap_lpm = {
|
||||||
|
.bLength = sizeof(struct usb_bos_capability_lpm),
|
||||||
|
.bDescriptorType = USB_DESC_DEVICE_CAPABILITY,
|
||||||
|
.bDevCapabilityType = USB_BOS_CAPABILITY_EXTENSION,
|
||||||
|
.bmAttributes = 0UL,
|
||||||
|
};
|
||||||
|
|
||||||
|
USBD_DESC_BOS_DEFINE(my_usb_usbext, sizeof(bos_cap_lpm), &bos_cap_lpm);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void usb_fix_code_triple(struct usbd_context *uds_ctx, const enum usbd_speed speed) {
|
||||||
|
/* Always use class code information from Interface Descriptors */
|
||||||
|
if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) ||
|
||||||
|
IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) ||
|
||||||
|
IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) ||
|
||||||
|
IS_ENABLED(CONFIG_USBD_MIDI2_CLASS) ||
|
||||||
|
IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS) ||
|
||||||
|
IS_ENABLED(CONFIG_USBD_VIDEO_CLASS)) {
|
||||||
|
/*
|
||||||
|
* Class with multiple interfaces have an Interface
|
||||||
|
* Association Descriptor available, use an appropriate triple
|
||||||
|
* to indicate it.
|
||||||
|
*/
|
||||||
|
usbd_device_set_code_triple(uds_ctx, speed,
|
||||||
|
USB_BCC_MISCELLANEOUS, 0x02, 0x01);
|
||||||
|
} else {
|
||||||
|
usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct usbd_context *usb_device_setup(usbd_msg_cb_t msg_cb) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&my_usb_context, &my_usb_lang);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to initialize language descriptor (%d)", err);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&my_usb_context, &my_usb_mfr);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&my_usb_context, &my_usb_product);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to initialize product descriptor (%d)", err);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
IF_ENABLED(CONFIG_HWINFO, (
|
||||||
|
err = usbd_add_descriptor(&my_usb_context, &my_usb_serial);
|
||||||
|
))
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to initialize SN descriptor (%d)", err);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USBD_SUPPORTS_HIGH_SPEED &&
|
||||||
|
usbd_caps_speed(&my_usb_context) == USBD_SPEED_HS) {
|
||||||
|
err = usbd_add_configuration(&my_usb_context, USBD_SPEED_HS,
|
||||||
|
&sample_hs_config);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to add High-Speed configuration");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_register_all_classes(&my_usb_context, USBD_SPEED_HS, 1,
|
||||||
|
blocklist);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to add register classes");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_fix_code_triple(&my_usb_context, USBD_SPEED_HS);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_configuration(&my_usb_context, USBD_SPEED_FS,
|
||||||
|
&sample_fs_config);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to add Full-Speed configuration");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_register_all_classes(&my_usb_context, USBD_SPEED_FS, 1, blocklist);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to add register classes");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_fix_code_triple(&my_usb_context, USBD_SPEED_FS);
|
||||||
|
usbd_self_powered(&my_usb_context, attributes & USB_SCD_SELF_POWERED);
|
||||||
|
|
||||||
|
if (msg_cb != NULL) {
|
||||||
|
err = usbd_msg_register_cb(&my_usb_context, msg_cb);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to register message callback");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_SAMPLE_USBD_20_EXTENSION_DESC
|
||||||
|
(void)usbd_device_set_bcd_usb(&my_usb_context, USBD_SPEED_FS, 0x0201);
|
||||||
|
(void)usbd_device_set_bcd_usb(&my_usb_context, USBD_SPEED_HS, 0x0201);
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&my_usb_context, &my_usb_usbext);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to add USB 2.0 Extension Descriptor");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return &my_usb_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct usbd_context *usb_device_init(usbd_msg_cb_t msg_cb) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (usb_device_setup(msg_cb) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_init(&my_usb_context);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to initialize device support");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &my_usb_context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef USB_CONF_H
|
||||||
|
#define USB_CONF_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <zephyr/usb/usbd.h>
|
||||||
|
|
||||||
|
struct usbd_context *usb_device_setup(usbd_msg_cb_t msg_cb);
|
||||||
|
struct usbd_context *usb_device_init(usbd_msg_cb_t msg_cb);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // USB_CONF_H
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include "zbus_channels.h"
|
||||||
|
#include "command_message.h"
|
||||||
|
|
||||||
|
|
||||||
|
ZBUS_CHAN_DEFINE(
|
||||||
|
in_command_chan,
|
||||||
|
struct command_message_t,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
ZBUS_OBSERVERS_EMPTY,
|
||||||
|
ZBUS_MSG_INIT(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
ZBUS_CHAN_DEFINE(
|
||||||
|
out_command_chan,
|
||||||
|
struct command_message_t,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
ZBUS_OBSERVERS_EMPTY,
|
||||||
|
ZBUS_MSG_INIT(0)
|
||||||
|
);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef ZBUS_CHANNELS_H
|
||||||
|
#define ZBUS_CHANNELS_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/zbus/zbus.h>
|
||||||
|
|
||||||
|
#include "command_message.h"
|
||||||
|
|
||||||
|
|
||||||
|
ZBUS_CHAN_DECLARE(in_command_chan);
|
||||||
|
ZBUS_CHAN_DECLARE(out_command_chan);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ZBUS_CHANNELS_H
|
||||||
Reference in New Issue
Block a user