Merge branch 'staging/asm_dedic_gpio_example' into 'master'

Dedicated/Fast GPIO: add examples implementing software I2C, UART and SPI on RISC-V targets

Closes IDF-5936

See merge request espressif/esp-idf!20921
This commit is contained in:
Omar Chebib 2022-11-23 14:19:01 +08:00
commit 7baa63cbc2
37 changed files with 1928 additions and 7 deletions

View File

@ -349,6 +349,25 @@ err:
return ret;
}
esp_err_t dedic_gpio_get_out_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(bundle && offset, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
*offset = bundle->out_offset;
err:
return ret;
}
esp_err_t dedic_gpio_get_in_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(bundle && offset, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
*offset = bundle->in_offset;
err:
return ret;
}
void dedic_gpio_bundle_write(dedic_gpio_bundle_handle_t bundle, uint32_t mask, uint32_t value)
{
// For performance reasons, we don't want to check the validation of parameters here

View File

@ -52,13 +52,13 @@ typedef struct {
esp_err_t dedic_gpio_new_bundle(const dedic_gpio_bundle_config_t *config, dedic_gpio_bundle_handle_t *ret_bundle);
/**
* @brief Destory GPIO bundle
* @brief Destroy GPIO bundle
*
* @param[in] bundle Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"
* @return
* - ESP_OK: Destory GPIO bundle successfully
* - ESP_ERR_INVALID_ARG: Destory GPIO bundle failed because of invalid argument
* - ESP_FAIL: Destory GPIO bundle failed because of other error
* - ESP_OK: Destroy GPIO bundle successfully
* - ESP_ERR_INVALID_ARG: Destroy GPIO bundle failed because of invalid argument
* - ESP_FAIL: Destroy GPIO bundle failed because of other error
*/
esp_err_t dedic_gpio_del_bundle(dedic_gpio_bundle_handle_t bundle);
@ -81,6 +81,25 @@ esp_err_t dedic_gpio_get_out_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *m
esp_err_t dedic_gpio_get_in_mask(dedic_gpio_bundle_handle_t bundle, uint32_t *mask);
/**@}*/
/**@{*/
/**
* @brief Get the channel offset of the GPIO bundle
*
* A GPIO bundle maps the GPIOS of a particular direction to a consecutive set of channels within
* a particular GPIO bank of a particular CPU. This function returns the offset to
* the bundle's first channel of a particular direction within the bank.
*
* @param[in] bundle Handle of GPIO bundle that returned from "dedic_gpio_new_bundle"
* @param[out] offset Offset value to the first channel of a specific direction (in or out)
* @return
* - ESP_OK: Get channel offset successfully
* - ESP_ERR_INVALID_ARG: Get channel offset failed because of invalid argument
* - ESP_FAIL: Get channel offset failed because of other error
*/
esp_err_t dedic_gpio_get_out_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset);
esp_err_t dedic_gpio_get_in_offset(dedic_gpio_bundle_handle_t bundle, uint32_t *offset);
/**@}*/
/**
* @brief Write value to GPIO bundle
*

View File

@ -111,7 +111,6 @@ api-reference/peripherals/sdm
api-reference/peripherals/touch_pad
api-reference/peripherals/adc_calibration
api-reference/peripherals/ds
api-reference/peripherals/dedic_gpio
api-reference/peripherals/sd_pullup_requirements
api-reference/peripherals/index
api-reference/peripherals/sdmmc_host

View File

@ -95,7 +95,7 @@ For advanced users, they can always manipulate the GPIOs by writing assembly cod
- Clear bits of GPIO: ``clr_bit_gpio_out imm[7:0]``
- Note: Immediate value width depends on the number of dedicated GPIO channels
.. only:: esp32c2 or esp32c3
.. only:: esp32c2 or esp32c3 or esp32c6
- Set bits of GPIO: ``csrrsi rd, csr, imm[4:0]``
- Clear bits of GPIO: ``csrrci rd, csr, imm[4:0]``
@ -109,7 +109,9 @@ For advanced users, they can always manipulate the GPIOs by writing assembly cod
For details of supported dedicated GPIO instructions, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > *Processor Instruction Extensions (PIE) (to be added later)* [`PDF <{IDF_TARGET_TRM_EN_URL}#pie>`__].
.. only:: esp32c2 or esp32c3
.. only:: esp32c2 or esp32c3 or esp32c6
Code examples for manipulating dedicated GPIOs from assembly are provided in the :example:`peripherals/dedicated_gpio` directory of ESP-IDF examples. These examples show how to emulate a UART, an I2C and an SPI bus in assembly thanks to dedicated GPIOs.
For details of supported dedicated GPIO instructions, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > *ESP-RISC-V CPU* [`PDF <{IDF_TARGET_TRM_EN_URL}#riscvcpu>`__].

View File

@ -0,0 +1,25 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
examples/peripherals/dedicated_gpio/soft_i2c:
disable:
- if: SOC_DEDICATED_GPIO_SUPPORTED != 1
temporary: false
reason: Target doesn't support dedicated GPIO
examples/peripherals/dedicated_gpio/soft_spi:
disable:
- if: SOC_DEDICATED_GPIO_SUPPORTED != 1
temporary: false
reason: Target doesn't support dedicated GPIO
- if: IDF_TARGET in ["esp32s2", "esp32s3"]
temporary: true
reason: Xtensa targets not supported yet
examples/peripherals/dedicated_gpio/soft_uart:
disable:
- if: SOC_DEDICATED_GPIO_SUPPORTED != 1
temporary: false
reason: Target doesn't support dedicated GPIO
- if: IDF_TARGET in ["esp32s2", "esp32s3"]
temporary: true
reason: Xtensa targets not supported yet

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(soft_i2c)

View File

@ -0,0 +1,81 @@
| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- | -------- | -------- |
# Example: Software I2C Master via Dedicated/Fast GPIOs
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example shows how to configure and use dedicated/fast GPIOs to emulate an I2C master. The I2C master requires 2 bi-directional pins for SCL (clock) and SDA (data) operating in open-drain mode, thus requires further configuration compared to other buses like SPI or UART.
### Data sent on the bus
After configuring the I/Os, the example performs a write-read transaction on the bus.
The device address, the write buffer and read buffer size can be configured in the function `emulate_i2c` in the main source file.
It is also possible to provide a NULL as a write or read buffer in order to perform a single `read` or `write` on the bus.
### Note on function placement
Due to the tight timing requirements of SW bit banging, the `emulate_i2c_transfer` function has been placed in IRAM. This circumvents the timing variations caused by cache misses/invalidation when executing a function placed in flash.
## How to use example
### Hardware Required
* A development board with an Espressif SoC
* A USB cable for Power supply and programming
* Some jumper wires to connect the I2C device to the development board.
### Configure the project
#### Watchdog timers
Due to the strict timing requirements of the I2C emulation, the I2C emulation will cause the CPU to disable interrupts while bit banging in order to ensure it is not preempted (by a task or ISR). Therefore, this example disables both the *Interrupt Watchdog* and *Task Watchdog* by default (see `sdkconfig.defaults`) to prevent the non-preemptive emulation from triggering either watchdog timer. Note that this is normally not permitted by ESP-IDF.
#### Bus frequency
The SCL frequency implemented is around 200-300KHz. Indeed, the example uses the internal pull-ups for SCL and SDA lines. These resistors are rather high, more than 10kOhms. If you wish to reach higher frequencies, please add external pull-up resistors (1kOhms or 2kOhms) and update `set_scl` and `set_sda` functions, in `i2c.c`, to reduce the delay.
### Build and flash the project
* Set the target of the project to a supported one. For example:
```
idf.py set-target esp32c3
```
* Configure the GPIOs to use as the software I2C bus in the menu `Example configuration` of `menuconfig`. By default, SCL and SDA are assigned to GPIOs 0, 2 respectively for RISC-V.
```
idf.py menuconfig
```
* Optional: update the `device_address`, write/read buffer array accordingly in the source code.
* Compile the example:
```
idf.py build
```
* Flash the example:
```
idf.py flash
```
* Plug the I2C device on the pins defined by the configuration.
* Power on the board and check the output:
```
idf.py monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
As you run the example, on success, you will see the following log:
```
Bytes received from the I2C device:
ff, ff
Emulation terminated: success
```
Of course, the bytes printed on screen depend on the data received from the connected I2C device.
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "soft_i2c_master.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver
LDFRAGMENTS linker.lf)

View File

@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Enumeration for the usable frequencies by the software I2C master bus
*/
typedef enum {
SOFT_I2C_100KHZ,
SOFT_I2C_200KHZ,
SOFT_I2C_300KHZ,
SOFT_I2C_FREQ_END
} soft_i2c_master_freq_t;
/**
* @brief Structure defining the configuration for the software I2C master bus
*/
typedef struct {
uint32_t scl_pin;
uint32_t sda_pin;
soft_i2c_master_freq_t freq;
} soft_i2c_master_config_t;
/**
* @brief Abstract type representing a software I2C bus.
*/
typedef struct i2c_master_bus_impl_t* soft_i2c_master_bus_t;
/**
* @brief Create and configure the software I2C bus.
*
* @param config Configuration to apply to the initialized bus.
* @param bus Output structure representing the freshly initialized software I2C bus.
*
* @return ESP_OK on success
*/
esp_err_t soft_i2c_master_new(soft_i2c_master_config_t *config, soft_i2c_master_bus_t *bus);
/**
* @brief Delete a previously initialized I2C software bus.
*
* @param bus Bus to delete, must have been initialized with `soft_i2c_master_new` first.
*
* @return ESP_OK on success
*/
esp_err_t soft_i2c_master_del(soft_i2c_master_bus_t bus);
/**
* @brief Perform a write to the given device on the software I2C bus.
*
* @param bus Software I2C bus to perform the transfer on.
* @param device_address 7-bit device address to communicate with.
* @param write_buffer Buffer containing the bytes to send on the buffer. Must not be NULL.
* @param write_size Size of the write buffer. Must not be 0.
*
* @return ESP_OK on success
*/
esp_err_t soft_i2c_master_write(soft_i2c_master_bus_t bus,
uint8_t device_address,
const uint8_t* write_buffer, size_t write_size);
/**
* @brief Perform a read from the given device on the software I2C bus.
*
* @param bus Software I2C bus to perform the transfer on.
* @param device_address 7-bit device address to communicate with.
* @param read_buffer Buffer that will contain the bytes received. Must not be NULL.
* @param read_size Size of the read buffer. Must not be 0.
*
* @return ESP_OK on success
*/
esp_err_t soft_i2c_master_read(soft_i2c_master_bus_t bus,
uint8_t device_address,
uint8_t* read_buffer, size_t read_size);
/**
* @brief Perform a write followed by a read to the given device on the software I2C bus.
*
* @param bus Software I2C bus to perform the transfer on.
* @param device_address 7-bit device address to communicate with.
* @param read_buffer Buffer that will contain the bytes received. Must not be NULL.
*
* @return ESP_OK on success
*/
esp_err_t soft_i2c_master_write_read(soft_i2c_master_bus_t bus,
uint8_t device_address,
const uint8_t* write_buffer, size_t write_size,
uint8_t* read_buffer, size_t read_size);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,4 @@
[mapping:main_default]
archive: libsoft_i2c_master.a
entries:
* (noflash)

View File

@ -0,0 +1,355 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_rom_sys.h"
#include "esp_check.h"
#include "driver/dedic_gpio.h"
#include "driver/gpio.h"
#include "soft_i2c_master.h"
#define ERR_CHECK_OR_GOTO(ret, label) do { if((ret) != ESP_OK ) goto label; } while (0)
#define I2C_SCL_MASK (1 << 0)
#define I2C_SDA_MASK (1 << 1)
#define I2C_BOTH_MASK ((I2C_SDA_MASK) | (I2C_SCL_MASK))
#define I2C_SCL_BIT 0
#define I2C_SDA_BIT 1
/* Forward declaration of static functions */
static uint32_t freq_to_delay(soft_i2c_master_freq_t freq);
static esp_err_t emulate_i2c_transfer(uint8_t device_address,
const uint8_t* write_buffer, uint32_t write_size,
uint8_t* read_buffer, uint32_t read_size,
soft_i2c_master_bus_t bus);
/* Mutex required to enter critical sections */
static portMUX_TYPE g_lock = portMUX_INITIALIZER_UNLOCKED;
const char* __attribute__((used)) SOFT_I2C_MASTER_TAG = "soft_i2c_master";
/***** Public API implementation *****/
struct i2c_master_bus_impl_t {
uint32_t scl_pin;
uint32_t sda_pin;
uint32_t freq_delay;
dedic_gpio_bundle_handle_t bundle;
};
esp_err_t soft_i2c_master_new(soft_i2c_master_config_t *config, soft_i2c_master_bus_t *bus)
{
esp_err_t ret;
struct i2c_master_bus_impl_t *bus_impl = NULL;
/* Check the parameters */
ESP_GOTO_ON_FALSE(config != NULL && bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG,
"Parameters must not be NULL");
ESP_GOTO_ON_FALSE(config->freq < SOFT_I2C_FREQ_END, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG,
"Invalid frequency");
/* Emulate I2C bus thanks to fast GPIOs. Both SCL and SDA are bi-directional pins and in open-drain mode.
* Using internal pull-ups will let SCL be clocked at around 100KHz. Using external pull-ups
* will let the clock be faster. */
const uint32_t scl = config->scl_pin;
const uint32_t sda = config->sda_pin;
ret = gpio_set_direction(scl, GPIO_MODE_INPUT_OUTPUT_OD);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_direction(sda, GPIO_MODE_INPUT_OUTPUT_OD);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_pull_mode(scl, GPIO_PULLUP_ONLY);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_pull_mode(sda, GPIO_PULLUP_ONLY);
ERR_CHECK_OR_GOTO(ret, error);
/* Configure both I/Os as fast GPIOs */
int bidir_array[] = { scl, sda };
dedic_gpio_bundle_config_t bidir_config = {
.gpio_array = bidir_array,
.array_size = 2,
.flags = {
.out_en = 1,
.in_en = 1
}
};
/* Allocate the master bus structure now that we need it */
bus_impl = malloc(sizeof(struct i2c_master_bus_impl_t));
ESP_GOTO_ON_FALSE(bus_impl != NULL, ESP_ERR_NO_MEM, error, SOFT_I2C_MASTER_TAG, "No more memory available in the system");
/* Initialize the dedicated GPIO bundle and fill the bus structure */
ret = dedic_gpio_new_bundle(&bidir_config, &bus_impl->bundle);
ERR_CHECK_OR_GOTO(ret, error);
bus_impl->scl_pin = config->scl_pin;
bus_impl->sda_pin = config->sda_pin;
bus_impl->freq_delay = freq_to_delay(config->freq);
*bus = bus_impl;
/* Set both lines to high-impedance, which is I2C idle state */
dedic_gpio_bundle_write(bus_impl->bundle, 3, 3);
return ret;
error:
if (bus_impl != NULL) {
free(bus_impl);
}
return ret;
}
esp_err_t soft_i2c_master_del(soft_i2c_master_bus_t bus)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG,
"Bus must not be NULL");
dedic_gpio_del_bundle(bus->bundle);
free(bus);
error:
return ret;
}
esp_err_t soft_i2c_master_write(soft_i2c_master_bus_t bus,
uint8_t device_address,
const uint8_t* write_buffer, size_t write_size)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(device_address < 0x80, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Invalid device address");
ESP_GOTO_ON_FALSE(write_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Buffer must not be NULL");
ESP_GOTO_ON_FALSE(write_size != 0, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Buffer size must not be 0");
portENTER_CRITICAL(&g_lock);
ret = emulate_i2c_transfer(device_address,
write_buffer, write_size,
NULL, 0,
bus);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}
esp_err_t soft_i2c_master_read(soft_i2c_master_bus_t bus,
uint8_t device_address,
uint8_t* read_buffer, size_t read_size)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(device_address < 0x80, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Invalid device address");
ESP_GOTO_ON_FALSE(read_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Buffer must not be NULL");
portENTER_CRITICAL(&g_lock);
ret = emulate_i2c_transfer(device_address,
NULL, 0,
read_buffer, read_size,
bus);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}
esp_err_t soft_i2c_master_write_read(soft_i2c_master_bus_t bus,
uint8_t device_address,
const uint8_t* write_buffer, size_t write_size,
uint8_t* read_buffer, size_t read_size)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(device_address < 0x80, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG, "Invalid device address");
ESP_GOTO_ON_FALSE(write_buffer != NULL && read_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG,
"Buffers must not be NULL");
ESP_GOTO_ON_FALSE(write_size != 0 && read_size != 0, ESP_ERR_INVALID_ARG, error, SOFT_I2C_MASTER_TAG,
"Buffers sizes must not be 0");
portENTER_CRITICAL(&g_lock);
ret = emulate_i2c_transfer(device_address,
write_buffer, write_size,
read_buffer, read_size,
bus);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}
/***** Private implementation *****/
static uint32_t freq_to_delay(soft_i2c_master_freq_t freq)
{
switch(freq) {
case SOFT_I2C_100KHZ: return 3;
case SOFT_I2C_200KHZ: return 2;
case SOFT_I2C_300KHZ: return 1;
default:
assert(false);
return 0;
}
}
static inline void set_scl(dedic_gpio_bundle_handle_t bidir_bundle, int value, uint32_t delay)
{
dedic_gpio_bundle_write(bidir_bundle, I2C_SCL_MASK, value << I2C_SCL_BIT);
esp_rom_delay_us(delay);
}
static inline void set_sda(dedic_gpio_bundle_handle_t bidir_bundle, int value, uint32_t delay)
{
dedic_gpio_bundle_write(bidir_bundle, I2C_SDA_MASK, value << I2C_SDA_BIT);
esp_rom_delay_us(delay);
}
static inline int get_sda(dedic_gpio_bundle_handle_t bidir_bundle)
{
uint32_t offset = 0;
/* We need the offset of the SDA pin in the bundle */
dedic_gpio_get_in_offset(bidir_bundle, &offset);
uint32_t in = dedic_gpio_bundle_read_in(bidir_bundle);
return in >> (offset + I2C_SDA_BIT);
}
static inline void emulate_start(soft_i2c_master_bus_t bus)
{
dedic_gpio_bundle_handle_t bidir_bundle = bus->bundle;
const uint32_t delay = bus->freq_delay;
/* A Start consists in pulling SDA low when SCL is high. */
set_scl(bidir_bundle, 1, delay);
/* If SDA is low, pull it high first */
set_sda(bidir_bundle, 1, delay);
set_sda(bidir_bundle, 0, delay);
}
static inline void emulate_stop(soft_i2c_master_bus_t bus)
{
dedic_gpio_bundle_handle_t bidir_bundle = bus->bundle;
const uint32_t delay = bus->freq_delay;
/* Stop consists in pulling SDA high while SCL is also high. First, pull both low, make sure
* it is not detected as a "start" so let's do it one by one. */
set_scl(bidir_bundle, 0, delay);
set_sda(bidir_bundle, 0, delay);
/* Pull SCL high */
set_scl(bidir_bundle, 1, delay);
/* Pull SDA high */
set_sda(bidir_bundle, 1, delay);
}
static inline int emulate_write_byte(soft_i2c_master_bus_t bus, uint_fast8_t byte)
{
dedic_gpio_bundle_handle_t bidir_bundle = bus->bundle;
const uint32_t delay = bus->freq_delay;
for (int i = 7; i >= 0; i--) {
const uint_fast8_t bit = (byte >> i) & 1;
/* Set SCL to low */
set_scl(bidir_bundle, 0, delay);
/* Set SDA value now */
set_sda(bidir_bundle, bit, delay);
/* Set SCL to high */
set_scl(bidir_bundle, 1, delay);
}
/* Send one more bit to get the ACK/NACK */
set_scl(bidir_bundle, 0, delay);
set_sda(bidir_bundle, 1, delay);
set_scl(bidir_bundle, 1, delay);
/* Get the SDA bit */
int ret = get_sda(bidir_bundle) == 0;
/* Pull clock low, to tell the device to stop pulling SDA down */
set_scl(bidir_bundle, 0, delay);
return ret;
}
static inline uint8_t emulate_read_byte(soft_i2c_master_bus_t bus, int send_ack)
{
uint8_t result = 0;
uint32_t in = 0;
dedic_gpio_bundle_handle_t bidir_bundle = bus->bundle;
const uint32_t delay = bus->freq_delay;
/* Set SCL to low as we are going to put SDA in high-impedance */
set_scl(bidir_bundle, 0, delay);
set_sda(bidir_bundle, 1, delay);
for (int i = 7; i >= 0; i--)
{
/* Set SCL to low */
set_scl(bidir_bundle, 0, delay);
/* Get SDA value now and store it in the final result */
in = get_sda(bidir_bundle);
result = (result << 1) | in;
/* Set SCL to high */
set_scl(bidir_bundle, 1, delay);
}
/* Send one more bit to set ACK/NACK */
set_scl(bidir_bundle, 0, delay);
set_sda(bidir_bundle, !send_ack, delay);
set_scl(bidir_bundle, 1, delay);
return result;
}
static esp_err_t emulate_i2c_transfer(uint8_t device_address,
const uint8_t* write_buffer, uint32_t write_size,
uint8_t* read_buffer, uint32_t read_size,
soft_i2c_master_bus_t bus)
{
esp_err_t ret = ESP_OK;
dedic_gpio_bundle_handle_t bidir_bundle = bus->bundle;
/* Set both pins to high to start */
dedic_gpio_bundle_write(bidir_bundle, I2C_BOTH_MASK, 0x3);
/* Perform a start/write on the bus */
if (write_buffer != NULL && write_size != 0) {
emulate_start(bus);
int ack = emulate_write_byte(bus, device_address << 1);
if (!ack) {
ret = ESP_ERR_NOT_FOUND;
}
for (int i = 0; i < write_size && ret == ESP_OK; i++) {
/* Check the ACK returned by the device */
ack = emulate_write_byte(bus, write_buffer[i]);
if (!ack) {
ret = ESP_FAIL;
}
}
}
/* Perform a (re)start/read on the bus */
if (ret == ESP_OK && read_buffer != NULL && read_size != 0) {
emulate_start(bus);
int ack = emulate_write_byte(bus, ((device_address << 1) | 1));
if (!ack) {
ret = ESP_ERR_NOT_FOUND;
} else {
for (int i = 0; i < read_size; i++) {
/* We must send an ACK after each byte read, except the last one */
const int send_ack = i != (read_size - 1);
read_buffer[i] = emulate_read_byte(bus, send_ack);
}
}
}
emulate_stop(bus);
return ret;
}

View File

@ -0,0 +1,5 @@
set(srcs "soft_i2c_master_main.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "."
PRIV_REQUIRES driver soft_i2c_master)

View File

@ -0,0 +1,21 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EXAMPLE_TARGET_SCL
int "GPIO pin for SCL"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 0 if IDF_TARGET_ARCH_RISCV
default 16
help
GPIO pin number to be used as I2C SCL.
config EXAMPLE_TARGET_SDA
int "GPIO pin for SDA"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 2 if IDF_TARGET_ARCH_RISCV
default 17
help
GPIO pin number to be used as I2C SDA.
endmenu

View File

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "soft_i2c_master.h"
/* I2C device address to communicate with */
#define I2C_DEVICE_ADDRESS 0x20
const char* EXAMPLE_TAG = "soft_i2c_master";
void app_main(void)
{
esp_err_t ret = ESP_OK;
soft_i2c_master_bus_t bus = NULL;
soft_i2c_master_config_t config = {
.scl_pin = CONFIG_EXAMPLE_TARGET_SCL,
.sda_pin = CONFIG_EXAMPLE_TARGET_SDA,
.freq = SOFT_I2C_100KHZ
};
/* Initialize and configure the software I2C bus */
ret = soft_i2c_master_new(&config, &bus);
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error configuring software I2C");
/* Write the register 0 of our device, set it to 0x42 */
uint8_t write_buffer[] = { 0x0, 0x42 };
ret = soft_i2c_master_write(bus, I2C_DEVICE_ADDRESS, write_buffer, sizeof(write_buffer));
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error writing to the I2C device");
/* Read from the device. The register that will be read cannot be pre-determined, it will depend on the device's
* internal counter. Let's simply make sure the function returns correctly. */
uint8_t read_buffer[] = { 0x0 };
ret = soft_i2c_master_read(bus, I2C_DEVICE_ADDRESS, read_buffer, sizeof(read_buffer));
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error reading from the I2C device");
/* Finally, read the back the first register (0) that we wrote earlier. */
write_buffer[0] = 0;
ret = soft_i2c_master_write_read(bus, I2C_DEVICE_ADDRESS, write_buffer, 1, read_buffer, 1);
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error reading a register from the I2C device");
ESP_LOGI(EXAMPLE_TAG, "I2C transfers succeeded, received byte 0x%02x", read_buffer[0]);
error:
if (bus != NULL) {
soft_i2c_master_del(bus);
}
if (ret != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "An error occurred while communicating with the I2C device");
}
}

View File

@ -0,0 +1,3 @@
# Check the README.md file for more info about why we need to disable these options
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT_EN=n

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(soft_spi)

View File

@ -0,0 +1,75 @@
| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C6 |
| ----------------- | -------- | -------- | -------- |
# Example: SPI software emulation using dedicated/fast GPIOs
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This test code shows how to configure and use dedicated/fast GPIOs to emulate a full-duplex SPI bus. The ESP target will act as a master, so it will require 3 output pins (for the clock, MOSI and chip select) and 1 input pin (for MISO).
### Data sent on the bus
After configuring the I/Os, the example writes a buffer on MOSI line while receiving bytes, at the same time, on MISO line. The buffer to output on the bus can be modified in the function `emulate_spi` in the main source file.
### Note on routine placement
Due to the tight timing requirements of SW bit banging, the `asm_emulate_spi` function has been placed in IRAM. This circumvents the timing variations caused by cache misses/invalidation when executing a function placed in flash.
## How to use example
### Hardware Required
* A development board with a RISC-V Espressif SoC (e.g., ESP32-C3 or ESP32-C2)
* A USB cable for Power supply and programming
* Some jumper wires to connect the SPI device to the development board.
### Configure the project
#### Watchdog timers
Due to the strict timing requirements of the SPI emulation, the SPI emulation will cause the CPU to disable interrupts while bit banging in order to ensure it is not preempted (by a task or ISR). Therefore, this example disables both the *Interrupt Watchdog* and *Task Watchdog* by default (see `sdkconfig.defaults`) to prevent the non-preemptive emulation from triggering either watchdog timer. Note that this is normally not permitted by ESP-IDF.
### Build and flash the project
* Set the target of the project to a RISC-V-based one. For example:
```
idf.py set-target esp32c3
```
* Configure the GPIOs to use as the software SPI bus in the menu `Example configuration` of `menuconfig`. By default, clock, MOSI, chip select and MISO are assigned to GPIOs 0, 2, 3 and 4 respectively.
```
idf.py menuconfig
```
* Optional: change the array `tx_buffer` containing data to be outputted on MOSI line.
* Compile the example:
```
idf.py build
```
* Flash the example:
```
idf.py flash
```
* Plug the SPI device on the pins defined by the configuration.
* Power on the board and check the output:
```
idf.py monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
As you run the example, on success, you will see the following log:
```
Bytes received from the SPI device:
ff, ff, ff, ff
Emulation terminated: success
```
Of course, the bytes printed on screen depend on the data received from the connected SPI device.
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,13 @@
set(srcs "soft_spi.c")
if(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "riscv/soft_spi.S")
elseif(CONFIG_IDF_TARGET_ARCH_XTENSA)
message(FATAL_ERROR "Xtensa targets not supported yet")
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver
LDFRAGMENTS linker.lf)

View File

@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Structure defining the configuration for the software SPI bus
*/
typedef struct {
uint32_t clk_pin;
uint32_t mosi_pin;
uint32_t miso_pin;
uint32_t cs_pin;
} soft_spi_config_t;
/**
* @brief Abstract type representing a software SPI bus.
*/
typedef struct soft_spi_bus_impl_t* soft_spi_bus_t;
/**
* @brief Create and configure the software SPI bus.
*
* @param config Configuration to apply to the initialized bus.
* @param bus Output structure representing the freshly initialized software SPI bus.
*
* @return ESP_OK on success
*/
esp_err_t soft_spi_new(soft_spi_config_t *config, soft_spi_bus_t *bus);
/**
* @brief Delete a previously initialized software SPI bus.
*
* @param bus Bus to delete, must have been initialized with `soft_spi_new` first.
*
* @return ESP_OK on success
*/
esp_err_t soft_spi_del(soft_spi_bus_t bus);
/**
* @brief Send the given bytes on the software SPI bus.
*
* @param bus Software SPI bus to send data on.
* @param write_buffer Buffer containing the bytes to send on the buffer. Must not be NULL.
* @param read_buffer Buffer to store the received bytes in. Can be NULL.
* @param buf_size Size of the given buffers. Must not be 0.
*
* @return ESP_OK on success
*/
esp_err_t soft_spi_transfer(soft_spi_bus_t bus,
const uint8_t* write_buffer, uint8_t* read_buffer,
size_t buf_size);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,4 @@
[mapping:main_default]
archive: libsoft_spi.a
entries:
* (noflash)

View File

@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
/* RISC-V fast GPIO special registers, taken from "hal/dedic_gpio_cpu_ll.h" */
#define CSR_GPIO_IN_USER 0x804
#define CSR_GPIO_OUT_USER 0x805
.section .text
/**
* @brief Routine to emulate SPI thanks to (fast) GPIOS.
* All Clock, MOSI, Chip Select and MISO must have been assigned beforehand to a bit in the GPIO_OUT_USER
*
* @param a0 tx Bytes to send on MOSI
* @param a1 rx Buffer to store the bytes received on MISO
* @param a2 size Size of the transaction
* @param a3 Offset of Clock I/O in the dedicated GPIO register
* @param a4 Offset of MOSI I/O in the dedicated GPIO register
* @param a5 Offset of CS I/O in the dedicated GPIO register
* @param a6 Offset of MISO I/O in the dedicated GPIO register
*
* The C signature of this routine would be:
* void asm_emulate_spi(const uint8_t* tx, uint8_t* rx, uint32_t size,
* uint32_t clock_bit, uint32_t mosi_bit, uint32_t cs_bit, uint32_t miso_bit);
*/
.global asm_emulate_spi_shift_byte
.type asm_emulate_spi_shift_byte, @function
asm_emulate_spi_shift_byte:
/* Convert the offset/bit to a mask for the clock and chip select */
li t0, 1
sll a5, t0, a5
sll a3, t0, a3
/* Move a0 (TX) to t6 as we will use a0 as a parameter and a return value for subroutines */
mv t6, a0
/* Start by asserting chip select (bit in a5), setting it to 0 */
csrrc zero, CSR_GPIO_OUT_USER, a5
/* Reading the bytes one by one, this is less efficient but simpler. */
spi_process_byte:
lb a0, (t6)
/* Output the byte while reading MISO line. Byte read on MISO is returned in a0.
* No need to use `call` as the subroutine is only called from here. */
j spi_send_receive_byte
spi_send_receive_return:
/* Store the resulted byte in RX buffer if not NULL */
beqz a1, spi_no_store
sb a0, (a1)
/* Point to the next byte in both RX and TX buffers */
addi a1, a1, 1
spi_no_store:
addi t6, t6, 1
/* Decrement the size and continue if not zero */
addi a2, a2, -1
bnez a2, spi_process_byte
/* Wait an additional delay and set Chip select to high again. */
li t6, ((CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ * 25)/100)
spi_wait:
add t6, t6, -1
bnez t6, spi_wait
csrrs zero, CSR_GPIO_OUT_USER, a5
ret
/**
* In theory, we would need to respect the calling convention but as this routine is private and
* won't interact with any C function, we don't need to respect it, so we can only use registers,
* and not the stack.
* It is possible to optimize the speed even more by unrolling the loop inside the routine. Of course,
* this will make the routine much bigger.
*
* @param a0 Byte to send on the bus
* @param a3 Bitmask of Clock I/O in the dedicated GPIO register
* @param a4 Offset of MOSI I/O in the dedicated GPIO register
* @param a6 Offset of MISO I/O in the dedicated GPIO register
*
* The C signature of this routine would be:
* uint8_t spi_send_receive_byte(uint8_t byte, uint32_t clock_bitmask, uint32_t mosi_bit, uint32_t miso_bit);
*/
spi_send_receive_byte:
/* Byte received on MISO will be stored in t3 */
li t3, 0
/* Save the iterations count in t4 */
li t4, 0
/* Maximum iteration count in t5. Pre-define here to make the loop faster. */
li t5, 8
/* Save MOSI bitmask in t0 */
li t0, 1
sll t0, t0, a4
/* Byte to send in a0, registers a1, a2, a3, a4, a5, a6 should not be altered.
* Get the value of a0's the lowest bit, which will be set to MOSI */
andi t1, a0, 1
sll t1, t1, a4
spi_send_receive_bit:
/* Two steps to output the value of a0 lowest bit:
* - Use csrrs to set the pin, if value is 1
* - Use csrrc (below) to set the pin, if value is 0
*/
csrrs zero, CSR_GPIO_OUT_USER, t1
/* Flip the data bit thanks to MOSI bitmask */
xor t1, t1, t0
/* Set Clock pin to 0. Thus, we need to set its bit in t1, use its bitmask for this. */
xor t1, t1, a3
csrrc zero, CSR_GPIO_OUT_USER, t1
/* We have some time here as we need to delay between the clock pulses, while making the time
* after clock is set to high again smaller. Let's prepare the next bit from a0 and decrement
* the iteration count */
srli a0, a0, 1
andi t1, a0, 1
sll t1, t1, a4
/* Add some delay to have a duty cycle approaching the 50% */
.rept 8
nop
.endr
/* Set Clock to 1, without modifying the other bits */
csrrs zero, CSR_GPIO_OUT_USER, a3
/* Sample data coming on the MISO line */
csrr t2, CSR_GPIO_IN_USER
/* Extract the incoming bit and put it in bit 0 */
srl t2, t2, a6
andi t2, t2, 1
/* Put it in t3 now, at the right place (t4-th bit) */
sll t2, t2, t4
or t3, t3, t2
/* Check if there as bits remaining */
addi t4, t4, 1
bne t4, t5, spi_send_receive_bit
/* Set return value in a0 */
mv a0, t3
j spi_send_receive_return

View File

@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "driver/dedic_gpio.h"
#include "driver/gpio.h"
#include "soft_spi.h"
#define ERR_CHECK_OR_GOTO(ret, label) do { if((ret) != ESP_OK ) goto label; } while (0)
/* Forward declaration of static functions */
/**
* @brief Send data on the emulated SPI MOSI while reading at the same time from MISO
* Fast GPIOs are used for the emulation, 4 I/Os are required.
*
* @param tx Buffer containing the bytes to send.
* @param rx Buffer to store the received bytes in.
* @param size Size of both buffers.
*/
void IRAM_ATTR asm_emulate_spi_shift_byte(const uint8_t* tx, uint8_t* rx, uint32_t size,
uint32_t clock_bit, uint32_t mosi_bit, uint32_t cs_bit,
uint32_t miso_bit);
/* Mutex required to enter critical sections */
static portMUX_TYPE g_lock = portMUX_INITIALIZER_UNLOCKED;
const char* __attribute__((used)) SOFT_SPI_TAG = "soft_spi";
struct soft_spi_bus_impl_t {
uint32_t clk_bit;
uint32_t mosi_bit;
uint32_t miso_bit;
uint32_t cs_bit;
dedic_gpio_bundle_handle_t out_bundle;
dedic_gpio_bundle_handle_t in_bundle;
};
esp_err_t soft_spi_new(soft_spi_config_t *config, soft_spi_bus_t *bus)
{
esp_err_t ret;
struct soft_spi_bus_impl_t *bus_impl = NULL;
/* Check the parameters */
ESP_GOTO_ON_FALSE(config != NULL && bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_SPI_TAG,
"Parameters must not be NULL");
const int clk = config->clk_pin;
const int mosi = config->mosi_pin;
const int miso = config->miso_pin;
const int cs = config->cs_pin;
/**
* Create a dedicated GPIO bundle configuration for the SPI output pins: Clock, MOSI, and CS, in this order.
* The order will define the position of each I/O in the bundle, which is important when writing to it and
* reading from it.
*/
int output_array[] = { clk, mosi, cs };
dedic_gpio_bundle_config_t output_config = {
.gpio_array = output_array,
.array_size = 3,
.flags = {
.out_en = 1
}
};
/**
* Same goes for the input pin: MISO.
*/
int input_array[] = { miso };
dedic_gpio_bundle_config_t input_config = {
.gpio_array = input_array,
.array_size = 1,
.flags = {
.in_en = 1
}
};
/* Allocate the master bus structure now that we need it. */
bus_impl = malloc(sizeof(struct soft_spi_bus_impl_t));
ESP_GOTO_ON_FALSE(bus_impl != NULL, ESP_ERR_NO_MEM, error, SOFT_SPI_TAG, "No more memory available in the system");
/* Initialize the dedicated GPIO bundles with the configurations we made above. */
ret = dedic_gpio_new_bundle(&output_config, &bus_impl->out_bundle);
ERR_CHECK_OR_GOTO(ret, error_output);
ret = dedic_gpio_new_bundle(&input_config, &bus_impl->in_bundle);
ERR_CHECK_OR_GOTO(ret, error_input);
/**
* Before executing the assembly routine, get the offset (bit) of each of the pins in the dedicated GPIO
* registers. For example, if CLK pin's offset is 0, then bit 0 of the special register will be assigned
* to it.
*/
ret = dedic_gpio_get_out_offset(bus_impl->out_bundle, &bus_impl->clk_bit);
ERR_CHECK_OR_GOTO(ret, error_offset);
ret = dedic_gpio_get_in_offset(bus_impl->in_bundle, &bus_impl->miso_bit);
ERR_CHECK_OR_GOTO(ret, error_offset);
/* MOSI and CS immediately follow the Clock in the special register. */
bus_impl->mosi_bit = bus_impl->clk_bit + 1;
bus_impl->cs_bit = bus_impl->clk_bit + 2;
/* Return the bus to the user */
*bus = bus_impl;
return ret;
error_offset:
dedic_gpio_del_bundle(bus_impl->in_bundle);
error_input:
dedic_gpio_del_bundle(bus_impl->out_bundle);
error_output:
if (bus_impl != NULL) {
free(bus_impl);
}
error:
return ret;
}
esp_err_t soft_spi_del(soft_spi_bus_t bus)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_SPI_TAG, "Bus must not be NULL");
dedic_gpio_del_bundle(bus->out_bundle);
dedic_gpio_del_bundle(bus->in_bundle);
free(bus);
error:
return ret;
}
esp_err_t soft_spi_transfer(soft_spi_bus_t bus, const uint8_t* write_buffer, uint8_t* read_buffer, size_t buf_size)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(bus != NULL, ESP_ERR_INVALID_ARG, error, SOFT_SPI_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(write_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_SPI_TAG, "Write buffer must not be NULL");
ESP_GOTO_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, error, SOFT_SPI_TAG, "Buffer size must not be 0");
portENTER_CRITICAL(&g_lock);
asm_emulate_spi_shift_byte(write_buffer, read_buffer, buf_size,
/* Provide the offset in special dedicated GPIO register for Clock, MOSI and Clock
* respectively */
bus->clk_bit, bus->mosi_bit, bus->cs_bit, bus->miso_bit);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "soft_spi_master_main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,38 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EMULATE_SPI_GPIO_CLOCK
int "GPIO pin for SPI Clock"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 0 if IDF_TARGET_ARCH_RISCV
default 16
help
GPIO pin number to be used as emulated SPI Clock.
config EMULATE_SPI_GPIO_MOSI
int "GPIO pin for SPI MOSI"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 2 if IDF_TARGET_ARCH_RISCV
default 17
help
GPIO pin number to be used as emulated SPI MOSI (Master Out Slave In).
config EMULATE_SPI_GPIO_CS
int "GPIO pin for SPI Chip Select"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 3 if IDF_TARGET_ARCH_RISCV
default 18
help
GPIO pin number to be used as emulated SPI Chip Select.
config EMULATE_SPI_GPIO_MISO
int "GPIO pin for SPI MISO"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 4 if IDF_TARGET_ARCH_RISCV
default 19
help
GPIO pin number to be used as emulated SPI MISO (Master In Slave Out).
endmenu

View File

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "soft_spi.h"
#define READ_COUNT 16
const char* EXAMPLE_TAG = "soft_spi_master";
void app_main(void)
{
esp_err_t ret = ESP_OK;
const uint8_t write_buffer[] = "Hello, world! This is a message.\n";
uint8_t read_buffer[sizeof(write_buffer)] = { 0 };
soft_spi_bus_t bus = NULL;
soft_spi_config_t config = {
.clk_pin = CONFIG_EMULATE_SPI_GPIO_CLOCK,
.mosi_pin = CONFIG_EMULATE_SPI_GPIO_MOSI,
.miso_pin = CONFIG_EMULATE_SPI_GPIO_MISO,
.cs_pin = CONFIG_EMULATE_SPI_GPIO_CS
};
/* Initialize and configure the software SPI bus */
ret = soft_spi_new(&config, &bus);
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error configuring software SPI");
/* Initiate the transfer. The software SPI is full-duplex, we will send and
* receive data at the same time. */
ret = soft_spi_transfer(bus, write_buffer, read_buffer, sizeof(read_buffer));
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error reading from the software SPI");
printf("SPI transfers succeeded, received bytes: { ");
for (int i = 0; i < sizeof(read_buffer); i++) {
printf("0x%02x ", read_buffer[i]);
}
printf("}\n");
error:
if (bus != NULL) {
soft_spi_del(bus);
}
if (ret != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "An error occurred while transferring date on software SPI bus");
}
}

View File

@ -0,0 +1,3 @@
# Check the README.md file for more info about why we need to disable these options
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT_EN=n

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(soft_uart)

View File

@ -0,0 +1,82 @@
| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C6 |
| ----------------- | -------- | -------- | -------- |
# Example: UART software emulation using dedicated/fast GPIOs
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to emulate a UART bus using dedicated/fast GPIOs. The ESP target (acting as a UART device) will send and receive characters on the UART bus using a TX pin and an RX pin respectively. The TX and RX pins can be configured `menuconfig`.
The baudrate will determine has fast bits are transferred on the bus, and the baudrate is configurable via `menuconfig`. Due to UART being an asynchronous protocol, the timing of I/O read/writes have to be very precise. This example is hard coded to emulates an 8N1 (8-bit characters, no parity bit, 1 stop bit) UART bus, thus this format cannot be configured via `menuconfig`.
### Data sent on the bus
After configuring the I/Os and the baudrate, the example sends characters on the TX line before waiting for characters on the RX line. Reading will stop once `\r` or `\n` is sent.
The message to send first and the size of the receive buffer can be changed in the function `emulate_uart` in the source file.
### Note on routine placement
Due to the tight timing requirements of SW bit banging, the `asm_emulate_uart` function has been placed in IRAM. This circumvents the timing variations caused by cache misses/invalidation when executing a function placed in flash.
## How to use example
### Hardware Required
* A development board with a RISC-V Espressif SoC (e.g., ESP32-C3 or ESP32-C2)
* A USB cable for Power supply and programming
* Some jumper wires to connect the UART to an external UART-to-USB adapter.
### Configure the project
#### Watchdog timers
Due to the strict timing requirements of the UART emulation, the UART emulation will cause the CPU to disable interrupts while bit banging in order to ensure it is not preempted (by a task or ISR). Therefore, this example disables both the *Interrupt Watchdog* and *Task Watchdog* by default (see `sdkconfig.defaults`) to prevent the non-preemptive emulation from triggering either watchdog timer. Note that this is normally not permitted by ESP-IDF.
### Build and flash the project
* Set the target of the project to a RISC-V-based one. For example:
```
idf.py set-target esp32c3
```
* Configure the GPIOs to use as the software UART bus in the menu `Example configuration` of `menuconfig`. By default, TX and RX are assigned to GPIOs 0, 2 respectively.
```
idf.py menuconfig
```
* Optional: update the message to send and the read buffer size accordingly in the source code.
* Compile the example:
```
idf.py build
```
* Flash the example:
```
idf.py flash
```
* Plug the external UART-to-USB adapter on the pins defined by the configuration and open a monitor on the computer it is connected to.
* Power on the Espressif board and check the output:
```
idf.py monitor
```
* Type some characters on the monitor opened on the emulated UART bus after you see the message:
```
Type a message and press enter to terminate
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
As you run the example, you will see the following log on the external software UART monitor:
```
Type a message and press enter to terminate
```
After typing "Hello world!" followed by "Enter:, the Espressif target monitor should show:
```
Received on emulated UART: "Hello world!"
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,13 @@
set(srcs "soft_uart.c")
if(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "riscv/soft_uart.S")
elseif(CONFIG_IDF_TARGET_ARCH_XTENSA)
message(FATAL_ERROR "Xtensa targets not supported yet")
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver
LDFRAGMENTS linker.lf)

View File

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Enumeration for the usable baudrates by the software UART
*/
typedef enum {
SOFT_UART_115200,
SOFT_UART_230400,
SOFT_UART_460800,
SOFT_UART_921600,
SOFT_UART_BAUD_END
} soft_uart_baudrate_t;
/**
* @brief Structure defining the configuration for the software UART port
*/
typedef struct {
uint32_t tx_pin;
uint32_t rx_pin;
soft_uart_baudrate_t baudrate;
} soft_uart_config_t;
/**
* @brief Abstract type representing a software UART port.
*/
typedef struct soft_uart_port_impl_t* soft_uart_port_t;
/**
* @brief Create and configure the software UART port.
*
* @param config Configuration to apply to the initialized port.
* @param port Output structure representing the freshly initialized software UART port.
*
* @return ESP_OK on success
*/
esp_err_t soft_uart_new(soft_uart_config_t *config, soft_uart_port_t *port);
/**
* @brief Delete a previously initialized software UART port.
*
* @param port Port to delete, must have been initialized with `soft_uart_new` first.
*
* @return ESP_OK on success
*/
esp_err_t soft_uart_del(soft_uart_port_t port);
/**
* @brief Send the given bytes on the software UART port.
*
* @param port Software UART port to send data on.
* @param write_buffer Buffer containing the bytes to send on the buffer. Must not be NULL.
* @param write_size Size of the write buffer. Must not be 0.
*
* @return ESP_OK on success
*/
esp_err_t soft_uart_send(soft_uart_port_t port, const uint8_t* write_buffer, size_t write_size);
/**
* @brief Receive bytes from the software UART port.
*
* @param port Software UART port to receive data from.
* @param read_buffer Buffer that will contain the bytes received. Must not be NULL.
* @param read_size Size of the read buffer. Must not be 0.
*
* @return ESP_OK on success
*/
esp_err_t soft_uart_receive(soft_uart_port_t port, uint8_t* read_buffer, size_t read_size);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,4 @@
[mapping:main_default]
archive: libsoft_uart.a
entries:
* (noflash)

View File

@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
/* RISC-V fast GPIO special registers, taken from "hal/dedic_gpio_cpu_ll.h" */
#define CSR_GPIO_IN_USER 0x804
#define CSR_GPIO_OUT_USER 0x805
/* Special register for machine cycle count */
#define CSR_PCCR_MACHINE 0x7e2
.section .text
/**
* @brief Send bytes on the emulated UART.
*
* @param tx_buffer (a0) Buffer to send on the TX line. Guaranteed not NULL by the caller.
* @param tx_size (a1) Size of tx_buffer. Guaranteed not 0 by the caller.
* @param tx_bit (a2) Offset of TX I/O in the dedicated GPIO register.
* @param baudrate (a3) CPU clock cycles taken by each bit.
*
* The C signature of this routine would be:
* void emulate_uart_send(const uint8_t* tx, uint32_t tx_size, uint32_t tx_bit, uint32_t baudrate_delay);
*/
.global emulate_uart_send
.type emulate_uart_send, @function
emulate_uart_send:
/* "Convert" tx_bit to an actual mask. Thus, use 1 << tx_bit instead.
* rx_bit is not modified as we need the bit offset controlling the RX I/O and not a bit mask. */
li t0, 1
sll a2, t0, a2
/* Save return address in a4 */
mv a4, ra
/* As UART is very time sensitive, we want each bit sent to be very precise in terms of duration.
* The first toggle of the fast GPIO register may be slow, ~1us, so let's send a dummy byte here
* before the actual UART emulation start, else the first byte sent would be corrupted.*/
li t0, 0
call uart_send_byte
/* Reading the characters 4 by 4 would be much faster, but in our case, we don't need
* the process to be fast as the bottleneck is the UART speed */
uart_read_next:
lb t0, (a0)
/* Output the next character on the TX line */
call uart_send_byte
/* Go to the next character and repeat */
addi a0, a0, 1
addi a1, a1, -1
/* If we don't have more bytes to send, return */
bnez a1, uart_read_next
uart_ret:
/* Restore the return address */
mv ra, a4
ret
/**
* In theory, we would need to respect the calling convention and receive the parameter
* in a0, but as this routine is private and won't interact with any C function, we don't need
* to respect it, so we can only use registers, and not the stack.
*
* The C signature of this routine would be:
* void uart_send_byte(uint8_t byte, uint32_t tx_bitmask, uint32_t baudrate_delay);
*/
uart_send_byte:
/* a0, a1, a3, a4 are used by the caller.
* Parameters:
* t0 - Character to send
* a2 - Bit mask of GPIO_OUT_USER controlling TX
* a3 - Delay to wait between each bit
*/
mv t1, ra
/* Setup t3 to as we will send all 8 bits of the parameter (t0) */
li t3, 8
/* Start bit, clear/reset TX bit */
csrrc zero, CSR_GPIO_OUT_USER, a2
/* Wait a bit, depends on the baudrate configured */
call uart_delay
uart_send_byte_loop:
/* Get the lowest bit of t0 (parameter), store the result in t2 */
andi t2, t0, 1
/* We could avoid using a branch, but writing a 0 or 1 would have different timings.
* Using branches, we can arrange the code to have roughly the same timings in both cases. */
beqz t2, uart_send_bit_zero
/* The following will set the GPIO pointed by the lowest bit to 1 */
csrrs zero, CSR_GPIO_OUT_USER, a2
j uart_send_bit_after
uart_send_bit_zero:
/* If the bit was 0, we have to "clear" the GPIO */
csrrc zero, CSR_GPIO_OUT_USER, a2
uart_send_bit_after:
call uart_delay
/* Shift the parameter right */
srli t0, t0, 1
/* Decrement the loop index and continue if needed */
addi t3, t3, -1
bnez t3, uart_send_byte_loop
/* Stop bit, set bit to 1 */
csrrs zero, CSR_GPIO_OUT_USER, a2
call uart_delay
/* Restore return address before returning */
mv ra, t1
ret
/**
* @brief Receive bytes from the emulated UART.
*
* @param rx_buffer (a0) Buffer to store the received bytes in. Guaranteed not NULL by the caller.
* @param rx_size (a1) Size of rx_buffer. Guaranteed not 0 by the caller.
* @param rx_bit (a2) Offset of RX I/O in the dedicated GPIO register.
* @param baudrate (a3) CPU clock cycles taken by each bit.
*
* The C signature of this routine would be:
* void emulate_uart_receive(uint8_t *rx_buffer, uint32_t tx_size, uint32_t rx_bit, uint32_t baudrate_delay);
*/
.global emulate_uart_receive
.type emulate_uart_receive, @function
emulate_uart_receive:
/* Save return address in a4 */
mv a4, ra
uart_receive_iterate:
/* Receive characters on RX line now */
call uart_receive_char
/* Character received in a5. Store it in the buffer */
sb a5, (a0)
addi a0, a0, 1
/* Decrement the size */
addi a1, a1, -1
/* Iterate until we don't have space in the buffer */
bnez a1, uart_receive_iterate
/* Restore the return address */
mv ra, a4
ret
/* Routine to receive a character from the RX line and return it in a0.
* For the same reasons as above, we can use temporary registers.
*
* The C signature of this routine would be:
* uint8_t uart_receive_char(uint32_t rx_bit, uint32_t baudrate_delay);
*/
uart_receive_char:
/* a0, a1, a3, a4 are used by the caller.
* Parameters:
* a2 - Bit offset of GPIO_OUT_USER controlling RX. For example, 0 if RX is mapped to BIT(0).
* a3 - Delay (CPU cycles) to wait between each bit.
*/
mv t1, ra
li a5, 0
uart_receive_wait:
/* Wait for the start bit. The input GPIO is bound to the lowest bit of CSR_GPIO_IN_USER */
csrr t0, CSR_GPIO_IN_USER
sra t0, t0, a2
andi t0, t0, 1
/* Check that the input pin is 0 (start bit) */
bnez t0, uart_receive_wait
/* t2 will go from 0 to 7 as we will receive 8 bits */
li t2, 0
/* Start bit arrived, wait a bit:
* Wait half a UART-bit period here, the rest when we enter the loop, this will let us
* sample the bits in the middle of the period */
srli t6, a3, 1
call uart_delay_t6
uart_receive_next_bit:
call uart_delay
/* Read the next bit of RX */
csrr t0, CSR_GPIO_IN_USER
sra t0, t0, a2
andi t0, t0, 1
/* Add the bit we've just received to a5 */
sll t0, t0, t2
add a5, a5, t0
/* Check if we have received all the bits */
addi t2, t2, 1
li t0, 8
bne t0, t2, uart_receive_next_bit
/* We have received all the bits, we have to wait for the stop bit, in theory.
* In practice, just wait and return */
call uart_delay
/* Restore return address that was saved in t1 */
mv ra, t1
ret
/* Routine to wait few microseconds. The delay depends on the baudrate configured. */
uart_delay:
/* Default baudrate to wait in a3 register */
mv t6, a3
/* Specify a delay, in machine cycles, to wait */
uart_delay_t6:
/* t4, t5, t6 are available.
* Use t4 to store the "end" point to wait.
* Use t5 to get the current machine cycle. */
csrr t4, CSR_PCCR_MACHINE
/* In a real use case, we would need to check for a potential overflow,
* In this example, there should be any issue. */
add t4, t4, t6
uart_delay_loop:
csrr t5, CSR_PCCR_MACHINE
bltu t5, t4, uart_delay_loop
ret

View File

@ -0,0 +1,188 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "driver/dedic_gpio.h"
#include "driver/gpio.h"
#include "soft_uart.h"
#define ERR_CHECK_OR_GOTO(ret, label) do { if((ret) != ESP_OK ) goto label; } while (0)
/* Forward declaration of static functions */
void IRAM_ATTR emulate_uart_send(const uint8_t* tx_msg, uint32_t tx_size, uint32_t tx_bit, uint32_t baudrate);
void IRAM_ATTR emulate_uart_receive(uint8_t* rx_msg, uint32_t rx_size, uint32_t rx_bit, uint32_t baudrate);
static uint32_t baudrate_to_cycles(soft_uart_baudrate_t baudrate);
/* Mutex required to enter critical sections */
static portMUX_TYPE g_lock = portMUX_INITIALIZER_UNLOCKED;
const char* __attribute__((used)) SOFT_UART_TAG = "soft_uart";
/***** Public API implementation *****/
struct soft_uart_port_impl_t {
uint32_t tx_bit;
uint32_t rx_bit;
uint32_t cycles;
dedic_gpio_bundle_handle_t tx_bundle;
dedic_gpio_bundle_handle_t rx_bundle;
};
esp_err_t soft_uart_new(soft_uart_config_t *config, soft_uart_port_t *port)
{
esp_err_t ret;
struct soft_uart_port_impl_t *port_impl = NULL;
/* Check the parameters */
ESP_GOTO_ON_FALSE(config != NULL && port != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG,
"Parameters must not be NULL");
ESP_GOTO_ON_FALSE(config->baudrate < SOFT_UART_BAUD_END, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG,
"Invalid baudrate");
int tx = config->tx_pin;
int rx = config->rx_pin;
/* In order to prevent the receiver to get garbage while we configure the GPIOs, pull the pins up to
* reflect a UART idle state. */
ret = gpio_set_pull_mode(tx, GPIO_PULLUP_ENABLE);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_pull_mode(rx, GPIO_PULLUP_ENABLE);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_direction(tx, GPIO_MODE_OUTPUT);
ERR_CHECK_OR_GOTO(ret, error);
ret = gpio_set_direction(rx, GPIO_MODE_INPUT);
ERR_CHECK_OR_GOTO(ret, error);
/**
* Before actually calling any assembly routine, we need to configure the GPIOs
* We can do this in C. Using dedic_gpio API will do this for us, it will route
* the instruction-controlled signals to the GPIO pads thanks to the GPIO matrix.
*
* Use one GPIO as output, for TX, and one as input, for RX.
* Create the configuration for each.
*/
dedic_gpio_bundle_config_t tx_config = {
.gpio_array = &tx,
.array_size = 1,
.flags = {
.out_en = 1
}
};
dedic_gpio_bundle_config_t rx_config = {
.gpio_array = &rx,
.array_size = 1,
.flags = {
.in_en = 1
}
};
/* Allocate the master port structure now that we need it */
port_impl = malloc(sizeof(struct soft_uart_port_impl_t));
ESP_GOTO_ON_FALSE(port_impl != NULL, ESP_ERR_NO_MEM, error, SOFT_UART_TAG, "No more memory available in the system");
/* Initialize the dedicated GPIO bundles */
ret = dedic_gpio_new_bundle(&tx_config, &port_impl->tx_bundle);
ERR_CHECK_OR_GOTO(ret, error);
ret = dedic_gpio_new_bundle(&rx_config, &port_impl->rx_bundle);
ERR_CHECK_OR_GOTO(ret, error_rx);
/**
* Before executing the assembly routine, get the offset of TX/RX in the dedicated GPIO registers
*/
ret = dedic_gpio_get_out_offset(port_impl->tx_bundle, &port_impl->tx_bit);
ERR_CHECK_OR_GOTO(ret, error_offset);
ret = dedic_gpio_get_out_offset(port_impl->rx_bundle, &port_impl->rx_bit);
ERR_CHECK_OR_GOTO(ret, error_offset);
port_impl->cycles = baudrate_to_cycles(config->baudrate);
*port = port_impl;
return ret;
error_offset:
dedic_gpio_del_bundle(port_impl->rx_bundle);
error_rx:
dedic_gpio_del_bundle(port_impl->tx_bundle);
error:
if (port_impl != NULL) {
free(port_impl);
}
return ret;
}
esp_err_t soft_uart_del(soft_uart_port_t port)
{
esp_err_t ret;
ESP_GOTO_ON_FALSE(port != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Bus must not be NULL");
dedic_gpio_del_bundle(port->tx_bundle);
dedic_gpio_del_bundle(port->rx_bundle);
free(port);
error:
return ret;
}
esp_err_t soft_uart_send(soft_uart_port_t port, const uint8_t* write_buffer, size_t write_size)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(port != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(write_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Buffer must not be NULL");
ESP_GOTO_ON_FALSE(write_size != 0, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Buffer size must not be 0");
portENTER_CRITICAL(&g_lock);
emulate_uart_send(write_buffer, write_size, port->tx_bit, port->cycles);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}
esp_err_t soft_uart_receive(soft_uart_port_t port, uint8_t* read_buffer, size_t read_size)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(port != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Bus must not be NULL");
ESP_GOTO_ON_FALSE(read_buffer != NULL, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Buffer must not be NULL");
ESP_GOTO_ON_FALSE(read_size != 0, ESP_ERR_INVALID_ARG, error, SOFT_UART_TAG, "Buffer size must not be 0");
portENTER_CRITICAL(&g_lock);
emulate_uart_receive(read_buffer, read_size, port->rx_bit, port->cycles);
portEXIT_CRITICAL(&g_lock);
error:
return ret;
}
/***** Private helpers *****/
static uint32_t baudrate_to_cycles(soft_uart_baudrate_t baudrate)
{
/**
* Calculate the delay to wait between each bit depending on the UART baudrate and the CPU frequency.
* For each delay, subtract a small amount of clock cycles which compensate for the instructions
* used to prepare the next bits (loop, shifts, logic...).
*/
switch(baudrate) {
case SOFT_UART_115200: // 115200, 8.63us per bit
return ((CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ * 863)/100 - 20);
case SOFT_UART_230400: // 4.34us per bit
return ((CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ * 434)/100 - 24);
case SOFT_UART_460800: // 2.17us per bit
return ((CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ * 217)/100 - 20);
case SOFT_UART_921600: // 1.085us per bit
return ((CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ * 108)/100 - 23);
default:
assert(false);
return 0;
}
}

View File

@ -0,0 +1,5 @@
set(srcs "soft_uart_main.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "."
PRIV_REQUIRES driver soft_uart)

View File

@ -0,0 +1,21 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
config EMULATE_UART_GPIO_TX
int "GPIO pin for UART TX"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 0 if IDF_TARGET_ARCH_RISCV
default 16
help
GPIO pin number to be used as UART TX.
config EMULATE_UART_GPIO_RX
int "GPIO pin for UART RX"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 2 if IDF_TARGET_ARCH_RISCV
default 17
help
GPIO pin number to be used as UART RX.
endmenu

View File

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "soft_uart.h"
#define READ_COUNT 16
const char* EXAMPLE_TAG = "dedicated_gpio_example";
void app_main(void)
{
esp_err_t ret = ESP_OK;
uint8_t read_buffer[READ_COUNT] = { 0 };
const uint8_t write_buffer[] = "Hello, world! This is a message.\r\n";
soft_uart_port_t port = NULL;
soft_uart_config_t config = {
.tx_pin = CONFIG_EMULATE_UART_GPIO_TX,
.rx_pin = CONFIG_EMULATE_UART_GPIO_RX,
.baudrate = SOFT_UART_115200
};
/* Initialize and configure the software UART port */
ret = soft_uart_new(&config, &port);
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error configuring software UART");
/* Write few bytes to the UART */
ret = soft_uart_send(port, write_buffer, sizeof(write_buffer));
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error writing to the software UART");
/* Read from the UART */
ret = soft_uart_receive(port, read_buffer, sizeof(read_buffer));
ESP_GOTO_ON_ERROR(ret, error, EXAMPLE_TAG, "Error reading from the software UART");
printf("UART transfers succeeded, received bytes: { ");
for (int i = 0; i < sizeof(read_buffer); i++) {
printf("0x%02x ", read_buffer[i]);
}
printf("}\n");
error:
if (port != NULL) {
soft_uart_del(port);
}
if (ret != ESP_OK) {
ESP_LOGE(EXAMPLE_TAG, "An error occurred while communicating through the UART");
}
}

View File

@ -0,0 +1,3 @@
# Check the README.md file for more info about why we need to disable these options
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT_EN=n