I will attach my main.c and skd_conifg, maybe i'm overseeing something in code. Also i will attach schematics and pcb. I'm aware of RGB led consumption, in reality it's around 0.4mA hence when i turn it of i'm getting the 1.6mA. The wireless charging ic was removed.
dev_pcb.zip
/****************************************************************************************
* nRF52820 - S112 SoftDevice *
* Vibration - RGB - Voltage-Alert BLE Demo *
* *
* 23-Apr-2025 *
* . Switched COMP from 3 ms polling to interrupt-driven edge detection. *
* . Removed RTC sampling timer, m_do_sample flag and related code. *
* . COMP "UP" event restarts 800 ms latch watchdog; timeout clears the alert. *
* . Saves 250 uA when idle. *
****************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "app_error.h"
#include "ble.h"
#include "ble_hci.h"
#include "ble_srv_common.h"
#include "ble_advdata.h"
#include "ble_advertising.h"
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gatt.h"
#include "nrf_delay.h"
#include "SEGGER_RTT.h"
#include "nrf_gpio.h"
#include "nrfx_comp.h"
#include "app_timer.h"
#include "nrf_error.h"
#include "nrfx_spim.h"
/* -------------------------------------------------------------------------- */
/* Conditional debug output */
/* -------------------------------------------------------------------------- */
#if defined(NRF_LOG_ENABLED) && NRF_LOG_ENABLED
# define DBG_PUTS(s) SEGGER_RTT_WriteString(0, (s))
# define DBG_PRINTF(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
# define DBG_PUTS(s) do { } while (0)
# define DBG_PRINTF(...) do { } while (0)
#endif
/* ---------- QUICK-FIX FOR OLD SDKs ---------------------------------------- */
#ifndef NRF_ERROR_ALREADY
#define NRF_ERROR_ALREADY (NRF_ERROR_BASE_NUM + 0x018)
#endif
/* -------------------------------------------------------------------------- */
#define APP_BLE_CONN_CFG_TAG 1
#define DEVICE_NAME "Oulffa DB1/2025"
/* --- GPIO ----------------------------------------------------------------- */
#define VIBRATION_PIN NRF_GPIO_PIN_MAP(0,30)
#define WS2812B_PIN 29
/* --- BLE timing ----------------------------------------------------------- */
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(1000, UNIT_1_25_MS)
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(1800, UNIT_1_25_MS)
#define SLAVE_LATENCY 4
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(24000, UNIT_10_MS)
/* --- Service / characteristic UUIDs -------------------------------------- */
#define GPIO_SERVICE_UUID_TYPE BLE_UUID_TYPE_BLE
#define GPIO_SERVICE_UUID 0x1523
#define VIBRATION_CHAR_UUID 0x1524
#define RGB_CHAR_UUID 0x1525
#define VOLTAGE_ALERT_CHAR_UUID 0x1526
/* --- Comparator threshold ------------------------------------------------- */
#define THRESHOLD_MV 150.0f
#define INTERNAL_REF_MV 1000.0f
/* --- Voltage-latch timing ------------------------------------------------- */
#define VOLTAGE_TIMEOUT_MS 800
/* --- BLE / advertising ---------------------------------------------------- */
NRF_BLE_GATT_DEF(m_gatt);
BLE_ADVERTISING_DEF(m_advertising);
/* --- Voltage-timeout timer ----------------------------------------------- */
APP_TIMER_DEF(m_voltage_timer_id);
/* -------------------------------------------------------------------------- */
/* WS2812 via SPIM0 */
/* -------------------------------------------------------------------------- */
#define WS_MOSI_PIN 29
#define WS_SCK_PIN 28
static const nrfx_spim_t WS_SPI = NRFX_SPIM_INSTANCE(0);
/* 5-bit symbol encoding helpers (4 MHz clock -> 250 ns/bit) */
static void encode_byte(uint8_t byte, uint8_t *dst)
{
uint8_t out[5] = {0};
int bitpos = 0;
for (int bit = 7; bit >= 0; --bit)
{
uint8_t pat = (byte & (1 << bit)) ? 0b11100 : 0b11000; /* 1 or 0 */
for (int k = 4; k >= 0; --k)
{
if (pat & (1 << k))
out[bitpos >> 3] |= 1 << (7 - (bitpos & 7));
bitpos++;
}
}
memcpy(dst, out, 5);
}
static uint8_t txbuf[25]; /* 15 B data + 10 B reset */
static void ws2812_send_color(uint8_t r,
uint8_t g,
uint8_t b,
uint8_t br) /* 0-255 brightness */
{
/* ---------- 1. Apply global brightness & build the frame -------- */
r = (uint16_t) r * br / 255;
g = (uint16_t) g * br / 255;
b = (uint16_t) b * br / 255;
encode_byte(g, &txbuf[0]); /* 0- 4 */
encode_byte(r, &txbuf[5]); /* 5- 9 */
encode_byte(b, &txbuf[10]); /* 10-14 */
memset(&txbuf[15], 0x00, 25); /* >= 50 us "reset" idle */
/* ---------- 2. Dynamically init SPIM0 --------------------------- */
static const nrfx_spim_t WS_SPI = NRFX_SPIM_INSTANCE(0);
nrfx_spim_config_t cfg = NRFX_SPIM_DEFAULT_CONFIG;
cfg.sck_pin = WS_SCK_PIN;
cfg.mosi_pin = WS_MOSI_PIN;
cfg.miso_pin = NRFX_SPIM_PIN_NOT_USED;
cfg.ss_pin = NRFX_SPIM_PIN_NOT_USED;
cfg.frequency = NRF_SPIM_FREQ_4M;
cfg.mode = NRF_SPIM_MODE_1;
cfg.bit_order = NRF_SPIM_BIT_ORDER_MSB_FIRST;
cfg.irq_priority = 6;
APP_ERROR_CHECK(nrfx_spim_init(&WS_SPI, &cfg, NULL, NULL));
/* ---------- 3. Transmit buffer ---------------------------------- */
nrfx_spim_xfer_desc_t x =
{
.p_tx_buffer = txbuf,
.tx_length = sizeof(txbuf)
};
APP_ERROR_CHECK(nrfx_spim_xfer(&WS_SPI, &x, 0));
/* ---------- 4. >= 60 us reset-latch ------------------------------ */
nrf_delay_us(60);
/* ---------- 5. Power-down SPIM0 -------------------------------- */
nrfx_spim_uninit(&WS_SPI);
}
/* -------------------------------------------------------------------------- */
/* GATT service struct */
/* -------------------------------------------------------------------------- */
typedef struct
{
uint16_t service_handle;
ble_gatts_char_handles_t vibration_char_handles;
ble_gatts_char_handles_t rgb_char_handles;
ble_gatts_char_handles_t voltage_alert_char_handles;
} ble_gpio_service_t;
static ble_gpio_service_t m_gpio_service;
static ble_uuid_t m_adv_uuids[] = {{GPIO_SERVICE_UUID, GPIO_SERVICE_UUID_TYPE}};
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;
static bool m_voltage_alert_enabled = false;
static bool m_voltage_active = false;
/* -------------------------------------------------------------------------- */
/* Support / error handling */
/* -------------------------------------------------------------------------- */
void assert_nrf_callback(uint16_t line, const uint8_t *file)
{
app_error_handler(0xDEADBEEF, line, file);
}
/* -------------------------------------------------------------------------- */
/* Vibration helpers */
/* -------------------------------------------------------------------------- */
static void vibration_init(void)
{
DBG_PUTS("Init vibration pin\r\n");
nrf_gpio_cfg_output(VIBRATION_PIN);
nrf_gpio_pin_clear(VIBRATION_PIN);
nrf_gpio_pin_set(VIBRATION_PIN);
nrf_delay_ms(500);
nrf_gpio_pin_clear(VIBRATION_PIN);
}
static void vibrate(uint8_t intensity, uint32_t ms)
{
if (!intensity) { nrf_gpio_pin_clear(VIBRATION_PIN); return; }
if (intensity >= 100)
{
nrf_gpio_pin_set(VIBRATION_PIN);
nrf_delay_ms(ms);
nrf_gpio_pin_clear(VIBRATION_PIN);
return;
}
uint32_t cyc = 10000, on = (cyc * intensity) / 100, off = cyc - on,
rep = ms / (cyc / 1000);
for (uint32_t i = 0; i < rep; i++)
{
nrf_gpio_pin_set(VIBRATION_PIN);
nrf_delay_us(on);
nrf_gpio_pin_clear(VIBRATION_PIN);
nrf_delay_us(off);
}
}
static void vibrate_pattern_single(uint8_t i) { vibrate(i, 200); }
static void vibrate_pattern_double(uint8_t i)
{
vibrate(i, 100); nrf_delay_ms(100); vibrate(i, 100);
}
static void vibrate_pattern_long(uint8_t i) { vibrate(i, 500); }
static void vibrate_pattern_escalating(void)
{
for (uint8_t i = 20; i <= 100; i += 20)
{ vibrate(i, 200); nrf_delay_ms(50); }
}
/* -------------------------------------------------------------------------- */
/* Comparator */
/* -------------------------------------------------------------------------- */
static void send_voltage_alert(bool high);
static void voltage_timeout_handler(void *p_context)
{
(void)p_context;
if (m_voltage_active)
{
m_voltage_active = false;
send_voltage_alert(false);
}
}
static void comp_evt_handler(nrf_comp_event_t event)
{
if (event == NRF_COMP_EVENT_UP)
{
if (!m_voltage_active)
{
m_voltage_active = true;
send_voltage_alert(true);
}
ret_code_t err = app_timer_start(m_voltage_timer_id,
APP_TIMER_TICKS(VOLTAGE_TIMEOUT_MS),
NULL);
if (err != NRF_SUCCESS &&
err != NRF_ERROR_INVALID_STATE &&
err != NRF_ERROR_ALREADY)
{
APP_ERROR_CHECK(err);
}
}
}
static void comp_init(void)
{
nrfx_comp_config_t config = NRFX_COMP_DEFAULT_CONFIG(NRF_COMP_INPUT_0);
config.threshold.th_up = config.threshold.th_down =
(uint8_t)((THRESHOLD_MV / INTERNAL_REF_MV) * 63.0f);
config.input = NRF_COMP_INPUT_0;
ret_code_t err = nrfx_comp_init(&config, comp_evt_handler);
APP_ERROR_CHECK(err);
nrfx_comp_start(COMP_INTENSET_UP_Msk, 0); // enable UP interrupt only
}
/* -------------------------------------------------------------------------- */
/* Voltage alert helpers */
/* -------------------------------------------------------------------------- */
static void send_voltage_alert(bool high)
{
if (!m_voltage_alert_enabled || m_conn_handle == BLE_CONN_HANDLE_INVALID)
return;
uint8_t d = high ? 1 : 0;
ble_gatts_hvx_params_t hvx =
{
.handle = m_gpio_service.voltage_alert_char_handles.value_handle,
.type = BLE_GATT_HVX_NOTIFICATION,
.p_data = &d,
.p_len = (uint16_t[]){1},
};
ret_code_t err = sd_ble_gatts_hvx(m_conn_handle, &hvx);
if (err != NRF_SUCCESS &&
err != NRF_ERROR_BUSY &&
err != NRF_ERROR_RESOURCES)
{
APP_ERROR_CHECK(err);
}
}
/* -------------------------------------------------------------------------- */
/* BLE event handling */
/* -------------------------------------------------------------------------- */
static void on_write(ble_evt_t const *e)
{
const ble_gatts_evt_write_t *w = &e->evt.gatts_evt.params.write;
if (w->handle == m_gpio_service.vibration_char_handles.value_handle && w->len == 2)
{
uint8_t p = w->data[0], i = w->data[1];
switch (p)
{
case 0: vibrate(0, 0); break;
case 1: vibrate_pattern_single(i); break;
case 2: vibrate_pattern_double(i); break;
case 3: vibrate_pattern_long(i); break;
case 4: vibrate_pattern_escalating();break;
}
}
else if (w->handle == m_gpio_service.rgb_char_handles.value_handle && w->len == 4)
{
ws2812_send_color(w->data[0], w->data[1], w->data[2], w->data[3]);
}
else if (w->handle == m_gpio_service.voltage_alert_char_handles.cccd_handle && w->len == 2)
{
m_voltage_alert_enabled = (w->data[0] & 1);
}
}
static void ble_evt_handler(ble_evt_t const *e, void *p_ctx)
{
(void)p_ctx;
switch (e->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
m_conn_handle = e->evt.gap_evt.conn_handle;
break;
case BLE_GAP_EVT_DISCONNECTED:
m_conn_handle = BLE_CONN_HANDLE_INVALID;
m_voltage_alert_enabled = false;
m_voltage_active = false;
nrf_gpio_pin_clear(VIBRATION_PIN);
ws2812_send_color(0, 0, 0, 0);
ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
break;
case BLE_GATTS_EVT_WRITE:
on_write(e);
break;
default:
break;
}
}
/* -------------------------------------------------------------------------- */
/* GATT service / characteristics */
/* -------------------------------------------------------------------------- */
static void gpio_service_init(void)
{
ret_code_t err;
ble_uuid_t svc = { .type = GPIO_SERVICE_UUID_TYPE, .uuid = GPIO_SERVICE_UUID };
APP_ERROR_CHECK(sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&svc,
&m_gpio_service.service_handle));
/* --- Vibration characteristic ------------------------------------- */
ble_gatts_char_md_t cmd = {0};
cmd.char_props.read = 1;
cmd.char_props.write = 1;
static char vib_desc[] = "Vibration Pattern";
cmd.p_char_user_desc = (uint8_t*)vib_desc;
cmd.char_user_desc_max_size = cmd.char_user_desc_size = strlen(vib_desc);
ble_uuid_t cuuid = { .type = GPIO_SERVICE_UUID_TYPE, .uuid = VIBRATION_CHAR_UUID };
ble_gatts_attr_md_t attr_md = {0};
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
attr_md.vloc = BLE_GATTS_VLOC_STACK;
uint8_t vib_init[2] = {0, 0};
ble_gatts_attr_t attr =
{
.p_uuid = &cuuid,
.p_attr_md = &attr_md,
.init_len = 2,
.max_len = 2,
.p_value = vib_init,
};
err = sd_ble_gatts_characteristic_add(m_gpio_service.service_handle,
&cmd,
&attr,
&m_gpio_service.vibration_char_handles);
APP_ERROR_CHECK(err);
/* --- RGB characteristic ------------------------------------------ */
memset(&cmd, 0, sizeof(cmd));
cmd.char_props.read = 1;
cmd.char_props.write = 1;
static char rgb_desc[] = "RGB Control";
cmd.p_char_user_desc = (uint8_t*)rgb_desc;
cmd.char_user_desc_max_size = cmd.char_user_desc_size = strlen(rgb_desc);
cuuid.uuid = RGB_CHAR_UUID;
uint8_t rgb_init[4] = {0, 0, 0, 0};
attr.p_uuid = &cuuid;
attr.init_len = 4;
attr.max_len = 4;
attr.p_value = rgb_init;
err = sd_ble_gatts_characteristic_add(m_gpio_service.service_handle,
&cmd,
&attr,
&m_gpio_service.rgb_char_handles);
APP_ERROR_CHECK(err);
/* --- Voltage alert characteristic -------------------------------- */
memset(&cmd, 0, sizeof(cmd));
cmd.char_props.read = 1;
cmd.char_props.notify = 1;
static char volt_desc[] = "Voltage Alert";
cmd.p_char_user_desc = (uint8_t*)volt_desc;
cmd.char_user_desc_max_size = cmd.char_user_desc_size = strlen(volt_desc);
ble_gatts_attr_md_t cccd_md = {0};
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
cmd.p_cccd_md = &cccd_md;
cuuid.uuid = VOLTAGE_ALERT_CHAR_UUID;
uint8_t volt_init = 0;
attr.p_uuid = &cuuid;
attr.init_len = 1;
attr.max_len = 1;
attr.p_value = &volt_init;
err = sd_ble_gatts_characteristic_add(m_gpio_service.service_handle,
&cmd,
&attr,
&m_gpio_service.voltage_alert_char_handles);
APP_ERROR_CHECK(err);
}
/* -------------------------------------------------------------------------- */
/* GAP / GATT / SoftDevice */
/* -------------------------------------------------------------------------- */
static void gap_params_init(void)
{
ble_gap_conn_sec_mode_t sec;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec);
APP_ERROR_CHECK(sd_ble_gap_device_name_set(&sec,
(const uint8_t*)DEVICE_NAME,
strlen(DEVICE_NAME)));
ble_gap_conn_params_t cp = {0};
cp.min_conn_interval = MIN_CONN_INTERVAL;
cp.max_conn_interval = MAX_CONN_INTERVAL;
cp.slave_latency = SLAVE_LATENCY;
cp.conn_sup_timeout = CONN_SUP_TIMEOUT;
APP_ERROR_CHECK(sd_ble_gap_ppcp_set(&cp));
}
static void gatt_init(void)
{
APP_ERROR_CHECK(nrf_ble_gatt_init(&m_gatt, NULL));
}
static void ble_stack_init(void)
{
APP_ERROR_CHECK(nrf_sdh_enable_request());
uint32_t ram_start = 0;
APP_ERROR_CHECK(nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start));
APP_ERROR_CHECK(nrf_sdh_ble_enable(&ram_start));
NRF_SDH_BLE_OBSERVER(m_observer, 3, ble_evt_handler, NULL);
}
/* -------------------------------------------------------------------------- */
/* Advertising */
/* -------------------------------------------------------------------------- */
static void advertising_init(void)
{
ble_advdata_t adv = {0};
adv.name_type = BLE_ADVDATA_FULL_NAME;
adv.include_appearance = true;
adv.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
adv.uuids_complete.p_uuids = m_adv_uuids;
adv.uuids_complete.uuid_cnt = ARRAY_SIZE(m_adv_uuids);
ble_advertising_init_t init = {0};
init.advdata = adv;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = 1600;
init.config.ble_adv_fast_timeout = 0;
APP_ERROR_CHECK(ble_advertising_init(&m_advertising, &init));
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
static void advertising_start(void)
{
APP_ERROR_CHECK(ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST));
}
/* -------------------------------------------------------------------------- */
/* MAIN */
/* -------------------------------------------------------------------------- */
int main(void)
{
//SEGGER_RTT_Init();
//DBG_PUTS("Demo start\r\n");
for (uint32_t pin = 0; pin < 32; pin++) {
if (pin != VIBRATION_PIN && pin != WS2812B_PIN) {
nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_PULLDOWN);
}
}
vibration_init();
comp_init();
ble_stack_init();
APP_ERROR_CHECK(app_timer_init());
APP_ERROR_CHECK(app_timer_create(&m_voltage_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
voltage_timeout_handler));
gap_params_init();
gatt_init();
gpio_service_init();
advertising_init();
advertising_start();
APP_ERROR_CHECK(sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, m_advertising.adv_handle, -20)); // Sets TX power to -20 dBm
ws2812_send_color(255, 0, 0, 255); nrf_delay_ms(500);
ws2812_send_color(0, 0, 0, 0);
// Example test sections:
nrfx_comp_uninit(); // Disable comparator
// Measure current
nrf_gpio_cfg_default(WS2812B_PIN); // Disable LED
// Measure current
// Disable BLE (temporarily):
sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
/* ---------- Main loop ---------- */
for (;;)
{
/* Let SoftDevice wake us on BLE, COMP, or timer IRQs */
(void) sd_app_evt_wait();
}
}