o copy component audio_board from ADF and create custom component from it o copy component audio_hal from ADF and create custom component from it o copy component audio_sal from ADF and create custom component from it o copy component esp_peripherals from ADF and create custom component from it - add fLaC support through xiph's original repository as a git module
506 lines
15 KiB
C
506 lines
15 KiB
C
/*
|
|
* ESPRESSIF MIT License
|
|
*
|
|
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
|
*
|
|
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in
|
|
* which case, it is free of charge, to any person obtaining a copy of this
|
|
* software and associated documentation files (the "Software"), to deal in the
|
|
* Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "audio_mem.h"
|
|
#include "driver/adc.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/event_groups.h"
|
|
#include "freertos/queue.h"
|
|
#include "freertos/task.h"
|
|
#include "math.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#if CONFIG_IDF_TARGET_ESP32
|
|
#include "esp_adc_cal.h"
|
|
#endif
|
|
#include "adc_button.h"
|
|
#include "audio_thread.h"
|
|
#include "esp_log.h"
|
|
#include "string.h"
|
|
|
|
#define V_REF 1100
|
|
|
|
#define ADC_SAMPLES_NUM 10
|
|
#define ADC_SAMPLE_INTERVAL_TIME_MS 20
|
|
#define DIAL_VOL_INTERVAL_TIME_MS 150
|
|
|
|
#define ADC_BTN_INVALID_ID -1
|
|
#define ADC_BTN_INVALID_ACT_ID -2
|
|
#define ADC_BTN_DETECT_TIME_MS 20
|
|
|
|
#ifndef ENABLE_ADC_VOLUME
|
|
#define USER_KEY_MAX 7
|
|
#endif
|
|
|
|
static char *TAG = "ADC_BTN";
|
|
static EventGroupHandle_t g_event_bit;
|
|
|
|
typedef struct
|
|
{
|
|
adc_button_callback btn_callback;
|
|
adc_btn_list *head;
|
|
void *user_data;
|
|
audio_thread_t audio_thread;
|
|
} adc_btn_tag_t;
|
|
|
|
static const int default_step_level[USER_KEY_MAX]
|
|
= { 0, 683, 1193, 1631, 2090, 2578, 3103 };
|
|
static const int DESTROY_BIT = BIT0;
|
|
static bool _task_flag;
|
|
|
|
adc_btn_list *
|
|
adc_btn_create_list (adc_arr_t *adc_conf, int channels)
|
|
{
|
|
adc_btn_list *head = NULL;
|
|
adc_btn_list *node = NULL;
|
|
adc_btn_list *find = NULL;
|
|
for (int i = 0; i < channels; i++)
|
|
{
|
|
node = (adc_btn_list *)audio_calloc (1, sizeof (adc_btn_list));
|
|
if (NULL == node)
|
|
{
|
|
ESP_LOGE (TAG, "Memory allocation failed! Line: %d", __LINE__);
|
|
return NULL;
|
|
}
|
|
memset (node, 0, sizeof (adc_btn_list));
|
|
adc_arr_t *info = &(node->adc_info);
|
|
memcpy (info, adc_conf + i, sizeof (adc_arr_t));
|
|
info->adc_level_step
|
|
= (int *)audio_calloc (1, (info->total_steps + 1) * sizeof (int));
|
|
memset (info->adc_level_step, 0, (info->total_steps + 1) * sizeof (int));
|
|
if (NULL == info->adc_level_step)
|
|
{
|
|
ESP_LOGE (TAG, "Memory allocation failed! Line: %d", __LINE__);
|
|
audio_free (node);
|
|
return NULL;
|
|
}
|
|
if (adc_conf[i].adc_level_step == NULL)
|
|
{
|
|
memcpy (info->adc_level_step, default_step_level,
|
|
USER_KEY_MAX * sizeof (int));
|
|
}
|
|
else
|
|
{
|
|
memcpy (info->adc_level_step, adc_conf[i].adc_level_step,
|
|
(adc_conf[i].total_steps + 1) * sizeof (int));
|
|
}
|
|
if (info->total_steps > USER_KEY_MAX)
|
|
{
|
|
ESP_LOGE (TAG, "The total_steps should be less than USER_KEY_MAX");
|
|
audio_free (info->adc_level_step);
|
|
audio_free (node);
|
|
}
|
|
node->btn_dscp = (btn_decription *)audio_calloc (
|
|
1, sizeof (btn_decription) * (adc_conf[i].total_steps));
|
|
if (NULL == node->btn_dscp)
|
|
{
|
|
ESP_LOGE (TAG, "Memory allocation failed! Line: %d", __LINE__);
|
|
audio_free (info->adc_level_step);
|
|
audio_free (node);
|
|
}
|
|
memset (node->btn_dscp, 0,
|
|
sizeof (btn_decription) * (adc_conf[i].total_steps));
|
|
node->next = NULL;
|
|
if (NULL == head)
|
|
{
|
|
head = node;
|
|
find = head;
|
|
}
|
|
else
|
|
{
|
|
find->next = node;
|
|
find = node;
|
|
}
|
|
}
|
|
return head;
|
|
}
|
|
|
|
esp_err_t
|
|
adc_btn_destroy_list (adc_btn_list *head)
|
|
{
|
|
if (head == NULL)
|
|
{
|
|
ESP_LOGD (TAG, "The head of list is null");
|
|
return ESP_OK;
|
|
}
|
|
adc_btn_list *find = head;
|
|
adc_btn_list *tmp = find;
|
|
|
|
while (find)
|
|
{
|
|
adc_arr_t *info = &(find->adc_info);
|
|
tmp = find->next;
|
|
audio_free (find->btn_dscp);
|
|
audio_free (info->adc_level_step);
|
|
audio_free (find);
|
|
find = tmp;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static int
|
|
get_adc_voltage (int channel)
|
|
{
|
|
uint32_t data[ADC_SAMPLES_NUM] = { 0 };
|
|
uint32_t sum = 0;
|
|
int tmp = 0;
|
|
|
|
#if CONFIG_IDF_TARGET_ESP32
|
|
esp_adc_cal_characteristics_t characteristics;
|
|
esp_adc_cal_characterize (ADC_UNIT_1, ADC_ATTEN_11db, ADC_WIDTH_12Bit, V_REF,
|
|
&characteristics);
|
|
for (int i = 0; i < ADC_SAMPLES_NUM; ++i)
|
|
{
|
|
esp_adc_cal_get_voltage (channel, &characteristics, &data[i]);
|
|
}
|
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
|
for (int i = 0; i < ADC_SAMPLES_NUM; i++)
|
|
{
|
|
data[i] = adc1_get_raw ((adc1_channel_t)channel);
|
|
}
|
|
#endif
|
|
|
|
for (int j = 0; j < ADC_SAMPLES_NUM - 1; j++)
|
|
{
|
|
for (int i = 0; i < ADC_SAMPLES_NUM - j - 1; i++)
|
|
{
|
|
if (data[i] > data[i + 1])
|
|
{
|
|
tmp = data[i];
|
|
data[i] = data[i + 1];
|
|
data[i + 1] = tmp;
|
|
}
|
|
}
|
|
}
|
|
for (int num = 1; num < ADC_SAMPLES_NUM - 1; num++)
|
|
sum += data[num];
|
|
return (sum / (ADC_SAMPLES_NUM - 2));
|
|
}
|
|
|
|
static int
|
|
get_button_id (adc_btn_list *node, int adc)
|
|
{
|
|
int m = ADC_BTN_INVALID_ID;
|
|
adc_arr_t *info = &(node->adc_info);
|
|
for (int i = 0; i < info->total_steps; i++)
|
|
{
|
|
ESP_LOGV (TAG, "max:%d, adc:%d, i:%d, %d, %d", info->total_steps, adc, i,
|
|
info->adc_level_step[i], info->adc_level_step[i + 1]);
|
|
if ((adc > info->adc_level_step[i])
|
|
&& (adc <= info->adc_level_step[i + 1]))
|
|
{
|
|
m = i;
|
|
break;
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
static void
|
|
reset_btn (btn_decription *btn_dscp, int btn_num)
|
|
{
|
|
memset (btn_dscp, 0, sizeof (btn_decription) * btn_num);
|
|
for (int i = 0; i < btn_num; ++i)
|
|
{
|
|
btn_dscp[i].active_id = ADC_BTN_INVALID_ID;
|
|
}
|
|
}
|
|
|
|
static adc_btn_state_t
|
|
get_adc_btn_state (int adc_value, int act_id, adc_btn_list *node)
|
|
{
|
|
adc_btn_state_t st = ADC_BTN_STATE_IDLE;
|
|
adc_arr_t *info = &(node->adc_info);
|
|
btn_decription *btn_dscp = node->btn_dscp;
|
|
int id = get_button_id (node, adc_value);
|
|
if (id == ADC_BTN_INVALID_ID)
|
|
{
|
|
if (act_id == ADC_BTN_INVALID_ACT_ID)
|
|
{
|
|
// No old act id and new act id.
|
|
return ADC_BTN_STATE_IDLE;
|
|
}
|
|
if (btn_dscp[act_id].click_cnt <= 1)
|
|
{
|
|
return ADC_BTN_STATE_IDLE;
|
|
}
|
|
// Have old act ID, new id is invalid
|
|
// Need to send release event
|
|
if (btn_dscp[act_id].click_cnt
|
|
< (info->press_judge_time / ADC_BTN_DETECT_TIME_MS))
|
|
{
|
|
ESP_LOGD (TAG, "pressed: Act ID:%d, ID:%d, Cnt:%d", act_id, id,
|
|
btn_dscp[act_id].click_cnt);
|
|
st = ADC_BTN_STATE_RELEASE;
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGD (TAG, "long press release: Act ID:%d, ID:%d, Cnt:%d",
|
|
act_id, id, btn_dscp[act_id].click_cnt);
|
|
st = ADC_BTN_STATE_LONG_RELEASE;
|
|
}
|
|
btn_dscp[act_id].active_id = -1;
|
|
btn_dscp[act_id].long_click = 0;
|
|
btn_dscp[act_id].click_cnt = 0;
|
|
return st;
|
|
}
|
|
// 1.ID is valid and act ID is invalid.
|
|
if (act_id == ADC_BTN_INVALID_ACT_ID)
|
|
{
|
|
// First new act id
|
|
btn_dscp[id].active_id = id;
|
|
return ADC_BTN_STATE_IDLE;
|
|
}
|
|
// 2.ID and act ID are valid, but not equal.
|
|
if (id != act_id)
|
|
{
|
|
ESP_LOGW (TAG, "Old ID:%d, New ID:%d", act_id, id);
|
|
// Invalid the act ID
|
|
btn_dscp[act_id].active_id = -1;
|
|
btn_dscp[act_id].long_click = 0;
|
|
btn_dscp[act_id].click_cnt = 0;
|
|
// Set the new id act ID
|
|
btn_dscp[id].active_id = id;
|
|
// Maybe need to check release long pressed.
|
|
return ADC_BTN_STATE_IDLE;
|
|
}
|
|
// 3.ID and act ID are valid, and equal.
|
|
btn_dscp[act_id].click_cnt++;
|
|
if (btn_dscp[act_id].click_cnt == 3)
|
|
{
|
|
return ADC_BTN_STATE_PRESSED;
|
|
}
|
|
|
|
if (btn_dscp[act_id].long_click)
|
|
{
|
|
return ADC_BTN_STATE_IDLE;
|
|
}
|
|
if (btn_dscp[act_id].click_cnt
|
|
>= (info->press_judge_time / ADC_BTN_DETECT_TIME_MS))
|
|
{
|
|
// Send long click event.
|
|
ESP_LOGD (TAG, "long press: Act ID:%d, ID:%d, Cnt:%d", act_id, id,
|
|
btn_dscp[act_id].click_cnt);
|
|
st = ADC_BTN_STATE_LONG_PRESSED;
|
|
btn_dscp[act_id].long_click = 1;
|
|
}
|
|
return st;
|
|
}
|
|
|
|
static void
|
|
button_task (void *parameters)
|
|
{
|
|
_task_flag = true;
|
|
adc_btn_tag_t *tag = (adc_btn_tag_t *)parameters;
|
|
adc_btn_list *head = tag->head;
|
|
adc_btn_list *find = head;
|
|
xEventGroupClearBits (g_event_bit, DESTROY_BIT);
|
|
#if CONFIG_IDF_TARGET_ESP32
|
|
adc1_config_width (ADC_WIDTH_BIT_12);
|
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
|
adc1_config_width (ADC_WIDTH_BIT_13);
|
|
#endif
|
|
|
|
while (find)
|
|
{
|
|
adc_arr_t *info = &(find->adc_info);
|
|
reset_btn (find->btn_dscp, info->total_steps);
|
|
adc1_config_channel_atten (info->adc_ch, ADC_ATTEN_11db);
|
|
find = find->next;
|
|
}
|
|
find = head;
|
|
|
|
#if defined ENABLE_ADC_VOLUME
|
|
adc1_config_channel_atten (DIAL_adc_ch, ADC_ATTEN_11db);
|
|
short adc_vol_prev = ADC_BTN_INVALID_ID;
|
|
short adc_vol_cur = ADC_BTN_INVALID_ID;
|
|
short internal_time_ms = DIAL_VOL_INTERVAL_TIME_MS
|
|
/ ADC_SAMPLE_INTERVAL_TIME_MS; /// 10 * 10 = 100ms
|
|
static bool empty_flag;
|
|
static bool full_flag;
|
|
bool is_first_time = true;
|
|
#endif // ENABLE_ADC_VOLUME
|
|
|
|
static adc_btn_state_t cur_state = ADC_BTN_STATE_ADC;
|
|
adc_btn_state_t btn_st = ADC_BTN_STATE_IDLE;
|
|
int cur_act_id = ADC_BTN_INVALID_ACT_ID;
|
|
while (_task_flag)
|
|
{
|
|
#if defined ENABLE_ADC_VOLUME
|
|
if (internal_time_ms == 0)
|
|
{
|
|
adc_vol_cur = get_adc_voltage (DIAL_adc_ch);
|
|
internal_time_ms
|
|
= DIAL_VOL_INTERVAL_TIME_MS / ADC_SAMPLE_INTERVAL_TIME_MS;
|
|
if (adc_vol_prev > 0)
|
|
{
|
|
short n = abs (adc_vol_cur - adc_vol_prev);
|
|
if (is_first_time)
|
|
{
|
|
is_first_time = false;
|
|
}
|
|
if (adc_vol_cur < 200)
|
|
{
|
|
if (empty_flag == false)
|
|
{
|
|
ESP_LOGI (TAG, "ABS_LOW:%d, %d->0", n, adc_vol_cur / 25);
|
|
empty_flag = true;
|
|
}
|
|
}
|
|
else if (adc_vol_cur > 2500)
|
|
{
|
|
if (full_flag == false)
|
|
{
|
|
ESP_LOGI (TAG, "ABS_HIGH:%d, %d->100", n,
|
|
adc_vol_cur / 25);
|
|
full_flag = true;
|
|
}
|
|
}
|
|
else if (n > 80)
|
|
{
|
|
empty_flag = false;
|
|
full_flag = false;
|
|
}
|
|
}
|
|
adc_vol_prev = adc_vol_cur;
|
|
}
|
|
internal_time_ms--;
|
|
#else
|
|
find = head;
|
|
while (find)
|
|
{
|
|
adc_arr_t *info = &(find->adc_info);
|
|
int act_id = ADC_BTN_INVALID_ACT_ID;
|
|
btn_decription *btn_dscp = find->btn_dscp;
|
|
switch (cur_state)
|
|
{
|
|
case ADC_BTN_STATE_ADC:
|
|
{
|
|
int adc = get_adc_voltage (info->adc_ch);
|
|
ESP_LOGD (TAG, "ADC:%d", adc);
|
|
for (int i = 0; i < info->total_steps; ++i)
|
|
{
|
|
if (btn_dscp[i].active_id > ADC_BTN_INVALID_ID)
|
|
{
|
|
act_id = i;
|
|
break;
|
|
}
|
|
}
|
|
btn_st = get_adc_btn_state (adc, act_id, find);
|
|
if (btn_st != ADC_BTN_STATE_IDLE)
|
|
{
|
|
cur_act_id = act_id;
|
|
cur_state = btn_st;
|
|
ESP_LOGD (TAG, "ADC ID:%d", act_id);
|
|
}
|
|
break;
|
|
}
|
|
case ADC_BTN_STATE_PRESSED:
|
|
{
|
|
tag->btn_callback ((void *)tag->user_data, info->adc_ch,
|
|
cur_act_id, ADC_BTN_STATE_PRESSED);
|
|
cur_state = ADC_BTN_STATE_ADC;
|
|
break;
|
|
}
|
|
case ADC_BTN_STATE_LONG_PRESSED:
|
|
{
|
|
tag->btn_callback ((void *)tag->user_data, info->adc_ch,
|
|
cur_act_id, ADC_BTN_STATE_LONG_PRESSED);
|
|
cur_state = ADC_BTN_STATE_ADC;
|
|
break;
|
|
}
|
|
case ADC_BTN_STATE_LONG_RELEASE:
|
|
{
|
|
tag->btn_callback ((void *)tag->user_data, info->adc_ch,
|
|
cur_act_id, ADC_BTN_STATE_LONG_RELEASE);
|
|
cur_state = ADC_BTN_STATE_ADC;
|
|
break;
|
|
}
|
|
case ADC_BTN_STATE_RELEASE:
|
|
{
|
|
tag->btn_callback ((void *)tag->user_data, info->adc_ch,
|
|
cur_act_id, ADC_BTN_STATE_RELEASE);
|
|
cur_state = ADC_BTN_STATE_ADC;
|
|
break;
|
|
}
|
|
default:
|
|
ESP_LOGE (TAG, "Not support state %d", cur_state);
|
|
break;
|
|
}
|
|
find = find->next;
|
|
}
|
|
#endif // ENABLE_ADC_VOLUME
|
|
|
|
vTaskDelay (ADC_SAMPLE_INTERVAL_TIME_MS / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
if (g_event_bit)
|
|
{
|
|
xEventGroupSetBits (g_event_bit, DESTROY_BIT);
|
|
}
|
|
audio_free (tag);
|
|
vTaskDelete (NULL);
|
|
}
|
|
|
|
void
|
|
adc_btn_delete_task (void)
|
|
{
|
|
if (_task_flag)
|
|
{
|
|
_task_flag = false;
|
|
}
|
|
|
|
if (g_event_bit)
|
|
{
|
|
xEventGroupWaitBits (g_event_bit, DESTROY_BIT, pdTRUE, pdFALSE,
|
|
portMAX_DELAY);
|
|
vEventGroupDelete (g_event_bit);
|
|
g_event_bit = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
adc_btn_init (void *user_data, adc_button_callback cb, adc_btn_list *head,
|
|
adc_btn_task_cfg_t *task_cfg)
|
|
{
|
|
adc_btn_tag_t *tag = audio_calloc (1, sizeof (adc_btn_tag_t));
|
|
if (NULL == tag)
|
|
{
|
|
ESP_LOGE (TAG, "Memory allocation failed! Line: %d", __LINE__);
|
|
return;
|
|
}
|
|
tag->user_data = user_data;
|
|
tag->head = head;
|
|
tag->btn_callback = cb;
|
|
|
|
g_event_bit = xEventGroupCreate ();
|
|
|
|
audio_thread_create (&tag->audio_thread, "button_task", button_task,
|
|
(void *)tag, task_cfg->task_stack, task_cfg->task_prio,
|
|
task_cfg->ext_stack, task_cfg->task_core);
|
|
}
|