From c4a67d73d1d1854f51b0400a3f40bca86e3107fb Mon Sep 17 00:00:00 2001 From: Michal Gorecki Date: Thu, 11 Aug 2022 15:01:11 +0200 Subject: [PATCH] apps/bleservo: Initial commit nRF52840-DK BLE peripheral sample with PWM servo control ability. --- apps/bleservo/README.md | 10 + apps/bleservo/pkg.yml | 46 +++++ apps/bleservo/src/ble_servo.h | 62 +++++++ apps/bleservo/src/gatt_svr.c | 195 ++++++++++++++++++++ apps/bleservo/src/main.c | 331 ++++++++++++++++++++++++++++++++++ apps/bleservo/syscfg.yml | 36 ++++ 6 files changed, 680 insertions(+) create mode 100644 apps/bleservo/README.md create mode 100644 apps/bleservo/pkg.yml create mode 100644 apps/bleservo/src/ble_servo.h create mode 100644 apps/bleservo/src/gatt_svr.c create mode 100644 apps/bleservo/src/main.c create mode 100644 apps/bleservo/syscfg.yml diff --git a/apps/bleservo/README.md b/apps/bleservo/README.md new file mode 100644 index 0000000000..8aa6b93db7 --- /dev/null +++ b/apps/bleservo/README.md @@ -0,0 +1,10 @@ +# BLE Servo peripheral app. + +The source files are located in the src/ directory. + +pkg.yml contains the base definition of the app. + +syscfg.yml contains setting definitions and overrides. + +On default servo PWM channel is connected to pin 31 of nRF52840-DK board, +and it can be changed in ble_servo.h file (make sure that chosen pin supports PWM). diff --git a/apps/bleservo/pkg.yml b/apps/bleservo/pkg.yml new file mode 100644 index 0000000000..b6dd6ed81b --- /dev/null +++ b/apps/bleservo/pkg.yml @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +pkg.name: apps/bleservo +pkg.type: app +pkg.description: BLE peripheral servo motor controller. +pkg.author: "Michal Gorecki" +pkg.email: "michal.gorecki@codecoup.pl" +pkg.homepage: +pkg.keywords: + +pkg.deps: + - "@apache-mynewt-core/boot/split" + - "@mcuboot/boot/bootutil" + - "@apache-mynewt-core/kernel/os" + - "@apache-mynewt-core/mgmt/imgmgr" + - "@apache-mynewt-core/mgmt/smp" + - "@apache-mynewt-core/mgmt/smp/transport/ble" + - "@apache-mynewt-core/sys/console/full" + - "@apache-mynewt-core/sys/log/full" + - "@apache-mynewt-core/sys/log/modlog" + - "@apache-mynewt-core/sys/stats/full" + - "@apache-mynewt-core/sys/sysinit" + - "@apache-mynewt-core/sys/id" + - nimble/host + - nimble/host/services/ans + - nimble/host/services/dis + - nimble/host/services/gap + - nimble/host/services/gatt + - nimble/host/store/config + - nimble/host/util diff --git a/apps/bleservo/src/ble_servo.h b/apps/bleservo/src/ble_servo.h new file mode 100644 index 0000000000..d429e32bf4 --- /dev/null +++ b/apps/bleservo/src/ble_servo.h @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 H_BLE_SERVO_ +#define H_BLE_SERVO_ + +#include "nimble/ble.h" +#include "modlog/modlog.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PWM_CH_CFG_PIN 31 +#define PWM_CH_CFG_INV false +#define PWM_CH_NUM 0 +#define PWM_IRQ_PRIO 1 + +/* + * This servomotor works with 50 Hz PWM signal and accept pulse duration from + * around 1000us minimum (0-degree rotation) to 2000us maximum (180-degree rotation). + * If you have to adjust these parameters for your servo, this is the place to do so. + */ +#define SERVO_MAX_PULSE_DURATION_US 2000 +#define SERVO_MIN_PULSE_DURATION_US 1000 +#define SERVO_PWM_FREQ 50 +#define SERVO_PWM_FULL_CYCLE_DURATION 20000 +#define SERVO_MAX_ANGLE_VAL 180 +#define SERVO_MIN_ANGLE_VAL 0 + + +int gatt_svr_init(void); +void servo_angle_setter(uint16_t); +void servo_pwm_pulse_duration_setter(uint16_t); + +uint16_t get_servo_angle(void); +uint16_t get_servo_pwm_pulse_duration(void); +uint16_t get_gatt_angle_val_handle(void); +uint16_t get_gatt_pulse_duration_val_handle(void); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/apps/bleservo/src/gatt_svr.c b/apps/bleservo/src/gatt_svr.c new file mode 100644 index 0000000000..87756af8f7 --- /dev/null +++ b/apps/bleservo/src/gatt_svr.c @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "ble_servo.h" + +/* 59462f12-9543-9999-12c8-58b459a2712d */ +static const ble_uuid128_t gatt_svr_svc_servo_uuid = + BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, + 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); + +/* 5c3a659e-897e-45e1-b016-007107c96df6 */ +static const ble_uuid128_t gatt_svr_chr_servo_angle_uuid = + BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c); + +/* 6d456567-8995-45e1-b016-007107c96df6 */ +static const ble_uuid128_t gatt_svr_chr_servo_pulse_duration_uuid = + BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0, + 0xe1, 0x45, 0x95, 0x89, 0x67, 0x65, 0x45, 0x6d); + +/* Values kept in characteristics */ +static uint16_t gatt_angle_val; +static uint16_t gatt_pulse_duration_val; + +/* GATT attribute handles */ +static uint16_t gatt_angle_val_handle; +static uint16_t gatt_pulse_duration_val_handle; + +static int +gatt_svr_chr_access_servo_angle(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static int +gatt_svr_chr_access_servo_pulse_duration(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg); + +static int +gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, + void *dst, uint16_t *len) +{ + uint16_t om_len; + int rc; + + om_len = OS_MBUF_PKTLEN(om); + if (om_len < min_len || om_len > max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + return 0; +} + +static const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /* Service: Servo */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &gatt_svr_svc_servo_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) { { + /* Characteristic: servo angle*/ + .uuid = &gatt_svr_chr_servo_angle_uuid.u, + .val_handle = &gatt_angle_val_handle, + .access_cb = gatt_svr_chr_access_servo_angle, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | + BLE_GATT_CHR_F_NOTIFY + }, { + /* Characteristic: servo PWM pulse duration*/ + .uuid = &gatt_svr_chr_servo_pulse_duration_uuid.u, + .val_handle = &gatt_pulse_duration_val_handle, + .access_cb = gatt_svr_chr_access_servo_pulse_duration, + .flags = BLE_GATT_CHR_F_READ | + BLE_GATT_CHR_F_WRITE | + BLE_GATT_CHR_F_NOTIFY + }, { + 0, /* No more characteristics in this service */ + }, } + }, + + { + 0, /* No more services */ + }, +}; + +static int +gatt_svr_chr_access_servo_angle(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + int rc; + + if (attr_handle != gatt_angle_val_handle) { + return BLE_ATT_ERR_INVALID_HANDLE; + } + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + gatt_angle_val = get_servo_angle(); + rc = os_mbuf_append(ctxt->om, &gatt_angle_val, + sizeof gatt_angle_val); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = gatt_svr_chr_write(ctxt->om, + sizeof gatt_angle_val, + sizeof gatt_angle_val, + &gatt_angle_val, NULL); + servo_angle_setter(gatt_angle_val); + + return rc; + default: + return BLE_ATT_ERR_UNLIKELY; + } +} + +static int +gatt_svr_chr_access_servo_pulse_duration(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + int rc; + + if (attr_handle != gatt_pulse_duration_val_handle) { + return BLE_ATT_ERR_INVALID_HANDLE; + } + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + gatt_pulse_duration_val = get_servo_pwm_pulse_duration(); + rc = os_mbuf_append(ctxt->om, &gatt_pulse_duration_val, + sizeof gatt_pulse_duration_val); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + case BLE_GATT_ACCESS_OP_WRITE_CHR: + rc = gatt_svr_chr_write(ctxt->om, + sizeof gatt_pulse_duration_val, + sizeof gatt_pulse_duration_val, + &gatt_pulse_duration_val, NULL); + servo_pwm_pulse_duration_setter(gatt_pulse_duration_val); + + return rc; + default: + return BLE_ATT_ERR_UNLIKELY; + } +} + +int +gatt_svr_init(void) +{ + int rc; + + rc = ble_gatts_count_cfg(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + rc = ble_gatts_add_svcs(gatt_svr_svcs); + if (rc != 0) { + return rc; + } + + return 0; +} + +uint16_t +get_gatt_angle_val_handle(void) +{ + return gatt_angle_val_handle; +} + +uint16_t +get_gatt_pulse_duration_val_handle(void) +{ + return gatt_pulse_duration_val_handle; +} diff --git a/apps/bleservo/src/main.c b/apps/bleservo/src/main.c new file mode 100644 index 0000000000..2ddc01c42d --- /dev/null +++ b/apps/bleservo/src/main.c @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include +#include "os/mynewt.h" +#include "config/config.h" +#include "nimble/ble.h" +#include "host/ble_hs.h" +#include "services/gap/ble_svc_gap.h" +#include "pwm/pwm.h" +#include "ble_servo.h" + +static int bleservo_gap_event(struct ble_gap_event *event, void *arg); + +static uint16_t conn_handle; +static uint8_t own_addr_type; +static const char *device_name = "ble_servo"; + +struct servo_sm { + struct pwm_dev *pwm_dev; + uint16_t pwm_top_val; + uint16_t frac_max_val; + uint16_t frac_min_val; + uint16_t pwm_pulse_duration; + uint16_t angle; + uint16_t frac; +} servo; + +/* A flag that determines if user passed angle value or pwm pulse duration value. */ +static bool was_angle_passed; + +/* PWM servo_frac update event and callback declaration*/ +static void pwm_frac_update_ev_cb(struct os_event *); +struct os_event pwm_frac_update_ev = { + .ev_cb = pwm_frac_update_ev_cb, +}; + +uint16_t +get_servo_angle(void) +{ + return servo.angle; +} + +uint16_t +get_servo_pwm_pulse_duration(void) +{ + return servo.pwm_pulse_duration; +} + +uint16_t +angle_to_frac(uint16_t angle) +{ + return servo.frac_min_val + + (angle * (servo.frac_max_val - servo.frac_min_val) / SERVO_MAX_ANGLE_VAL); +} + +uint16_t +us_to_frac(uint16_t us) +{ + return (us * servo.pwm_top_val) / SERVO_PWM_FULL_CYCLE_DURATION; +} + +uint16_t +frac_to_angle(uint16_t frac_) +{ + return (SERVO_MAX_ANGLE_VAL * frac_ - SERVO_MAX_ANGLE_VAL * servo.frac_min_val) / + (servo.frac_max_val - servo.frac_min_val); +} + +uint16_t +frac_to_us(uint16_t frac_) +{ + return (SERVO_PWM_FULL_CYCLE_DURATION * frac_) / servo.pwm_top_val; +} + +/* Servo angle setter. Used in gatt_svr.c after receiving new angle value. */ +void +servo_angle_setter(uint16_t gatt_value) +{ + if (gatt_value > SERVO_MAX_ANGLE_VAL) { + servo.angle = SERVO_MAX_ANGLE_VAL; + } else if (gatt_value < SERVO_MIN_ANGLE_VAL) { + servo.angle = SERVO_MIN_ANGLE_VAL; + } else { + servo.angle = gatt_value; + } + + was_angle_passed = 1; + + /* + * After changing servo angle value an event + * to update PWM fraction is put to the queue. + */ + os_eventq_put(os_eventq_dflt_get(), &pwm_frac_update_ev); +} + +/* + * Servo PWM pulse duration setter. + * Called in gatt_svr.c after receiving new pulse duration value. + */ +void +servo_pwm_pulse_duration_setter(uint16_t gatt_value) +{ + if (gatt_value > SERVO_MAX_PULSE_DURATION_US) { + servo.pwm_pulse_duration = SERVO_MAX_PULSE_DURATION_US; + } else if (gatt_value < SERVO_MIN_PULSE_DURATION_US) { + servo.pwm_pulse_duration = SERVO_MIN_PULSE_DURATION_US; + } else { + servo.pwm_pulse_duration = gatt_value; + } + + was_angle_passed = 0; + + /* + * After changing servo PWM pulse duration value + * an event to update PWM fraction is put to the queue. + */ + os_eventq_put(os_eventq_dflt_get(), &pwm_frac_update_ev); +} + + +static void +pwm_frac_update_ev_cb(struct os_event *ev) +{ + struct os_mbuf * om; + + if (was_angle_passed == true) { + servo.frac = angle_to_frac(servo.angle); + servo.pwm_pulse_duration = frac_to_us(servo.frac); + } else if (was_angle_passed == false) { + servo.frac = us_to_frac(servo.pwm_pulse_duration); + servo.angle = frac_to_angle(servo.frac); + } + + pwm_set_duty_cycle(servo.pwm_dev, 0, servo.frac); + pwm_enable(servo.pwm_dev); + + /* + * Notifications are sent to keep client's info + * about pwm pulse duration and angle values up to date + */ + om = ble_hs_mbuf_from_flat(&servo.angle, sizeof(servo.angle)); + ble_gatts_notify_custom(conn_handle, get_gatt_angle_val_handle(), om); + + om = ble_hs_mbuf_from_flat(&servo.pwm_pulse_duration, sizeof(servo.pwm_pulse_duration)); + ble_gatts_notify_custom(conn_handle, get_gatt_pulse_duration_val_handle(), om); +} + +void +servo_pwm_init(void) +{ + struct pwm_chan_cfg chan_conf = { + .pin = PWM_CH_CFG_PIN, + .inverted = PWM_CH_CFG_INV, + .data = NULL, + }; + struct pwm_dev_cfg dev_conf = { + .n_cycles = 0, + .int_prio = PWM_IRQ_PRIO, + .cycle_handler = NULL, + .seq_end_handler = NULL, + .cycle_data = NULL, + .seq_end_data = NULL, + .data = NULL + }; + + int rc; + + servo.pwm_dev = (struct pwm_dev *)os_dev_open("pwm0", 0, NULL); + if (!servo.pwm_dev) { + MODLOG_DFLT(ERROR, "Device pwm0 not available\n"); + return; + } + + pwm_configure_device(servo.pwm_dev, &dev_conf); + + rc = pwm_set_frequency(servo.pwm_dev, SERVO_PWM_FREQ); + assert(rc > 0); + rc = pwm_configure_channel(servo.pwm_dev, PWM_CH_NUM, &chan_conf); + assert(rc == 0); + + /* Calculate minimum PWM fracture value */ + servo.pwm_top_val = (uint16_t) pwm_get_top_value(servo.pwm_dev); + servo.frac_max_val = us_to_frac(SERVO_MAX_PULSE_DURATION_US); + servo.frac_min_val = us_to_frac(SERVO_MIN_PULSE_DURATION_US); + + /* At the beginning PWM fracture is set to minimum */ + servo.frac = servo.frac_min_val; + rc = pwm_set_duty_cycle(servo.pwm_dev, PWM_CH_NUM, servo.frac); + assert(rc == 0); + rc = pwm_enable(servo.pwm_dev); + assert(rc == 0); +} + +static void +bleservo_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + memset(&fields, 0, sizeof(fields)); + + fields.name = (uint8_t *)device_name; + fields.name_len = strlen(device_name); + fields.name_is_complete = 1; + fields.flags = BLE_HS_ADV_F_BREDR_UNSUP; + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising */ + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, bleservo_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + + +static int +bleservo_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed */ + MODLOG_DFLT(INFO, "connection %s; status=%d\n", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising */ + bleservo_advertise(); + conn_handle = 0; + } else { + conn_handle = event->connect.conn_handle; + } + + break; + + case BLE_GAP_EVENT_DISCONNECT: + MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason); + conn_handle = BLE_HS_CONN_HANDLE_NONE; /* reset conn_handle */ + + /* Connection terminated; resume advertising */ + bleservo_advertise(); + break; + + case BLE_GAP_EVENT_ADV_COMPLETE: + MODLOG_DFLT(INFO, "adv complete\n"); + bleservo_advertise(); + break; + + case BLE_GAP_EVENT_SUBSCRIBE: + MODLOG_DFLT(INFO, "subscribe event; cur_notify=%d\n value handle; " + "val_handle=%d\n", + event->subscribe.cur_notify, servo.angle); + break; + + case BLE_GAP_EVENT_MTU: + MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.value); + break; + + } + + return 0; +} + +static void +bleservo_on_sync(void) +{ + int rc; + + /* Don't use privacy */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + assert(rc == 0); + + /* Start advertising */ + bleservo_advertise(); +} + +int +main(void) +{ + int rc; + + /* Initialize OS and PWM */ + sysinit(); + servo_pwm_init(); + + /* Initialize the NimBLE host configuration */ + ble_hs_cfg.sync_cb = bleservo_on_sync; + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name */ + rc = ble_svc_gap_device_name_set(device_name); + assert(rc == 0); + + /* As the last thing, process events from default event queue */ + while (1) { + os_eventq_run(os_eventq_dflt_get()); + } + return 0; +} diff --git a/apps/bleservo/syscfg.yml b/apps/bleservo/syscfg.yml new file mode 100644 index 0000000000..1f892ab7b2 --- /dev/null +++ b/apps/bleservo/syscfg.yml @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + + +syscfg.vals: + # Disable central and observer roles. + BLE_ROLE_BROADCASTER: 1 + BLE_ROLE_CENTRAL: 0 + BLE_ROLE_OBSERVER: 0 + BLE_ROLE_PERIPHERAL: 1 + + # Disable unused eddystone feature. + BLE_EDDYSTONE: 0 + + # Set public device address. + BLE_LL_PUBLIC_DEV_ADDR: 0x1122aabb33cc + + # Whether to save data to sys/config, or just keep it in RAM. + BLE_STORE_CONFIG_PERSIST: 0 + + # Enable PWM channel 0. + PWM_0: 1