Echo task only outputs avg of queue1, not queue2

shenanigami
Posts: 8
Joined: Sat Dec 16, 2023 10:52 am

Echo task only outputs avg of queue1, not queue2

Postby shenanigami » Sat Jun 01, 2024 3:00 pm

Hi,

Some background:
I have 2 tasks. The app_main task creates a HW timer that runs an ISR (timer callback) to sample from ADC 10 times per second. These sampled values are then sent to queue1/queue2 - the queue that the values get sent to alternates (depending on which queue is full (queue, queue2, queue1, ...), compute_avg task depopulates queue1/queue2, more on this task later). Once 10 samples have been collected over the course of 1 second, the ISR is to wake up compute_avg, which then computes the average of those 10 samples.

NOTE: For testing purposes only, instead of the adc values I'm just using incremented integers (0, 1, 2, 3, 4, ...) to populate the queue; cb_data->num in adc_sampling_timer_cb callback func.

The average is a floating point value and it is stored in a global variable.

There's also a second task, echo_avg task, which echos back any characters input into serial monitor. Upon input of avg, echo_avg prints out the value found in the global variable.


Here's the code:

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include "esp_adc/adc_continuous.h"

/* configs */
// configTASK_NOTIFICATION_ARRAY_ENTRIES set to 2

// uart driver configuration
#define ECHO_TEST_TXD GPIO_NUM_1
#define ECHO_TEST_RXD GPIO_NUM_3
// no flow control
#define ECHO_TEST_RTS (UART_PIN_NO_CHANGE)
#define ECHO_TEST_CTS (UART_PIN_NO_CHANGE)

#define ECHO_UART_PORT_NUM      UART_NUM_0
#define ECHO_UART_BAUD_RATE     115200

#define AVG_BUF_SIZE 3
#define DATA_BUF_SIZE 2
#define BUF_SIZE 256

#define ADC_READ_LEN 4

static const char *TAG = "[HW_INTERRUPT] ";

static TaskHandle_t x_compute_avg_task = NULL, x_echo_avg_task = NULL;
static QueueHandle_t queue1, queue2;
static adc_continuous_handle_t adc_handle = NULL;
#define TASK_STACK_SIZE 2*1024
#define QUEUE_SIZE 10

typedef struct {
    uint8_t queue_num;
    uint32_t num;
} timer_cb_data_t;

portMUX_TYPE adc_mutex = portMUX_INITIALIZER_UNLOCKED;

static float avg = 0.0;

static gptimer_handle_t adc_read_timer = NULL;

static void continuous_adc_init(adc_continuous_handle_t *adc_handle)
{
    // sample adc value
    // resource allocation
    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 40,       // 10 conversion frames is good
        .conv_frame_size = 4,           // conversion frame size, in bytes. This should be in multiples of SOC_ADC_DIGI_DATA_BYTES_PER_CONV
                                        // conversion frame consisting of single conversion result is enough for this program
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, adc_handle));

    adc_digi_pattern_config_t *adc_pattern = malloc(sizeof(adc_digi_pattern_config_t));
    assert(adc_pattern);
    *adc_pattern = (adc_digi_pattern_config_t) {
        .atten = ADC_ATTEN_DB_0,            // No input attenuation, ADC can measure up to approx.
        .channel = ADC_CHANNEL_7,
        .unit = ADC_UNIT_1,
        .bit_width = SOC_ADC_DIGI_MAX_BITWIDTH,     // 12 bits
    };

    // ADC Configurations
    adc_continuous_config_t dig_cfg = {
        .pattern_num = 1,                           // number of ADC channels that will be used
        .adc_pattern = adc_pattern,                 // list of configs for each ADC channel that will be used
        .sample_freq_hz = 20 * 1000,                // minimum sample rate, defined in soc/soc_caps.h
        .conv_mode = ADC_CONV_SINGLE_UNIT_1,        // Only use ADC1 for conversion
        .format = ADC_DIGI_OUTPUT_FORMAT_TYPE1,     // type 1 seems to be the suitable type for esp32 adc1 single unit conversion
    };

    ESP_ERROR_CHECK(adc_continuous_config(*adc_handle, &dig_cfg));
    ESP_ERROR_CHECK(adc_continuous_start(*adc_handle));
}

// fromISR
// return bool value is - Whether a high priority task has been waken up by this function
static bool adc_sampling_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
    timer_cb_data_t *cb_data = (timer_cb_data_t*) user_ctx;

    uint8_t result[ADC_READ_LEN] = { 0 };
    uint32_t out_length = 0;
    adc_continuous_read(adc_handle, result, ADC_READ_LEN, &out_length, 0);

    QueueHandle_t queue_cb = cb_data->queue_num == 1 ? queue1 : queue2;

    if (xQueueSendFromISR(queue_cb, &cb_data->num, NULL) != pdPASS) {
        cb_data->queue_num ^= 0x03;
        // notify compute_avg task - queue 1 is index 0, queue2 is index 1
        xTaskNotifyIndexedFromISR(x_compute_avg_task, cb_data->queue_num - 1, 0, eNoAction, NULL);
    }
    else
        cb_data->num++;

    return false;
}


static void compute_avg_task(void *arg)
{

    static uint32_t num = 0;
    int i = 0;
    while(1) {
        xTaskNotifyWaitIndexed(0,               // index 0
                               0x00,            // no need to clear notification value (not being used)
                               0x00,            // again, nothing to clear on exit (not used)
                               NULL,            // notification value is not needed, so set to NULL
                               portMAX_DELAY    // max time to wait in blocked state for notifcation
                              );
        avg = 0.0; i = 0;
        while (xQueueReceive(queue1, &num, 0) == pdPASS) {
            i++;
            avg += num;
        }
        avg /= i;
        ESP_LOGI(TAG, "avg of queue1 is %f", avg);

        xTaskNotifyWaitIndexed(1,               // index 1
                               0x00,            // no need to clear notification value (not being used)
                               0x00,            // again, nothing to clear on exit (not used)
                               NULL,            // notification value is not needed, so set to NULL
                               portMAX_DELAY    // max time to wait in blocked state for notifcation
                              );
        avg = 0.0; i = 0;
        while (xQueueReceive(queue2, &num, 0) == pdPASS) {
            i++;
            avg += num;
        }
        avg /= i;
        ESP_LOGI(TAG, "avg of queue2 is %f", avg);
    }

}

// no timeout
static bool readUntilWhitespaceOrAvg(size_t cmd_word_size, const char* cmd_word)
{
    bool is_cmd_word = true;
    uint8_t c = 0;
    int i = 1;

    int len = uart_read_bytes(ECHO_UART_PORT_NUM, &c, 1, 20 / portTICK_PERIOD_MS);
    while (i < cmd_word_size) {

        if (len > 0 && c > 0) {
            uart_write_bytes(ECHO_UART_PORT_NUM, (const char*) &c, len);
            if (c == 'a') {
                i = 0;
                ESP_LOGD(TAG, "avg soon?");
            } else if (c != cmd_word[i]) {
                is_cmd_word = false;
                break;
            }
            i++;
        }
        len = uart_read_bytes(ECHO_UART_PORT_NUM, &c, 1, 20 / portTICK_PERIOD_MS);
    }

    return is_cmd_word;
}

static void echo_avg_task(void *arg)
{
    // Configure parameters of an UART driver,
     // communication pins and install the driver
    uart_config_t uart_config = {
        .baud_rate = ECHO_UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    int intr_alloc_flags = 0;

#if CONFIG_UART_ISR_IN_IRAM
    intr_alloc_flags = ESP_INTR_FLAG_IRAM;
#endif
    ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE, 0, 0, NULL, intr_alloc_flags));
    ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, ECHO_TEST_TXD, ECHO_TEST_RXD, ECHO_TEST_RTS, ECHO_TEST_CTS));

    // Configure a temporary buffer for the incoming data
    uint8_t data[DATA_BUF_SIZE] = { 0 };
    bool is_avg = false;
    const char* avg_cmd = "avg";

    while (1) {
        // Read data from the UART
        int len = uart_read_bytes(ECHO_UART_PORT_NUM, data, 1, 20 / portTICK_PERIOD_MS);
        if (*data == '\r')
            *data = '\n';
        else if (*data == 'a') {
            ESP_LOGD(TAG, "avg, is that you?");
            is_avg = true;
        }

        // Write data back to the UART
        uart_write_bytes(ECHO_UART_PORT_NUM, (const char*) data, len);
        if (len) {
            data[len] = '\0';
            ESP_LOGD(TAG, "Recv str: %s", (char *) data);
        }

        if (is_avg) {
            if (readUntilWhitespaceOrAvg(AVG_BUF_SIZE, avg_cmd))
                ESP_LOGI(TAG, "avg is %f", avg);
            else
                ESP_LOGI(TAG, "not avg");

            is_avg = false;
        }
        *data = '\0';
    }

}

void app_main(void)
{
    // resource allocation timer
    gptimer_config_t timer_config = {
        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
        .direction = GPTIMER_COUNT_UP,
        .resolution_hz = 1 * 1000 * 1000,  // 1 MHz, 1 tick = 1 us
    };
    ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &adc_read_timer));

    // alarm config
    gptimer_alarm_config_t alarm_config = {
        .reload_count = 0,  // counter will reload with 0 on alarm event
        .alarm_count = 100000,  // period = 100ms @resolution 1MHz
        .flags.auto_reload_on_alarm = true,  // enable auto-reload
    };
    ESP_ERROR_CHECK(gptimer_set_alarm_action(adc_read_timer, &alarm_config));

    // callback config
    gptimer_event_callbacks_t cbs = {
        .on_alarm = adc_sampling_timer_cb,  // register user callback
    };

    timer_cb_data_t *cb_data = calloc(1, sizeof(timer_cb_data_t));
    assert(cb_data);
    cb_data->queue_num = 1;

    ESP_ERROR_CHECK(gptimer_register_event_callbacks(adc_read_timer, &cbs, (void *) cb_data));
    // Create queues
    queue1 = xQueueCreate(QUEUE_SIZE, sizeof(uint32_t));
    queue2 = xQueueCreate(QUEUE_SIZE, sizeof(uint32_t));

    xTaskCreate(compute_avg_task, "compute_avg_task", TASK_STACK_SIZE, NULL, 1, &x_compute_avg_task);
    xTaskCreate(echo_avg_task, "echo_avg_task", TASK_STACK_SIZE, NULL, 1, &x_echo_avg_task);

    continuous_adc_init(&adc_handle);

    ESP_ERROR_CHECK(gptimer_enable(adc_read_timer));
    ESP_ERROR_CHECK(gptimer_start(adc_read_timer));
}
Output:

Code: Select all

I (322) main_task: Started on CPU0
I (332) main_task: Calling app_main()
I (332) gpio: Gd from app_main()
I (2542) [HW_INTERRUPT] : avg of queue1 is 4.500000
I (2542) [HW_INTERRUPT] : avg of queue2 is 14.500000
I (4742) [HW_INTERRUPT] : avg of queue1 is 24.500000
I (4742) [HW_INTERRUPT] : avg of queue2 is 34.500000
Question:
I don't understand why whenever I input avg I only get avg of queue1 output to serial, but never of queue2. If anyone can understand why, please explain.

MicroController
Posts: 1553
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Echo task only outputs avg of queue1, not queue2

Postby MicroController » Sat Jun 01, 2024 4:20 pm

Not sure what you're actually trying to do with those two queues and the two notifications, but it seems like you're notifying the wrong (inverted) index: When queue1 is full you notify index 1, when queue2 is full you notify index 0. The task otoh waits on index 0 to consume from queue1 and on index 1 to consume from queue2.

Btw, note that you can use (the bits of) the notification value to send information to a task. You could use two bits therein to indicate which of the queues is/are full, and the task would only have to wait for one notification to figure out what to consume.

shenanigami
Posts: 8
Joined: Sat Dec 16, 2023 10:52 am

Re: Echo task only outputs avg of queue1, not queue2

Postby shenanigami » Tue Jun 04, 2024 4:35 pm

Thank you so much!

I made sure to flip queue_num after task notify, not before, and it worked

Code: Select all

    if (xQueueSendFromISR(queue_cb, &cb_data->num, NULL) != pdPASS) {
        // notify compute_avg task - index 0
        xTaskNotifyIndexedFromISR(x_compute_avg_task, 0, (uint32_t) cb_data->queue_num, eSetValueWithOverwrite, NULL);
        cb_data->queue_num ^= 0x03;    // toggle between 1 and 2
    }
and used the bits of notification value as you suggested to make the code cleaner.

Who is online

Users browsing this forum: jsolsona, mark.k92 and 86 guests