JSMSolns
Published © GPL3+

Bluetooth Page Turner using Seeed Xiao-NRF52840

Wireless page turner designed for use with piano music apps such as Forscore on a tablet. Can be battery or USB powered.

BeginnerFull instructions provided1 hour259
Bluetooth Page Turner using Seeed Xiao-NRF52840

Things used in this project

Hardware components

Seeed Studio Xiao nRF52840
×1
Metal foot switch pedal
×1
Rocker switch
Small 12v rocker SPST switch panel cut out 12 x 8 mm
×1
LIPO Battery 3.7v 650mAh
To fit case
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fusion 360
Autodesk Fusion 360
For CAD design/modifications

Story

Read more

Custom parts and enclosures

Complete Case (Fusion 360 file)

CAD design file for Bluetooth page turner

Case Lid (G-Code)

Case - Base (G-Code)

Schematics

Bluetooth Page Turner Schematic

Schematic

Code

BlueToothPageTurner

Arduino
//Bluetooth Page Turner designed as a wireless foot pedal for turning pages in ForScore. 
//Short press = page forwards, long press = page backwards.
//Designed for Seeed Xiao nRF52840
//V6.0 - first release. Tested with Forscore and iPad OK. 

#define VERSION 6.0

#include <Arduino.h>
#include <bluefruit.h>
#include <SdFat.h>
#include <Adafruit_SPIFlash.h>
#include <nrf_sdm.h>
#include <nrfx_power.h>
#include "TickTwo.h"
#include "xiaobattery.h"
#include <OneButton.h>

#define SERIAL_DEBUGGING 1
//#define BATTERY_MONITORING 1
//#define PLAY_PAUSE_TEST 1

#define KEY_MEDIA_PLAY_PAUSE 1
#define KEY_MEDIA_STOP 2

#define PEDAL_PIN D4
#define VBAT_PIN PIN_VBAT    //P0.31 AIN7 VBAT ADC pin
#define VBAT_ADC_ENABLE VBAT_ENABLE  //P0.14 READ_VBAT
#define VBAT_CHARGE_RATE_PIN HICHG  //P0.13
#define VBAT_CHARGE_STATUS_PIN 23  //P0.17

#define LEDR LED_RED
#define LEDG LED_GREEN
#define LEDB LED_BLUE

#define BTLED_ONTIME_MS 500  //on for 0.5 secs
#define BTLED_OFFTIME_MS 5000  //off for 5 secs

#define TIMEOUT_secs 300    //time in seconds of no key presses to go into deep sleep (5 minutes)

#define VBAT_MV_PER_LSB   (0.73242188F)   // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096

#define VBAT_DIVIDER      (0.796812749F)   // 2M + 0.510M voltage divider on VBAT = (2M / (0.510M + 2M))
#define VBAT_DIVIDER_COMP (1.255F)        // Compensation factor for the VBAT divider (1/VBAT_DIVIDER)

#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)

const bool  NO_TIMEOUT_IF_CONNECTED = true;
const bool HIGH_CHARGE_RATE = false;

const uint32_t GoToSleepTimeOut_ms = TIMEOUT_secs  * 1000;
const uint32_t VOLATGEDISPLAYTIMEOUT_MS = 10000;  //display the battery voltage every 10 seconds

uint32_t TimerStart_ms = 0;
uint32_t VoltageDisplayTimer = 0;
uint16_t g_conn_handle;
bool BatteryChargeStateChanged = false;
bool BatteryCharging = false;
nrfx_power_usb_state_t USBPower;
nrfx_power_usb_state_t USBPowerPrevious;
bool USBPowered = false;


Xiao battery;

BLEDis bledis;
BLEHidAdafruit blehid;
Adafruit_FlashTransport_QSPI flashTransport;
Adafruit_SPIFlash flash(&flashTransport);
uint8_t RAkeycodes[6] = { HID_KEY_ARROW_RIGHT, HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE };
uint8_t LAkeycodes[6] = { HID_KEY_ARROW_LEFT, HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE , HID_KEY_NONE };


void BT_LED_Flash();
void ShortPress_LED_Flash();
void LongPress_LED_Flash();
void RGB_LED(char c, bool onoffstate);


TickTwo BT_LED_Timer(BT_LED_Flash, 2000, 0, MILLIS);
TickTwo ShortPress_LED_Timer(ShortPress_LED_Flash, 500, 0, MILLIS);
TickTwo LongPress_LED_Timer(LongPress_LED_Flash, 500, 0, MILLIS);

enum ButtonPressType
{
  NoPress = 0,
  ShortPress = 1,
  LongPress = 2,
  DoublePress = 3,
};

ButtonPressType PressType;

OneButton pedal(PEDAL_PIN, true);


bool hasKeyPressed = false;
bool BTConnected = false;
bool button;

bool BlueLEDState = false;
bool RedLEDState = false;
bool GreenLEDState = false;

void BT_LED_Flash()
{
  //flash blue LED to show bluetooth connected - LED on for .5 sec, off for 5 seconds
  if (BlueLEDState == true)
  {//go to off state
    RGB_LED('B',false);
    BT_LED_Timer.interval(BTLED_OFFTIME_MS);
    BT_LED_Timer.start();

  }
  else
  {//go to on state
    RGB_LED('B',true);
    BT_LED_Timer.interval(BTLED_ONTIME_MS);
    BT_LED_Timer.start();
  }
  
}

void ShortPress_LED_Flash()
{
  
}

void LongPress_LED_Flash()
{
  
}

uint8_t checkForSoftDevice() {
  uint8_t check;
  sd_softdevice_is_enabled(&check);

  return check;
}

void sleepFlash() {
  //put external flash chip to sleep
    flashTransport.begin();
    flashTransport.runCommand(0xB9);
    flashTransport.end();
}

void startAdv(void)
{  
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
  
  // Include BLE HID service
  Bluefruit.Advertising.addService(blehid);

  // There is enough room for the dev name in the advertising packet
  Bluefruit.Advertising.addName();
  
  /* Start Advertising
   * - Enable auto advertising if disconnected
   * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
   * - Timeout for fast mode is 30 seconds
   * - Start(timeout) with timeout = 0 will advertise forever (until connected)
   * 
   * For recommended advertising interval
   * https://developer.apple.com/library/content/qa/qa1931/_index.html   
   */
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 480);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(15);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds
}


void BT_connect_callback(uint16_t conn_handle)
{
  
  g_conn_handle = conn_handle;
  
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  BTConnected = true;
  BT_LED_Timer.start();
  RGB_LED('B',true);  //ensure blue led is on to strt with

  char peer_name[32] = { 0 };
  connection->getPeerName(peer_name, sizeof(peer_name));

  #ifdef SERIAL_DEBUGGING
     Serial.print("Bluetooth connected to ");
     Serial.println(peer_name);


     Bluefruit.printInfo();
  #endif
  
}

void BT_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  BTConnected = false;

  #ifdef SERIAL_DEBUGGING
     Serial.println("Bluetooth disconnected");
  #endif

   BT_LED_Timer.stop();
   RGB_LED('B',false);  //ensure blue led is off
  
}

void PrintResetReason(int32_t Reset_Reason)
{
 #ifdef SERIAL_DEBUGGING
  //decode the reset reason to a string for printing
  if (Reset_Reason == 0)
  {
    Serial.println("Power on reset");
    return;
  }
  if (Reset_Reason & 0x1UL)
  {
    Serial.println("Pin reset");
    return;    
  }
  if (Reset_Reason & 0x2UL)
  {
    Serial.println("Watchdog reset");
    return;    
  }  
  if (Reset_Reason & 0x4L)
  {
    Serial.println("Soft reset");
    return;    
  }
  if (Reset_Reason & 0x8L)
  {
    Serial.println("CPU Lock-up");
    return;    
  }
  if (Reset_Reason & 0x10000UL)
  {
    Serial.println("GPIO wake up from sleep");
    return;    
  }
  if (Reset_Reason & 0x20000UL)
  {
    Serial.println("LPCOMP wake up from sleep");
    return;    
  }
  if (Reset_Reason & 0x4000L)
  {
    Serial.println("Debug i/f wake up from sleep");
    return;    
  }
  if (Reset_Reason & 0x80000UL)
  {
    Serial.println("NFC wake up from sleep");
    return;    
  }
  #endif
  return;
}

void print_wakeup_reason()
{
  uint32_t reset_reason = 0;    
  reset_reason = NRF_POWER->RESETREAS;
  reset_reason = readResetReason();
  #ifdef SERIAL_DEBUGGING
    Serial.print("reset_reason: ");
    Serial.print(reset_reason, HEX);  
    Serial.print(" -> "); 
    PrintResetReason(reset_reason);
  #endif
  //cler the reset reason as it is cumulative
  //NRF_POWER->RESETREAS |= NRF_POWER->RESETREAS;
  //esp_sleep_wakeup_cause_t wakeup_reason;

}

bool Battery_Is_Charging()
{
//  if (digitalRead(VBAT_CHARGE_STATUS_PIN) == LOW)
//  {
//    if (BatteryCharging == false)
//    {
//      BatteryChargeStateChanged = true;
//    }
//    BatteryCharging = true;
//    return(true);
//  }
//  else
//  {
//    if (BatteryCharging == true)
//    {
//      BatteryChargeStateChanged = true;
//    }
//    BatteryCharging = false;
//    return(false);
//  }
   
   bool IsCharging;
   
   IsCharging = battery.IsChargingBattery();

   if (IsCharging == true)
   {
      if (BatteryCharging == false)
      {
        BatteryChargeStateChanged = true;
      }
      BatteryCharging = true;
      return(true);
   }
   else
   {
      if (BatteryCharging == true)
      {
        BatteryChargeStateChanged = true;
      }
      BatteryCharging = false;
      return(false);     
   }

   
}

float readVBAT(void) {
//  float raw;
//
//  // Get the raw 12-bit, 0..3000mV ADC value
//  raw = analogRead(VBAT_PIN);
//
//  // Convert the raw value to compensated mv, taking the resistor-
//  // divider into account (providing the actual LIPO voltage)
//  // ADC range is 0..3000mV and resolution is 12-bit (0..4095)
//  return raw * REAL_VBAT_MV_PER_LSB;

   return(battery.GetBatteryVoltage());
}

uint8_t mvToPercent(float mvolts) {
  if(mvolts<3300)
    return 0;

  if(mvolts <3600) {
    mvolts -= 3300;
    return mvolts/30;
  }

  mvolts -= 3600;
  return 10 + (mvolts * 0.15F );  // thats mvolts /6.66666666
}

void RGB_LED(char Colour, bool TurnOn)
{

  if (Colour == 'R')
  {
    digitalWrite(LEDR,TurnOn ? LOW:HIGH);
    RedLEDState = TurnOn ? true:false;
  }
  else if (Colour == 'G')
  {
    digitalWrite(LEDG,TurnOn ? LOW:HIGH);
    GreenLEDState = TurnOn ? true:false;
  }
  else if (Colour == 'B')
  {
    digitalWrite(LEDB,TurnOn ? LOW:HIGH);
    BlueLEDState = TurnOn ? true:false;
  }
  
}

void AllLEDsOff()
{
  digitalWrite(LEDR, HIGH);  //LEDs off
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);
  BlueLEDState = false;
  RedLEDState = false;
  GreenLEDState = false;    
}

void SendBTKeyStroke(ButtonPressType SwitchPress)
{
  if(BTConnected) 
  {
    
        //Left/Right arrow code test...used for actual page turner
        //blehid.keyPress(counter < 10 ? HID_KEY_ARROW_RIGHT : HID_KEY_ARROW_LEFT);  

        if (SwitchPress == ShortPress)
        {
           blehid.keyboardReport( 0 ,  RAkeycodes  );
           blehid.keyRelease();
           delay(5);
        }
        if (SwitchPress == LongPress)
        {
           blehid.keyboardReport( 0 ,  LAkeycodes  );
           blehid.keyRelease();
           delay(5);
        }
//        if (SwitchPress == DoublePress)
//        {
//        }
         

        blehid.keyRelease();
        //delay(5);
 
  }
}

void shortPress()
{
  PressType = ShortPress;
}

void longPress()
{
  PressType = LongPress;
}

void setup() 
{




  
  pinMode(PEDAL_PIN, INPUT_PULLUP_SENSE);

//  pinMode(VBAT_ADC_ENABLE,OUTPUT);
//  digitalWrite(VBAT_ADC_ENABLE, LOW);

//  pinMode (VBAT_CHARGE_STATUS_PIN,INPUT);

//  pinMode(VBAT_CHARGE_RATE_PIN, OUTPUT);
//  if (HIGH_CHARGE_RATE)
//  {
//     digitalWrite(VBAT_CHARGE_RATE_PIN, LOW);  //high charge rate 100mA
//  }
//  else
//  {
//     digitalWrite(VBAT_CHARGE_RATE_PIN, HIGH);//low charge rate 50mA
//  }


  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
  AllLEDsOff();


//  // Set the analog reference to 3.0V (default = 3.6V)
//  analogReference(AR_INTERNAL_3_0);
//
//  // Set the resolution to 12-bit (0..4095)
//  analogReadResolution(12); // Can be 8, 10, 12 or 14 
  
   // Get a single ADC sample and throw it away
  #ifdef BATTERY_MONITORING
     readVBAT(); 
  #endif


  #ifdef SERIAL_DEBUGGING
    Serial.begin(115200);
    //while ( !Serial ) delay(10);   // wait for serial input
    delay(1000);
  #endif


  #ifdef SERIAL_DEBUGGING  
     print_wakeup_reason();
     Serial.println("Starting!");

     Serial.println();
     Serial.println("Go to your phone's Bluetooth settings to pair your device");
     Serial.println("then open an application that accepts keyboard input");
  #endif

  Bluefruit.setName("BTPageTurner");
  Bluefruit.begin();
  //Bluefruit.autoConnLed(false);
  Bluefruit.setTxPower(2);    // Check bluefruit.h for supported values

  // Configure and Start Device Information Service
  bledis.setManufacturer("JSMSolutions");
  bledis.setModel("XIAO nRF52840V6");
  bledis.begin();

  //Bluefruit.Central.setConnectCallback(BT_connect_callback);
  //Bluefruit.Central.setDisconnectCallback(BT_disconnect_callback);
  Bluefruit.Periph.setConnectCallback(BT_connect_callback);
  Bluefruit.Periph.setDisconnectCallback(BT_disconnect_callback);

  /* Start BLE HID
   * Note: Apple requires BLE device must have min connection interval >= 20m
   * ( The smaller the connection interval the faster we could send data).
   * However for HID and MIDI device, Apple could accept min connection interval 
   * up to 11.25 ms. Therefore BLEHidAdafruit::begin() will try to set the min and max
   * connection interval to 11.25  ms and 15 ms respectively for best performance.
   */
  blehid.begin();

  flash.begin();


  // Set callback for set LED from central
  //blehid.setKeyboardLedCallback(set_keyboard_led);

  /* Set connection interval (min, max) to your perferred value.
   * Note: It is already set by BLEHidAdafruit::begin() to 11.25ms - 15ms
   * min = 9*1.25=11.25 ms, max = 12*1.25= 15 ms 
   */
  /* Bluefruit.Periph.setConnInterval(9, 12); */

  // Set up and start advertising
  startAdv();

  TimerStart_ms = millis();
  VoltageDisplayTimer = millis() - VOLATGEDISPLAYTIMEOUT_MS; 

  PressType = NoPress;
  USBPower = NRFX_POWER_USB_STATE_DISCONNECTED;

  pedal.attachClick(shortPress);
  pedal.attachLongPressStop(longPress);
}


void loop() 
{

  uint32_t CurrentSleepTime;


  BT_LED_Timer.update();
  ShortPress_LED_Timer.update();
  LongPress_LED_Timer.update();
  pedal.tick();

  //nrfx_power_usb_state_t 
  USBPower = nrfx_power_usbstatus_get();
  if (USBPower == NRFX_POWER_USB_STATE_DISCONNECTED)
  {
    //Serial.println("No USB Power");
    RGB_LED('R', false);
    USBPowered = false;
  }
  else
  {
    RGB_LED('R', true);
    USBPowered = true;
    //Serial.println("USB powered");
  }

  
  
  
   if ( PressType != NoPress)
   {
      if (BTConnected)
      {
        SendBTKeyStroke(PressType);
       
      }
      TimerStart_ms = millis();
      PressType = NoPress;
   }


  //delay(50);

  if (BTConnected && NO_TIMEOUT_IF_CONNECTED)
  {
    TimerStart_ms = millis();
  }

  if (USBPowered)
  {//not sleep mode if USB power is connected
    TimerStart_ms = millis();
  }

  CurrentSleepTime = millis() - TimerStart_ms;
  if (CurrentSleepTime > GoToSleepTimeOut_ms)
  {
    //activate deep sleep here
    #ifdef SERIAL_DEBUGGING
       Serial.println("Going into deep sleep");
       delay(100);
    #endif

    if (BTConnected)
    {//disconnect BT
      Bluefruit.disconnect(g_conn_handle);
      delay(100);
    }

    Bluefruit.Advertising.stop();
    
//    Bluefruit._stopConnLed();
//    Bluefruit._setConnLed(false);
    
    AllLEDsOff(); //make sure LED not left on to reduce power
    
    
//    sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
//    sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
//    __WFE();
//    __WFI();
//    sd_app_evt_wait() ; //SOFT DEVICE SYSTEM ON POWER SAVE
    
    nrf_gpio_cfg_sense_input(g_ADigitalPinMap[PEDAL_PIN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
    sleepFlash();
    delay(1000);
    //esp_deep_sleep_start();
    if (checkForSoftDevice() == 1) 
    {
      // SoftDevice enabled
      sd_power_system_off();
    } 
    else 
    {
      // No SoftDevice
      NRF_POWER->SYSTEMOFF = 1;
    } 
    //Gone to low power deep sleep! Wake up by pressing the pedal.

  }

  // Get a raw ADC reading
  #ifdef BATTERY_MONITORING
     float vbat_v = readVBAT();


    bool Charging = Battery_Is_Charging();
    #ifdef SERIAL_DEBUGGING 
      if (BatteryChargeStateChanged)
      {
        if (Charging)
        {
           Serial.println("Battery is charging");
        }
        else
        {
           Serial.println("Battery not charging");
        }
      }
    #endif
    BatteryChargeStateChanged = false;
  
    // Convert from raw mv to percentage (based on LIPO chemistry)
    uint8_t vbat_per = mvToPercent(vbat_v * 1000.0);
    #ifdef SERIAL_DEBUGGING
  
       if (millis() - VoltageDisplayTimer >= VOLATGEDISPLAYTIMEOUT_MS)
       {
         Serial.print("Battery voltage = ");
         Serial.print(vbat_v,3);
         Serial.println("V");
    
         Serial.print("Battery percentage = ");
         Serial.print(vbat_per);
         Serial.println("%");
         VoltageDisplayTimer = millis();  //reset timer
       }
       
    #endif
 #endif
  
}

TickTwo

C/C++
/* Ticker library code is placed under the MIT license
 * Copyright (c) 2018 Stefan Staub
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "TickTwo.h"

TickTwo::TickTwo(fptr callback, uint32_t timer, uint32_t repeat, resolution_t resolution) {
  this->resolution = resolution;
  if (resolution == MICROS) timer = timer * 1000;
  this->timer = timer;
  this->repeat = repeat;
  this->callback = callback;
  enabled = false;
  lastTime = 0;
  counts = 0;
  }

TickTwo::~TickTwo() {}

void TickTwo::start() {
  if (callback == NULL) return;
  if (resolution == MILLIS) lastTime = millis();
  else lastTime = micros();
  enabled = true;
  counts = 0;
  status = RUNNING;
  }

void TickTwo::resume() {
  if (callback == NULL) return;
  if (resolution == MILLIS) lastTime = millis() - diffTime;
  else lastTime = micros() - diffTime;
  if (status == STOPPED) counts = 0;
  enabled = true;
  status = RUNNING;
  }

void TickTwo::stop() {
  enabled = false;
  counts = 0;
  status = STOPPED;
  }

void TickTwo::pause() {
  if (resolution == MILLIS) diffTime = millis() - lastTime;
  else diffTime = micros() - lastTime;
  enabled = false;
  status = PAUSED;
  }

void TickTwo::update() {
  if (tick()) callback();
  }

bool TickTwo::tick() {
  if (!enabled) return false; 
  uint32_t currentTime = (resolution == MILLIS) ? millis() : micros();
  if ((currentTime - lastTime) >= timer) {
    lastTime = currentTime;
    if (repeat - counts == 1 && counts != 0xFFFFFFFF) {
      enabled = false;
      status = STOPPED;
      }
    counts++;
    return true;
    }
  return false;
  }

void TickTwo::interval(uint32_t timer) {
  if (resolution == MICROS) timer *= 1000;
  this->timer = timer;
  }

uint32_t TickTwo::interval() {
  if (resolution == MILLIS) return timer / 1000;
  else return timer;
  }

uint32_t TickTwo::elapsed() {
  if (resolution == MILLIS) return millis() - lastTime;
  else return micros() - lastTime;
  }

uint32_t TickTwo::remaining() {
  return timer - elapsed();
  }

status_t TickTwo::state() {
  return status;
  }

uint32_t TickTwo::counter() {
  return counts;
  }

TickTwo

C Header File
/* Ticker library code is placed under the MIT license
 * Copyright (c) 2018 Stefan Staub
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef TICKTWO_H
#define TICKTWO_H

#include "Arduino.h"

/** Ticker internal resolution
 *
 * @param MICROS default, the resoution is in micro seconds, max is 70 minutes, the real resoltuion is 4 microseconds at 16MHz CPU cycle
 * @param MILLIS set the resolution to millis, for longer cycles over 70 minutes
 *
 */
enum resolution_t {
  MICROS,
  MILLIS,
  MICROS_MICROS
  };

/** Ticker status
 *
 * @param STOPPED default, ticker is stopped
 * @param RUNNIBG ticker is running
 * @param PAUSED ticker is paused
 *
 */
enum status_t {
  STOPPED,
  RUNNING,
  PAUSED};

//#if defined(__arm__) || defined(ESP8266) || defined(ESP32)
//#include <functional>
//using fptr = std::function<void()>;
//#else
//typedef void (*fptr)();
//#endif

typedef void (*fptr)();


class TickTwo {

public:

  /** create a Ticker object
   *
   * @param callback the name of the function to call
   * @param timer interval length in ms or us
   * @param repeat default 0 -> endless, repeat > 0 -> number of repeats
   * @param resolution default MICROS for tickers under 70min, use MILLIS for tickers over 70 min
   *
   */
  TickTwo(fptr callback, uint32_t timer, uint32_t repeat = 0, resolution_t resolution = MICROS);

  /** destructor for the Ticker object
   *
   */
  ~TickTwo();

  /** start the ticker
   *
   */
  void start();

  /** resume the ticker. If not started, it will start it.
   *
   */
  void resume();

  /** pause the ticker
   *
   */
  void pause();

  /** stops the ticker
   *
   */
  void stop();

  /** must to be called in the main loop(), it will check the Ticker, and if necessary, will run the callback
   *
   */
  void update();

  /**
   * @brief set the interval timer
   * 
   * @param timer interval length in ms or us
   */
  void interval(uint32_t timer);

  /**
   * @brief get the interval time
   * 
   * @returns the interval time
   */
  uint32_t interval();

  /** actual ellapsed time
   *
   * @returns the elapsed time after the last tick
   *
   */
  uint32_t elapsed();

  /** time remaining to the next tick
   *
   * @returns the remaining time to the next tick in ms or us depending from mode
   *
   */
  uint32_t remaining();

  /** get the state of the ticker
   *
   * @returns the state of the ticker: STOPPED, RUNNING or PAUSED
   */
  status_t state();

  /** get the numbers of executed repeats
   *
   * @returns the number of executed repeats
   *
   */
  uint32_t counter();

private:
  bool tick();
  bool enabled;
  uint32_t timer;
  uint32_t repeat;
  resolution_t resolution = MICROS;
  uint32_t counts;
  status_t status;
  fptr callback;
  uint32_t lastTime;
  uint32_t diffTime;
};

#endif

xiaobattery

C Header File
#include <Arduino.h>
#include <bluefruit.h>

#define BAT_HIGH_CHARGE 22  // HIGH for 50mA, LOW for 100mA
#define BAT_CHARGE_STATE 23 // LOW for charging, HIGH not charging

class Xiao {
public:
  Xiao();
  float GetBatteryVoltage();
  bool IsChargingBattery();
};

Xiao::Xiao() {
  pinMode(VBAT_ENABLE, OUTPUT);
  pinMode(BAT_CHARGE_STATE, INPUT);
  pinMode(BAT_HIGH_CHARGE, OUTPUT);

  digitalWrite(BAT_HIGH_CHARGE, HIGH); // charge with 50mA
}

#define VBAT_MV_PER_LBS (0.003395996F) // 3.6 reference and 10 bit resolution

float Xiao::GetBatteryVoltage() {
  digitalWrite(VBAT_ENABLE, LOW);

  uint32_t adcCount = analogRead(PIN_VBAT);
  float adcVoltage = adcCount * VBAT_MV_PER_LBS;
  float vBat = adcVoltage * (1510.0 / 510.0);

  digitalWrite(VBAT_ENABLE, HIGH);

  return vBat;
}

bool Xiao::IsChargingBattery() { return digitalRead(BAT_CHARGE_STATE) == LOW; }

Credits

JSMSolns

JSMSolns

12 projects • 34 followers

Comments