Add Timer, Indicator, Button and NV Key-Value Store Components (#6)

* Add System, Timer, Button and Indicator (LED) components

* Add Non-Volatile Key Value Storage Component

* And Example Apps for Timer, Button, Indicator and NVKVS

* Fix up some compile issues
This commit is contained in:
Justin Hammond 2023-02-02 01:58:55 +08:00 committed by GitHub
parent 52f6992eca
commit bc9ae8a108
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 7096 additions and 8 deletions

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
# Get SDK path
if(NOT SDK_PATH)
get_filename_component(SDK_PATH ../../../ ABSOLUTE)
if(EXISTS $ENV{OBLFR_SDK_PATH})
set(SDK_PATH $ENV{OBLFR_SDK_PATH})
endif()
endif()
# Check SDK Path
if(NOT EXISTS ${SDK_PATH})
message(FATAL_ERROR "SDK path Error, Please set OBLFR_SDK_PATH variable")
endif()
include(${SDK_PATH}/cmake/bflbsdk.cmake)
sdk_add_include_directories(.)
sdk_set_main_file(main.c)
project(button)

View file

View file

@ -0,0 +1,7 @@
OBLFR_SDK_PATH := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../../../)
-include sdkconfig
include $(OBLFR_SDK_PATH)/cmake/project.build

103
apps/examples/button/main.c Normal file
View file

@ -0,0 +1,103 @@
#include "bflb_mtimer.h"
#include "bflb_gpio.h"
#include "board.h"
#include "oblfr_button.h"
#define DBG_TAG "BTN"
#include "log.h"
#if !defined(CONFIG_PINMUX_ENABLE_BTN1) || !defined(CONFIG_PINMUX_ENABLE_BTN2)
#error This Example Requires a Board with 1 or 2 buttons
#else
static void _button_click_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_CLICK\r\n", btn);
}
static void _button_dblclick_up_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_DOUBLECLICK\r\n", btn);
}
static void _button_press_down_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_PRESS_DOWN\r\n", btn);
}
static void _button_press_up_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_PRESS_UP\r\n", btn);
}
static void _button_press_repeat_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_PRESS_REPEAT\r\n", btn);
}
static void _button_press_repeat_done_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_PRESS_REPEAT_DONE\r\n", btn);
}
static void _button_long_press_start_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_LONG_PRESS_START\r\n", btn);
}
static void _button_long_press_hold_cb(void *arg, void *data)
{
uint8_t btn = (uintptr_t)data;
LOG_I("BTN%d: BUTTON_LONG_PRESS_HOLD\r\n", btn);
}
#endif
void app_main(void *arg) {
#ifdef CONFIG_PINMUX_ENABLE_BTN1
oblfr_button_config_t cfg1 = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1000,
.short_press_time = 200,
.gpio_button_config = {
.gpio_num = BSP_GPIO_BTN1,
.active_level = 0,
},
};
oblfr_button_handle_t s_btn1 = oblfr_button_create(&cfg1);
oblfr_button_register_cb(s_btn1, BUTTON_SINGLE_CLICK, _button_click_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_DOUBLE_CLICK, _button_dblclick_up_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_PRESS_DOWN, _button_press_down_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_PRESS_UP, _button_press_up_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_PRESS_REPEAT, _button_press_repeat_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_PRESS_REPEAT_DONE, _button_press_repeat_done_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_LONG_PRESS_START, _button_long_press_start_cb, (void *)1);
oblfr_button_register_cb(s_btn1, BUTTON_LONG_PRESS_HOLD, _button_long_press_hold_cb, (void *)1);
#endif
#ifdef CONFIG_PINMUX_ENABLE_BTN2
oblfr_button_config_t cfg2 = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1000,
.short_press_time = 200,
.gpio_button_config = {
.gpio_num = BSP_GPIO_BTN2,
.active_level = 0,
},
};
oblfr_button_handle_t s_btn2 = oblfr_button_create(&cfg2);
oblfr_button_register_cb(s_btn2, BUTTON_SINGLE_CLICK, _button_click_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_DOUBLE_CLICK, _button_dblclick_up_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_PRESS_DOWN, _button_press_down_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_PRESS_UP, _button_press_up_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_PRESS_REPEAT, _button_press_repeat_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_PRESS_REPEAT_DONE, _button_press_repeat_done_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_LONG_PRESS_START, _button_long_press_start_cb, (void *)2);
oblfr_button_register_cb(s_btn2, BUTTON_LONG_PRESS_HOLD, _button_long_press_hold_cb, (void *)2);
#endif
while (true) {
bflb_mtimer_delay_ms(1000);
}
}

View file

@ -0,0 +1 @@
CONFIG_COMPONENT_BUTTON=y

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
# Get SDK path
if(NOT SDK_PATH)
get_filename_component(SDK_PATH ../../../ ABSOLUTE)
if(EXISTS $ENV{OBLFR_SDK_PATH})
set(SDK_PATH $ENV{OBLFR_SDK_PATH})
endif()
endif()
# Check SDK Path
if(NOT EXISTS ${SDK_PATH})
message(FATAL_ERROR "SDK path Error, Please set OBLFR_SDK_PATH variable")
endif()
include(${SDK_PATH}/cmake/bflbsdk.cmake)
sdk_add_include_directories(.)
sdk_set_main_file(main.c)
project(indicator)

View file

View file

@ -0,0 +1,7 @@
OBLFR_SDK_PATH := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../../../)
-include sdkconfig
include $(OBLFR_SDK_PATH)/cmake/project.build

View file

@ -0,0 +1,36 @@
#include "bflb_mtimer.h"
#include "bflb_gpio.h"
#include "board.h"
#include "oblfr_indicator.h"
#define DBG_TAG "TMR"
#include "log.h"
#if !defined(CONFIG_PINMUX_ENABLE_LED1)
#error "This Example Requires a Board with 1 LED"
#else
void app_main(void *arg) {
const oblfr_indicator_config_t led_cfg = { };
oblfr_indicator_handle_t oblfr_indicator = oblfr_indicator_create(BSP_GPIO_LED1, &led_cfg);
oblfr_err_t ret = oblfr_indicator_start(oblfr_indicator, BLINK_FACTORY_RESET);
if (ret != OBLFR_OK) {
LOG_E("Failed to start LED indicator\r\n");
}
int i = 0;
while (true) {
bflb_mtimer_delay_ms(5000);
oblfr_indicator_stop(oblfr_indicator, i);
i++;
if (i >= BLINK_MAX) {
i = 0;
}
LOG_I("Changing to Next Indicator Pattern %d\r\n", i);
ret = oblfr_indicator_start(oblfr_indicator, i);
if (ret != OBLFR_OK) {
LOG_E("Failed to start LED indicator\r\n");
}
}
}
#endif

View file

@ -0,0 +1 @@
CONFIG_COMPONENT_STATUS_LED=y

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
# Get SDK path
if(NOT SDK_PATH)
get_filename_component(SDK_PATH ../../../ ABSOLUTE)
if(EXISTS $ENV{OBLFR_SDK_PATH})
set(SDK_PATH $ENV{OBLFR_SDK_PATH})
endif()
endif()
# Check SDK Path
if(NOT EXISTS ${SDK_PATH})
message(FATAL_ERROR "SDK path Error, Please set OBLFR_SDK_PATH variable")
endif()
include(${SDK_PATH}/cmake/bflbsdk.cmake)
sdk_add_include_directories(.)
sdk_set_main_file(main.c)
project(nvkvsdemo)

View file

View file

@ -0,0 +1,7 @@
OBLFR_SDK_PATH := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../../../)
-include sdkconfig
include $(OBLFR_SDK_PATH)/cmake/project.build

262
apps/examples/nvkvs/main.c Normal file
View file

@ -0,0 +1,262 @@
#include <bflb_mtimer.h>
#include "board.h"
#include "FreeRTOS.h"
#include "task.h"
#include "oblfr_nvkvs.h"
#define DBG_TAG "NVKVSDEMO"
#include "log.h"
#ifdef KVED_DEBUG
#define KVED_RUN_DUMP(x) \
oblfr_nvkvs_dump(x)
#else
#define KVED_RUN_DUMP(x)
#endif
#define CHECK_ERR(x) \
{ \
oblfr_err_t err = x; \
if (err != OBLFR_OK) { \
LOG_E("Error %x\r\n", err); \
} \
}
void app_main(void *arg) {
oblfr_kved_flash_driver_t kved_flash_drv = {
.flash_addr = 0xF0000,
};
oblfr_nvkvs_cfg_t nvkvs_cfg = {
.storage = OBLFR_NVKVS_STORAGE_FLASH,
// .storage = OBLFR_NVKVS_STORAGE_RAM,
.drv_cfg.flash = &kved_flash_drv,
};
oblfr_nvkvs_handle_t *handle = oblfr_nvkvs_init(&nvkvs_cfg);
if (handle == NULL) {
LOG_E("Failed to init NVKVS\r\n");
return;
}
// uint8_t
{
LOG_I("Test U8\r\n");
uint8_t v = 34;
CHECK_ERR(oblfr_nvkvs_set_u8(handle, "U8", v));
uint8_t v2;
CHECK_ERR(oblfr_nvkvs_get_u8(handle, "U8", &v2));
LOG_I("U8: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// int8_t
{
LOG_I("Test I8\r\n");
int8_t v = 21;
CHECK_ERR(oblfr_nvkvs_set_i8(handle, "I8", v));
int8_t v2;
CHECK_ERR(oblfr_nvkvs_get_i8(handle, "I8", &v2));
LOG_I("i8: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// uint16_t
{
LOG_I("Test U16\r\n");
uint16_t v = 5525;
CHECK_ERR(oblfr_nvkvs_set_u16(handle, "U16", v));
uint16_t v2;
CHECK_ERR(oblfr_nvkvs_get_u16(handle, "U16", &v2));
LOG_I("U16: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// int16_t
{
LOG_I("Test I16\r\n");
int16_t v = 4432;
CHECK_ERR(oblfr_nvkvs_set_i16(handle, "I16", v));
int16_t v2;
CHECK_ERR(oblfr_nvkvs_get_i16(handle, "I16", &v2));
LOG_I("I16: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// uint32_t
{
LOG_I("Test U32\r\n");
uint32_t v = 75333;
CHECK_ERR(oblfr_nvkvs_set_u32(handle, "U32", v));
uint32_t v2;
CHECK_ERR(oblfr_nvkvs_get_u32(handle, "U32", &v2));
LOG_I("U32: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// int32_t
{
LOG_I("Test I32\r\n");
int32_t v = 86232;
CHECK_ERR(oblfr_nvkvs_set_i32(handle, "I32", v));
int32_t v2;
CHECK_ERR(oblfr_nvkvs_get_i32(handle, "I32", &v2));
LOG_I("I32: %d = %d\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// uint64_t
{
LOG_I("Test U64\r\n");
uint64_t v = 664375333;
CHECK_ERR(oblfr_nvkvs_set_u64(handle, "U64", v));
uint64_t v2;
CHECK_ERR(oblfr_nvkvs_get_u64(handle, "U64", &v2));
LOG_I("U64: %ld = %ld\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// int64_t
{
LOG_I("Test I64\r\n");
int64_t v = 223223434;
CHECK_ERR(oblfr_nvkvs_set_i64(handle, "I64", v));
int64_t v2;
CHECK_ERR(oblfr_nvkvs_get_i64(handle, "I64", &v2));
LOG_I("I64: %ld = %ld\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// FLOAT
{
LOG_I("Test FLOAT\r\n");
float v = 3.14159265;
CHECK_ERR(oblfr_nvkvs_set_float(handle, "FLOAT", v));
float v2;
CHECK_ERR(oblfr_nvkvs_get_float(handle, "FLOAT", &v2));
LOG_I("FLOAT: %f = %f\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// DOUBLE
{
LOG_I("Test DOUBLE\r\n");
double v = 3.14159265;
CHECK_ERR(oblfr_nvkvs_set_double(handle, "DOUBLE", v));
double v2;
CHECK_ERR(oblfr_nvkvs_get_double(handle, "DOUBLE", &v2));
LOG_I("DOUBLE: %f = %f\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
// STRING
{
LOG_I("Test STRING\r\n");
char *v = "Hello World, this is a really long string";
CHECK_ERR(oblfr_nvkvs_set_string(handle, "STRING", v));
char v2[128];
CHECK_ERR(oblfr_nvkvs_get_string(handle, "STRING", (char *)&v2));
LOG_I("STRING: %s = %s\r\n", v, v2);
KVED_RUN_DUMP(ctrl);
}
/* force a Index Table Rollover */
{
LOG_I("Force a Index Table Rollover.. This might take a while\r\n");
for (uint16_t i = 0; i < 300; i++) {
char key[16];
sprintf(key, "KEY");
CHECK_ERR(oblfr_nvkvs_set_u16(handle, key, i));
uint16_t v;
CHECK_ERR(oblfr_nvkvs_get_u16(handle, key, &v));
if (v != i) {
LOG_E("Error: %d != %d\r\n", v, i);
}
}
LOG_I("Done\r\n");
//oblfr_nvkvs_dump(handle);
}
/* Delete a entry */
{
LOG_I("Delete a entry\r\n");
char key[16] = "KEY";
CHECK_ERR(oblfr_nvkvs_delete(handle, key));
uint16_t v2;
int err = oblfr_nvkvs_get_u16(handle, key, &v2);
if (err == OBLFR_ERR_ERROR) {
LOG_I("Entry deleted\r\n");
} else {
LOG_E("Error: %d\r\n", err);
}
}
// iterate over all keys
{
LOG_I("Iterate over all keys\r\n");
uint16_t iter = oblfr_nvkvs_iter_init(handle);
while (iter != 0) {
oblfr_nvkvs_data_t data;
oblfr_nvkvs_get_item(handle, iter, &data);
LOG_I("Key: %s\r\n", data.key);
switch (data.type) {
case OBLFR_NVKVS_DATA_TYPE_UINT8:
LOG_I("\tU8: %d\r\n", data.value.u8);
break;
case OBLFR_NVKVS_DATA_TYPE_INT8:
LOG_I("\tI8: %d\r\n", data.value.i8);
break;
case OBLFR_NVKVS_DATA_TYPE_UINT16:
LOG_I("\tU16: %d\r\n", data.value.u16);
break;
case OBLFR_NVKVS_DATA_TYPE_INT16:
LOG_I("\tI16: %d\r\n", data.value.i16);
break;
case OBLFR_NVKVS_DATA_TYPE_UINT32:
LOG_I("\tU32: %d\r\n", data.value.u32);
break;
case OBLFR_NVKVS_DATA_TYPE_INT32:
LOG_I("\tI32: %d\r\n", data.value.i32);
break;
case OBLFR_NVKVS_DATA_TYPE_UINT64:
LOG_I("\tU64: %ld\r\n", data.value.u64);
break;
case OBLFR_NVKVS_DATA_TYPE_INT64:
LOG_I("\tI64: %ld\r\n", data.value.i64);
break;
case OBLFR_NVKVS_DATA_TYPE_FLOAT:
LOG_I("\tFLOAT: %f\r\n", data.value.flt);
break;
case OBLFR_NVKVS_DATA_TYPE_DOUBLE:
LOG_I("\tDOUBLE: %f\r\n", data.value.dbl);
break;
case OBLFR_NVKVS_DATA_TYPE_STRING:
LOG_I("\tSTRING: %s\r\n", data.value.str);
break;
}
iter = oblfr_nvkvs_iter_next(handle, iter);
}
LOG_I("Done\r\n");
}
/* some statistics */
{
LOG_I("Statistics\r\n");
LOG_I("Total Size: %d\r\n", oblfr_nvkvs_get_size(handle));
LOG_I("Used Size: %d\r\n", oblfr_nvkvs_used_entries(handle));
LOG_I("Free Size: %d\r\n", oblfr_nvkvs_free_entries(handle));
LOG_I("Deleted Entries: %d\r\n", oblfr_nvkvs_deleted_entries(handle));
}
/* compact */
{
LOG_I("Compact\r\n");
vTaskDelay(100);
oblfr_nvkvs_dump(handle);
vTaskDelay(100);
oblfr_nvkvs_compact(handle);
vTaskDelay(100);
oblfr_nvkvs_dump(handle);
}
{
LOG_I("Final Statistics\r\n");
LOG_I("Total Size: %d\r\n", oblfr_nvkvs_get_size(handle));
LOG_I("Used Size: %d\r\n", oblfr_nvkvs_used_entries(handle));
LOG_I("Free Size: %d\r\n", oblfr_nvkvs_free_entries(handle));
LOG_I("Deleted Entries: %d\r\n", oblfr_nvkvs_deleted_entries(handle));
}
while (true) {
bflb_mtimer_delay_ms(1000);
LOG_I("Complete!\r\n");
}
}

View file

@ -0,0 +1,2 @@
CONFIG_COMPONENT_NVKVS=y
CONFIG_COMPONENT_NVKVS_FLASH_BACKEND=y

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15)
# Get SDK path
if(NOT SDK_PATH)
get_filename_component(SDK_PATH ../../../ ABSOLUTE)
if(EXISTS $ENV{OBLFR_SDK_PATH})
set(SDK_PATH $ENV{OBLFR_SDK_PATH})
endif()
endif()
# Check SDK Path
if(NOT EXISTS ${SDK_PATH})
message(FATAL_ERROR "SDK path Error, Please set OBLFR_SDK_PATH variable")
endif()
include(${SDK_PATH}/cmake/bflbsdk.cmake)
sdk_add_include_directories(.)
sdk_set_main_file(main.c)
project(timer)

View file

View file

@ -0,0 +1,7 @@
OBLFR_SDK_PATH := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))../../../)
-include sdkconfig
include $(OBLFR_SDK_PATH)/cmake/project.build

View file

@ -0,0 +1,52 @@
#include "bflb_mtimer.h"
#include "bflb_gpio.h"
#include "board.h"
#include "oblfr_timer.h"
#define DBG_TAG "TMR"
#include "log.h"
void timer1(void *arg) {
LOG_I("timer1\r\n");
}
void timer2(void *arg) {
LOG_I("timer2\r\n");
}
void one_shot(void *arg) {
LOG_I("one-shot\r\n");
}
void app_main(void *arg) {
oblfr_timer_create_args_t timer1_cfg = {
.callback = timer1,
.arg = NULL,
.name = "Timer1",
};
oblfr_timer_t timer1;
oblfr_timer_create(&timer1_cfg, &timer1);
oblfr_timer_start_periodic(timer1, 2000);
oblfr_timer_create_args_t timer2_cfg = {
.callback = timer2,
.arg = NULL,
.name = "Timer2",
};
oblfr_timer_t timer2;
oblfr_timer_create(&timer2_cfg, &timer2);
oblfr_timer_start_periodic(timer2, 1534);
oblfr_timer_create_args_t timer3_cfg = {
.callback = one_shot,
.arg = NULL,
.name = "One-Shot",
};
oblfr_timer_t timer3;
oblfr_timer_create(&timer3_cfg, &timer3);
oblfr_timer_start_once(timer3, 4000);
while (true) {
bflb_mtimer_delay_ms(1000);
}
}

View file

@ -0,0 +1 @@
CONFIG_COMPONENT_TIMER=y

View file

@ -1,6 +1,8 @@
#ifndef BSP_COMMON_H
#define BSP_COMMON_H
#include "sdkconfig.h"
typedef struct {
uint32_t pin;
uint32_t mode;

View file

@ -51,6 +51,7 @@ void bl_show_flashinfo(void)
LOG_I("cread support 0x%02X\r\n", flashCfg.c_read_support);
LOG_I("cread code 0x%02X\r\n", flashCfg.c_read_mode);
LOG_I("burst wrap cmd 0x%02X\r\n", flashCfg.burst_wrap_cmd);
LOG_I("sector size: 0x%02X\r\n", flashCfg.sector_size);
LOG_I("=====================================\r\n");
}

View file

@ -3,6 +3,7 @@
#include "bl616_bsp.h"
#include "bsp_common.h"
#include "sdkconfig.h"
void board_init(void);

View file

@ -3,6 +3,7 @@
#include "bl702_bsp.h"
#include "bsp_common.h"
#include "sdkconfig.h"
void board_init(void);

View file

@ -3,6 +3,7 @@
#include "bl808_bsp.h"
#include "bflb_gpio.h"
#include "sdkconfig.h"
void board_init(void);

View file

@ -2,6 +2,8 @@
#define _BOARD_H
#include "bl808_bsp.h"
#include "sdkconfig.h"
void board_init(void);

View file

@ -64,7 +64,7 @@ menu "SDK configuration"
menu "Logging Configuration"
choice LOG_TYPE
prompt "Logging Type"
default BFLOG
default LOG
help
Select the logging type to use.
config BFLOG

View file

@ -43,9 +43,8 @@ if(BOARD_DIR)
message(STATUS "BOARD_DIR: ${BOARD_DIR}")
endif()
find_package(bouffalo_sdk REQUIRED HINTS $ENV{BL_SDK_BASE})
include(${SDK_PATH}/cmake/kconfig.cmake)
add_subdirectory(${SDK_PATH}/bsp/common/ bsp_common)
#add_subdirectory(${SDK_PATH}/components/ components)
add_subdirectory(${SDK_PATH}/components/ components)

View file

@ -7,7 +7,7 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/config/sdkconfig.h
COMMENT "Generating config/sdkconfig.h"
VERBATIM)
target_sources(app PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/config/sdkconfig.h)
target_sources(sdk_intf_lib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/config/sdkconfig.h)
sdk_add_include_directories(${CMAKE_CURRENT_BINARY_DIR}/config)

View file

@ -11,7 +11,6 @@ CROSS_COMPILE ?= riscv64-unknown-elf-
default: proj.conf
@$(MAKE) build
config: proj.conf
cd build && make config
@ -22,11 +21,11 @@ proj.conf:
include $(BL_SDK_BASE)/project.build
distclean: clean
-@rm -rf sdkconfig || true
-@rm -rf build || true
-@rm -f sdkconfig || true
clean:
-@rm -rf build || true
-@rm -f proj.conf || true
.PHONY:config clean distclean default
.PHONY:config clean default distclean

View file

@ -0,0 +1,5 @@
sdk_add_subdirectory_ifdef(CONFIG_COMPONENT_SYSTEM oblfr)
sdk_add_subdirectory_ifdef(CONFIG_COMPONENT_BUTTON button)
sdk_add_subdirectory_ifdef(CONFIG_COMPONENT_STATUS_LED indicator)
sdk_add_subdirectory_ifdef(CONFIG_COMPONENT_TIMER timer)
sdk_add_subdirectory_ifdef(CONFIG_COMPONENT_NVKVS nvkvs)

View file

@ -0,0 +1,5 @@
sdk_generate_library(oblfr_button)
sdk_add_include_directories(include)
sdk_library_add_sources(${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_button.c
${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_button_gpio.c)

63
components/button/Kconfig Normal file
View file

@ -0,0 +1,63 @@
config COMPONENT_BUTTON
bool "Button Component"
default n
select FREERTOS
select COMPONENT_TIMER
help
"Button Component from esp-iot-solution modified for Bouffalo MCU SDK"
menu "IoT Button"
visible if COMPONENT_BUTTON
config BUTTON_PERIOD_TIME_MS
int "BUTTON PERIOD TIME (MS)"
range 2 20
default 5
help
"Button scan interval"
config BUTTON_DEBOUNCE_TICKS
int "BUTTON DEBOUNCE TICKS"
range 1 8
default 2
help
"One CONFIG_BUTTON_DEBOUNCE_TICKS equal to CONFIG_BUTTON_PERIOD_TIME_MS"
config BUTTON_SHORT_PRESS_TIME_MS
int "BUTTON SHORT PRESS TIME (MS)"
range 50 800
default 180
config BUTTON_LONG_PRESS_TIME_MS
int "BUTTON LONG PRESS TIME (MS)"
range 500 5000
default 1500
config BUTTON_SERIAL_TIME_MS
int "BUTTON SERIAL TIME (MS)"
range 2 1000
default 20
help
"Serial trigger interval"
config ADC_BUTTON_MAX_CHANNEL
int "ADC BUTTON MAX CHANNEL"
range 1 5
default 3
help
"Maximum number of channels for ADC buttons"
config ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
int "ADC BUTTON MAX BUTTON PER CHANNEL"
range 1 10
default 8
help
"Maximum number of buttons per channel"
config ADC_BUTTON_SAMPLE_TIMES
int "ADC BUTTON SAMPLE TIMES"
range 1 4
default 1
help
"Number of samples per scan"
endmenu

202
components/button/LICENSE Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,18 @@
# Component: Button
Taken from the esp-iot-solution and modified for Bouffalolab SDK.
After creating a new button object by calling function `button_create()`, the button object can create press events, every press event can have its own callback.
List of supported events:
* Button pressed
* Button released
* Button pressed - repeated
* Button single click
* Button double click
* Button long press start
* Button long press hold
* Button long press done
There are two ways this driver can handle buttons:
1. Buttons connected to standard digital GPIO
2. Multiple buttons connected to single ADC channel

View file

@ -0,0 +1,177 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_H_
#define _IOT_BUTTON_H_
#include "oblfr_button_adc.h"
#include "oblfr_button_gpio.h"
#include "oblfr_common.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (* oblfr_button_cb_t)(void *oblfr_button_handle, void *usr_data);
typedef void *oblfr_button_handle_t;
/**
* @brief Button events
*
*/
typedef enum {
BUTTON_PRESS_DOWN = 0,
BUTTON_PRESS_UP,
BUTTON_PRESS_REPEAT,
BUTTON_PRESS_REPEAT_DONE,
BUTTON_SINGLE_CLICK,
BUTTON_DOUBLE_CLICK,
BUTTON_LONG_PRESS_START,
BUTTON_LONG_PRESS_HOLD,
BUTTON_EVENT_MAX,
BUTTON_NONE_PRESS,
} oblfr_button_event_t;
/**
* @brief Supported button type
*
*/
typedef enum {
BUTTON_TYPE_GPIO,
BUTTON_TYPE_ADC,
BUTTON_TYPE_CUSTOM
} oblfr_button_type_t;
/**
* @brief custom button configuration
*
*/
typedef struct {
uint8_t active_level; /**< active level when press down */
oblfr_err_t (*button_custom_init)(void *param); /**< user defined button init */
uint8_t (*button_custom_get_key_value)(void *param); /**< user defined button get key value */
oblfr_err_t (*button_custom_deinit)(void *param); /**< user defined button deinit */
void *priv; /**< private data used for custom button, MUST be allocated dynamically and will be auto freed in oblfr_button_delete*/
} oblfr_button_custom_config_t;
/**
* @brief Button configuration
*
*/
typedef struct {
oblfr_button_type_t type; /**< button type, The corresponding button configuration must be filled */
uint16_t long_press_time; /**< Trigger time(ms) for long press, if 0 default to BUTTON_LONG_PRESS_TIME_MS */
uint16_t short_press_time; /**< Trigger time(ms) for short press, if 0 default to BUTTON_SHORT_PRESS_TIME_MS */
union {
oblfr_button_gpio_config_t gpio_button_config; /**< gpio button configuration */
/* oblfr_button_adc_config_t adc_button_config; */ /**< adc button configuration */
oblfr_button_custom_config_t custom_button_config; /**< custom button configuration */
}; /**< button configuration */
} oblfr_button_config_t;
/**
* @brief Create a button
*
* @param config pointer of button configuration, must corresponding the button type
*
* @return A handle to the created button, or NULL in case of error.
*/
oblfr_button_handle_t oblfr_button_create(const oblfr_button_config_t *config);
/**
* @brief Delete a button
*
* @param btn_handle A button handle to delete
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
oblfr_err_t oblfr_button_delete(oblfr_button_handle_t btn_handle);
/**
* @brief Register the button event callback function.
*
* @param btn_handle A button handle to register
* @param event Button event
* @param cb Callback function.
* @param usr_data user data
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
oblfr_err_t oblfr_button_register_cb(oblfr_button_handle_t btn_handle, oblfr_button_event_t event, oblfr_button_cb_t cb, void *usr_data);
/**
* @brief Unregister the button event callback function.
*
* @param btn_handle A button handle to unregister
* @param event Button event
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
oblfr_err_t oblfr_button_unregister_cb(oblfr_button_handle_t btn_handle, oblfr_button_event_t event);
/**
* @brief how many Callbacks are still registered.
*
* @param btn_handle A button handle to unregister
*
* @return 0 if no callbacks registered, or 1 .. (BUTTON_EVENT_MAX-1) for the number of Registered Buttons.
*/
size_t oblfr_button_count_cb(oblfr_button_handle_t btn_handle);
/**
* @brief Get button event
*
* @param btn_handle Button handle
*
* @return Current button event. See oblfr_button_event_t
*/
oblfr_button_event_t oblfr_button_get_event(oblfr_button_handle_t btn_handle);
/**
* @brief Get button repeat times
*
* @param btn_handle Button handle
*
* @return button pressed times. For example, double-click return 2, triple-click return 3, etc.
*/
uint8_t oblfr_button_get_repeat(oblfr_button_handle_t btn_handle);
/**
* @brief Get button ticks time
*
* @param btn_handle Button handle
*
* @return Actual time from press down to up (ms).
*/
uint16_t oblfr_button_get_ticks_time(oblfr_button_handle_t btn_handle);
/**
* @brief Get button long press hold count
*
* @param btn_handle Button handle
*
* @return Count of trigger cb(BUTTON_LONG_PRESS_HOLD)
*/
uint16_t oblfr_button_get_long_press_hold_cnt(oblfr_button_handle_t btn_handle);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,88 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_ADC_H_
#define _IOT_BUTTON_ADC_H_
#include "bflb_gpio.h"
#if 0
// #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// #include "esp_adc/adc_oneshot.h"
// #else
// #include "driver/adc.h"
// #endif
#ifdef __cplusplus
extern "C" {
#endif
#define ADC_BUTTON_COMBINE(channel, index) ((channel)<<8 | (index))
#define ADC_BUTTON_SPLIT_INDEX(data) ((uint32_t)(data)&0xff)
#define ADC_BUTTON_SPLIT_CHANNEL(data) (((uint32_t)(data) >> 8) & 0xff)
/**
* @brief adc button configuration
*
*/
typedef struct {
uint8_t adc_channel; /**< Channel of ADC */
uint8_t button_index; /**< button index on the channel */
uint16_t min; /**< min voltage in mv corresponding to the button */
uint16_t max; /**< max voltage in mv corresponding to the button */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
adc_oneshot_unit_handle_t *adc_handle; /**< handle of adc unit, if NULL will create new one internal, else will use the handle */
#endif
} button_adc_config_t;
/**
* @brief Initialize gpio button
*
* @param config pointer of configuration struct
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
* - ESP_ERR_NOT_SUPPORTED Arguments out of range.
* - ESP_ERR_INVALID_STATE State is error.
*/
esp_err_t button_adc_init(const button_adc_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param channel ADC channel
* @param button_index Button index on the channel
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
esp_err_t button_adc_deinit(uint8_t channel, int button_index);
/**
* @brief Get the adc button level
*
* @param button_index It is compressed by ADC channel and button index, use the macro ADC_BUTTON_COMBINE to generate. It will be treated as a uint32_t variable.
*
* @return
* - 0 Not pressed
* - 1 Pressed
*/
uint8_t button_adc_get_key_level(void *button_index);
#ifdef __cplusplus
}
#endif
#endif
#endif

View file

@ -0,0 +1,66 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_GPIO_H_
#define _IOT_BUTTON_GPIO_H_
#include "bflb_gpio.h"
#include "oblfr_common.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief gpio button configuration
*
*/
typedef struct {
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
} oblfr_button_gpio_config_t;
/**
* @brief Initialize gpio button
*
* @param config pointer of configuration struct
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
*/
oblfr_err_t oblfr_button_gpio_init(const oblfr_button_gpio_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param gpio_num gpio number of button
*
* @return Always return ESP_OK
*/
oblfr_err_t oblfr_button_gpio_deinit(int gpio_num);
/**
* @brief Get current level on button gpio
*
* @param gpio_num gpio number of button, it will be treated as a uint32_t variable.
*
* @return Level on gpio
*/
uint8_t oblfr_button_gpio_get_key_level(void *gpio_num);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,388 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "bflb_gpio.h"
#include "oblfr_timer.h"
#include "oblfr_button.h"
#include "sdkconfig.h"
#define DBG_TAG "BTN"
#include "log.h"
#define BTN_CHECK(a, str, ret_val) \
if (!(a)) { \
LOG_E("%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
/**
* @brief Structs to record individual key parameters
*
*/
typedef struct Button {
uint16_t ticks;
uint16_t long_press_ticks; /*! Trigger ticks for long press*/
uint16_t short_press_ticks; /*! Trigger ticks for repeat press*/
uint16_t long_press_hold_cnt; /*! Record long press hold count*/
uint8_t repeat;
uint8_t state: 3;
uint8_t debounce_cnt: 3;
uint8_t active_level: 1;
uint8_t oblfr_button_level: 1;
oblfr_button_event_t event;
uint8_t (*hal_oblfr_button_Level)(void *hardware_data);
oblfr_err_t (*hal_oblfr_button_deinit)(void *hardware_data);
void *hardware_data;
void *usr_data[BUTTON_EVENT_MAX];
oblfr_button_type_t type;
oblfr_button_cb_t cb[BUTTON_EVENT_MAX];
struct Button *next;
} oblfr_button_dev_t;
//button handle list head.
static oblfr_button_dev_t *g_head_handle = NULL;
static oblfr_timer_t g_oblfr_button_timer_handle;
static bool g_is_timer_running = false;
#define TICKS_INTERVAL CONFIG_BUTTON_PERIOD_TIME_MS
#define DEBOUNCE_TICKS CONFIG_BUTTON_DEBOUNCE_TICKS //MAX 8
#define SHORT_TICKS (CONFIG_BUTTON_SHORT_PRESS_TIME_MS /TICKS_INTERVAL)
#define LONG_TICKS (CONFIG_BUTTON_LONG_PRESS_TIME_MS /TICKS_INTERVAL)
#define SERIAL_TICKS (CONFIG_BUTTON_SERIAL_TIME_MS /TICKS_INTERVAL)
#define CALL_EVENT_CB(ev) if(btn->cb[ev])btn->cb[ev](btn, btn->usr_data[ev])
#define TIME_TO_TICKS(time, congfig_time) (0 == (time))?congfig_time:(((time) / TICKS_INTERVAL))?((time) / TICKS_INTERVAL):1
/**
* @brief Button driver core function, driver state machine.
*/
static void oblfr_button_handler(oblfr_button_dev_t *btn)
{
uint8_t read_gpio_level = btn->hal_oblfr_button_Level(btn->hardware_data);
/** ticks counter working.. */
if ((btn->state) > 0) {
btn->ticks++;
}
/**< button debounce handle */
if (read_gpio_level != btn->oblfr_button_level) {
if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) {
btn->oblfr_button_level = read_gpio_level;
btn->debounce_cnt = 0;
}
} else {
btn->debounce_cnt = 0;
}
/** State machine */
switch (btn->state) {
case 0:
if (btn->oblfr_button_level == btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->ticks = 0;
btn->repeat = 1;
btn->state = 1;
} else {
btn->event = (uint8_t)BUTTON_NONE_PRESS;
}
break;
case 1:
if (btn->oblfr_button_level != btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->ticks = 0;
btn->state = 2;
} else if (btn->ticks > btn->long_press_ticks) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
CALL_EVENT_CB(BUTTON_LONG_PRESS_START);
btn->state = 5;
}
break;
case 2:
if (btn->oblfr_button_level == btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->repeat++;
CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit
btn->ticks = 0;
btn->state = 3;
} else if (btn->ticks > btn->short_press_ticks) {
if (btn->repeat == 1) {
btn->event = (uint8_t)BUTTON_SINGLE_CLICK;
CALL_EVENT_CB(BUTTON_SINGLE_CLICK);
} else if (btn->repeat == 2) {
btn->event = (uint8_t)BUTTON_DOUBLE_CLICK;
CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit
}
btn->event = (uint8_t)BUTTON_PRESS_REPEAT_DONE;
CALL_EVENT_CB(BUTTON_PRESS_REPEAT_DONE); // repeat hit
btn->state = 0;
}
break;
case 3:
if (btn->oblfr_button_level != btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
if (btn->ticks < SHORT_TICKS) {
btn->ticks = 0;
btn->state = 2; //repeat press
} else {
btn->state = 0;
}
}
break;
case 5:
if (btn->oblfr_button_level == btn->active_level) {
//continue hold trigger
if (btn->ticks >= (btn->long_press_hold_cnt + 1) * SERIAL_TICKS) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_HOLD;
btn->long_press_hold_cnt++;
CALL_EVENT_CB(BUTTON_LONG_PRESS_HOLD);
}
} else { //releasd
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->state = 0; //reset
btn->long_press_hold_cnt = 0;
}
break;
}
}
static void oblfr_button_cb(void *args)
{
oblfr_button_dev_t *target;
for (target = g_head_handle; target; target = target->next) {
oblfr_button_handler(target);
}
}
static oblfr_button_dev_t *oblfr_button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *hardware_data), void *hardware_data, uint16_t long_press_ticks, uint16_t short_press_ticks)
{
BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) calloc(1, sizeof(oblfr_button_dev_t));
BTN_CHECK(NULL != btn, "Button memory alloc failed", NULL);
btn->hardware_data = hardware_data;
btn->event = BUTTON_NONE_PRESS;
btn->active_level = active_level;
btn->hal_oblfr_button_Level = hal_get_key_state;
btn->oblfr_button_level = !active_level;
btn->long_press_ticks = long_press_ticks;
btn->short_press_ticks = short_press_ticks;
/** Add handle to list */
btn->next = g_head_handle;
g_head_handle = btn;
if (false == g_is_timer_running) {
oblfr_timer_create_args_t oblfr_button_timer;
oblfr_button_timer.arg = NULL;
oblfr_button_timer.callback = oblfr_button_cb;
oblfr_button_timer.name = "oblfr_button_timer";
oblfr_timer_create(&oblfr_button_timer, &g_oblfr_button_timer_handle);
oblfr_timer_start_periodic(g_oblfr_button_timer_handle, TICKS_INTERVAL);
g_is_timer_running = true;
}
return btn;
}
static oblfr_err_t oblfr_button_delete_com(oblfr_button_dev_t *btn)
{
BTN_CHECK(NULL != btn, "Pointer of handle is invalid", OBLFR_ERR_INVALID);
oblfr_button_dev_t **curr;
for (curr = &g_head_handle; *curr; ) {
oblfr_button_dev_t *entry = *curr;
if (entry == btn) {
*curr = entry->next;
free(entry);
} else {
curr = &entry->next;
}
}
/* count button number */
uint16_t number = 0;
oblfr_button_dev_t *target = g_head_handle;
while (target) {
target = target->next;
number++;
}
LOG_D("remain btn number=%d", number);
if (0 == number && g_is_timer_running) { /**< if all button is deleted, stop the timer */
oblfr_timer_stop(g_oblfr_button_timer_handle);
oblfr_timer_delete(g_oblfr_button_timer_handle);
g_is_timer_running = false;
}
return OBLFR_OK;
}
oblfr_button_handle_t oblfr_button_create(const oblfr_button_config_t *config)
{
BTN_CHECK(config, "Invalid button config", NULL);
oblfr_err_t ret = OBLFR_OK;
oblfr_button_dev_t *btn = NULL;
uint16_t long_press_time = 0;
uint16_t short_press_time = 0;
long_press_time = TIME_TO_TICKS(config->long_press_time, LONG_TICKS);
short_press_time = TIME_TO_TICKS(config->short_press_time, SHORT_TICKS);
switch (config->type) {
case BUTTON_TYPE_GPIO: {
const oblfr_button_gpio_config_t *cfg = &(config->gpio_button_config);
ret = oblfr_button_gpio_init(cfg);
BTN_CHECK(OBLFR_OK == ret, "gpio button init failed", NULL);
btn = oblfr_button_create_com(cfg->active_level, oblfr_button_gpio_get_key_level, (void *)cfg->gpio_num, long_press_time, short_press_time);
} break;
#if 0
case BUTTON_TYPE_ADC: {
const oblfr_button_adc_config_t *cfg = &(config->adc_oblfr_button_config);
ret = oblfr_button_adc_init(cfg);
BTN_CHECK(OBLFR_OK == ret, "adc button init failed", NULL);
btn = oblfr_button_create_com(1, oblfr_button_adc_get_key_level, (void *)ADC_BUTTON_COMBINE(cfg->adc_channel, cfg->oblfr_button_index), long_press_time, short_press_time);
} break;
#endif
case BUTTON_TYPE_CUSTOM: {
if (config->custom_button_config.button_custom_init) {
ret = config->custom_button_config.button_custom_init(config->custom_button_config.priv);
BTN_CHECK(OBLFR_OK == ret, "custom button init failed", NULL);
}
btn = oblfr_button_create_com(config->custom_button_config.active_level,
config->custom_button_config.button_custom_get_key_value,
config->custom_button_config.priv,
long_press_time, short_press_time);
if (btn) {
btn->hal_oblfr_button_deinit = config->custom_button_config.button_custom_deinit;
}
} break;
default:
LOG_E("Unsupported button type");
break;
}
BTN_CHECK(NULL != btn, "button create failed", NULL);
btn->type = config->type;
return (oblfr_button_handle_t)btn;
}
oblfr_err_t oblfr_button_delete(oblfr_button_handle_t btn_handle)
{
oblfr_err_t ret = OBLFR_OK;
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", OBLFR_ERR_INVALID);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *)btn_handle;
switch (btn->type) {
case BUTTON_TYPE_GPIO:
ret = oblfr_button_gpio_deinit((int)(btn->hardware_data));
break;
#if 0
case BUTTON_TYPE_ADC:
ret = oblfr_button_adc_deinit(ADC_BUTTON_SPLIT_CHANNEL(btn->hardware_data), ADC_BUTTON_SPLIT_INDEX(btn->hardware_data));
break;
#endif
case BUTTON_TYPE_CUSTOM:
if (btn->hal_oblfr_button_deinit) {
ret = btn->hal_oblfr_button_deinit(btn->hardware_data);
}
if (btn->hardware_data) {
free(btn->hardware_data);
btn->hardware_data = NULL;
}
break;
default:
break;
}
BTN_CHECK(OBLFR_OK == ret, "button deinit failed", OBLFR_ERR_ERROR);
oblfr_button_delete_com(btn);
return OBLFR_OK;
}
oblfr_err_t oblfr_button_register_cb(oblfr_button_handle_t btn_handle, oblfr_button_event_t event, oblfr_button_cb_t cb, void *usr_data)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", OBLFR_ERR_INVALID);
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", OBLFR_ERR_INVALID);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
btn->cb[event] = cb;
btn->usr_data[event] = usr_data;
return OBLFR_OK;
}
oblfr_err_t oblfr_button_unregister_cb(oblfr_button_handle_t btn_handle, oblfr_button_event_t event)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", OBLFR_ERR_INVALID);
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", OBLFR_ERR_INVALID);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
btn->cb[event] = NULL;
btn->usr_data[event] = NULL;
return OBLFR_OK;
}
size_t oblfr_button_count_cb(oblfr_button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", OBLFR_ERR_INVALID);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
size_t ret = 0;
for (size_t i = 0; i < BUTTON_EVENT_MAX; i++) {
if (btn->cb[i]) {
ret++;
}
}
return ret;
}
oblfr_button_event_t oblfr_button_get_event(oblfr_button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
return btn->event;
}
uint8_t oblfr_button_get_repeat(oblfr_button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
return btn->repeat;
}
uint16_t oblfr_button_get_ticks_time(oblfr_button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
return (btn->ticks * TICKS_INTERVAL);
}
uint16_t oblfr_button_get_long_press_hold_cnt(oblfr_button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
oblfr_button_dev_t *btn = (oblfr_button_dev_t *) btn_handle;
return btn->long_press_hold_cnt;
}

View file

@ -0,0 +1,318 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "soc/soc_caps.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#else
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#endif
#include "button_adc.h"
static const char *TAG = "adc button";
#define ADC_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
#define DEFAULT_VREF 1100
#define NO_OF_SAMPLES CONFIG_ADC_BUTTON_SAMPLE_TIMES //Multisampling
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH
#define ADC1_BUTTON_CHANNEL_MAX SOC_ADC_MAX_CHANNEL_NUM
#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11
#else
#define ADC_BUTTON_WIDTH ADC_WIDTH_MAX-1
#define ADC1_BUTTON_CHANNEL_MAX ADC1_CHANNEL_MAX
#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11
#endif
#define ADC_BUTTON_ADC_UNIT ADC_UNIT_1
#define ADC_BUTTON_MAX_CHANNEL CONFIG_ADC_BUTTON_MAX_CHANNEL
#define ADC_BUTTON_MAX_BUTTON CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
typedef struct {
uint16_t min;
uint16_t max;
} button_data_t;
typedef struct {
uint8_t channel;
uint8_t is_init;
button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */
uint64_t last_time; /* the last time of adc sample */
} btn_adc_channel_t;
typedef struct {
bool is_configured;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
adc_cali_handle_t adc1_cali_handle;
adc_oneshot_unit_handle_t adc1_handle;
#else
esp_adc_cal_characteristics_t adc_chars;
#endif
btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL];
uint8_t ch_num;
} adc_button_t;
static adc_button_t g_button = {0};
static int find_unused_channel(void)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.ch[i].is_init) {
return i;
}
}
return -1;
}
static int find_channel(uint8_t channel)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (channel == g_button.ch[i].channel) {
return i;
}
}
return -1;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static esp_err_t adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BUTTON_WIDTH,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BUTTON_WIDTH,
};
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
*out_handle = handle;
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Calibration Success");
} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated?ESP_OK:ESP_FAIL;
}
#endif
esp_err_t button_adc_init(const button_adc_config_t *config)
{
ADC_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(config->adc_channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->max > 0, "key max voltage invalid", ESP_ERR_INVALID_ARG);
int ch_index = find_channel(config->adc_channel);
if (ch_index >= 0) { /**< the channel has been initialized */
ADC_BTN_CHECK(g_button.ch[ch_index].btns[config->button_index].max == 0, "The button_index has been used", ESP_ERR_INVALID_STATE);
} else { /**< this is a new channel */
int unused_ch_index = find_unused_channel();
ADC_BTN_CHECK(unused_ch_index >= 0, "exceed max channel number, can't create a new channel", ESP_ERR_INVALID_STATE);
ch_index = unused_ch_index;
}
/** initialize adc */
if (0 == g_button.is_configured) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_err_t ret;
if (NULL == config->adc_handle) {
//ADC1 Init
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
};
ret = adc_oneshot_new_unit(&init_config, &g_button.adc1_handle);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot new unit fail!", ESP_FAIL);
} else {
g_button.adc1_handle = *config->adc_handle ;
ESP_LOGI(TAG, "ADC1 has been initialized");
}
#else
//Configure ADC
adc1_config_width(ADC_BUTTON_WIDTH);
//Characterize ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, ADC_BUTTON_WIDTH, DEFAULT_VREF, &g_button.adc_chars);
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
ESP_LOGI(TAG, "Characterized using Two Point Value");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
ESP_LOGI(TAG, "Characterized using eFuse Vref");
} else {
ESP_LOGI(TAG, "Characterized using Default Vref");
}
#endif
g_button.is_configured = 1;
}
/** initialize adc channel */
if (0 == g_button.ch[ch_index].is_init) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
//ADC1 Config
adc_oneshot_chan_cfg_t oneshot_config = {
.bitwidth = ADC_BUTTON_WIDTH,
.atten = ADC_BUTTON_ATTEN,
};
esp_err_t ret = adc_oneshot_config_channel(g_button.adc1_handle, config->adc_channel, &oneshot_config);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot config channel fail!", ESP_FAIL);
//-------------ADC1 Calibration Init---------------//
ret = adc_calibration_init(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, &g_button.adc1_cali_handle);
ADC_BTN_CHECK(ret == ESP_OK, "ADC1 Calibration Init False", 0);
#else
adc1_config_channel_atten(config->adc_channel, ADC_BUTTON_ATTEN);
#endif
g_button.ch[ch_index].channel = config->adc_channel;
g_button.ch[ch_index].is_init = 1;
g_button.ch[ch_index].last_time = 0;
}
g_button.ch[ch_index].btns[config->button_index].max = config->max;
g_button.ch[ch_index].btns[config->button_index].min = config->min;
g_button.ch_num++;
return ESP_OK;
}
esp_err_t button_adc_deinit(uint8_t channel, int button_index)
{
ADC_BTN_CHECK(channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_INVALID_ARG);
int ch_index = find_channel(channel);
ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG);
g_button.ch[ch_index].btns[button_index].max = 0;
g_button.ch[ch_index].btns[button_index].min = 0;
/** check button usage on the channel*/
uint8_t unused_button = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_BUTTON; i++) {
if (0 == g_button.ch[ch_index].btns[i].max) {
unused_button++;
}
}
if (unused_button == ADC_BUTTON_MAX_BUTTON && g_button.ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */
g_button.ch[ch_index].is_init = 0;
g_button.ch[ch_index].channel = ADC1_BUTTON_CHANNEL_MAX;
ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.ch[ch_index].channel);
}
/** check channel usage on the adc*/
uint8_t unused_ch = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.ch[i].is_init) {
unused_ch++;
}
}
if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.is_configured) { /**< if all channel is unused, deinit the adc */
g_button.is_configured = false;
memset(&g_button, 0, sizeof(adc_button_t));
ESP_LOGD(TAG, "all channel is unused, , deinit adc");
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_err_t ret = adc_oneshot_del_unit(g_button.adc1_handle);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot deinit fail", ESP_FAIL);
#endif
return ESP_OK;
}
static uint32_t get_adc_volatge(uint8_t channel)
{
uint32_t adc_reading = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
int adc_raw = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_oneshot_read(g_button.adc1_handle, channel, &adc_raw);
adc_reading += adc_raw;
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
int voltage = 0;
adc_cali_raw_to_voltage(g_button.adc1_cali_handle, adc_reading, &voltage);
ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %dmV", adc_reading, voltage);
#else
//Multisampling
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_reading += adc1_get_raw(channel);
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &g_button.adc_chars);
ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %"PRIu32"mV", adc_reading, voltage);
#endif
return voltage;
}
uint8_t button_adc_get_key_level(void *button_index)
{
static uint16_t vol = 0;
uint32_t ch = ADC_BUTTON_SPLIT_CHANNEL(button_index);
uint32_t index = ADC_BUTTON_SPLIT_INDEX(button_index);
ADC_BTN_CHECK(ch < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", 0);
ADC_BTN_CHECK(index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", 0);
int ch_index = find_channel(ch);
ADC_BTN_CHECK(ch_index >= 0, "The button_index is not init", 0);
/** It starts only when the elapsed time is more than 1ms */
if ((esp_timer_get_time() - g_button.ch[ch_index].last_time) > 1000) {
vol = get_adc_volatge(ch);
g_button.ch[ch_index].last_time = esp_timer_get_time();
}
if (vol <= g_button.ch[ch_index].btns[index].max &&
vol > g_button.ch[ch_index].btns[index].min) {
return 1;
}
return 0;
}

View file

@ -0,0 +1,61 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "bflb_gpio.h"
#include "oblfr_button_gpio.h"
#define DBG_TAG "BTN"
#include "log.h"
#define GPIO_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
LOG_E("%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
oblfr_err_t oblfr_button_gpio_init(const oblfr_button_gpio_config_t *config)
{
GPIO_BTN_CHECK(NULL != config, "Pointer of config is invalid", OBLFR_ERR_INVALID);
/* GPIO_BTN_CHECK(GPIO_IS_VALID_GPIO(config->gpio_num), "GPIO number error", OBLFR_ERR_INVALID); */
LOG_D("oblfr_button_gpio_init %d\r\n", config->gpio_num);
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
uint32_t cfg = 0;
if (config->active_level)
cfg = GPIO_INPUT | GPIO_PULLDOWN | GPIO_DRV_1;
else
cfg = GPIO_INPUT | GPIO_PULLUP | GPIO_DRV_1;
bflb_gpio_init(gpio, config->gpio_num, cfg);
return OBLFR_OK;
}
oblfr_err_t oblfr_button_gpio_deinit(int gpio_num)
{
LOG_D("oblfr_button_gpio_deinit %d\r\n", gpio_num);
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
bflb_gpio_deinit(gpio, gpio_num);
return OBLFR_OK;
}
uint8_t oblfr_button_gpio_get_key_level(void *gpio_num)
{
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
return bflb_gpio_read(gpio, (uint32_t)gpio_num);
}

View file

@ -0,0 +1,4 @@
sdk_generate_library(oblfr_indicator)
sdk_add_include_directories(include)
sdk_library_add_sources(${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_indicator.c)

View file

@ -0,0 +1,6 @@
config COMPONENT_STATUS_LED
bool "Status Led Component"
default n
select COMPONENT_SYSTEM
help
"Status Led Component from esp-iot-solution modified for Bouffalo MCU SDK"

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,2 @@
## LED
Taken from the esp-iot-solution and modified for Bouffalolab SDK.

View file

@ -0,0 +1,145 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) CO LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __LED_INDICATOR_H__
#define __LED_INDICATOR_H__
#include <stdint.h>
#include <stdbool.h>
#include "oblfr_common.h"
#include "FreeRTOS.h"
#include "timers.h"
#include "sdkconfig.h"
/**
* @brief led on-off state
*
*/
typedef enum {
LED_STATE_OFF = 0, /**< turn off the led */
LED_STATE_ON = 1, /**< turn on the led */
} blink_step_state_t;
/**
* @brief actions in this type
*
*/
typedef enum {
LED_BLINK_STOP = -1, /**< stop the blink */
LED_BLINK_HOLD, /**< hold the on-off state */
LED_BLINK_LOOP, /**< loop from first step */
} blink_step_type_t;
/**
* @brief one blink step, a meaningful signal consists of a group of steps
*
*/
typedef struct {
blink_step_type_t type; /**< action type in this step */
blink_step_state_t on_off; /**< hold on or off, set NULL if not LED_BLINK_HOLD*/
uint32_t hold_time_ms; /**< hold time(ms), set NULL if not LED_BLINK_HOLD,*/
} blink_step_t;
/**
* @brief led indicator blink mode, as a member of oblfr_indicator_config_t
*
*/
typedef enum {
LED_GPIO_MODE, /**< blink with max brightness*/
}oblfr_indicator_mode_t;
/**
* @brief led indicator specified configurations, as a arg when create a new indicator
*
*/
typedef struct {
bool off_level; /*!< gpio level of turn off. 0 if attach led positive side to esp32 gpio pin, 1 if attach led negative side*/
oblfr_indicator_mode_t mode; /*!< led work mode, eg. gpio or pwm mode */
}oblfr_indicator_config_t;
/**
* @brief The blink type with smaller index has the higher priority
* eg. BLINK_FACTORY_RESET priority is higher than BLINK_UPDATING
*/
typedef enum {
BLINK_FACTORY_RESET, /**< restoring factory settings */
BLINK_UPDATING, /**< updating software */
BLINK_CONNECTED, /**< connected to AP (or Cloud) succeed */
BLINK_PROVISIONED, /**< provision done */
BLINK_CONNECTING, /**< connecting to AP (or Cloud) */
BLINK_RECONNECTING, /**< reconnecting to AP (or Cloud), if lose connection */
BLINK_PROVISIONING, /**< provisioning */
BLINK_MAX, /**< INVALIED type */
} oblfr_indicator_blink_type_t;
typedef void* oblfr_indicator_handle_t; /*!< led indicator operation handle */
/**
* @brief create a led indicator instance with gpio number and configuration
*
* @param io_num gpio number of the led
* @param config configuration of the led, eg. gpio level when led off
* @return oblfr_indicator_handle_t handle of the led indicator, NULL if create failed.
*/
oblfr_indicator_handle_t oblfr_indicator_create(int io_num, const oblfr_indicator_config_t* config);
/**
* @brief get the handle of created oblfr_indicator with gpio number
*
* @param io_num gpio number of the led
* @return oblfr_indicator_handle_t handle of the created led indicator, NULL if not created
*/
oblfr_indicator_handle_t oblfr_indicator_get_handle(int io_num);
/**
* @brief delete the led indicator and release resource
*
* @param p_handle pointer to led indicator handle
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_FAIL Fail
* - ESP_OK Success
*/
oblfr_err_t oblfr_indicator_delete(oblfr_indicator_handle_t* p_handle);
/**
* @brief start a new blink_type on the led indicator. if mutiple blink_type started simultaneously,
* it will be executed according to priority.
*
* @param handle led indicator handle
* @param blink_type predefined blink type
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NOT_FOUND no predefined blink_type found
* - ESP_OK Success
*/
oblfr_err_t oblfr_indicator_start(oblfr_indicator_handle_t handle, oblfr_indicator_blink_type_t blink_type);
/**
* @brief stop a blink_type. you can stop a blink_type at any time, no matter it is executing or waiting to be executed.
*
* @param handle led indicator handle
* @param blink_type predefined blink type
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NOT_FOUND no predefined blink_type found
* - ESP_OK Success
*/
oblfr_err_t oblfr_indicator_stop(oblfr_indicator_handle_t handle, oblfr_indicator_blink_type_t blink_type);
#endif

View file

@ -0,0 +1,435 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) CO LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdbool.h>
#include <string.h>
#include <sys/queue.h>
#include "FreeRTOS.h"
#include "timers.h"
#include "semphr.h"
#include "bflb_gpio.h"
#include "oblfr_indicator.h"
#define DBG_TAG "LED"
#include "log.h"
#define LED_INDICATOR_CHECK(a, str, ret) if(!(a)) { \
LOG_E("%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
return (ret); \
}
#define LED_INDICATOR_CHECK_GOTO(a, str, lable) if(!(a)) { \
LOG_E("%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
goto lable; \
}
#define NULL_ACTIVE_BLINK -1
/*********************************** Config Blink List in Different Conditions ***********************************/
/**
* @brief connecting to AP (or Cloud)
*
*/
static const blink_step_t connecting[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 200},
{LED_BLINK_HOLD, LED_STATE_OFF, 800},
{LED_BLINK_LOOP, 0, 0},
};
/**
* @brief connected to AP (or Cloud) succeed
*
*/
static const blink_step_t connected[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 1000},
{LED_BLINK_LOOP, 0, 0},
};
/**
* @brief reconnecting to AP (or Cloud), if lose connection
*
*/
static const blink_step_t reconnecting[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 100},
{LED_BLINK_HOLD, LED_STATE_OFF, 200},
{LED_BLINK_LOOP, 0, 0},
}; //offline
/**
* @brief updating software
*
*/
static const blink_step_t updating[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 50},
{LED_BLINK_HOLD, LED_STATE_OFF, 100},
{LED_BLINK_HOLD, LED_STATE_ON, 50},
{LED_BLINK_HOLD, LED_STATE_OFF, 800},
{LED_BLINK_LOOP, 0, 0},
};
/**
* @brief restoring factory settings
*
*/
static const blink_step_t factory_reset[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 200},
{LED_BLINK_HOLD, LED_STATE_OFF, 200},
{LED_BLINK_LOOP, 0, 0},
};
/**
* @brief provisioning
*
*/
static const blink_step_t provisioning[] = {
{LED_BLINK_HOLD, LED_STATE_ON, 500},
{LED_BLINK_HOLD, LED_STATE_OFF, 500},
{LED_BLINK_LOOP, 0, 0},
};
/**
* @brief provision done
*
*/
static const blink_step_t provisioned[] = {
{LED_BLINK_HOLD, LED_STATE_OFF, 1000},
{LED_BLINK_STOP, 0, 0},
};
/**
* @brief led indicator blink lists, the index like BLINK_FACTORY_RESET defined the priority of the blink
*
*/
blink_step_t const * oblfr_indicator_blink_lists[] = {
[BLINK_FACTORY_RESET] = factory_reset,
[BLINK_UPDATING] = updating,
[BLINK_CONNECTED] = connected,
[BLINK_PROVISIONED] = provisioned,
[BLINK_RECONNECTING] = reconnecting,
[BLINK_CONNECTING] = connecting,
[BLINK_PROVISIONING] = provisioning,
[BLINK_MAX] = NULL,
};
/* Led blink_steps handling machine implementation */
#define BLINK_LIST_NUM (sizeof(oblfr_indicator_blink_lists)/sizeof(oblfr_indicator_blink_lists[0]))
/**
* @brief led indicator object
*
*/
typedef struct {
bool off_level; /*!< gpio level during led turn off */
int io_num; /*!< gpio number of the led indicator */
oblfr_indicator_mode_t mode; /*!< led work mode, eg. gpio or pwm mode */
int active_blink; /*!< active blink list*/
int *p_blink_steps; /*!< stage of each blink list */
SemaphoreHandle_t mutex; /*!< mutex to achive thread-safe */
TimerHandle_t h_timer; /*!< led timmer handle, invalid if works in pwm mode */
}_oblfr_indicator_t;
typedef struct _oblfr_indicator_slist_t{
SLIST_ENTRY(_oblfr_indicator_slist_t) next;
_oblfr_indicator_t *p_oblfr_indicator;
}_oblfr_indicator_slist_t;
static SLIST_HEAD(_oblfr_indicator_head_t, _oblfr_indicator_slist_t) s_oblfr_indicator_slist_head = SLIST_HEAD_INITIALIZER(s_oblfr_indicator_slist_head);
static oblfr_err_t _oblfr_indicator_add_node(_oblfr_indicator_t *p_oblfr_indicator)
{
LED_INDICATOR_CHECK(p_oblfr_indicator != NULL, "pointer can not be NULL", OBLFR_ERR_INVALID);
_oblfr_indicator_slist_t *node = calloc(1, sizeof(_oblfr_indicator_slist_t));
LED_INDICATOR_CHECK(node != NULL, "calloc node failed", OBLFR_ERR_NOMEM);
node->p_oblfr_indicator = p_oblfr_indicator;
SLIST_INSERT_HEAD(&s_oblfr_indicator_slist_head, node, next);
return OBLFR_OK;
}
static oblfr_err_t _oblfr_indicator_remove_node(_oblfr_indicator_t *p_oblfr_indicator)
{
LED_INDICATOR_CHECK(p_oblfr_indicator != NULL, "pointer can not be NULL", OBLFR_ERR_INVALID);
_oblfr_indicator_slist_t *node;
SLIST_FOREACH(node, &s_oblfr_indicator_slist_head, next) {
if (node->p_oblfr_indicator == p_oblfr_indicator) {
SLIST_REMOVE(&s_oblfr_indicator_slist_head, node, _oblfr_indicator_slist_t, next);
free(node);
break;
}
}
return OBLFR_OK;
}
/**
* @brief init a gpio to control led
*
* @param io_num gpio number of the led
* @return true init succeed
* @return false init failed
*/
static bool _led_gpio_init(int io_num)
{
LOG_D("led_gpio_init %d\r\n", io_num);
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, io_num, GPIO_OUTPUT|GPIO_PULLDOWN|GPIO_DRV_3);
return true;
}
/**
* @brief deinit a gpio to control led
*
* @param io_num gpio number of the led
* @return true deinit succeed
* @return false deinit failed
*/
static bool _led_gpio_deinit(int io_num)
{
LOG_D("led_gpio_deinit %d\r\n", io_num);
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
bflb_gpio_deinit(gpio, io_num);
return true;
}
/**
* @brief turn on or off of the led
*
* @param io_num gpio number of the led
* @param off_level gpio level when off, 0 if attach led positive side to esp32 gpio pin, 1 if attach led negative side
* @param state target state
* @return esp_err_t
*/
static oblfr_err_t _led_set_state(int io_num, bool off_level, blink_step_state_t state)
{
LOG_D("_led_set_state %d state %d %d\r\n", io_num, state, off_level);
struct bflb_device_s *gpio = bflb_device_get_by_name("gpio");
switch (state)
{
case LED_STATE_ON:
if (off_level)
bflb_gpio_set(gpio, io_num);
else
bflb_gpio_reset(gpio, io_num);
break;
case LED_STATE_OFF:
default :
if (off_level)
bflb_gpio_reset(gpio, io_num);
else
bflb_gpio_set(gpio, io_num);
break;
}
return OBLFR_OK;
}
/**
* @brief switch to the first high priority incomplete blink steps
*
* @param p_oblfr_indicator pointer to led indicator
*/
static void _blink_list_switch(_oblfr_indicator_t *p_oblfr_indicator)
{
p_oblfr_indicator->active_blink = NULL_ACTIVE_BLINK; //stop active blink
for(size_t index = 0; index < BLINK_LIST_NUM; index ++) //find the first incomplete blink
{
if (p_oblfr_indicator->p_blink_steps[index] != LED_BLINK_STOP)
{
p_oblfr_indicator->active_blink = index;
break;
}
}
}
/**
* @brief timmer callback to control led and counter steps
*
* @param xTimer handle of the timmer instance
*/
static void _blink_list_runner(TimerHandle_t xTimer)
{
_oblfr_indicator_t * p_oblfr_indicator = (_oblfr_indicator_t *)pvTimerGetTimerID(xTimer);
bool leave = false;
while(!leave) {
if (p_oblfr_indicator->active_blink == NULL_ACTIVE_BLINK)
return;
int active_blink = p_oblfr_indicator->active_blink;
int active_step = p_oblfr_indicator->p_blink_steps[active_blink];
const blink_step_t *p_blink_step = &oblfr_indicator_blink_lists[active_blink][active_step];
p_oblfr_indicator->p_blink_steps[active_blink] += 1;
if (pdFALSE == xSemaphoreTake(p_oblfr_indicator->mutex, pdMS_TO_TICKS(100))) {
LOG_E("blinks runner blockTime expired, try repairing...");
xTimerChangePeriod(p_oblfr_indicator->h_timer, pdMS_TO_TICKS(100), 0);
xTimerStart(p_oblfr_indicator->h_timer, 0);
break;
}
switch(p_blink_step->type) {
case LED_BLINK_LOOP:
p_oblfr_indicator->p_blink_steps[active_blink] = 0;
break;
case LED_BLINK_STOP:
p_oblfr_indicator->p_blink_steps[active_blink] = LED_BLINK_STOP;
_blink_list_switch(p_oblfr_indicator);
break;
case LED_BLINK_HOLD:
_led_set_state(p_oblfr_indicator->io_num, p_oblfr_indicator->off_level, p_blink_step->on_off);
if (p_blink_step->hold_time_ms == 0)
break;
xTimerChangePeriod(p_oblfr_indicator->h_timer, pdMS_TO_TICKS(p_blink_step->hold_time_ms), 0);
xTimerStart(p_oblfr_indicator->h_timer, 0);
leave=true;
break;
default:
assert(false && "invalid state");
break;
}
xSemaphoreGive(p_oblfr_indicator->mutex);
}
}
oblfr_indicator_handle_t oblfr_indicator_create(int io_num, const oblfr_indicator_config_t* config)
{
LED_INDICATOR_CHECK(config != NULL, "invalid config pointer", NULL);
char timmer_name[16] = {'\0'};
snprintf(timmer_name, sizeof(timmer_name) - 1, "%s%02x", "led_tmr_", io_num);
_oblfr_indicator_t *p_oblfr_indicator = (_oblfr_indicator_t *)calloc(1, sizeof(_oblfr_indicator_t));
LED_INDICATOR_CHECK(p_oblfr_indicator != NULL, "calloc indicator memory failed", NULL);
p_oblfr_indicator->off_level = config->off_level;
p_oblfr_indicator->io_num = io_num;
p_oblfr_indicator->mode = config->mode;
p_oblfr_indicator->active_blink = NULL_ACTIVE_BLINK;
p_oblfr_indicator->p_blink_steps = (int *)calloc(BLINK_LIST_NUM, sizeof(int));
LED_INDICATOR_CHECK_GOTO(p_oblfr_indicator->p_blink_steps != NULL, "calloc blink_steps memory failed", cleanup_indicator);
p_oblfr_indicator->mutex = xSemaphoreCreateMutex();
LED_INDICATOR_CHECK_GOTO(p_oblfr_indicator->mutex != NULL, "create mutex failed", cleanup_indicator_blinkstep);
for(size_t j = 0; j < BLINK_LIST_NUM; j++) {
*(p_oblfr_indicator->p_blink_steps + j) = LED_BLINK_STOP;
}
switch (p_oblfr_indicator->mode)
{
case LED_GPIO_MODE: /**< blink with max brightness*/
{
bool ininted = _led_gpio_init(p_oblfr_indicator->io_num);
LED_INDICATOR_CHECK_GOTO(ininted != false, "init led gpio failed", cleanup_all);
p_oblfr_indicator->h_timer = xTimerCreate(timmer_name, (pdMS_TO_TICKS(100)), pdFALSE, (void *)p_oblfr_indicator, _blink_list_runner);
LED_INDICATOR_CHECK_GOTO(p_oblfr_indicator->h_timer != NULL, "led timmer create failed", cleanup_all);
}
break;
default:
LED_INDICATOR_CHECK_GOTO(false, "mode not supported", cleanup_all);
break;
}
_oblfr_indicator_add_node(p_oblfr_indicator);
return (oblfr_indicator_handle_t)p_oblfr_indicator;
cleanup_indicator:
free(p_oblfr_indicator);
return NULL;
cleanup_indicator_blinkstep:
free(p_oblfr_indicator->p_blink_steps);
free(p_oblfr_indicator);
return NULL;
cleanup_all:
vSemaphoreDelete(p_oblfr_indicator->mutex);
free(p_oblfr_indicator->p_blink_steps);
free(p_oblfr_indicator);
return NULL;
}
oblfr_indicator_handle_t oblfr_indicator_get_handle(int io_num)
{
_oblfr_indicator_slist_t *node;
SLIST_FOREACH(node, &s_oblfr_indicator_slist_head, next) {
if (node->p_oblfr_indicator->io_num == io_num) {
return (oblfr_indicator_handle_t)(node->p_oblfr_indicator);
}
}
return NULL;
}
oblfr_err_t oblfr_indicator_delete(oblfr_indicator_handle_t* p_handle)
{
LED_INDICATOR_CHECK(p_handle != NULL && *p_handle != NULL, "invalid p_handle", OBLFR_ERR_INVALID);
_oblfr_indicator_t *p_oblfr_indicator = (_oblfr_indicator_t *)(*p_handle);
xSemaphoreTake(p_oblfr_indicator->mutex, portMAX_DELAY);
switch (p_oblfr_indicator->mode)
{
case LED_GPIO_MODE:
{
bool deinited = _led_gpio_deinit(p_oblfr_indicator->io_num);
LED_INDICATOR_CHECK(deinited != false, "deinit led gpio failed", OBLFR_ERR_ERROR);
BaseType_t ret = xTimerDelete(p_oblfr_indicator->h_timer, portMAX_DELAY);
LED_INDICATOR_CHECK(ret == pdPASS, "led timmer delete failed", OBLFR_ERR_ERROR);
}
break;
default:
LED_INDICATOR_CHECK(false, "mode not supported", OBLFR_ERR_NOTSUPPORTED);
break;
}
_oblfr_indicator_remove_node(p_oblfr_indicator);
vSemaphoreDelete(p_oblfr_indicator->mutex);
free(p_oblfr_indicator->p_blink_steps);
free(*p_handle);
*p_handle = NULL;
return OBLFR_OK;
}
oblfr_err_t oblfr_indicator_start(oblfr_indicator_handle_t handle, oblfr_indicator_blink_type_t blink_type)
{
LED_INDICATOR_CHECK(handle != NULL && blink_type >= 0 && blink_type < BLINK_MAX, "invalid p_handle", OBLFR_ERR_INVALID);
LED_INDICATOR_CHECK(oblfr_indicator_blink_lists[blink_type] != NULL, "undefined blink_type", OBLFR_ERR_INVALID);
_oblfr_indicator_t *p_oblfr_indicator = (_oblfr_indicator_t *)handle;
xSemaphoreTake(p_oblfr_indicator->mutex, portMAX_DELAY);
p_oblfr_indicator->p_blink_steps[blink_type] = 0;
_blink_list_switch(p_oblfr_indicator);
xSemaphoreGive(p_oblfr_indicator->mutex);
if(p_oblfr_indicator->active_blink == blink_type) { //re-run from first step
_blink_list_runner(p_oblfr_indicator->h_timer);
}
return OBLFR_OK;
}
oblfr_err_t oblfr_indicator_stop(oblfr_indicator_handle_t handle, oblfr_indicator_blink_type_t blink_type)
{
LED_INDICATOR_CHECK(handle != NULL && blink_type >= 0 && blink_type < BLINK_MAX, "invalid p_handle", OBLFR_ERR_INVALID);
LED_INDICATOR_CHECK(oblfr_indicator_blink_lists[blink_type] != NULL, "undefined blink_type", OBLFR_ERR_INVALID);
_oblfr_indicator_t *p_oblfr_indicator = (_oblfr_indicator_t *)handle;
xSemaphoreTake(p_oblfr_indicator->mutex, portMAX_DELAY);
p_oblfr_indicator->p_blink_steps[blink_type] = LED_BLINK_STOP;
_blink_list_switch(p_oblfr_indicator); //stop and swith to next blink steps
xSemaphoreGive(p_oblfr_indicator->mutex);
if(p_oblfr_indicator->active_blink == blink_type) { //re-run from first step
_blink_list_runner(p_oblfr_indicator->h_timer);
}
return OBLFR_OK;
}

View file

@ -0,0 +1,8 @@
sdk_generate_library(oblfr_nvkvs)
sdk_add_include_directories(include)
sdk_add_include_directories(kved)
sdk_library_add_sources(${CMAKE_CURRENT_SOURCE_DIR}/kved/kved.c
${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_nvkvs.c)
sdk_library_add_sources_ifdef(CONFIG_COMPONENT_NVKVS_MEM_BACKEND ${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_kved_memory.c)
sdk_library_add_sources_ifdef(CONFIG_COMPONENT_NVKVS_FLASH_BACKEND ${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_kved_flash.c)

26
components/nvkvs/Kconfig Normal file
View file

@ -0,0 +1,26 @@
config COMPONENT_NVKVS
bool "Non-Volatile Key-Value Storage Component"
default y
select COMPONENT_SYSTEM
help
Non-Volatile Key-Value Storage Component
menu "NVKVS Configuration"
visible if COMPONENT_NVKVS
config COMPONENT_NVKVS_MEM_BACKEND
bool "Use memory backend"
default y
help
Use memory backend
config COMPONENT_NVKVS_FLASH_BACKEND
bool "Use flash backend"
default y
help
Use flash backend
config COMPONENT_NVKVS_MAX_STRING_SIZE
int "Maximum String Size that can be stored in NVKVS"
default 64
range 8 256
help
The Maximum String Size that can be stored in a Key
endmenu

View file

@ -0,0 +1,10 @@
#ifndef OBLFR_KVED_FILE_H
#define OBLFR_KVED_FILE_H
#include "kved.h"
kved_flash_driver_t *oblfr_kved_file_configure();
void oblfr_kved_file_close(kved_flash_driver_t *driver);
#endif // OBLFR_KVED_FILE_H

View file

@ -0,0 +1,20 @@
#ifndef OBLFR_KVED_FLASH_H
#define OBLFR_KVED_FLASH_H
#include "kved.h"
/**
* @brief Flash driver configuration
*/
typedef struct oblfr_kved_flash_driver_s {
uint32_t flash_addr; /**< Start Address in Flash to store the configuration */
uint32_t flash_sector_size; /**< Flash Sector Size. Auto Populated */
uint32_t max_entries; /**< Max number of entries in the flash. If 0, then auto calculated from Flash Sector Size. (255 for Flash Sector Size of 4096Bytes) */
} oblfr_kved_flash_driver_t;
kved_flash_driver_t *oblfr_kved_flash_configure(oblfr_kved_flash_driver_t *cfg);
void oblfr_kved_flash_close(kved_flash_driver_t *driver);
#endif // OBLFR_KVED_MEMORY_H

View file

@ -0,0 +1,10 @@
#ifndef OBLFR_KVED_MEMORY_H
#define OBLFR_KVED_MEMORY_H
#include "kved.h"
kved_flash_driver_t *oblfr_kved_memory_configure();
void oblfr_kved_memory_close(kved_flash_driver_t *driver);
#endif // OBLFR_KVED_MEMORY_H

View file

@ -0,0 +1,487 @@
// Copyright 2023 Justin Hammond
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Modeled After the esp-idf timer implementation
#ifndef OBLFR_NVKVS_H
#define OBLFR_NVKVS_H
#include <stdint.h>
#include <stdbool.h>
#include "oblfr_common.h"
#include "kved.h"
#include "oblfr_kved_flash.h"
#include "oblfr_kved_memory.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief NVKVS storage types
*/
typedef enum {
OBLFR_NVKVS_STORAGE_FLASH, /**< Flash storage - Need to provide a oblfr_kved_flash_driver_t configuration*/
OBLFR_NVKVS_STORAGE_RAM, /**< RAM storage */
} oblfr_nvkvs_storage_t;
/**
* @brief NVKVS configuration
*/
typedef struct {
oblfr_nvkvs_storage_t storage; /**< Storage type */
union {
oblfr_kved_flash_driver_t *flash; /**< Flash driver configuration */
} drv_cfg;
} oblfr_nvkvs_cfg_t;
/**
* @brief NVKVS handle
*/
typedef struct oblfr_nvkvs_handle_s oblfr_nvkvs_handle_t;
/**
* @brief NVKVS data types
*/
typedef enum oblfr_nvkvs_data_types_e
{
OBLFR_NVKVS_DATA_TYPE_UINT8 = 0, /**< 8 bits, unsigned */
OBLFR_NVKVS_DATA_TYPE_INT8, /**< 8 bits, signed */
OBLFR_NVKVS_DATA_TYPE_UINT16, /**< 16 bits, unsigned */
OBLFR_NVKVS_DATA_TYPE_INT16, /**< 16 bits, signed */
OBLFR_NVKVS_DATA_TYPE_UINT32, /**< 32 bits, unsigned */
OBLFR_NVKVS_DATA_TYPE_INT32, /**< 32 bits, com sinal */
OBLFR_NVKVS_DATA_TYPE_FLOAT, /**< Single precision floating point (float) */
OBLFR_NVKVS_DATA_TYPE_STRING, /**< String up to @ref CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE bytes, excluding terminator */
OBLFR_NVKVS_DATA_TYPE_UINT64, /**< 64 bits, signed */
OBLFR_NVKVS_DATA_TYPE_INT64, /**< 64 bits, unsigned */
OBLFR_NVKVS_DATA_TYPE_DOUBLE, /**< Double precision floating point (double) */
} oblfr_nvkvs_data_types_t;
/**
* @brief NVKVS data values
*/
typedef union oblfr_nvkvs_value_u
{
uint8_t u8; /**< unsigned 8 bits value */
int8_t i8; /**< signed 8 bits value */
uint16_t u16; /**< unsigned 16 bits value */
int16_t i16; /**< signed 16 bits value */
uint32_t u32; /**< unsigned 32 bits value */
int32_t i32; /**< signed 32 bits value */
float flt; /**< single precision float */
uint8_t str[CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE]; /**< string */
uint64_t u64; /**< unsigned 64 bits value */
int64_t i64; /**< signed 64 bits value */
double dbl; /**< double precision float */
} oblfr_nvkvs_value_t;
/**
* @brief NVKVS data structure
*/
typedef struct oblfr_nvkvs_data_s {
char key[8];
oblfr_nvkvs_data_types_t type;
oblfr_nvkvs_value_t value;
} oblfr_nvkvs_data_t;
/**
* @brief NVKVS initialization
*
* Initilize the NVKVS storage
* @param in cfg NVKVS configuration
* @return oblfr_nvkvs_handle_t handle for use with other NVKVS functions or NULL on error
*/
oblfr_nvkvs_handle_t *oblfr_nvkvs_init(const oblfr_nvkvs_cfg_t *cfg);
/**
* @brief NVKVS deinitialization
*
* Deinitilize the NVKVS storage
* @param in handle NVKVS handle
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid or a invalid NVKVS Storage Driver was used
*/
oblfr_err_t oblfr_nvkvs_deinit(oblfr_nvkvs_handle_t *handle);
/**
* @brief Dump the NVKVS storage to stdout for debugging
*
* @param in handle NVKVS handle
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
*/
oblfr_err_t oblfr_nvkvs_dump(oblfr_nvkvs_handle_t *handle);
/**
* @brief Compact the NVKVS storage
*
* This function will compact the NVKVS storage, removing deleted entries
* You don't normally need to call this function, as it will automatically be compacted when the database
* reaches capacity
* @param in handle NVKVS handle
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the storage was not compacted
*/
oblfr_err_t oblfr_nvkvs_compact(oblfr_nvkvs_handle_t *handle);
/**
* @brief Get the maximum number of entries the storage can hold
*
* This includes Deleted entries as well
*
* @param in handle NVKVS handle
* @return Number of entries the storage can hold
*/
uint16_t oblfr_nvkvs_get_size(oblfr_nvkvs_handle_t *handle);
/**
* @brief Get the number of used entries in the storage
*
* @param in handle NVKVS handle
* @return Number of used entries in the storage
*/
uint16_t oblfr_nvkvs_used_entries(oblfr_nvkvs_handle_t *handle);
/**
* @brief Get the number of free entries in the storage.
*
* (includes deleted entries as these can be recycled by a database compact)
*
* @param in handle NVKVS handle
* @return Number of free entries in the storage
*/
uint16_t oblfr_nvkvs_free_entries(oblfr_nvkvs_handle_t *handle);
/**
* @brief Get the number of deleted entries in the storage
*
* @param in handle NVKVS handle
* @return Number of deleted entries in the storage
*/
uint16_t oblfr_nvkvs_deleted_entries(oblfr_nvkvs_handle_t *handle);
/**
* @brief obtain a index to the first entry in the database
*
* @param in handle NVKVS handle
* @return index to the first entry in the database
* 0 if the database is empty
*/
int16_t oblfr_nvkvs_iter_init(oblfr_nvkvs_handle_t *handle);
/**
* @brief obtain a index to the next entry in the database
*
* @param in handle NVKVS handle
* @param in iter index to the current entry in the database
* @return index to the next entry in the database
* 0 if the database is empty
*/
int16_t oblfr_nvkvs_iter_next(oblfr_nvkvs_handle_t *handle, int16_t iter);
/**
* @brief Get the data for a given index
*
* @param in handle NVKVS handle
* @param in index index to the entry in the database
* @param out data pointer to a data structure to store the data
* @return OBLFR_OK on success
* OBLFR_ERR_ERROR if the index was invalid
*/
oblfr_err_t oblfr_nvkvs_get_item(oblfr_nvkvs_handle_t *handle, uint16_t index, oblfr_nvkvs_data_t *data);
/**
* @brief Save a uint8_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_u8(oblfr_nvkvs_handle_t *handle, const char *key, uint8_t value);
/**
* @brief Get a uint8_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a uint8_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_u8(oblfr_nvkvs_handle_t *handle, const char *key, uint8_t *value);
/**
* @brief Save a int8_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_i8(oblfr_nvkvs_handle_t *handle, const char *key, int8_t value);
/**
* @brief Get a int8_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a int8_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_i8(oblfr_nvkvs_handle_t *handle, const char *key, int8_t *value);
/**
* @brief Save a uint16_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_u16(oblfr_nvkvs_handle_t *handle, const char *key, uint16_t value);
/**
* @brief Get a uint16_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a uint16_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_u16(oblfr_nvkvs_handle_t *handle, const char *key, uint16_t *value);
/**
* @brief Save a int16_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_i16(oblfr_nvkvs_handle_t *handle, const char *key, int16_t value);
/**
* @brief Get a int16_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a int16_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_i16(oblfr_nvkvs_handle_t *handle, const char *key, int16_t *value);
/**
* @brief Save a uint32_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_u32(oblfr_nvkvs_handle_t *handle, const char *key, uint32_t value);
/**
* @brief Get a uint32_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a uint32_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_u32(oblfr_nvkvs_handle_t *handle, const char *key, uint32_t *value);
/**
* @brief Save a int32_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_i32(oblfr_nvkvs_handle_t *handle, const char *key, int32_t value);
/**
* @brief Get a int32_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a int32_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_i32(oblfr_nvkvs_handle_t *handle, const char *key, int32_t *value);
/**
* @brief Save a uint64_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_u64(oblfr_nvkvs_handle_t *handle, const char *key, uint64_t value);
/**
* @brief Get a uint64_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a uint64_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_u64(oblfr_nvkvs_handle_t *handle, const char *key, uint64_t *value);
/**
* @brief Save a int64_t value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_i64(oblfr_nvkvs_handle_t *handle, const char *key, int64_t value);
/**
* @brief Get a int64_t value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a int64_t to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_i64(oblfr_nvkvs_handle_t *handle, const char *key, int64_t *value);
/**
* @brief Save a float value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_float(oblfr_nvkvs_handle_t *handle, const char *key, float value);
/**
* @brief Get a float value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a float to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_float(oblfr_nvkvs_handle_t *handle, const char *key, float *value);
/**
* @brief Save a double value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_double(oblfr_nvkvs_handle_t *handle, const char *key, double value);
/**
* @brief Get a double value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a double to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_double(oblfr_nvkvs_handle_t *handle, const char *key, double *value);
/**
* @brief Save a string value to the database
*
* @param in handle NVKVS handle
* @param in key key to store the value under
* @param in value value to store
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be stored
*/
oblfr_err_t oblfr_nvkvs_set_string(oblfr_nvkvs_handle_t *handle, const char *key, const char *value);
/**
* @brief Get a string value from the database
*
* @param in handle NVKVS handle
* @param in key key to retrieve the value from
* @param out value pointer to a string to store the value
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the value could not be retrieved
*/
oblfr_err_t oblfr_nvkvs_get_string(oblfr_nvkvs_handle_t *handle, const char *key, char *value);
/**
* @brief Delete a key from the database
*
* @param in handle NVKVS handle
* @param in key key to delete
* @return OBLFR_OK on success
* OBLFR_ERR_INVALID if the handle was invalid
* OBLFR_ERR_ERROR if the key could not be deleted
* OBLFR_ERR_NOT_FOUND if the key was not found
*/
oblfr_err_t oblfr_nvkvs_delete(oblfr_nvkvs_handle_t *handle, const char *key);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 Marcelo Barros de Almeida <marcelobarrosalmeida@gmail.com>
Permission is hereby granted, 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.

View file

@ -0,0 +1,166 @@
# KVED
This has been heavily modified from the original version to work with the OBLFR. The information below might be out dated!
kved (key/value embedded database) is a simple key/value database implementation for microcontrollers.
## kved features
* It is a copy-on-write (COW) implementation: the value is only written when it changes.
* Wear leveling: new values or changed values are always written into different positions, cycling over the flash sector.
* Two sectors (with same size) are used to allow sector clean up when the current sector has space but it is full due to erased entries.
* Power loss tolerant: incomplete writings due to power outages does not corrupts the database. However, the newer value can be lost.
* Integrity checks at startup, erasing incomplete writings (old values are not lost) and checking which sector is in use.
* Flash with word size of 32 or 64 bits are supported.
* Iteration over the database supported.
## Limitations
* You need to use two flash sectors, with same size.
* Your flash needs to support word granularity writings.
* After writing into a flash position (a word) should be possible to write again, lowering bit that were high. This is the mechanism to invalided a register.
* Multiple sectors support not implemented.
* As with many wear leveling systems, it is not desirable to use the system near maximum storage capacity. An effective use of 50% or less of the entries is recommended.
## How kved works
kved is based on the principle that each writing (of a word) is atomic and also on the idea that a sector can be erased individually.
Each entry in the database is encoded by two words. The first encodes the access key, record type and recorde size and the second the value itself. The size of the input label depends on whether the word is 32-bit or 64-bit. Type and size are encoded in the first byte, in 4 bits each.
<pre>
32 bits flash:
+------+--+
| 3 2 1| 0| <= bytes
+------+--+
| LABEL|TS|
+------+--+
64 bits flash:
+--------------+--+
| 7 6 5 4 3 2 1| 0| <= bytes
+--------------+--+
| LABEL|TS|
+--------------+--+
</pre>
Thus, the complete entry has the following format:
<pre>
+---------+---------+
| 1st WORD| 2nd WORD|
+------+--+---------+
| LABEL|TS| VALUE |
+------+--+---------+
</pre>
Entries that are invalidated have the key value set as zero. This is used when a new key value is written. In this case, the new value is written in the first free position of the sector and the old one has its keyword zeroed.
The database has a header with two words, at the beggining of the sector.
The first word is a kved signature, identified by 0xDEADBEEF, and the second is a counter.
This counter is used to identify which sector is the most recent (with the highest counter value). Every time the sector is copied this counter is incremented.
Thus, in memory, the organization of the data will be as follows:
<pre>
<-- WORD --><-- WORD --> <= 8 bytes when using flash with word of 64 bits
+------------+------------+
| SIGNATURE | COUNTER | <= HEADER ID AND NEWER COPY IDENTIFICATION
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE | <= VALID KEY (KEY ENTRY, TYPE, SIZE AND VALUE)
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE |
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE |
+---------+--+------------+
|000 ... 0000| KEY VALUE | <= ERASED KEY
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE |
+---------+--+------------+
|FFF ... FFFF|FFF ... FFFF| <= EMPTY ENTRY
+------------+------------+
|FFF ... FFFF|FFF ... FFFF|
+------------+------------+
|FFF ... FFFF|FFF ... FFFF|
+------------+------------+
</pre>
The second sector is used when the first sector is full or with many invalid entries. In this case, the second sector is erased and it is populated with all valid entries only, increasing the header counter and freeing space for new entries. When the second sector is full, the first sector will be erased and used, following the same strategy.
At startup, some integrity checks are made. The first one is related to which sector should be used, being done by the ```kved_sector_consistency_check()``` function. Once the sector in use is decided, the data is also checked using the ```kved_data_consistency_check()``` function. These checks allow database consistency to be maintained even in the event of a power failure during writing or copying.
The amount of available entries is dependent on the sector size and the flash word size and can be given by the following expression:
<pre>
num_entries = sector_size/(word_size*2) - 1
</pre>
For a sector of 2048 bytes and a 32 bits flash, the number of entries is given by 255 entries (2048/(4*2) - 1).
## Missing features
* Data key integrity check after writing (verify valid data label, size and labels)
# Porting kved
kved basically depends on the following files:
* ```kved.c``` / ```kved.h```: kved implementation
* ```kved_cpu.c``` / ```kved_cpu.h```: cpu portability API if you need thead/interrupt safe operation
* ```kved_flash.c``` / ```kved_flash.h```: flash API
All these files should be portable and platform independent. However, you need to write the portability layer. So, create these files and code them according to your microcontroller:
### ```port_cpu.c```
Create your implementation for thread/interrupt safe operation (optional):
* ```void kved_cpu_critical_section_enter(void)```
* ```void kved_cpu_critical_section_leave(void)```
### ```port_flash.c```
You need to reserve two sectors of your microcontroller for kved usage and create your functions for erase sector, read and write words and intialize the flash. As the sector size depends on the microcontroller used, an additional function for reporting it is also required.
* ```bool kved_flash_sector_erase(kved_flash_sector_t sec)```
* ```void kved_flash_data_write(kved_flash_sector_t sec, uint16_t index, kved_word_t data)```
* ```kved_word_t kved_flash_data_read(kved_flash_sector_t sec, uint16_t index)```
* ```uint32_t kved_flash_sector_size(void)```
* ```void kved_flash_init(void)```
### ```port_flash.h```
Define flash word size.
## Linker
Do not forget to reserve your flash sectors on your linker file otherwise your compiler can use them. For GNU linker (ld) see examples in STM32L433RC, STM32F411CE, STM32WB55RG and STM32F103C8 ports.
## Ports
A the momment, 5 ports are supported:
* Simul (simulation port, runs on PC, useful for debugging). Just type scons at repository root and run ```./kved```. GCC will be used as compiler.
* STM32L433RC using low level STM32 drivers.
* STM32F411CE (blackpill) using high level STM32 drivers.
* STM32WB55RG using low level STM32 drivers plus optional HSEM, using high level STM32 drivers.
* STM32F103C8 (bluepill) using low level STM32 drivers.
# Documentation
Please, see the [HTML Documentation](https://marcelobarrosalmeida.github.io/kved/).
# License
[MIT License](./LICENSE.md)
# Contatc
For enhancements and new ports, please clone the repository and submit a pull request.
For bugs, please fill a new issue.
Any other requests, please contact me by [email](marcelobarrosalmeida@gmail.com).

1251
components/nvkvs/kved/kved.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,383 @@
#ifndef KVED_H
#define KVED_H
/*
kved (key/value embedded database), a simple key/value database
implementation for microcontrollers.
Copyright (c) 2022 Marcelo Barros de Almeida <marcelobarrosalmeida@gmail.com>
*/
#pragma once
/**
@file
@defgroup KVED KVED
@brief KVED (key/value embedded databse): simple key/value persistence for embedded applications.
@{
*/
#include <stddef.h>
#include <stdbool.h>
#include "sdkconfig.h"
#define KVED_FLASH_WORD_SIZE 8
/** Maximum supported string length, per record, without termination */
#ifndef CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE
#error "KVED: CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE not defined"
#else
#define KVED_MAX_STRING_SIZE CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE
#endif
//#define KVED_DEBUG
/* kved structure
Main Index:
<- 8 bytes -><- 8 bytes ->
+------------+------------+
| SIGNATURE | COUNTER | <= HEADER ID AND NEWER COPY IDENTIFICATION
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE | <= VALID KEY (KEY ENTRY, TYPE, SIZE AND VALUE)
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE | <= IF VALUE IS A STRING, THEN KEY VALUE POINTS TO A IDX ENTRY IN THE STRING TABLE
+---------+--+------------+
|KEY ENTRY|TS| KEY VALUE |
+---------+--+------------+
|000 ... 0000| KEY VALUE | <= ERASED KEY. WHEN A STRING KEY IS ERASED, THE INDEX IN THE STRING TABLE IS NOT ERASED!
+------------+------------+
|FFF ... FFFF|FFF ... FFFF| <= EMPTY ENTRY
+------------+------------+
|FFF ... FFFF|FFF ... FFFF|
+------------+------------+
|FFF ... FFFF|FFF ... FFFF|
+------------+------------+
String Table:
<- 8 bytes -><- 8 bytes -><- 8 bytes -><- 8 bytes ->
+------------+------------+------------+------------+
| SIGNATURE | IDX 1 | IDX x | SIGNATURE |
+------------+------------+------------+------------+------------+
| STRING DATA 1 | STR DATA 2 |
+------------+------------+------------+------------+------------+
| STRING DATA 3 | EMPTY |
+------------+------------+------------+------------+------------+
IDX Headers are 8 bytes long and contain the offset and length of the string data.
The offset is stored in the first 4 bytes and the length in the last 4 bytes.
The offset is relative to the start of the string data are, and points to the sector that contains the start of the string
The Length is the raw length of the string, including the NULL terminator. (not the number of sectors it occupies)
The Number of IDX entries equals the total number of keys in the main index.
The string data area is made up of 8 byte entries. Strings longer flow into the next sector.
Strings start on a 8 byte boundary. Strings must be NULL terminated
Empty space in the String Table is filled with 0xFF.
When compacting the tables, only referenced strings are copied to the new string table.
*/
typedef uint64_t kved_word_t; /**< Main KVED Header Entry Size */
#define KVED_SIGNATURE_ENTRY(x) ((0xDEADBEEF00000000ULL + (KVED_MAX_STRING_SIZE << 16)) + x->drv_max_entries)
#define KVED_STR_SIGNATURE_ENTRY(x) ((0xBF00BF1B00000000ULL + (KVED_MAX_STRING_SIZE << 16)) + x->drv_max_entries)
#define KVED_STR_SIGNATURE_END(x) ((0xBEEFDEAD00000000ULL + (KVED_MAX_STRING_SIZE << 16)) + x->drv_max_entries)
//#define KVED_SIGNATURE_ENTRY 0xDEADBEEFDEADBEEFULL
#define KVED_DELETED_ENTRY 0x0000000000000000ULL
#define KVED_FREE_ENTRY 0xFFFFFFFFFFFFFFFFULL
#define KVED_HDR_ENTRY_MSK 0xFFFFFFFFFFFFFF00ULL
//#define KVED_STR_SIGNATURE_ENTRY 0xBF00BF1BDEADBEEFULL
//#define KVED_STR_SIGNATURE_END 0xDEADBEEFBF00BF1BULL
#define KVED_STR_DELETED_ENTRY 0x0000000000000000ULL
#define KVED_STR_FREE_ENTRY 0xFFFFFFFFFFFFFFFFULL
#define KVED_STR_HDR_OFFSET_MSK 0xFFFFFFFF00000000ULL
#define KVED_STR_HDR_LEN_MSK 0x00000000FFFFFFFFULL
#define KVED_HDR_SIZE_IN_WORDS 2 /**< kved header size */
#define KVED_ENTRY_SIZE_IN_WORDS 2 /**< kved entry size */
#define KVED_HDR_MASK_KEY(k) ( (k) & KVED_HDR_ENTRY_MSK) /**< label entry mask */
#define KVED_HDR_MASK_TYPE(k) (((k) & 0xF0) >> 4) /**< type entry mask */
#define KVED_HDR_MASK_SIZE(k) (((k) & 0x0F)) /**< size entry mask */
#define KVED_FLASH_UINT_MAX ((kved_word_t)(~0)) /**< last valid unsigned int value for current flash word */
/** Key size for data access, with terminator */
#define KVED_MAX_KEY_SIZE (KVED_FLASH_WORD_SIZE-1)
/** Index return value when a key is not found in the database */
#define KVED_INDEX_NOT_FOUND 0
typedef struct kved_ctrl_s kved_ctrl_t;
/**
@brief Supported data types.
*/
typedef enum kved_data_types_e
{
KVED_DATA_TYPE_UINT8 = 0, /**< 8 bits, unsigned */
KVED_DATA_TYPE_INT8, /**< 8 bits, signed */
KVED_DATA_TYPE_UINT16, /**< 16 bits, unsigned */
KVED_DATA_TYPE_INT16, /**< 16 bits, signed */
KVED_DATA_TYPE_UINT32, /**< 32 bits, unsigned */
KVED_DATA_TYPE_INT32, /**< 32 bits, com sinal */
KVED_DATA_TYPE_FLOAT, /**< Single precision floating point (float) */
KVED_DATA_TYPE_STRING, /**< String up to @ref KVED_MAX_STRING_SIZE bytes, excluding terminator */
KVED_DATA_TYPE_UINT64, /**< 64 bits, signed */
KVED_DATA_TYPE_INT64, /**< 64 bits, unsigned */
KVED_DATA_TYPE_DOUBLE, /**< Double precision floating point (double) */
} kved_data_types_t;
/**
@brief Union with supported data types
*/
typedef union kved_value_u
{
uint8_t u8; /**< unsigned 8 bits value */
int8_t i8; /**< signed 8 bits value */
uint16_t u16; /**< unsigned 16 bits value */
int16_t i16; /**< signed 16 bits value */
uint32_t u32; /**< unsigned 32 bits value */
int32_t i32; /**< signed 32 bits value */
float flt; /**< single precision float */
uint8_t str[KVED_MAX_STRING_SIZE]; /**< string */
uint64_t u64; /**< unsigned 64 bits value */
int64_t i64; /**< signed 64 bits value */
double dbl; /**< double precision float */
} kved_value_t;
/**
@brief Structure for accessing the database containing information about key, type and value
*/
typedef struct kved_data_s
{
kved_value_t value; /**< User value */
uint8_t key[KVED_MAX_KEY_SIZE]; /**< String used as access key */
kved_data_types_t type; /**< Data type used according to @ref kved_data_types_t */
} kved_data_t;
/**
* @brief Flash Table enumeration
* @details This enumeration is used to identify the flash table to be used.
*/
typedef enum kved_flash_sector_e
{
KVED_FLASH_SECTOR_A = 0, /**< Main Table A */
KVED_FLASH_SECTOR_B, /**< Main Table B */
KVED_FLASH_STRING_SECTOR_A, /**< String Table A */
KVED_FLASH_STRING_SECTOR_B, /**< String Table B */
KVED_FLASH_NUM_SECTORS, /**< Number of Tables */
} kved_flash_sector_t;
/**
* @brief Flash driver structure
* @details This structure is used Populated with the functions to read/write to flash/disk/memory
*/
typedef struct {
bool (*sector_erase)(kved_flash_sector_t sec, void *drv_arg); /**< Erase sector */
void (*header_write)(kved_flash_sector_t sec, uint16_t index, kved_word_t data, void *drv_arg); /**< Write Headers (in Main and String Table) */
kved_word_t (*header_read)(kved_flash_sector_t sec, uint16_t index, void *drv_arg); /**< Read header (in Main and String Table)*/
void (*data_write)(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg); /**< Write data in String Table */
void (*data_read)(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg); /**< Read data in String Table */
uint32_t (*sector_size)(kved_flash_sector_t sec, void *drv_arg); /**< Get Table Size */
bool (*init)( void *drv_arg); /**< Init driver */
uint16_t (*max_entries)(void *drv_arg); /**< Max entries in table */
void *drv_arg; /**< Driver argument */
} kved_flash_driver_t;
typedef enum kved_error_e
{
KVED_OK = 0,
KVED_INVALID_HANDLE = -1,
KVED_NOT_INITIALIZED = -2,
KVED_INVALID_KEY = -3,
KVED_INVALID_INDEX = -4,
KVED_TABLE_FULL = -5,
KVED_CORRUPT_TABLE = -6,
KVED_ERROR = -7,
} kved_error_t;
/**
@brief Writes a new value to the database.
@param[in] data - information about the data to be written
@return true: recording successful.
@return false: error during the recording process.
@code
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT32,
.key = "ca1",
.value.u32 = 0x12345678
};
kved_data_t kv2 = {
.type = KVED_DATA_TYPE_STRING,
.key = "ID",
.value.str ="N01"
};
kved_data_write(&kv1);
kved_data_write(&kv2);
@endcode
*/
kved_error_t kved_data_write(kved_ctrl_t *ctrl, kved_data_t *data);
/**
@brief Retrieves a previously saved value from database.
@param[out] data - Structure where the retrieved value will be stored (type and content)
@return true: read successfully.
@return false: error during the reading process.
@code
kved_data_t kv1 = {
.key = "ca1",
};
kved_data_t kv2 = {
.key = "ID",
};
if(kved_data_read(&kv1))
printf("Value: %d\n",kv1.value.u32);
if(kved_data_read(&kv2))
printf("Value: %4s\n",kv2.value.str);
@endcode
*/
kved_error_t kved_data_read(kved_ctrl_t *ctrl, kved_data_t *data);
/**
@brief Deletes a previously saved value in the database, if it exists.
@param[in] data - Structure where the retrieved value will be stored (type and content)
@return true: deletion successful.
@return false: error during the erasuring process.
@code
kved_data_t kv1 = {
.key = "ca1",
};
if(kved_data_delete(&kv1))
printf("Entry erased");
@endcode
*/
kved_error_t kved_data_delete(kved_ctrl_t *ctrl, kved_data_t *data);
/**
@brief Retrieves a previously saved value from database but using an index.
This function is used in conjunction with @ref kved_first_used_index_get and @ref kved_next_used_index_get
to iterate over the database.
@param[in] index - index of the value to retrieve
@param[out] data - structure where the retrieved value will be stored (type and content)
@return true: read successfully.
@return false: error during reading process.
@code
kved_data_t kv1;
uint16_t index = kved_first_used_index_get();
printf("Database keys:\n");
while(index != KVED_INDEX_NOT_FOUND)
{
kved_data_read_by_index(index,&data);
printf("- Key: %s\n",kv1.key);
index = kved_next_used_index_get(index);
}
@endcode
*/
kved_error_t kved_data_read_by_index(kved_ctrl_t *ctrl, uint16_t index, kved_data_t *data);
/**
@brief Get the first valid index in the database.
@return returns @ref KVED_INDEX_NOT_FOUND (index not found, database empty) or an index value greater than zero
*/
int16_t kved_first_used_index_get(kved_ctrl_t *ctrl);
/**
@brief Given the last index, get the next valid index from the database.
@param[in] last_index - last valid index used
@return return @ref KVED_INDEX_NOT_FOUND when the end of the database is reached or a valid index value (greater than zero)
*/
int16_t kved_next_used_index_get(kved_ctrl_t *ctrl, uint16_t last_index);
/**
@brief Returns the number of database entries (used or not)
@return Number of entries
*/
int16_t kved_total_entries_get(kved_ctrl_t *ctrl);
/**
@brief Returns the number of used database entries
@return Number of entries
*/
int16_t kved_used_entries_get(kved_ctrl_t *ctrl);
/**
@brief Returns the number available entries in the database
@return Number of entries
*/
int16_t kved_free_entries_get(kved_ctrl_t *ctrl);
/**
* @brief Returns the number of deleted database entries
* @return Number of entries
*/
int16_t kved_deleted_entries_get(kved_ctrl_t *ctrl);
/**
* @breif compact the database
* @return KVED_OK if success
*/
kved_error_t kved_compact_database(kved_ctrl_t *ctrl);
/**
@brief Print all values stored in the database
*/
kved_error_t kved_dump(kved_ctrl_t *ctrl);
/**
@brief Format the database, deleting all values
*/
kved_error_t kved_format(kved_ctrl_t *ctrl);
/**
@brief Given a key entry from database, decode it to data type and user key
@param[out] data - structure where data type and user key will be stored
@param[in] key - key entry
*/
kved_error_t kved_key_decode(kved_ctrl_t *ctrl, kved_data_t *data, kved_word_t key);
/**
@brief Given a data type and user key, encode it as a key entry to be written in the database
@param[in] data - user entry
@return Encoded key entry
*/
kved_word_t kved_key_encode(kved_ctrl_t *ctrl, kved_data_t *data);
/**
@brief Initialize the database. Must be called before any use.
*/
kved_ctrl_t *kved_init(kved_flash_driver_t *driver);
/**
@brief Initialize the database. Must be called before any use.
*/
void kved_deinit(kved_ctrl_t *ctrl);
/**
@}
*/
#endif

View file

@ -0,0 +1,197 @@
/*
kved (key/value embedded database), a simple key/value database
implementation for microcontrollers.
Copyright (c) 2022 Marcelo Barros de Almeida <marcelobarrosalmeida@gmail.com>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "oblfr_kved_file.h"
#if 0
#define DBG_TAG "MAIN"
#include "log.h"
#else
#define LOG_T(...)
#define LOG_I(...) printf(__VA_ARGS__);
#define LOG_E(...) printf(__VA_ARGS__);
#endif
#if 0
#undef LOG_T
#define LOG_T(...) printf(__VA_ARGS__);
#endif
#define FLASH_NUM_ENTRIES (48)
/* flash Sector uses NUM_ENTRIES as part of headers */
#define FLASH_SECTOR_SIZE (FLASH_NUM_ENTRIES * KVED_FLASH_WORD_SIZE)
/* String Sector has seperate indexes */
/* no of entries * max string size + no of idx's * 2 bytes for index + Magic Headers */
#define FLASH_STR_SECTOR_SIZE ((FLASH_NUM_ENTRIES * KVED_MAX_STRING_SIZE) + (FLASH_NUM_ENTRIES * KVED_FLASH_WORD_SIZE) + (FLASH_NUM_ENTRIES * 2))
typedef struct file_driver_s {
char filename[13];
FILE *file;
} file_driver_t;
static uint16_t get_sector_addr(kved_flash_sector_t sec) {
switch (sec) {
case KVED_FLASH_SECTOR_A:
return 0;
case KVED_FLASH_SECTOR_B:
return FLASH_SECTOR_SIZE;
case KVED_FLASH_STRING_SECTOR_A:
return FLASH_SECTOR_SIZE * 2;
case KVED_FLASH_STRING_SECTOR_B:
return FLASH_SECTOR_SIZE * 2 + FLASH_STR_SECTOR_SIZE;
default:
return 0;
}
}
static uint16_t get_index_address(uint16_t index)
{
return sizeof(kved_word_t) * index;
}
bool oblfr_kved_file_sector_erase(kved_flash_sector_t sec, void *drv_arg)
{
file_driver_t *file_driver = (file_driver_t *)drv_arg;
if (file_driver == NULL || file_driver->file == NULL) {
LOG_E("File not open\r\n");
return false;
}
uint8_t data = 0xff;
switch (sec) {
case KVED_FLASH_SECTOR_A:
case KVED_FLASH_SECTOR_B:
LOG_T("Erase Index Sector %d size: %d\r\n", get_sector_addr(sec), FLASH_SECTOR_SIZE);
fseek(file_driver->file, get_sector_addr(sec), SEEK_SET);
for (int i = 0; i < FLASH_SECTOR_SIZE; i++)
fwrite(&data, 1, 1, file_driver->file);
break;
case KVED_FLASH_STRING_SECTOR_A:
case KVED_FLASH_STRING_SECTOR_B:
LOG_T("Erase String Sector %d size: %ld\r\n", get_sector_addr(sec), FLASH_STR_SECTOR_SIZE);
fseek(file_driver->file, get_sector_addr(sec), SEEK_SET);
for (int i = 0; i < FLASH_STR_SECTOR_SIZE; i++)
fwrite(&data, 1, 1, file_driver->file);
break;
}
return true;
}
void oblfr_kved_file_header_write(kved_flash_sector_t sec, uint16_t index, kved_word_t data, void *drv_arg)
{
file_driver_t *file_driver = (file_driver_t *)drv_arg;
if (file_driver == NULL || file_driver->file == NULL) {
LOG_E("File not open\r\n");
return;
}
uint16_t addr = get_sector_addr(sec) + get_index_address(index);
fseek(file_driver->file, addr, SEEK_SET);
fwrite(&data, sizeof(kved_word_t), 1, file_driver->file);
}
kved_word_t oblfr_kved_file_header_read(kved_flash_sector_t sec, uint16_t index, void *drv_arg)
{
file_driver_t *file_driver = (file_driver_t *)drv_arg;
if (file_driver == NULL || file_driver->file == NULL) {
LOG_E("File not open\r\n");
return 0;
}
uint16_t addr = get_sector_addr(sec) + get_index_address(index);
static kved_word_t data;
fseek(file_driver->file, addr, SEEK_SET);
fread(&data, sizeof(kved_word_t), 1, file_driver->file);
return data;
}
void oblfr_kved_file_data_write(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
file_driver_t *file_driver = (file_driver_t *)drv_arg;
if (file_driver == NULL || file_driver->file == NULL) {
LOG_E("File not open\r\n");
return;
}
uint16_t addr = get_sector_addr(sec) + get_index_address(index);
fseek(file_driver->file, addr, SEEK_SET);
fwrite(data, 1, len, file_driver->file);
}
void oblfr_kved_file_data_read(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
file_driver_t *file_driver = (file_driver_t *)drv_arg;
if (file_driver->file == NULL) {
LOG_E("File not open\r\n");
return;
}
uint16_t addr = get_sector_addr(sec) + get_index_address(index);
fseek(file_driver->file, addr, SEEK_SET);
fread(data, 1, len, file_driver->file);
}
uint32_t oblfr_kved_file_sector_size(kved_flash_sector_t sec, void *drv_arg)
{
// sector sizes must be equal
if (sec == KVED_FLASH_STRING_SECTOR_A || sec == KVED_FLASH_STRING_SECTOR_B)
return FLASH_STR_SECTOR_SIZE;
else if (sec == KVED_FLASH_SECTOR_A || sec == KVED_FLASH_SECTOR_B)
return FLASH_SECTOR_SIZE;
}
void oblfr_kved_file_init(void *drv_arg)
{
LOG_T("oblfr_kved_file_init()\r\n");
}
static kved_flash_driver_t oblfr_kved_file_driver = {
.init = oblfr_kved_file_init,
.sector_erase = oblfr_kved_file_sector_erase,
.header_write = oblfr_kved_file_header_write,
.header_read = oblfr_kved_file_header_read,
.data_read = oblfr_kved_file_data_read,
.data_write = oblfr_kved_file_data_write,
.sector_size = oblfr_kved_file_sector_size,
};
kved_flash_driver_t *oblfr_kved_file_configure() {
struct stat st = {0};
file_driver_t *file_driver = malloc(sizeof(file_driver_t));
snprintf(file_driver->filename, sizeof(file_driver->filename), "kved.bin");
if (stat(file_driver->filename, &st) == -1) {
/* New File */
LOG_T("Creating new file %s\r\n", file_driver->filename);
file_driver->file = fopen(file_driver->filename, "wb+");
} else {
/* existing file */
LOG_T("Opened file %s\r\n", file_driver->filename);
file_driver->file = fopen(file_driver->filename, "rb+");
}
if (file_driver->file == NULL) {
LOG_E("Failed to open file %s\r\n", file_driver->filename);
return NULL;
}
oblfr_kved_file_driver.drv_arg = file_driver;
return &oblfr_kved_file_driver;
}
void oblfr_kved_file_close(kved_flash_driver_t *driver) {
file_driver_t *file_driver = (file_driver_t *)driver->drv_arg;
if (file_driver->file != NULL) {
fclose(file_driver->file);
file_driver->file = NULL;
}
free(file_driver);
driver->drv_arg = NULL;
}

View file

@ -0,0 +1,170 @@
/*
kved (key/value embedded database), a simple key/value database
implementation for microcontrollers.
Copyright (c) 2022 Marcelo Barros de Almeida <marcelobarrosalmeida@gmail.com>
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <bflb_flash.h>
#include "oblfr_kved_flash.h"
#define DBG_TAG "KVED_FLASH"
#include "log.h"
uint32_t oblfr_kved_flash_sector_size(kved_flash_sector_t sec, void *drv_arg);
static uint32_t get_sector_addr(kved_flash_sector_t sec, uint16_t index, void *drv_arg) {
uint32_t addr = ((oblfr_kved_flash_driver_t *)drv_arg)->flash_addr;
uint32_t flash_sector_size = ((oblfr_kved_flash_driver_t *)drv_arg)->flash_sector_size;
uint32_t start_addr = ((addr) & (~(flash_sector_size - 1)));
uint8_t sectors_required = 0;
/* all fall through in switch statement */
switch (sec) {
case KVED_FLASH_STRING_SECTOR_B:
/* calc the size of FLASH_STR_SECTOR_A to figure out the start of string sector b*/
sectors_required += (oblfr_kved_flash_sector_size(KVED_FLASH_STRING_SECTOR_A, drv_arg) / flash_sector_size) + 1;
case KVED_FLASH_STRING_SECTOR_A:
/* calc the size of FLASH_SECTOR B to figure out the start of String Sector A*/
sectors_required += (oblfr_kved_flash_sector_size(KVED_FLASH_SECTOR_B, drv_arg) / flash_sector_size) + 1;
case KVED_FLASH_SECTOR_B:
/* calc the size of FLASH_SECTOR A to figure out the start of Sector B*/
sectors_required += (oblfr_kved_flash_sector_size(KVED_FLASH_SECTOR_A, drv_arg) / flash_sector_size) + 1;
case KVED_FLASH_SECTOR_A:
/* flash sector A starts at start_addr */
break;
case KVED_FLASH_NUM_SECTORS:
break;
}
//LOG_D("Sectors required for %d is %d\r\n", sec, sectors_required);
//LOG_D("Final Start Address %x\r\n", start_addr + (sectors_required * flash_sector_size));
assert(start_addr + (sectors_required * flash_sector_size) + (sizeof(kved_word_t) * index) != 0);
return start_addr + (sectors_required * flash_sector_size) + (sizeof(kved_word_t) * index);
}
bool oblfr_kved_flash_sector_erase(kved_flash_sector_t sec, void *drv_arg)
{
uint32_t addr = get_sector_addr(sec, 0, drv_arg);
if (bflb_flash_erase(addr, oblfr_kved_flash_sector_size(sec, drv_arg)) != 0) {
LOG_E("Erase Sector %d Failed\r\n", sec);
return false;
}
return true;
}
void oblfr_kved_flash_header_write(kved_flash_sector_t sec, uint16_t index, kved_word_t data, void *drv_arg)
{
uint32_t addr = get_sector_addr(sec, index, drv_arg);
if (bflb_flash_write(addr, (uint8_t*)&data, sizeof(kved_word_t)) != 0) {
LOG_E("Write Sector %d Failed\r\n", sec);
return;
}
}
kved_word_t oblfr_kved_flash_header_read(kved_flash_sector_t sec, uint16_t index, void *drv_arg)
{
uint32_t addr = get_sector_addr(sec, index, drv_arg);
kved_word_t data;
if (bflb_flash_read(addr, (uint8_t*)&data, sizeof(kved_word_t)) != 0) {
LOG_E("Read Sector %d Failed\r\n", sec);
return 0;
}
return data;
}
void oblfr_kved_flash_data_write(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
uint32_t addr = get_sector_addr(sec, index, drv_arg);
if (bflb_flash_write(addr, (uint8_t*)data, len) != 0) {
LOG_E("Write Sector %d Failed\r\n", sec);
return;
}
}
void oblfr_kved_flash_data_read(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
uint32_t addr = get_sector_addr(sec, index, drv_arg);
if (bflb_flash_read(addr, data, len) != 0) {
LOG_E("Read Sector %d Failed\r\n", sec);
return;
}
return;
}
uint32_t oblfr_kved_flash_sector_size(kved_flash_sector_t sec, void *drv_arg)
{
oblfr_kved_flash_driver_t *flash_drv = (oblfr_kved_flash_driver_t *)drv_arg;
// sector sizes must be equal
if (sec == KVED_FLASH_STRING_SECTOR_A || sec == KVED_FLASH_STRING_SECTOR_B) {
/* No of strings times string size + indexes and 2 magic bytes */
return ( flash_drv->max_entries * KVED_MAX_STRING_SIZE) + ((flash_drv->max_entries + 2) * sizeof(kved_word_t));
} else if (sec == KVED_FLASH_SECTOR_A || sec == KVED_FLASH_SECTOR_B)
return (flash_drv->flash_sector_size);
return 0;
}
bool oblfr_kved_flash_init(void *drv_arg)
{
oblfr_kved_flash_driver_t *flash_drv = (oblfr_kved_flash_driver_t *)drv_arg;
spi_flash_cfg_type flashCfg;
uint8_t *pFlashCfg = NULL;
uint32_t flashCfgLen = 0;
bflb_flash_get_cfg(&pFlashCfg, &flashCfgLen);
arch_memcpy((void *)&flashCfg, pFlashCfg, flashCfgLen);
flash_drv->flash_sector_size = (flashCfg.sector_size * 1024);
if (flash_drv->max_entries == 0)
flash_drv->max_entries = (flash_drv->flash_sector_size) / (sizeof(kved_word_t) * 2);
else {
if (flash_drv->max_entries > (flash_drv->flash_sector_size) / (sizeof(kved_word_t) * 2)) {
LOG_E("Max Entries %d is greater than sector size %d\r\n", flash_drv->max_entries, flash_drv->flash_sector_size);
return false;
}
}
LOG_I("Initializing KVED Flash Driver\r\n");
LOG_I("Flash Sector Size: %d\r\n", flash_drv->flash_sector_size);
LOG_I("Number of entries: %d - Max String Size %d\r\n", flash_drv->max_entries, KVED_MAX_STRING_SIZE);
LOG_I("Index Size: %dKB\r\n", oblfr_kved_flash_sector_size(KVED_FLASH_SECTOR_A, drv_arg)/1024);
LOG_I("String Index Size: %dKB\r\n", oblfr_kved_flash_sector_size(KVED_FLASH_STRING_SECTOR_A, drv_arg)/1024);
LOG_I("Total KVED Partition Size: %dKB\r\n", ((oblfr_kved_flash_sector_size(KVED_FLASH_SECTOR_A, drv_arg) * 2) + (oblfr_kved_flash_sector_size(KVED_FLASH_STRING_SECTOR_A, drv_arg) * 2))/1024);
LOG_I("Start 0x%x - End 0x%x\r\n", flash_drv->flash_addr, flash_drv->flash_addr + ((oblfr_kved_flash_sector_size(KVED_FLASH_SECTOR_A, drv_arg) * 2) + (oblfr_kved_flash_sector_size(KVED_FLASH_STRING_SECTOR_A, drv_arg) * 2)));
return true;
}
uint16_t oblfr_kved_flash_max_entries(void *drv_arg)
{
oblfr_kved_flash_driver_t *flash_drv = (oblfr_kved_flash_driver_t *)drv_arg;
return flash_drv->max_entries;
}
static kved_flash_driver_t oblfr_kved_flash_driver = {
.init = oblfr_kved_flash_init,
.sector_erase = oblfr_kved_flash_sector_erase,
.header_write = oblfr_kved_flash_header_write,
.header_read = oblfr_kved_flash_header_read,
.data_read = oblfr_kved_flash_data_read,
.data_write = oblfr_kved_flash_data_write,
.sector_size = oblfr_kved_flash_sector_size,
.max_entries = oblfr_kved_flash_max_entries,
};
kved_flash_driver_t *oblfr_kved_flash_configure(oblfr_kved_flash_driver_t *cfg) {
oblfr_kved_flash_driver.drv_arg = cfg;
return &oblfr_kved_flash_driver;
}
void oblfr_kved_flash_close(kved_flash_driver_t *driver) {
}

View file

@ -0,0 +1,155 @@
/*
kved (key/value embedded database), a simple key/value database
implementation for microcontrollers.
Copyright (c) 2022 Marcelo Barros de Almeida <marcelobarrosalmeida@gmail.com>
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "oblfr_common.h"
#include "oblfr_kved_memory.h"
#if 1
#define DBG_TAG "KVED"
#include "log.h"
#else
#define LOG_T(...)
#define LOG_I(...) printf(__VA_ARGS__);
#define LOG_E(...) printf(__VA_ARGS__);
#endif
#if 0
#undef LOG_T
#define LOG_T(...) printf(__VA_ARGS__);
#endif
#define FLASH_NUM_ENTRIES (48)
/* flash Sector uses NUM_ENTRIES as part of headers */
#define FLASH_SECTOR_SIZE (FLASH_NUM_ENTRIES * KVED_FLASH_WORD_SIZE)
/* String Sector has seperate indexes */
/* no of entries * max string size + no of idx's * 2 bytes for index + Magic Headers */
#define FLASH_STR_SECTOR_SIZE ((FLASH_NUM_ENTRIES * KVED_MAX_STRING_SIZE) + (FLASH_NUM_ENTRIES * KVED_FLASH_WORD_SIZE) + (FLASH_NUM_ENTRIES * 2))
uint32_t *sector_address[KVED_FLASH_NUM_SECTORS];
uint32_t *data_bank0;
uint32_t *data_bank1;
uint32_t *str_bank0;
uint32_t *str_bank1;
static uint16_t get_index_address(uint16_t index)
{
return index * 2;
}
bool oblfr_kved_memory_sector_erase(kved_flash_sector_t sec, void *drv_arg)
{
if (sec == KVED_FLASH_SECTOR_A || sec == KVED_FLASH_SECTOR_B) {
memset(sector_address[sec], 0xFF, FLASH_SECTOR_SIZE);
} else if (sec == KVED_FLASH_STRING_SECTOR_A || sec == KVED_FLASH_STRING_SECTOR_B) {
memset(sector_address[sec], 0xFF, FLASH_STR_SECTOR_SIZE);
} else {
return false;
}
return true;
}
void oblfr_kved_memory_header_write(kved_flash_sector_t sec, uint16_t index, kved_word_t data, void *drv_arg)
{
sector_address[sec][get_index_address(index)] = ((uint32_t)(data >> 32));
sector_address[sec][get_index_address(index) + 1] = (data);
}
kved_word_t oblfr_kved_memory_header_read(kved_flash_sector_t sec, uint16_t index, void *drv_arg)
{
kved_word_t data = ((kved_word_t)sector_address[sec][get_index_address(index)] << 32) | sector_address[sec][get_index_address(index) + 1];
return data;
}
void oblfr_kved_memory_data_write(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
for (int i = 0; i < len; i++)
{
sector_address[sec][get_index_address(index) + i] = ((uint32_t *)data)[i];
}
}
void oblfr_kved_memory_data_read(kved_flash_sector_t sec, uint16_t index, void *data, uint16_t len, void *drv_arg)
{
for (int i = 0; i < len; i++)
{
((uint32_t *)data)[i] = sector_address[sec][get_index_address(index) + i];
}
}
uint32_t oblfr_kved_memory_sector_size(kved_flash_sector_t sec, void *drv_arg)
{
// sector sizes must be equal
if (sec == KVED_FLASH_STRING_SECTOR_A || sec == KVED_FLASH_STRING_SECTOR_B)
return FLASH_STR_SECTOR_SIZE;
else if (sec == KVED_FLASH_SECTOR_A || sec == KVED_FLASH_SECTOR_B)
return FLASH_SECTOR_SIZE;
else
return 0;
}
bool oblfr_kved_memory_init(void *drv_arg)
{
return true;
}
uint16_t oblfr_kved_memory_max_entries(void *drv_arg)
{
return FLASH_NUM_ENTRIES;
}
static kved_flash_driver_t oblfr_kved_memory_driver = {
.init = oblfr_kved_memory_init,
.sector_erase = oblfr_kved_memory_sector_erase,
.header_write = oblfr_kved_memory_header_write,
.header_read = oblfr_kved_memory_header_read,
.data_read = oblfr_kved_memory_data_read,
.data_write = oblfr_kved_memory_data_write,
.sector_size = oblfr_kved_memory_sector_size,
.max_entries = oblfr_kved_memory_max_entries,
};
kved_flash_driver_t *oblfr_kved_memory_configure() {
data_bank0 = malloc(FLASH_SECTOR_SIZE);
data_bank1 = malloc(FLASH_SECTOR_SIZE);
str_bank0 = malloc(FLASH_STR_SECTOR_SIZE);
str_bank1 = malloc(FLASH_STR_SECTOR_SIZE);
LOG_I("Allocated %d memory for Main Table Size - Max Entries: %d\r\n", FLASH_SECTOR_SIZE * 2, (FLASH_NUM_ENTRIES/2)-1);
LOG_I("Allocated %ld memory for String Table Size - Max String Size: %ld\r\n", FLASH_STR_SECTOR_SIZE * 2, KVED_MAX_STRING_SIZE);
sector_address[KVED_FLASH_SECTOR_A] = data_bank0;
sector_address[KVED_FLASH_SECTOR_B] = data_bank1;
sector_address[KVED_FLASH_STRING_SECTOR_A] = str_bank0;
sector_address[KVED_FLASH_STRING_SECTOR_B] = str_bank1;
oblfr_kved_memory_sector_erase(KVED_FLASH_SECTOR_A, sector_address);
oblfr_kved_memory_sector_erase(KVED_FLASH_SECTOR_B, sector_address);
oblfr_kved_memory_sector_erase(KVED_FLASH_STRING_SECTOR_A, sector_address);
oblfr_kved_memory_sector_erase(KVED_FLASH_STRING_SECTOR_B, sector_address);
oblfr_kved_memory_driver.drv_arg = sector_address;
return &oblfr_kved_memory_driver;
}
void oblfr_kved_memory_close(kved_flash_driver_t *driver) {
free(sector_address[KVED_FLASH_SECTOR_A]);
free(sector_address[KVED_FLASH_SECTOR_B]);
free(sector_address[KVED_FLASH_STRING_SECTOR_A]);
free(sector_address[KVED_FLASH_STRING_SECTOR_B]);
}

View file

@ -0,0 +1,611 @@
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include "oblfr_common.h"
#include "kved.h"
#include "oblfr_nvkvs.h"
#include "oblfr_kved_flash.h"
#include "oblfr_kved_memory.h"
#define DBG_TAG "NVKVS"
#include "log.h"
typedef struct oblfr_nvkvs_handle_s
{
kved_flash_driver_t *storage_driver;
oblfr_nvkvs_storage_t storage;
kved_ctrl_t *kved_ctrl;
} oblfr_nvkvs_handle_t;
oblfr_nvkvs_handle_t *oblfr_nvkvs_init(const oblfr_nvkvs_cfg_t *cfg)
{
if (cfg == NULL)
{
LOG_E("Invalid configuration");
return NULL;
}
oblfr_nvkvs_handle_t *handle = malloc(sizeof(oblfr_nvkvs_handle_t));
if (handle == NULL)
{
LOG_E("Failed to allocate memory for handle");
return NULL;
}
switch (cfg->storage)
{
#ifdef CONFIG_COMPONENT_NVKVS_FLASH_BACKEND
case OBLFR_NVKVS_STORAGE_FLASH:
handle->storage_driver = oblfr_kved_flash_configure(cfg->drv_cfg.flash);
break;
#endif
#ifdef CONFIG_COMPONENT_NVKVS_MEM_BACKEND
case OBLFR_NVKVS_STORAGE_RAM:
handle->storage_driver = oblfr_kved_memory_configure();
break;
#endif
default:
LOG_E("Invalid storage type");
return NULL;
}
handle->storage = cfg->storage;
handle->kved_ctrl = kved_init(handle->storage_driver);
if (handle->kved_ctrl == NULL)
{
LOG_E("Failed to initialize KVED");
oblfr_nvkvs_deinit(handle);
return NULL;
}
return handle;
}
oblfr_err_t oblfr_nvkvs_deinit(oblfr_nvkvs_handle_t *handle)
{
if (handle == NULL)
{
return OBLFR_ERR_INVALID;
}
kved_deinit(handle->kved_ctrl);
switch (handle->storage)
{
case OBLFR_NVKVS_STORAGE_FLASH:
oblfr_kved_flash_close(handle->storage_driver);
break;
case OBLFR_NVKVS_STORAGE_RAM:
oblfr_kved_memory_close(handle->storage_driver);
break;
default:
return OBLFR_ERR_INVALID;
}
free(handle);
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_dump(oblfr_nvkvs_handle_t *handle)
{
if (handle == NULL)
{
return OBLFR_ERR_INVALID;
}
kved_dump(handle->kved_ctrl);
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_compact(oblfr_nvkvs_handle_t *handle) {
if (handle == NULL)
{
return OBLFR_ERR_INVALID;
}
if (kved_compact_database(handle->kved_ctrl) != KVED_OK) {
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_u8(oblfr_nvkvs_handle_t *handle, const char *key, uint8_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT8,
.value.u8 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_u8(oblfr_nvkvs_handle_t *handle, const char *key, uint8_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT8,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.u8;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_i8(oblfr_nvkvs_handle_t *handle, const char *key, int8_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT8,
.value.i8 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_i8(oblfr_nvkvs_handle_t *handle, const char *key, int8_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT8,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.i8;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_u16(oblfr_nvkvs_handle_t *handle, const char *key, uint16_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT16,
.value.u16 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_u16(oblfr_nvkvs_handle_t *handle, const char *key, uint16_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT16,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.u16;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_i16(oblfr_nvkvs_handle_t *handle, const char *key, int16_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT16,
.value.i16 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_i16(oblfr_nvkvs_handle_t *handle, const char *key, int16_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT16,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.i16;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_u32(oblfr_nvkvs_handle_t *handle, const char *key, uint32_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT32,
.value.u32 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_u32(oblfr_nvkvs_handle_t *handle, const char *key, uint32_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT32,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.u32;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_i32(oblfr_nvkvs_handle_t *handle, const char *key, int32_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT32,
.value.i32 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_i32(oblfr_nvkvs_handle_t *handle, const char *key, int32_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT32,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.i32;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_u64(oblfr_nvkvs_handle_t *handle, const char *key, uint64_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT64,
.value.u64 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_u64(oblfr_nvkvs_handle_t *handle, const char *key, uint64_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_UINT64,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.u64;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_i64(oblfr_nvkvs_handle_t *handle, const char *key, int64_t value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT64,
.value.i64 = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_i64(oblfr_nvkvs_handle_t *handle, const char *key, int64_t *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_INT64,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.i64;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_float(oblfr_nvkvs_handle_t *handle, const char *key, float value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_FLOAT,
.value.flt = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_float(oblfr_nvkvs_handle_t *handle, const char *key, float *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_FLOAT,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.flt;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_double(oblfr_nvkvs_handle_t *handle, const char *key, double value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_DOUBLE,
.value.dbl = value};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_double(oblfr_nvkvs_handle_t *handle, const char *key, double *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_DOUBLE,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
*value = kv1.value.dbl;
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_set_string(oblfr_nvkvs_handle_t *handle, const char *key, const char *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
if (strlen(value) > CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_STRING,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
strncpy((char *)kv1.value.str, value, CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE);
kved_error_t err = kved_data_write(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_write failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_get_string(oblfr_nvkvs_handle_t *handle, const char *key, char *value)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
LOG_E("key is too long");
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
.type = KVED_DATA_TYPE_STRING,
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_read(handle->kved_ctrl, &kv1);
if (err != KVED_OK)
{
LOG_E("kved_data_read failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
strncpy(value, (char *)kv1.value.str, CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE);
return OBLFR_OK;
}
oblfr_err_t oblfr_nvkvs_delete(oblfr_nvkvs_handle_t *handle, const char *key)
{
if (strlen(key) > KVED_MAX_KEY_SIZE)
{
return OBLFR_ERR_INVALID;
}
kved_data_t kv1 = {
};
strncpy((char *)kv1.key, key, KVED_MAX_KEY_SIZE);
kved_error_t err = kved_data_delete(handle->kved_ctrl, &kv1);
if (err != KVED_OK) {
LOG_E("kved_data_delete failed %d\r\n", err);
return OBLFR_ERR_ERROR;
}
return OBLFR_OK;
}
uint16_t oblfr_nvkvs_get_size(oblfr_nvkvs_handle_t *handle) {
return kved_total_entries_get(handle->kved_ctrl);
}
uint16_t oblfr_nvkvs_used_entries(oblfr_nvkvs_handle_t *handle) {
return kved_used_entries_get(handle->kved_ctrl);
}
uint16_t oblfr_nvkvs_free_entries(oblfr_nvkvs_handle_t *handle) {
return kved_free_entries_get(handle->kved_ctrl);
}
uint16_t oblfr_nvkvs_deleted_entries(oblfr_nvkvs_handle_t *handle) {
return kved_deleted_entries_get(handle->kved_ctrl);
}
int16_t oblfr_nvkvs_iter_init(oblfr_nvkvs_handle_t *handle) {
return kved_first_used_index_get(handle->kved_ctrl);
}
int16_t oblfr_nvkvs_iter_next(oblfr_nvkvs_handle_t *handle, int16_t iter) {
return kved_next_used_index_get(handle->kved_ctrl, iter);
}
oblfr_err_t oblfr_nvkvs_get_item(oblfr_nvkvs_handle_t *handle, uint16_t index, oblfr_nvkvs_data_t *data) {
kved_data_t kv1;
if (kved_data_read_by_index(handle->kved_ctrl, index, &kv1) != KVED_OK) {
LOG_W("kved_data_read_by_index failed\r\n");
return OBLFR_ERR_ERROR;
}
data->type = kv1.type;
strncpy(data->key, (char *)kv1.key, KVED_MAX_KEY_SIZE);
switch (kv1.type) {
case KVED_DATA_TYPE_INT8:
data->value.i8 = kv1.value.i8;
break;
case KVED_DATA_TYPE_UINT8:
data->value.u8 = kv1.value.u8;
break;
case KVED_DATA_TYPE_INT16:
data->value.i16 = kv1.value.i16;
break;
case KVED_DATA_TYPE_UINT16:
data->value.u16 = kv1.value.u16;
break;
case KVED_DATA_TYPE_INT32:
data->value.i32 = kv1.value.i32;
break;
case KVED_DATA_TYPE_UINT32:
data->value.u32 = kv1.value.u32;
break;
case KVED_DATA_TYPE_INT64:
data->value.i64 = kv1.value.i64;
break;
case KVED_DATA_TYPE_UINT64:
data->value.u64 = kv1.value.u64;
break;
case KVED_DATA_TYPE_FLOAT:
data->value.flt = kv1.value.flt;
break;
case KVED_DATA_TYPE_DOUBLE:
data->value.dbl = kv1.value.dbl;
break;
case KVED_DATA_TYPE_STRING:
LOG_I("KVED_DATA_TYPE_STRING %s\r\n", kv1.value.str);
strncpy((char *)data->value.str, (char *)kv1.value.str, CONFIG_COMPONENT_NVKVS_MAX_STRING_SIZE);
break;
}
return OBLFR_OK;
}

View file

@ -0,0 +1,4 @@
sdk_generate_library(oblfr_system)
sdk_add_include_directories(include)
sdk_library_add_sources(${CMAKE_CURRENT_SOURCE_DIR}/src/oblfr_system.c)

49
components/oblfr/Kconfig Normal file
View file

@ -0,0 +1,49 @@
config COMPONENT_SYSTEM
bool "System Component"
default n
select FREERTOS
help
"System Component - Initializes FreeRTOS and any selected optional components"
menu "System Configuration"
visible if COMPONENT_SYSTEM
config COMPONENT_SYSTEM_MAIN_TASK_STACK_SIZE
int "Main Task Stack Size"
default 4096
help
"Stack Size For the app_main task"
config COMPONENT_SYSTEM_MAIN_TASK_PRIORITY
int "Main Task Priority"
default 5
help
"Main Task Priority"
config COMPONENT_SYSTEM_DEFAULT_STACK_SIZE
int "Default Stack Size"
default 1024
help
"Default Stack Size"
config COMPONENT_SYSTEM_RUNTIME_STATS
bool "FreeRTOS RunTime Stats"
default y
help
"FreeRTOS RunTime Stats"
config COMPONENT_SYSTEM_TIMER_STACK_SIZE
int "FreeRTOS Timer Task Stack Size"
default 1024
help
"FreeRTOS Timer Task Stack Size. This is not the same as the oblfr_timer component"
choice
prompt "Check Stack Overflow"
default COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_NONE
config COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_NONE
bool "None"
config COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_SIZE
bool "Check if the Stack Space has overflowed at Context Switches"
config COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_CANARY_GUARD
bool "Place 16 Canary Guard Bytes at top of stack and check at Context Switch"
endchoice
config COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW
int
default 0 if COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_NONE
default 1 if COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_SIZE
default 2 if COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW_CANARY_GUARD
endmenu

View file

@ -0,0 +1,135 @@
/*
* FreeRTOS Kernel V10.2.1
* Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, 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.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "sdkconfig.h"
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html.
*----------------------------------------------------------*/
#if defined(BL602) || defined(BL702) || defined(BL702L)
#define configMTIME_BASE_ADDRESS (0x02000000UL + 0xBFF8UL)
#define configMTIMECMP_BASE_ADDRESS (0x02000000UL + 0x4000UL)
#else
#if __riscv_xlen == 64
#define configMTIME_BASE_ADDRESS (0)
#define configMTIMECMP_BASE_ADDRESS ((0xE4000000UL) + 0x4000UL)
#else
#define configMTIME_BASE_ADDRESS ((0xE0000000UL) + 0xBFF8UL)
#define configMTIMECMP_BASE_ADDRESS ((0xE0000000UL) + 0x4000UL)
#endif
#endif
#define configSUPPORT_STATIC_ALLOCATION 1
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ((uint32_t)(1 * 1000 * 1000))
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES (7)
#define configMINIMAL_STACK_SIZE ((unsigned short)CONFIG_COMPONENT_SYSTEM_DEFAULT_STACK_SIZE) /* Only needs to be this high as some demo tasks also use this constant. In production only the idle task would use this. */
#define configTOTAL_HEAP_SIZE ((size_t)0) /* we use the OS memory, not FreeRTOS Heap */
#define configMAX_TASK_NAME_LEN (16)
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 0
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW CONFIG_COMPONENT_SYSTEM_CHECK_STACK_OVERFLOW
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 1
#define configUSE_COUNTING_SEMAPHORES 1
#ifdef CONFIG_COMPONENT_SYSTEM_RUNTIME_STATS
#define configGENERATE_RUN_TIME_STATS 1
#else
#define configGENERATE_RUN_TIME_STATS 0
#endif
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#define configUSE_TICKLESS_IDLE 0
#define configUSE_POSIX_ERRNO 1
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES (2)
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH 4
#define configTIMER_TASK_STACK_DEPTH (CONFIG_COMPONENT_SYSTEM_TIMER_STACK_SIZE)
/* Task priorities. Allow these to be overridden. */
#ifndef uartPRIMARY_PRIORITY
#define uartPRIMARY_PRIORITY (configMAX_PRIORITIES - 3)
#endif
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskAbortDelay 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_xSemaphoreGetMutexHolder 1
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
void vApplicationMallocFailedHook(void);
void vAssertCalled(void);
#include <stdio.h>
#define configASSERT(x) \
if ((x) == 0) { \
printf("file [%s]\r\n", __FILE__); \
printf("func [%s]\r\n", __FUNCTION__); \
printf("line [%d]\r\n", __LINE__); \
printf("%s\r\n", (const char *)(#x)); \
vAssertCalled(); \
}
#if (configUSE_TICKLESS_IDLE != 0)
void vApplicationSleep(uint32_t xExpectedIdleTime);
#define portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime) vApplicationSleep(xExpectedIdleTime)
#endif
// #define portUSING_MPU_WRAPPERS
#if (configGENERATE_RUN_TIME_STATS == 1)
#include <bflb_mtimer.h>
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE() (bflb_mtimer_get_time_ms())
#endif
#endif /* FREERTOS_CONFIG_H */

View file

@ -0,0 +1,36 @@
// Copyright 2023 Justin Hammond
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef OBLFR_COMMON_H
#define OBLFR_COMMON_H
#include "sdkconfig.h"
#include <assert.h>
typedef enum {
OBLFR_OK = 0,
OBLFR_ERR_ERROR = 1,
OBLFR_ERR_TIMEOUT = 2,
OBLFR_ERR_INVALID = 3, /* invalid arguments */
OBLFR_ERR_NORESC = 4, /* no resource or resource temperary unavailable */
OBLFR_ERR_NOMEM = 5, /* no memory */
OBLFR_ERR_NOTSUPPORTED = 6
} OBLFR_Err;
typedef int oblfr_err_t;
#endif

View file

@ -0,0 +1,29 @@
// Copyright 2023 Justin Hammond
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef OBLFR_SYSTEM_H
#define OBLFR_SYSTEM_H
#ifdef __cplusplus
extern "C" {
#endif
extern void app_main(void *arg);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,50 @@
// Copyright 2023 Justin Hammond
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <stdint.h>
#include <stdbool.h>
#include "sdkconfig.h"
#include "FreeRTOSConfig.h"
#include <FreeRTOS.h>
#include <task.h>
#include "board.h"
#include "oblfr_system.h"
#include "oblfr_common.h"
#ifdef CONFIG_COMPONENT_TIMER
#include "oblfr_timer.h"
#endif
#define DBG_TAG "SYS"
#include "log.h"
int main(void)
{
board_init();
#ifdef CONFIG_COMPONENT_TIMER
oblfr_timer_init();
#endif
if (xTaskCreate(app_main, "app_main", CONFIG_COMPONENT_SYSTEM_MAIN_TASK_STACK_SIZE, NULL, CONFIG_COMPONENT_SYSTEM_MAIN_TASK_PRIORITY, NULL) != pdPASS) {
LOG_E("Failed to create app_main task");
while (true) {
;
}
}
vTaskStartScheduler();
}

View file

@ -0,0 +1,4 @@
sdk_generate_library(oblfr_timer)
sdk_add_include_directories(include)
sdk_library_add_sources(${CMAKE_CURRENT_SOURCE_DIR}/oblfr_timer.c)

10
components/timer/Kconfig Normal file
View file

@ -0,0 +1,10 @@
config COMPONENT_TIMER
bool "Timer Component"
default n
select COMPONENT_SYSTEM
help
"Timer Component"
menu "Timer Configuration"
endmenu

View file

@ -0,0 +1,169 @@
// Copyright 2023 Justin Hammond
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Modeled After the esp-idf timer implementation
#ifndef OBLFR_TIMER_H
#define OBLFR_TIMER_H
#include <stdint.h>
#include <stdbool.h>
#include "oblfr_common.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Maximum number of timers that can be created
*/
#define OBLFR_TIMER_MAX 10
/**
* @brief Opaque handle to a timer
*/
typedef struct oblfr_timer *oblfr_timer_t;
/**
* @brief Timer callback function
*/
typedef void (*oblfr_timer_cb_t)(void* arg);
/**
* @brief Timer configuration passed to esp_timer_create
*/
typedef struct {
const char* name; //!< Timer name, used in esp_timer_dump function
oblfr_timer_cb_t callback; //!< Function to call when timer expires
void* arg; //!< Argument to pass to the callback
} oblfr_timer_create_args_t;
/**
* @brief Initialize the timer module
*
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_ERROR: If we can not create the FreeRTOS Timer Task
*/
oblfr_err_t oblfr_timer_init();
/**
* @brief Deinitialize the timer module
*
* This function will stop the timer interupt and remove all timers from the active timer
* list. The user is responsible for calling oblfr_timer_delete for created timers.
*
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_ERROR if we are unable to stop all registered timers.
*/
oblfr_err_t oblfr_timer_deinit();
/**
* @brief Create a timer
*
* @param create_args Configuration for the timer
* @param out_handle Pointer to a variable to receive the timer handle
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If create_args is NULL or out_handle is NULL or create_args->callback is NULL
* - OBLFR_ERR_NOMEM: If we are unable to allocate memory for the timer out_handle
*/
oblfr_err_t oblfr_timer_create(const oblfr_timer_create_args_t* create_args,
oblfr_timer_t* out_handle);
/**
* @brief Start a one-shot timer.
*
* The Timer will be called after timeout_ms milliseconds. If the timer is already active,
* a error will be returned.
*
* @param timer Handle of the timer to start
* @param timeout_ms Timeout in milliseconds
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If timer is NULL, or it is already active
*/
oblfr_err_t oblfr_timer_start_once(oblfr_timer_t timer, uint32_t timeout_ms);
/**
* @brief Start a periodic timer.
*
* The Timer will be called every period_ms milliseconds. If the timer is already active,
* a error will be returned.
*
* @param timer Handle of the timer to start
* @param period_ms Period in milliseconds
* @param oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If timer is NULL, or it is already active
*/
oblfr_err_t oblfr_timer_start_periodic(oblfr_timer_t timer, uint32_t period_ms);
/**
* @brief Restart a timer.
*
* If the timer is not active, a error will be returned. Otherwise the timer will be rescheduled
* to call after timeout_ms milliseconds.
*
* @param timer Handle of the timer to restart
* @param timeout_ms Timeout in milliseconds
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If timer is NULL, or it is not active, or we are unable to reschedule the timer
*/
oblfr_err_t oblfr_timer_restart(oblfr_timer_t timer, uint32_t timeout_ms);
/**
* @brief Stop a timer.
*
* If the timer is not active, a error will be returned. Otherwise the timer will be stopped.
*
* @param timer Handle of the timer to stop
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If timer is NULL, or it is not active
*/
oblfr_err_t oblfr_timer_stop(oblfr_timer_t timer);
/**
* @brief Delete a timer.
*
* If the timer is active, a error will be returned. Otherwise the timer will be deleted.
*
* @param timer Handle of the timer to delete
* @return oblfr_err_t
* - OBLFR_OK: Success
* - OBLFR_ERR_INVALID: If timer is NULL, or it is active
*/
oblfr_err_t oblfr_timer_delete(oblfr_timer_t timer);
/**
* @brief Check if a timer is active.
*
* @param timer Handle of the timer to check
* @return true if the timer is active, false otherwise
*/
bool oblfr_timer_is_active(oblfr_timer_t timer);
/**
* @brief Dump the Timer list to the console
*/
void oblfr_timer_dump();
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,338 @@
#include "oblfr_timer.h"
#include <sys/queue.h>
#include <bflb_timer.h>
#include <bflb_mtimer.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define DBG_TAG "TMR"
#include "log.h"
struct oblfr_timer {
uint64_t when;
uint64_t period;
oblfr_timer_cb_t callback;
bool repeat;
const char *name;
LIST_ENTRY(oblfr_timer) list_entry;
};
static LIST_HEAD(oblfr_timer_list, oblfr_timer) oblfr_timer_list = LIST_HEAD_INITIALIZER(oblfr_timer_list);
static SemaphoreHandle_t oblfr_timer_list_lock = NULL;
static TaskHandle_t oblfr_timer_task_handle = NULL;
static struct bflb_device_s *timer0 = NULL;
void timer_isr(int irq, void *arg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
bool status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_0);
if (status) {
bflb_timer_compint_clear(timer0, TIMER_COMP_ID_0);
vTaskNotifyGiveFromISR(oblfr_timer_task_handle, &xHigherPriorityTaskWoken);
}
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
static bool oblfr_timer_lock() {
if (xSemaphoreTake(oblfr_timer_list_lock, portMAX_DELAY) != pdPASS) {
LOG_E("Failed to take timer list lock\r\n");
return false;
}
return true;
}
static bool oblfr_timer_unlock() {
if (xSemaphoreGive(oblfr_timer_list_lock) != pdPASS) {
LOG_E("Failed to give timer list lock\r\n");
return false;
}
return true;
}
static oblfr_err_t oblfr_recalc_next_fire() {
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_process: Failed to take timer list lock\r\n");
return OBLFR_ERR_ERROR;
}
oblfr_timer_t head = LIST_FIRST(&oblfr_timer_list);
assert(head != NULL);
uint64_t when = (head->when - bflb_mtimer_get_time_ms());
if (when > 1000) {
when = 1000;
}
if (when < 0) {
when = 0;
}
/* increase 1ms so it fires after the next timer expires */
when += 1;
bflb_timer_set_compvalue(timer0, TIMER_COMP_ID_0, (when*1000));
oblfr_timer_unlock();
return OBLFR_OK;
}
static oblfr_err_t oblfr_timer_insert(struct oblfr_timer *timer) {
struct oblfr_timer *t;
if (!oblfr_timer_lock()) {
return OBLFR_ERR_ERROR;
}
oblfr_timer_t tail;
if (LIST_FIRST(&oblfr_timer_list) == NULL) {
LIST_INSERT_HEAD(&oblfr_timer_list, timer, list_entry);
} else {
LIST_FOREACH(t, &oblfr_timer_list, list_entry) {
if (t->when > timer->when) {
LIST_INSERT_BEFORE(t, timer, list_entry);
break;
}
tail = t;
}
if (t == NULL) {
LIST_INSERT_AFTER(tail, timer, list_entry);
}
}
oblfr_timer_unlock();
return OBLFR_OK;
}
static oblfr_err_t oblfr_timer_remove(struct oblfr_timer *timer) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (!oblfr_timer_lock()) {
return OBLFR_ERR_ERROR;
}
LIST_REMOVE(timer, list_entry);
timer->when = 0;
oblfr_timer_unlock();
return OBLFR_OK;
}
static void oblfr_timer_process() {
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_process: Failed to take timer list lock\r\n");
return;
}
struct oblfr_timer *t = LIST_FIRST(&oblfr_timer_list);
while (t != NULL) {
uint64_t now = bflb_mtimer_get_time_ms();
if (t->when > now) {
LOG_D("oblfr_timer_process: next timer is %s, %lld ms later\r\n", t->name, t->when - now);
break;
}
struct oblfr_timer *next = LIST_NEXT(t, list_entry);
LIST_REMOVE(t, list_entry);
t->callback(t);
if (t->repeat) {
t->when += t->period;
LOG_D("oblfr_timer_process: repeat timer %s, next time is %lld ms later\r\n", t->name, t->period);
oblfr_timer_unlock();
oblfr_timer_insert(t);
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_process: Failed to take timer list lock\r\n");
return;
}
}
t = next;
}
oblfr_timer_unlock();
oblfr_recalc_next_fire();
}
static void oblfr_timer_task(void *arg) {
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
oblfr_timer_process();
}
}
oblfr_err_t oblfr_timer_init() {
/* timer clk = XCLK/(div + 1 )*/
struct bflb_timer_config_s cfg0;
cfg0.counter_mode = TIMER_COUNTER_MODE_PROLOAD; /* preload when match occur */
cfg0.clock_source = TIMER_CLKSRC_XTAL;
#ifdef CONFIG_CHIP_BL708
cfg0.clock_div = 31; /* for bl616/bl808/bl606p is 39, for bl702 is 31 */
#else
cfg0.clock_div = 39; /* for bl616/bl808/bl606p is 39, for bl702 is 31 */
#endif
cfg0.trigger_comp_id = TIMER_COMP_ID_0;
cfg0.comp0_val = 1000000; /* this is 100ms TODO: reduce for production */
timer0 = bflb_device_get_by_name("timer0");
bflb_timer_init(timer0, &cfg0);
bflb_irq_attach(timer0->irq_num, timer_isr, NULL);
bflb_irq_enable(timer0->irq_num);
vSemaphoreCreateBinary(oblfr_timer_list_lock);
if (xTaskCreate(oblfr_timer_task, "oblfr_timer_task", 1024, NULL, 5, &oblfr_timer_task_handle) != pdPASS) {
LOG_E("Create Timer Task failed\r\n");
return OBLFR_ERR_ERROR;
}
bflb_timer_start(timer0);
return OBLFR_OK;
}
oblfr_err_t oblfr_timer_deinit() {
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_deinit: Failed to take timer list lock\r\n");
return OBLFR_ERR_ERROR;
}
bflb_irq_disable(timer0->irq_num);
bflb_irq_detach(timer0->irq_num);
bflb_timer_stop(timer0);
struct oblfr_timer *t = LIST_FIRST(&oblfr_timer_list);
while (t != NULL) {
struct oblfr_timer *next = LIST_NEXT(t, list_entry);
oblfr_timer_unlock();
oblfr_timer_stop(t);
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_deinit: Failed to take timer list lock\r\n");
return OBLFR_ERR_ERROR;
}
t = next;
}
vTaskDelete(oblfr_timer_task_handle);
LOG_D("Timer deinit\r\n");
oblfr_timer_unlock();
return OBLFR_OK;
}
oblfr_err_t oblfr_timer_create(const oblfr_timer_create_args_t* create_args, oblfr_timer_t* out_handle) {
if (create_args == NULL || create_args->callback == NULL || out_handle == NULL) {
return OBLFR_ERR_INVALID;
}
oblfr_timer_t timer = (oblfr_timer_t)malloc(sizeof(struct oblfr_timer));
if (timer == NULL) {
return OBLFR_ERR_NOMEM;
}
timer->callback = create_args->callback;
timer->name = create_args->name;
timer->list_entry.le_next = NULL;
*out_handle = timer;
LOG_D("Timer %s created\r\n", timer->name);
return OBLFR_OK;
}
oblfr_err_t oblfr_timer_start_once(oblfr_timer_t timer, uint32_t timeout_ms) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (oblfr_timer_is_active(timer)) {
LOG_E("Timer %s is already active\r\n", timer->name);
return OBLFR_ERR_INVALID;
}
timer->when = bflb_mtimer_get_time_ms() + timeout_ms;
timer->period = 0;
timer->repeat = false;
oblfr_err_t ret = oblfr_timer_insert(timer);
oblfr_recalc_next_fire();
LOG_D("Starting one-shot timer %s - Trigger at %lld\r\n", timer->name, timeout_ms);
return ret;
}
oblfr_err_t oblfr_timer_start_periodic(oblfr_timer_t timer, uint32_t period_ms) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (oblfr_timer_is_active(timer)) {
LOG_E("Timer %s is already active\r\n", timer->name);
return OBLFR_ERR_INVALID;
}
timer->when = bflb_mtimer_get_time_ms() + period_ms;
timer->period = period_ms;
timer->repeat = true;
oblfr_err_t ret = oblfr_timer_insert(timer);
oblfr_recalc_next_fire();
LOG_D("Starting periodic timer %s - Next Trigger at %lld\r\n", timer->name, timer->period);
return ret;
}
oblfr_err_t oblfr_timer_restart(oblfr_timer_t timer, uint32_t timeout_ms) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (timeout_ms == 0) {
return OBLFR_ERR_INVALID;
}
if (!oblfr_timer_is_active(timer)) {
return OBLFR_ERR_INVALID;
}
if (oblfr_timer_remove(timer) != OBLFR_OK) {
return OBLFR_ERR_INVALID;
}
if (timer->repeat) {
LOG_D("Restarting periodic timer %s\r\n", timer->name);
return oblfr_timer_start_periodic(timer, timeout_ms);
} else {
LOG_D("Restarting one-shot timer %s\r\n", timer->name);
return oblfr_timer_start_once(timer, timeout_ms);
}
}
oblfr_err_t oblfr_timer_stop(oblfr_timer_t timer) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (oblfr_timer_remove(timer) != OBLFR_OK) {
return OBLFR_ERR_INVALID;
}
LOG_D("Timer %s stopped\r\n", timer->name);
oblfr_recalc_next_fire();
return OBLFR_OK;
}
oblfr_err_t oblfr_timer_delete(oblfr_timer_t timer) {
if (timer == NULL) {
return OBLFR_ERR_INVALID;
}
if (oblfr_timer_is_active(timer)) {
return OBLFR_ERR_INVALID;
}
LOG_D("Deleting Timer %s\r\n", timer->name);
free(timer);
return OBLFR_OK;
}
bool oblfr_timer_is_active(oblfr_timer_t timer) {
if (timer == NULL) {
return false;
}
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_deinit: Failed to take timer list lock\r\n");
return false;
}
if (LIST_NEXT(timer, list_entry) != NULL) {
oblfr_timer_unlock();
return true;
}
oblfr_timer_unlock();
return false;
}
void oblfr_timer_dump() {
if (!oblfr_timer_lock()) {
LOG_E("oblfr_timer_deinit: Failed to take timer list lock\r\n");
return;
}
struct oblfr_timer *t;
LOG_I("Timer List Dump:\r\n");
LIST_FOREACH(t, &oblfr_timer_list, list_entry) {
int64_t in = t->when - bflb_mtimer_get_time_ms();
if ( in < 0)
in = 0;
LOG_I("\tTimer %s: in=%lld repeat=%s, period=%lld\r\n", t->name, in, t->repeat ? "true":"false", t->period);
}
LOG_I("End of Timer List Dump\r\n");
oblfr_timer_unlock();
}