espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
12.91k stars 7.09k forks source link

Serial communication between PC and device usin ESP32-S3 USB (IDFGH-10730) #11948

Open krupis opened 11 months ago

krupis commented 11 months ago

Answers checklist.

General issue report

Hello. I have been using UART0 commands when debugging various ESP32 devices. Example code:


void UART0_setup() {
    uart_config_t uart_config = {
        .baud_rate           = 115200,
        .data_bits           = UART_DATA_8_BITS,
        .parity              = UART_PARITY_DISABLE,
        .stop_bits           = UART_STOP_BITS_1,
        .flow_ctrl           = UART_HW_FLOWCTRL_DISABLE,
        //.rx_flow_ctrl_thresh = 122,
        //.use_ref_tick        = false,
    };

    // Configure UART parameters
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_0, &uart_config));
    // Set UART pins(TX: IO4, RX: IO5, RTS: IO18, CTS: IO19)
    //ESP_ERROR_CHECK(uart_set_pin(UART_NUM_0, 13, 26, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0, UART0_COMMAND_LINE_MAX_SIZE, 0, 0, NULL, 0));
}

void UART0_task(void *argument)
{
    UART0_setup();

    char command_line[UART0_COMMAND_LINE_MAX_SIZE];
    for (;;)
    {   
        int len = uart_read_bytes(UART_NUM_0, command_line, (UART0_COMMAND_LINE_MAX_SIZE - 1), 20 / portTICK_PERIOD_MS);
        if (len) {
            command_line[len] = 0;
            ParseSystemCmd(command_line, len); // Line is complete. Execute it!
            memset(&command_line, 0, sizeof(command_line));
        }
        vTaskDelay(10/portTICK_PERIOD_MS);

  }

}

I can then connect to the device using some terminal for example PuTTY, Termite, MobaXterm and etc.. and send various commands to the device.

I have recently purchased ESP32-S3 Lilygo device: https://www.lilygo.cc/products/t-display-s3

and after experimenting with it a little I have realized that I can program the device via USB and the device com port will appear on the serial terminal software but I cannot send any commands to it. I have looked at the device schematic:

image

image

As you can see from above, the USB connects to GPIO19 and GPIO20 respectively.

Is there any way to send/receive serial commands via USB in this manner?

krupis commented 11 months ago

I have tested out the following esp-idf example:

esp-idf\examples\peripherals\usb\device\tusb_serial_device

And it seems to work fine, I can send/receive serial commands via USB, but there is a couple of problems:

  1. After the device is flashed for the initial time, I will no longer be able to flash the device because the USB has been reconfigured and will not allow flashing. The only way to solve this issue to manually put the device into boot mode by holding boot button and reseting the device. Is that correct?

  2. I can send/receive UART commands via serial terminal such as "Termite" "PuTTy" and etc.. But printf and ESP_LOG statements are not visible. Is there any way to see the debug printf and ESP_LOG statements while also being able to send serial commands?

0xjakob commented 11 months ago

@krupis

Is there any way to send/receive serial commands via USB in this manner?

Yes, there is. ESP32-S3 has a peripheral called "USB-Serial-JTAG", which has only the respective functions, Serial and JTAG, and is connected to GPIO19/20 unless it's configured differently. If you want to use it, you just need to configure IDF to use the USB-serial as primary console output. You do that in menuconfig by going to Component config → ESP System Settings → Channel for console output, then choosing "USB Serial/JTAG Controller". If you do this, you should be able to flash and debug examples the same way as for ESP32 devices (which needed a UART-to-USB converter, usually located on the developement boards).

The confusion probably comes from the actual USB devices that the ESP32-S3 has, too. This is a generic USB peripheral. Via the example you mentioned, the generic USB peripheral can also be configured as a serial device, but with USB drivers and software stack in the background. The USB-Serial/JTAG peripheral is much simpler, but can only do Serial and JTAG.

krupis commented 11 months ago

@0xjakob

Thanks for informative answer. I think I have managed to brick 2 ESP32-S3 devices and realized what I have done.

I have enabled Channel for console output "USB Serial/JTAG Controller" option.

and I call the following code in my program:

void UART0_setup() {
    uart_config_t uart_config = {
        .baud_rate           = 115200,
        .data_bits           = UART_DATA_8_BITS,
        .parity              = UART_PARITY_DISABLE,
        .stop_bits           = UART_STOP_BITS_1,
        .flow_ctrl           = UART_HW_FLOWCTRL_DISABLE,
        //.rx_flow_ctrl_thresh = 122,
        //.use_ref_tick        = false,
    };

    // Configure UART parameters
    ESP_ERROR_CHECK(uart_param_config(UART_NUM_0, &uart_config));
    // Set UART pins(TX: IO4, RX: IO5, RTS: IO18, CTS: IO19)
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM_0, 20, 19, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0, UART0_COMMAND_LINE_MAX_SIZE, 0, 0, NULL, 0));
}

as you can see from above, I have reconfigured GPIO19 and GPIO20 (USB pins) to UART. So as soon as device boots up, the USB pins are reconfigured and the device bricks. I have managed to restore by manually putting the device into boot mode and reflash the device without UART0_setup(). Lesson learned.

Can you please confirm to me how can I send/receive serial commands via the USB Serial/JTAG controller (Exactly how I am sending and receiving commands using tusb_serial_device example) or that is not possible?

From what I understand, in order to do that, I need to config UART but I cannot do that on the USB pins GPIO 19 and GPIO 20. Do you understand what I am trying to do or I am not being clear enough?

In short, when I plug USB cable on the ESP32-S3 , I want to use it as I am using it on the normal ESP32. I use it for 3 things:

  1. Flashing the device
  2. Debugging using printf statements
  3. Sending and receiving serial commands via the UART0 (UART0 is connected to USB via USB->Serial bridge on most ESP32 development boards as you have mentioned)

At the moment on the ESP32-S3 I can only do 2/3:

  1. Flashing the device
  2. Debugging using printf statements

I cannot send/receive serial commands since the USB is not connected to any UART pins hence I wonder can I achieve that.

Spritetm commented 11 months ago

I think your issue is that you assume the USB-serial-JTAG controller 'looks like' an UART on the side of the ESP32 as well - it does not and requires a slightly different API to communicate if you do it on a lower level. If all you need to do is send/receive data, you should be able to work around that by fopen()ing /dev/usbserjtag and using fread/fwrite against that. This is portable to a chip where you want to use a 'real' uart as well; you'd simply open /dev/uartX in that case.

Another alternative (on the same level as using the UART driver would be) is to use the driver-level USB-serial-JTAG code, as described here:https://github.com/espressif/esp-idf/blob/master/components/driver/usb_serial_jtag/include/driver/usb_serial_jtag.h

krupis commented 11 months ago

@Spritetm

Thanks for the response. I am still trying to get a grasp on how it is even possible to emulate UART via USB/JTAG. I think I need to get the basic example running then I can examine the code.

Since you mentioned driver-level USB-serial-JTAG, I have found a relevant ESP32 forum thread regarding this: https://esp32.com/viewtopic.php?t=27944

I have written a simple program for this:


#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "nvs_flash.h"

#include "driver/usb_serial_jtag.h"
#include "esp_vfs_usb_serial_jtag.h"
#include "esp_vfs_dev.h"

static void initUart();

void app_main(void)
{
    esp_err_t ret;
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    printf("hello world \n");

    initUart();

}

static void initUart() {
    /* Disable buffering on stdin */
    setvbuf(stdin, NULL, _IONBF, 0);

    /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
    esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
    /* Move the caret to the beginning of the next line on '\n' */
    esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);

    /* Enable non-blocking mode on stdin and stdout */
    fcntl(fileno(stdout), F_SETFL, 0);
    fcntl(fileno(stdin), F_SETFL, 0);

    usb_serial_jtag_driver_config_t usb_serial_jtag_config;
    usb_serial_jtag_config.rx_buffer_size = 1024;
    usb_serial_jtag_config.tx_buffer_size = 1024;

    esp_err_t ret = ESP_OK;
    /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */
    ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
    if (ret != ESP_OK) {
        return;
    }

    /* Tell vfs to use usb-serial-jtag driver */
    esp_vfs_usb_serial_jtag_use_driver();

}

Am I on the right track here? The termite log:

image

Would you be able to suggest how can I attach a simple receive callback to this? For example, I have written command "ping" via termite and I want to parse this message and respond accordingly.

Spritetm commented 11 months ago

That should work. The driver at that level doesn't really have receive callback; what you'd do is start a task and then use a blocking getchar() or fgets() in that. Since you installed the driver, that call will not return until it received a char or an entire string, respectively.

krupis commented 11 months ago

@Spritetm

Understood!.

Have a look at my code:


#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "nvs_flash.h"

#include "driver/usb_serial_jtag.h"
#include "esp_vfs_usb_serial_jtag.h"
#include "esp_vfs_dev.h"

static void initUart();
static void Receive_callback(void *argument);

void app_main(void)
{
    esp_err_t ret;
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    printf("hello world before init Uart\n");

    initUart();

    printf("hello world after init Uart\n");

    xTaskCreate(Receive_callback,"UART receive callback",4096,NULL,5,NULL); // receiving commands from main uart

}

static void initUart() {

    /* Disable buffering on stdin */
    setvbuf(stdin, NULL, _IONBF, 0);

    /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
    esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
    /* Move the caret to the beginning of the next line on '\n' */
    esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);

    /* Enable non-blocking mode on stdin and stdout */
    fcntl(fileno(stdout), F_SETFL, 0);
    fcntl(fileno(stdin), F_SETFL, 0);

    usb_serial_jtag_driver_config_t usb_serial_jtag_config;
    usb_serial_jtag_config.rx_buffer_size = 1024;
    usb_serial_jtag_config.tx_buffer_size = 1024;

    esp_err_t ret = ESP_OK;
    /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes */
    ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
    if (ret != ESP_OK) {
        return;
    }

    /* Tell vfs to use usb-serial-jtag driver */
    esp_vfs_usb_serial_jtag_use_driver();

}

static void Receive_callback(void *argument)
{
    for (;;)
    {   
        uint8_t ch;
        ch = fgetc(stdin);
        if (ch!=0xFF)
        {
            //fputc(ch, stdout);
            printf("char received = %c \n",ch);
        }

        vTaskDelay(10/portTICK_PERIOD_MS);
     }

}

The terminal log:

image

As you can see I can now receive the command that I have sent via Termite. That was exactly what I was trying to achieve.

Although I am not really sure how that even works?

Spritetm commented 11 months ago

Although I am not really sure how that even works?

Printf and getchar etc are standard C functions, which read/write from/to two file descriptors called 'stdin' and 'stdout', which are effectively defined as 'the terminal the user is on'. On an Unix system, they'd point to the terminal window you're using, for instance; in ESP-IDF they usually point to UART0 with a secondary output going to the USB-serial-JTAG adapter. What you did was install a driver for USB-serial-JTAG that replaces that, making stdin and stdout always refer to the USB-serial-JTAG adapter. From then on, things like printf() will write to and things like getchar() will read from the USB-serial-JTAG device.

krupis commented 11 months ago

@Spritetm Thanks for clarifying :)