Files
snapclient/components/esp_peripherals/lib/adc_button/adc_button.c
Carlos 30d2e54dab - completely drop ADF but copy necessary parts
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
2021-09-05 20:20:36 +02:00

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);
}