AC63_BT_SDK/cpu/br34/lp_touch_key_alog.c
2025-02-18 15:40:42 +08:00

590 lines
15 KiB
C

#include "includes.h"
#include "asm/lp_touch_key_alog.h"
#define ABS(a) ((a)>0?(a):-(a))
#define MIN(a,b) ((a)>(b)?(b):(a))
#define MAX(a,b) ((a)<(b)?(b):(a))
#define ELEM_SWAP_USHORT(a,b) { unsigned short t=(a);(a)=(b);(b)=t; }
#define INIT_SIGMA (5)
#define MIN_SIGMA (5)
#define OUTLIER_ZSCORE (6)
#define PRESSED_ZSCORE (12)
#define PULSE_WIDTH (8)
#define MIN_VALLEY_SAMPLE (3)
#define FIFO_BUF_SIZE (8)
#define HISTORY_BUF_SIZE (12)
#define UNBALANCED_TH (2) //0.2
#define RANGE_TH_ALPHA (5)
#define RANGE_TH_DOWN_ALPHA (2)
#define RANGE_TH_DOWNWARD_EN (0)
#define RANGE_STABLE_CHECK_EN (1)
#define RANGE_STABLE_VALUE_TH (3) //0.3
#define RANGE_STABLE_COUNT_TH (10)
#define RANGE_STABLE_ALPHA (12) //0.04296875
typedef struct {
u32 count;
//for median filter
s16 med_dat0;
s16 med_dat1;
u8 med_sign;
//for gradient
s16 fifo_buf[FIFO_BUF_SIZE];
u8 fifo_pos;
u8 fifo_size;
s32 grad_max;
u8 key_state;
//for sigma tracing
s32 std_sum;
s32 std_count;
s32 sigma_iir_state;
s16 sigma_iir_alpha;
s16 sigma_iir_alpha_fast;
//stable count
s32 stable_count;
u8 is_stable;
//for valley tracing
s16 valley_grad;
s16 delayed_valley_grad;
s16 peak_grad;
s16 valley_val;
s16 peak_max;
s16 valley_duration;
//down continued
u8 down_cont;
//up continued
u8 up_cont;
u8 delayed_keyup;
#if RANGE_TH_DOWNWARD_EN
s32 high_undetect_count;
s32 low_detect_count;
u8 th_downward;
#endif
//for delta filter
u16 prev_val;
u16 range;
u16 range_median;
u16 range_for_th;
u16 range_list[HISTORY_BUF_SIZE];
u16 range_list_median[HISTORY_BUF_SIZE];
u8 range_size;
u8 range_pos;
u8 range_valid;
u16 range_min;
u16 range_max;
u8 maybe_noise;
#if RANGE_STABLE_CHECK_EN
s32 range_stable_iir_state;
s32 range_stable_count;
u8 range_isstable;
#endif
} TouchAlgo_t;
static TouchAlgo_t ta_ch[1];
static inline unsigned short kth_smallest_ushort(unsigned short a[], int n, int k)
{
int i, j, l, m ;
unsigned short x ;
l = 0 ;
m = n - 1 ;
while (l < m) {
x = a[k] ;
i = l ;
j = m ;
do {
while (a[i] < x) {
i++ ;
}
while (x < a[j]) {
j-- ;
}
if (i <= j) {
ELEM_SWAP_USHORT(a[i], a[j]) ;
i++ ;
j-- ;
}
} while (i <= j) ;
if (j < k) {
l = i ;
}
if (k < i) {
m = j ;
}
}
return a[k] ;
}
static u16 medfilt(TouchAlgo_t *ta, u16 x)
{
u16 ret = 0;
if (ta->med_sign) {
//dat0 <= dat1;
if (x <= ta->med_dat0) {
ret = ta->med_dat0;
ta->med_sign = 0;
} else if (x >= ta->med_dat1) {
ret = ta->med_dat1;
ta->med_sign = 1;
} else {
ret = x;
ta->med_sign = 0;
}
} else {
//da0 > dat1;
if (x <= ta->med_dat1) {
ret = ta->med_dat1;
ta->med_sign = 0;
} else if (x >= ta->med_dat0) {
ret = ta->med_dat0;
ta->med_sign = 1;
} else {
ret = x;
ta->med_sign = 1;
}
}
ta->med_dat0 = ta->med_dat1;
ta->med_dat1 = x;
return ret;
}
static s32 iir_filter(s32 *state, u16 x, s16 alpha)
{
*state = (*state * (256 - alpha) + ((s32)x << 6) * alpha + 128) >> 8;
return *state;
}
static s32 newton_sqrt(s32 in)
{
int x = 1;
int y = (x + in / x) >> 1;
while (ABS(y - x) > 1) {
x = y;
y = (x + in / x) >> 1;
}
return y;
}
static void TouchAlgo_tracing(TouchAlgo_t *ta, u16 x)
{
s32 sigma;
s32 delta, delta_abs;
s32 outlier_th;
s32 pressed_th, sigma_pressed_th;
s32 grad, grad_abs, grad_abs_max;
s32 grad_sign_max;
sigma = (ta->sigma_iir_state + 32) >> 6;
sigma_pressed_th = sigma * PRESSED_ZSCORE;
outlier_th = sigma * OUTLIER_ZSCORE;
if (ta->range_valid || ta->range_size > 0) {
pressed_th = ta->range_for_th / 2;
if (pressed_th < sigma_pressed_th) {
pressed_th = sigma_pressed_th;
}
} else {
pressed_th = sigma_pressed_th;
}
//delta filter
delta = (s32)x - (s32)ta->prev_val;
ta->prev_val = x;
delta_abs = delta;
if (delta_abs < 0) {
delta_abs = -delta_abs;
}
if (delta_abs < outlier_th) {
ta->std_sum += delta * delta;
ta->std_count += 1;
if (ta->std_count == 128) {
s32 var = newton_sqrt((ta->std_sum + ta->std_count / 2) / ta->std_count);
ta->std_count = 0;
ta->std_sum = 0;
if (var < MIN_SIGMA) {
var = MIN_SIGMA;
}
if (ta->count < 128 * 8) {
iir_filter(&ta->sigma_iir_state, var, ta->sigma_iir_alpha_fast);
} else {
iir_filter(&ta->sigma_iir_state, var, ta->sigma_iir_alpha);
}
}
ta->stable_count += 1;
if (ta->stable_count > 10) {
ta->is_stable = 1;
}
} else {
ta->stable_count = 0;
ta->is_stable = 0;
}
// printf("%d\n", sigma);
//gradient
grad_abs_max = delta_abs;
grad_sign_max = delta >> 31;
for (int i = 1; i < ta->fifo_size; i += 2) {
int idx = (FIFO_BUF_SIZE + ta->fifo_pos - 1 - i) % FIFO_BUF_SIZE;
grad = x - ta->fifo_buf[idx];
grad_abs = ABS(grad);
if (grad_abs > grad_abs_max) {
grad_abs_max = grad_abs;
grad_sign_max = grad >> 31;
}
}
if (grad_sign_max == -1) {
ta->grad_max = -grad_abs_max;
} else {
ta->grad_max = grad_abs_max;
}
//append to fifo
ta->fifo_buf[ta->fifo_pos] = x;
ta->fifo_pos = (ta->fifo_pos + 1) % FIFO_BUF_SIZE;
ta->fifo_size += 1;
if (ta->fifo_size > FIFO_BUF_SIZE) {
ta->fifo_size = FIFO_BUF_SIZE;
}
#if RANGE_TH_DOWNWARD_EN
if (grad_abs_max <= pressed_th) {
ta->high_undetect_count += 1;
if (grad_abs_max > sigma_pressed_th) {
ta->low_detect_count += 1;
}
if (ta->high_undetect_count >= 60000 && ta->low_detect_count > 0) { //10min
ta->th_downward = 1;
}
if (ta->th_downward && ta->high_undetect_count % 100 == 0) {
ta->range_for_th = (ta->range_for_th * (256 - RANGE_TH_DOWN_ALPHA) + sigma_pressed_th * 2 * RANGE_TH_DOWN_ALPHA + 128) >> 8;
}
} else {
ta->high_undetect_count = 0;
ta->low_detect_count = 0;
ta->th_downward = 0;
}
#endif
if (ta->maybe_noise) {
if (ta->is_stable) {
ta->maybe_noise = 0;
} else {
return;
}
}
//key state machine
if (ta->key_state == 0) {
if (grad_abs_max > pressed_th && grad_sign_max == 0) {
//key up
//nothing to do
if (ta->up_cont == 0) {
ta->maybe_noise = 1;
}
if (ta->up_cont) {
if (ta->peak_max < x) {
ta->peak_max = x;
ta->peak_grad = ta->peak_max - ta->valley_val;
}
}
ta->down_cont = 0;
ta->up_cont = 1;
} else if (grad_abs_max > pressed_th && grad_sign_max == -1) {
//key down
ta->key_state = 1;
ta->valley_duration = 1;
ta->valley_val = x;
ta->valley_grad = grad_abs_max;
ta->down_cont = 1;
ta->up_cont = 0;
} else {
//nothing to do
ta->down_cont = 0;
ta->up_cont = 0;
}
} else {
if (grad_abs_max > pressed_th && grad_sign_max == 0) {
//key up
ta->key_state = 0;
ta->peak_max = x;
ta->peak_grad = ta->peak_max - ta->valley_val;
if (ta->valley_duration >= PULSE_WIDTH) {
ta->delayed_keyup = 1;
ta->delayed_valley_grad = ta->valley_grad;
}
ta->down_cont = 0;
ta->up_cont = 1;
} else if (grad_abs_max > pressed_th && grad_sign_max == -1) {
//key down
if (ta->down_cont) {
ta->valley_duration += 1;
if (x < ta->valley_val) {
ta->valley_grad += ta->valley_val - x;
ta->valley_val = x;
}
} else {
//keydown when it's already in keydown state
//Discarding all history information, cause it's not reliable
// ta->range_size = 0;
// ta->range_pos = 0;
// ta->range_valid = 0;
// printf("keydown when it's already in keydown state\n");
//reset keydown state
ta->key_state = 1;
ta->valley_duration = 1;
ta->valley_val = x;
ta->valley_grad = grad_abs_max;
}
ta->down_cont = 1;
ta->up_cont = 0;
} else {
ta->valley_duration += 1;
if (x < ta->valley_val) {
ta->valley_grad += ta->valley_val - x;
ta->valley_val = x;
}
ta->down_cont = 0;
ta->up_cont = 0;
}
if (ta->delayed_keyup && ta->up_cont == 0) {
s16 peak_grad = ta->peak_grad;
s16 min_grad = MIN(peak_grad, ta->delayed_valley_grad);
s16 max_grad = MAX(peak_grad, ta->delayed_valley_grad);
s32 stablized_range;
s32 range_th;
ta->delayed_keyup = 0;
#if RANGE_STABLE_CHECK_EN
stablized_range = (ta->range_stable_iir_state + 32) >> 6;
range_th = stablized_range * RANGE_STABLE_VALUE_TH / 10;
if ((max_grad - min_grad) * 10 < max_grad * UNBALANCED_TH
&& min_grad > ta->range_min
&& max_grad < ta->range_max
&& ((ta->range_isstable && ABS(stablized_range - max_grad) < range_th) || ta->range_isstable == 0))
#else
if ((max_grad - min_grad) * 10 < max_grad * UNBALANCED_TH
&& min_grad > ta->range_min
&& max_grad < ta->range_max)
#endif
{
ta->range_list[ta->range_pos] = max_grad;
ta->range_pos = (ta->range_pos + 1) % HISTORY_BUF_SIZE;
if (ta->range_size < HISTORY_BUF_SIZE) {
ta->range_size += 1;
}
ta->range = ta->range_list[0];
for (int i = 1; i < ta->range_size; i++) {
if (ta->range < ta->range_list[i]) {
ta->range = ta->range_list[i];
}
}
memcpy(&ta->range_list_median, &ta->range_list, ta->range_size * sizeof(u16));
ta->range_median = kth_smallest_ushort(ta->range_list_median, ta->range_size, ta->range_size / 2);
// if(ta->range_size > HISTORY_BUF_SIZE/2)
// {
ta->range = ta->range_median;
// }
if (ta->range_valid == 0) {
ta->range_for_th = sigma_pressed_th * 2;
#if RANGE_STABLE_CHECK_EN
ta->range_stable_iir_state = ta->range << 6;
#endif
} else {
ta->range_for_th = (ta->range_for_th * (256 - RANGE_TH_ALPHA) + ta->range_median * RANGE_TH_ALPHA + 128) >> 8;
#if RANGE_STABLE_CHECK_EN
iir_filter(&ta->range_stable_iir_state, ta->range, RANGE_STABLE_ALPHA);
if (ta->range > stablized_range - range_th
&& ta->range < stablized_range + range_th
) {
ta->range_stable_count += 1;
} else {
ta->range_stable_count = 0;
}
if (ta->range_stable_count > RANGE_STABLE_COUNT_TH) {
ta->range_isstable = 1;
}
#endif
}
if (ta->range_size >= MIN_VALLEY_SAMPLE) {
ta->range_valid = 1;
}
if (ta->range_valid) {
// printf("range:%d, median:%d\n", ta->range, ta->range_median);
}
} else {
printf("invalid touch value\n");
}
}
}
return;
}
void TouchAlgo_Init(u8 ch, u16 min, u16 max)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
ta->count = 0;
ta->fifo_pos = 0;
ta->fifo_size = 0;
ta->key_state = 0;
ta->med_dat0 = ta->med_dat1 = 0;
ta->med_sign = 1;
ta->std_sum = 0;
ta->std_count = 0;
ta->sigma_iir_alpha = 5;
ta->sigma_iir_alpha_fast = 32;
ta->sigma_iir_state = INIT_SIGMA << 6;
ta->stable_count = 0;
ta->is_stable = 0;
ta->valley_val = 0;
ta->valley_duration = 0;
ta->prev_val = 0;
ta->delayed_keyup = 0;
#if RANGE_TH_DOWNWARD_EN
ta->high_undetect_count = 0;
ta->low_detect_count = 0;
ta->th_downward = 0;
#endif
ta->range_valid = 0;
ta->range_median = 0;
ta->range = 0;
ta->range_size = 0;
ta->range_pos = 0;
ta->range_min = min;
ta->range_max = max;
ta->maybe_noise = 0;
#if RANGE_STABLE_CHECK_EN
ta->range_stable_iir_state = 0;
ta->range_stable_count = 0;
ta->range_isstable = 0;
#endif
}
void TouchAlgo_Update(u8 ch, u16 x)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
u16 dat0;
dat0 = medfilt(ta, x);
if (ta->count < 3) {
ta->count++;
ta->prev_val = dat0;
return;
}
TouchAlgo_tracing(ta, dat0);
ta->count++;
return;
}
void TouchAlgo_Reset(u8 ch, u16 min, u16 max)
{
TouchAlgo_Init(ch, min, max);
}
s32 TouchAlgo_GetSigma(u8 ch)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
return ta->sigma_iir_state;
}
u16 TouchAlgo_GetRange(u8 ch, u8 *valid)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
*valid = ta->range_valid;
return ta->range_valid ? ta->range : 0;
}
void TouchAlgo_SetRange(u8 ch, u16 range)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
ta->range = range;
ta->range_valid = 1;
ta->range_list[0] = range;
ta->range_size = 1;
ta->range_pos = 1;
}
void TouchAlgo_SetSigma(u8 ch, s32 sigma)
{
TouchAlgo_t *ta = (TouchAlgo_t *)&ta_ch[ch];
ta->sigma_iir_state = sigma;
}