From a8c61cbc266edfcb25236904b3d2dd7a4146a80e Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 14 Jun 2025 20:20:37 -0500 Subject: [PATCH 1/4] Add nRF52 dongle information --- README.md | 3 +++ examples/fm25_kit/README.md | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 examples/fm25_kit/README.md diff --git a/README.md b/README.md index 13df40b..fef4d7a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ the project can be extended to support a variety of shield peripherals. The following hardware is targeted for a first pass at support: - [nRF52840 Dev Kit](https://www.nordicsemi.com/Products/Development-hardware/nRF52840-DK) + - More expensive, higher pin controller option +- [nRF52840 Dongle](https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle) + - Less expensive and compact controller option - [DFRobot 2x7A DC Motor Driver](https://www.dfrobot.com/product-1534.html) - For controlling the standard higher powered FTC legal DC motors - [Adafruit Motor/Stepper/Servo Shield for Arduino v2](https://www.adafruit.com/product/1438) diff --git a/examples/fm25_kit/README.md b/examples/fm25_kit/README.md new file mode 100644 index 0000000..a4c0cce --- /dev/null +++ b/examples/fm25_kit/README.md @@ -0,0 +1,7 @@ +# Furry Migration 2025 Workshop Kit + +## Materials + +| Item | Link | Cost (USD) | +| --------------- | ------------------------------------------------------------------------ | ---------- | +| nRF52840 Dongle | https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle | $10 | -- 2.56.0 From 603ef99919e45be9cb3f1ed535e9677d318ea1cb Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 31 Aug 2025 12:59:01 -0500 Subject: [PATCH 2/4] BLE stack groundwork --- .gitignore | 1 + .vscode/c_cpp_properties.json | 18 ++ CMakeLists.txt | 7 + core/ble.c | 383 ++++++++++++++++++++++++++++++++++ core/ble.h | 19 ++ core/main.c | 25 +++ examples/fm25_kit/README.md | 7 - prj.conf | 8 + 8 files changed, 461 insertions(+), 7 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 CMakeLists.txt create mode 100644 core/ble.c create mode 100644 core/ble.h create mode 100644 core/main.c delete mode 100644 examples/fm25_kit/README.md create mode 100644 prj.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5309e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build*/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..6a68e8e --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**", + "C:/ncs/v3.0.2/zephyr/include/" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "configurationProvider": "nordic-semiconductor.nrf-connect" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..997f7ea --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(openrcore) + +target_sources(app PRIVATE core/main.c) +target_sources(app PRIVATE core/ble.c) diff --git a/core/ble.c b/core/ble.c new file mode 100644 index 0000000..4d8e99c --- /dev/null +++ b/core/ble.c @@ -0,0 +1,383 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ble.h" + +/** + * @brief Begin BLE advertising with the default parameters + */ +static int _start_advertising(void); + + +/** Callbacks **/ +/** + * @brief Callback registered for when a BLE client connects to us + */ +static void _connected_callback(struct bt_conn *connection, uint8_t error); +/** + * @brief Callback registered for when a BLE client disconnects from us + */ +static void _disconnected_callback(struct bt_conn *connection, uint8_t reason); + +static struct bt_conn_cb connection_callbacks = { + .connected = _connected_callback, + .disconnected = _disconnected_callback, +}; + + +/** Advertising data **/ +/** Our name to advertise */ +#define BLE_NAME_DEFAULT ("FM25 Kit 1") + +/** Advertising/Scanning parameters */ +static struct bt_le_adv_param advertise_params = { + .id = BT_ID_DEFAULT, + .interval_min = 800, /* (800 * 0.625) = 500ms */ + .interval_max = 800, + .options = BT_LE_ADV_OPT_CONN, +}; + +/** Advertising buffer data */ +static struct bt_data advertise_data[] = { + { + .type = BT_DATA_NAME_COMPLETE, + .data = BLE_NAME_DEFAULT, + .data_len = sizeof(BLE_NAME_DEFAULT) - 1, + }, +}; + +/** Scan buffer data */ +static struct bt_data scan_data[] = { + { + .type = BT_DATA_NAME_COMPLETE, + .data = BLE_NAME_DEFAULT, + .data_len = sizeof(BLE_NAME_DEFAULT) - 1, + }, +}; + + + +/** BLE Service Data **/ +/** + * @brief Attribute read callback function for attributes that have a user data + * type of ble_attribute_userdata_t + */ +static ssize_t _generic_attribute_read( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + void *buffer, + uint16_t buffer_len, + uint16_t offset +); + +/** + * @brief Attribute write callback function for attributes that have a user data + * type of ble_attribute_userdata_t + */ +static ssize_t _generic_attribute_write( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + const void *buffer, + uint16_t buffer_len, + uint16_t offset, + uint8_t flags +); + +/*** UUID Data ***/ +static struct bt_uuid_16 service_declaration_uuid = { + .uuid = { .type = BT_UUID_TYPE_16 }, + .val = BT_UUID_GATT_PRIMARY_VAL, +}; + +static struct bt_uuid_16 characteristic_declaration_uuid = { + .uuid = { .type = BT_UUID_TYPE_16 }, + .val = BT_UUID_GATT_CHRC_VAL, +}; + +static struct bt_uuid_16 characteristic_description_uuid = { + .uuid = { .type = BT_UUID_TYPE_16 }, + .val = BT_UUID_GATT_CUD_VAL, +}; + +static struct bt_uuid_128 service_uuid = { + .uuid = { .type = BT_UUID_TYPE_128 }, + .val = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, +}; + +static struct bt_uuid_128 input_characteristic_uuid = { + .uuid = { .type = BT_UUID_TYPE_128 }, + .val = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, +}; + +static struct bt_uuid_128 telemetry_characteristic_uuid = { + .uuid = { .type = BT_UUID_TYPE_128 }, + .val = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, +}; + +/*** Characteristic Data ***/ +typedef struct ble_attribute_userdata_struct { + /** Buffer allocated for this attribute's data */ + void *buffer; + /** Size of the buffer in bytes */ + uint16_t buffer_size; + /** Size of the current value in the buffer */ + uint16_t value_size; + /** Lock to protect the data in the buffer */ + struct k_mutex lock; +} ble_attribute_userdata_t; + +/**** Input Characteristic ****/ +/** Zephyr GATT characteristic struct data for the Input characteristic */ +struct bt_gatt_chrc input_characteristic = { + .uuid = (void *)&input_characteristic_uuid, + .properties = BT_GATT_CHRC_WRITE, +}; +/** Buffer for the contents of the Input characteristic */ +uint8_t input_characteristic_buffer[32]; +/** Userdata utilized in the write callback for the Input characteristic */ +ble_attribute_userdata_t input_characteristic_userdata = { + .buffer = input_characteristic_buffer, + .buffer_size = sizeof(input_characteristic_buffer), + .value_size = 0, + /* lock initialized in ble_init */ +}; +/** Description of the Input characteristic */ +char input_characteristic_description[] = "Input Data"; + + +/**** Telemetry Characteristic ****/ +struct bt_gatt_chrc telemetry_characteristic = { + .uuid = (void *)&telemetry_characteristic_uuid, + .properties = BT_GATT_CHRC_READ, +}; +/** Buffer for the contents of the Telemetry characteristic */ +uint8_t telemetry_characteristic_buffer[512]; +/** Userdata utilized in the write callback for the Input characteristic */ +ble_attribute_userdata_t telemetry_characteristic_userdata = { + .buffer = telemetry_characteristic_buffer, + .buffer_size = sizeof(telemetry_characteristic_buffer), + .value_size = 0, + /* lock initialized in ble_init */ +}; +/** Description of the telemetry characteristic */ +char telemetry_characteristic_description[] = "Telemetry Data"; + + +/*** Service Layout ***/ +static struct bt_gatt_attr primary_service_attributes[] = { + /* Service declaration */ + { + .uuid = (struct bt_uuid *)&service_declaration_uuid, + .read = bt_gatt_attr_read_service, + .user_data = (void *)&service_uuid, + .perm = BT_GATT_PERM_READ, + }, + /* Characteristics */ + /** Control Input Characteristic **/ + { + .uuid = (struct bt_uuid *)&characteristic_declaration_uuid, + .read = bt_gatt_attr_read_chrc, + .user_data = (void *)&input_characteristic, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&input_characteristic_uuid, + .write = _generic_attribute_write, + .user_data = (void *)&input_characteristic_userdata, + .perm = BT_GATT_PERM_WRITE, + }, + { + .uuid = (struct bt_uuid *)&characteristic_description_uuid, + .read = bt_gatt_attr_read_cud, + .user_data = (void *)input_characteristic_description, + .perm = BT_GATT_PERM_READ, + }, + /** Telemetry Data Characteristic **/ + { + .uuid = (struct bt_uuid *)&characteristic_declaration_uuid, + .read = bt_gatt_attr_read_chrc, + .user_data = (void *)&telemetry_characteristic, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&telemetry_characteristic_uuid, + .read = _generic_attribute_read, + .user_data = (void *)&telemetry_characteristic_userdata, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&characteristic_description_uuid, + .read = bt_gatt_attr_read_cud, + .user_data = (void *)telemetry_characteristic_description, + .perm = BT_GATT_PERM_READ, + } +}; + +static struct bt_gatt_service primary_service = { + .attrs = primary_service_attributes, + .attr_count = sizeof(primary_service_attributes) / sizeof(struct bt_gatt_attr), +}; + + +/** Other BLE Data **/ +typedef struct ble_state_struct { + /** Current callback for a complete input write */ + void (*oninput_callback)(const void *buffer, uint16_t buffer_len); + /** Lock for data in the BLE state */ + struct k_mutex lock; +} ble_state_t; + +static ble_state_t ble = { + .oninput_callback = NULL, + /* lock initialized in ble_init */ +}; + +int +ble_init() +{ + int ret; + + k_mutex_init(&ble.lock); + + ret = bt_enable(NULL); + if (ret) { + return ret; + } + + bt_conn_cb_register(&connection_callbacks); + + /* Initialize the service data */ + k_mutex_init(&input_characteristic_userdata.lock); + k_mutex_init(&telemetry_characteristic_userdata.lock); + + ret = bt_gatt_service_register(&primary_service); + if (ret) { + return ret; + } + + _start_advertising(); + + return 0; +} + +void +ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buffer_len)) +{ + k_mutex_lock(&ble.lock, K_FOREVER); + ble.oninput_callback = callback; + k_mutex_unlock(&ble.lock); +} + +void +ble_set_telemetry_data(const void *buffer, uint16_t buffer_len) +{ + uint16_t copy_len = ( + buffer_len < telemetry_characteristic_userdata.buffer_size ? + buffer_len : telemetry_characteristic_userdata.buffer_size + ); + + k_mutex_lock(&telemetry_characteristic_userdata.lock, K_FOREVER); + memcpy(telemetry_characteristic_userdata.buffer, buffer, copy_len); + telemetry_characteristic_userdata.value_size = copy_len; + k_mutex_unlock(&telemetry_characteristic_userdata.lock); +} + +static int +_start_advertising() +{ + return bt_le_adv_start( + &advertise_params, + advertise_data, sizeof(advertise_data) / sizeof(struct bt_data), + scan_data, sizeof(scan_data) / sizeof(struct bt_data) + ); +} + +static void +_connected_callback(struct bt_conn *connection, uint8_t error) +{ + if (error) { + return; + } + + /* Zephyr automatically stops advertising on a new connection */ +} + +static void +_disconnected_callback(struct bt_conn *connection, uint8_t reason) +{ + /* Resume advertising so we can be found again */ + _start_advertising(); +} + +static ssize_t +_generic_attribute_read( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + void *buffer, + uint16_t buffer_len, + uint16_t offset +) { + ble_attribute_userdata_t *data = attribute->user_data; + ssize_t read_len; + + /* Copy nothing on a read */ + if (offset >= data->value_size) { + return 0; + } + + /* Truncate reads if they would be too long */ + read_len = buffer_len; + if ((offset + read_len) > data->value_size) { + read_len = data->value_size - offset; + } + + k_mutex_lock(&data->lock, K_FOREVER); + memcpy(buffer, &((uint8_t *)data->buffer)[offset], read_len); + k_mutex_unlock(&data->lock); + + return read_len; +} + +static int _generic_attribute_write( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + const void *buffer, + uint16_t buffer_len, + uint16_t offset, + uint8_t flags +) { + ble_attribute_userdata_t *data = attribute->user_data; + ssize_t write_len; + + if (offset >= data->buffer_size) { + return 0; + } + + write_len = buffer_len; + if ((offset + write_len) > data->buffer_size) { + write_len = data->buffer_size - offset; + } + + k_mutex_lock(&data->lock, K_FOREVER); + memcpy(&((uint8_t *)data->buffer)[offset], buffer, write_len); + data->value_size = offset + write_len; + k_mutex_unlock(&data->lock); + + if (data == &input_characteristic_userdata) { + k_mutex_lock(&ble.lock, K_FOREVER); + if (ble.oninput_callback) { + ble.oninput_callback(data->buffer, data->value_size); + } + k_mutex_unlock(&ble.lock); + } + + return write_len; +} diff --git a/core/ble.h b/core/ble.h new file mode 100644 index 0000000..618074c --- /dev/null +++ b/core/ble.h @@ -0,0 +1,19 @@ +#ifndef _BLE_H_ +#define _BLE_H_ + +/** + * @brief Enable Bluetooth, setup default services, and begin advertising + */ +int ble_init(void); + +/** + * @brief Register a callback to occur when a new complete input is sent + */ +void ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buffer_len)); + +/** + * @brief Set a new value for telemetry data + */ +void ble_set_telemetry_data(const void *buffer, uint16_t buffer_len); + +#endif /* _BLE_H_ */ diff --git a/core/main.c b/core/main.c new file mode 100644 index 0000000..79385aa --- /dev/null +++ b/core/main.c @@ -0,0 +1,25 @@ +#include +#include + +#include + +#include "ble.h" + +static void oninput(const void *buffer, uint16_t buffer_len); + +int main(void) +{ + ble_init(); + ble_register_oninput_callback(oninput); + + while (1) { + k_sleep(K_MSEC(1000)); + k_yield(); + } +} + +static void +oninput(const void *buffer, uint16_t buffer_len) +{ + ble_set_telemetry_data(buffer, buffer_len); +} diff --git a/examples/fm25_kit/README.md b/examples/fm25_kit/README.md deleted file mode 100644 index a4c0cce..0000000 --- a/examples/fm25_kit/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Furry Migration 2025 Workshop Kit - -## Materials - -| Item | Link | Cost (USD) | -| --------------- | ------------------------------------------------------------------------ | ---------- | -| nRF52840 Dongle | https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle | $10 | diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..04f6fc9 --- /dev/null +++ b/prj.conf @@ -0,0 +1,8 @@ +CONFIG_BT=y +CONFIG_BT_MAX_CONN=1 +CONFIG_BT_HCI=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="FM Sample 1" +CONFIG_BT_GATT_DYNAMIC_DB=y + +CONFIG_DEBUG=y -- 2.56.0 From 9f005b547bfce998810994ddc71cc09cfae6265a Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 31 Aug 2025 22:03:41 -0500 Subject: [PATCH 3/4] Routine infrastructure --- CMakeLists.txt | 15 +++- core/ble.c | 195 ++++++++++++++++++++++++++++++++++++++-- core/ble.h | 14 ++- core/main.c | 17 +--- core/routine.c | 154 +++++++++++++++++++++++++++++++ core/routine.h | 36 ++++++++ includes/ble.h | 22 +++++ includes/routine.h | 47 ++++++++++ prj.conf | 2 + routines/input_echo.c | 44 +++++++++ routines/routine_list.c | 11 +++ 11 files changed, 527 insertions(+), 30 deletions(-) create mode 100644 core/routine.c create mode 100644 core/routine.h create mode 100644 includes/ble.h create mode 100644 includes/routine.h create mode 100644 routines/input_echo.c create mode 100644 routines/routine_list.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 997f7ea..9055fbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,5 +3,16 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(openrcore) -target_sources(app PRIVATE core/main.c) -target_sources(app PRIVATE core/ble.c) +target_include_directories(app PRIVATE .) + +target_sources(app PRIVATE + core/main.c + core/ble.c + core/routine.c +) + +# User routines to compile +target_sources(app PRIVATE + routines/routine_list.c + routines/input_echo.c +) diff --git a/core/ble.c b/core/ble.c index 4d8e99c..52ae0e1 100644 --- a/core/ble.c +++ b/core/ble.c @@ -9,7 +9,8 @@ #include #include -#include "ble.h" +#include "core/ble.h" +#include "core/routine.h" /** * @brief Begin BLE advertising with the default parameters @@ -35,7 +36,7 @@ static struct bt_conn_cb connection_callbacks = { /** Advertising data **/ /** Our name to advertise */ -#define BLE_NAME_DEFAULT ("FM25 Kit 1") +#define BLE_NAME_DEFAULT ("FM25 Kit x") /** Advertising/Scanning parameters */ static struct bt_le_adv_param advertise_params = { @@ -107,6 +108,11 @@ static struct bt_uuid_16 characteristic_description_uuid = { .val = BT_UUID_GATT_CUD_VAL, }; +static struct bt_uuid_16 characteristic_format_uuid = { + .uuid = { .type = BT_UUID_TYPE_16 }, + .val = BT_UUID_GATT_CPF_VAL, +}; + static struct bt_uuid_128 service_uuid = { .uuid = { .type = BT_UUID_TYPE_128 }, .val = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, @@ -122,7 +128,25 @@ static struct bt_uuid_128 telemetry_characteristic_uuid = { .val = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, }; +static struct bt_uuid_128 routine_list_characteristic_uuid = { + .uuid = { .type = BT_UUID_TYPE_128 }, + .val = { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 }, +}; + +static struct bt_uuid_128 current_routine_characteristic_uuid = { + .uuid = { .type = BT_UUID_TYPE_128 }, + .val = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }, +}; + /*** Characteristic Data ***/ +static struct bt_gatt_cpf string_format = { + .format = 0x19, /* UTF8 string */ + .exponent = 0, + .unit = 0, + .name_space = 0x01, /* Bluetooth SIG */ + .description = 0, +}; + typedef struct ble_attribute_userdata_struct { /** Buffer allocated for this attribute's data */ void *buffer; @@ -155,7 +179,7 @@ char input_characteristic_description[] = "Input Data"; /**** Telemetry Characteristic ****/ struct bt_gatt_chrc telemetry_characteristic = { - .uuid = (void *)&telemetry_characteristic_uuid, + .uuid = (struct bt_uuid *)&telemetry_characteristic_uuid, .properties = BT_GATT_CHRC_READ, }; /** Buffer for the contents of the Telemetry characteristic */ @@ -171,6 +195,45 @@ ble_attribute_userdata_t telemetry_characteristic_userdata = { char telemetry_characteristic_description[] = "Telemetry Data"; +/**** Routine List Characteristic ****/ +extern const routine_listing_t *routine_list; +struct bt_gatt_chrc routine_list_characteristic = { + .uuid = (struct bt_uuid *)&routine_list_characteristic_uuid, + .properties = BT_GATT_CHRC_READ, +}; +char routine_list_characteristic_buffer[512]; +ble_attribute_userdata_t routine_list_characterist_userdata = { + .buffer = routine_list_characteristic_buffer, + .buffer_size = sizeof(routine_list_characteristic_buffer), + .value_size = 0, +}; +char routine_list_characteristic_description[] = "Routine List"; + + + +/**** Current Routine Characteristic ****/ +struct bt_gatt_chrc current_routine_characteristic = { + .uuid = (struct bt_uuid *)¤t_routine_characteristic_uuid, + .properties = BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, +}; +static ssize_t _read_current_routine( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + void *buffer, + uint16_t buffer_len, + uint16_t offset +); +static ssize_t _write_current_routine( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + const void *buffer, + uint16_t buffer_len, + uint16_t offset, + uint8_t flags +); +char current_routine_characteristic_description[] = "Current Routine"; + + /*** Service Layout ***/ static struct bt_gatt_attr primary_service_attributes[] = { /* Service declaration */ @@ -200,6 +263,12 @@ static struct bt_gatt_attr primary_service_attributes[] = { .user_data = (void *)input_characteristic_description, .perm = BT_GATT_PERM_READ, }, + { + .uuid = (struct bt_uuid *)&characteristic_format_uuid, + .read = bt_gatt_attr_read_cpf, + .user_data = (void *)&string_format, + .perm = BT_GATT_PERM_READ, + }, /** Telemetry Data Characteristic **/ { .uuid = (struct bt_uuid *)&characteristic_declaration_uuid, @@ -218,7 +287,57 @@ static struct bt_gatt_attr primary_service_attributes[] = { .read = bt_gatt_attr_read_cud, .user_data = (void *)telemetry_characteristic_description, .perm = BT_GATT_PERM_READ, - } + }, + { + .uuid = (struct bt_uuid *)&characteristic_format_uuid, + .read = bt_gatt_attr_read_cpf, + .user_data = (void *)&string_format, + .perm = BT_GATT_PERM_READ, + }, + /** Routine List Characteristic **/ + { + .uuid = (struct bt_uuid *)&characteristic_declaration_uuid, + .read = bt_gatt_attr_read_chrc, + .user_data = (void *)&routine_list_characteristic, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&routine_list_characteristic_uuid, + .read = _generic_attribute_read, + .user_data = (void *)&routine_list_characterist_userdata, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&characteristic_description_uuid, + .read = bt_gatt_attr_read_cud, + .user_data = (void *)routine_list_characteristic_description, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)&characteristic_format_uuid, + .read = bt_gatt_attr_read_cpf, + .user_data = (void *)&string_format, + .perm = BT_GATT_PERM_READ, + }, + /** Current Routine Characteristic */ + { + .uuid = (struct bt_uuid *)&characteristic_declaration_uuid, + .read = bt_gatt_attr_read_chrc, + .user_data = (void *)¤t_routine_characteristic, + .perm = BT_GATT_PERM_READ, + }, + { + .uuid = (struct bt_uuid *)¤t_routine_characteristic_uuid, + .read = _read_current_routine, + .write = _write_current_routine, + .perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE + }, + { + .uuid = (struct bt_uuid *)&characteristic_description_uuid, + .read = bt_gatt_attr_read_cud, + .user_data = (void *)current_routine_characteristic_description, + .perm = BT_GATT_PERM_READ, + }, }; static struct bt_gatt_service primary_service = { @@ -231,6 +350,8 @@ static struct bt_gatt_service primary_service = { typedef struct ble_state_struct { /** Current callback for a complete input write */ void (*oninput_callback)(const void *buffer, uint16_t buffer_len); +#define BLE_FLAG_DISCONNECTED (0x01) + uint32_t flags; /** Lock for data in the BLE state */ struct k_mutex lock; } ble_state_t; @@ -257,6 +378,8 @@ ble_init() /* Initialize the service data */ k_mutex_init(&input_characteristic_userdata.lock); k_mutex_init(&telemetry_characteristic_userdata.lock); + routine_get_listing(routine_list_characteristic_buffer, sizeof(routine_list_characteristic_buffer)); + routine_list_characterist_userdata.value_size = strlen(routine_list_characteristic_buffer); ret = bt_gatt_service_register(&primary_service); if (ret) { @@ -268,6 +391,17 @@ ble_init() return 0; } +void +ble_tick() +{ + k_mutex_lock(&ble.lock, K_FOREVER); + if (ble.flags & BLE_FLAG_DISCONNECTED) { + _start_advertising(); + ble.flags &= ~BLE_FLAG_DISCONNECTED; + } + k_mutex_unlock(&ble.lock); +} + void ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buffer_len)) { @@ -276,6 +410,14 @@ ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buff k_mutex_unlock(&ble.lock); } +void +ble_clear_oninput_callback() +{ + k_mutex_lock(&ble.lock, K_FOREVER); + ble.oninput_callback = NULL; + k_mutex_unlock(&ble.lock); +} + void ble_set_telemetry_data(const void *buffer, uint16_t buffer_len) { @@ -314,7 +456,9 @@ static void _disconnected_callback(struct bt_conn *connection, uint8_t reason) { /* Resume advertising so we can be found again */ - _start_advertising(); + k_mutex_lock(&ble.lock, K_FOREVER); + ble.flags |= BLE_FLAG_DISCONNECTED; + k_mutex_unlock(&ble.lock); } static ssize_t @@ -346,7 +490,7 @@ _generic_attribute_read( return read_len; } -static int _generic_attribute_write( +static ssize_t _generic_attribute_write( struct bt_conn *connection, const struct bt_gatt_attr *attribute, const void *buffer, @@ -381,3 +525,42 @@ static int _generic_attribute_write( return write_len; } + +static ssize_t _read_current_routine( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + void *buffer, + uint16_t buffer_len, + uint16_t offset +) { + if ((offset != 0) || (buffer_len < sizeof(int8_t))) { + return (0); + } + + ((int8_t *)buffer)[0] = routine_get_current(); + + return (sizeof(int8_t)); +} + +static ssize_t _write_current_routine( + struct bt_conn *connection, + const struct bt_gatt_attr *attribute, + const void *buffer, + uint16_t buffer_len, + uint16_t offset, + uint8_t flags +) { + uint8_t index; + + if ((offset != 0) || (buffer_len < sizeof(uint8_t))) { + return (0); + } + + index = ((uint8_t *)buffer)[0]; + + if (routine_select(index)) { + return (0); + } else { + return (sizeof(uint8_t)); + } +} diff --git a/core/ble.h b/core/ble.h index 618074c..33c8ea2 100644 --- a/core/ble.h +++ b/core/ble.h @@ -1,19 +1,15 @@ #ifndef _BLE_H_ #define _BLE_H_ +#include + +#include "includes/ble.h" + /** * @brief Enable Bluetooth, setup default services, and begin advertising */ int ble_init(void); -/** - * @brief Register a callback to occur when a new complete input is sent - */ -void ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buffer_len)); - -/** - * @brief Set a new value for telemetry data - */ -void ble_set_telemetry_data(const void *buffer, uint16_t buffer_len); +void ble_tick(void); #endif /* _BLE_H_ */ diff --git a/core/main.c b/core/main.c index 79385aa..dde5cfd 100644 --- a/core/main.c +++ b/core/main.c @@ -3,23 +3,14 @@ #include -#include "ble.h" +#include "core/ble.h" +#include "core/routine.h" -static void oninput(const void *buffer, uint16_t buffer_len); int main(void) { + routine_init(); ble_init(); - ble_register_oninput_callback(oninput); - while (1) { - k_sleep(K_MSEC(1000)); - k_yield(); - } -} - -static void -oninput(const void *buffer, uint16_t buffer_len) -{ - ble_set_telemetry_data(buffer, buffer_len); + routine_exec(); } diff --git a/core/routine.c b/core/routine.c new file mode 100644 index 0000000..9ed21ca --- /dev/null +++ b/core/routine.c @@ -0,0 +1,154 @@ +#include +#include + +#include + +#include "core/ble.h" +#include "core/routine.h" + +extern const routine_listing_t *routine_list; + +typedef struct routine_state_struct { + uint8_t routine_count; + /** Index of the current routine */ + int8_t current_routine; + /** Index of the next routine */ + int8_t next_routine; + /** Return status of the last routine to complete execution */ + routine_return_e last_routine_ret; + /** Routine status */ + routine_status_e status; + /** Lock for the */ + struct k_mutex lock; +} routine_state_t; + +static routine_state_t routine_state = { + .routine_count = 0, + .current_routine = ROUTINE_INDEX_NONE, + .next_routine = ROUTINE_INDEX_NONE, +}; + +void +routine_init() +{ + k_mutex_init(&routine_state.lock); + + while (1) { + const routine_listing_t *listing = &routine_list[routine_state.routine_count]; + + if (listing->entry == NULL) { + break; + } + + routine_state.routine_count++; + } +} + +void +routine_exec() +{ + while (1) { + routine_return_e ret; + + ble_tick(); + + k_mutex_lock(&routine_state.lock, K_FOREVER); + + if (routine_state.next_routine == ROUTINE_INDEX_NONE) { + /* nothing to do */ + k_mutex_unlock(&routine_state.lock); + k_yield(); + continue; + } + + /* something to do! */ + routine_state.current_routine = routine_state.next_routine; + routine_state.next_routine = ROUTINE_INDEX_NONE; + routine_state.status = ROUTINE_STATUS_RUNNING; + + k_mutex_unlock(&routine_state.lock); + + + ret = routine_list[routine_state.current_routine].entry(); + + k_mutex_lock(&routine_state.lock, K_FOREVER); + routine_state.last_routine_ret = ret; + routine_state.current_routine = ROUTINE_INDEX_NONE; + routine_state.status = ROUTINE_STATUS_STOPPED; + k_mutex_unlock(&routine_state.lock); + } +} + +int +routine_select(uint8_t index) +{ + if (index >= routine_state.routine_count) { + return (-1); + } + + k_mutex_lock(&routine_state.lock, K_FOREVER); + routine_state.next_routine = index; + routine_state.status = ROUTINE_STATUS_STOPPING; + k_mutex_unlock(&routine_state.lock); + + return (0); +} + +void +routine_get_listing(char *buf, uint16_t buf_len) +{ + int written; + uint16_t pos = 0; + + written = snprintf(&buf[pos], buf_len - pos, "Routines (%d):\r\n", routine_state.routine_count); + pos += written; + + for (int i = 0; i < routine_state.routine_count; i++) { + written = snprintf( + &buf[pos], buf_len - pos, + "%d: %s\r\n", + i, routine_list[i].name + ); + pos += written; + } + + /* walk back trailing \r\n */ + buf[pos - 2] = '\0'; +} + +int8_t +routine_get_current() +{ + int8_t ret; + + k_mutex_lock(&routine_state.lock, K_FOREVER); + ret = routine_state.current_routine; + k_mutex_unlock(&routine_state.lock); + + return (ret); +} + +routine_status_e +routine_status() +{ + routine_status_e ret; + + k_mutex_lock(&routine_state.lock, K_FOREVER); + ret = routine_state.status; + k_mutex_unlock(&routine_state.lock); + + return (ret); +} + +void +routine_delay(uint32_t milliseconds) +{ + k_sleep(K_MSEC(milliseconds)); +} + +void +routine_yield() +{ + ble_tick(); + k_yield(); +} diff --git a/core/routine.h b/core/routine.h new file mode 100644 index 0000000..20ba588 --- /dev/null +++ b/core/routine.h @@ -0,0 +1,36 @@ +#ifndef _ROUTINE_H_ +#define _ROUTINE_H_ + +#include +#include + +#include "includes/routine.h" + +#define ROUTINE_INDEX_NONE (-1) + +/** + * @brief Initialize the routine state + */ +void routine_init(void); + +/** + * @brief Execute the routine, intended to be called from main. Does not return. + */ +void routine_exec(void); + +/** + * @brief Select a new routine to be active + */ +int routine_select(uint8_t index); + +/** + * @brief Fill out a buffer with a human readable list of routines + */ +void routine_get_listing(char *buf, uint16_t buf_len); + +/** + * @brief Get the index of the current running routine + */ +int8_t routine_get_current(void); + +#endif /* _ROUTINE_H_ */ diff --git a/includes/ble.h b/includes/ble.h new file mode 100644 index 0000000..3923e1b --- /dev/null +++ b/includes/ble.h @@ -0,0 +1,22 @@ +#ifndef _INCLUDES_BLE_H_ +#define _INCLUDES_BLE_H_ + +#include + +/** + * @brief Register a callback to occur when a new complete input is sent + */ +void ble_register_oninput_callback(void (*callback)(const void *buffer, uint16_t buffer_len)); + +/** + * @brief Clear the on-input callback registered with + * ble_register_oninput_callback + */ +void ble_clear_oninput_callback(void); + +/** + * @brief Set a new value for telemetry data + */ +void ble_set_telemetry_data(const void *buffer, uint16_t buffer_len); + +#endif /* _INCLUDES_BLE_H_ */ diff --git a/includes/routine.h b/includes/routine.h new file mode 100644 index 0000000..7e2f36f --- /dev/null +++ b/includes/routine.h @@ -0,0 +1,47 @@ +#ifndef _INCLUDES_ROUTINE_H_ +#define _INCLUDES_ROUTINE_H_ + +#include + +typedef enum routine_return_enum { + /** Routine executed successfully */ + ROUTINE_RETURN_SUCCESS = 0, + /** Generic error occured during routine */ + ROUTINE_RETURN_FAILURE, +} routine_return_e; + +typedef routine_return_e (*routine_t)(void); + +typedef struct routine_listing_struct { + /** Method that will be called to enter the routine */ + routine_t entry; + /** Name that will be listed for the routine */ + const char *name; +} routine_listing_t; + +typedef enum routine_status_enum { + /** no routine is currently running */ + ROUTINE_STATUS_STOPPED = 0, + /** a routine is running */ + ROUTINE_STATUS_RUNNING, + /** a routine is running but has been requested to stop*/ + ROUTINE_STATUS_STOPPING, +} routine_status_e; + +/** + * @brief Get the current routine status + */ +routine_status_e routine_status(void); + +/** + * @brief Delay the routine for the specified number of milliseconds + */ +void routine_delay(uint32_t milliseconds); + +/** + * @brief Function that a routine should call while running in a loop to allow + * for the rest of the system to properly execute + */ +void routine_yield(void); + +#endif /* _INCLUDE_ROUTINE_H_ */ diff --git a/prj.conf b/prj.conf index 04f6fc9..0d402a5 100644 --- a/prj.conf +++ b/prj.conf @@ -5,4 +5,6 @@ CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="FM Sample 1" CONFIG_BT_GATT_DYNAMIC_DB=y +CONFIG_HEAP_MEM_POOL_SIZE=1024 + CONFIG_DEBUG=y diff --git a/routines/input_echo.c b/routines/input_echo.c new file mode 100644 index 0000000..bef0cf4 --- /dev/null +++ b/routines/input_echo.c @@ -0,0 +1,44 @@ +#include +#include + +#include + +#include "includes/routine.h" +#include "includes/ble.h" + +static void ble_input_callback(const void *buffer, uint16_t buffer_len); + +routine_return_e +input_echo() +{ + ble_register_oninput_callback(ble_input_callback); + + while (routine_status() == ROUTINE_STATUS_RUNNING) { + routine_yield(); + } + + ble_clear_oninput_callback(); + return (ROUTINE_RETURN_SUCCESS); +} + +static void +ble_input_callback(const void *buffer, uint16_t buffer_len) +{ + size_t echo_length = snprintk( + NULL, 0, "Echo: %.*s", + buffer_len, (const char *)buffer + ); + char *echo_buf = k_malloc(echo_length + 1); + + if (echo_buf == NULL) { + ble_set_telemetry_data("Alloc failure", 13); + return; + } + + snprintk( + echo_buf, echo_length + 1, "Echo: %.*s", + buffer_len, (const char *)buffer + ); + ble_set_telemetry_data(echo_buf, echo_length); + k_free(echo_buf); +} diff --git a/routines/routine_list.c b/routines/routine_list.c new file mode 100644 index 0000000..fab748c --- /dev/null +++ b/routines/routine_list.c @@ -0,0 +1,11 @@ +#include + +#include "core/routine.h" + +/** Extern all of your routines here so they can be included in the list **/ +extern routine_return_e input_echo(); + +const routine_listing_t routine_list[] = { + { .entry = input_echo, .name = "Input Echo" }, + { NULL, NULL }, +}; -- 2.56.0 From 8f0f0a053541c0835a7eb61089627b1fca0fa05d Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 8 Sep 2025 15:38:08 -0500 Subject: [PATCH 4/4] Add basic ADC support Planning on abstracting this behind hardware setup APIs, but this works for a simple demo until then --- CMakeLists.txt | 2 + boards/nrf52840dongle_nrf52840.overlay | 19 +++++++ core/hardware/adc.c | 75 ++++++++++++++++++++++++++ core/hardware/adc.h | 11 ++++ core/main.c | 2 + includes/hardware/adc.h | 10 ++++ prj.conf | 3 ++ routines/photosensor.c | 27 ++++++++++ routines/routine_list.c | 2 + 9 files changed, 151 insertions(+) create mode 100644 boards/nrf52840dongle_nrf52840.overlay create mode 100644 core/hardware/adc.c create mode 100644 core/hardware/adc.h create mode 100644 includes/hardware/adc.h create mode 100644 routines/photosensor.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9055fbb..8966607 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,10 +9,12 @@ target_sources(app PRIVATE core/main.c core/ble.c core/routine.c + core/hardware/adc.c ) # User routines to compile target_sources(app PRIVATE routines/routine_list.c routines/input_echo.c + routines/photosensor.c ) diff --git a/boards/nrf52840dongle_nrf52840.overlay b/boards/nrf52840dongle_nrf52840.overlay new file mode 100644 index 0000000..a0fa543 --- /dev/null +++ b/boards/nrf52840dongle_nrf52840.overlay @@ -0,0 +1,19 @@ +/ { + zephyr,user { + io-channels = <&adc 0>; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P0.02 */ + zephyr,resolution = <12>; + }; +}; diff --git a/core/hardware/adc.c b/core/hardware/adc.c new file mode 100644 index 0000000..9042a77 --- /dev/null +++ b/core/hardware/adc.c @@ -0,0 +1,75 @@ +#include +#include +#include + +#include +#include +#include +#include + +/** Setup code referenced from Zephyr ADC example **/ +#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ + !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) +#error "No suitable devicetree overlay specified" +#endif + +#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ + ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA) +}; + +static int setup = 0; + +int +adc_init() +{ + for (int i = 0; i < ARRAY_SIZE(adc_channels); i++) { + int err; + const struct adc_dt_spec *chan = &adc_channels[i]; + + if (!adc_is_ready_dt(chan)) { + return (-1); + } + + err = adc_channel_setup_dt(chan); + if (err) { + return (err); + } + } + + setup = 1; + return (0); +} + +int +adc_get_mv(int channel) +{ + uint16_t val_raw; + int32_t val_mv; + struct adc_sequence seq = { + .buffer = &val_raw, + .buffer_size = sizeof(val_raw) + }; + const struct adc_dt_spec *chan; + + if ((channel >= ARRAY_SIZE(adc_channels)) || + (!setup)) { + return (INT32_MIN); + } + + chan = &adc_channels[channel]; + adc_sequence_init_dt(chan, &seq); + + if (adc_read_dt(chan, &seq) < 0) { + return (INT32_MIN); + } + + val_mv = (int32_t)val_raw; + if (adc_raw_to_millivolts_dt(chan, &val_mv)) { + return (INT32_MIN); + } + + return (val_mv); +} diff --git a/core/hardware/adc.h b/core/hardware/adc.h new file mode 100644 index 0000000..dad8a74 --- /dev/null +++ b/core/hardware/adc.h @@ -0,0 +1,11 @@ +#ifndef _ADC_H_ +#define _ADC_H_ + +#include "includes/hardware/adc.h" + +/** + * @brief Initialize ADC hardware + */ +int adc_init(void); + +#endif /* _ADC_H_ */ diff --git a/core/main.c b/core/main.c index dde5cfd..c99500d 100644 --- a/core/main.c +++ b/core/main.c @@ -5,10 +5,12 @@ #include "core/ble.h" #include "core/routine.h" +#include "core/hardware/adc.h" int main(void) { + adc_init(); routine_init(); ble_init(); diff --git a/includes/hardware/adc.h b/includes/hardware/adc.h new file mode 100644 index 0000000..859fc1b --- /dev/null +++ b/includes/hardware/adc.h @@ -0,0 +1,10 @@ +#ifndef _INCLUDES_HARDWARE_ADC_H_ +#define _INCLUDES_HARDWARE_ADC_H_ + +/** + * @brief Read the ADC value from the given channel index. See "adc-channels" in + * your board overlay file. + */ +int adc_get_mv(int channel); + +#endif /* _INCLUDES_HARDWARE_ADC_H_ */ diff --git a/prj.conf b/prj.conf index 0d402a5..1b2d9ed 100644 --- a/prj.conf +++ b/prj.conf @@ -7,4 +7,7 @@ CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_HEAP_MEM_POOL_SIZE=1024 +CONFIG_ADC=y + CONFIG_DEBUG=y +CONFIG_DEBUG_OPTIMIZATIONS=y diff --git a/routines/photosensor.c b/routines/photosensor.c new file mode 100644 index 0000000..238248c --- /dev/null +++ b/routines/photosensor.c @@ -0,0 +1,27 @@ +#include + +#include "includes/ble.h" +#include "includes/routine.h" +#include "includes/hardware/adc.h" + +routine_return_e +photosensor() +{ + while (routine_status() == ROUTINE_STATUS_RUNNING) { + int32_t adc_mv = adc_get_mv(0); + char buf[16]; + size_t print_len; + + if (adc_mv == INT32_MIN) { + ble_set_telemetry_data("Reading failed", 14); + } else { + print_len = snprintk(buf, sizeof(buf), "%dmV", adc_mv); + ble_set_telemetry_data(buf, print_len); + } + + routine_delay(500); + routine_yield(); + } + + return ROUTINE_RETURN_SUCCESS; +} diff --git a/routines/routine_list.c b/routines/routine_list.c index fab748c..b491ea3 100644 --- a/routines/routine_list.c +++ b/routines/routine_list.c @@ -4,8 +4,10 @@ /** Extern all of your routines here so they can be included in the list **/ extern routine_return_e input_echo(); +extern routine_return_e photosensor(); const routine_listing_t routine_list[] = { { .entry = input_echo, .name = "Input Echo" }, + { .entry = photosensor, .name = "Photosensor" }, { NULL, NULL }, }; -- 2.56.0