RP2350 FreeRTOS - usb stdio printf() doesn't work (but usb works fine) if tinyusb is locked to core 1 #2803

Open
opened 2026-01-22 14:44:53 -06:00 by akohlsmith · 4 comments
akohlsmith commented 2026-01-22 14:44:53 -06:00 (Migrated from github.com)

This is a weird one.

QueueHandle_t rxq;

typedef struct msg {
	uint8_t buf[64];
	uint8_t len;
} msg_t;

static void tud_main(__unused void *ctx)
{
#if (configNUMBER_OF_CORES > 1)
	vTaskCoreAffinitySet(NULL, (1 << 1));
#endif

	tud_init(0);
	stdio_init_all();

	while (1) {
		msg_t msg;

		tud_task();

		if (xQueueReceive(rxq, &msg, pdMS_TO_TICKS(1)) == pdPASS) {
			tud_cdc_n_write(1, msg.buf, msg.len);
			tud_cdc_n_write_flush(1);
		}
	};
}

static void foo_main(__unused void *ctx)
{
	int c = 0;

	while (1) {
		char buf[64];
		msg_t msg;

		snprintf(buf, sizeof(buf), "foo. c is %d\n", c++);
		printf("this is printf - %s", buf);

		memset(&msg, 0, sizeof(msg));
		msg.len = strlen(buf);
		memcpy(msg.buf, buf, msg.len);
		xQueueSendToBack(rxq, &msg, pdMS_TO_TICKS(1));

		vTaskDelay(pdMS_TO_TICKS(1000));
	};
}

void main(void)
{
	TaskHandle_t foo_task, tud_task;
	rxq = xQueueCreate(16, sizeof(msg_t));

	xTaskCreate(foo_main, "foo", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY, &foo_task);
	xTaskCreate(tud_main, "tusb_device", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY + 1, &tud_task);
	vTaskStartScheduler();

	while (1) ;
}

tinyusb is set up to be device-only and present two CDC interfaces. The first one is the usual stdio related stuff, and the second one just prints out the messages received over the queue.

If I do not set the core affinity of tud_task it works as expected. However if I do set it, or set it and then set it back to the default (no affinity) then usb still works great, I still see the messages on the second CDC interface, but none of the printf() messages come out. I'm certain that FreeRTOS is working since if it wasn't, or if tud_task was hanging or otherwise misbehaving then I would not have either CDC interface (and the queue system wouldn't be working either.

I've played with configRUN_MULTIPLE_PRIORITIES without any change, and if I set configTASK_DEFAULT_CORE_AFFINITY so both tasks run on the same core, printf/stdio is still broken. In my actual codebase I have a few more tasks and they are all running fine, as are the FreeRTOS timers and so on... it feels like the stdio system or maybe something deeper in tinyusb is making an assumption about the system that it shouldn't be.

This is a weird one. ```c QueueHandle_t rxq; typedef struct msg { uint8_t buf[64]; uint8_t len; } msg_t; static void tud_main(__unused void *ctx) { #if (configNUMBER_OF_CORES > 1) vTaskCoreAffinitySet(NULL, (1 << 1)); #endif tud_init(0); stdio_init_all(); while (1) { msg_t msg; tud_task(); if (xQueueReceive(rxq, &msg, pdMS_TO_TICKS(1)) == pdPASS) { tud_cdc_n_write(1, msg.buf, msg.len); tud_cdc_n_write_flush(1); } }; } static void foo_main(__unused void *ctx) { int c = 0; while (1) { char buf[64]; msg_t msg; snprintf(buf, sizeof(buf), "foo. c is %d\n", c++); printf("this is printf - %s", buf); memset(&msg, 0, sizeof(msg)); msg.len = strlen(buf); memcpy(msg.buf, buf, msg.len); xQueueSendToBack(rxq, &msg, pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1000)); }; } void main(void) { TaskHandle_t foo_task, tud_task; rxq = xQueueCreate(16, sizeof(msg_t)); xTaskCreate(foo_main, "foo", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY, &foo_task); xTaskCreate(tud_main, "tusb_device", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY + 1, &tud_task); vTaskStartScheduler(); while (1) ; } ``` tinyusb is set up to be device-only and present two CDC interfaces. The first one is the usual stdio related stuff, and the second one just prints out the messages received over the queue. If I do not set the core affinity of `tud_task` it works as expected. However if I do set it, or set it and then set it back to the default (no affinity) then usb still works great, I still see the messages on the second CDC interface, but none of the printf() messages come out. I'm certain that FreeRTOS is working since if it wasn't, or if `tud_task` was hanging or otherwise misbehaving then I would not have either CDC interface (and the queue system wouldn't be working either. I've played with `configRUN_MULTIPLE_PRIORITIES` without any change, and if I set `configTASK_DEFAULT_CORE_AFFINITY` so both tasks run on the same core, printf/stdio is still broken. In my actual codebase I have a few more tasks and they are all running fine, as are the FreeRTOS timers and so on... it feels like the stdio system or maybe something deeper in tinyusb is making an assumption about the system that it shouldn't be.
akohlsmith commented 2026-01-22 14:51:57 -06:00 (Migrated from github.com)

Bit of an update.

  • set the task priorities the same - no change
  • comment out vTaskCoreAffinitySet(), leave configTASK_DEFAULT_CORE_AFFINITY to default (tskNO_AFFINITY) - works fine
  • comment out vTaskCoreAffinitySet(), but set configTASK_DEFAULT_CORE_AFFINITY to (1 << 0) - works fine (everything runs on core 0)
  • comment out vTaskCoreAffinitySet(), but set configTASK_DEFAULT_CORE_AFFINITY to (1 << 1) - broken behaviour (everything runs on core 1)

It looks like the stdio library (or something in tinyusb) does not like core 1.

Bit of an update. * set the task priorities the same - no change * comment out `vTaskCoreAffinitySet()`, leave `configTASK_DEFAULT_CORE_AFFINITY` to default (`tskNO_AFFINITY`) - works fine * comment out `vTaskCoreAffinitySet()`, but set `configTASK_DEFAULT_CORE_AFFINITY` to (1 << 0) - works fine (everything runs on core 0) * comment out `vTaskCoreAffinitySet()`, but set `configTASK_DEFAULT_CORE_AFFINITY` to (1 << 1) - broken behaviour (everything runs on core 1) It looks like the stdio library (or something in tinyusb) does not like core 1.
akohlsmith commented 2026-01-23 14:11:25 -06:00 (Migrated from github.com)

Another interesting datapoint -- it appears to be something going sideways during initialization. If I move tud_init(0) and stdio_init_all() to the code running on core 0, I can still pin tud_thread to core1 and printf() and friends work as expected.

e.g.

static void tud_main(__unused void *ctx)
{
#if (configNUMBER_OF_CORES > 1)
	vTaskCoreAffinitySet(NULL, (1 << 1));
#endif

	while (1) {
		msg_t msg;

		tud_task();

		if (xQueueReceive(rxq, &msg, pdMS_TO_TICKS(1)) == pdPASS) {
			tud_cdc_n_write(1, msg.buf, msg.len);
			tud_cdc_n_write_flush(1);
		}
	};
}

void main(void)
{
	TaskHandle_t foo_task, tud_task;
	rxq = xQueueCreate(16, sizeof(msg_t));

	xTaskCreate(foo_main, "foo", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY, &foo_task);
	xTaskCreate(tud_main, "tusb_device", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY + 1, &tud_task);

	tud_init(0);
	stdio_init_all();

	vTaskStartScheduler();

	while (1) ;
}

works without issue even though tud_task() is running on core1.

Another interesting datapoint -- it appears to be something going sideways during initialization. If I move `tud_init(0)` and `stdio_init_all()` to the code running on core 0, I can still pin `tud_thread` to core1 and `printf()` and friends work as expected. e.g. ```c static void tud_main(__unused void *ctx) { #if (configNUMBER_OF_CORES > 1) vTaskCoreAffinitySet(NULL, (1 << 1)); #endif while (1) { msg_t msg; tud_task(); if (xQueueReceive(rxq, &msg, pdMS_TO_TICKS(1)) == pdPASS) { tud_cdc_n_write(1, msg.buf, msg.len); tud_cdc_n_write_flush(1); } }; } void main(void) { TaskHandle_t foo_task, tud_task; rxq = xQueueCreate(16, sizeof(msg_t)); xTaskCreate(foo_main, "foo", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY, &foo_task); xTaskCreate(tud_main, "tusb_device", 8 * configMINIMAL_STACK_SIZE, NULL, DEFAULT_TASK_PRIORITY + 1, &tud_task); tud_init(0); stdio_init_all(); vTaskStartScheduler(); while (1) ; } ``` works without issue even though `tud_task()` is running on core1.
kilograham commented 2026-06-30 09:32:08 -05:00 (Migrated from github.com)

could you maybe attach the rest of the files, so it's quicker for me to reproduce

could you maybe attach the rest of the files, so it's quicker for me to reproduce
kilograham commented 2026-07-01 13:30:13 -05:00 (Migrated from github.com)

moving to 2.3.1 as the initialize from core 0 is a reasonable workaround

note, i wonder if you are using a release build, as there is an assertion about stdio_usb init being done from core 0 which you're probably supposed to be hitting

moving to 2.3.1 as the initialize from core 0 is a reasonable workaround note, i wonder if you are using a release build, as there is an assertion about stdio_usb init being done from core 0 which you're probably supposed to be hitting
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-raspberrypi/pico-sdk#2803
No description provided.