Merge branch 'feature/ulp_uart' into 'master'

ulp-riscv: uart print

See merge request espressif/esp-idf!19229
This commit is contained in:
Marius Vikhammer 2022-08-02 09:14:48 +08:00
commit 5d23a757d6
17 changed files with 478 additions and 10 deletions

View File

@ -35,4 +35,18 @@ menu "Ultra Low Power (ULP) Co-processor"
Bytes of memory to reserve for ULP Co-processor firmware & data.
Data is reserved at the beginning of RTC slow memory.
menu "ULP RISC-V Settings"
depends on ULP_COPROC_TYPE_RISCV
config ULP_RISCV_UART_BAUDRATE
int
prompt "Baudrate used by the bitbanged ULP RISC-V UART driver"
default 9600
help
The accuracy of the bitbanged UART driver is limited, it is not
recommend to increase the value above 19200.
endmenu
endmenu # Ultra Low Power (ULP) Co-processor

View File

@ -75,6 +75,8 @@ if(ULP_COCPU_IS_RISCV)
list(APPEND ULP_S_SOURCES
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/start.S"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c"
"${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c")
#dummy loop to force pre-processed linker file generation:

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Underlying driver function for printing a char, e.g. over UART */
typedef void (*putc_fn_t)(const void *ctx, const char c);
/**
* @brief Installs a print driver that will be used for ulp_riscv_print calls
*
* @param putc Underlying driver function for printing a char, e.g. over UART
* @param putc_ctx Context that will be passed when calling the putc function
*/
void ulp_riscv_print_install(putc_fn_t putc, void *putc_ctx);
/**
* @brief Prints a null-terminated string
*
* @param str String to print
*/
void ulp_riscv_print_str(const char *str);
/**
* @brief Prints a hex number. Does not print 0x, only the digits
*
* @param Hex number to print
*/
void ulp_riscv_print_hex(int h);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "ulp_riscv_gpio.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct {
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_cfg_t; // Config for the driver
typedef struct {
uint32_t bit_duration_cycles; // Number of cycles to hold the line for each bit
gpio_num_t tx_pin; // TX pin number
} ulp_riscv_uart_t; // Context for the driver, initialized by ulp_riscv_uart_init
/**
* @brief Initialize the bit-banged UART driver
*
* @note Will also initialize the underlying HW, i.e. the RTC GPIO used.
*
* @param uart Pointer to the struct that will contain the initialized context
* @param cfg Pointer to the config struct which will be used to initialize the driver
*/
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg);
/**
* @brief Outputs a single byte on the tx pin
*
* @param uart Pointer to the initialized driver context
* @param c Byte to output
*/
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c);
#ifdef __cplusplus
}
#endif

View File

@ -94,7 +94,16 @@ void ulp_riscv_timer_resume(void);
*
* @param cycles Number of cycles to busy wait
*/
void ulp_riscv_delay_cycles(uint32_t cycles);
void static inline ulp_riscv_delay_cycles(uint32_t cycles)
{
uint32_t start = ULP_RISCV_GET_CCOUNT();
/* Off with an estimate of cycles in this function to improve accuracy */
uint32_t end = start + cycles - 20;
while (ULP_RISCV_GET_CCOUNT() < end) {
/* Wait */
}
}
/**
* @brief Clears the GPIO wakeup interrupt bit

View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_riscv_print.h"
typedef struct {
putc_fn_t putc; // Putc function of the underlying driver, e.g. UART
void *putc_ctx; // Context passed to the putc function
} ulp_riscv_print_ctx_t;
static ulp_riscv_print_ctx_t s_print_ctx;
void ulp_riscv_print_install(putc_fn_t putc, void * putc_ctx)
{
s_print_ctx.putc_ctx = putc_ctx;
s_print_ctx.putc = putc;
}
void ulp_riscv_print_str(const char *str)
{
if (!s_print_ctx.putc) {
return;
}
for (int i = 0; str[i] != 0; i++) {
s_print_ctx.putc(s_print_ctx.putc_ctx ,str[i]);
}
}
void ulp_riscv_print_hex(int h)
{
int x;
int c;
if (!s_print_ctx.putc) {
return;
}
// Does not print '0x', only the digits (8 digits to print)
for (x = 0; x < 8; x++) {
c = (h >> 28) & 0xf; // extract the leftmost byte
if (c < 10) {
s_print_ctx.putc(s_print_ctx.putc_ctx ,'0' + c);
} else {
s_print_ctx.putc(s_print_ctx.putc_ctx ,'a' + c - 10);
}
h <<= 4; // move the 2nd leftmost byte to the left, to be extracted next
}
}

View File

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_gpio.h"
#include "ulp_riscv_uart_ulp_core.h"
/* We calculate the bit duration at compile time to speed up and avoid pulling in soft-float libs */
#define BIT_DURATION_CYCLES ( ULP_RISCV_CYCLES_PER_US * ((1000*1000) / CONFIG_ULP_RISCV_UART_BAUDRATE) )
void ulp_riscv_uart_init(ulp_riscv_uart_t *uart, const ulp_riscv_uart_cfg_t *cfg)
{
uart->tx_pin = cfg->tx_pin;
/* 1 bit duration with length given in clock cycles */
uart->bit_duration_cycles = BIT_DURATION_CYCLES;
/* Setup GPIO used for uart TX */
ulp_riscv_gpio_init(cfg->tx_pin);
ulp_riscv_gpio_output_enable(cfg->tx_pin);
ulp_riscv_gpio_set_output_mode(cfg->tx_pin, RTCIO_MODE_OUTPUT_OD);
ulp_riscv_gpio_pullup(cfg->tx_pin);
ulp_riscv_gpio_pulldown_disable(cfg->tx_pin);
ulp_riscv_gpio_output_level(cfg->tx_pin, 1);
}
void ulp_riscv_uart_putc(const ulp_riscv_uart_t *uart, const char c)
{
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
for (int i = 0; i<8; i++) {
/* Offset the delay to account for cycles spent setting the bit */
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 100);
if ( (1 << i) & c) {
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
} else {
ulp_riscv_gpio_output_level(uart->tx_pin, 0);
}
}
ulp_riscv_delay_cycles(uart->bit_duration_cycles - 20);
ulp_riscv_gpio_output_level(uart->tx_pin, 1);
ulp_riscv_delay_cycles(uart->bit_duration_cycles);
}

View File

@ -35,15 +35,6 @@ void ulp_riscv_halt(void)
while(1);
}
void ulp_riscv_delay_cycles(uint32_t cycles)
{
uint32_t start = ULP_RISCV_GET_CCOUNT();
while ((ULP_RISCV_GET_CCOUNT() - start) < cycles) {
/* Wait */
}
}
void ulp_riscv_timer_stop(void)
{
CLEAR_PERI_REG_MASK(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);

View File

@ -53,6 +53,8 @@ To compile the ULP RISC-V code as part of the component, the following steps mus
7. **Add the generated binary to the list of binary files** to be embedded into the application.
.. _ulp-riscv-access-variables:
Accessing the ULP RISC-V Program Variables
------------------------------------------
@ -136,10 +138,25 @@ The program runs until the field ``RTC_CNTL_COCPU_DONE`` in register ``RTC_CNTL_
To disable the timer (effectively preventing the ULP program from running again), please clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_ULP_CP_TIMER_REG`` register. This can be done both from the ULP code and from the main program.
Debugging Your ULP RISC-V Program
----------------------------------
When programming the ULP RISC-V it can sometimes be challenging to figure out why the program is not behaving as expected. Due to the simplicity of the core many of the standard methods of debugging, e.g. JTAG or ``printf``, are simply not available.
Keeping this in mind, here are some ways that may help you debug you ULP RISC-V program:
* Share program state through shared variables: as described in :ref:`ulp-riscv-access-variables`, both the main CPU and the ULP core can easily access global variables in RTC memory. Writing state information to such a variable from the ULP and reading it from the main CPU can help you discern what is happening on the ULP core. The downside of this approach is that it requires the main CPU to be awake, which will not always be the case. Keeping the main CPU awake might even, in some cases, mask problems, as some issues may only occur when certain power domains are powered down.
* Printing using the bit-banged UART driver: the ULP RISC-V component comes with a low-speed bit-banged UART TX driver that can be used for printing information independently of the main CPU state. See :example:`system/ulp_riscv/uart_print` for an example of how to use this driver.
* Trap signal: the ULP RISC-V has a hardware trap that will trigger under certain conditions, e.g. illegal instruction. This will cause the main CPU to be woken up with the wake-up cause :cpp:enumerator:`ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG`.
Application Examples
--------------------
* ULP RISC-V Coprocessor polls GPIO while main CPU is in deep sleep: :example:`system/ulp_riscv/gpio`.
* ULP RISC-V Coprocessor uses bit-banged UART tx to print: :example:`system/ulp_riscv/uart_print`.
* ULP RISC-V Coprocessor reads external temperature sensor while main CPU is in deep sleep: :example:`system/ulp_riscv/ds18b20_onewire`.
API Reference

View File

@ -215,3 +215,7 @@ examples/system/ulp_riscv/gpio_interrupt:
- if: IDF_TARGET in ["esp32s2", "esp32s3"]
temporary: true
reason: the other targets are not tested yet
examples/system/ulp_riscv/uart_print:
enable:
- if: SOC_RISCV_COPROC_SUPPORTED == 1

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ulp_riscv_example)

View File

@ -0,0 +1,75 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# ULP-RISC-V bitbanged UART transmit:
This example demonstrates how to program the ULP-RISC-V coprocessor to bitbang an UART TX line. This can be used to log output directly from the ULP, even when the main CPU is in deep sleep.
## How to use example
### Hardware Required
The example can be run on any ESP32-S2 or ESP32-S3 based development board connected to a computer with a single USB cable for flashing and monitoring. The external interface should have 3.3V outputs. You may use e.g. 3.3V compatible USB-to-Serial dongle.
### Setup the Hardware
Connect the external serial interface to the board as follows.
```
-----------------------------------------------------------------------------------------
| Target chip Interface | Kconfig Option | Default ESP Pin | External UART Pin |
| ----------------------|--------------------|----------------------|--------------------
| Transmit Data (TxD) | EXAMPLE_UART_TXD | GPIO4 | RxD |
| Ground | n/a | GND | GND |
-----------------------------------------------------------------------------------------
```
Note: Some GPIOs can not be used with certain chips because they are reserved for internal use. Please refer to UART documentation for selected target.
### Configure the project
Use the command below to configure project using Kconfig menu as showed in the table above. The baudrate can be changed with by configuring `ULP_RISCV_UART_BAUDRATE` under `components` -> `ULP`, but it is recommended to keep the baudrate low, as driver will not be able to accurately output higher rates.
```
idf.py menuconfig
```
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
Output from main CPU:
```
Not a ULP wakeup, initializing it!
Entering in deep sleep
```
Output from ULP-RISCV:
``
Hello World from ULP-RISCV!
Cnt: 0x00000000
Hello World from ULP-RISCV!
Cnt: 0x00000001
Hello World from ULP-RISCV!
Cnt: 0x00000002
Hello World from ULP-RISCV!
Cnt: 0x00000003
Hello World from ULP-RISCV!
Cnt: 0x00000004
``
## Troubleshoot
If the received output contains garbled characters then try to lower the baudrate (ULP_RISCV_UART_BAUDRATE) using `menuconfig`.

View File

@ -0,0 +1,27 @@
# Set usual component variables
set(COMPONENT_SRCS "ulp_riscv_example_main.c")
set(COMPONENT_ADD_INCLUDEDIRS "")
set(COMPONENT_REQUIRES soc nvs_flash ulp driver)
register_component()
#
# ULP support additions to component CMakeLists.txt.
#
# 1. The ULP app name must be unique (if multiple components use ULP).
set(ulp_app_name ulp_${COMPONENT_NAME})
#
# 2. Specify all C and Assembly source files.
# Files should be placed into a separate directory (in this case, ulp/),
# which should not be added to COMPONENT_SRCS.
set(ulp_riscv_sources "ulp/main.c")
#
# 3. List all the component source files which include automatically
# generated ULP export file, ${ulp_app_name}.h:
set(ulp_exp_dep_srcs "ulp_riscv_example_main.c")
#
# 4. Call function to build ULP binary and embed in project using the argument
# values above.
ulp_embed_binary(${ulp_app_name} "${ulp_riscv_sources}" "${ulp_exp_dep_srcs}")

View File

@ -0,0 +1,13 @@
menu "ULP-RISCV UART TX Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_UART_TXD
int "UART TXD pin number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 4
help
GPIO number for UART TX pin. See UART documentation for more information
about available pin numbers for UART.
endmenu

View File

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* ULP-RISC-V example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
This code runs on ULP-RISC-V coprocessor
*/
#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_print.h"
#include "ulp_riscv_uart_ulp_core.h"
#include "sdkconfig.h"
static ulp_riscv_uart_t s_print_uart;
int main (void)
{
ulp_riscv_uart_cfg_t cfg = {
.tx_pin = CONFIG_EXAMPLE_UART_TXD,
};
ulp_riscv_uart_init(&s_print_uart, &cfg);
ulp_riscv_print_install((putc_fn_t)ulp_riscv_uart_putc, &s_print_uart);
int cnt = 0;
while(1) {
ulp_riscv_print_str("Hello World from ULP-RISCV!\n");
ulp_riscv_print_str("Cnt: 0x");
ulp_riscv_print_hex(cnt);
ulp_riscv_print_str("\n");
cnt++;
ulp_riscv_delay_cycles(1000 * ULP_RISCV_CYCLES_PER_MS);
}
}

View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* ULP riscv uart print example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "esp_sleep.h"
#include "ulp_riscv.h"
#include "ulp_main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[] asm("_binary_ulp_main_bin_end");
static void init_ulp_program(void);
void app_main(void)
{
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
/* not a wakeup from ULP, load the firmware */
if (cause != ESP_SLEEP_WAKEUP_ULP) {
printf("Not a ULP-RISC-V wakeup, initializing it! \n");
init_ulp_program();
}
/* ULP Risc-V read and detected a change in GPIO_0, prints */
if (cause == ESP_SLEEP_WAKEUP_ULP) {
printf("ULP-RISC-V woke up the main CPU! \n");
}
/* Go back to sleep, only the ULP Risc-V will run */
printf("Entering in deep sleep\n\n");
/* Small delay to ensure the messages are printed */
vTaskDelay(100);
ESP_ERROR_CHECK( esp_sleep_enable_ulp_wakeup());
esp_deep_sleep_start();
}
static void init_ulp_program(void)
{
esp_err_t err = ulp_riscv_load_binary(ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start));
ESP_ERROR_CHECK(err);
/* The first argument is the period index, which is not used by the ULP-RISC-V timer
* The second argument is the period in microseconds, which gives a wakeup time period of: 20ms
*/
ulp_set_wakeup_period(0, 20000);
/* Start the program */
err = ulp_riscv_run();
ESP_ERROR_CHECK(err);
}

View File

@ -0,0 +1,9 @@
# Enable ULP
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_RISCV=y
CONFIG_ULP_COPROC_RESERVE_MEM=4096
# Set log level to Warning to produce clean output
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_BOOTLOADER_LOG_LEVEL=2
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
CONFIG_LOG_DEFAULT_LEVEL=2