FM25 Demo Code #1
19 changed files with 1112 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build*/
|
||||
18
.vscode/c_cpp_properties.json
vendored
Normal file
18
.vscode/c_cpp_properties.json
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
19
boards/nrf52840dongle_nrf52840.overlay
Normal file
19
boards/nrf52840dongle_nrf52840.overlay
Normal file
|
|
@ -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 = <ADC_ACQ_TIME_DEFAULT>;
|
||||
zephyr,input-positive = <NRF_SAADC_AIN0>; /* P0.02 */
|
||||
zephyr,resolution = <12>;
|
||||
};
|
||||
};
|
||||
566
core/ble.c
Normal file
566
core/ble.c
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/gap.h>
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/util_macro.h>
|
||||
#include <zephyr/types.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
}
|
||||
15
core/ble.h
Normal file
15
core/ble.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef _BLE_H_
|
||||
#define _BLE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "includes/ble.h"
|
||||
|
||||
/**
|
||||
* @brief Enable Bluetooth, setup default services, and begin advertising
|
||||
*/
|
||||
int ble_init(void);
|
||||
|
||||
void ble_tick(void);
|
||||
|
||||
#endif /* _BLE_H_ */
|
||||
75
core/hardware/adc.c
Normal file
75
core/hardware/adc.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/adc.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
/** 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);
|
||||
}
|
||||
11
core/hardware/adc.h
Normal file
11
core/hardware/adc.h
Normal file
|
|
@ -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_ */
|
||||
18
core/main.c
Normal file
18
core/main.c
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include "core/ble.h"
|
||||
#include "core/routine.h"
|
||||
#include "core/hardware/adc.h"
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
adc_init();
|
||||
routine_init();
|
||||
ble_init();
|
||||
|
||||
routine_exec();
|
||||
}
|
||||
154
core/routine.c
Normal file
154
core/routine.c
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
36
core/routine.h
Normal file
36
core/routine.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef _ROUTINE_H_
|
||||
#define _ROUTINE_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
22
includes/ble.h
Normal file
22
includes/ble.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef _INCLUDES_BLE_H_
|
||||
#define _INCLUDES_BLE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @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_ */
|
||||
10
includes/hardware/adc.h
Normal file
10
includes/hardware/adc.h
Normal file
|
|
@ -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_ */
|
||||
47
includes/routine.h
Normal file
47
includes/routine.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef _INCLUDES_ROUTINE_H_
|
||||
#define _INCLUDES_ROUTINE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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_ */
|
||||
13
prj.conf
Normal file
13
prj.conf
Normal file
|
|
@ -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
|
||||
44
routines/input_echo.c
Normal file
44
routines/input_echo.c
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
27
routines/photosensor.c
Normal file
27
routines/photosensor.c
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#include <zephyr/kernel.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
13
routines/routine_list.c
Normal file
13
routines/routine_list.c
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include <stddef.h>
|
||||
|
||||
#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 },
|
||||
};
|
||||
Loading…
Reference in a new issue