Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Serial UART Console Improvements #9964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 16, 2025
Merged

Conversation

eightycc
Copy link
Collaborator

A set of improvements to the serial UART console.

  1. Corrects a crash or hang when a write is attempted to the serial UART console before it is initialized.
  2. Allow serial_early_init() to be called earlier in port initialization.
  3. Ensures that all lines written to the serial UART console are properly "cooked", i.e., line endings are translated to '\r\n'.
  4. Refactors serial UART console write routines.
  5. Adds an optional timestamp to serial UART console writes.
  6. Adds a macro that provides a printf style wrapper for serial UART console output.
  7. Adds a macro that wraps a hex dump print function.
  8. A hex dump print function that formats a hex and character dump of an arbitrary buffer in memory to an mp_print_t device.

@eightycc
Copy link
Collaborator Author

eightycc commented Jan 16, 2025

Allow serial_early_init to be called earlier

Initializing the serial UART console earlier than its default can be useful when debugging inside a port-specific port_init(). Since serial_early_init() is not serially reusable, a guard is implemented that ignores additional calls to serial_early_init(). This guard also serves to prevent writing to an uninitialized UART. Here is an example from RP2's port_init():

    // Initialize RTC
    #if CIRCUITPY_RTC
    common_hal_rtc_init();
    #endif

    // For the tick.
    hardware_alarm_claim(0);
    hardware_alarm_set_callback(0, _tick_callback);

    // RP2 port-specific early serial initialization for psram debug.
    // The RTC must already be initialized, otherwise the serial UART
    // will hang.
    serial_early_init();

    #ifdef CIRCUITPY_PSRAM_CHIP_SELECT
    setup_psram();
    #endif

Optional Timestamp

An optional timestamp can be added to serial UART console output by setting CIRCUITPY_CONSOLE_UART_TIMESTAMP. Here is an example from a board's circuitpy_mpconfig.h:

#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO1)
#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO0)
#define CIRCUITPY_CONSOLE_UART_TIMESTAMP (1)

CIRCUITPY_CONSOLE_UART_PRINTF

This macro wraps an mp_printf() call to print to the serial UART console. Since the macro expands to a NOP when the UART console is not present, calls of it can remain safely in production builds. Because of the '\r\n' cooking that happens to all line endings sent to the UART console, it can be easily attached to existing library debug facilities. For example, cyw43-driver used by the RP2 port can be attached by adding these lines to ports/raspberrypi/cyw43_configport.h then building with DEBUG=1::

#include "supervisor/shared/serial.h"
#define CYW43_PRINTF(...)               CIRCUITPY_CONSOLE_UART_PRINTF(__VA_ARGS__)
#define CYW43_VERBOSE_DEBUG             (1)
#define CYW43_VDEBUG(...)               CYW43_PRINTF(__VA_ARGS__)

Here is a small sample with the above plus timestamps enabled:

69.826(0.897): cyw43_read_reg_u16 BUS_FUNCTION 0x4=0xbe2020
69.829(0.003): cyw43_write_reg_u16 BUS_FUNCTION 0x4=0x2020
69.833(0.004): cyw43_read_reg_u32 BUS_FUNCTION 0x8=0x20728
69.838(0.005): bus_gspi_status 0x20728 0x103
69.842(0.004): cyw43_read_reg_u32 BUS_FUNCTION 0x8=0x28
69.847(0.005): bus_gspi_status 0x28 0x0
69.851(0.004): No packet
70.132(0.281): cyw43_read_reg_u16 BUS_FUNCTION 0x4=0xbe2020
70.135(0.003): cyw43_write_reg_u16 BUS_FUNCTION 0x4=0x2020
70.140(0.005): cyw43_read_reg_u32 BUS_FUNCTION 0x8=0x20528
70.146(0.006): bus_gspi_status 0x20528 0x102
70.150(0.004): cyw43_read_reg_u32 BUS_FUNCTION 0x8=0x20528
70.155(0.005): bus_gspi_status 0x20528 0x102
70.159(0.004): cyw43_read_reg_u32 BUS_FUNCTION 0x8=0x28
70.164(0.005): bus_gspi_status 0x28 0x0
70.167(0.003): No packet

The timestamps are wall-clock seconds.milliseconds and delta seconds.milliseconds in ().

@eightycc
Copy link
Collaborator Author

@dhalbert This is the first part of the serial console/RAM logging changes we chatted about a few weeks back. Please have a look.

@tannewt tannewt requested a review from dhalbert January 16, 2025 18:07
Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding these features, which will be very handy. I had one question about naming.

Often when I am debugging, I do not use CONSOLE_UART, but instead just use mp_printf(&mp_plat_print, ...), so that my print's are interspersed with prints from the CircuitPython program. I think I mentioned briefly about making a DEBUG_PRINTF-style macro that printed to &mp_plat_print. It would also be handy to do such logging with timestamps as well =, to &mp_plat_print.

The above could all be "second part", since you mentioned this is "first part". What did you have in mind for "second part"?

@eightycc
Copy link
Collaborator Author

Often when I am debugging, I do not use CONSOLE_UART, but instead just use mp_printf(&mp_plat_print, ...), so that my print's are interspersed with prints from the CircuitPython program. I think I mentioned briefly about making a DEBUG_PRINTF-style macro that printed to &mp_plat_print. It would also be handy to do such logging with timestamps as well =, to &mp_plat_print.

The macro wraps mp_printf() so it does intersperse with plain old console output sent to the serial UART. Likewise, debug output and console output will be time stamped when timestamping is active.

The above could all be "second part", since you mentioned this is "first part". What did you have in mind for "second part"?

I'm testing the second part now:

  • Logging to a circular buffer in RAM including debug messages and normal console output.
  • RAM log may optionally start at boot time.
  • RAM log wraps or stops when log area is full.
  • RAM log survives through VM restarts.
  • RAM log survives non-POR resets. Useful for debugging crashing bugs.
  • Because RAM log never blocks, it can be used concurrently from user level and IRQ level code. A simple exclusion mechanism is used to prevent "checkering" of log entries.
  • A complementary ramlog module that provides complete control of the facility from Python code. Adheres to and implements the stream protocol allowing log unloading from Python code. Additionally, stream writes provide an interface for Adafruit_Logging so that the facility can also be used for low-level Python debugging.

Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! We had a side conversation in discord about some details. All set.

@dhalbert dhalbert merged commit 98e2afa into adafruit:main Jan 16, 2025
227 checks passed
@dhalbert
Copy link
Collaborator

Smoke tested builds on an ESP32-C3 and Feather ESP32 V2 board

@tannewt
Copy link
Member

tannewt commented Feb 12, 2025

FYI the removal of console_uart_printf broke the ability to use the console_uart_printf for tinyusb debugging: https://github.com/adafruit/circuitpython/blob/main/supervisor/shared/usb/tusb_config.h#L61

@eightycc
Copy link
Collaborator Author

@tannewt Yes, I missed that case. Change console_uart_printf to CIRCUITPY_CONSOLE_UART_PRINTF to fix it. I'm normalizing debug macros into something less unwieldy with the RamLog facility. Would you like a PR to fix it now, or shall we wait a week or two for the RamLog PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants