From 64d441981ce1bbe61c5593a8227fbc201d47fa44 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Thu, 15 Jul 2021 17:51:25 -0400 Subject: [PATCH 01/11] Start of V0.17 --- CHANGELOG.md | 2 ++ KBikeBLE.ino | 2 +- TODO.md | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d188079..188e117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,3 +52,5 @@ - Adafruit's Bluefruit Connect app works well. Be sure that the Uart function is set to include an end-of-line. The system won't care whether or is used. - Calibration similar to Keiser's procedure, initiated through the *calibrate* command and with step-by-step prompts for using the calibration tool. - Calibration will not yet survive a reset. Saving parameters to nonvolatile memory (LittleFS) is next. +- V0.17 + diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 6621284..0e1991a 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -1,5 +1,5 @@ /* Bluetooth console for Keiser M3 ********************************** - V0.16 + V0.17 *********************************************************************/ #include diff --git a/TODO.md b/TODO.md index 31dd3e7..20c44d8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,8 @@ ## TODO - Calibration items - Save calibration in littleFS - - Automated sensor calibration, equivalent to Keiser's procedure - Console access - - Bluetooth console instead of USB serial - - Does USB or BLEUart console preclude need for bike-based cal triggers like Keiser's? + - Option to use conventional USB serial instead of the spiffier BLE Uart service - Documentation - More comprehensive writeup of calibration - Tutorial how-to-build for less experienced folks From cda62714431345ecc1219ef935395d3d2e949b75 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Mon, 19 Jul 2021 21:30:24 -0400 Subject: [PATCH 02/11] See CHANGELOG --- .gitignore | 1 + CHANGELOG.md | 5 +- KBikeBLE.ino | 300 +++++++++++++++++++++++++----------------------- TODO.md | 15 ++- globals.h | 79 +++++++++++++ param_store.cpp | 56 +++++++++ param_store.h | 36 ++++++ 7 files changed, 340 insertions(+), 152 deletions(-) create mode 100644 globals.h create mode 100644 param_store.cpp create mode 100644 param_store.h diff --git a/.gitignore b/.gitignore index 467e2c8..a4adacf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +plans*.h *ino- .vscode/* !.vscode/settings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 188e117..5c6d43f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,4 +53,7 @@ - Calibration similar to Keiser's procedure, initiated through the *calibrate* command and with step-by-step prompts for using the calibration tool. - Calibration will not yet survive a reset. Saving parameters to nonvolatile memory (LittleFS) is next. - V0.17 - + - Calibration is now saved to nonvolatile memory (via LittleFS). If cal files aren't present, they're created from the defaults in calibration.h. + - Little U8G2 log screen at startup to show parameters read from or written to nonvolatile memory + - The Bluetooth command line interface now allows calibration values to be re-read from, or written to, nonvolatile memory. + - Moved globals to globals.h - start of some code cleanup diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 0e1991a..3aae7f2 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -1,6 +1,7 @@ /* Bluetooth console for Keiser M3 ********************************** V0.17 *********************************************************************/ +#define VERSION "0.17" #include #include // nrf52 built-in bluetooth @@ -17,6 +18,7 @@ #include "power_gear_tables.h" #include "calibration.h" #include "serial_commands.h" +#include "param_store.h" /********************************************************************** * Optional functions and debugging @@ -61,6 +63,12 @@ uint8_t stepnum = 0; #define DWAIT() #endif +/************************************************************************ + * Globals + ************************************************************************/ + +#include "globals.h" + /************************************************************************ * Miscellany ************************************************************************/ @@ -81,90 +89,99 @@ bool gear_display = GEAR_DISPLAY; /******************************************************************************** Globals ********************************************************************************/ -// BLE data blocks addressable as bytes for flags and words for data -union ble_data_block -{ // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) - uint8_t bytes[2]; - uint16_t words[16]; -}; -union ble_data_block bike_data; // For the Bike Data Characteristic (FiTness Machine Service) -union ble_data_block power_data; // For the Cycling Power Measurement Characteristic (Cycling Power Service) - -// Globals. Variable accessed in multiple places are declared here. -// Those used only in specific functions are declared within or nearby. - -float cadence; // Pedal cadence, determined from crank event timing -float power; // Power, calculated from resistance and cadence to match Keiser's estimates -float bspeed; // Bike speed, required by FTMS, to be estimated from power and cadence. A real estimate is TO DO -float inst_resistance; // Normalized resistance reading, determined from the eddy current brake magnet position -float resistance; // Normalized resistance used in power calculations (can be filtered) -float disp_resistance; // Resistance valued that's displayed (can be filtered) -uint32_t raw_resistance; // Raw resistance measurement from the ADC. Global because it's reported in the serial monitor -float res_offset; // Cal fators - raw_resistance to normalized resistance -float res_factor; -bool serial_present = false; -//float resistance_sq; // Used in both gear and power calcs if not using the lookup table -uint8_t gear; // Gear number: Index into power tables and, optionally, displayed -#if (POWERSAVE > 0) && (POWERSAVE != 1) -bool suspended; // Set to true when suspending the main loop -#endif - -float batt_mvolts; // Battery voltage -uint8_t batt_pct; // Battery percentage charge -bool batt_low; // Battery low - -#define TICK_INTERVAL 500 // ms -uint8_t ticker = 0; // Ticker for the main loop scheduler - inits to zero, also is reset under certain circumstances - - -volatile float inst_cadence = 0; // Cadence calculated in the crank ISR -volatile uint16_t crank_count = 0; // Cumulative crank rotations - set by the crank sensor ISR, reported by CPS and used to determine cadence -volatile uint32_t crank_event_time = 0; // Set to the most recent crank event time by the crank sensor ISR [ms] -//volatile uint32_t last_change_time; // Used in the crank sensor ISR for sensor debounce [ms]. Initialized to millis() in Setup(). -volatile bool new_crank_event = false; // Set by the crank sensor ISR; cleared by the main loop -volatile uint16_t crank_ticks; // 1/1024 sec per tick crank clock, for Cycling Power Measurement [ticks] +// // BLE data blocks addressable as bytes for flags and words for data +// union ble_data_block +// { // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) +// uint8_t bytes[2]; +// uint16_t words[16]; +// }; +// union ble_data_block bike_data; // For the Bike Data Characteristic (FiTness Machine Service) +// union ble_data_block power_data; // For the Cycling Power Measurement Characteristic (Cycling Power Service) + +// // Globals. Variable accessed in multiple places are declared here. +// // Those used only in specific functions are declared within or nearby. + +// float cadence; // Pedal cadence, determined from crank event timing +// float power; // Power, calculated from resistance and cadence to match Keiser's estimates +// float bspeed; // Bike speed, required by FTMS, to be estimated from power and cadence. A real estimate is TO DO +// float inst_resistance; // Normalized resistance reading, determined from the eddy current brake magnet position +// float resistance; // Normalized resistance used in power calculations (can be filtered) +// float disp_resistance; // Resistance valued that's displayed (can be filtered) +// uint32_t raw_resistance; // Raw resistance measurement from the ADC. Global because it's reported in the serial monitor +// float res_offset; // Cal fators - raw_resistance to normalized resistance +// float res_factor; +// bool serial_present = false; +// //float resistance_sq; // Used in both gear and power calcs if not using the lookup table +// uint8_t gear; // Gear number: Index into power tables and, optionally, displayed +// #if (POWERSAVE > 0) && (POWERSAVE != 1) +// bool suspended; // Set to true when suspending the main loop +// #endif + +// float batt_mvolts; // Battery voltage +// uint8_t batt_pct; // Battery percentage charge +// bool batt_low; // Battery low + +// #define TICK_INTERVAL 500 // ms +// uint8_t ticker = 0; // Ticker for the main loop scheduler - inits to zero, also is reset under certain circumstances + + +// volatile float inst_cadence = 0; // Cadence calculated in the crank ISR +// volatile uint16_t crank_count = 0; // Cumulative crank rotations - set by the crank sensor ISR, reported by CPS and used to determine cadence +// volatile uint32_t crank_event_time = 0; // Set to the most recent crank event time by the crank sensor ISR [ms] +// //volatile uint32_t last_change_time; // Used in the crank sensor ISR for sensor debounce [ms]. Initialized to millis() in Setup(). +// volatile bool new_crank_event = false; // Set by the crank sensor ISR; cleared by the main loop +// volatile uint16_t crank_ticks; // 1/1024 sec per tick crank clock, for Cycling Power Measurement [ticks] + +// //uint32_t prior_event_time = 0; // Used in the main loop to hold the time of the last reported crank event [ms] + +// bool ftm_active = true; // Once a client connects with either service, we stop updating the other. +// bool cp_active = true; + +// uint8_t display_state = 2; // Display state: 0 = off, 1 = dim, 2 = full on + +// // U8G2 display instance. What goes here depends upon the specific display and interface. See U8G2 examples. +// U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R1, /* reset=*/U8X8_PIN_NONE); // 128 x 64 SH1106 display on hardware I2C + +// // FTMS Service Definitions +// BLEService svc_ftms = BLEService(0x1826); // FiTness machine service +// BLECharacteristic char_ftm_feature = BLECharacteristic(0x2ACC); // FiTness machine Feature characteristic +// BLECharacteristic char_bike_data = BLECharacteristic(0x2AD2); // Indoor Bike Data characteristic + +// // CPS Service Definitions +// BLEService svc_cps = BLEService(0x1818); // Cycling Power Service +// BLECharacteristic char_cp_feature = BLECharacteristic(0x2A65); // Cycling Power Feature +// BLECharacteristic char_cp_measurement = BLECharacteristic(0x2A63); // Cycling Power Measurement +// BLECharacteristic char_sensor_loc = BLECharacteristic(0x2A5D); // Sensor Location + +// BLEDis bledis; // DIS (Device Information Service) helper class instance +// #ifdef BLEBAS +// BLEBas blebas; // BAS (Battery Service) helper class instance +// #endif +// #ifdef BLEUART +// BLEUart bleuart; // UART over BLE +// #endif -//uint32_t prior_event_time = 0; // Used in the main loop to hold the time of the last reported crank event [ms] - -bool ftm_active = true; // Once a client connects with either service, we stop updating the other. -bool cp_active = true; - -uint8_t display_state = 2; // Display state: 0 = off, 1 = dim, 2 = full on - -// U8G2 display instance. What goes here depends upon the specific display and interface. See U8G2 examples. -U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R1, /* reset=*/U8X8_PIN_NONE); // 128 x 64 SH1106 display on hardware I2C -//U8G2_SH1106_128X64_WINSTAR_F_HW_I2C display(U8G2_R1, /* reset=*/ U8X8_PIN_NONE); -//U8G2_SH1106_128X64_VCOMH0_F_HW_I2C display(U8G2_R1, /* reset=*/ U8X8_PIN_NONE); // Provides a wider setContrast() range but at the expense of uniformity when dimmed - -// FTMS Service Definitions -BLEService svc_ftms = BLEService(0x1826); // FiTness machine service -BLECharacteristic char_ftm_feature = BLECharacteristic(0x2ACC); // FiTness machine Feature characteristic -BLECharacteristic char_bike_data = BLECharacteristic(0x2AD2); // Indoor Bike Data characteristic - -// CPS Service Definitions -BLEService svc_cps = BLEService(0x1818); // Cycling Power Service -BLECharacteristic char_cp_feature = BLECharacteristic(0x2A65); // Cycling Power Feature -BLECharacteristic char_cp_measurement = BLECharacteristic(0x2A63); // Cycling Power Measurement -BLECharacteristic char_sensor_loc = BLECharacteristic(0x2A5D); // Sensor Location - -BLEDis bledis; // DIS (Device Information Service) helper class instance -#ifdef BLEBAS -BLEBas blebas; // BAS (Battery Service) helper class instance -#endif -#ifdef BLEUART -BLEUart bleuart; // UART over BLE -#endif /********************************************************************************* Display code **********************************************************************************/ +// Log used at startup +#define U8LOG_WIDTH 16 // 64/4 +#define U8LOG_HEIGHT 21 // 128/6 +uint8_t u8log_buffer[U8LOG_WIDTH * U8LOG_HEIGHT]; +U8G2LOG displog; + void display_setup(void) { display.begin(); display.setContrast(CONTRAST_FULL); //display.enableUTF8Print(); // Can leave this out if using no symbols display.setFontMode(1); // "Transparent": Character background not drawn (since we clear the display anyway) + + displog.begin(display, U8LOG_WIDTH, U8LOG_HEIGHT, u8log_buffer); + displog.setLineHeightOffset(0); + displog.setRedrawMode(0); } void right_just(uint16_t number, int x, int y, int width) @@ -624,51 +641,6 @@ void format_power_data(void) interrupts(); } -/********************************************************************************* - --- OLD --- - ISR for crank sensor events. Triggerd on any change. - To keep this short, it simply identifies a crank event as sensor active (0) when it's been inactive (1) - for at least MIN_INACTIVE ms. Sets the new_crank_event flag, increments the crank event count, and records the crank_event_time. - The main loop handles calculating the cadence, formatting of crank/cadence data and doing BLE notify. -********************************************************************************** - -uint8_t prev_crank_state = 0b10; -uint32_t last_event_time = 0; -const uint32_t MIN_INACTIVE = 100; // milliseconds - -void crank_callback() -{ - uint32_t now = millis(); - uint32_t dt; - uint8_t state = prev_crank_state | digitalRead(CRANK_PIN); // Yields a combined state between 0b00 and 0b11 - - if (suspended) resume(); - - switch (state) { - case 0b00 : // Was low (active) and still low - spurious/noise. - //prev_crank_state = 0b00; - break; - case 0b01 : // Was low (active) and now high - nothing to do except note the event. - prev_crank_state = 0b10; // previous is current shifted left - break; - case 0b10 : // Was high (inactive) and now low (active) - depends upon whether inactive long enough. - dt = now - crank_event_time; // ms since last true leading edge - if (dt > MIN_INACTIVE) { // True crank sensor leading edge. - crank_count++; // Accumulated crank rotations - used by Cycling Power Measurement and by the main loop to get cadence - crank_ticks += min(dt * 1024 / 1000, 0xFFFF); // Crank event clock in 1/1024 sec ticks - used by Cycling Power Measurement - new_crank_event = true; // This tells the main loop that there is new crank data - crank_event_time = now; - inst_cadence = 60000 / (crank_event_time - prior_event_time); - } - prev_crank_state = 0b00; - break; - case 0b11 : // Was high (inactive) and still high - spurious/noise. - break; - } - last_change_time = now; -} -*/ - /********************************************************************************************* Simplified ISR for crank sensor events. Triggerd on the pin tranisitioning low (switch closure). As long as there's been more than MIN_INACTIVE time, trust the hardware and call it a crank event. @@ -707,10 +679,50 @@ void crank_callback() ********************************************************************************/ void init_cal() { - res_offset = RESISTANCE_OFFSET; // Later, these will be read from a file if present - res_factor = RESISTANCE_FACTOR; + display.setFont(u8g2_font_5x7_tr); + displog.print("KBikeBLE "); + displog.println(VERSION); + displog.println(); + + if (!read_param_file("offset", &res_offset, sizeof(res_offset))) + { + res_offset = RESISTANCE_OFFSET; + write_param_file("offset", &res_offset, sizeof(res_offset)); + displog.println("OFFSET"); + displog.println("DEFAULT"); + displog.println("WRITE:"); + displog.println(res_offset); + } + else + { + displog.print("OFFSET"); + displog.println(" READ:"); + displog.println(res_offset); + } + if (!read_param_file("factor", &res_factor, sizeof(res_factor))) + { + res_factor = RESISTANCE_FACTOR; + write_param_file("factor", &res_factor, sizeof(res_factor)); + displog.println("\nFACTOR"); + displog.println("DEFAULT"); + displog.println("WRITE:"); + displog.printf("%.1f\n\n", res_factor); + } + else + { + displog.print("\nFACTOR"); + displog.println(" READ:"); + displog.printf("%.4f\n", res_factor); + } + + delay(5000); } +/******************************************************************************** + * Startup + ********************************************************************************/ + + SoftwareTimer update_timer; void setup() @@ -731,6 +743,8 @@ void setup() display_setup(); + // Set up the internal filesystem (LittleFS), read files, and where any files are missing write defaults + setup_InternalFS(); init_cal(); STEP(10) @@ -739,6 +753,8 @@ void setup() STEP(20) + // Set up Bluetooth + // Set power - needs only to work in pretty close range Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.setConnLedInterval(BLE_LED_INTERVAL); @@ -769,10 +785,7 @@ void setup() // Start the BLEUart service #ifdef BLEUART bleuart.begin(); - //bleuart.bufferTXD(true); console_init(); - console_clear(); - console_reset(); #endif STEP(30) @@ -1218,6 +1231,8 @@ inline void console_reset() inline void console_init() { bool result = xTaskCreate(bleuart_check, "BLEuartCheck", SCHEDULER_STACK_SIZE_DFLT, ( void * ) 1, TASK_PRIO_LOW, &ble_task_handle); + console_clear(); + console_reset(); } void bleuart_take_input() @@ -1274,29 +1289,10 @@ void bleuart_take_input() } } +#define BLEUART_MAX_MSG 20 +// Send a (relatively) lengthy message over BLE. // BLEUart messages are limited to the BLE MTU - 3. The bleuart library print/write functions will truncate // anything longer. So we need our own function to send longer strings in pieces. - - -#define BLEUART_MAX_MSG 20 -/* using absolute memory addresses -void bleuart_send(const char * message) -{ - const char * addr = message; - const char * last = message + strlen(message); - while (last - addr >= BLEUART_MAX_MSG) - { - bleuart.write(addr, BLEUART_MAX_MSG); - addr += BLEUART_MAX_MSG; - } - if (addr < last) - { - bleuart.write(addr, last - addr); - } -} -*/ - -/* Using relative memory addresses */ void bleuart_send(const char * message) { size_t index = 0; @@ -1459,7 +1455,15 @@ void cmd_write() { if (AWAITING_CONF()) { - RESPOND("\nWrite confirmed.\n"); + if (write_param_file("offset", &res_offset, sizeof(res_offset)) & + write_param_file("factor", &res_factor, sizeof(res_factor)) ) + { + RESPOND("\nWrite confirmed.\n"); + } + else + { + RESPOND("\Failed to write the files.\n"); + } awaiting_conf = AWAITING_NONE; } else @@ -1474,7 +1478,13 @@ void cmd_write() void cmd_read() { - RESPOND("Here, we'd read from the filesystem.\n"); + if (read_param_file("offset", &new_offset, sizeof(new_offset)) & + read_param_file("factor", &new_factor, sizeof(new_factor))) + { + RESPOND("Read from the parameter files:"); + BLE_PRINT("Offset %.1f; factor %.4f\n", new_offset, new_factor); + RESPOND("Y to write these to the file..."); + } } void cmd_help() diff --git a/TODO.md b/TODO.md index 20c44d8..1d3833c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,4 @@ ## TODO -- Calibration items - - Save calibration in littleFS - Console access - Option to use conventional USB serial instead of the spiffier BLE Uart service - Documentation @@ -8,12 +6,10 @@ - Tutorial how-to-build for less experienced folks - Writeup on power consumption - Consider using a different reference, e.g., Vdd ref for the pot to maintain cal near battery end of charge -- Keeping more parameters or options in the filesystem- +- Keeping more parameters or options in the filesystem + - Gear vs. Res% display - BLE Services - If continuing to support FTMS, implement a real model-based calc for speed (mph/kph) as a function of power and cadence. - - Either way, use globals rather than passed parameters for the data. -- Lots of functions have void argument lists and are there just for organizational purposes. Should they be marked as inline? -- Clean up code in connect callbacks - Improve the display: larger size or double area - Things maybe to add - Accumulated data @@ -22,6 +18,13 @@ - Elapsed time - Other uses for swings of the resistance lever as a signal - e.g., reset ride between people, if providing accumulated data +- More comprehensive code cleanup + - Split code across multiple source files + - Clean up code in connect callbacks + - Consistent use of globals rather than passed parameters for BLE characteristic data. + - Review for functions that should be inline + - C++ style for C-style #defines and macros where appropriate + ONE DAY? - Servo on the resistance for full FTMS function! diff --git a/globals.h b/globals.h new file mode 100644 index 0000000..5984863 --- /dev/null +++ b/globals.h @@ -0,0 +1,79 @@ +/******************************************************************************** + Globals + ********************************************************************************/ +#ifndef GLOBALS_ +#define GLOBALS_ + +// BLE data blocks addressable as bytes for flags and words for data +union ble_data_block +{ // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) + uint8_t bytes[2]; + uint16_t words[16]; +}; +union ble_data_block bike_data; // For the Bike Data Characteristic (FiTness Machine Service) +union ble_data_block power_data; // For the Cycling Power Measurement Characteristic (Cycling Power Service) + +// Globals. Variable accessed in multiple places are declared here. +// Those used only in specific functions are declared within or nearby. + +float cadence; // Pedal cadence, determined from crank event timing +float power; // Power, calculated from resistance and cadence to match Keiser's estimates +float bspeed; // Bike speed, required by FTMS, to be estimated from power and cadence. A real estimate is TO DO +float inst_resistance; // Normalized resistance reading, determined from the eddy current brake magnet position +float resistance; // Normalized resistance used in power calculations (can be filtered) +float disp_resistance; // Resistance valued that's displayed (can be filtered) +uint32_t raw_resistance; // Raw resistance measurement from the ADC. Global because it's reported in the serial monitor +float res_offset; // Cal fators - raw_resistance to normalized resistance +float res_factor; +bool serial_present = false; +//float resistance_sq; // Used in both gear and power calcs if not using the lookup table +uint8_t gear; // Gear number: Index into power tables and, optionally, displayed +#if (POWERSAVE > 0) && (POWERSAVE != 1) +bool suspended; // Set to true when suspending the main loop +#endif + +float batt_mvolts; // Battery voltage +uint8_t batt_pct; // Battery percentage charge +bool batt_low; // Battery low + +#define TICK_INTERVAL 500 // ms +uint8_t ticker = 0; // Ticker for the main loop scheduler - inits to zero, also is reset under certain circumstances + + +volatile float inst_cadence = 0; // Cadence calculated in the crank ISR +volatile uint16_t crank_count = 0; // Cumulative crank rotations - set by the crank sensor ISR, reported by CPS and used to determine cadence +volatile uint32_t crank_event_time = 0; // Set to the most recent crank event time by the crank sensor ISR [ms] +//volatile uint32_t last_change_time; // Used in the crank sensor ISR for sensor debounce [ms]. Initialized to millis() in Setup(). +volatile bool new_crank_event = false; // Set by the crank sensor ISR; cleared by the main loop +volatile uint16_t crank_ticks; // 1/1024 sec per tick crank clock, for Cycling Power Measurement [ticks] + +//uint32_t prior_event_time = 0; // Used in the main loop to hold the time of the last reported crank event [ms] + +bool ftm_active = true; // Once a client connects with either service, we stop updating the other. +bool cp_active = true; + +uint8_t display_state = 2; // Display state: 0 = off, 1 = dim, 2 = full on + +// U8G2 display instance. What goes here depends upon the specific display and interface. See U8G2 examples. +U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R1, /* reset=*/U8X8_PIN_NONE); // 128 x 64 SH1106 display on hardware I2C + +// FTMS Service Definitions +BLEService svc_ftms = BLEService(0x1826); // FiTness machine service +BLECharacteristic char_ftm_feature = BLECharacteristic(0x2ACC); // FiTness machine Feature characteristic +BLECharacteristic char_bike_data = BLECharacteristic(0x2AD2); // Indoor Bike Data characteristic + +// CPS Service Definitions +BLEService svc_cps = BLEService(0x1818); // Cycling Power Service +BLECharacteristic char_cp_feature = BLECharacteristic(0x2A65); // Cycling Power Feature +BLECharacteristic char_cp_measurement = BLECharacteristic(0x2A63); // Cycling Power Measurement +BLECharacteristic char_sensor_loc = BLECharacteristic(0x2A5D); // Sensor Location + +BLEDis bledis; // DIS (Device Information Service) helper class instance +#ifdef BLEBAS +BLEBas blebas; // BAS (Battery Service) helper class instance +#endif +#ifdef BLEUART +BLEUart bleuart; // UART over BLE +#endif + +#endif \ No newline at end of file diff --git a/param_store.cpp b/param_store.cpp new file mode 100644 index 0000000..916a3c1 --- /dev/null +++ b/param_store.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#define PARAM_FOLDER "/KBikeBLE" +inline void make_path(char * dest, const char * path, const char * file) +{ + strncpy(dest, path, LFS_NAME_MAX); + strncat(dest, "/", LFS_NAME_MAX); + strncat(dest, file, LFS_NAME_MAX); +} + +using namespace Adafruit_LittleFS_Namespace; +File file(InternalFS); + +// Start the InternalFS and ensure that the parameter folder is there. +void setup_InternalFS(void) +{ + InternalFS.begin(); + if (InternalFS.exists(PARAM_FOLDER)) return; + InternalFS.mkdir(PARAM_FOLDER); +} + +byte stored_value[8]; +char filepath[LFS_NAME_MAX + 1]; + +bool read_param_file(const char * name, void * value, const uint8_t length) +{ + make_path(filepath, PARAM_FOLDER, name); + if (!InternalFS.exists(filepath)) return false; + + uint8_t len = min(length, 8); + //File file(filepath, FILE_O_READ, InternalFS); + if (!file.open(filepath, FILE_O_READ)) return false; + uint8_t written = file.read(stored_value, len); + file.close(); + if (written != len) return false; + + memcpy(value, stored_value, len); // Alt is to use text (atoi, etc) + return true; +} + +bool write_param_file(const char * name, const void * value, uint8_t length) +{ + make_path(filepath, PARAM_FOLDER, name); + if (InternalFS.exists(filepath)) InternalFS.remove(filepath); // Example in bonding.cpp shows remove and re-write instead of open/seek/write. + + uint8_t len = min(length, 8); + if (!file.open(filepath, FILE_O_WRITE)) return false; + uint8_t written = file.write( (const char *) value, len); + file.close(); + if (written != len) return false; + return true; + byte var; +} \ No newline at end of file diff --git a/param_store.h b/param_store.h new file mode 100644 index 0000000..9d0d577 --- /dev/null +++ b/param_store.h @@ -0,0 +1,36 @@ +#ifndef PARAM_STORE_ +#define PARAM_STORE_ +#include +#include +#include + +#define PARAM_FOLDER "/KBikeBLE" + +// Construct a full file path from the directory path and filename +// dest = the full file path +// path = directory path +// file = file name +inline void make_path(char * dest, const char * path, const char * file); + +//using namespace Adafruit_LittleFS_Namespace; +//File file(InternalFS); + +// Start the InternalFS and ensure that the parameter folder is there. +void setup_InternalFS(void); + +//byte stored_value[8]; +//char filepath[LFS_NAME_MAX + 1]; + +// Read a parameter from InternalFS. +// name = filename (typically, the name of the variable) +// value = receives the value +// length = number of bytes (should be sizeof(variable) where variable is what's pointed to by value) +bool read_param_file(const char * name, void * value, const uint8_t length); + +// Write a parameter to InternalFS +// name = filename (typically the name of the variable) +// value = the value to write +// length = number of bytes (should be sizeof(variable) where variable is what's pointed to by value) +bool write_param_file(const char * name, const void * value, uint8_t length); + +#endif \ No newline at end of file From 94ac4df94c59fa99fec1f04dcbd1c8b4828d3140 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Wed, 21 Jul 2021 15:44:07 -0400 Subject: [PATCH 03/11] More code cleanup --- KBikeBLE.ino | 218 ++++++++++++++-------------------------------- globals.h | 6 +- options.h | 11 +++ serial_commands.h | 2 +- 4 files changed, 84 insertions(+), 153 deletions(-) diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 3aae7f2..eeebfae 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -14,6 +14,7 @@ #endif #include "options.h" // Options of most interest to the end user +#include "globals.h" #include "bike_interface.h" // Defines the hardware connections to the bike #include "power_gear_tables.h" #include "calibration.h" @@ -24,11 +25,6 @@ * Optional functions and debugging **********************************************************************/ -//#define USE_SERIAL // Incorporate USB serial functions. Will attempt serial connection at startup. - // CAREFUL! With 1/sec updates through a timer task, it must always finish in < 1 sec. -#define BLEUART // Activates serial over BLE -#define BLEBAS // Activate BLE battery service - //#define QUICKTIMEOUT // Quick timeout options for testing power saving functions //#define DEBUGGING // Activates debug code. Requires USE_SERIAL for any serial console bits. @@ -44,13 +40,13 @@ #if defined(USE_SERIAL) && defined(DEBUGGING) #define DEBUG(x, l) \ - Serial.print(x); \ - Serial.print(l); + Serial.print(F(x)); \ + Serial.print(F(l)); #else #define DEBUG(x, l) #endif -#ifdef DEBUGGING // Stepwise startup to watch for power consumption +#ifdef DEBUGGING // Pauses at points in startup can be helpful in checking power consumption uint8_t stepnum = 0; #define STEP(N) \ cadence = N; \ @@ -63,12 +59,6 @@ uint8_t stepnum = 0; #define DWAIT() #endif -/************************************************************************ - * Globals - ************************************************************************/ - -#include "globals.h" - /************************************************************************ * Miscellany ************************************************************************/ @@ -86,100 +76,28 @@ uint8_t lever_state = 0b00000000; bool gear_display = GEAR_DISPLAY; #define BRAKE 100 // Edge of the valid range, less than the max reading -/******************************************************************************** - Globals - ********************************************************************************/ -// // BLE data blocks addressable as bytes for flags and words for data -// union ble_data_block -// { // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) -// uint8_t bytes[2]; -// uint16_t words[16]; -// }; -// union ble_data_block bike_data; // For the Bike Data Characteristic (FiTness Machine Service) -// union ble_data_block power_data; // For the Cycling Power Measurement Characteristic (Cycling Power Service) - -// // Globals. Variable accessed in multiple places are declared here. -// // Those used only in specific functions are declared within or nearby. - -// float cadence; // Pedal cadence, determined from crank event timing -// float power; // Power, calculated from resistance and cadence to match Keiser's estimates -// float bspeed; // Bike speed, required by FTMS, to be estimated from power and cadence. A real estimate is TO DO -// float inst_resistance; // Normalized resistance reading, determined from the eddy current brake magnet position -// float resistance; // Normalized resistance used in power calculations (can be filtered) -// float disp_resistance; // Resistance valued that's displayed (can be filtered) -// uint32_t raw_resistance; // Raw resistance measurement from the ADC. Global because it's reported in the serial monitor -// float res_offset; // Cal fators - raw_resistance to normalized resistance -// float res_factor; -// bool serial_present = false; -// //float resistance_sq; // Used in both gear and power calcs if not using the lookup table -// uint8_t gear; // Gear number: Index into power tables and, optionally, displayed -// #if (POWERSAVE > 0) && (POWERSAVE != 1) -// bool suspended; // Set to true when suspending the main loop -// #endif - -// float batt_mvolts; // Battery voltage -// uint8_t batt_pct; // Battery percentage charge -// bool batt_low; // Battery low - -// #define TICK_INTERVAL 500 // ms -// uint8_t ticker = 0; // Ticker for the main loop scheduler - inits to zero, also is reset under certain circumstances - - -// volatile float inst_cadence = 0; // Cadence calculated in the crank ISR -// volatile uint16_t crank_count = 0; // Cumulative crank rotations - set by the crank sensor ISR, reported by CPS and used to determine cadence -// volatile uint32_t crank_event_time = 0; // Set to the most recent crank event time by the crank sensor ISR [ms] -// //volatile uint32_t last_change_time; // Used in the crank sensor ISR for sensor debounce [ms]. Initialized to millis() in Setup(). -// volatile bool new_crank_event = false; // Set by the crank sensor ISR; cleared by the main loop -// volatile uint16_t crank_ticks; // 1/1024 sec per tick crank clock, for Cycling Power Measurement [ticks] - -// //uint32_t prior_event_time = 0; // Used in the main loop to hold the time of the last reported crank event [ms] - -// bool ftm_active = true; // Once a client connects with either service, we stop updating the other. -// bool cp_active = true; - -// uint8_t display_state = 2; // Display state: 0 = off, 1 = dim, 2 = full on - -// // U8G2 display instance. What goes here depends upon the specific display and interface. See U8G2 examples. -// U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R1, /* reset=*/U8X8_PIN_NONE); // 128 x 64 SH1106 display on hardware I2C - -// // FTMS Service Definitions -// BLEService svc_ftms = BLEService(0x1826); // FiTness machine service -// BLECharacteristic char_ftm_feature = BLECharacteristic(0x2ACC); // FiTness machine Feature characteristic -// BLECharacteristic char_bike_data = BLECharacteristic(0x2AD2); // Indoor Bike Data characteristic - -// // CPS Service Definitions -// BLEService svc_cps = BLEService(0x1818); // Cycling Power Service -// BLECharacteristic char_cp_feature = BLECharacteristic(0x2A65); // Cycling Power Feature -// BLECharacteristic char_cp_measurement = BLECharacteristic(0x2A63); // Cycling Power Measurement -// BLECharacteristic char_sensor_loc = BLECharacteristic(0x2A5D); // Sensor Location - -// BLEDis bledis; // DIS (Device Information Service) helper class instance -// #ifdef BLEBAS -// BLEBas blebas; // BAS (Battery Service) helper class instance -// #endif -// #ifdef BLEUART -// BLEUart bleuart; // UART over BLE -// #endif - - /********************************************************************************* Display code **********************************************************************************/ +#define DISPLAY_LABEL_FONT u8g2_font_helvB10_tr +#define DISPLAY_NUMBER_FONT u8g2_font_helvB24_tn // Log used at startup -#define U8LOG_WIDTH 16 // 64/4 -#define U8LOG_HEIGHT 21 // 128/6 -uint8_t u8log_buffer[U8LOG_WIDTH * U8LOG_HEIGHT]; +#define LOG_FONT u8g2_font_5x7_tr +#define LOG_FONT_BIG u8g2_font_7x14_tf +#define LOG_WIDTH 12 // 64/5 +#define LOG_HEIGHT 18 // 128/7 +uint8_t u8log_buffer[LOG_WIDTH * LOG_HEIGHT]; U8G2LOG displog; -void display_setup(void) +void display_setup() { display.begin(); display.setContrast(CONTRAST_FULL); //display.enableUTF8Print(); // Can leave this out if using no symbols display.setFontMode(1); // "Transparent": Character background not drawn (since we clear the display anyway) - displog.begin(display, U8LOG_WIDTH, U8LOG_HEIGHT, u8log_buffer); + displog.begin(display, LOG_WIDTH, LOG_HEIGHT, u8log_buffer); displog.setLineHeightOffset(0); displog.setRedrawMode(0); } @@ -242,7 +160,7 @@ void display_numbers() if ((cad != prev_cad) || (rg != prev_rg) || (pwr != prev_pwr) || (batt_pct != prev_batt) || batt_low) { display.clearBuffer(); - display.setFont(u8g2_font_helvB10_tr); + display.setFont(DISPLAY_LABEL_FONT); display.setCursor(0, 16); display.print("RPM"); display.setCursor(0, 58); @@ -253,7 +171,7 @@ void display_numbers() if (!batt_low || (ticker % 2)) draw_batt(batt_pct); //else blank_batt(); - display.setFont(u8g2_font_helvB24_tn); + display.setFont(DISPLAY_NUMBER_FONT); right_just(cad, 10, 43, 18); right_just(rg, 10, 85, 18); right_just(pwr, 10, 127, 18); @@ -271,7 +189,7 @@ void display_numbers() Analog input processing - resistance magnet position and battery * ********************************************************************************/ -void ADC_setup(void) // Set up the ADC for ongoing resistance measurement +void ADC_setup() // Set up the ADC for ongoing resistance measurement { analogReference(AR_INTERNAL); // 3.6V analogOversampling(ANALOG_OVERSAMPLE); @@ -318,7 +236,7 @@ float sinterp(const float x_ref[], const float y_ref[], const float slopes[], in advertising. * ********************************************************************************/ -void startAdv(void) +void startAdv() { // FiTness Machine Service requires a Service Data field specifying bike support and availability // Per https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/) Service Data is Type 0x16 @@ -351,7 +269,7 @@ void startAdv(void) Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds } -void stopBLE(void) +void stopBLE() { DEBUG("Stopping BLE", "\n") // If we're connected, disconnect. Since we only allow one connection, we know that the handle is 0 @@ -364,7 +282,7 @@ void stopBLE(void) Bluefruit.Advertising.stop(); // this looks like it will work even if advertising isn't running } -void restartBLE(void) +void restartBLE() { if (!Bluefruit.connected(0) && !Bluefruit.Advertising.isRunning()) { @@ -373,7 +291,7 @@ void restartBLE(void) } } -void setupFTMS(void) +void setupFTMS() { // Configure and start the FTM service // See: @@ -425,7 +343,7 @@ void setupFTMS(void) char_ftm_feature.begin(); - uint8_t ftmf_data[4] = {0b00000010, 0b01000000, 0b00000000, 0b00000000}; + const uint8_t ftmf_data[4] = {0b00000010, 0b01000000, 0b00000000, 0b00000000}; char_ftm_feature.write(ftmf_data, 4); // Configure the Indoor Bike Data characteristic - See 4.9.1 IN FTMS_V1.0.pdf @@ -462,7 +380,7 @@ void setupFTMS(void) bike_data.bytes[1] = 0b00000000; } -void setupCPS(void) +void setupCPS() { // Configure and start the CP service // See: @@ -511,7 +429,7 @@ void setupCPS(void) // B3 // .0-7 - Reserved - uint8_t cpf_data[4] = {0x0, 0x0, 0x0, 0x0}; + const uint8_t cpf_data[4] = {0x0, 0x0, 0x0, 0x0}; char_cp_feature.setProperties(CHR_PROPS_READ); char_cp_feature.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); char_cp_feature.setFixedLen(4); @@ -524,7 +442,7 @@ void setupCPS(void) // Fixed len = 4 // // 0x01, 0x00, 0x00, 0x00 = "Other" - uint8_t cl_data[4] = {0x01, 0x00, 0x00, 0x00}; + const uint8_t cl_data[4] = {0x01, 0x00, 0x00, 0x00}; char_sensor_loc.setProperties(CHR_PROPS_READ); char_sensor_loc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); char_sensor_loc.setFixedLen(4); @@ -623,7 +541,7 @@ void format_bike_data(float bspeed, float cadence, float resistance, float power bike_data.words[4] = power_int; } -void format_power_data(void) +void format_power_data() { // Fields are // B2-3 = Instantaneous power - UINT16 - W with 1.0 resolution @@ -679,39 +597,40 @@ void crank_callback() ********************************************************************************/ void init_cal() { - display.setFont(u8g2_font_5x7_tr); - displog.print("KBikeBLE "); - displog.println(VERSION); + display.setFont(LOG_FONT_BIG); + displog.print(F("KBikeBLE\n")); + displog.println(F(VERSION)); displog.println(); + display.setFont(LOG_FONT); if (!read_param_file("offset", &res_offset, sizeof(res_offset))) { res_offset = RESISTANCE_OFFSET; write_param_file("offset", &res_offset, sizeof(res_offset)); - displog.println("OFFSET"); - displog.println("DEFAULT"); - displog.println("WRITE:"); + displog.println(F("OFFSET")); + displog.println(F("DEFAULT")); + displog.println(F("WRITE:")); displog.println(res_offset); } else { - displog.print("OFFSET"); - displog.println(" READ:"); + displog.println(F("OFFSET")); + displog.println(F(" READ:")); displog.println(res_offset); } if (!read_param_file("factor", &res_factor, sizeof(res_factor))) { res_factor = RESISTANCE_FACTOR; write_param_file("factor", &res_factor, sizeof(res_factor)); - displog.println("\nFACTOR"); - displog.println("DEFAULT"); - displog.println("WRITE:"); + displog.println(F("\nFACTOR")); + displog.println(F("DEFAULT")); + displog.println(F("WRITE:")); displog.printf("%.1f\n\n", res_factor); } else { - displog.print("\nFACTOR"); - displog.println(" READ:"); + displog.println(F("\nFACTOR")); + displog.println(F(" READ:")); displog.printf("%.4f\n", res_factor); } @@ -722,7 +641,6 @@ void init_cal() * Startup ********************************************************************************/ - SoftwareTimer update_timer; void setup() @@ -739,21 +657,15 @@ void setup() } #endif - DWAIT() - - display_setup(); + // Start the OLED display + display_setup(); // Set up the internal filesystem (LittleFS), read files, and where any files are missing write defaults setup_InternalFS(); init_cal(); - STEP(10) - - Bluefruit.begin(); - - STEP(20) - // Set up Bluetooth + Bluefruit.begin(); // Set power - needs only to work in pretty close range Bluefruit.setTxPower(BLE_TX_POWER); @@ -776,7 +688,6 @@ void setup() #ifdef BLEBAS blebas.begin(); #endif - // blebas.write(100); // Setup the FiTness Machine and Cycling Power services setupFTMS(); @@ -788,20 +699,16 @@ void setup() console_init(); #endif -STEP(30) - // Setup the advertising packet(s) startAdv(); // Crank sensing. A falling edge (switch closure) triggers the interrupt. This counts as a crank event (rotation) // if it's been long enough since the last event. The rider could conceivably initiate faux rotations by holding // the crank right at a certain spot, but there are similar risks with any scheme. - crank_event_time = xTaskGetTickCountFromISR(); + crank_event_time = xTaskGetTickCount(); // xTaskGetTickCountFromISR(); pinMode(CRANK_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(CRANK_PIN), crank_callback, FALLING); -STEP(40) - // Apply voltage to the resistance pot pinMode(RESISTANCE_TOP, OUTPUT); digitalWrite(RESISTANCE_TOP, HIGH); @@ -810,11 +717,16 @@ STEP(40) ftm_active = true; cp_active = true; -STEP(50) - // Set up the analog input for resistance measurement ADC_setup(); + // With crank sensing handled by the interrupt, everything else - + // - Bluetooth updates + // - Display updates + // - Battery check + // - Power savings when pedaling stops + // - Notifying the BLEUart console to check for input + // is handled by the recurring update() task. update_timer.begin(TICK_INTERVAL, update); update_timer.start(); suspendLoop(); @@ -830,8 +742,9 @@ STEP(50) #if (POWERSAVE > 0) #if (POWERSAVE == 1) /* -Power down until reset. Reset is by a selected level (not edge) on the crank switch GPIO pin. -We configure reset to occur when the crank switch changes to the opposite of its current state. +Power down until reset. Reset will be caused by a selected level (not edge) on the crank switch GPIO pin. +The crank switch closes at one point in the rotation and is likely to be open. We can't be certain, +so we configure reset to occur when the crank switch changes to the opposite of its current state. Though the crank switch is usually open because the range of movement during which the magnet is nearby is small, it's possible for the pedals to be stopped with the switch closed. The stock @@ -995,16 +908,10 @@ void process_crank_event() crank_still_timer = 0b100; // Reset the shift register used to detect no pedaling // 3 shifts = 3 seconds without an event indicates no pedaling - restartBLE(); // Be sure BLE is running + restartBLE(); // Be sure BLE is running (RestartBLE checks whether it already is. + // there should possibly be a state flag. new_crank_event = false; // Reset the flag - - // Calculate cadence and clear the event flag - // noInterrupts(); // crank_count and crank_event_time mustn't change - // inst_cadence = (crank_count - last_crank_count) * 60000 / (crank_event_time - prior_event_time); - // last_crank_count = crank_count; - // prior_event_time = crank_event_time; - // interrupts(); } else // If no crank event, check timeouts { @@ -1013,16 +920,18 @@ void process_crank_event() { inst_cadence = 0; stop_time++; + + // Timeouts related to the display switch (display_state) { - case 2: // Full brightness + case 2: // Full brightness - see whether it's time to dim if (stop_time > DIM_TIME) { display.setContrast(CONTRAST_DIM); display_state = 1; } break; - case 1: // Dimmed + case 1: // Dimmed - see whether it's time to turn off if (stop_time > BLANK_TIME) { display.setPowerSave(1); @@ -1030,6 +939,8 @@ void process_crank_event() } break; } + + // Timeouts related to further power savings - different according to Bluetooth state if (Bluefruit.connected()) { if (stop_time >= BLE_PS_TIME) // Active BLE connection: @@ -1104,7 +1015,7 @@ bool serial_confirm(String prompt, char expected) return (input == expected); } -void serial_check(void) +void serial_check() { // Simple serial monitor // Commands are @@ -1311,8 +1222,13 @@ void bleuart_send(const char * message) #define PBLEN 128 char pbuffer[PBLEN]; #define RESPOND(string) bleuart_send(string) +//#define RESPOND(string) const PROGMEM char * str = string; \ +// bleuart_send(str) #define BLE_PRINT(format, args...) snprintf(pbuffer, PBLEN, format, args); \ bleuart_send(pbuffer) +//#define BLE_PRINT(format, args...) const PROGMEM char * fmt = format; \ +// snprintf(pbuffer, PBLEN, fmt, args); \ +// bleuart_send(pbuffer) void cmd_batt() { diff --git a/globals.h b/globals.h index 5984863..b4894b7 100644 --- a/globals.h +++ b/globals.h @@ -4,6 +4,10 @@ #ifndef GLOBALS_ #define GLOBALS_ +#include +#include // nrf52 built-in bluetooth +#include "options.h" + // BLE data blocks addressable as bytes for flags and words for data union ble_data_block { // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) @@ -36,7 +40,7 @@ float batt_mvolts; // Battery voltage uint8_t batt_pct; // Battery percentage charge bool batt_low; // Battery low -#define TICK_INTERVAL 500 // ms +#define TICK_INTERVAL 500 uint8_t ticker = 0; // Ticker for the main loop scheduler - inits to zero, also is reset under certain circumstances diff --git a/options.h b/options.h index f978c28..506ea73 100644 --- a/options.h +++ b/options.h @@ -66,3 +66,14 @@ #define BLE_LED_INTERVAL 1000 // ms #define CONFIRMATION_TIMEOUT 15; // In the console, approx seconds to timeout of commands requiring confirmation + +/* Optional features and functions. + The console interface allows the user to, among other things, calibrate the computer to the individual + bike, so enabling it is recommended. Presently, the BLEUart interface is fully developed, while the + serial interface is not. +*/ +//#define USE_SERIAL // Incorporate USB serial functions. Will attempt serial connection at startup. + // CAREFUL! With 1/sec updates through a timer task, it must always finish in < 1 sec. +#define BLEUART // Activates serial over BLE +#define BLEBAS // Activate BLE battery service + diff --git a/serial_commands.h b/serial_commands.h index 5ae32bf..f1126bf 100644 --- a/serial_commands.h +++ b/serial_commands.h @@ -11,7 +11,7 @@ struct cmd_table_t { // Define the command set extern cmdHandler_t cmd_batt, cmd_res, cmd_showcal, cmd_factor, cmd_offset, cmd_cal, cmd_activate, cmd_write, cmd_read, cmd_defaults, cmd_help; -const cmd_table_t cmd_table[] = { +const cmd_table_t PROGMEM cmd_table[] = { {"batt", cmd_batt, "Show battery status."}, {"res", cmd_res, "Monitor ADC readings. Any input to stop."}, {"showcal", cmd_showcal, "Show the calibration currently in use."}, From 79b2f2a52534a9d00443c5f39ed019af47004633 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Fri, 23 Jul 2021 15:15:28 -0400 Subject: [PATCH 04/11] CLI through serial, and more cleanup --- BLE_services.h | 156 ++++++++++++++ CHANGELOG.md | 9 +- KBikeBLE.ino | 555 +++++++++++++++---------------------------------- TODO.md | 4 - globals.h | 44 +++- options.h | 8 +- 6 files changed, 367 insertions(+), 409 deletions(-) create mode 100644 BLE_services.h diff --git a/BLE_services.h b/BLE_services.h new file mode 100644 index 0000000..aadf960 --- /dev/null +++ b/BLE_services.h @@ -0,0 +1,156 @@ +// BLE services + +#ifndef BLE_SERVICES_ +#define BLE_SERVICES_ + +/* Cycling Power Service (CPS) --------------------------------------------------------------------------------- + Supported Characteristics: + Name UUID Requirement Properties + ---------------------------- ------ ----------- ---------- + Cycling Power Feature 0x2A65 Mandatory Read + Cycling Power Measurement 0x2A63 Mandatory Notify + Sensor Location 0x2A5D Mandatory Read +*/ + + /* Cycling Power Feature characteristic + See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.cycling_power_feature.xml + Properties = Read + Fixed len = 4 + + B0 + .7 - Accumulated energy supported - 0 + .6 - Top and bottom dead spot angles supported - 0 + .5 - Extreme angles supported - 0 + .4 - Extreme magnitudes supported - 0 + .3 - Crank revolution data supported - 0 + .2 - Wheel revolution data supported - 0 + .1 - Accumulated torque supported - 0 + .0 - Pedal power balance supported - 0 + B1 + .7 - Span Length Adjustment Supported - 0 + .6 - Chain Weight Adjustment Supported - 0 + .5 - Chain Length Adjustment Supported - 0 + .4 - Crank Length Adjustment Supported - 0 + .3 - Multiple Sensor Locations Supported - 0 + .2 - Cyc Pow Meas Char Cont Masking Supported - 0 + .1 - Offset Compensation Supported - 0 + .0 - Offset Compensation Indicator Supported - 0 + B2 + .7 - Reserved + .6 - Reserved + .4-5 - Distribute System Support - 0 + .4-5 - [00 Legacy; 01 No; 02 Yes; 03 RFU - 0 + .3 - Enhanced Offset Compensation Supported - 0 + .2 - Factory Calibration Date Supported - 0 + .1 - Instantaneous Measu Direction Supported - 0 + .0 - Sensor Meas Context (0 Force, 1 Torque) - 0 + B3 + .0-7 - Reserved + */ + + const uint8_t cpf_data[4] = {0x0, 0x0, 0x0, 0x0}; + + /* Sensor Location characteristic + See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.sensor_location.xml + Properties = Read + Fixed len = 4 + 0x01, 0x00, 0x00, 0x00 = "Other" + */ + + const uint8_t cl_data[4] = {0x01, 0x00, 0x00, 0x00}; + +/* Cycle Power Measurement characteristic + See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.cycling_power_measurement.xml + Properties = Notify + Fixed len = 8 // Flags[2], InstPower[2], CrankRev[2], Timestamp[2] + Flags = { 0x20, 0x00 } = Crank Revolution Data present + */ + +// #define CPM_DATA_0 0x20 +// #define CPM_DATA_1 0x00 +/* The union (or struct) holding the transmitted data should probably be initialized here...*/ +struct power_data_t +{ + uint8_t flags[2] {0x20, 0x00}; // These don't change + uint16_t data[3]; // These are loaded with the data +} power_data; + + +/* FiTness Machine Service (FTMS) ----------------------------------------------------------------------------- + Supported Characteristics: + Name UUID Requirement Properties + ---------------------------- ------ ----------- ---------- + Fitness Machine Feature 0x2ACC Mandatory Read + Indoor Bike Data 0x2A38 Mandatory Notify +*/ + + /* FiTness Machine Service requires a Service Data field specifying bike support and availability + Per https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/) Service Data is Type 0x16 + Contents are + B0-1 - Fitness Machine Service UUID - UINT16 - 2 bytes = 0x1826 + B2 - Flags - UINT8 - 1 byte = 0x01 (Machine available) + B3-4 - Fitness Machine Type - UINT16 - 2 bytes = 0x0020 (Indoor bike supported (bit 5)) + */ + + const uint8_t FTMS_Adv_Data[5] = {0x26, 0x18, 0x01, 0x20, 0x00}; + + /* Fitness Machine Feature characteristic + See: + Properties = Read + Fixed Len = 4 + B0 = UINT8 - Flag (MANDATORY) + 7 = 0 - Resistance level supported + 6 = 0 - Step count supported + 5 = 0 - Pace supported + 4 = 0 - Elevation gain supported + 3 = 0 - Inclination supported + 2 = 0 - Total distance supported + 1 = 1 - Cadence supported + 0 = 0 - Average speed supported + B1 = UINT8 + 7 = 0 - Force on belt and power output supported + 6 = 1 - Power measurement supported + 5 = 0 - Remaining time supported + 4 = 0 - Elapsed time supported + 3 = 0 - Metabolic equivalent supported + 2 = 0 - Heart rate measurement supported + 1 = 0 - Expended energy supported + 0 = 0 - Stride count supported + B2 = UINT8 + 2-7 = 0 - RESERVED + 0 = 0 - User data retention supported + B3 = UINT8 - RESERVED + */ + + const uint8_t ftmf_data[4] = {0b00000010, 0b01000000, 0b00000000, 0b00000000}; + + /* Indoor Bike Data characteristic - See 4.9.1 IN FTMS_V1.0.pdf + See: + Properties = Notify + Fixed Len = 8 + B0 = UINT8 - Flag (MANDATORY) + 7 = 0 - Average power present + 6 = 1 - Instantaneous power present + 5 = 1 - Resistance level present + 4 = 0 - Total distance present + 3 = 0 - Average cadence present + 2 = 1 - Instantaneous cadence present + 1 = 0 - Average speed present + 0 = 0 - More Data (Instantaneous speed field not present) + B1 = UINT 8 + 4 = 0 - Remaining time present + 3 = 0 - Elapsed time present + 2 = 0 - Metabolic equivalent present + 1 = 0 - Heart rate present + 0 = 0 - Expended energy present + B2-3 = Instantaneous speed - UINT16 - Km/Hr with 0.01 resolution + B4-5 = Instantaneous cadence - UINT16 - 1/min with 0.5 resolution + B6-7 = Instantaneous power - UINT16 - W with 1.0 resolution */ + +struct bike_data_t +{ + uint8_t flags[2] {0b01100100, 0b00000000}; // These don't change + uint16_t data[4]; // These are loaded with the data +} bike_data; + +#endif \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6d43f..b9d891f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,10 @@ - Calibration similar to Keiser's procedure, initiated through the *calibrate* command and with step-by-step prompts for using the calibration tool. - Calibration will not yet survive a reset. Saving parameters to nonvolatile memory (LittleFS) is next. - V0.17 - - Calibration is now saved to nonvolatile memory (via LittleFS). If cal files aren't present, they're created from the defaults in calibration.h. + - Calibration is now saved to nonvolatile memory (via LittleFS). If cal files aren't present, they're created from the defaults in calibration.h and written out to nonvolatile memory. Further changes are via the console. - Little U8G2 log screen at startup to show parameters read from or written to nonvolatile memory - - The Bluetooth command line interface now allows calibration values to be re-read from, or written to, nonvolatile memory. - - Moved globals to globals.h - start of some code cleanup + - The command line interface now allows calibration values to be re-read from, or written to, nonvolatile memory. + - The command line interface now works via BLEUart or serial +- Moved globals to globals.h - start of some code cleanup + - Moved BLE service definitions and flags to BLE_services.h + - Cleaner struct definition for BLE data diff --git a/KBikeBLE.ino b/KBikeBLE.ino index eeebfae..bf4567f 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -16,6 +16,7 @@ #include "options.h" // Options of most interest to the end user #include "globals.h" #include "bike_interface.h" // Defines the hardware connections to the bike +#include "BLE_services.h" // Flags/fields for the BLE services #include "power_gear_tables.h" #include "calibration.h" #include "serial_commands.h" @@ -83,10 +84,10 @@ bool gear_display = GEAR_DISPLAY; #define DISPLAY_LABEL_FONT u8g2_font_helvB10_tr #define DISPLAY_NUMBER_FONT u8g2_font_helvB24_tn // Log used at startup -#define LOG_FONT u8g2_font_5x7_tr +#define LOG_FONT u8g2_font_7x14_tf #define LOG_FONT_BIG u8g2_font_7x14_tf -#define LOG_WIDTH 12 // 64/5 -#define LOG_HEIGHT 18 // 128/7 +#define LOG_WIDTH 9 +#define LOG_HEIGHT 10 uint8_t u8log_buffer[LOG_WIDTH * LOG_HEIGHT]; U8G2LOG displog; @@ -238,15 +239,6 @@ float sinterp(const float x_ref[], const float y_ref[], const float slopes[], in void startAdv() { - // FiTness Machine Service requires a Service Data field specifying bike support and availability - // Per https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/) Service Data is Type 0x16 - // Contents are - // B0-1 - Fitness Machine Service UUID - UINT16 - 2 bytes = 0x1826 - // B2 - Flags - UINT8 - 1 byte = 0x01 (Machine available) - // B3-4 - Fitness Machine Type - UINT16 - 2 bytes = 0x0020 (Indoor bike supported (bit 5)) - - uint8_t FTMS_Adv_Data[5] = {0x26, 0x18, 0x01, 0x20, 0x00}; - Bluefruit.Advertising.addService(svc_ftms, svc_cps); // Advertise the services Bluefruit.Advertising.addData(0x16, FTMS_Adv_Data, 5); // Required data field for FTMS @@ -271,7 +263,7 @@ void startAdv() void stopBLE() { - DEBUG("Stopping BLE", "\n") + //DEBUG("Stopping BLE", "\n") // If we're connected, disconnect. Since we only allow one connection, we know that the handle is 0 // if (Bluefruit.connected(0)) Bluefruit.disconnect(0); Bluefruit.disconnect(0); // disconnect() includes check for whether connected @@ -286,7 +278,7 @@ void restartBLE() { if (!Bluefruit.connected(0) && !Bluefruit.Advertising.isRunning()) { - DEBUG("Restarting BLE", "\n") + //DEBUG("Restarting BLE", "\n") Bluefruit.Advertising.start(0); } } @@ -294,13 +286,6 @@ void restartBLE() void setupFTMS() { // Configure and start the FTM service - // See: - // Supported Characteristics: - // Name UUID Requirement Properties - // ---------------------------- ------ ----------- ---------- - // Fitness Machine Feature 0x2ACC Mandatory Read - // Indoor Bike Data 0x2A38 Mandatory Notify - // First .begin the service, prior to .begin on characteristics svc_ftms.begin(); @@ -309,161 +294,48 @@ void setupFTMS() // a BLECharacteristic will cause it to be added to the last BLEService that // was 'begin()'ed! - // Configure the Fitness Machine Feature characteristic - // See: - // Properties = Read - // Fixed Len = 4 - // B0 = UINT8 - Flag (MANDATORY) - // 7 = 0 - Resistance level supported - // 6 = 0 - Step count supported - // 5 = 0 - Pace supported - // 4 = 0 - Elevation gain supported - // 3 = 0 - Inclination supported - // 2 = 0 - Total distance supported - // 1 = 1 - Cadence supported - // 0 = 0 - Average speed supported - // B1 = UINT8 - // 7 = 0 - Force on belt and power output supported - // 6 = 1 - Power measurement supported - // 5 = 0 - Remaining time supported - // 4 = 0 - Elapsed time supported - // 3 = 0 - Metabolic equivalent supported - // 2 = 0 - Heart rate measurement supported - // 1 = 0 - Expended energy supported - // 0 = 0 - Stride count supported - // B2 = UINT8 - // 2-7 = 0 - RESERVED - // 0 = 0 - User data retention supported - // B3 = UINT8 - RESERVED - char_ftm_feature.setProperties(CHR_PROPS_READ); char_ftm_feature.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); - char_ftm_feature.setFixedLen(4); + char_ftm_feature.setFixedLen(sizeof(ftmf_data)); + // Should the following callback be set for ftm_feature or for bike_data?? char_ftm_feature.setCccdWriteCallback(ftm_cccd_callback); // Optionally capture CCCD updates char_ftm_feature.begin(); - const uint8_t ftmf_data[4] = {0b00000010, 0b01000000, 0b00000000, 0b00000000}; - char_ftm_feature.write(ftmf_data, 4); - - // Configure the Indoor Bike Data characteristic - See 4.9.1 IN FTMS_V1.0.pdf - // See: - // Properties = Notify - // Fixed Len = 8 - // B0 = UINT8 - Flag (MANDATORY) - // 7 = 0 - Average power present - // 6 = 1 - Instantaneous power present - // 5 = 1 - Resistance level present - // 4 = 0 - Total distance present - // 3 = 0 - Average cadence present - // 2 = 1 - Instantaneous cadence present - // 1 = 0 - Average speed present - // 0 = 0 - More Data (Instantaneous speed field not present) - // B1 = UINT 8 - // 4 = 0 - Remaining time present - // 3 = 0 - Elapsed time present - // 2 = 0 - Metabolic equivalent present - // 1 = 0 - Heart rate present - // 0 = 0 - Expended energy present - // B2-3 = Instantaneous speed - UINT16 - Km/Hr with 0.01 resolution - // B4-5 = Instantaneous cadence - UINT16 - 1/min with 0.5 resolution - // B6-7 = Instantaneous power - UINT16 - W with 1.0 resolution + char_ftm_feature.write(ftmf_data, sizeof(ftmf_data)); char_bike_data.setProperties(CHR_PROPS_NOTIFY); char_bike_data.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); - char_bike_data.setFixedLen(10); + char_bike_data.setFixedLen(sizeof(bike_data)); char_bike_data.begin(); - // Load flags into the data array. They won't change. - bike_data.bytes[0] = 0b01100100; - bike_data.bytes[1] = 0b00000000; } void setupCPS() { // Configure and start the CP service - // See: - // Supported Characteristics: - // Name UUID Requirement Properties - // ---------------------------- ------ ----------- ---------- - // Cycling Power Feature 0x2A65 Mandatory Read - // Cycling Power Measurement 0x2A63 Mandatory Notify - // Sensor Location 0x2A5D Mandatory Read - // First .begin the service, prior to .begin on characteristics svc_cps.begin(); - // Configure the Cycling Power Feature characteristic - // See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.cycling_power_feature.xml - // Properties = Read - // Fixed len = 4 - // - // B0 - // .7 - Accumulated energy supported - 0 - // .6 - Top and bottom dead spot angles supported - 0 - // .5 - Extreme angles supported - 0 - // .4 - Extreme magnitudes supported - 0 - // .3 - Crank revolution data supported - 0 - // .2 - Wheel revolution data supported - 0 - // .1 - Accumulated torque supported - 0 - // .0 - Pedal power balance supported - 0 - // B1 - // .7 - Span Length Adjustment Supported - 0 - // .6 - Chain Weight Adjustment Supported - 0 - // .5 - Chain Length Adjustment Supported - 0 - // .4 - Crank Length Adjustment Supported - 0 - // .3 - Multiple Sensor Locations Supported - 0 - // .2 - Cyc Pow Meas Char Cont Masking Supported - 0 - // .1 - Offset Compensation Supported - 0 - // .0 - Offset Compensation Indicator Supported - 0 - // B2 - // .7 - Reserved - // .6 - Reserved - // .4-5 - Distribute System Support - 0 - // .4-5 - [00 Legacy; 01 No; 02 Yes; 03 RFU - 0 - // .3 - Enhanced Offset Compensation Supported - 0 - // .2 - Factory Calibration Date Supported - 0 - // .1 - Instantaneous Measu Direction Supported - 0 - // .0 - Sensor Meas Context (0 Force, 1 Torque) - 0 - // B3 - // .0-7 - Reserved - - const uint8_t cpf_data[4] = {0x0, 0x0, 0x0, 0x0}; char_cp_feature.setProperties(CHR_PROPS_READ); char_cp_feature.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); - char_cp_feature.setFixedLen(4); + char_cp_feature.setFixedLen(sizeof(cpf_data)); char_cp_feature.begin(); - char_cp_feature.write(cpf_data, 4); - - // Configure the Sensor Location characteristic - // See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.sensor_location.xml - // Properties = Read - // Fixed len = 4 - // - // 0x01, 0x00, 0x00, 0x00 = "Other" - const uint8_t cl_data[4] = {0x01, 0x00, 0x00, 0x00}; + char_cp_feature.write(cpf_data, sizeof(cpf_data)); + char_sensor_loc.setProperties(CHR_PROPS_READ); char_sensor_loc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); - char_sensor_loc.setFixedLen(4); + char_sensor_loc.setFixedLen(sizeof(cl_data)); char_sensor_loc.begin(); - char_sensor_loc.write(cl_data, 4); - - // Configure the Cycle Power Measurement characteristic - // See https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/characteristic/org.bluetooth.characteristic.cycling_power_measurement.xml - // Properties = Notify - // Fixed len = 8 // Flags[2], InstPower[2], CrankRev[2], Timestamp[2] - // - // Flags = { 0x20, 0x00 } = Crank Revolution Data present + char_sensor_loc.write(cl_data, sizeof(cl_data)); char_cp_measurement.setProperties(CHR_PROPS_NOTIFY); char_cp_measurement.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); - char_cp_measurement.setFixedLen(8); + char_cp_measurement.setFixedLen(sizeof(power_data)); char_cp_measurement.setCccdWriteCallback(cps_cccd_callback); // Optionally capture CCCD updates char_cp_measurement.begin(); - power_data.bytes[0] = 0x20; // Won't change - power_data.bytes[1] = 0.00; } void connect_callback(uint16_t conn_handle) @@ -524,21 +396,21 @@ void cps_cccd_callback(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_ } } -void format_bike_data(float bspeed, float cadence, float resistance, float power) +void format_bike_data() { // B2-3 = Instantaneous speed - UINT16 - Km/Hr with 0.01 resolution // B4-5 = Instantaneous cadence - UINT16 - 1/min with 0.5 resolution // B6-7 = Instantaneous power - UINT16 - W with 1.0 resolution - uint16_t speed_int = round(bspeed / 0.01); - uint16_t cadence_int = round(cadence / 0.5); - uint16_t power_int = round(power); - uint16_t resistance_int = round(resistance); + uint16_t speed_int = roundpos(bspeed / 0.01); + uint16_t cadence_int = roundpos(cadence / 0.5); + uint16_t power_int = roundpos(power); + uint16_t resistance_int = roundpos(resistance); - bike_data.words[1] = speed_int; - bike_data.words[2] = cadence_int; - bike_data.words[3] = resistance_int; - bike_data.words[4] = power_int; + bike_data.data[0] = speed_int; + bike_data.data[1] = cadence_int; + bike_data.data[2] = resistance_int; + bike_data.data[3] = power_int; } void format_power_data() @@ -552,10 +424,10 @@ void format_power_data() //uint16_t revs = crank_count; //uint16_t et = ((crank_event_time & 0xFFFF) * 1024 / 1000) & 0xFFFF ; // uint32 millisec to uint16 1024ths - power_data.words[1] = roundpos(power); + power_data.data[0] = roundpos(power); noInterrupts(); // crank_count and crank_ticks are set in the crank ISR - power_data.words[2] = crank_count; - power_data.words[3] = crank_ticks; + power_data.data[1] = crank_count; + power_data.data[2] = crank_ticks; interrupts(); } @@ -625,7 +497,7 @@ void init_cal() displog.println(F("\nFACTOR")); displog.println(F("DEFAULT")); displog.println(F("WRITE:")); - displog.printf("%.1f\n\n", res_factor); + displog.printf("%.1f\n", res_factor); } else { @@ -647,14 +519,6 @@ void setup() { #ifdef USE_SERIAL Serial.begin(115200); - uint16_t ticks; - for (ticks = 1000; !Serial && (ticks > 0); ticks--) - delay(10); // Serial, if present, takes some time to connect - if (Serial) - { - serial_present = true; - Serial.setTimeout(5000); - } #endif // Start the OLED display @@ -684,20 +548,24 @@ void setup() bledis.setModel("Bluefruit Feather52"); bledis.begin(); -// Start the BLE Battery Service and set it to 100% -#ifdef BLEBAS - blebas.begin(); -#endif + // Start the BLE Battery Service and set it to 100% + #ifdef BLEBAS + blebas.begin(); + #endif // Setup the FiTness Machine and Cycling Power services setupFTMS(); setupCPS(); -// Start the BLEUart service -#ifdef BLEUART - bleuart.begin(); - console_init(); -#endif + // Start the BLEUart service + #ifdef BLEUART + bleuart.begin(); + #endif + + // Start the console + #if defined(BLEUART) || defined(USE_SERIAL) + console_init(); + #endif // Setup the advertising packet(s) startAdv(); @@ -713,13 +581,13 @@ void setup() pinMode(RESISTANCE_TOP, OUTPUT); digitalWrite(RESISTANCE_TOP, HIGH); + // Set up the analog input for resistance measurement + ADC_setup(); + // Set both notify characteristics to be active. Whichever the client responds to becomes the sole active characteristic ftm_active = true; cp_active = true; - // Set up the analog input for resistance measurement - ADC_setup(); - // With crank sensing handled by the interrupt, everything else - // - Bluetooth updates // - Display updates @@ -831,15 +699,13 @@ void resume() * Updating the display as needed. These are all handled by a single function, update(), which calls each individual function - at the appropriate times. update() is scheduled as a recurring task in FreeRTOS which is part - of the Adafruit Arduino core for the nRF52840. This gives better performance (lower energy) than - using the traditional loop() with delay(), despite the FreeRTOS tickless idle mode by which - delay() ought to basically be a sleep(). - - To provide good responsiveness for the user, twice per second the bike resistance is measured - and the display is updated if anything has changed. The battery level is measured every 30 seconds. - Everything else - power calculation and updating Bluetooth are done once per second. These could be - scheduled as three separate recurring tasks in FreeRTOS but that doesn't seem worth the trouble. + at the appropriate times. update() is scheduled as a recurring task, twice per second, + in FreeRTOS (part of the Adafruit Arduino core for the nRF52840. In the extended absence of + pedaling, one option for power savings is to simply un-schedule this task, leaving nothing + to do until pedaling resumes, and allowing the system to default into a low-power state. + + The BLE console interface is handled by a low-priority process that sits idle until + triggered (the FreeRTOS term is Notify). update() does this once per second. **********************************************************************************************/ #define BATT_TICKS 60 // Battery check every __ ticks @@ -974,138 +840,19 @@ void updateBLE() if (cp_active) { format_power_data(); - char_cp_measurement.notify((uint8_t *)&power_data, 8); + char_cp_measurement.notify((uint8_t *)&power_data, 8); // The cast should not be needed } if (ftm_active) { bspeed = 20 * cadence / 60; // NOT A REAL CAL OF ANY KIND! - format_bike_data(bspeed, cadence, resistance, power); + format_bike_data(); char_bike_data.notify((uint8_t *)&bike_data, 10); } } } -#ifdef USE_SERIAL - -bool new_cal = false; -float new_offset; -float new_factor; -char input; -int n; - -char serial_cmd() -{ - int cmd = Serial.read(); // Grab the first character - while (Serial.available()) - Serial.read(); // Flush the rest - eliminates type-ahead and CR/LF - return cmd; -} - -bool serial_confirm(String prompt, char expected) -{ - while (Serial.available()) - Serial.read(); // Flush - Serial.print(prompt); - n = Serial.readBytes(&input, 1); - if (n > 0) - Serial.println(input); - while (Serial.available()) - Serial.read(); - return (input == expected); -} - -void serial_check() -{ - // Simple serial monitor - // Commands are - // R - report raw resistance value - // O - report the current offset - // F - report the current scale factor - // E - report current cal values - // C - enter cal values - // A - make new cal values active - // W - write cal values to file - - char cmd = serial_cmd(); - if (cmd < 0) - return; - - switch (cmd) - { - case 'R': - Serial.print("\nRaw R "); - Serial.println(raw_resistance); - break; - case 'O': - Serial.print("\nOffset "); - Serial.println(res_offset, 2); - break; - case 'F': - Serial.print("\nFactor "); - Serial.println(res_factor, 4); - break; - case 'C': - Serial.print("\nEnter cal...\n --> New slope --> "); - new_factor = Serial.parseFloat(SKIP_WHITESPACE); - if (new_factor == 0) - { - Serial.println(" ... timeout\n"); - break; - } - Serial.println(new_factor); - Serial.print(" --> New intercept --> "); - new_offset = Serial.parseFloat(SKIP_WHITESPACE); - if (new_offset == 0) - { - Serial.println(" ... timeout\n"); - break; - } - Serial.println(new_offset); - Serial.println("Use A to activate.\n"); - new_cal = true; - break; - case 'A': - if (!new_cal) - { - Serial.println("\nEnter cal before trying to activate."); - break; - } - Serial.println("\nReady to activate..."); - Serial.print(" Factor "); - Serial.println(new_factor, 4); - Serial.print(" Offset "); - Serial.println(new_offset, 2); - if (!serial_confirm(" A to activate; any other key or 5 seconds to skip --> ", 'A')) - { - Serial.println(" ... skipped\n"); - break; - } - res_factor = new_factor; - res_offset = new_offset; - new_cal = false; - Serial.println("\n New cal factors active.\n"); - break; - case 'W': - Serial.println(" Ready to save cal..."); - Serial.print(" Factor "); - Serial.println(res_factor, 4); - Serial.print(" OFFSET "); - Serial.println(res_offset, 2); - Serial.print(" W to write, or any other key or 5 seconds to skip -->"); - if (!serial_confirm(" W to write; any other key or 5 seconds to skip --> ", 'W')) - { - Serial.println(" ... skipped\n"); - break; - } - //write_cal_file(); - Serial.println("\n** If we had the filesystem set up, cal would have been written. **\n"); - break; - } -} -#endif - -#ifdef BLEUART +#if defined(BLEUART) || defined(USE_SERIAL) #define SBUF_LEN 20 char sbuffer[SBUF_LEN]; // Serial console input buffer char cmd[SBUF_LEN]; // Tokenized command @@ -1119,7 +866,9 @@ uint8_t awaiting_timer; // Timeout counter for confirmation float new_factor; float new_offset; -TaskHandle_t ble_task_handle; +TaskHandle_t console_task_handle; + +Stream * console = nullptr; inline void console_clear() // Clears the console for fresh input { @@ -1141,16 +890,52 @@ inline void console_reset() inline void console_init() { - bool result = xTaskCreate(bleuart_check, "BLEuartCheck", SCHEDULER_STACK_SIZE_DFLT, ( void * ) 1, TASK_PRIO_LOW, &ble_task_handle); + #ifdef USE_SERIAL // These shouldn't be needed + console = &Serial; + #endif + #ifdef BLEUART + console = &bleuart; + #endif + bool result = xTaskCreate(console_check, "ConsoleCheck", SCHEDULER_STACK_SIZE_DFLT, + ( void * ) 1, TASK_PRIO_LOW, &console_task_handle); console_clear(); console_reset(); } -void bleuart_take_input() +bool console_source_check() { - while (bleuart.available()) + bool avail = false; + + #ifdef USE_SERIAL // If enabled, check Serial + bool s = Serial.available(); + if (s) + { + console = &Serial; + avail = true; + } + #endif + + #ifdef BLEUART // If enabled, check BLEUart + bool b = bleuart.available(); + if (b) + { + console = &bleuart; + avail = true; + #ifdef USE_SERIAL + if (s) // BLE has precedence, so dump any serial input + while (Serial.available()) Serial.read(); + #endif + } + #endif + + return avail; +} + +void console_take_input() +{ + while (console->available()) // Might need && !new_input and flush at the end to be sure to get only one line { - uint8_t c = (uint8_t) bleuart.read(); + uint8_t c = (uint8_t) console->read(); switch (c) { case 32: // delimeter @@ -1194,46 +979,48 @@ void bleuart_take_input() default: if (sbix < SBUF_LEN - 1) sbuffer[sbix++] = tolower(c); // Add the character to the pending command or argument - //std::cout << sbuffer << "\n"; break; } } } #define BLEUART_MAX_MSG 20 + // Send a (relatively) lengthy message over BLE. // BLEUart messages are limited to the BLE MTU - 3. The bleuart library print/write functions will truncate // anything longer. So we need our own function to send longer strings in pieces. -void bleuart_send(const char * message) +// For simplicity (right now) this is imposed upon both BLE and Serial. +void console_send(const char * message) { size_t index = 0; size_t last = strlen(message); while (last - index >= BLEUART_MAX_MSG) { - bleuart.write(message + index, BLEUART_MAX_MSG); + console->write(message + index, BLEUART_MAX_MSG); index += BLEUART_MAX_MSG; } if (index < last) { - bleuart.write(message + index, last - index); + console->write(message + index, last - index); } } - +/* +void console_send(const char * message) +{ + console->write((uint8_t*) message, strlen(message)); + //console->cBLEUart::write(message, strlen(message)); +} +*/ #define PBLEN 128 char pbuffer[PBLEN]; -#define RESPOND(string) bleuart_send(string) -//#define RESPOND(string) const PROGMEM char * str = string; \ -// bleuart_send(str) -#define BLE_PRINT(format, args...) snprintf(pbuffer, PBLEN, format, args); \ - bleuart_send(pbuffer) -//#define BLE_PRINT(format, args...) const PROGMEM char * fmt = format; \ -// snprintf(pbuffer, PBLEN, fmt, args); \ -// bleuart_send(pbuffer) +#define CONSOLE_RESP(string) console_send(string) +#define CONSOLE_PRINT(format, args...) snprintf(pbuffer, PBLEN, format, args); \ + console_send(pbuffer) void cmd_batt() { - BLE_PRINT("Battery voltage %.2f \n", batt_mvolts/1000);//batt_mvolts*1000); - BLE_PRINT(" %d%% charge\n\n", batt_pct); + CONSOLE_PRINT("Battery voltage %.2f \n", batt_mvolts/1000);//batt_mvolts*1000); + CONSOLE_PRINT(" %d%% charge\n\n", batt_pct); } void cmd_res() @@ -1243,14 +1030,14 @@ void cmd_res() { if (raw_resistance != last_resistance) { - BLE_PRINT("Raw ADC value %d\n", raw_resistance); - BLE_PRINT("Resistance %.1f%%\n", resistance); - BLE_PRINT("Keiser gear number %d\n\n", gear); + CONSOLE_PRINT("Raw ADC value %d\n", raw_resistance); + CONSOLE_PRINT("Resistance %.1f%%\n", resistance); + CONSOLE_PRINT("Keiser gear number %d\n\n", gear); last_resistance = raw_resistance; } - if (bleuart.available()) + if (console->available()) { - bleuart.flush(); + while (console->available()) console->read(); //NOTE: bleuart.flush() flushes input; Serial.flush() flushes output break; } delay(TICK_INTERVAL); @@ -1259,41 +1046,41 @@ void cmd_res() void cmd_showcal() { - BLE_PRINT("Offset %.1f; factor %.4f\n\n", res_offset, res_factor); + CONSOLE_PRINT("Offset %.1f; factor %.4f\n\n", res_offset, res_factor); } void cmd_factor() { if (arg[0] == 0) //Should check for numeric as well { - RESPOND("Factor command requires a numeric value.\n\n"); + CONSOLE_RESP("Factor command requires a numeric value.\n\n"); return; } new_factor = atof(arg); - BLE_PRINT("New factor will be %.4f .\n", new_factor); - RESPOND("Use activate to have the bike use the new value.\n\n"); + CONSOLE_PRINT("New factor will be %.4f .\n", new_factor); + CONSOLE_RESP("Use activate to have the bike use the new value.\n\n"); } void cmd_offset() { if (arg[0] == 0) //Should check for numeric as well { - RESPOND("Offset command requires a numeric value.\n\n"); + CONSOLE_RESP("Offset command requires a numeric value.\n\n"); return; } new_offset = atof(arg); - BLE_PRINT("New offset will be %.1f .\n", new_offset); - RESPOND("Use activate to have the bike use the new value.\n\n"); + CONSOLE_PRINT("New offset will be %.1f .\n", new_offset); + CONSOLE_RESP("Use activate to have the bike use the new value.\n\n"); } void cmd_defaults() { new_offset = RESISTANCE_OFFSET; new_factor = RESISTANCE_FACTOR; - RESPOND("\nDefaults...\n"); - BLE_PRINT("New offset will be %.1f .\n", new_offset); - BLE_PRINT("New factor will be %.4f .\n", new_factor); - RESPOND("Use activate to have the bike use these values.\n\n"); + CONSOLE_RESP("\nDefaults...\n"); + CONSOLE_PRINT("New offset will be %.1f .\n", new_offset); + CONSOLE_PRINT("New factor will be %.4f .\n", new_factor); + CONSOLE_RESP("Use activate to have the bike use these values.\n\n"); } uint8_t i; @@ -1304,7 +1091,7 @@ void cmd_res() { for (i = steps; i > 0; i--) { - BLE_PRINT(" %d..", i); + CONSOLE_PRINT(" %d..", i); delay(time); } } @@ -1312,36 +1099,36 @@ void cmd_res() { if (AWAITING_CONF()) { - RESPOND("\n\nPlace the calibration tool on the flywheel and rotate the flywheel so that the calibration tool contacts the magnet assembly.\n"); + CONSOLE_RESP("\n\nPlace the calibration tool on the flywheel and rotate the flywheel so that the calibration tool contacts the magnet assembly.\n"); delay_message(5, 1000); //base = analogRead(RESISTANCE_PIN); // Baseline reading - should increase from here - RESPOND("\nRotate the magnet assembly by hand so that the magnet settles into the pocket in the calibration tool."); - RESPOND(" Do not use the lever!\n"); + CONSOLE_RESP("\nRotate the magnet assembly by hand so that the magnet settles into the pocket in the calibration tool."); + CONSOLE_RESP(" Do not use the lever!\n"); delay_message(5, 1000); - RESPOND("\n\nHold while readings are taken...\n"); + CONSOLE_RESP("\n\nHold while readings are taken...\n"); uint32_t cal_reading = 0; for (uint8_t i = 10; i > 0; i--) { reading = analogRead(RESISTANCE_PIN); - BLE_PRINT(" %d \n", reading); + CONSOLE_PRINT(" %d \n", reading); cal_reading += reading; delay(200); } cal_reading /= 10; - BLE_PRINT("Done. Average was %d.\n", cal_reading); + CONSOLE_PRINT("Done. Average was %d.\n", cal_reading); new_offset = cal_reading - CALTOOL_RES/res_factor; - BLE_PRINT("\nThe new offset is %.1f.\n", new_offset); - RESPOND("Use activate to have the bike use these values.\n\n"); + CONSOLE_PRINT("\nThe new offset is %.1f.\n", new_offset); + CONSOLE_RESP("Use activate to have the bike use these values.\n\n"); awaiting_conf = AWAITING_NONE; } else { - RESPOND("Enter the calibration procedure.\n"); - RESPOND("This will set the offset to match the bike.\n"); - RESPOND("If for any reason you need to change the factor, enter and activate it *before* proceeding.\n"); - RESPOND("\nBe sure that the resistance lever is at the bottom (lowest gear)"); - RESPOND(" and have the calibration tool ready to place on the flywheel.\n"); - RESPOND("Y to continue..."); + CONSOLE_RESP("Enter the calibration procedure.\n"); + CONSOLE_RESP("This will set the offset to match the bike.\n"); + CONSOLE_RESP("If for any reason you need to change the factor, enter and activate it *before* proceeding.\n"); + CONSOLE_RESP("\nBe sure that the resistance lever is at the bottom (lowest gear)"); + CONSOLE_RESP(" and have the calibration tool ready to place on the flywheel.\n"); + CONSOLE_RESP("Y to continue..."); awaiting_conf = cmd_number; awaiting_timer = CONFIRMATION_TIMEOUT ; @@ -1354,14 +1141,14 @@ void cmd_activate() { res_offset = new_offset; res_factor = new_factor; - RESPOND("\nActivate factor and offset confirmed.\n"); - BLE_PRINT("Offset %.1f; factor %.4f\n\n", res_offset, res_factor); + CONSOLE_RESP("\nActivate factor and offset confirmed.\n"); + CONSOLE_PRINT("Offset %.1f; factor %.4f\n\n", res_offset, res_factor); awaiting_conf = AWAITING_NONE; } else { - BLE_PRINT("Factor will be %.4f and offset will be %.1f .\n", new_factor, new_offset); - RESPOND("Y to make these the active values..."); + CONSOLE_PRINT("Factor will be %.4f and offset will be %.1f .\n", new_factor, new_offset); + CONSOLE_RESP("Y to make these the active values..."); awaiting_conf = cmd_number; awaiting_timer = CONFIRMATION_TIMEOUT ; } @@ -1374,19 +1161,19 @@ void cmd_write() if (write_param_file("offset", &res_offset, sizeof(res_offset)) & write_param_file("factor", &res_factor, sizeof(res_factor)) ) { - RESPOND("\nWrite confirmed.\n"); + CONSOLE_RESP("\nWrite confirmed.\n"); } else { - RESPOND("\Failed to write the files.\n"); + CONSOLE_RESP("\Failed to write the files.\n"); } awaiting_conf = AWAITING_NONE; } else { - RESPOND("Currently active factor and offset are...\n"); - BLE_PRINT("Offset %.1f; factor %.4f\n", res_offset, res_factor); - RESPOND("Y to write these to the file..."); + CONSOLE_RESP("Currently active factor and offset are...\n"); + CONSOLE_PRINT("Offset %.1f; factor %.4f\n", res_offset, res_factor); + CONSOLE_RESP("Y to write these to the file..."); awaiting_conf = cmd_number; awaiting_timer = CONFIRMATION_TIMEOUT ; } @@ -1397,18 +1184,18 @@ void cmd_read() if (read_param_file("offset", &new_offset, sizeof(new_offset)) & read_param_file("factor", &new_factor, sizeof(new_factor))) { - RESPOND("Read from the parameter files:"); - BLE_PRINT("Offset %.1f; factor %.4f\n", new_offset, new_factor); - RESPOND("Y to write these to the file..."); + CONSOLE_RESP("Read from the parameter files:"); + CONSOLE_PRINT("Offset %.1f; factor %.4f\n", new_offset, new_factor); + CONSOLE_RESP("Y to write these to the file..."); } } void cmd_help() { - RESPOND("\n"); + CONSOLE_RESP("\n"); for (int i = 0; i < n_cmds; i++) { - BLE_PRINT("%s - %s \n", cmd_table[i].cmd, cmd_table[i].help); + CONSOLE_PRINT("%s - %s \n", cmd_table[i].cmd, cmd_table[i].help); // RESPOND(cmd_table[i].cmd); // RESPOND(" - "); // RESPOND(cmd_table[i].help); @@ -1425,7 +1212,7 @@ void process_cmd() cmd_table[awaiting_conf].cmdHandler(); } else { - RESPOND("Canceled.\n"); + CONSOLE_RESP("Canceled.\n"); awaiting_conf = AWAITING_NONE; } //awaiting_conf = AWAITING_NONE; // Each handler needs to reset awaiting_conf @@ -1442,18 +1229,18 @@ void process_cmd() return; } } - RESPOND("Not a valid command."); + CONSOLE_RESP("Not a valid command."); cmd_help(); return; } -void bleuart_check(void *notUsed) +void console_check(void *notUsed) { uint32_t puNotificationValue; while (1) { xTaskNotifyWait(0x00, portMAX_DELAY, &puNotificationValue, DELAY_FOREVER); // Just sit here, blocked, until update() notifies us - bleuart_take_input(); // Take in whatever's there + console_take_input(); // Take in whatever's there if (new_input) { process_cmd(); @@ -1462,11 +1249,11 @@ void bleuart_check(void *notUsed) } else if (AWAITING_CONF()) { - RESPOND("."); + CONSOLE_RESP("."); if (--awaiting_timer == 0) { awaiting_conf = AWAITING_NONE; - RESPOND("Cancelled.\n\n"); + CONSOLE_RESP("Cancelled.\n\n"); console_clear(); } } @@ -1496,10 +1283,6 @@ void update(TimerHandle_t xTimerID) // Things that happens on every tick ------------------------------------------------------------------------------------- update_resistance(); -#ifdef USE_SERIAL - //serial_check(); // Can't do this here in its current form because it can take longer than the timer interval -#endif - // Things happen at the default tick interval ---------------------------------------------------------------------------- if ((ticker % DEFAULT_TICKS) == 0) { @@ -1513,9 +1296,9 @@ void update(TimerHandle_t xTimerID) updateBLE(); } -#ifdef BLEUART - // NOTE: It may be OK to just unblock. The if() is intended to avoid the overhead of the task notify - if (bleuart.available() || AWAITING_CONF()) xTaskNotifyGive(ble_task_handle); // Unblock bleuart_check() +#if defined(BLEUART) || defined(USE_SERIAL) + // If there's console input, select the corresponding source and unblock the handler + if (console_source_check() || AWAITING_CONF()) xTaskNotifyGive(console_task_handle); #endif // Things that happen on BATT_TICKS ------------------------------------------------------------------------------------ diff --git a/TODO.md b/TODO.md index 1d3833c..b46e6df 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,4 @@ ## TODO -- Console access - - Option to use conventional USB serial instead of the spiffier BLE Uart service - Documentation - More comprehensive writeup of calibration - Tutorial how-to-build for less experienced folks @@ -21,10 +19,8 @@ - More comprehensive code cleanup - Split code across multiple source files - Clean up code in connect callbacks - - Consistent use of globals rather than passed parameters for BLE characteristic data. - Review for functions that should be inline - C++ style for C-style #defines and macros where appropriate - ONE DAY? - Servo on the resistance for full FTMS function! diff --git a/globals.h b/globals.h index b4894b7..94d493d 100644 --- a/globals.h +++ b/globals.h @@ -8,15 +8,6 @@ #include // nrf52 built-in bluetooth #include "options.h" -// BLE data blocks addressable as bytes for flags and words for data -union ble_data_block -{ // Used to set flag bytes (0, 1) and data uint16's (words 1, ...) - uint8_t bytes[2]; - uint16_t words[16]; -}; -union ble_data_block bike_data; // For the Bike Data Characteristic (FiTness Machine Service) -union ble_data_block power_data; // For the Cycling Power Measurement Characteristic (Cycling Power Service) - // Globals. Variable accessed in multiple places are declared here. // Those used only in specific functions are declared within or nearby. @@ -76,8 +67,39 @@ BLEDis bledis; // DIS (Device Information Service) helper class instance #ifdef BLEBAS BLEBas blebas; // BAS (Battery Service) helper class instance #endif + #ifdef BLEUART -BLEUart bleuart; // UART over BLE +#define BLEUART_MAX_MSG 20 + +/* +class cBLEUart : public BLEUart +{ + //using BLEUart::BLEUart; +public: + size_t write(const uint8_t *content, size_t len); +}; + +size_t cBLEUart::write(const uint8_t *content, size_t len) // Needs to match a virtual function in BLEUart +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + size_t index = 0; + size_t last = len; + while (last - index >= BLEUART_MAX_MSG) + { + BLEUart::write(content + index, BLEUART_MAX_MSG); + index += BLEUART_MAX_MSG; + } + if (index < last) + { + BLEUart::write(content + index, last - index); + } +} + +cBLEUart bleuart; //(int)BLEUART_MAX_MSG); +*/ + +BLEUart bleuart; #endif -#endif \ No newline at end of file +#endif diff --git a/options.h b/options.h index 506ea73..7563e5e 100644 --- a/options.h +++ b/options.h @@ -72,8 +72,6 @@ bike, so enabling it is recommended. Presently, the BLEUart interface is fully developed, while the serial interface is not. */ -//#define USE_SERIAL // Incorporate USB serial functions. Will attempt serial connection at startup. - // CAREFUL! With 1/sec updates through a timer task, it must always finish in < 1 sec. -#define BLEUART // Activates serial over BLE -#define BLEBAS // Activate BLE battery service - +#define USE_SERIAL // Incorporate USB serial functions including the console and any debugging. +define BLEUART // Activates serial over BLE +#define BLEBAS // Activate BLE battery service From e605b3b7411773e1785d42682ce9691d8ace79e2 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Sat, 24 Jul 2021 16:00:39 -0400 Subject: [PATCH 05/11] Fixed error in the cBLEUart class --- KBikeBLE.ino | 57 +++++++++++----------------------------- globals.h | 22 +++++++++------- options.h | 8 +++++- serial_commands.h | 7 ++++- workspace.code-workspace | 11 +++++++- 5 files changed, 51 insertions(+), 54 deletions(-) diff --git a/KBikeBLE.ino b/KBikeBLE.ino index bf4567f..490b2da 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -984,38 +984,16 @@ void console_take_input() } } -#define BLEUART_MAX_MSG 20 +// void console_send(const char * message) +// { +// console->write((uint8_t*) message, strlen(message)); +// } -// Send a (relatively) lengthy message over BLE. -// BLEUart messages are limited to the BLE MTU - 3. The bleuart library print/write functions will truncate -// anything longer. So we need our own function to send longer strings in pieces. -// For simplicity (right now) this is imposed upon both BLE and Serial. -void console_send(const char * message) -{ - size_t index = 0; - size_t last = strlen(message); - while (last - index >= BLEUART_MAX_MSG) - { - console->write(message + index, BLEUART_MAX_MSG); - index += BLEUART_MAX_MSG; - } - if (index < last) - { - console->write(message + index, last - index); - } -} -/* -void console_send(const char * message) -{ - console->write((uint8_t*) message, strlen(message)); - //console->cBLEUart::write(message, strlen(message)); -} -*/ #define PBLEN 128 char pbuffer[PBLEN]; -#define CONSOLE_RESP(string) console_send(string) +#define CONSOLE_RESP(string) console->write((uint8_t *) string, strlen(string)) //console_send(string) #define CONSOLE_PRINT(format, args...) snprintf(pbuffer, PBLEN, format, args); \ - console_send(pbuffer) + console->write((uint8_t *) pbuffer, strlen(pbuffer)) //console_send(pbuffer) void cmd_batt() { @@ -1077,7 +1055,7 @@ void cmd_res() { new_offset = RESISTANCE_OFFSET; new_factor = RESISTANCE_FACTOR; - CONSOLE_RESP("\nDefaults...\n"); + CONSOLE_RESP("Defaults...\n"); CONSOLE_PRINT("New offset will be %.1f .\n", new_offset); CONSOLE_PRINT("New factor will be %.4f .\n", new_factor); CONSOLE_RESP("Use activate to have the bike use these values.\n\n"); @@ -1118,7 +1096,8 @@ void cmd_res() CONSOLE_PRINT("Done. Average was %d.\n", cal_reading); new_offset = cal_reading - CALTOOL_RES/res_factor; CONSOLE_PRINT("\nThe new offset is %.1f.\n", new_offset); - CONSOLE_RESP("Use activate to have the bike use these values.\n\n"); + CONSOLE_RESP("\nUse activate to have the bike use these values."); + CONSOLE_RESP("If the readings weren't consistent, you can try again.\n\n"); awaiting_conf = AWAITING_NONE; } else @@ -1131,7 +1110,6 @@ void cmd_res() CONSOLE_RESP("Y to continue..."); awaiting_conf = cmd_number; awaiting_timer = CONFIRMATION_TIMEOUT ; - } } @@ -1141,7 +1119,7 @@ void cmd_activate() { res_offset = new_offset; res_factor = new_factor; - CONSOLE_RESP("\nActivate factor and offset confirmed.\n"); + CONSOLE_RESP("\nActivate factor and offset confirmed.\n\n"); CONSOLE_PRINT("Offset %.1f; factor %.4f\n\n", res_offset, res_factor); awaiting_conf = AWAITING_NONE; } @@ -1161,11 +1139,11 @@ void cmd_write() if (write_param_file("offset", &res_offset, sizeof(res_offset)) & write_param_file("factor", &res_factor, sizeof(res_factor)) ) { - CONSOLE_RESP("\nWrite confirmed.\n"); + CONSOLE_RESP("\nWrite confirmed.\n\n"); } else { - CONSOLE_RESP("\Failed to write the files.\n"); + CONSOLE_RESP("\nFailed to write the files.\n\n"); } awaiting_conf = AWAITING_NONE; } @@ -1185,8 +1163,7 @@ void cmd_read() read_param_file("factor", &new_factor, sizeof(new_factor))) { CONSOLE_RESP("Read from the parameter files:"); - CONSOLE_PRINT("Offset %.1f; factor %.4f\n", new_offset, new_factor); - CONSOLE_RESP("Y to write these to the file..."); + CONSOLE_PRINT("Offset %.1f; factor %.4f\n\n", new_offset, new_factor); } } @@ -1196,11 +1173,8 @@ void cmd_help() for (int i = 0; i < n_cmds; i++) { CONSOLE_PRINT("%s - %s \n", cmd_table[i].cmd, cmd_table[i].help); - // RESPOND(cmd_table[i].cmd); - // RESPOND(" - "); - // RESPOND(cmd_table[i].help); - // RESPOND("\n"); } + CONSOLE_RESP("\n"); } void process_cmd() @@ -1298,7 +1272,8 @@ void update(TimerHandle_t xTimerID) #if defined(BLEUART) || defined(USE_SERIAL) // If there's console input, select the corresponding source and unblock the handler - if (console_source_check() || AWAITING_CONF()) xTaskNotifyGive(console_task_handle); + // Note the order of operations: Don't switch sources while awaiting confirmation. + if (AWAITING_CONF() || console_source_check()) xTaskNotifyGive(console_task_handle); #endif // Things that happen on BATT_TICKS ------------------------------------------------------------------------------------ diff --git a/globals.h b/globals.h index 94d493d..60d5b98 100644 --- a/globals.h +++ b/globals.h @@ -71,7 +71,9 @@ BLEBas blebas; // BAS (Battery Service) helper class instance #ifdef BLEUART #define BLEUART_MAX_MSG 20 -/* +// Child class of BLEUart to provide BLEUart with write(uint8_t *, size_t) +// that behaves the same as the equivalent in Serial. Allows us to define +// console as a pointer to Stream that works with both BLEUart and Serial. class cBLEUart : public BLEUart { //using BLEUart::BLEUart; @@ -79,27 +81,27 @@ class cBLEUart : public BLEUart size_t write(const uint8_t *content, size_t len); }; -size_t cBLEUart::write(const uint8_t *content, size_t len) // Needs to match a virtual function in BLEUart +// Piecewise unbuffered write of longer messages +// The parent unbuffered BLEUart::write() truncates messages longer than can +// fit in one packet, while buffered writes work differently from Serial. +size_t cBLEUart::write(const uint8_t *content, size_t len) { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); - size_t index = 0; - size_t last = len; + const uint8_t * index = content; + const uint8_t * last = content + len; while (last - index >= BLEUART_MAX_MSG) { - BLEUart::write(content + index, BLEUART_MAX_MSG); + BLEUart::write(index, BLEUART_MAX_MSG); index += BLEUART_MAX_MSG; } if (index < last) { - BLEUart::write(content + index, last - index); + BLEUart::write(index, last - index); } + return len; } cBLEUart bleuart; //(int)BLEUART_MAX_MSG); -*/ -BLEUart bleuart; #endif #endif diff --git a/options.h b/options.h index 7563e5e..14908b2 100644 --- a/options.h +++ b/options.h @@ -1,5 +1,8 @@ // Options of most interest to the end user +#ifndef OPTIONS_ +#define OPTIONS_ + /* Filters on the resistance value. Bike resistance measurements can be noisy. Filtered value is (FILTER * last + new)/(FILTER + 1) @@ -73,5 +76,8 @@ serial interface is not. */ #define USE_SERIAL // Incorporate USB serial functions including the console and any debugging. -define BLEUART // Activates serial over BLE +#define BLEUART // Activates serial over BLE #define BLEBAS // Activate BLE battery service + + +#endif \ No newline at end of file diff --git a/serial_commands.h b/serial_commands.h index f1126bf..0d68340 100644 --- a/serial_commands.h +++ b/serial_commands.h @@ -1,5 +1,8 @@ // The parser - calls the appropriate function; leaves argument parsing to that function +#ifndef SERIAL_COMMANDS_ +#define SERIAL_COMMANDS_ + typedef void cmdHandler_t(); // All handlers take nothing and return nothing struct cmd_table_t { @@ -54,4 +57,6 @@ void test() curr_cmd_set = &cmd_set; // Activate a command set curr_cmd_set->cmd_table[1].cmdHandler(); // Call a member of the set in use } -*/ \ No newline at end of file +*/ + +#endif \ No newline at end of file diff --git a/workspace.code-workspace b/workspace.code-workspace index 78cc355..6b6dc2e 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -9,7 +9,16 @@ ], "settings": { "files.associations": { - "iostream": "cpp" + "iostream": "cpp", + "ostream": "cpp", + "*.tcc": "cpp", + "exception": "cpp", + "memory_resource": "cpp", + "fstream": "cpp", + "iosfwd": "cpp", + "new": "cpp", + "sstream": "cpp", + "streambuf": "cpp" }, "telemetry.enableCrashReporter": false } From 28c7894bc172e5acc789bf0f0f78dc8a2138925b Mon Sep 17 00:00:00 2001 From: ajs123 Date: Mon, 26 Jul 2021 13:14:34 -0400 Subject: [PATCH 06/11] Some serial console improvements --- KBikeBLE.ino | 22 +++++++++++++++++++--- param_store.cpp | 9 +++++++++ param_store.h | 14 ++------------ serial_commands.h | 4 ++-- workspace.code-workspace | 3 ++- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 490b2da..6ed2cd9 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -1004,9 +1004,19 @@ void cmd_batt() void cmd_res() { uint32_t last_resistance = raw_resistance; - for (uint8_t i = 100; i > 0; i--) // Runs for 100 samples or until interrupted by input + uint32_t sum_resistance = 0; + uint8_t n_resistance = 0; + uint8_t max_n = 100; // Default + + if (arg[0] != 0) + { + max_n = min(0xFF, max(atoi(arg), 1)); + } + for (uint8_t i = max_n; i > 0; i--) // Runs for 100 samples or until interrupted by input { - if (raw_resistance != last_resistance) + sum_resistance += raw_resistance; + n_resistance++; + if (true) //raw_resistance != last_resistance) { CONSOLE_PRINT("Raw ADC value %d\n", raw_resistance); CONSOLE_PRINT("Resistance %.1f%%\n", resistance); @@ -1020,6 +1030,11 @@ void cmd_res() } delay(TICK_INTERVAL); } + if (n_resistance > 0) + { + CONSOLE_PRINT("%d measurements.\n", n_resistance); + CONSOLE_PRINT("Average ADC value %.1f\n\n", (float)sum_resistance / n_resistance); + } } void cmd_showcal() @@ -1162,8 +1177,9 @@ void cmd_read() if (read_param_file("offset", &new_offset, sizeof(new_offset)) & read_param_file("factor", &new_factor, sizeof(new_factor))) { - CONSOLE_RESP("Read from the parameter files:"); + CONSOLE_RESP("Read from the parameter files:\n"); CONSOLE_PRINT("Offset %.1f; factor %.4f\n\n", new_offset, new_factor); + CONSOLE_RESP("Use activate to have the bike use these values.\n\n"); } } diff --git a/param_store.cpp b/param_store.cpp index 916a3c1..18b05df 100644 --- a/param_store.cpp +++ b/param_store.cpp @@ -3,6 +3,9 @@ #include #include +#include "param_store.h" +#include "options.h" + #define PARAM_FOLDER "/KBikeBLE" inline void make_path(char * dest, const char * path, const char * file) { @@ -14,6 +17,12 @@ inline void make_path(char * dest, const char * path, const char * file) using namespace Adafruit_LittleFS_Namespace; File file(InternalFS); +// Construct a full file path from the directory path and filename +// dest = the full file path +// path = directory path +// file = file name +inline void make_path(char * dest, const char * path, const char * file); + // Start the InternalFS and ensure that the parameter folder is there. void setup_InternalFS(void) { diff --git a/param_store.h b/param_store.h index 9d0d577..e63286f 100644 --- a/param_store.h +++ b/param_store.h @@ -1,3 +1,5 @@ +// Parameter storage using the LittleFS flash memory filesystem + #ifndef PARAM_STORE_ #define PARAM_STORE_ #include @@ -6,21 +8,9 @@ #define PARAM_FOLDER "/KBikeBLE" -// Construct a full file path from the directory path and filename -// dest = the full file path -// path = directory path -// file = file name -inline void make_path(char * dest, const char * path, const char * file); - -//using namespace Adafruit_LittleFS_Namespace; -//File file(InternalFS); - // Start the InternalFS and ensure that the parameter folder is there. void setup_InternalFS(void); -//byte stored_value[8]; -//char filepath[LFS_NAME_MAX + 1]; - // Read a parameter from InternalFS. // name = filename (typically, the name of the variable) // value = receives the value diff --git a/serial_commands.h b/serial_commands.h index 0d68340..e468824 100644 --- a/serial_commands.h +++ b/serial_commands.h @@ -16,7 +16,7 @@ extern cmdHandler_t cmd_batt, cmd_res, cmd_showcal, cmd_factor, cmd_offset, cmd_ const cmd_table_t PROGMEM cmd_table[] = { {"batt", cmd_batt, "Show battery status."}, - {"res", cmd_res, "Monitor ADC readings. Any input to stop."}, + {"res", cmd_res, "optional Monitor ADC readings - indicated number, or any input to stop."}, {"showcal", cmd_showcal, "Show the calibration currently in use."}, {"factor", cmd_factor, " Enter a new cal factor (should not be necessary)."}, {"offset", cmd_offset, " Enter a new calibration offset (follow with activate)."}, @@ -59,4 +59,4 @@ void test() } */ -#endif \ No newline at end of file +#endif diff --git a/workspace.code-workspace b/workspace.code-workspace index 6b6dc2e..a901f8f 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -18,7 +18,8 @@ "iosfwd": "cpp", "new": "cpp", "sstream": "cpp", - "streambuf": "cpp" + "streambuf": "cpp", + "cstdlib": "cpp" }, "telemetry.enableCrashReporter": false } From 067b34b9bd22465477d5f3b55e6cdc5a72acd8f5 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Fri, 30 Jul 2021 07:41:04 -0400 Subject: [PATCH 07/11] Tweaks to ADC config --- KBikeBLE.ino | 115 ++++++++++++++++++++++++++++++++++----- TODO.md | 7 ++- bike_interface.h | 2 +- calibration.h | 8 ++- options.h | 58 ++++++++++++++------ serial_commands.h | 8 ++- workspace.code-workspace | 5 +- 7 files changed, 163 insertions(+), 40 deletions(-) diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 6ed2cd9..4c68bca 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -21,6 +21,7 @@ #include "calibration.h" #include "serial_commands.h" #include "param_store.h" +//#include "KB_analog.h" /********************************************************************** * Optional functions and debugging @@ -107,9 +108,9 @@ void right_just(uint16_t number, int x, int y, int width) // This works for the current font, but it ought to count pixels rather than characters. { if (number < 10) - x = x + width; + x += width; if (number < 100) - x = x + width; + x += width; display.setCursor(x, y); display.print(number); } @@ -124,8 +125,6 @@ void draw_batt(uint8_t pct) { display.drawFrame(BATT_POS_X, BATT_POS_Y, BWIDTH, BHEIGHT); // Battery body display.drawBox(BATT_POS_X + BWIDTH, BATT_POS_Y + ((BHEIGHT - BUTTON) / 2), BUTTON, BUTTON); // Battery "button" - //display.setDrawColor(0); - //display.drawBox(BATT_POS_X + 2, BATT_POS_Y + 2, BWIDTH - 4, BHEIGHT - 4); display.setDrawColor(1); display.drawBox(BATT_POS_X + 2, BATT_POS_Y + 2, roundpct(pct, (BWIDTH - 4)), (BHEIGHT - 4)); // Filled area proportional to charge estimate } @@ -189,11 +188,35 @@ void display_numbers() /********************************************************************************* Analog input processing - resistance magnet position and battery * ********************************************************************************/ +//#define analogReference KB_analog::analogReference +//#define analogOversampling KB_analog::analogOversampling +//#define analogSampleTime KB_analog::analogSampleTime +//#define analogReadResolution KB_analog::analogReadResolution +//#define analogRead KB_analog::analogRead + +eAnalogReference analog_reference = AR_INTERNAL; + +void ADC_calibrate_offset() // Periodic calibration of the ADC +{ + digitalWrite(PIN_LED1, HIGH); + NRF_SAADC->EVENTS_CALIBRATEDONE = 0; + NRF_SAADC->TASKS_CALIBRATEOFFSET = SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Trigger; + delay(1); + //while (!NRF_SAADC->EVENTS_CALIBRATEDONE) delay(1); // This should work + if (!NRF_SAADC->EVENTS_CALIBRATEDONE) delay(1000); + NRF_SAADC->EVENTS_CALIBRATEDONE = 0; + digitalWrite(PIN_LED1, LOW); +} void ADC_setup() // Set up the ADC for ongoing resistance measurement { - analogReference(AR_INTERNAL); // 3.6V + analogReference(analog_reference); // 3.6V + ADC_calibrate_offset(); + //analogReference(AR_VDD4); // VDD = nominal 3.3V - Resistance readings will be proportional to high side voltage analogOversampling(ANALOG_OVERSAMPLE); + #ifdef SAADC_TACQ + analogSampleTime(20); + #endif analogReadResolution(10); // 10 bits for better gear delineation and for battery measurement delay(1); // Let the ADC settle before any measurements. Only important if changing the reference or possibly resolution } @@ -669,11 +692,12 @@ void suspend() #else /* Enter the power save state by stopping the update() task. + If full = true, also de-energize the resistance sense pot Resume by re-starting it just as in Setup(). */ -void suspend() +void suspend(bool full = true) { - digitalWrite(RESISTANCE_TOP, LOW); // De-energize the resistance pot + if (full) digitalWrite(RESISTANCE_TOP, LOW); // De-energize the resistance pot update_timer.stop(); suspended = true; } @@ -681,6 +705,7 @@ void suspend() void resume() { digitalWrite(RESISTANCE_TOP, HIGH); + ADC_calibrate_offset(); update_timer.start(); suspended = false; } @@ -731,10 +756,13 @@ void lever_check() // Moving the gear lever to the top switches the resistance/g void update_resistance() { raw_resistance = analogRead(RESISTANCE_PIN); // ADC set to oversample + //raw_pot_top = analogRead(RESISTANCE_TOP); + //raw_pot_wiper = analogRead(RESISTANCE_PIN); + //raw_resistance = raw_pot_wiper * ( ( (3300 * 1024) + 1800) / 3600 ) / raw_pot_top; // (3.3 V Vdd)/(3.6 V Vref)*(ADC counts), rounded inst_resistance = max((raw_resistance - res_offset) * res_factor, 0); - resistance = (RESISTANCE_FILTER * resistance + inst_resistance) / (RESISTANCE_FILTER + 1); + resistance = (RESISTANCE_FILTER * resistance + 2 * inst_resistance) / (RESISTANCE_FILTER + 2); gear = gear_lookup(resistance); - disp_resistance = (RESISTANCE_DISPLAY_FILTER * resistance + inst_resistance) / (RESISTANCE_DISPLAY_FILTER + 1); + disp_resistance = (RESISTANCE_DISPLAY_FILTER * resistance + 2 * inst_resistance) / (RESISTANCE_DISPLAY_FILTER + 2); //resistance_sq = resistance * resistance; //gear = max(floor(GC + GB * resistance + GA * resistance_sq), 1) ; lever_check(); @@ -742,7 +770,18 @@ void update_resistance() void update_battery() { + + if (analog_reference != AR_INTERNAL) + { + analogReference(AR_INTERNAL); + delay(1); + } batt_mvolts = analogRead(BATTERY_PIN) * VBAT_MV_PER_LSB; + if (analog_reference != AR_INTERNAL) + { + analogReference(analog_reference); + delay(1); + } if (batt_mvolts < 3300) // LiPo model... batt_pct = 0.0; // Dead at 3.3V @@ -853,11 +892,11 @@ void updateBLE() } #if defined(BLEUART) || defined(USE_SERIAL) -#define SBUF_LEN 20 -char sbuffer[SBUF_LEN]; // Serial console input buffer -char cmd[SBUF_LEN]; // Tokenized command -char arg[SBUF_LEN]; // Tokenized argument -uint8_t sbix; // Current index into input buffer +#define SBUF_LEN 40 +char sbuffer[SBUF_LEN]; // Serial console input buffer +char cmd[SBUF_LEN]; // Tokenized command +char arg[SBUF_LEN]; // Tokenized argument +uint8_t sbix; // Current index into input buffer bool cmd_rcvd; // Console state: command received bool new_input; // Console state: new input ready to process uint8_t cmd_number; // Current command (= position in the table) @@ -1008,6 +1047,10 @@ void cmd_res() uint8_t n_resistance = 0; uint8_t max_n = 100; // Default + double mean = 0; + double M2 = 0; + double variance = 0; + if (arg[0] != 0) { max_n = min(0xFF, max(atoi(arg), 1)); @@ -1022,6 +1065,11 @@ void cmd_res() CONSOLE_PRINT("Resistance %.1f%%\n", resistance); CONSOLE_PRINT("Keiser gear number %d\n\n", gear); last_resistance = raw_resistance; + + double delta = raw_resistance - mean; + mean += delta / n_resistance; + M2 += delta * (raw_resistance - mean); + variance = M2 / n_resistance; } if (console->available()) { @@ -1033,8 +1081,35 @@ void cmd_res() if (n_resistance > 0) { CONSOLE_PRINT("%d measurements.\n", n_resistance); - CONSOLE_PRINT("Average ADC value %.1f\n\n", (float)sum_resistance / n_resistance); + CONSOLE_PRINT("Average ADC value %.1f\n", (float)sum_resistance / n_resistance); + CONSOLE_PRINT("Estimated mean %.1f; SD %.1f . \n\n", mean, sqrt(variance)); + } +} + +void cmd_adcref() +{ + if (analog_reference == AR_INTERNAL) + { + analog_reference = AR_VDD4; + res_offset *= (3.6 / 3.3); + res_factor /= (3.6 / 3.3); + CONSOLE_RESP("ADC reference is now Vdd.\n\n"); + } + else + { + analog_reference = AR_INTERNAL; + res_offset *= (3.3 / 3.6); + res_factor /= (3.3 / 3.6); + CONSOLE_RESP("ADC reference is now internal 3.6V.\n\n"); } + analogReference(analog_reference); +} + +void cmd_adccal() +{ + CONSOLE_RESP("Doing ADC calibration..."); + ADC_calibrate_offset(); + CONSOLE_RESP("Done.\n\n"); } void cmd_showcal() @@ -1088,6 +1163,7 @@ void cmd_res() delay(time); } } + void cmd_cal() { if (AWAITING_CONF()) @@ -1099,6 +1175,8 @@ void cmd_res() CONSOLE_RESP(" Do not use the lever!\n"); delay_message(5, 1000); CONSOLE_RESP("\n\nHold while readings are taken...\n"); + + suspend(false); // Suspend updates so that resistance updates don't interfere, but leave the sense pot energized uint32_t cal_reading = 0; for (uint8_t i = 10; i > 0; i--) { @@ -1107,6 +1185,8 @@ void cmd_res() cal_reading += reading; delay(200); } + resume(); // Resume updates + cal_reading /= 10; CONSOLE_PRINT("Done. Average was %d.\n", cal_reading); new_offset = cal_reading - CALTOOL_RES/res_factor; @@ -1183,6 +1263,11 @@ void cmd_read() } } +void cmd_comment() +{ + CONSOLE_PRINT("%s\n\n", arg); +} + void cmd_help() { CONSOLE_RESP("\n"); diff --git a/TODO.md b/TODO.md index b46e6df..973557b 100644 --- a/TODO.md +++ b/TODO.md @@ -3,12 +3,15 @@ - More comprehensive writeup of calibration - Tutorial how-to-build for less experienced folks - Writeup on power consumption -- Consider using a different reference, e.g., Vdd ref for the pot to maintain cal near battery end of charge + - Better writeup on calibration +- Calibration + - Consider using a different reference, e.g., Vdd ref for the pot to maintain cal near battery end of charge + - Check for temperature dependence or other sources of variation - is SAADC offset calibration needed? - Keeping more parameters or options in the filesystem - Gear vs. Res% display - BLE Services - If continuing to support FTMS, implement a real model-based calc for speed (mph/kph) as a function of power and cadence. -- Improve the display: larger size or double area +- Improve the display: larger size with more data - Things maybe to add - Accumulated data - Miles - requires a model diff --git a/bike_interface.h b/bike_interface.h index 0b40e93..a27bfe4 100644 --- a/bike_interface.h +++ b/bike_interface.h @@ -12,5 +12,5 @@ //#define CRANK_PIN 7 // Pushbutton on the Adafruit nrf52840 Express, for debugging #define CRANK_PIN 9 #define RESISTANCE_PIN A1 -#define RESISTANCE_TOP 10 +#define RESISTANCE_TOP 10 // Should move this to A2 so that the voltage at the top can be measured #define BATTERY_PIN A6 \ No newline at end of file diff --git a/calibration.h b/calibration.h index 3de35cf..9921bea 100644 --- a/calibration.h +++ b/calibration.h @@ -17,14 +17,16 @@ // The factor shouldn't change unless there is a significant change in the total resistance of the sense pot, // the high side voltage, or the high side impedance. // The offset changes from bike to bike due to how the magnet assembly engages with the sense pot. -#define RESISTANCE_OFFSET 154.6 // From Raw ADC - Keiser graph regression +#define RESISTANCE_OFFSET 161.7 // From Raw ADC - Keiser graph regression #define RESISTANCE_FACTOR 0.2182 +//#define RESISTANCE_OFFSET 168.7 // 154.6 * (3.6/3.3) - For Vdd reference instead of 3.6V fixed +//#define RESISTANCE_FACTOR 0.2380 // 0.2182 * (3.6/3.3) // Calibration with the Keiser tool assumes that the normalized resistance is this value when the -// magnet is snug against the semicircular pocket in the tool +// magnet is snug against the semicircular pocket in the tool. This corresponds to the bottom +// edge of gear 14 in Keiser's graph. #define CALTOOL_RES 40 - // Power vs. resistance at reference cadence (90 RPM) as Ax^2 + Bx + C // The following, which is pretty good from Keiser gears 2-20, was abandonded in favor of a table lookup// //#define PC1 29.3 // Power_ref vs. R @ reference cadence (Ref cadence is 90 RPM) diff --git a/options.h b/options.h index 14908b2..4d2f301 100644 --- a/options.h +++ b/options.h @@ -3,22 +3,40 @@ #ifndef OPTIONS_ #define OPTIONS_ -/* Filters on the resistance value. - Bike resistance measurements can be noisy. - Filtered value is (FILTER * last + new)/(FILTER + 1) +/* Filters on the resistance value, to avoid flickering digits. --------------------------------------- + + Filtered value is (FILTER * last + 2 * new)/(FILTER + 2) where FILTER = the #defined value below last = last value new = new instantaneous value - Setting filter to 0 means no filtering (instantaneous measurements pass through). + Setting filter to... + 0 means no filtering (instantaneous measurements pass through). + 1 means the new value is 2/3 of the new measurement plus 1/3 of the old. + 2 means new = 1/2 of the new measurement + 1/2 of the old + ...etc. + Separate filters are provided for the display and for the power measurements. + Excessive filtering of the display can make it harder for the user to crisply + adjust the resistance. + + Values between 0 and 2 are recommended. Most BLE software will have its own filtering. + Some software may capture data such as max 5-second sustained power that could be + compromised by excessive filtering. + + If resistance values are really noisy, check electrical connections to the bike. + Prototyping connectors can be unreliable, and dirt can get into the connector + on the bike. Measured resistance changing when the display dims is a sign of + a poor Ground connection to the controller board. + NOTE: When displaying the Keiser gear number instead of resistance %, the resistance used in the power calculation is used to determine the gear. */ -#define RESISTANCE_FILTER 1 // Resistance used in power calculation and gear determination -#define RESISTANCE_DISPLAY_FILTER 1 // Resistance used in the resistance display +#define RESISTANCE_FILTER 0 // Resistance used in power calculation and gear determination +#define RESISTANCE_DISPLAY_FILTER 0 // Resistance used in the resistance display + +/* Power savings settings. ---------------------------------------------------------------------------- -/* Power savings settings. When the user isn't pedaling, the system will - dim the display, then - blank the display, then @@ -27,9 +45,12 @@ POWERSAVE determines whether and how the system saves power when not in use - 0 - Nothing beyond blanking the display and shutting down Bluetooth. - 1 - Shut down the system. Pedaling will then cause a full restart. - - Other - Power savings without shutdown. Presently, this involves lengthening the delay + - Other - Power savings without shutdown. between checks for pedal movement. + Because FreeRTOS does a good job of going into a low-power state when there's nothing to do, there's + little difference between POWERSAVE = 1 (shutdown) and POWERSAVE = 2 (just idle all tasks). + Times are in approximate seconds. For convenience, use N * 60 to specify N minutes. */ @@ -39,26 +60,29 @@ #define NO_BLE_PS_TIME 5 * 60 // In absence of Bluetooth connection and POWERSAVE defined, enter power save mode #define BLE_PS_TIME 15 * 60 // Disconnect Bluetooth and, if POWERSAVE defined, enter power save mode -/* Display contrast. For OLED displays, this sets the pixel current and therefore the brightness. +/* Display contrast. ---------------------------------------------------------------------------------- + For OLED displays, this sets the pixel current and therefore the brightness. Different OLED displays are affected differently by the contrast settings - Defaults are for a generic SH1106, which doesn't provide a very wide brightness range. + Defaults are for a generic SH1106, which doesn't provide a very wide brightness range. Higher values + can involve significantly higher current (battery usage) without much difference in brightness or clarity. */ -#define CONTRAST_FULL 127 // Full and reduced display brightness. These depend upon the display used. +#define CONTRAST_FULL 128 // Full and reduced display brightness. These depend upon the display used. #define CONTRAST_DIM 0 -/* Low battery indicator - flashes when below this percentage. - This ought to be determined by the level at which the proper ADC reference is lost. +/* Low battery indicator - flashes when below this percentage. ----------------------------------------- + This ought to be determined by the level at which the proper ADC reference is lost. For the + Adfruit Feather nrf52840 Express, things should work down to about 10%. */ #define BATT_LOW 20 -/* Display the Keiser gear number, or % resistance, at startup? +/* Display the Keiser gear number, or % resistance, at startup? --------------------------------------- The alternative can always be chosen by moving the resistance lever to the top. */ #define GEAR_DISPLAY true // true = gear, false = res % -/* Bluetooth options. +/* Bluetooth options. --------------------------------------------------------------------------------- The device connecting via Bluetooth is usually very close to the bike, so the power can be reduced. Set the transmit power to something that works reliably without wasting power. The blue LED blink rate can be changed as well but is probably not significant. @@ -78,6 +102,8 @@ #define USE_SERIAL // Incorporate USB serial functions including the console and any debugging. #define BLEUART // Activates serial over BLE #define BLEBAS // Activate BLE battery service +//#define SAADC_TACQ // Use the extension to the Adafruit nRF52 core to set ADC acquisition time + // - improves resistance measuresments but not yet part of the distribution -#endif \ No newline at end of file +#endif diff --git a/serial_commands.h b/serial_commands.h index e468824..dbe0e22 100644 --- a/serial_commands.h +++ b/serial_commands.h @@ -12,11 +12,14 @@ struct cmd_table_t { } ; // Define the command set -extern cmdHandler_t cmd_batt, cmd_res, cmd_showcal, cmd_factor, cmd_offset, cmd_cal, cmd_activate, cmd_write, cmd_read, cmd_defaults, cmd_help; +extern cmdHandler_t cmd_batt, cmd_res, cmd_adcref, cmd_adccal, cmd_showcal, cmd_factor, cmd_offset, cmd_cal, + cmd_activate, cmd_write, cmd_read, cmd_defaults, cmd_comment, cmd_help; const cmd_table_t PROGMEM cmd_table[] = { {"batt", cmd_batt, "Show battery status."}, - {"res", cmd_res, "optional Monitor ADC readings - indicated number, or any input to stop."}, + {"res", cmd_res, " Monitor ADC readings - indicated number, or any input to stop."}, + {"adcref", cmd_adcref, "Toggle the analog reference between default 3.6V and Vdd."}, + {"adccal", cmd_adccal, "Calibrate the ADC offset."}, {"showcal", cmd_showcal, "Show the calibration currently in use."}, {"factor", cmd_factor, " Enter a new cal factor (should not be necessary)."}, {"offset", cmd_offset, " Enter a new calibration offset (follow with activate)."}, @@ -25,6 +28,7 @@ const cmd_table_t PROGMEM cmd_table[] = { {"write", cmd_write, "Write currently active calibration to the calibration file. Requires confirmation."}, {"read", cmd_read, "Read calibration from the calibration file."}, {"defaults", cmd_defaults, "Set calibration to hard-coded defaults. Requires confirmation."}, + {"c", cmd_comment, "Enter a comment (shows in the terminal log."}, {"help", cmd_help, "This list."} } ; diff --git a/workspace.code-workspace b/workspace.code-workspace index a901f8f..ccb25f7 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -19,7 +19,10 @@ "new": "cpp", "sstream": "cpp", "streambuf": "cpp", - "cstdlib": "cpp" + "cstdlib": "cpp", + "nrf.h": "c", + "wiring_private.h": "c", + "nrfx.h": "c" }, "telemetry.enableCrashReporter": false } From 30071892467871af85fe83d712e09afb01f4fb30 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Tue, 3 Aug 2021 07:21:41 -0400 Subject: [PATCH 08/11] SAADC cal and log improvements --- CHANGELOG.md | 14 ++-- KBikeBLE.ino | 173 ++++++++++++++++++++++++++++----------- options.h | 7 +- workspace.code-workspace | 3 +- 4 files changed, 138 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9d891f..7fe6516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,8 +55,12 @@ - V0.17 - Calibration is now saved to nonvolatile memory (via LittleFS). If cal files aren't present, they're created from the defaults in calibration.h and written out to nonvolatile memory. Further changes are via the console. - Little U8G2 log screen at startup to show parameters read from or written to nonvolatile memory - - The command line interface now allows calibration values to be re-read from, or written to, nonvolatile memory. - - The command line interface now works via BLEUart or serial -- Moved globals to globals.h - start of some code cleanup - - Moved BLE service definitions and flags to BLE_services.h - - Cleaner struct definition for BLE data + - Bike resistance measurement + - Set the ADC sample time (TACQ) as recommended by Nordic for higher resistance sources (such as the 20K resistance sense pot). This reduces noise in resistance measurements. This required an addition to the Adafruit analog input core which is included on Adafruit v0.25 and the current Github master. If not available, comment out the #define for SAADC_TACQ. + - Calibration of the ADC offset - This is recommended by Nordic if the temperature could vary by more than 10 C. It's done at reset and at wakeup from low power mode. + - Improvements and additions to the command line interface + - Now allows calibration values to be re-read from, or written to, nonvolatile memory. + - Now works via BLEUart or serial. It will respond to whichever is the source of input. + - Moved globals to globals.h - start of some code cleanup + - Moved BLE service definitions and flags to BLE_services.h + - Cleaner struct definition for BLE data diff --git a/KBikeBLE.ino b/KBikeBLE.ino index 4c68bca..d76113d 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -21,7 +21,6 @@ #include "calibration.h" #include "serial_commands.h" #include "param_store.h" -//#include "KB_analog.h" /********************************************************************** * Optional functions and debugging @@ -35,9 +34,9 @@ #ifdef QUICKTIMEOUT #define DIM_TIME 20 //60 // Duration (sec) of no pedaling to dim display (if supported) -#define BLANK_TIME 30 //180 // Duration (sec) to blank display -#define BLE_PS_TIME 30 //900 // Duration (sec) to turn off Bluetooth to save power. Will disconnect from Central after this time. -#define POWERDOWN_TIME 40 //1200 // Duration (sec) to suspend the main loop pending a crank interrupt +#define BLANK_TIME 25 //180 // Duration (sec) to blank display +#define NO_BLE_PS_TIME 30 //900 // Duration (sec) to turn off Bluetooth to save power. Will disconnect from Central after this time. +#define BLE_PS_TIME 30 //1200 // Duration (sec) to suspend the main loop pending a crank interrupt #endif #if defined(USE_SERIAL) && defined(DEBUGGING) @@ -86,7 +85,6 @@ bool gear_display = GEAR_DISPLAY; #define DISPLAY_NUMBER_FONT u8g2_font_helvB24_tn // Log used at startup #define LOG_FONT u8g2_font_7x14_tf -#define LOG_FONT_BIG u8g2_font_7x14_tf #define LOG_WIDTH 9 #define LOG_HEIGHT 10 uint8_t u8log_buffer[LOG_WIDTH * LOG_HEIGHT]; @@ -94,14 +92,21 @@ U8G2LOG displog; void display_setup() { + // Start the display display.begin(); display.setContrast(CONTRAST_FULL); //display.enableUTF8Print(); // Can leave this out if using no symbols display.setFontMode(1); // "Transparent": Character background not drawn (since we clear the display anyway) + // Start the startup log display displog.begin(display, LOG_WIDTH, LOG_HEIGHT, u8log_buffer); displog.setLineHeightOffset(0); displog.setRedrawMode(0); + + display.setFont(LOG_FONT); + displog.print(F("KBikeBLE\n")); + displog.println(F(VERSION)); + displog.println(); } void right_just(uint16_t number, int x, int y, int width) @@ -188,37 +193,115 @@ void display_numbers() /********************************************************************************* Analog input processing - resistance magnet position and battery * ********************************************************************************/ -//#define analogReference KB_analog::analogReference -//#define analogOversampling KB_analog::analogOversampling -//#define analogSampleTime KB_analog::analogSampleTime -//#define analogReadResolution KB_analog::analogReadResolution -//#define analogRead KB_analog::analogRead eAnalogReference analog_reference = AR_INTERNAL; -void ADC_calibrate_offset() // Periodic calibration of the ADC +bool analogCalibrateOffset() // Periodic calibration of the ADC { - digitalWrite(PIN_LED1, HIGH); - NRF_SAADC->EVENTS_CALIBRATEDONE = 0; - NRF_SAADC->TASKS_CALIBRATEOFFSET = SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Trigger; - delay(1); - //while (!NRF_SAADC->EVENTS_CALIBRATEDONE) delay(1); // This should work - if (!NRF_SAADC->EVENTS_CALIBRATEDONE) delay(1000); - NRF_SAADC->EVENTS_CALIBRATEDONE = 0; - digitalWrite(PIN_LED1, LOW); + const uint32_t calibrate_done = ( (SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_Generated << + SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_Pos ) + && SAADC_EVENTS_CH_LIMITH_LIMITH_Msk ); + + const uint32_t calibrate_not_done = ( (SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_NotGenerated << + SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_Pos ) + && SAADC_EVENTS_CH_LIMITH_LIMITH_Msk ); + + const uint32_t saadc_enable = ( (SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos) + && SAADC_ENABLE_ENABLE_Msk ); + + const uint32_t saadc_disable = ( (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos) + && SAADC_ENABLE_ENABLE_Msk ); + + const uint32_t calibrate_trigger = ( (SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Trigger << + SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Pos) + && SAADC_TASKS_CALIBRATEOFFSET_TASKS_CALIBRATEOFFSET_Msk ); + + // Enable the SAADC + NRF_SAADC->ENABLE = saadc_enable; + + // Be sure the done flag is cleared, then trigger offset calibration + NRF_SAADC->EVENTS_CALIBRATEDONE = calibrate_not_done; + NRF_SAADC->TASKS_CALIBRATEOFFSET = calibrate_trigger; + + // Wait for completion + bool done = false; + for (int i = 0; (i < 20) && !(done = (NRF_SAADC->EVENTS_CALIBRATEDONE == calibrate_done)); i++ ) delay(1); + + // Clear the done flag (really shouldn't have to do this both times) + NRF_SAADC->EVENTS_CALIBRATEDONE = calibrate_not_done; + + // Disable the SAADC + NRF_SAADC->ENABLE = saadc_disable; + + return done; +} + +float get_SOC_temp() +{ + const uint32_t temp_ready = ( (TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Generated << TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Pos) + && TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Msk ); + + const uint32_t temp_not_ready = ( (TEMP_EVENTS_DATARDY_EVENTS_DATARDY_NotGenerated << TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Pos) + && TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Msk ); + + const uint32_t temp_trigger = ( (TEMP_TASKS_START_TASKS_START_Trigger << TEMP_TASKS_STOP_TASKS_STOP_Pos) + && TEMP_TASKS_START_TASKS_START_Msk ); + + const uint32_t temp_stop = ( (TEMP_TASKS_STOP_TASKS_STOP_Trigger << TEMP_TASKS_STOP_TASKS_STOP_Pos) + && TEMP_TASKS_STOP_TASKS_STOP_Msk ); + + uint8_t en; + int32_t temp; + if (sd_softdevice_is_enabled(&en)) + { + sd_temp_get(&temp); + return temp / 4.0; + } + else + { + NRF_TEMP->EVENTS_DATARDY = temp_not_ready; // Should not be needed + NRF_TEMP->TASKS_START = temp_trigger; + + bool done = false; + for (int i = 0; (i < 20) && !(done = (NRF_TEMP->EVENTS_DATARDY == temp_ready)); i++) delay(1); + + NRF_TEMP->TASKS_STOP = temp_stop; // Needed? + NRF_TEMP->EVENTS_DATARDY = temp_not_ready; + + if (done) return (NRF_TEMP->TEMP / 4.0F); + else return -40.0F; + } +} + +float averageADC() +{ + uint32_t raw_sum = 0; + const uint8_t n_samp = 20; + const uint8_t interval = 50; + for (uint8_t i = 0; i < n_samp; i++) + { + raw_sum += analogRead(RESISTANCE_PIN); + delay(interval); + } + return ((float) raw_sum) / n_samp; } void ADC_setup() // Set up the ADC for ongoing resistance measurement { - analogReference(analog_reference); // 3.6V - ADC_calibrate_offset(); - //analogReference(AR_VDD4); // VDD = nominal 3.3V - Resistance readings will be proportional to high side voltage + analogReference(analog_reference); analogOversampling(ANALOG_OVERSAMPLE); #ifdef SAADC_TACQ analogSampleTime(20); #endif analogReadResolution(10); // 10 bits for better gear delineation and for battery measurement delay(1); // Let the ADC settle before any measurements. Only important if changing the reference or possibly resolution + + displog.printf("\nT %.1f\n", get_SOC_temp()); + displog.printf("ADC %.1f\n", averageADC()); + if (analogCalibrateOffset()) displog.println("Cal done."); + else displog.println("Cal undone."); + displog.printf("ADC %.1f\n", averageADC()); + delay(5000); } /***************************************************************************************************** @@ -492,44 +575,33 @@ void crank_callback() ********************************************************************************/ void init_cal() { - display.setFont(LOG_FONT_BIG); - displog.print(F("KBikeBLE\n")); - displog.println(F(VERSION)); - displog.println(); - display.setFont(LOG_FONT); - if (!read_param_file("offset", &res_offset, sizeof(res_offset))) { res_offset = RESISTANCE_OFFSET; write_param_file("offset", &res_offset, sizeof(res_offset)); - displog.println(F("OFFSET")); - displog.println(F("DEFAULT")); - displog.println(F("WRITE:")); - displog.println(res_offset); + displog.println(F("OFFSET\nDEFAULT\n\ WRITE")); + displog.printf(" %.1f\n", res_offset); } else { - displog.println(F("OFFSET")); - displog.println(F(" READ:")); - displog.println(res_offset); + displog.println(F("OFFSET\n READ")); + displog.printf(" %.1f\n", res_offset); } + if (!read_param_file("factor", &res_factor, sizeof(res_factor))) { res_factor = RESISTANCE_FACTOR; write_param_file("factor", &res_factor, sizeof(res_factor)); - displog.println(F("\nFACTOR")); - displog.println(F("DEFAULT")); - displog.println(F("WRITE:")); - displog.printf("%.1f\n", res_factor); + displog.println(F("\nFACTOR\nDEFAULT\n WRITE")); + displog.printf(" %.4f\n", res_factor); } else { - displog.println(F("\nFACTOR")); - displog.println(F(" READ:")); - displog.printf("%.4f\n", res_factor); + displog.println(F("\nFACTOR\n READ")); + displog.printf(" %.4f\n", res_factor); } - delay(5000); + delay(2000); } /******************************************************************************** @@ -692,12 +764,12 @@ void suspend() #else /* Enter the power save state by stopping the update() task. - If full = true, also de-energize the resistance sense pot + If full = true, also de-energize the resistance sense pot. full = false is used Resume by re-starting it just as in Setup(). */ -void suspend(bool full = true) +void suspend() { - if (full) digitalWrite(RESISTANCE_TOP, LOW); // De-energize the resistance pot + digitalWrite(RESISTANCE_TOP, LOW); // De-energize the resistance pot update_timer.stop(); suspended = true; } @@ -705,7 +777,7 @@ void suspend(bool full = true) void resume() { digitalWrite(RESISTANCE_TOP, HIGH); - ADC_calibrate_offset(); + analogCalibrateOffset(); update_timer.start(); suspended = false; } @@ -1108,7 +1180,7 @@ void cmd_adcref() void cmd_adccal() { CONSOLE_RESP("Doing ADC calibration..."); - ADC_calibrate_offset(); + analogCalibrateOffset(); CONSOLE_RESP("Done.\n\n"); } @@ -1176,7 +1248,7 @@ void cmd_adccal() delay_message(5, 1000); CONSOLE_RESP("\n\nHold while readings are taken...\n"); - suspend(false); // Suspend updates so that resistance updates don't interfere, but leave the sense pot energized + update_timer.stop(); // Suspend updates so that resistance updates don't interfere uint32_t cal_reading = 0; for (uint8_t i = 10; i > 0; i--) { @@ -1185,7 +1257,8 @@ void cmd_adccal() cal_reading += reading; delay(200); } - resume(); // Resume updates + update_timer.start(); // Resume updates. Note: Since update() enables the console to run, we wouldn't + // be here if the timer were stopped. cal_reading /= 10; CONSOLE_PRINT("Done. Average was %d.\n", cal_reading); diff --git a/options.h b/options.h index 4d2f301..f341c80 100644 --- a/options.h +++ b/options.h @@ -87,12 +87,13 @@ Set the transmit power to something that works reliably without wasting power. The blue LED blink rate can be changed as well but is probably not significant. */ - #define BLE_TX_POWER -12 // BLE transmit power in dBm // Valid options for nRF52840: -40, -20, -16, -12, -8, -4, 0, +2, +3, +4, +5, +6, +7, or +8 #define BLE_LED_INTERVAL 1000 // ms -#define CONFIRMATION_TIMEOUT 15; // In the console, approx seconds to timeout of commands requiring confirmation +/* Console (command line interface) options. ---------------------------------------------------------- +*/ +#define CONFIRMATION_TIMEOUT 15; // Approx seconds to timeout of commands requiring confirmation /* Optional features and functions. The console interface allows the user to, among other things, calibrate the computer to the individual @@ -102,7 +103,7 @@ #define USE_SERIAL // Incorporate USB serial functions including the console and any debugging. #define BLEUART // Activates serial over BLE #define BLEBAS // Activate BLE battery service -//#define SAADC_TACQ // Use the extension to the Adafruit nRF52 core to set ADC acquisition time +#define SAADC_TACQ // Use the extension to the Adafruit nRF52 core to set ADC acquisition time // - improves resistance measuresments but not yet part of the distribution diff --git a/workspace.code-workspace b/workspace.code-workspace index ccb25f7..21ca254 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -22,7 +22,8 @@ "cstdlib": "cpp", "nrf.h": "c", "wiring_private.h": "c", - "nrfx.h": "c" + "nrfx.h": "c", + "wiring_constants.h": "c" }, "telemetry.enableCrashReporter": false } From 0507c711585a299681e1ac6fd00ad63e0748f5b2 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Tue, 3 Aug 2021 20:17:30 -0400 Subject: [PATCH 09/11] Read CPU temp and offset cal now in nRF52 library --- .vscode/c_cpp_properties.json | 54 +++++++++++++++++----------------- KBikeBLE.ino | 55 +++++++++++++++++++---------------- options.h | 3 ++ 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 63dc4bc..ea79186 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -24,32 +24,32 @@ "includePath": [ "/Users/alan/Library/Arduino15/packages/adafruit/tools/CMSIS/5.7.0/CMSIS/Core/Include/", "/Users/alan/Library/Arduino15/packages/adafruit/tools/CMSIS/5.7.0/CMSIS/DSP/Include/", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx/hal", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx/mdk", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx/soc", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx/drivers/include", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/nrfx/drivers/src", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/softdevice/s140_nrf52_6.1.1_API/include", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/nordic/softdevice/s140_nrf52_6.1.1_API/include/nrf52", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/freertos/Source/include", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/freertos/config", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/freertos/portable/GCC/nrf52", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/freertos/portable/CMSIS/nrf52", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/sysview/SEGGER", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/sysview/Config", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Adafruit_TinyUSB_Arduino/src/arduino", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/variants/feather_nrf52840_express", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Bluefruit52Lib/src", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Adafruit_nRFCrypto/src", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Adafruit_TinyUSB_Arduino/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/hal", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/mdk", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/soc", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/drivers/include", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/nrfx/drivers/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/softdevice/s140_nrf52_6.1.1_API/include", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/nordic/softdevice/s140_nrf52_6.1.1_API/include/nrf52", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/freertos/Source/include", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/freertos/config", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/freertos/portable/GCC/nrf52", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/freertos/portable/CMSIS/nrf52", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/sysview/SEGGER", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/sysview/Config", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Adafruit_TinyUSB_Arduino/src/arduino", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/variants/feather_nrf52840_express", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Bluefruit52Lib/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Adafruit_nRFCrypto/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Adafruit_TinyUSB_Arduino/src", "/Users/alan/Dropbox (Personal)/Arduino/libraries/U8g2/src", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/SPI", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Wire", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/Adafruit_LittleFS/src", - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/libraries/InternalFileSytem/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/SPI", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Wire", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/Adafruit_LittleFS/src", + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/libraries/InternalFileSytem/src", "/Users/alan/Library/Arduino15/packages/adafruit/tools/arm-none-eabi-gcc/9-2019q4/arm-none-eabi/include/c++/9.2.1", "/Users/alan/Library/Arduino15/packages/adafruit/tools/arm-none-eabi-gcc/9-2019q4/arm-none-eabi/include/c++/9.2.1/arm-none-eabi", "/Users/alan/Library/Arduino15/packages/adafruit/tools/arm-none-eabi-gcc/9-2019q4/arm-none-eabi/include/c++/9.2.1/backward", @@ -58,7 +58,7 @@ "/Users/alan/Library/Arduino15/packages/adafruit/tools/arm-none-eabi-gcc/9-2019q4/arm-none-eabi/include" ], "forcedInclude": [ - "/Users/alan/Library/Arduino15/packages/adafruit/hardware/nrf52/0.24.0/cores/nRF5/Arduino.h" + "/Users/alan/Dropbox (Personal)/Arduino/hardware/Adafruit/Adafruit_nRF52_Arduino/cores/nRF5/Arduino.h" ], "cStandard": "c11", "cppStandard": "c++11", @@ -66,7 +66,7 @@ "F_CPU=64000000", "ARDUINO=10607", "ARDUINO_NRF52840_FEATHER", - "ARDUINO_ARCH_NRF52", + "ARDUINO_ARCH_ADAFRUIT_NRF52_ARDUINO", "ARDUINO_BSP_VERSION=\"0.24.0\"", "NRF52840_XXAA", "USBCON", diff --git a/KBikeBLE.ino b/KBikeBLE.ino index d76113d..d24ca5d 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -35,8 +35,8 @@ #ifdef QUICKTIMEOUT #define DIM_TIME 20 //60 // Duration (sec) of no pedaling to dim display (if supported) #define BLANK_TIME 25 //180 // Duration (sec) to blank display -#define NO_BLE_PS_TIME 30 //900 // Duration (sec) to turn off Bluetooth to save power. Will disconnect from Central after this time. -#define BLE_PS_TIME 30 //1200 // Duration (sec) to suspend the main loop pending a crank interrupt +#define NO_BLE_PS_TIME 30 //900 // Duration (sec) to powersave when no BLE connection +#define BLE_PS_TIME 30 //1200 // Duration (sec) to powersave with BLE connection #endif #if defined(USE_SERIAL) && defined(DEBUGGING) @@ -86,7 +86,7 @@ bool gear_display = GEAR_DISPLAY; // Log used at startup #define LOG_FONT u8g2_font_7x14_tf #define LOG_WIDTH 9 -#define LOG_HEIGHT 10 +#define LOG_HEIGHT 10 uint8_t u8log_buffer[LOG_WIDTH * LOG_HEIGHT]; U8G2LOG displog; @@ -196,7 +196,8 @@ void display_numbers() eAnalogReference analog_reference = AR_INTERNAL; -bool analogCalibrateOffset() // Periodic calibration of the ADC +#ifndef SAADC_CALIBRATE_OFFSET +analogCalibrateOffset() // Periodic calibration of the ADC { const uint32_t calibrate_done = ( (SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_Generated << SAADC_EVENTS_CALIBRATEDONE_EVENTS_CALIBRATEDONE_Pos ) @@ -224,20 +225,22 @@ bool analogCalibrateOffset() // Periodic calibration of the ADC NRF_SAADC->TASKS_CALIBRATEOFFSET = calibrate_trigger; // Wait for completion - bool done = false; - for (int i = 0; (i < 20) && !(done = (NRF_SAADC->EVENTS_CALIBRATEDONE == calibrate_done)); i++ ) delay(1); + while (NRF_SAADC->EVENTS_CALIBRATEDONE != calibrate_done); // Clear the done flag (really shouldn't have to do this both times) NRF_SAADC->EVENTS_CALIBRATEDONE = calibrate_not_done; // Disable the SAADC NRF_SAADC->ENABLE = saadc_disable; - - return done; } +#endif -float get_SOC_temp() +#ifndef READ_CPU_TEMP +float readCPUTemperature() { + // These are defined simply to make the ensuing code easier to read. + // Ready and triggers are 0x01, and not ready is 0x00. Other analog code depends upon ready + // Since these are unlikely to ever change, use of the defines is just a formalilty. const uint32_t temp_ready = ( (TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Generated << TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Pos) && TEMP_EVENTS_DATARDY_EVENTS_DATARDY_Msk ); @@ -252,26 +255,27 @@ float get_SOC_temp() uint8_t en; int32_t temp; - if (sd_softdevice_is_enabled(&en)) + (void) sd_softdevice_is_enabled(&en); + if (en) { sd_temp_get(&temp); return temp / 4.0; } else { - NRF_TEMP->EVENTS_DATARDY = temp_not_ready; // Should not be needed + NRF_TEMP->EVENTS_DATARDY = temp_not_ready; // Only needed in case another function is also looking at this event flag NRF_TEMP->TASKS_START = temp_trigger; - bool done = false; - for (int i = 0; (i < 20) && !(done = (NRF_TEMP->EVENTS_DATARDY == temp_ready)); i++) delay(1); + while (NRF_TEMP->EVENTS_DATARDY != temp_ready); + temp = NRF_TEMP->TEMP; // Per anomaly 29 (unclear whether still there), TASKS_STOP will clear the TEMP register. - NRF_TEMP->TASKS_STOP = temp_stop; // Needed? + NRF_TEMP->TASKS_STOP = temp_stop; // Per anomaly 30 (unclear whether still there), the temp peripheral needs to be shut down NRF_TEMP->EVENTS_DATARDY = temp_not_ready; - if (done) return (NRF_TEMP->TEMP / 4.0F); - else return -40.0F; + return temp / 4.0F; } -} +} +#endif float averageADC() { @@ -296,10 +300,11 @@ void ADC_setup() // Set up the ADC for ongoing resistance measurement analogReadResolution(10); // 10 bits for better gear delineation and for battery measurement delay(1); // Let the ADC settle before any measurements. Only important if changing the reference or possibly resolution - displog.printf("\nT %.1f\n", get_SOC_temp()); - displog.printf("ADC %.1f\n", averageADC()); - if (analogCalibrateOffset()) displog.println("Cal done."); - else displog.println("Cal undone."); + displog.printf("\nADC %.1f\n", averageADC()); + displog.printf("T %.2f\n", readCPUTemperature()); + analogCalibrateOffset(); + //if (analogCalibrateOffset()) displog.println("Cal done."); + //else displog.println("Cal undone."); displog.printf("ADC %.1f\n", averageADC()); delay(5000); } @@ -579,12 +584,12 @@ void init_cal() { res_offset = RESISTANCE_OFFSET; write_param_file("offset", &res_offset, sizeof(res_offset)); - displog.println(F("OFFSET\nDEFAULT\n\ WRITE")); + displog.println(F("Offset\ndefault\n\ write")); displog.printf(" %.1f\n", res_offset); } else { - displog.println(F("OFFSET\n READ")); + displog.println(F("Offset\n read")); displog.printf(" %.1f\n", res_offset); } @@ -592,12 +597,12 @@ void init_cal() { res_factor = RESISTANCE_FACTOR; write_param_file("factor", &res_factor, sizeof(res_factor)); - displog.println(F("\nFACTOR\nDEFAULT\n WRITE")); + displog.println(F("\nFactor\ndefault\n write")); displog.printf(" %.4f\n", res_factor); } else { - displog.println(F("\nFACTOR\n READ")); + displog.println(F("\nFactor\n read")); displog.printf(" %.4f\n", res_factor); } diff --git a/options.h b/options.h index f341c80..c29f8a7 100644 --- a/options.h +++ b/options.h @@ -105,6 +105,9 @@ #define BLEBAS // Activate BLE battery service #define SAADC_TACQ // Use the extension to the Adafruit nRF52 core to set ADC acquisition time // - improves resistance measuresments but not yet part of the distribution +#define SAADC_CALIBRATE_OFFSET // Use the extension to the Adafruit nRF52 core to support calibration + // of the SAADC offset +#define READ_CPU_TEMP // Adafruit nRF52 core includes readCPUTemperature() #endif From 787814b329e2dbdba8d0b2405e64e7a019ebb271 Mon Sep 17 00:00:00 2001 From: ajs123 Date: Wed, 4 Aug 2021 09:25:04 -0400 Subject: [PATCH 10/11] Edits to comments --- options.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/options.h b/options.h index c29f8a7..2ea227f 100644 --- a/options.h +++ b/options.h @@ -103,11 +103,15 @@ #define USE_SERIAL // Incorporate USB serial functions including the console and any debugging. #define BLEUART // Activates serial over BLE #define BLEBAS // Activate BLE battery service -#define SAADC_TACQ // Use the extension to the Adafruit nRF52 core to set ADC acquisition time - // - improves resistance measuresments but not yet part of the distribution -#define SAADC_CALIBRATE_OFFSET // Use the extension to the Adafruit nRF52 core to support calibration - // of the SAADC offset -#define READ_CPU_TEMP // Adafruit nRF52 core includes readCPUTemperature() +/* The following depend upon functions provided by the Adafruit nRF52 core that are not present + * in v0.24. + */ +#define SAADC_TACQ // The Adafruit nRF52 core includes analogSampleTime(). + // This will reduce variability in resistance measurements but the system will function without it. +#define SAADC_CALIBRATE_OFFSET // The Adafruit core includes analogCalibrateOffset(). + // If not defined here, one will be compiled in from the .ino. +#define READ_CPU_TEMP // The Adafruit nRF52 core includes readCPUTemperature(). + // If note defined here, one will be compiled in from the .ino. #endif From f94534cd3257b83b633c521d395c76250d7723bc Mon Sep 17 00:00:00 2001 From: ajs123 Date: Wed, 4 Aug 2021 09:30:28 -0400 Subject: [PATCH 11/11] #define for SAADC sampling time --- KBikeBLE.ino | 2 +- bike_interface.h | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/KBikeBLE.ino b/KBikeBLE.ino index d24ca5d..2e58e81 100644 --- a/KBikeBLE.ino +++ b/KBikeBLE.ino @@ -295,7 +295,7 @@ void ADC_setup() // Set up the ADC for ongoing resistance measurement analogReference(analog_reference); analogOversampling(ANALOG_OVERSAMPLE); #ifdef SAADC_TACQ - analogSampleTime(20); + analogSampleTime(ANALOG_SAMPLE_TIME); #endif analogReadResolution(10); // 10 bits for better gear delineation and for battery measurement delay(1); // Let the ADC settle before any measurements. Only important if changing the reference or possibly resolution diff --git a/bike_interface.h b/bike_interface.h index a27bfe4..c6a7784 100644 --- a/bike_interface.h +++ b/bike_interface.h @@ -12,5 +12,8 @@ //#define CRANK_PIN 7 // Pushbutton on the Adafruit nrf52840 Express, for debugging #define CRANK_PIN 9 #define RESISTANCE_PIN A1 -#define RESISTANCE_TOP 10 // Should move this to A2 so that the voltage at the top can be measured -#define BATTERY_PIN A6 \ No newline at end of file +#define RESISTANCE_TOP 10 +#define BATTERY_PIN A6 + +#define ANALOG_SAMPLE_TIME 20 // The SAADC sampling time in us. 10 us is probably enough for reading + // from the 20K resistance sense potentiometer \ No newline at end of file