first commit
This commit is contained in:
597
ebtn/bit_array.h
Normal file
597
ebtn/bit_array.h
Normal file
@@ -0,0 +1,597 @@
|
||||
#ifndef _BIT_ARRAY_H_
|
||||
#define _BIT_ARRAY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
// #define BIT_ARRAY_CONFIG_64
|
||||
|
||||
// here can change to uint64_t, if you system is 64bit.
|
||||
#ifdef BIT_ARRAY_CONFIG_64
|
||||
typedef uint64_t bit_array_t;
|
||||
#define BIT_ARRAY_BIT(n) (1ULL << (n))
|
||||
#else
|
||||
typedef uint32_t bit_array_t;
|
||||
#define BIT_ARRAY_BIT(n) (1UL << (n))
|
||||
#endif
|
||||
typedef bit_array_t bit_array_val_t;
|
||||
|
||||
#define BIT_ARRAY_BITS (sizeof(bit_array_val_t) * 8)
|
||||
|
||||
#define BIT_ARRAY_BIT_WORD(bit) ((bit) / BIT_ARRAY_BITS)
|
||||
#define BIT_ARRAY_BIT_INDEX(bit) ((bit_array_val_t)(bit) & (BIT_ARRAY_BITS - 1U))
|
||||
|
||||
#define BIT_ARRAY_MASK(bit) BIT_ARRAY_BIT(BIT_ARRAY_BIT_INDEX(bit))
|
||||
#define BIT_ARRAY_ELEM(addr, bit) ((addr)[BIT_ARRAY_BIT_WORD(bit)])
|
||||
|
||||
// word of all 1s
|
||||
#define BIT_ARRAY_WORD_MAX (~(bit_array_val_t)0)
|
||||
|
||||
#define BIT_ARRAY_SUB_MASK(nbits) ((nbits) ? BIT_ARRAY_WORD_MAX >> (BIT_ARRAY_BITS - (nbits)) : (bit_array_val_t)0)
|
||||
|
||||
// A possibly faster way to combine two words with a mask
|
||||
// #define bitmask_merge(a,b,abits) ((a & abits) | (b & ~abits))
|
||||
#define bitmask_merge(a, b, abits) (b ^ ((a ^ b) & abits))
|
||||
|
||||
/**
|
||||
* @brief This macro computes the number of bit array variables necessary to
|
||||
* represent a bitmap with @a num_bits.
|
||||
*
|
||||
* @param num_bits Number of bits.
|
||||
*/
|
||||
#define BIT_ARRAY_BITMAP_SIZE(num_bits) (1 + ((num_bits)-1) / BIT_ARRAY_BITS)
|
||||
|
||||
/**
|
||||
* @brief Define an array of bit array variables.
|
||||
*
|
||||
* This macro defines an array of bit array variables containing at least
|
||||
* @a num_bits bits.
|
||||
*
|
||||
* @note
|
||||
* If used from file scope, the bits of the array are initialized to zero;
|
||||
* if used from within a function, the bits are left uninitialized.
|
||||
*
|
||||
* @cond INTERNAL_HIDDEN
|
||||
* @note
|
||||
* This macro should be replicated in the PREDEFINED field of the documentation
|
||||
* Doxyfile.
|
||||
* @endcond
|
||||
*
|
||||
* @param name Name of array of bit array variables.
|
||||
* @param num_bits Number of bits needed.
|
||||
*/
|
||||
#define BIT_ARRAY_DEFINE(name, num_bits) bit_array_t name[BIT_ARRAY_BITMAP_SIZE(num_bits)]
|
||||
|
||||
#if 1
|
||||
// See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
|
||||
static inline bit_array_val_t _windows_popcount(bit_array_val_t w)
|
||||
{
|
||||
w = w - ((w >> 1) & (bit_array_val_t) ~(bit_array_val_t)0 / 3);
|
||||
w = (w & (bit_array_val_t) ~(bit_array_val_t)0 / 15 * 3) + ((w >> 2) & (bit_array_val_t) ~(bit_array_val_t)0 / 15 * 3);
|
||||
w = (w + (w >> 4)) & (bit_array_val_t) ~(bit_array_val_t)0 / 255 * 15;
|
||||
return (bit_array_val_t)(w * ((bit_array_val_t) ~(bit_array_val_t)0 / 255)) >> (sizeof(bit_array_val_t) - 1) * 8;
|
||||
}
|
||||
|
||||
#define POPCOUNT(x) _windows_popcount(x)
|
||||
#else
|
||||
#define POPCOUNT(x) (unsigned)__builtin_popcountll(x)
|
||||
#endif
|
||||
|
||||
#define bits_in_top_word(nbits) ((nbits) ? BIT_ARRAY_BIT_INDEX((nbits)-1) + 1 : 0)
|
||||
|
||||
static inline void _bit_array_mask_top_word(bit_array_t *target, int num_bits)
|
||||
{
|
||||
// Mask top word
|
||||
int num_of_words = BIT_ARRAY_BITMAP_SIZE(num_bits);
|
||||
int bits_active = bits_in_top_word(num_bits);
|
||||
target[num_of_words - 1] &= BIT_ARRAY_SUB_MASK(bits_active);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bit Array test a bit.
|
||||
*
|
||||
* This routine tests whether bit number @a bit of @a target is set or not.
|
||||
*
|
||||
* @param target Address of bit array variable or array.
|
||||
* @param bit Bit number (starting from 0).
|
||||
*
|
||||
* @return true if the bit was set, false if it wasn't.
|
||||
*/
|
||||
static inline int bit_array_get(const bit_array_t *target, int bit)
|
||||
{
|
||||
bit_array_val_t val = BIT_ARRAY_ELEM(target, bit);
|
||||
|
||||
return (1 & (val >> (bit & (BIT_ARRAY_BITS - 1)))) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bit Array clear a bit.
|
||||
*
|
||||
* Bit Array clear bit number @a bit of @a target.
|
||||
*
|
||||
* @param target Address of bit array variable or array.
|
||||
* @param bit Bit number (starting from 0).
|
||||
*/
|
||||
static inline void bit_array_clear(bit_array_t *target, int bit)
|
||||
{
|
||||
bit_array_val_t mask = BIT_ARRAY_MASK(bit);
|
||||
|
||||
BIT_ARRAY_ELEM(target, bit) &= ~mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bit Array set a bit.
|
||||
*
|
||||
* Bit Array set bit number @a bit of @a target.
|
||||
*
|
||||
* @param target Address of bit array variable or array.
|
||||
* @param bit Bit number (starting from 0).
|
||||
*/
|
||||
static inline void bit_array_set(bit_array_t *target, int bit)
|
||||
{
|
||||
bit_array_val_t mask = BIT_ARRAY_MASK(bit);
|
||||
|
||||
BIT_ARRAY_ELEM(target, bit) |= mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bit Array toggle a bit.
|
||||
*
|
||||
* Bit Array toggle bit number @a bit of @a target.
|
||||
*
|
||||
* @param target Address of bit array variable or array.
|
||||
* @param bit Bit number (starting from 0).
|
||||
*/
|
||||
static inline void bit_array_toggle(bit_array_t *target, int bit)
|
||||
{
|
||||
bit_array_val_t mask = BIT_ARRAY_MASK(bit);
|
||||
|
||||
BIT_ARRAY_ELEM(target, bit) ^= mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bit Array set a bit to a given value.
|
||||
*
|
||||
* Bit Array set bit number @a bit of @a target to value @a val.
|
||||
*
|
||||
* @param target Address of bit array variable or array.
|
||||
* @param bit Bit number (starting from 0).
|
||||
* @param val true for 1, false for 0.
|
||||
*/
|
||||
static inline void bit_array_assign(bit_array_t *target, int bit, int val)
|
||||
{
|
||||
bit_array_val_t mask = BIT_ARRAY_MASK(bit);
|
||||
|
||||
if (val)
|
||||
{
|
||||
BIT_ARRAY_ELEM(target, bit) |= mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
BIT_ARRAY_ELEM(target, bit) &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void bit_array_clear_all(bit_array_t *target, int num_bits)
|
||||
{
|
||||
memset((void *)target, 0, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t));
|
||||
}
|
||||
|
||||
static inline void bit_array_set_all(bit_array_t *target, int num_bits)
|
||||
{
|
||||
memset((void *)target, 0xff, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t));
|
||||
_bit_array_mask_top_word(target, num_bits);
|
||||
}
|
||||
|
||||
static inline void bit_array_toggle_all(bit_array_t *target, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
target[i] ^= BIT_ARRAY_WORD_MAX;
|
||||
}
|
||||
_bit_array_mask_top_word(target, num_bits);
|
||||
}
|
||||
|
||||
//
|
||||
// Strings and printing
|
||||
//
|
||||
|
||||
// Construct a BIT_ARRAY from a substring with given on and off characters.
|
||||
|
||||
// From string method
|
||||
static inline void bit_array_from_str(bit_array_t *bitarr, const char *str)
|
||||
{
|
||||
int i, index;
|
||||
int space = 0;
|
||||
int len = strlen(str);
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
index = i - space;
|
||||
if (strchr("1", str[i]) != NULL)
|
||||
{
|
||||
bit_array_set(bitarr, index);
|
||||
}
|
||||
else if (strchr("0", str[i]) != NULL)
|
||||
{
|
||||
bit_array_clear(bitarr, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// error.
|
||||
space++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length
|
||||
// Terminates string with '\0'
|
||||
static inline char *bit_array_to_str(const bit_array_t *bitarr, int num_bits, char *str)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_bits; i++)
|
||||
{
|
||||
str[i] = bit_array_get(bitarr, i) ? '1' : '0';
|
||||
}
|
||||
|
||||
str[num_bits] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// Takes a char array to write to. `str` must be bitarr->num_of_bits+1 in length
|
||||
// Terminates string with '\0'
|
||||
static inline char *bit_array_to_str_8(const bit_array_t *bitarr, int num_bits, char *str)
|
||||
{
|
||||
int i;
|
||||
int space = 0;
|
||||
|
||||
for (i = 0; i < num_bits; i++)
|
||||
{
|
||||
str[i + space] = bit_array_get(bitarr, i) ? '1' : '0';
|
||||
|
||||
if ((i + 1) % 8 == 0)
|
||||
{
|
||||
space++;
|
||||
str[i + space] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
str[num_bits + space] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
//
|
||||
// Get and set words (internal use only -- no bounds checking)
|
||||
//
|
||||
|
||||
static inline bit_array_val_t _bit_array_get_word(const bit_array_t *target, int num_bits, int start)
|
||||
{
|
||||
int word_index = BIT_ARRAY_BIT_WORD(start);
|
||||
int word_offset = BIT_ARRAY_BIT_INDEX(start);
|
||||
|
||||
bit_array_val_t result = target[word_index] >> word_offset;
|
||||
|
||||
int bits_taken = BIT_ARRAY_BITS - word_offset;
|
||||
|
||||
// word_offset is now the number of bits we need from the next word
|
||||
// Check the next word has at least some bits
|
||||
if (word_offset > 0 && start + bits_taken < num_bits)
|
||||
{
|
||||
result |= target[word_index + 1] << (BIT_ARRAY_BITS - word_offset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set 64 bits from a particular start position
|
||||
// Doesn't extend bit array
|
||||
static inline void _bit_array_set_word(bit_array_t *target, int num_bits, int start, bit_array_val_t word)
|
||||
{
|
||||
int word_index = BIT_ARRAY_BIT_WORD(start);
|
||||
int word_offset = BIT_ARRAY_BIT_INDEX(start);
|
||||
|
||||
if (word_offset == 0)
|
||||
{
|
||||
target[word_index] = word;
|
||||
}
|
||||
else
|
||||
{
|
||||
target[word_index] = (word << word_offset) | (target[word_index] & BIT_ARRAY_SUB_MASK(word_offset));
|
||||
|
||||
if (word_index + 1 < BIT_ARRAY_BITMAP_SIZE(num_bits))
|
||||
{
|
||||
target[word_index + 1] = (word >> (BIT_ARRAY_BITS - word_offset)) | (target[word_index + 1] & (BIT_ARRAY_WORD_MAX << word_offset));
|
||||
}
|
||||
}
|
||||
|
||||
// Mask top word
|
||||
_bit_array_mask_top_word(target, num_bits);
|
||||
}
|
||||
|
||||
//
|
||||
// Fill a region (internal use only)
|
||||
//
|
||||
|
||||
// FillAction is fill with 0 or 1 or toggle
|
||||
typedef enum
|
||||
{
|
||||
ZERO_REGION,
|
||||
FILL_REGION,
|
||||
SWAP_REGION
|
||||
} FillAction;
|
||||
|
||||
static inline void _bit_array_set_region(bit_array_t *target, int start, int length, FillAction action)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
int first_word = BIT_ARRAY_BIT_WORD(start);
|
||||
int last_word = BIT_ARRAY_BIT_WORD(start + length - 1);
|
||||
int foffset = BIT_ARRAY_BIT_INDEX(start);
|
||||
int loffset = BIT_ARRAY_BIT_INDEX(start + length - 1);
|
||||
|
||||
if (first_word == last_word)
|
||||
{
|
||||
bit_array_val_t mask = BIT_ARRAY_SUB_MASK(length) << foffset;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case ZERO_REGION:
|
||||
target[first_word] &= ~mask;
|
||||
break;
|
||||
case FILL_REGION:
|
||||
target[first_word] |= mask;
|
||||
break;
|
||||
case SWAP_REGION:
|
||||
target[first_word] ^= mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set first word
|
||||
switch (action)
|
||||
{
|
||||
case ZERO_REGION:
|
||||
target[first_word] &= BIT_ARRAY_SUB_MASK(foffset);
|
||||
break;
|
||||
case FILL_REGION:
|
||||
target[first_word] |= ~BIT_ARRAY_SUB_MASK(foffset);
|
||||
break;
|
||||
case SWAP_REGION:
|
||||
target[first_word] ^= ~BIT_ARRAY_SUB_MASK(foffset);
|
||||
break;
|
||||
}
|
||||
|
||||
int i;
|
||||
|
||||
// Set whole words
|
||||
switch (action)
|
||||
{
|
||||
case ZERO_REGION:
|
||||
for (i = first_word + 1; i < last_word; i++)
|
||||
target[i] = (bit_array_val_t)0;
|
||||
break;
|
||||
case FILL_REGION:
|
||||
for (i = first_word + 1; i < last_word; i++)
|
||||
target[i] = BIT_ARRAY_WORD_MAX;
|
||||
break;
|
||||
case SWAP_REGION:
|
||||
for (i = first_word + 1; i < last_word; i++)
|
||||
target[i] ^= BIT_ARRAY_WORD_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set last word
|
||||
switch (action)
|
||||
{
|
||||
case ZERO_REGION:
|
||||
target[last_word] &= ~BIT_ARRAY_SUB_MASK(loffset + 1);
|
||||
break;
|
||||
case FILL_REGION:
|
||||
target[last_word] |= BIT_ARRAY_SUB_MASK(loffset + 1);
|
||||
break;
|
||||
case SWAP_REGION:
|
||||
target[last_word] ^= BIT_ARRAY_SUB_MASK(loffset + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the number of bits set (hamming weight)
|
||||
static inline int bit_array_num_bits_set(bit_array_t *target, int num_bits)
|
||||
{
|
||||
int i;
|
||||
|
||||
int num_of_bits_set = 0;
|
||||
|
||||
for (i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
if (target[i] > 0)
|
||||
{
|
||||
num_of_bits_set += POPCOUNT(target[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return num_of_bits_set;
|
||||
}
|
||||
|
||||
// Get the number of bits not set (1 - hamming weight)
|
||||
static inline int bit_array_num_bits_cleared(bit_array_t *target, int num_bits)
|
||||
{
|
||||
return num_bits - bit_array_num_bits_set(target, num_bits);
|
||||
}
|
||||
|
||||
// Copy bits from one array to another
|
||||
// Note: use MACRO bit_array_copy
|
||||
// Destination and source can be the same bit_array and
|
||||
// src/dst regions can overlap
|
||||
static inline void bit_array_copy(bit_array_t *dst, int dstindx, const bit_array_t *src, int srcindx, int length, int src_num_bits, int dst_num_bits)
|
||||
{
|
||||
// Num of full words to copy
|
||||
int num_of_full_words = length / BIT_ARRAY_BITS;
|
||||
int i;
|
||||
|
||||
int bits_in_last_word = bits_in_top_word(length);
|
||||
|
||||
if (dst == src && srcindx > dstindx)
|
||||
{
|
||||
// Work left to right
|
||||
for (i = 0; i < num_of_full_words; i++)
|
||||
{
|
||||
bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS);
|
||||
_bit_array_set_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS, word);
|
||||
}
|
||||
|
||||
if (bits_in_last_word > 0)
|
||||
{
|
||||
bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx + i * BIT_ARRAY_BITS);
|
||||
bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx + i * BIT_ARRAY_BITS);
|
||||
|
||||
bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word);
|
||||
bit_array_val_t word = bitmask_merge(src_word, dst_word, mask);
|
||||
|
||||
_bit_array_set_word(dst, dst_num_bits, dstindx + num_of_full_words * BIT_ARRAY_BITS, word);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Work right to left
|
||||
for (i = 0; i < num_of_full_words; i++)
|
||||
{
|
||||
bit_array_val_t word = _bit_array_get_word(src, src_num_bits, srcindx + length - (i + 1) * BIT_ARRAY_BITS);
|
||||
_bit_array_set_word(dst, dst_num_bits, dstindx + length - (i + 1) * BIT_ARRAY_BITS, word);
|
||||
}
|
||||
|
||||
if (bits_in_last_word > 0)
|
||||
{
|
||||
bit_array_val_t src_word = _bit_array_get_word(src, src_num_bits, srcindx);
|
||||
bit_array_val_t dst_word = _bit_array_get_word(dst, dst_num_bits, dstindx);
|
||||
|
||||
bit_array_val_t mask = BIT_ARRAY_SUB_MASK(bits_in_last_word);
|
||||
bit_array_val_t word = bitmask_merge(src_word, dst_word, mask);
|
||||
_bit_array_set_word(dst, dst_num_bits, dstindx, word);
|
||||
}
|
||||
}
|
||||
|
||||
_bit_array_mask_top_word(dst, dst_num_bits);
|
||||
}
|
||||
|
||||
// copy all of src to dst. dst is resized to match src.
|
||||
static inline void bit_array_copy_all(bit_array_t *dst, const bit_array_t *src, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Logic operators
|
||||
//
|
||||
|
||||
// Destination can be the same as one or both of the sources
|
||||
static inline void bit_array_and(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
dest[i] = src1[i] & src2[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void bit_array_or(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
dest[i] = src1[i] | src2[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void bit_array_xor(bit_array_t *dest, const bit_array_t *src1, const bit_array_t *src2, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
dest[i] = src1[i] ^ src2[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void bit_array_not(bit_array_t *dest, const bit_array_t *src, int num_bits)
|
||||
{
|
||||
for (int i = 0; i < BIT_ARRAY_BITMAP_SIZE(num_bits); i++)
|
||||
{
|
||||
dest[i] = ~src[i];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Shift array left/right. If fill is zero, filled with 0, otherwise 1
|
||||
//
|
||||
|
||||
// Shift towards LSB / lower index
|
||||
static inline void bit_array_shift_right(bit_array_t *target, int num_bits, int shift_dist, int fill)
|
||||
{
|
||||
if (shift_dist >= num_bits)
|
||||
{
|
||||
fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits);
|
||||
return;
|
||||
}
|
||||
else if (shift_dist == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FillAction action = fill ? FILL_REGION : ZERO_REGION;
|
||||
|
||||
int cpy_length = num_bits - shift_dist;
|
||||
bit_array_copy(target, 0, target, shift_dist, cpy_length, num_bits, num_bits);
|
||||
|
||||
_bit_array_set_region(target, cpy_length, shift_dist, action);
|
||||
}
|
||||
|
||||
// Shift towards MSB / higher index
|
||||
static inline void bit_array_shift_left(bit_array_t *target, int num_bits, int shift_dist, int fill)
|
||||
{
|
||||
if (shift_dist >= num_bits)
|
||||
{
|
||||
fill ? bit_array_set_all(target, num_bits) : bit_array_clear_all(target, num_bits);
|
||||
return;
|
||||
}
|
||||
else if (shift_dist == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FillAction action = fill ? FILL_REGION : ZERO_REGION;
|
||||
|
||||
int cpy_length = num_bits - shift_dist;
|
||||
bit_array_copy(target, shift_dist, target, 0, cpy_length, num_bits, num_bits);
|
||||
_bit_array_set_region(target, 0, shift_dist, action);
|
||||
}
|
||||
|
||||
//
|
||||
// Comparisons
|
||||
//
|
||||
|
||||
// Compare two bit arrays by value stored, with index 0 being the Least
|
||||
// Significant Bit (LSB). Arrays must have the same length.
|
||||
// returns:
|
||||
// >0 iff bitarr1 > bitarr2
|
||||
// 0 iff bitarr1 == bitarr2
|
||||
// <0 iff bitarr1 < bitarr2
|
||||
static inline int bit_array_cmp(const bit_array_t *bitarr1, const bit_array_t *bitarr2, int num_bits)
|
||||
{
|
||||
return memcmp(bitarr1, bitarr2, BIT_ARRAY_BITMAP_SIZE(num_bits) * sizeof(bit_array_val_t));
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* _BIT_ARRAY_H_ */
|
||||
550
ebtn/ebtn.c
Normal file
550
ebtn/ebtn.c
Normal file
@@ -0,0 +1,550 @@
|
||||
#include <string.h>
|
||||
#include "ebtn.h"
|
||||
|
||||
#define EBTN_FLAG_ONPRESS_SENT ((uint16_t)0x0001) /*!< Flag indicates that on-press event has been sent */
|
||||
#define EBTN_FLAG_IN_PROCESS ((uint16_t)0x0002) /*!< Flag indicates that button in process */
|
||||
|
||||
/* Default button group instance */
|
||||
static ebtn_t ebtn_default;
|
||||
|
||||
/**
|
||||
* \brief Process the button information and state
|
||||
*
|
||||
* \param[in] btn: Button instance to process
|
||||
* \param[in] old_state: old state
|
||||
* \param[in] new_state: new state
|
||||
* \param[in] mstime: Current milliseconds system time
|
||||
*/
|
||||
static void prv_process_btn(ebtn_btn_t *btn, uint8_t old_state, uint8_t new_state, ebtn_time_t mstime)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
|
||||
/* Check params set or not. */
|
||||
if (btn->param == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Button state has just changed */
|
||||
if (new_state != old_state)
|
||||
{
|
||||
btn->time_state_change = mstime;
|
||||
|
||||
if (new_state)
|
||||
{
|
||||
btn->flags |= EBTN_FLAG_IN_PROCESS;
|
||||
}
|
||||
}
|
||||
/* Button is still pressed */
|
||||
if (new_state)
|
||||
{
|
||||
/*
|
||||
* Handle debounce and send on-press event
|
||||
*
|
||||
* This is when we detect valid press
|
||||
*/
|
||||
if (!(btn->flags & EBTN_FLAG_ONPRESS_SENT))
|
||||
{
|
||||
/*
|
||||
* Run if statement when:
|
||||
*
|
||||
* - Runtime mode is enabled -> user sets its own config for debounce
|
||||
* - Config debounce time for press is more than `0`
|
||||
*/
|
||||
if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce)
|
||||
{
|
||||
/*
|
||||
* Check mutlti click limit reach or not.
|
||||
*/
|
||||
if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max))
|
||||
{
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK);
|
||||
btn->click_cnt = 0;
|
||||
}
|
||||
|
||||
/* Start with new on-press */
|
||||
btn->flags |= EBTN_FLAG_ONPRESS_SENT;
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONPRESS);
|
||||
|
||||
/* Set keep alive time */
|
||||
btn->keepalive_last_time = mstime;
|
||||
btn->keepalive_cnt = 0;
|
||||
|
||||
btn->time_change = mstime; /* Button state has now changed */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle keep alive, but only if on-press event has been sent
|
||||
*
|
||||
* Keep alive is sent when valid press is being detected
|
||||
*/
|
||||
else
|
||||
{
|
||||
while ((btn->param->time_keepalive_period > 0) && (ebtn_timer_sub(mstime, btn->keepalive_last_time) >= btn->param->time_keepalive_period))
|
||||
{
|
||||
btn->keepalive_last_time += btn->param->time_keepalive_period;
|
||||
++btn->keepalive_cnt;
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_KEEPALIVE);
|
||||
}
|
||||
|
||||
// Scene1: multi click end with a long press, need send onclick event.
|
||||
if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) > btn->param->time_click_pressed_max))
|
||||
{
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK);
|
||||
|
||||
btn->click_cnt = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Button is still released */
|
||||
else
|
||||
{
|
||||
/*
|
||||
* We only need to react if on-press event has even been started.
|
||||
*
|
||||
* Do nothing if that was not the case
|
||||
*/
|
||||
if (btn->flags & EBTN_FLAG_ONPRESS_SENT)
|
||||
{
|
||||
/*
|
||||
* Run if statement when:
|
||||
*
|
||||
* - Runtime mode is enabled -> user sets its own config for debounce
|
||||
* - Config debounce time for release is more than `0`
|
||||
*/
|
||||
if (ebtn_timer_sub(mstime, btn->time_state_change) >= btn->param->time_debounce_release)
|
||||
{
|
||||
/* Handle on-release event */
|
||||
btn->flags &= ~EBTN_FLAG_ONPRESS_SENT;
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONRELEASE);
|
||||
|
||||
/* Check time validity for click event */
|
||||
if (ebtn_timer_sub(mstime, btn->time_change) >= btn->param->time_click_pressed_min &&
|
||||
ebtn_timer_sub(mstime, btn->time_change) <= btn->param->time_click_pressed_max)
|
||||
{
|
||||
++btn->click_cnt;
|
||||
|
||||
btn->click_last_time = mstime;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scene2: If last press was too short, and previous sequence of clicks was
|
||||
// positive, send event to user.
|
||||
if ((btn->click_cnt > 0) && (ebtn_timer_sub(mstime, btn->time_change) < btn->param->time_click_pressed_min))
|
||||
{
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK);
|
||||
}
|
||||
/*
|
||||
* There was an on-release event, but timing
|
||||
* for click event detection is outside allowed window.
|
||||
*
|
||||
* Reset clicks counter -> not valid sequence for click event.
|
||||
*/
|
||||
btn->click_cnt = 0;
|
||||
}
|
||||
|
||||
// Scene3: this part will send on-click event immediately after release event, if
|
||||
// maximum number of consecutive clicks has been reached.
|
||||
if ((btn->click_cnt > 0) && (btn->click_cnt == btn->param->max_consecutive))
|
||||
{
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK);
|
||||
btn->click_cnt = 0;
|
||||
}
|
||||
|
||||
btn->time_change = mstime; /* Button state has now changed */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Based on te configuration, this part of the code
|
||||
* will send on-click event after certain timeout.
|
||||
*
|
||||
* This feature is useful if users prefers multi-click feature
|
||||
* that is reported only after last click event happened,
|
||||
* including number of clicks made by user
|
||||
*/
|
||||
if (btn->click_cnt > 0)
|
||||
{
|
||||
if (ebtn_timer_sub(mstime, btn->click_last_time) >= btn->param->time_click_multi_max)
|
||||
{
|
||||
ebtobj->evt_fn(btn, EBTN_EVT_ONCLICK);
|
||||
btn->click_cnt = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// check button in process
|
||||
if (btn->flags & EBTN_FLAG_IN_PROCESS)
|
||||
{
|
||||
btn->flags &= ~EBTN_FLAG_IN_PROCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
|
||||
if (evt_fn == NULL || get_state_fn == NULL /* Parameter is a must only in callback-only mode */
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(ebtobj, 0x00, sizeof(*ebtobj));
|
||||
ebtobj->btns = btns;
|
||||
ebtobj->btns_cnt = btns_cnt;
|
||||
ebtobj->btns_combo = btns_combo;
|
||||
ebtobj->btns_combo_cnt = btns_combo_cnt;
|
||||
ebtobj->evt_fn = evt_fn;
|
||||
ebtobj->get_state_fn = get_state_fn;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get all button state with get_state_fn.
|
||||
*
|
||||
* \param[out] state_array: store the button state
|
||||
*/
|
||||
static void ebtn_get_current_state(bit_array_t *state_array)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
ebtn_btn_dyn_t *target;
|
||||
int i;
|
||||
|
||||
/* Process all buttons */
|
||||
for (i = 0; i < ebtobj->btns_cnt; ++i)
|
||||
{
|
||||
/* Get button state */
|
||||
uint8_t new_state = ebtobj->get_state_fn(&ebtobj->btns[i]);
|
||||
// save state
|
||||
bit_array_assign(state_array, i, new_state);
|
||||
}
|
||||
|
||||
for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++)
|
||||
{
|
||||
/* Get button state */
|
||||
uint8_t new_state = ebtobj->get_state_fn(&target->btn);
|
||||
|
||||
// save state
|
||||
bit_array_assign(state_array, i, new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Process the button state
|
||||
*
|
||||
* \param[in] btn: Button instance to process
|
||||
* \param[in] old_state: all button old state
|
||||
* \param[in] curr_state: all button current state
|
||||
* \param[in] idx: Button internal key_idx
|
||||
* \param[in] mstime: Current milliseconds system time
|
||||
*/
|
||||
static void ebtn_process_btn(ebtn_btn_t *btn, bit_array_t *old_state, bit_array_t *curr_state, int idx, ebtn_time_t mstime)
|
||||
{
|
||||
prv_process_btn(btn, bit_array_get(old_state, idx), bit_array_get(curr_state, idx), mstime);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Process the combo-button state
|
||||
*
|
||||
* \param[in] btn: Button instance to process
|
||||
* \param[in] old_state: all button old state
|
||||
* \param[in] curr_state: all button current state
|
||||
* \param[in] comb_key: Combo key
|
||||
* \param[in] mstime: Current milliseconds system time
|
||||
*/
|
||||
static void ebtn_process_btn_combo(ebtn_btn_t *btn, bit_array_t *old_state, bit_array_t *curr_state, bit_array_t *comb_key, ebtn_time_t mstime)
|
||||
{
|
||||
BIT_ARRAY_DEFINE(tmp_data, EBTN_MAX_KEYNUM) = {0};
|
||||
|
||||
if (bit_array_num_bits_set(comb_key, EBTN_MAX_KEYNUM) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bit_array_and(tmp_data, curr_state, comb_key, EBTN_MAX_KEYNUM);
|
||||
uint8_t curr = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0;
|
||||
|
||||
bit_array_and(tmp_data, old_state, comb_key, EBTN_MAX_KEYNUM);
|
||||
uint8_t old = bit_array_cmp(tmp_data, comb_key, EBTN_MAX_KEYNUM) == 0;
|
||||
|
||||
prv_process_btn(btn, old, curr, mstime);
|
||||
}
|
||||
|
||||
void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
ebtn_btn_dyn_t *target;
|
||||
ebtn_btn_combo_dyn_t *target_combo;
|
||||
int i;
|
||||
|
||||
/* Process all buttons */
|
||||
for (i = 0; i < ebtobj->btns_cnt; ++i)
|
||||
{
|
||||
ebtn_process_btn(&ebtobj->btns[i], ebtobj->old_state, curr_state, i, mstime);
|
||||
}
|
||||
|
||||
for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++)
|
||||
{
|
||||
ebtn_process_btn(&target->btn, ebtobj->old_state, curr_state, i, mstime);
|
||||
}
|
||||
|
||||
/* Process all comb buttons */
|
||||
for (i = 0; i < ebtobj->btns_combo_cnt; ++i)
|
||||
{
|
||||
ebtn_process_btn_combo(&ebtobj->btns_combo[i].btn, ebtobj->old_state, curr_state, ebtobj->btns_combo[i].comb_key, mstime);
|
||||
}
|
||||
|
||||
for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next)
|
||||
{
|
||||
ebtn_process_btn_combo(&target_combo->btn.btn, ebtobj->old_state, curr_state, target_combo->btn.comb_key, mstime);
|
||||
}
|
||||
|
||||
bit_array_copy_all(ebtobj->old_state, curr_state, EBTN_MAX_KEYNUM);
|
||||
}
|
||||
|
||||
void ebtn_process(ebtn_time_t mstime)
|
||||
{
|
||||
BIT_ARRAY_DEFINE(curr_state, EBTN_MAX_KEYNUM) = {0};
|
||||
|
||||
// Get Current State
|
||||
ebtn_get_current_state(curr_state);
|
||||
|
||||
ebtn_process_with_curr_state(curr_state, mstime);
|
||||
}
|
||||
|
||||
int ebtn_get_total_btn_cnt(void)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
int total_cnt = 0;
|
||||
ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head;
|
||||
|
||||
total_cnt += ebtobj->btns_cnt;
|
||||
|
||||
while (curr)
|
||||
{
|
||||
total_cnt++;
|
||||
curr = curr->next;
|
||||
}
|
||||
return total_cnt;
|
||||
}
|
||||
|
||||
int ebtn_get_btn_index_by_key_id(uint16_t key_id)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
int i = 0;
|
||||
ebtn_btn_dyn_t *target;
|
||||
|
||||
for (i = 0; i < ebtobj->btns_cnt; ++i)
|
||||
{
|
||||
if (ebtobj->btns[i].key_id == key_id)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++)
|
||||
{
|
||||
if (target->btn.key_id == key_id)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
int i = 0;
|
||||
ebtn_btn_dyn_t *target;
|
||||
|
||||
for (i = 0; i < ebtobj->btns_cnt; ++i)
|
||||
{
|
||||
if (ebtobj->btns[i].key_id == key_id)
|
||||
{
|
||||
return &ebtobj->btns[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++)
|
||||
{
|
||||
if (target->btn.key_id == key_id)
|
||||
{
|
||||
return &target->btn;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn)
|
||||
{
|
||||
return ebtn_get_btn_index_by_key_id(btn->key_id);
|
||||
}
|
||||
|
||||
int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn)
|
||||
{
|
||||
return ebtn_get_btn_index_by_key_id(btn->btn.key_id);
|
||||
}
|
||||
|
||||
void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx)
|
||||
{
|
||||
bit_array_set(btn->comb_key, idx);
|
||||
}
|
||||
|
||||
void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx)
|
||||
{
|
||||
bit_array_clear(btn->comb_key, idx);
|
||||
}
|
||||
|
||||
void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id)
|
||||
{
|
||||
int idx = ebtn_get_btn_index_by_key_id(key_id);
|
||||
if (idx < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ebtn_combo_btn_add_btn_by_idx(btn, idx);
|
||||
}
|
||||
|
||||
void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id)
|
||||
{
|
||||
int idx = ebtn_get_btn_index_by_key_id(key_id);
|
||||
if (idx < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ebtn_combo_btn_remove_btn_by_idx(btn, idx);
|
||||
}
|
||||
|
||||
int ebtn_is_btn_active(const ebtn_btn_t *btn)
|
||||
{
|
||||
return btn != NULL && (btn->flags & EBTN_FLAG_ONPRESS_SENT);
|
||||
}
|
||||
|
||||
int ebtn_is_btn_in_process(const ebtn_btn_t *btn)
|
||||
{
|
||||
return btn != NULL && (btn->flags & EBTN_FLAG_IN_PROCESS);
|
||||
}
|
||||
|
||||
int ebtn_is_in_process(void)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
ebtn_btn_dyn_t *target;
|
||||
ebtn_btn_combo_dyn_t *target_combo;
|
||||
int i;
|
||||
|
||||
/* Process all buttons */
|
||||
for (i = 0; i < ebtobj->btns_cnt; ++i)
|
||||
{
|
||||
if (ebtn_is_btn_in_process(&ebtobj->btns[i]))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (target = ebtobj->btn_dyn_head, i = ebtobj->btns_cnt; target; target = target->next, i++)
|
||||
{
|
||||
if (ebtn_is_btn_in_process(&target->btn))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process all comb buttons */
|
||||
for (i = 0; i < ebtobj->btns_combo_cnt; ++i)
|
||||
{
|
||||
if (ebtn_is_btn_in_process(&ebtobj->btns_combo[i].btn))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (target_combo = ebtobj->btn_combo_dyn_head; target_combo; target_combo = target_combo->next)
|
||||
{
|
||||
if (ebtn_is_btn_in_process(&target_combo->btn.btn))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ebtn_register(ebtn_btn_dyn_t *button)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
|
||||
ebtn_btn_dyn_t *curr = ebtobj->btn_dyn_head;
|
||||
ebtn_btn_dyn_t *last = NULL;
|
||||
|
||||
if (!button)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ebtn_get_total_btn_cnt() >= EBTN_MAX_KEYNUM)
|
||||
{
|
||||
return 0; /* reach max cnt. */
|
||||
}
|
||||
|
||||
if (curr == NULL)
|
||||
{
|
||||
ebtobj->btn_dyn_head = button;
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (curr)
|
||||
{
|
||||
if (curr == button)
|
||||
{
|
||||
return 0; /* already exist. */
|
||||
}
|
||||
last = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
last->next = button;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ebtn_combo_register(ebtn_btn_combo_dyn_t *button)
|
||||
{
|
||||
ebtn_t *ebtobj = &ebtn_default;
|
||||
|
||||
ebtn_btn_combo_dyn_t *curr = ebtobj->btn_combo_dyn_head;
|
||||
ebtn_btn_combo_dyn_t *last = NULL;
|
||||
|
||||
if (!button)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (curr == NULL)
|
||||
{
|
||||
ebtobj->btn_combo_dyn_head = button;
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (curr)
|
||||
{
|
||||
if (curr == button)
|
||||
{
|
||||
return 0; /* already exist. */
|
||||
}
|
||||
last = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
last->next = button;
|
||||
|
||||
return 1;
|
||||
}
|
||||
463
ebtn/ebtn.h
Normal file
463
ebtn/ebtn.h
Normal file
@@ -0,0 +1,463 @@
|
||||
#ifndef _EBTN_H
|
||||
#define _EBTN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bit_array.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
// #define EBTN_CONFIG_TIMER_16
|
||||
|
||||
// here can change to uint16_t, if you want reduce RAM size.
|
||||
#ifdef EBTN_CONFIG_TIMER_16
|
||||
typedef uint16_t ebtn_time_t;
|
||||
typedef int16_t ebtn_time_sign_t;
|
||||
#else
|
||||
typedef uint32_t ebtn_time_t;
|
||||
typedef int32_t ebtn_time_sign_t;
|
||||
#endif
|
||||
|
||||
/* Forward declarations */
|
||||
struct ebtn_btn;
|
||||
struct ebtn;
|
||||
|
||||
#define EBTN_MAX_KEYNUM (64)
|
||||
|
||||
/**
|
||||
* \brief List of button events
|
||||
*
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
EBTN_EVT_ONPRESS = 0x00, /*!< On press event - sent when valid press is detected */
|
||||
EBTN_EVT_ONRELEASE, /*!< On release event - sent when valid release event is detected (from
|
||||
active to inactive) */
|
||||
EBTN_EVT_ONCLICK, /*!< On Click event - sent when valid sequence of on-press and on-release
|
||||
events occurs */
|
||||
EBTN_EVT_KEEPALIVE, /*!< Keep alive event - sent periodically when button is active */
|
||||
} ebtn_evt_t;
|
||||
|
||||
/**
|
||||
* @brief Returns the difference between two absolute times: time1-time2.
|
||||
* @param[in] time1: Absolute time expressed in internal time units.
|
||||
* @param[in] time2: Absolute time expressed in internal time units.
|
||||
* @return resulting signed relative time expressed in internal time units.
|
||||
*/
|
||||
static inline ebtn_time_sign_t ebtn_timer_sub(ebtn_time_t time1, ebtn_time_t time2)
|
||||
{
|
||||
return time1 - time2;
|
||||
}
|
||||
|
||||
// test time overflow error
|
||||
// #define ebtn_timer_sub(time1, time2) (time1 - time2)
|
||||
|
||||
/**
|
||||
* \brief Button event function callback prototype
|
||||
* \param[in] btn: Button instance from array for which event occured
|
||||
* \param[in] evt: Event type
|
||||
*/
|
||||
typedef void (*ebtn_evt_fn)(struct ebtn_btn *btn, ebtn_evt_t evt);
|
||||
|
||||
/**
|
||||
* \brief Get button/input state callback function
|
||||
*
|
||||
* \param[in] btn: Button instance from array to read state
|
||||
* \return `1` when button is considered `active`, `0` otherwise
|
||||
*/
|
||||
typedef uint8_t (*ebtn_get_state_fn)(struct ebtn_btn *btn);
|
||||
|
||||
/**
|
||||
* \brief Button Params structure
|
||||
*/
|
||||
typedef struct ebtn_btn_param
|
||||
{
|
||||
/**
|
||||
* \brief Minimum debounce time for press event in units of milliseconds
|
||||
*
|
||||
* This is the time when the input shall have stable active level to detect valid *onpress*
|
||||
* event.
|
||||
*
|
||||
* When value is set to `> 0`, input must be in active state for at least
|
||||
* minimum milliseconds time, before valid *onpress* event is detected.
|
||||
*
|
||||
* \note If value is set to `0`, debounce is not used and *press* event will be
|
||||
* triggered immediately when input states goes to *inactive* state.
|
||||
*
|
||||
* To be safe not using this feature, external logic must ensure stable
|
||||
* transition at input level.
|
||||
*
|
||||
*/
|
||||
uint16_t time_debounce; /*!< Debounce time in milliseconds */
|
||||
|
||||
/**
|
||||
* \brief Minimum debounce time for release event in units of milliseconds
|
||||
*
|
||||
* This is the time when the input shall have minimum stable released level to detect valid
|
||||
* *onrelease* event.
|
||||
*
|
||||
* This setting can be useful if application wants to protect against
|
||||
* unwanted glitches on the line when input is considered "active".
|
||||
*
|
||||
* When value is set to `> 0`, input must be in inactive low for at least
|
||||
* minimum milliseconds time, before valid *onrelease* event is detected
|
||||
*
|
||||
* \note If value is set to `0`, debounce is not used and *release* event will be
|
||||
* triggered immediately when input states goes to *inactive* state
|
||||
*
|
||||
*/
|
||||
uint16_t time_debounce_release; /*!< Debounce time in milliseconds for release event */
|
||||
|
||||
/**
|
||||
* \brief Minimum active input time for valid click event, in milliseconds
|
||||
*
|
||||
* Input shall be in active state (after debounce) at least this amount of time to even consider
|
||||
* the potential valid click event. Set the value to `0` to disable this feature
|
||||
*
|
||||
*/
|
||||
uint16_t time_click_pressed_min; /*!< Minimum pressed time for valid click event */
|
||||
|
||||
/**
|
||||
* \brief Maximum active input time for valid click event, in milliseconds
|
||||
*
|
||||
* Input shall be pressed at most this amount of time to still trigger valid click.
|
||||
* Set to `-1` to allow any time triggering click event.
|
||||
*
|
||||
* When input is active for more than the configured time, click even is not detected and is
|
||||
* ignored.
|
||||
*
|
||||
*/
|
||||
uint16_t time_click_pressed_max; /*!< Maximum pressed time for valid click event*/
|
||||
|
||||
/**
|
||||
* \brief Maximum allowed time between last on-release and next valid on-press,
|
||||
* to still allow multi-click events, in milliseconds
|
||||
*
|
||||
* This value is also used as a timeout length to send the *onclick* event to application from
|
||||
* previously detected valid click events.
|
||||
*
|
||||
* If application relies on multi consecutive clicks, this is the max time to allow user
|
||||
* to trigger potential new click, or structure will get reset (before sent to user if any
|
||||
* clicks have been detected so far)
|
||||
*
|
||||
*/
|
||||
uint16_t time_click_multi_max; /*!< Maximum time between 2 clicks to be considered consecutive
|
||||
click */
|
||||
|
||||
/**
|
||||
* \brief Keep-alive event period, in milliseconds
|
||||
*
|
||||
* When input is active, keep alive events will be sent through this period of time.
|
||||
* First keep alive will be sent after input being considered
|
||||
* active.
|
||||
*
|
||||
*/
|
||||
uint16_t time_keepalive_period; /*!< Time in ms for periodic keep alive event */
|
||||
|
||||
/**
|
||||
* \brief Maximum number of allowed consecutive click events,
|
||||
* before structure gets reset to default value.
|
||||
*
|
||||
* \note When consecutive value is reached, application will get notification of
|
||||
* clicks. This can be executed immediately after last click has been detected, or after
|
||||
* standard timeout (unless next on-press has already been detected, then it is send to
|
||||
* application just before valid next press event).
|
||||
*
|
||||
*/
|
||||
uint16_t max_consecutive; /*!< Max number of consecutive clicks */
|
||||
} ebtn_btn_param_t;
|
||||
|
||||
#define EBTN_PARAMS_INIT(_time_debounce, _time_debounce_release, _time_click_pressed_min, _time_click_pressed_max, _time_click_multi_max, \
|
||||
_time_keepalive_period, _max_consecutive) \
|
||||
{ \
|
||||
.time_debounce = _time_debounce, .time_debounce_release = _time_debounce_release, .time_click_pressed_min = _time_click_pressed_min, \
|
||||
.time_click_pressed_max = _time_click_pressed_max, .time_click_multi_max = _time_click_multi_max, .time_keepalive_period = _time_keepalive_period, \
|
||||
.max_consecutive = _max_consecutive \
|
||||
}
|
||||
|
||||
#define EBTN_BUTTON_INIT(_key_id, _param) \
|
||||
{ \
|
||||
.key_id = _key_id, .param = _param, \
|
||||
}
|
||||
|
||||
#define EBTN_BUTTON_DYN_INIT(_key_id, _param) \
|
||||
{ \
|
||||
.next = NULL, .btn = EBTN_BUTTON_INIT(_key_id, _param), \
|
||||
}
|
||||
|
||||
#define EBTN_BUTTON_COMBO_INIT(_key_id, _param) \
|
||||
{ \
|
||||
.comb_key = {0}, .btn = EBTN_BUTTON_INIT(_key_id, _param), \
|
||||
}
|
||||
|
||||
#define EBTN_BUTTON_COMBO_DYN_INIT(_key_id, _param) \
|
||||
{ \
|
||||
.next = NULL, .btn = EBTN_BUTTON_COMBO_INIT(_key_id, _param), \
|
||||
}
|
||||
|
||||
#define EBTN_ARRAY_SIZE(_arr) sizeof(_arr) / sizeof((_arr)[0])
|
||||
|
||||
/**
|
||||
* \brief Button structure
|
||||
*/
|
||||
typedef struct ebtn_btn
|
||||
{
|
||||
uint16_t key_id; /*!< User defined custom argument for callback function purpose */
|
||||
uint16_t flags; /*!< Private button flags management */
|
||||
ebtn_time_t time_change; /*!< Time in ms when button state got changed last time after valid
|
||||
debounce */
|
||||
ebtn_time_t time_state_change; /*!< Time in ms when button state got changed last time */
|
||||
|
||||
ebtn_time_t keepalive_last_time; /*!< Time in ms of last send keep alive event */
|
||||
ebtn_time_t click_last_time; /*!< Time in ms of last successfully detected (not sent!) click event
|
||||
*/
|
||||
|
||||
uint16_t keepalive_cnt; /*!< Number of keep alive events sent after successful on-press
|
||||
detection. Value is reset after on-release */
|
||||
uint16_t click_cnt; /*!< Number of consecutive clicks detected, respecting maximum timeout
|
||||
between clicks */
|
||||
|
||||
const ebtn_btn_param_t *param;
|
||||
} ebtn_btn_t;
|
||||
|
||||
/**
|
||||
* \brief ComboButton structure
|
||||
*/
|
||||
typedef struct ebtn_btn_combo
|
||||
{
|
||||
BIT_ARRAY_DEFINE(comb_key, EBTN_MAX_KEYNUM); /*!< select key index - `1` means active, `0` means inactive */
|
||||
|
||||
ebtn_btn_t btn;
|
||||
} ebtn_btn_combo_t;
|
||||
|
||||
/**
|
||||
* \brief Dynamic Button structure
|
||||
*/
|
||||
typedef struct ebtn_btn_dyn
|
||||
{
|
||||
struct ebtn_btn_dyn *next; /*!< point to next button */
|
||||
|
||||
ebtn_btn_t btn;
|
||||
} ebtn_btn_dyn_t;
|
||||
|
||||
/**
|
||||
* \brief Dynamic ComboButton structure
|
||||
*/
|
||||
typedef struct ebtn_btn_combo_dyn
|
||||
{
|
||||
struct ebtn_btn_combo_dyn *next; /*!< point to next combo-button */
|
||||
|
||||
ebtn_btn_combo_t btn;
|
||||
} ebtn_btn_combo_dyn_t;
|
||||
|
||||
/**
|
||||
* \brief easy_button group structure
|
||||
*/
|
||||
typedef struct ebtn
|
||||
{
|
||||
ebtn_btn_t *btns; /*!< Pointer to buttons array */
|
||||
uint16_t btns_cnt; /*!< Number of buttons in array */
|
||||
ebtn_btn_combo_t *btns_combo; /*!< Pointer to comb-buttons array */
|
||||
uint16_t btns_combo_cnt; /*!< Number of comb-buttons in array */
|
||||
|
||||
ebtn_btn_dyn_t *btn_dyn_head; /*!< Pointer to btn-dynamic list */
|
||||
ebtn_btn_combo_dyn_t *btn_combo_dyn_head; /*!< Pointer to btn-combo-dynamic list */
|
||||
|
||||
ebtn_evt_fn evt_fn; /*!< Pointer to event function */
|
||||
ebtn_get_state_fn get_state_fn; /*!< Pointer to get state function */
|
||||
|
||||
BIT_ARRAY_DEFINE(old_state, EBTN_MAX_KEYNUM); /*!< Old button state - `1` means active, `0` means inactive */
|
||||
} ebtn_t;
|
||||
|
||||
/**
|
||||
* \brief Button processing function, that reads the inputs and makes actions accordingly.
|
||||
*
|
||||
*
|
||||
* \param[in] mstime: Current system time in milliseconds
|
||||
*/
|
||||
void ebtn_process(ebtn_time_t mstime);
|
||||
|
||||
/**
|
||||
* \brief Button processing function, with all button input state.
|
||||
*
|
||||
* \param[in] curr_state: Current all button input state
|
||||
* \param[in] mstime: Current system time in milliseconds
|
||||
*/
|
||||
void ebtn_process_with_curr_state(bit_array_t *curr_state, ebtn_time_t mstime);
|
||||
|
||||
/**
|
||||
* \brief Check if button is active.
|
||||
* Active is considered when initial debounce period has been a pass.
|
||||
* This is the period between on-press and on-release events.
|
||||
*
|
||||
* \param[in] btn: Button handle to check
|
||||
* \return `1` if active, `0` otherwise
|
||||
*/
|
||||
int ebtn_is_btn_active(const ebtn_btn_t *btn);
|
||||
|
||||
/**
|
||||
* \brief Check if button is in process.
|
||||
* Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep.
|
||||
*
|
||||
* \param[in] btn: Button handle to check
|
||||
* \return `1` if in process, `0` otherwise
|
||||
*/
|
||||
int ebtn_is_btn_in_process(const ebtn_btn_t *btn);
|
||||
|
||||
/**
|
||||
* \brief Check if some button is in process.
|
||||
* Used for low-power processing, indicating that the buttons are temporarily idle, and embedded systems can consider entering deep sleep.
|
||||
*
|
||||
* \return `1` if in process, `0` otherwise
|
||||
*/
|
||||
int ebtn_is_in_process(void);
|
||||
|
||||
/**
|
||||
* \brief Initialize button manager
|
||||
* \param[in] btns: Array of buttons to process
|
||||
* \param[in] btns_cnt: Number of buttons to process
|
||||
* \param[in] btns_combo: Array of combo-buttons to process
|
||||
* \param[in] btns_combo_cnt: Number of combo-buttons to process
|
||||
* \param[in] get_state_fn: Pointer to function providing button state on demand.
|
||||
* \param[in] evt_fn: Button event function callback
|
||||
*
|
||||
* \return `1` on success, `0` otherwise
|
||||
*/
|
||||
int ebtn_init(ebtn_btn_t *btns, uint16_t btns_cnt, ebtn_btn_combo_t *btns_combo, uint16_t btns_combo_cnt, ebtn_get_state_fn get_state_fn, ebtn_evt_fn evt_fn);
|
||||
|
||||
/**
|
||||
* @brief Register a dynamic button
|
||||
*
|
||||
* @param button: Dynamic button structure instance
|
||||
* \return `1` on success, `0` otherwise
|
||||
*/
|
||||
int ebtn_register(ebtn_btn_dyn_t *button);
|
||||
|
||||
/**
|
||||
* \brief Register a dynamic combo-button
|
||||
* \param[in] button: Dynamic combo-button structure instance
|
||||
*
|
||||
* \return `1` on success, `0` otherwise
|
||||
*/
|
||||
int ebtn_combo_register(ebtn_btn_combo_dyn_t *button);
|
||||
|
||||
/**
|
||||
* \brief Get the current total button cnt
|
||||
*
|
||||
* \return size of button.
|
||||
*/
|
||||
int ebtn_get_total_btn_cnt(void);
|
||||
|
||||
/**
|
||||
* \brief Get the internal key_idx of the key_id
|
||||
* \param[in] key_id: key_id
|
||||
*
|
||||
* \return '-1' on error, other is key_idx
|
||||
*/
|
||||
int ebtn_get_btn_index_by_key_id(uint16_t key_id);
|
||||
|
||||
/**
|
||||
* \brief Get the internal btn instance of the key_id, here is the button instance, and what is dynamically registered is also to obtain its button
|
||||
* instance
|
||||
*
|
||||
* \param[in] key_id: key_id
|
||||
*
|
||||
* \return 'NULL' on error, other is button instance
|
||||
*/
|
||||
ebtn_btn_t *ebtn_get_btn_by_key_id(uint16_t key_id);
|
||||
|
||||
/**
|
||||
* \brief Get the internal key_idx of the button
|
||||
* \param[in] btn: Button
|
||||
*
|
||||
* \return '-1' on error, other is key_idx
|
||||
*/
|
||||
int ebtn_get_btn_index_by_btn(ebtn_btn_t *btn);
|
||||
|
||||
/**
|
||||
* \brief Get the internal key_idx of the dynamic button
|
||||
* \param[in] btn: Button
|
||||
*
|
||||
* \return '-1' on error, other is key_idx
|
||||
*/
|
||||
int ebtn_get_btn_index_by_btn_dyn(ebtn_btn_dyn_t *btn);
|
||||
|
||||
/**
|
||||
* \brief Bind combo-button key with key_idx
|
||||
* \param[in] btn: Combo Button
|
||||
* \param[in] idx: key_idx
|
||||
*
|
||||
*/
|
||||
void ebtn_combo_btn_add_btn_by_idx(ebtn_btn_combo_t *btn, int idx);
|
||||
|
||||
/**
|
||||
* \brief Remove combo-button key with key_idx
|
||||
* \param[in] btn: Combo Button
|
||||
* \param[in] idx: key_idx
|
||||
*
|
||||
*/
|
||||
void ebtn_combo_btn_remove_btn_by_idx(ebtn_btn_combo_t *btn, int idx);
|
||||
|
||||
/**
|
||||
* \brief Bind combo-button key with key_id, make sure key_id(button) is already register.
|
||||
* \param[in] btn: Combo Button
|
||||
* \param[in] key_id: key_id
|
||||
*
|
||||
*/
|
||||
void ebtn_combo_btn_add_btn(ebtn_btn_combo_t *btn, uint16_t key_id);
|
||||
|
||||
/**
|
||||
* \brief Remove combo-button key with key_id, make sure key_id(button) is already
|
||||
* register. \param[in] btn: Combo Button \param[in] key_id: key_id
|
||||
*
|
||||
*/
|
||||
void ebtn_combo_btn_remove_btn(ebtn_btn_combo_t *btn, uint16_t key_id);
|
||||
|
||||
/**
|
||||
* \brief Get keep alive period for specific button
|
||||
* \param[in] btn: Button instance to get keep alive period for
|
||||
* \return Keep alive period in `ms`
|
||||
*/
|
||||
#define ebtn_keepalive_get_period(btn) ((btn)->time_keepalive_period)
|
||||
|
||||
/**
|
||||
* \brief Get actual number of keep alive counts since the last on-press event.
|
||||
* It is set to `0` if btn isn't pressed
|
||||
* \param[in] btn: Button instance to get keep alive period for
|
||||
* \return Number of keep alive events since on-press event
|
||||
*/
|
||||
#define ebtn_keepalive_get_count(btn) ((btn)->keepalive_cnt)
|
||||
|
||||
/**
|
||||
* \brief Get number of keep alive counts for specific required time in milliseconds.
|
||||
* It will calculate number of keepalive ticks specific button shall make,
|
||||
* before requested time is reached.
|
||||
*
|
||||
* Result of the function can be used with \ref ebtn_keepalive_get_count which returns
|
||||
* actual number of keep alive counts since last on-press event of the button.
|
||||
*
|
||||
* \note Value is always integer aligned, with granularity of one keepalive time period
|
||||
* \note Implemented as macro, as it may be optimized by compiler when static keep alive
|
||||
* is used
|
||||
*
|
||||
* \param[in] btn: Button to use for check
|
||||
* \param[in] ms_time: Time in ms to calculate number of keep alive counts
|
||||
* \return Number of keep alive counts
|
||||
*/
|
||||
#define ebtn_keepalive_get_count_for_time(btn, ms_time) ((ms_time) / ebtn_keepalive_get_period(btn))
|
||||
|
||||
/**
|
||||
* \brief Get number of consecutive click events on a button
|
||||
* \param[in] btn: Button instance to get number of clicks
|
||||
* \return Number of consecutive clicks on a button
|
||||
*/
|
||||
#define ebtn_click_get_count(btn) ((btn)->click_cnt)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* _EBTN_H */
|
||||
Reference in New Issue
Block a user