Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions bits_button.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "bits_button.h"
#include "string.h"
#include <string.h>

static bits_button_t bits_btn_entity;
static bits_btn_debug_printf_func debug_printf = NULL;
Expand Down Expand Up @@ -96,7 +96,9 @@ static size_t get_bits_btn_buffer_used_count_c11(void)

static size_t get_bits_btn_buffer_capacity_c11(void)
{
return BITS_BTN_BUFFER_SIZE;
// A circular buffer needs to reserve 1 empty slot to distinguish between full and empty states,
// so the actual available capacity is SIZE - 1.
return BITS_BTN_BUFFER_SIZE - 1;
}

/**
Expand All @@ -107,8 +109,8 @@ static void bits_btn_clear_buffer_c11(void)
{
bits_btn_ring_buffer_t *buf = &ring_buffer;

atomic_store_explicit(&buf->read_idx, 0, memory_order_relaxed);
atomic_store_explicit(&buf->write_idx, 0, memory_order_relaxed);
atomic_store_explicit(&buf->write_idx, 0, memory_order_release);
atomic_store_explicit(&buf->read_idx, 0, memory_order_release);
}

static size_t get_bits_btn_buffer_overwrite_count_c11(void)
Expand Down Expand Up @@ -161,20 +163,21 @@ static uint8_t bits_btn_write_buffer_overwrite_c11(bits_btn_result_t *result)
// Get the current write position
size_t current_write = atomic_load_explicit(&buf->write_idx, memory_order_relaxed);
size_t next_write = (current_write + 1) % BITS_BTN_BUFFER_SIZE;
size_t current_read = atomic_load_explicit(&buf->read_idx, memory_order_acquire);

// Get the current read position (ensure the latest value is seen)
size_t current_read = atomic_load_explicit(&buf->read_idx, memory_order_consume);

buf->buffer[current_write] = *result;

// Advance the read pointer when the buffer is full
if (next_write == current_read) {
atomic_fetch_add_explicit(&overwrite_count, 1, memory_order_relaxed);
size_t new_read = (current_read + 1) % BITS_BTN_BUFFER_SIZE;

atomic_store_explicit(&buf->read_idx, new_read, memory_order_release);
current_read = new_read;
}

// Write data (space is guaranteed to be safe at this point)
buf->buffer[current_write] = *result;

// Update the write pointer (ensure data is visible before index update)
atomic_store_explicit(&buf->write_idx, next_write, memory_order_release);
return true;
Expand Down Expand Up @@ -407,14 +410,23 @@ int32_t bits_button_init(button_obj_t* btns
bits_button_t *button = &bits_btn_entity;
debug_printf = bis_btn_debug_printf;

if (btns == NULL || read_button_level_func == NULL ||
(btns_combo_cnt > 0 && btns_combo == NULL))
if ((btns == NULL)
|| (btns_cnt == 0)
|| (read_button_level_func == NULL)
|| (btns_combo_cnt > 0 && btns_combo == NULL))
{
if(debug_printf)
debug_printf("Invalid init parameters !\n");
return -2;
}

if (btns_cnt > BITS_BTN_MAX_BUTTONS)
{
if (debug_printf)
debug_printf("Error: Too many buttons (%d > max %d)\n", btns_cnt, (int)BITS_BTN_MAX_BUTTONS);
return -5;
}

memset(button, 0, sizeof(bits_button_t));

button->btns = btns;
Expand All @@ -434,6 +446,28 @@ int32_t bits_button_init(button_obj_t* btns
return -3;
}

// Check single button param pointers
for (uint16_t i = 0; i < btns_cnt; i++)
{
if (btns[i].param == NULL)
{
if (debug_printf)
debug_printf("Error: Button[%d] param is NULL\n", i);
return -6;
}
}

// Check combo button param pointers
for (uint16_t i = 0; i < btns_combo_cnt; i++)
{
if (btns_combo[i].btn.param == NULL)
{
if (debug_printf)
debug_printf("Error: Combo button[%d] param is NULL\n", i);
return -7;
}
}

for(uint16_t i = 0; i < btns_combo_cnt; i++)
{
button_obj_combo_t *combo = &button->btns_combo[i];
Expand Down
25 changes: 19 additions & 6 deletions bits_button.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
extern "C" {
#endif

#include "stdint.h"
#include "stdio.h"
#include <stdint.h>
#include <stdio.h>

#ifndef BITS_BTN_MAX_COMBO_BUTTONS
#define BITS_BTN_MAX_COMBO_BUTTONS 8 // 默认最大支持8个组合按钮
Expand All @@ -16,6 +16,9 @@ typedef uint32_t key_value_type_t;
typedef uint32_t state_bits_type_t;
typedef state_bits_type_t button_mask_type_t;

// Maximum number of buttons based on mask type size
#define BITS_BTN_MAX_BUTTONS (sizeof(button_mask_type_t) * 8)


typedef enum {
BTN_STATE_IDLE ,
Expand Down Expand Up @@ -169,12 +172,16 @@ typedef struct
* This function is called when a button event (press, release, etc.) is detected.
* @param bis_btn_debug_printf: Function pointer for debug logging. Pass NULL if debug output is not needed.
*
* @retval Status code indicating the result of the initialization:
* @retval Status code indicating the result of the initialization:
* - 0: Success. All parameters are valid, and the button system is initialized.
* - -1: Invalid key ID in combination button configuration. A key ID specified in a
* combination button configuration does not exist in the single button array.
* - -2: Invalid input parameters. Returned if either `btns` or `read_button_level_func` is NULL.
* - -3: Too many combo buttons. The number of combo buttons exceeds the maximum allowed.
* - -3: Too many combo buttons. The number of combo buttons exceeds BITS_BTN_MAX_COMBO_BUTTONS.
* - -4: User buffer mode requires setting buffer ops before init.
* - -5: Too many buttons. The number of buttons exceeds BITS_BTN_MAX_BUTTONS.
* - -6: Button param is NULL. A single button has NULL param pointer.
* - -7: Combo button param is NULL. A combo button has NULL param pointer.
*/
int32_t bits_button_init(button_obj_t* btns , \
uint16_t btns_cnt , \
Expand Down Expand Up @@ -237,6 +244,7 @@ uint8_t bits_btn_is_buffer_full(void);
*/
uint8_t bits_btn_is_buffer_empty(void);

#ifdef BITS_BTN_USE_USER_BUFFER
/**
* @brief Set custom buffer operations for external buffer management.
* This function allows users to register their own buffer implementation
Expand All @@ -245,14 +253,19 @@ uint8_t bits_btn_is_buffer_empty(void);
* Pass NULL to restore default buffer operations.
* @retval None
* @note This function should be called before bits_button_init().
* Only available when BITS_BTN_USE_USER_BUFFER is defined.
*/
void bits_button_set_buffer_ops(const bits_btn_buffer_ops_t *user_buffer_ops);
#endif

/**
* @brief Get the total capacity of the button result buffer.
* @brief Get the usable capacity of the button result buffer.
* This function returns the maximum number of button events that
* can be stored in the buffer.
* @retval The total buffer capacity in number of elements. Returns 0 if buffer is disabled.
* @note The underlying array size is capacity + 1 due to ring buffer
* implementation requirements (one slot reserved to distinguish
* full/empty states).
* @retval The usable buffer capacity in number of elements. Returns 0 if buffer is disabled.
*/
size_t get_bits_btn_buffer_capacity(void);

Expand Down
142 changes: 142 additions & 0 deletions test/cases/basic/test_buffer_operations.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,146 @@ void test_buffer_edge_cases(void) {
TEST_ASSERT_TRUE(bits_btn_is_buffer_empty());

printf("缓冲区边界情况测试通过\n");
}

// ==================== 覆写功能测试 ====================

void test_buffer_overwrite_data_correctness(void) {
printf("\n=== 测试覆写后数据正确性 ===\n");

// 使用多个按键产生可区分的事件
static const bits_btn_obj_param_t param = TEST_DEFAULT_PARAM();
button_obj_t buttons[10];
for (int i = 0; i < 10; i++) {
buttons[i] = (button_obj_t)BITS_BUTTON_INIT(i + 1, 1, &param);
}

bits_button_init(buttons, 10, NULL, 0,
test_framework_mock_read_button,
test_framework_event_callback,
test_framework_log_printf);

size_t capacity = get_bits_btn_buffer_capacity();

// 填满缓冲区:使用 key_id 1 产生 capacity 个事件
for (size_t i = 0; i < capacity; i++) {
mock_button_click(1, STANDARD_CLICK_TIME_MS);
time_simulate_time_window_end();
bits_button_ticks();
}

// 再写入 3 个新事件,使用 key_id 2 区分
for (int i = 0; i < 3; i++) {
mock_button_click(2, STANDARD_CLICK_TIME_MS);
time_simulate_time_window_end();
bits_button_ticks();
}

// 读取所有事件,验证最旧的被覆盖
bits_btn_result_t result;
size_t key1_count = 0;
size_t key2_count = 0;

while (bits_button_get_key_result(&result)) {
if (result.key_id == 1) key1_count++;
else if (result.key_id == 2) key2_count++;
}

// 验证:key_id=2 的事件应该有 3 个(最新写入的)
TEST_ASSERT_EQUAL_MESSAGE(3, key2_count, "应有3个新事件(key_id=2)");

// 验证:key_id=1 的事件应该是 capacity - 3 个(被覆盖了3个)
TEST_ASSERT_EQUAL_MESSAGE(capacity - 3, key1_count,
"旧事件(key_id=1)应被覆盖3个");

printf("覆写后数据正确性测试通过: 旧事件被正确覆盖\n");
}

void test_buffer_overwrite_multiple_cycles(void) {
printf("\n=== 测试连续多轮覆写 ===\n");

static const bits_btn_obj_param_t param = TEST_DEFAULT_PARAM();
button_obj_t button = BITS_BUTTON_INIT(1, 1, &param);
bits_button_init(&button, 1, NULL, 0,
test_framework_mock_read_button,
test_framework_event_callback,
test_framework_log_printf);

size_t capacity = get_bits_btn_buffer_capacity();
size_t total_writes = capacity * 2 + 5; // 写入超过2轮

size_t initial_overwrite = get_bits_btn_buffer_overwrite_count();

// 连续写入大量事件
for (size_t i = 0; i < total_writes; i++) {
mock_button_click(1, STANDARD_CLICK_TIME_MS);
time_simulate_time_window_end();
bits_button_ticks();
}

size_t final_overwrite = get_bits_btn_buffer_overwrite_count();
size_t overwrite_delta = final_overwrite - initial_overwrite;

// 验证覆写次数
size_t expected_overwrites = total_writes - capacity;
TEST_ASSERT_EQUAL_MESSAGE(expected_overwrites, overwrite_delta,
"覆写次数应等于 (总写入数 - 缓冲区容量)");

// 验证只能读到 capacity 个事件
bits_btn_result_t result;
size_t read_count = 0;
while (bits_button_get_key_result(&result)) {
read_count++;
if (read_count > capacity + 10) break; // 防止无限循环
}

TEST_ASSERT_EQUAL_MESSAGE(capacity, read_count,
"只能读到缓冲区容量个事件");

printf("连续多轮覆写测试通过: 覆写 %zu 次, 读取 %zu 个事件\n",
overwrite_delta, read_count);
}

void test_buffer_overwrite_count_accuracy(void) {
printf("\n=== 测试覆写计数准确性 ===\n");

static const bits_btn_obj_param_t param = TEST_DEFAULT_PARAM();
button_obj_t button = BITS_BUTTON_INIT(1, 1, &param);
bits_button_init(&button, 1, NULL, 0,
test_framework_mock_read_button,
test_framework_event_callback,
test_framework_log_printf);

size_t capacity = get_bits_btn_buffer_capacity();

// 记录初始覆写计数
size_t count_before_fill = get_bits_btn_buffer_overwrite_count();

// 填满缓冲区(不应触发覆写)
for (size_t i = 0; i < capacity; i++) {
mock_button_click(1, STANDARD_CLICK_TIME_MS);
time_simulate_time_window_end();
bits_button_ticks();
}

size_t count_after_fill = get_bits_btn_buffer_overwrite_count();
TEST_ASSERT_EQUAL_MESSAGE(count_before_fill, count_after_fill,
"填满缓冲区不应触发覆写");

// 再写入 5 个事件(应触发 5 次覆写)
const size_t extra_writes = 5;
for (size_t i = 0; i < extra_writes; i++) {
mock_button_click(1, STANDARD_CLICK_TIME_MS);
time_simulate_time_window_end();
bits_button_ticks();
}

size_t count_after_extra = get_bits_btn_buffer_overwrite_count();
size_t actual_overwrites = count_after_extra - count_after_fill;

TEST_ASSERT_EQUAL_MESSAGE(extra_writes, actual_overwrites,
"覆写计数应精确等于额外写入次数");

printf("覆写计数准确性测试通过: 额外写入 %zu 次, 覆写计数增加 %zu\n",
extra_writes, actual_overwrites);
}
Loading
Loading