Skip to content

Commit 1b918b9

Browse files
committed
wip: set usb hid and cdc
1 parent 69c4167 commit 1b918b9

7 files changed

Lines changed: 208 additions & 96 deletions

File tree

.vscode/macrolev.code-workspace

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@
6868
"forward_list": "cpp",
6969
"list": "cpp",
7070
"format": "cpp",
71-
"functional": "cpp"
71+
"functional": "cpp",
72+
"queue.h": "c"
7273
},
7374
"typescript.tsdk": "./node_modules/typescript/lib",
74-
"tailwindCSS.classAttributes": ["class", "className", ".*Styles", ".*Class"]
75+
"tailwindCSS.classAttributes": ["class", "className", ".*Styles", ".*Class"],
76+
"cSpell.enabled": false
7577
}
7678
}

firmware/esp32-s3/config.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
idf_component_register(
2-
SRCS "main.c" "macrolev.c"
2+
SRCS "main.c" "macrolev.c" "usb_descriptors.c"
33
INCLUDE_DIRS "."
44
PRIV_REQUIRES esp_adc esp_timer esp_driver_gpio spiffs json
55
)

firmware/esp32-s3/main/main.c

Lines changed: 107 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "cJSON.h"
2+
#include "class/hid/hid_device.h"
23
#include "driver/gpio.h"
34
#include "esp_adc/adc_continuous.h"
45
#include "esp_log.h"
@@ -205,7 +206,7 @@ static esp_err_t init_spiffs(void) {
205206
esp_vfs_spiffs_conf_t conf = {
206207
.base_path = "/spiffs",
207208
.partition_label = NULL,
208-
.max_files = 5,
209+
.max_files = 1,
209210
.format_if_mount_failed = true
210211
};
211212

@@ -249,6 +250,7 @@ esp_err_t save_json_to_file(const char *filename, cJSON *json) {
249250
free(json_string);
250251

251252
ESP_LOGI(TAG, "JSON saved to file: %s", filepath);
253+
ESP_LOGI(TAG, "JSON: %s", cJSON_Print(json));
252254
return ESP_OK;
253255
}
254256

@@ -296,6 +298,13 @@ cJSON *load_json_from_file(const char *filename) {
296298
return json;
297299
}
298300

301+
#define CDC_ACCUM_BUF_SIZE (1024 * 1024) // 1MB buffer for large JSON payloads
302+
static char cdc_accum_buf[CDC_ACCUM_BUF_SIZE];
303+
static size_t cdc_accum_len = 0;
304+
305+
#define MARKER "[EOF]"
306+
#define MARKER_LEN (sizeof(MARKER) - 1)
307+
299308
/**
300309
* @brief CDC device RX callback
301310
*
@@ -307,77 +316,48 @@ cJSON *load_json_from_file(const char *filename) {
307316
void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
308317
size_t rx_size = 0;
309318
esp_err_t ret = tinyusb_cdcacm_read(itf, rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
310-
if (ret == ESP_OK) {
311-
// Null terminate the received data
312-
rx_buf[rx_size] = '\0';
313-
314-
// Try to parse as JSON
315-
cJSON *json = cJSON_Parse((char *)rx_buf);
316-
if (json != NULL) {
317-
// Valid JSON received, save it
318-
ESP_LOGI(TAG, "Valid JSON received, saving to config.json");
319-
esp_err_t save_ret = save_json_to_file(JSON_FILENAME, json);
320-
321-
// Send response
322-
const char *response;
323-
if (save_ret == ESP_OK) {
324-
response = "JSON saved successfully\r\n";
325-
} else {
326-
response = "Error saving JSON\r\n";
327-
}
328-
tinyusb_cdcacm_write_queue(itf, (uint8_t *)response, strlen(response));
329-
tinyusb_cdcacm_write_flush(itf, 0);
330-
331-
cJSON_Delete(json);
332-
} else {
333-
// Not valid JSON, echo back as normal
334-
usb_message_t tx_msg = {
335-
.buf_len = rx_size,
336-
.itf = itf,
337-
};
338-
memcpy(tx_msg.buf, rx_buf, rx_size);
339-
xQueueSend(usb_queue, &tx_msg, 0);
340-
}
341-
} else {
342-
ESP_LOGE(TAG, "Read Error");
343-
}
344-
}
345-
346-
/**
347-
* @brief CDC device line change callback
348-
*
349-
* CDC device signals, that the DTR, RTS states changed
350-
*
351-
* @param[in] itf CDC device index
352-
* @param[in] event CDC event type
353-
*/
354-
void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
355-
int dtr = event->line_state_changed_data.dtr;
356-
int rts = event->line_state_changed_data.rts;
357-
ESP_LOGI(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts);
358-
359-
// When DTR is set, terminal connection is opened
360-
if (dtr) {
361-
// Send welcome message
362-
const char *hello = "Hello! ESP32-S3 CDC device is ready!\r\n";
363-
tinyusb_cdcacm_write_queue(itf, (uint8_t *)hello, strlen(hello));
364-
tinyusb_cdcacm_write_flush(itf, 0);
365-
366-
// Load and send current JSON content
367-
cJSON *json = load_json_from_file(JSON_FILENAME);
368-
if (json != NULL) {
369-
char *json_str = cJSON_PrintUnformatted(json);
370-
if (json_str != NULL) {
371-
tinyusb_cdcacm_write_queue(itf, (uint8_t *)json_str, strlen(json_str));
372-
tinyusb_cdcacm_write_queue(itf, (uint8_t *)"\r\n", 2);
373-
tinyusb_cdcacm_write_flush(itf, 0);
374-
free(json_str);
319+
ESP_LOGI(TAG, "CDC RX callback: ret=%d, rx_size=%d", ret, rx_size);
320+
if (ret == ESP_OK && rx_size > 0) {
321+
ESP_LOGI(TAG, "Accumulating %d bytes to buffer (current len: %d)", rx_size, cdc_accum_len);
322+
// Check for buffer overflow
323+
if (cdc_accum_len + rx_size < CDC_ACCUM_BUF_SIZE) {
324+
memcpy(&cdc_accum_buf[cdc_accum_len], rx_buf, rx_size);
325+
cdc_accum_len += rx_size;
326+
ESP_LOGI(TAG, "Buffer after accumulation: len=%d", cdc_accum_len);
327+
ESP_LOG_BUFFER_HEXDUMP(TAG, rx_buf, rx_size, ESP_LOG_INFO);
328+
329+
// Robustly search for marker anywhere in the buffer
330+
if (cdc_accum_len >= MARKER_LEN) {
331+
for (size_t i = 0; i <= cdc_accum_len - MARKER_LEN; ++i) {
332+
if (memcmp(&cdc_accum_buf[i], MARKER, MARKER_LEN) == 0) {
333+
ESP_LOGI(TAG, "Marker detected ([EOF]) at position %d. Attempting to parse JSON.", (int)i);
334+
cdc_accum_buf[i] = '\0'; // Null-terminate before marker
335+
336+
// Try to parse as JSON
337+
cJSON *json = cJSON_Parse(cdc_accum_buf);
338+
if (json != NULL) {
339+
ESP_LOGI(TAG, "Valid JSON received, saving to config.json");
340+
save_json_to_file(JSON_FILENAME, json);
341+
cJSON_Delete(json);
342+
} else {
343+
ESP_LOGE(TAG, "Invalid JSON received. Parse failed.");
344+
}
345+
// Move any data after the marker to the start of the buffer (for next message)
346+
size_t remaining = cdc_accum_len - (i + MARKER_LEN);
347+
if (remaining > 0) {
348+
memmove(cdc_accum_buf, &cdc_accum_buf[i + MARKER_LEN], remaining);
349+
}
350+
cdc_accum_len = remaining;
351+
ESP_LOGI(TAG, "Buffer reset for next message. Remaining bytes: %d", (int)cdc_accum_len);
352+
ESP_LOGI(TAG, "Last 16 bytes: %.*s", 16, &cdc_accum_buf[cdc_accum_len > 16 ? cdc_accum_len - 16 : 0]);
353+
break; // Only handle one message per callback
354+
}
355+
}
375356
}
376-
cJSON_Delete(json);
377357
} else {
378-
const char *no_json = "No JSON configuration found\r\n";
379-
tinyusb_cdcacm_write_queue(itf, (uint8_t *)no_json, strlen(no_json));
380-
tinyusb_cdcacm_write_flush(itf, 0);
358+
// Buffer overflow, reset (no response)
359+
ESP_LOGE(TAG, "CDC accumulation buffer overflow. Resetting buffer.");
360+
cdc_accum_len = 0;
381361
}
382362
}
383363
}
@@ -393,12 +373,7 @@ void app_main(void) {
393373

394374
// Initialize TinyUSB
395375
ESP_LOGI(TAG, "USB initialization");
396-
const tinyusb_config_t tusb_cfg = {
397-
.device_descriptor = NULL,
398-
.string_descriptor = NULL,
399-
.external_phy = false,
400-
.configuration_descriptor = NULL,
401-
};
376+
extern const tinyusb_config_t tusb_cfg;
402377
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
403378

404379
// Initialize CDC
@@ -407,28 +382,68 @@ void app_main(void) {
407382
.cdc_port = TINYUSB_CDC_ACM_0,
408383
.callback_rx = &tinyusb_cdc_rx_callback,
409384
.callback_rx_wanted_char = NULL,
410-
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
411-
.callback_line_coding_changed = NULL
385+
.callback_line_state_changed = NULL,
386+
.callback_line_coding_changed = NULL,
387+
.rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE
412388
};
413389
ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
414390

415391
ESP_LOGI(TAG, "USB initialization DONE");
416392

393+
// test init json
394+
cJSON *json = load_json_from_file(JSON_FILENAME);
395+
ESP_LOGI(TAG, "JSON: %s", cJSON_PrintUnformatted(json));
396+
// const int size = cJSON_GetArraySize(json);
397+
// for (int i = 0; i < size; i++) {
398+
// cJSON *item = cJSON_GetArrayItem(json, i);
399+
// cJSON *id = cJSON_GetObjectItemCaseSensitive(item, "id");
400+
// cJSON *hardware = cJSON_GetObjectItemCaseSensitive(item, "hardware");
401+
// cJSON *adcChannel = cJSON_GetObjectItemCaseSensitive(hardware, "adcChannel");
402+
// cJSON *muxChannel = cJSON_GetObjectItemCaseSensitive(hardware, "muxChannel");
403+
// cJSON *magnetDirection = cJSON_GetObjectItemCaseSensitive(hardware, "magnetDirection");
404+
// ESP_LOGI(TAG, "id: %d", id->valueint);
405+
// ESP_LOGI(TAG, "adcChannel: %d", adcChannel->valueint);
406+
// ESP_LOGI(TAG, "muxChannel: %d", muxChannel->valueint);
407+
// ESP_LOGI(TAG, "magnetDirection: %d", magnetDirection->valueint);
408+
// }
409+
// cJSON_Delete(json);
410+
417411
// Main loop
418412
while (1) {
419-
if (xQueueReceive(usb_queue, &msg, portMAX_DELAY)) {
420-
if (msg.buf_len) {
421-
// Print received data
422-
ESP_LOGI(TAG, "Data from channel %d:", msg.itf);
423-
ESP_LOG_BUFFER_HEXDUMP(TAG, msg.buf, msg.buf_len, ESP_LOG_INFO);
424-
425-
// Echo back
426-
tinyusb_cdcacm_write_queue(msg.itf, msg.buf, msg.buf_len);
427-
esp_err_t err = tinyusb_cdcacm_write_flush(msg.itf, 0);
428-
if (err != ESP_OK) {
429-
ESP_LOGE(TAG, "CDC write error: %s", esp_err_to_name(err));
430-
}
431-
}
432-
}
413+
vTaskDelay(pdMS_TO_TICKS(10));
414+
// if (xQueueReceive(usb_queue, &msg, portMAX_DELAY)) {
415+
// if (msg.buf_len) {
416+
// // Print received data
417+
// ESP_LOGI(TAG, "Data from channel %d with length %d", msg.itf, msg.buf_len);
418+
// ESP_LOG_BUFFER_HEXDUMP(TAG, msg.buf, msg.buf_len, ESP_LOG_INFO);
419+
420+
// // // Echo back
421+
// // tinyusb_cdcacm_write_queue(msg.itf, msg.buf, msg.buf_len);
422+
// // esp_err_t err = tinyusb_cdcacm_write_flush(msg.itf, 0);
423+
// // if (err != ESP_OK) {
424+
// // ESP_LOGE(TAG, "CDC write error: %s", esp_err_to_name(err));
425+
// // }
426+
// }
427+
// }
433428
}
434429
}
430+
431+
/********* TinyUSB HID callbacks ***************/
432+
433+
// Invoked when received GET_REPORT control request
434+
// Application must fill buffer report's content and return its length.
435+
// Return zero will cause the stack to STALL request
436+
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
437+
(void)instance;
438+
(void)report_id;
439+
(void)report_type;
440+
(void)buffer;
441+
(void)reqlen;
442+
443+
return 0;
444+
}
445+
446+
// Invoked when received SET_REPORT control request or
447+
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
448+
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
449+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "tinyusb.h"
2+
3+
// Interface definitions
4+
enum {
5+
ITF_NUM_HID = 0,
6+
ITF_NUM_CDC,
7+
ITF_NUM_CDC_DATA,
8+
ITF_NUM_TOTAL
9+
};
10+
11+
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN * CFG_TUD_HID + TUD_CDC_DESC_LEN * CFG_TUD_CDC)
12+
13+
/**
14+
* @brief HID report descriptor
15+
*
16+
* In this example we implement Keyboard + Mouse HID device,
17+
* so we must define both report descriptors
18+
*/
19+
const uint8_t hid_report_descriptor[] = {
20+
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD)),
21+
};
22+
23+
const char *string_descriptor[] = {
24+
// array of pointer to string descriptors
25+
(char[]){ 0x09, 0x04 }, // 0: is supported language is English (0x0409)
26+
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer
27+
CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product
28+
CONFIG_TINYUSB_DESC_SERIAL_STRING, // 3: Serials, should use chip ID
29+
CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface
30+
"Macrolev HID Device", // 5: HID Interface
31+
};
32+
33+
/**
34+
* @brief Configuration descriptor
35+
*
36+
* This is a simple configuration descriptor that defines 1 configuration and 1 HID interface
37+
*/
38+
static uint8_t configuration_descriptor[] = {
39+
// Configuration number, interface count, string index, total length, attribute, power in mA
40+
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
41+
42+
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
43+
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 5, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_report_descriptor), 0x83, CFG_TUD_HID_EP_BUFSIZE, 10),
44+
45+
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
46+
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, 0x02, 0x82, TUD_OPT_HIGH_SPEED ? 512 : 64),
47+
};
48+
49+
// Invoked when received GET HID REPORT DESCRIPTOR request
50+
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
51+
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
52+
// We use only one interface and one HID report descriptor, so we can ignore parameter 'instance'
53+
return hid_report_descriptor;
54+
}
55+
56+
const tinyusb_config_t tusb_cfg = {
57+
.device_descriptor = NULL,
58+
.string_descriptor = string_descriptor,
59+
.external_phy = false,
60+
.configuration_descriptor = configuration_descriptor,
61+
};
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# This file was generated using idf.py save-defconfig. It can be edited manually.
2-
# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration
2+
# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Minimal Configuration
33
#
44
CONFIG_IDF_TARGET="esp32s3"
55
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
66
CONFIG_PARTITION_TABLE_CUSTOM=y
77
CONFIG_LOG_COLORS=y
88
CONFIG_WL_SECTOR_SIZE_512=y
9+
CONFIG_TINYUSB_DEBUG_LEVEL=3
910
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Heiso"
1011
CONFIG_TINYUSB_DESC_PRODUCT_STRING="Macrolev Rev.2"
1112
CONFIG_TINYUSB_DESC_CDC_STRING="Macrolev CDC Device"
13+
CONFIG_TINYUSB_MSC_ENABLED=y
1214
CONFIG_TINYUSB_CDC_ENABLED=y
15+
CONFIG_TINYUSB_CDC_RX_BUFSIZE=1024
16+
CONFIG_TINYUSB_CDC_TX_BUFSIZE=1024
17+
CONFIG_TINYUSB_HID_COUNT=1

firmware/esp32-s3/send_cdc.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
import sys
3+
import time
4+
5+
if len(sys.argv) < 3:
6+
print("Usage: python3 send_cdc.py <serial_port> <file_path>")
7+
sys.exit(1)
8+
9+
SERIAL_PORT = sys.argv[1]
10+
FILE_PATH = sys.argv[2]
11+
CHUNK_SIZE = 64
12+
MARKER = b'[EOF]'
13+
14+
# Open serial port
15+
fd = os.open(SERIAL_PORT, os.O_WRONLY | os.O_NOCTTY)
16+
17+
with open(FILE_PATH, 'rb') as f:
18+
while True:
19+
chunk = f.read(CHUNK_SIZE)
20+
if not chunk:
21+
break
22+
os.write(fd, chunk)
23+
print(f"Sent {len(chunk)} bytes")
24+
time.sleep(0.01) # 10ms delay
25+
26+
os.write(fd, MARKER)
27+
print("Sent marker [EOF]. Done.")
28+
os.close(fd)

0 commit comments

Comments
 (0)