sdm: add wave output example

This commit is contained in:
laokaiyao
2023-01-04 12:53:06 +08:00
parent 751fa28919
commit 59984e7d1f
13 changed files with 223 additions and 11 deletions

View File

@@ -6,6 +6,14 @@ Introduction
{IDF_TARGET_NAME} has a second-order sigma-delta modulator, which can generate independent PDM pulses to multiple channels. Please refer to the TRM to check how many hardware channels are available. [1]_ {IDF_TARGET_NAME} has a second-order sigma-delta modulator, which can generate independent PDM pulses to multiple channels. Please refer to the TRM to check how many hardware channels are available. [1]_
Delta-sigma modulation converts an analog voltage signal into a pulse frequency, or pulse density, which can be understood as pulse-density modulation (PDM) (refer to [Delta-sigma modulation on Wikipedia](https://en.wikipedia.org/wiki/Delta-sigma_modulation)).
The main differences comparing to the PDM in I2S peripheral and DAC are:
1. SDM has no clock signal, it just like the DAC mode of PDM;
2. SDM has no DMA, it can only output the analog signal by a timer just like DAC oneshot mode, therefore, its output rate is limited by the timer callback interval;
3. Base on the former two points, an external active or passive filter is required to restore the analog wave;
Typically, a Sigma-Delta modulated channel can be used in scenarios like: Typically, a Sigma-Delta modulated channel can be used in scenarios like:
- LED dimming - LED dimming

View File

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

View File

@@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gpio_sigma_delta) project(sdm_dac_example)

View File

@@ -0,0 +1,61 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
# Sigma Delta Modulation DAC Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example uses the sigma-delta driver to generate modulated output on a GPIO. If you filter the output signal with an active or passive filter, you can get a 1 KHz sine wave.
## How to use example
### Hardware Required
* A development board with any supported Espressif SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
* An active or passive filter. Connect them as below:
```
┌──────────────────────────────┐
│ ESP │ ┌───────────────────┐
│ │ │ Active or Passive │ Sine Wave
│ EXAMPLE_SIGMA_DELTA_GPIO_NUM ├────►│ Filter ├──────────────
└──────────────────────────────┘ └───────────────────┘
```
### Configure the project
The main configurations can be update by the macro that defined at the top of the [example source file](main/sdm_dac_example_main.c), including the output gpio, timer callback interval and the sine wave frequency and amplitude.
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(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
Once the upload is complete and the board is reset, the program should start running. This is reported on the monitor as below:
```
...
I (299) main_task: Calling app_main()
I (309) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) sdm_dac: Sigma-delta output is attached to GPIO 0
I (319) sdm_dac: Timer allocated with resolution 10000000 Hz
I (329) sdm_dac: Timer callback registered, interval 10 us
I (329) sdm_dac: Timer enabled
I (339) sdm_dac: Output start
```
After the output stated, you can monitor the sine wave after the filter by an oscilloscope.
![output_wave](output_wave.jpeg)
## 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,2 @@
idf_component_register(SRCS "sdm_dac_example_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/sdm.h"
#include "driver/gptimer.h"
#define EXAMPLE_SIGMA_DELTA_GPIO_NUM (0) // Select GPIO_NUM_0 as the sigma-delta output pin
#define EXAMPLE_OVER_SAMPLE_RATE (20 * 1000 * 1000) // 20 MHz over sample rate
#define EXAMPLE_TIMER_RESOLUTION (10 * 1000 * 1000) // 10 MHz timer counting resolution
#define EXAMPLE_CALLBACK_INTERVAL_US (10) // 20 us interval of each timer callback
#define EXAMPLE_ALARM_COUNT (EXAMPLE_CALLBACK_INTERVAL_US * (EXAMPLE_TIMER_RESOLUTION / 1000000))
#define EXAMPLE_SINE_WAVE_FREQ_HZ (1000) // 1 KHz wave
#define EXAMPLE_SINE_WAVE_AMPLITUDE (127.0f) // 1 ~ 127
#define EXAMPLE_SINE_WAVE_POINT_NUM (1000000 / (EXAMPLE_CALLBACK_INTERVAL_US * EXAMPLE_SINE_WAVE_FREQ_HZ))
#define CONST_PI (3.1416f) // Constant of PI, used for calculating the sine wave
_Static_assert(EXAMPLE_SINE_WAVE_POINT_NUM > 1, "Sine wave frequency is too high");
_Static_assert(EXAMPLE_CALLBACK_INTERVAL_US >= 7, "Timer callback interval is too short");
static const char *TAG = "sdm_dac";
static int8_t sine_wave[EXAMPLE_SINE_WAVE_POINT_NUM]; // Sine wave data buffer
static bool example_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
static uint32_t cnt = 0;
sdm_channel_handle_t sdm_chan = (sdm_channel_handle_t)user_ctx;
/* Set the pulse density */
sdm_channel_set_duty(sdm_chan, sine_wave[cnt]);
/* Loop the sine wave data buffer */
cnt++;
cnt %= EXAMPLE_SINE_WAVE_POINT_NUM;
return false;
}
static gptimer_handle_t example_init_gptimer(void* args)
{
/* Allocate GPTimer handle */
gptimer_handle_t timer_handle;
gptimer_config_t timer_cfg = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = EXAMPLE_TIMER_RESOLUTION,
.flags.intr_shared = false,
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_cfg, &timer_handle));
ESP_LOGI(TAG, "Timer allocated with resolution %d Hz", EXAMPLE_TIMER_RESOLUTION);
/* Set the timer alarm configuration */
gptimer_alarm_config_t alarm_cfg = {
.alarm_count = EXAMPLE_ALARM_COUNT,
.reload_count = 0,
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(timer_handle, &alarm_cfg));
/* Register the alarm callback */
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_callback,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(timer_handle, &cbs, args));
ESP_LOGI(TAG, "Timer callback registered, interval %d us", EXAMPLE_CALLBACK_INTERVAL_US);
/* Clear the timer raw count value, make sure it'll count from 0 */
ESP_ERROR_CHECK(gptimer_set_raw_count(timer_handle, 0));
/* Enable the timer */
ESP_ERROR_CHECK(gptimer_enable(timer_handle));
ESP_LOGI(TAG, "Timer enabled");
return timer_handle;
}
static sdm_channel_handle_t example_init_sdm(void)
{
/* Allocate sdm channel handle */
sdm_channel_handle_t sdm_chan = NULL;
sdm_config_t config = {
.clk_src = SDM_CLK_SRC_DEFAULT,
.gpio_num = EXAMPLE_SIGMA_DELTA_GPIO_NUM,
.sample_rate_hz = EXAMPLE_OVER_SAMPLE_RATE,
};
ESP_ERROR_CHECK(sdm_new_channel(&config, &sdm_chan));
/* Enable the sdm channel */
ESP_ERROR_CHECK(sdm_channel_enable(sdm_chan));
ESP_LOGI(TAG, "Sigma-delta output is attached to GPIO %d", EXAMPLE_SIGMA_DELTA_GPIO_NUM);
return sdm_chan;
}
void app_main(void)
{
/* Assign sine wave data */
for (int i = 0; i < EXAMPLE_SINE_WAVE_POINT_NUM; i++) {
sine_wave[i] = (int8_t)((sin(2 * (float)i * CONST_PI / EXAMPLE_SINE_WAVE_POINT_NUM)) * EXAMPLE_SINE_WAVE_AMPLITUDE);
}
/* Initialize sigma-delta modulation on the specific GPIO */
sdm_channel_handle_t sdm_chan = example_init_sdm();
/* Initialize GPTimer and register the timer alarm callback */
gptimer_handle_t timer_handle = example_init_gptimer(sdm_chan);
/* Start the GPTimer */
ESP_LOGI(TAG, "Output start");
ESP_ERROR_CHECK(gptimer_start(timer_handle));
/* Forever loop */
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

View File

@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.generic
def test_sdm_dac_example(dut: Dut) -> None:
dut.expect(r'sdm_dac: Sigma-delta output is attached to GPIO \w+')
dut.expect(r'sdm_dac: Timer allocated with resolution \w+ Hz')
dut.expect(r'sdm_dac: Timer callback registered, interval \w+ us')
dut.expect_exact('sdm_dac: Timer enabled')
dut.expect_exact('sdm_dac: Output start')

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

View File

@@ -1,7 +1,7 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | | Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | | ----------------- | ----- | -------- | -------- | -------- | -------- |
# Sigma Delta Modulation Example # Sigma Delta Modulation LED Example
(See the README.md file in the upper level 'examples' directory for more information about examples.) (See the README.md file in the upper level 'examples' directory for more information about examples.)
@@ -22,7 +22,7 @@ EXAMPLE_SIGMA_DELTA_GPIO_NUM +----/\/\/\----+------|>|-----+ GND
### Configure the project ### Configure the project
You can change the GPIO number by [EXAMPLE_SIGMA_DELTA_GPIO_NUM](main/sdm_example_main.c). You can change the GPIO number by [EXAMPLE_SIGMA_DELTA_GPIO_NUM](main/sdm_led_example_main.c).
### Build and Flash ### Build and Flash

View File

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

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@@ -16,7 +16,7 @@
#define EXAMPLE_LED_DIM_DUTY_MAX 90 #define EXAMPLE_LED_DIM_DUTY_MAX 90
#define EXAMPLE_LED_DIM_DUTY_MIN (EXAMPLE_LED_DIM_DUTY_MAX - EXAMPLE_LED_DIM_PERIOD_MS / EXAMPLE_LED_DIM_DELAY_MS * EXAMPLE_LED_DIM_DUTY_STEP) #define EXAMPLE_LED_DIM_DUTY_MIN (EXAMPLE_LED_DIM_DUTY_MAX - EXAMPLE_LED_DIM_PERIOD_MS / EXAMPLE_LED_DIM_DELAY_MS * EXAMPLE_LED_DIM_DUTY_STEP)
static const char *TAG = "example"; static const char *TAG = "sdm_led";
void app_main(void) void app_main(void)
{ {

View File

@@ -11,7 +11,7 @@ from pytest_embedded import Dut
@pytest.mark.esp32c3 @pytest.mark.esp32c3
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.generic @pytest.mark.generic
def test_sdm_example(dut: Dut) -> None: def test_sdm_led_example(dut: Dut) -> None:
dut.expect_exact('example: Install sigma delta channel') dut.expect_exact('sdm_led: Install sigma delta channel')
dut.expect_exact('example: Enable sigma delta channel') dut.expect_exact('sdm_led: Enable sigma delta channel')
dut.expect_exact('example: Change duty cycle continuously') dut.expect_exact('sdm_led: Change duty cycle continuously')