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..8966607 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(openrcore) + +target_include_directories(app PRIVATE .) + +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/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/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/ble.c b/core/ble.c new file mode 100644 index 0000000..52ae0e1 --- /dev/null +++ b/core/ble.c @@ -0,0 +1,566 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "core/ble.h" +#include "core/routine.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 x") + +/** 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_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 }, +}; + +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 }, +}; + +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; + /** 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 = (struct bt_uuid *)&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"; + + +/**** 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 */ + { + .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, + }, + { + .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, + .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, + }, + { + .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 = { + .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); +#define BLE_FLAG_DISCONNECTED (0x01) + uint32_t flags; + /** 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); + 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) { + return ret; + } + + _start_advertising(); + + 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)) +{ + k_mutex_lock(&ble.lock, K_FOREVER); + ble.oninput_callback = callback; + 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) +{ + 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 */ + k_mutex_lock(&ble.lock, K_FOREVER); + ble.flags |= BLE_FLAG_DISCONNECTED; + k_mutex_unlock(&ble.lock); +} + +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 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 +) { + 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; +} + +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 new file mode 100644 index 0000000..33c8ea2 --- /dev/null +++ b/core/ble.h @@ -0,0 +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); + +void ble_tick(void); + +#endif /* _BLE_H_ */ 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 new file mode 100644 index 0000000..c99500d --- /dev/null +++ b/core/main.c @@ -0,0 +1,18 @@ +#include +#include + +#include + +#include "core/ble.h" +#include "core/routine.h" +#include "core/hardware/adc.h" + + +int main(void) +{ + adc_init(); + routine_init(); + ble_init(); + + 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/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/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 new file mode 100644 index 0000000..1b2d9ed --- /dev/null +++ b/prj.conf @@ -0,0 +1,13 @@ +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_HEAP_MEM_POOL_SIZE=1024 + +CONFIG_ADC=y + +CONFIG_DEBUG=y +CONFIG_DEBUG_OPTIMIZATIONS=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/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 new file mode 100644 index 0000000..b491ea3 --- /dev/null +++ b/routines/routine_list.c @@ -0,0 +1,13 @@ +#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(); +extern routine_return_e photosensor(); + +const routine_listing_t routine_list[] = { + { .entry = input_echo, .name = "Input Echo" }, + { .entry = photosensor, .name = "Photosensor" }, + { NULL, NULL }, +};