網頁

2020年6月24日 星期三

Wii 平衡板 修改 (二) Arduino 軟體

Arduino IDE
可能因為使用 Esp32, 導致 Arduino IDE 無法開啟
直到更新版本至 arduino-PR-beta1.9-BUILD-119
主要參考 Library HX711 ,但不能多個 HX711 共用一個 SCK
參考 Library HX711-multi,雖然可以共用 SCK,但不能用 Esp32


HX711.h

/**
 *
 * HX711 library for Arduino
 * https://github.com/bogde/HX711
 *
 * MIT License
 * (c) 2018 Bogdan Necula
 *
**/
#ifndef HX711_h
#define HX711_h

#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

class HX711
{
private:
byte PD_SCK; // Power Down and Serial Clock Input Pin
    byte COUNT;    // The number of channels to read
byte *DOUT; // Serial Data Output Pin
byte GAIN; // amplification factor
float SCALE = 1; // used to return weight in grams, kg, ounces, whatever
    uint8_t *mShiftBuf0;
    uint8_t *mShiftBuf1;
    uint8_t *mShiftBuf2;

public:

    // Initialize library with data output pin, clock input pin and gain factor.
    // Channel selection is made by passing the appropriate gain:
    // - With a gain factor of 64 or 128, channel A is selected
    // - With a gain factor of 32, channel B is selected
    // The library default is "128" (Channel A).
HX711(int count, byte *dout, byte pd_sck, byte gain = 128);

virtual ~HX711();

    double *mOffset = 0;  // used for tare weight
    long *mValueRaw;
    double *mValueAvg;
    double *mValue;
    double *mValueUnits;

// Check if HX711 is ready
// from the datasheet: When output data is not ready for retrieval, digital output pin DOUT is high. Serial clock
// input PD_SCK should be low. When DOUT goes to low, it indicates data is ready for retrieval.
bool is_ready();

// Wait for the HX711 to become ready
void wait_ready(unsigned long delay_ms = 0);
bool wait_ready_retry(int retries = 3, unsigned long delay_ms = 0);
bool wait_ready_timeout(unsigned long timeout = 1000, unsigned long delay_ms = 0);

// set the gain factor; takes effect only after a call to read()
// channel A can be set for a 128 or 64 gain; channel B has a fixed 32 gain
// depending on the parameter, the channel is also set to either A or B
void set_gain(byte gain = 128);

    void multiShiftInSlow(uint8_t bitOrder, uint8_t *shiftBuf);
    
// waits for the chip to be ready and returns a reading
void read();

    // returns an average reading; times = how many times to read
    void read_average(byte times = 10);

// returns (read_average() - OFFSET), that is the current value without the tare weight; times = how many readings to do
void get_value(byte times = 1);

// returns get_value() divided by SCALE, that is the raw value divided by a value obtained via calibration
// times = how many readings to do
void get_units(byte times = 1);

// set the OFFSET value for tare weight; times = how many times to read the tare value
void tare(byte times = 10);

// set the SCALE value; this value is used to convert the raw data to "human readable" data (measure units)
void set_scale(float scale = 1.f);

// get the current SCALE
float get_scale();

// set OFFSET, the value that's subtracted from the actual reading (tare weight)
void set_offset(double *offset);

// puts the chip into power down mode
void power_down();

// wakes up the chip after power down mode
void power_up();
};

#endif /* HX711_h */

HX711.cpp
 /**
 *
 * HX711 library for Arduino
 * https://github.com/bogde/HX711
 *
 * MIT License
 * (c) 2018 Bogdan Necula
 *
**/
#include <Arduino.h>
#include "HX711.h"

// TEENSYDUINO has a port of Dean Camera's ATOMIC_BLOCK macros for AVR to ARM Cortex M3.
#define HAS_ATOMIC_BLOCK (defined(ARDUINO_ARCH_AVR) || defined(TEENSYDUINO))

// Whether we are running on either the ESP8266 or the ESP32.
#define ARCH_ESPRESSIF (defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32))

// Whether we are actually running on FreeRTOS.
#define IS_FREE_RTOS defined(ARDUINO_ARCH_ESP32)

// Define macro designating whether we're running on a reasonable
// fast CPU and so should slow down sampling from GPIO.
#define FAST_CPU \
    ( \
    ARCH_ESPRESSIF || \
    defined(ARDUINO_ARCH_SAM)     || defined(ARDUINO_ARCH_SAMD) || \
    defined(ARDUINO_ARCH_STM32)   || defined(TEENSYDUINO) \
    )

#if HAS_ATOMIC_BLOCK
// Acquire AVR-specific ATOMIC_BLOCK(ATOMIC_RESTORESTATE) macro.
#include <util/atomic.h>
#endif

#if FAST_CPU
// Make shiftIn() be aware of clockspeed for
// faster CPUs like ESP32, Teensy 3.x and friends.
// See also:
// - https://github.com/bogde/HX711/issues/75
// - https://github.com/arduino/Arduino/issues/6561
// - https://community.hiveeyes.org/t/using-bogdans-canonical-hx711-library-on-the-esp32/539
uint8_t shiftInSlow(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder) {
    uint8_t value = 0;
    uint8_t i;

    for(i = 0; i < 8; ++i) {
        digitalWrite(clockPin, HIGH);
        delayMicroseconds(1);
        if(bitOrder == LSBFIRST)
            value |= digitalRead(dataPin) << i;
        else
            value |= digitalRead(dataPin) << (7 - i);
        digitalWrite(clockPin, LOW);
        delayMicroseconds(1);
    }
    return value;
}
#define SHIFTIN_WITH_SPEED_SUPPORT(data,clock,order) shiftInSlow(data,clock,order)
#else
#define SHIFTIN_WITH_SPEED_SUPPORT(data,clock,order) shiftIn(data,clock,order)
#endif


HX711::HX711(int count, byte *dout, byte pd_sck, byte gain) {
  PD_SCK = pd_sck;
  DOUT = dout;
  COUNT = count;

  pinMode(PD_SCK, OUTPUT);
  for (int i = 0; i < COUNT; i++) {
    pinMode(DOUT[i], INPUT_PULLUP);
  }

  set_gain(gain);
  mOffset = (double *)malloc(COUNT * sizeof(double));
  mShiftBuf0 = (uint8_t *)malloc(COUNT * sizeof(uint8_t));
  mShiftBuf1 = (uint8_t *)malloc(COUNT * sizeof(uint8_t));
  mShiftBuf2 = (uint8_t *)malloc(COUNT * sizeof(uint8_t));
  mValueRaw = (long *)malloc(COUNT * sizeof(long));
  mValueAvg = (double *)malloc(COUNT * sizeof(double));
  mValue = (double *)malloc(COUNT * sizeof(double));
  mValueUnits = (double *)malloc(COUNT * sizeof(double));
  for (int i = 0; i < COUNT; i++) {
    mOffset[i] = 0;
    mShiftBuf0[i] = 0;
    mShiftBuf1[i] = 0;
    mShiftBuf2[i] = 0;
    mValueRaw[i] = 0;
    mValue[i] = 0;
    mValueUnits[i] = 0;
  }
}

HX711::~HX711() {
  free(mOffset);
  free(mShiftBuf0);
  free(mShiftBuf1);
  free(mShiftBuf2);
  free(mValueRaw);
  free(mValueAvg);
  free(mValue);
  free(mValueUnits);
}

bool HX711::is_ready() {
  bool result = true;
  for (int i = 0; i < COUNT; i++) {
    if (digitalRead(DOUT[i]) == HIGH) {
      result = false;
    }
  }
  return result;
}

void HX711::set_gain(byte gain) {
switch (gain) {
case 128: // channel A, gain factor 128
GAIN = 1;
break;
case 64: // channel A, gain factor 64
GAIN = 3;
break;
case 32: // channel B, gain factor 32
GAIN = 2;
break;
}

}

void HX711::multiShiftInSlow(uint8_t bitOrder, uint8_t *shiftBuf) {
    uint8_t i;

    for (int i = 0; i < COUNT; i++) {
      shiftBuf[i] = 0;
    }
    for(i = 0; i < 8; ++i) {
        digitalWrite(PD_SCK, HIGH);
        delayMicroseconds(1);
        for (int j = 0; j < COUNT; j++) {
          if(bitOrder == LSBFIRST)
              shiftBuf[j] |= digitalRead(DOUT[j]) << i;
          else
              shiftBuf[j] |= digitalRead(DOUT[j]) << (7 - i);
        }
        digitalWrite(PD_SCK, LOW);
        delayMicroseconds(1);
    }
}

void HX711::read() {
// Wait for the chip to become ready.
wait_ready();

// Define structures for reading data into.
uint8_t data[3] = { 0 };
uint8_t filler = 0x00;

// Protect the read sequence from system interrupts.  If an interrupt occurs during
// the time the PD_SCK signal is high it will stretch the length of the clock pulse.
// If the total pulse time exceeds 60 uSec this will cause the HX711 to enter
// power down mode during the middle of the read sequence.  While the device will
// wake up when PD_SCK goes low again, the reset starts a new conversion cycle which
// forces DOUT high until that cycle is completed.
//
// The result is that all subsequent bits read by shiftIn() will read back as 1,
// corrupting the value returned by read().  The ATOMIC_BLOCK macro disables
// interrupts during the sequence and then restores the interrupt mask to its previous
// state after the sequence completes, insuring that the entire read-and-gain-set
// sequence is not interrupted.  The macro has a few minor advantages over bracketing
// the sequence between `noInterrupts()` and `interrupts()` calls.
#if HAS_ATOMIC_BLOCK
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {

#elif IS_FREE_RTOS
// Begin of critical section.
// Critical sections are used as a valid protection method
// against simultaneous access in vanilla FreeRTOS.
// Disable the scheduler and call portDISABLE_INTERRUPTS. This prevents
// context switches and servicing of ISRs during a critical section.
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
portENTER_CRITICAL(&mux);

#else
// Disable interrupts.
noInterrupts();
#endif

// Pulse the clock pin 24 times to read the data.
  multiShiftInSlow(MSBFIRST, mShiftBuf2);
  multiShiftInSlow(MSBFIRST, mShiftBuf1);
  multiShiftInSlow(MSBFIRST, mShiftBuf0);

// Set the channel and the gain factor for the next reading using the clock pin.
for (unsigned int i = 0; i < GAIN; i++) {
digitalWrite(PD_SCK, HIGH);
#if ARCH_ESPRESSIF
delayMicroseconds(1);
#endif
digitalWrite(PD_SCK, LOW);
#if ARCH_ESPRESSIF
delayMicroseconds(1);
#endif
}

#if IS_FREE_RTOS
// End of critical section.
portEXIT_CRITICAL(&mux);

#elif HAS_ATOMIC_BLOCK
}

#else
// Enable interrupts again.
interrupts();
#endif

  for (int i = 0; i < COUNT; i++) {
    // Replicate the most significant bit to pad out a 32-bit signed integer
    if (mShiftBuf2[i] & 0x80) {
      filler = 0xFF;
    } else {
      filler = 0x00;
    }
  
    // Construct a 32-bit signed integer
    mValueRaw[i] = ( static_cast<unsigned long>(filler) << 24
        | static_cast<unsigned long>(mShiftBuf2[i]) << 16
        | static_cast<unsigned long>(mShiftBuf1[i]) << 8
        | static_cast<unsigned long>(mShiftBuf0[i]) );
  }
}

void HX711::wait_ready(unsigned long delay_ms) {
// Wait for the chip to become ready.
// This is a blocking implementation and will
// halt the sketch until a load cell is connected.
while (!is_ready()) {
// Probably will do no harm on AVR but will feed the Watchdog Timer (WDT) on ESP.
// https://github.com/bogde/HX711/issues/73
delay(delay_ms);
}
}

bool HX711::wait_ready_retry(int retries, unsigned long delay_ms) {
// Wait for the chip to become ready by
// retrying for a specified amount of attempts.
// https://github.com/bogde/HX711/issues/76
int count = 0;
while (count < retries) {
if (is_ready()) {
return true;
}
delay(delay_ms);
count++;
}
return false;
}

bool HX711::wait_ready_timeout(unsigned long timeout, unsigned long delay_ms) {
// Wait for the chip to become ready until timeout.
// https://github.com/bogde/HX711/pull/96
unsigned long millisStarted = millis();
while (millis() - millisStarted < timeout) {
if (is_ready()) {
return true;
}
delay(delay_ms);
}
return false;
}

void HX711::read_average(byte times) {
  for (int i = 0; i < COUNT; i++) {
    mValueAvg[i] = 0;
  }
for (byte i = 0; i < times; i++) {
read();
    for (int j = 0; j < COUNT; j++) {
      mValueAvg[j] += mValueRaw[j];
    }
// Probably will do no harm on AVR but will feed the Watchdog Timer (WDT) on ESP.
// https://github.com/bogde/HX711/issues/73
delay(0);
}
  for (int i = 0; i < COUNT; i++) {
    mValueAvg[i] = mValueAvg[i] / times;
  }
}

void HX711::get_value(byte times) {
  read_average(times);
  for (int i = 0; i < COUNT; i++) {
    mValue[i] = mValueAvg[i] - mOffset[i];
  }
}

void HX711::get_units(byte times) {
  get_value(times);
  for (int i = 0; i < COUNT; i++) {
    mValueUnits[i] = mValue[i] / SCALE;
  }
}

void HX711::tare(byte times) {
read_average(times);
set_offset(mValueAvg);
}

void HX711::set_scale(float scale) {
SCALE = scale;
}

float HX711::get_scale() {
return SCALE;
}

void HX711::set_offset(double *offset) {
  for (int i = 0; i < COUNT; i++) {
    mOffset[i] = offset[i];
  }
}

void HX711::power_down() {
digitalWrite(PD_SCK, LOW);
digitalWrite(PD_SCK, HIGH);
}

void HX711::power_up() {
digitalWrite(PD_SCK, LOW);
}

WiiBalanceBoard.ino
// https://circuits4you.com/2018/02/02/esp32-led-blink-example/
// https://youyouyou.pixnet.net/blog/post/119410732
// https://circuitdigest.com/microcontroller-projects/esp32-ble-server-how-to-use-gatt-services-for-battery-level-indication

#include "HX711.h"
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Bounce2.h>

#define LED_PIN GPIO_NUM_32
#define BUTTON_PIN GPIO_NUM_33
#define BATTERY_PIN1 GPIO_NUM_34
#define BATTERY_PIN2 GPIO_NUM_35

// HX711 circuit wiring
byte HX711_SCK_PIN = GPIO_NUM_23;
byte DOUTS[] = {GPIO_NUM_16, GPIO_NUM_18, GPIO_NUM_19, GPIO_NUM_17};
//byte DOUTS[] = {GPIO_NUM_16};
#define HX711_COUNT sizeof(DOUTS)/sizeof(byte)

HX711 scale(HX711_COUNT, DOUTS, HX711_SCK_PIN);
Bounce Debouncer = Bounce(); // Instantiate a Bounce object

BLEServer* pServer = NULL;
BLECharacteristic* pDataCharacter = NULL;
BLECharacteristic* pLedCharacter = NULL;
BLECharacteristic* pButtonCharacter = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
int32_t sendBuf[HX711_COUNT+1];
byte sendCnt = 0;
unsigned long sleepTime = 0;

void InitScale() {
  Serial.println("Initializing the scale");
  
  Serial.println("Before setting up the scale:");
  Serial.print("read raw:");
  scale.read();
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueRaw[i]);
  }
  Serial.println();

  Serial.print("read average:");
  scale.read_average(20);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueAvg[i]);
  }
  Serial.println("");

  Serial.print("get value:");
  scale.get_value(5);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValue[i]);
  }
  Serial.println("");

  Serial.print("get units:");
  scale.get_units(5);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueUnits[i]);
  }
  Serial.println("");

  scale.set_scale(2280.f); // this value is obtained by calibrating the scale with known weights; see the README for details
  scale.tare(); // reset the scale to 0

  Serial.println("After setting up the scale:");

  Serial.print("read raw:");
  scale.read();
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueRaw[i]);
  }
  Serial.println();

  Serial.print("read average:");
  scale.read_average(20);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueAvg[i]);
  }
  Serial.println("");

  Serial.print("get value:");
  scale.get_value(5);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValue[i]);
  }
  Serial.println("");

  Serial.print("get units:");
  scale.get_units(5);
  for (int i = 0; i < HX711_COUNT; i++) {
    Serial.print("\t");
    Serial.print(scale.mValueUnits[i]);
  }
  Serial.println("");

  Serial.println("Readings:");
}

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_DATA_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHARACTERISTIC_LED_UUID  "ca89b981-2701-40ea-91ef-1b67c137f9e0"
#define CHARACTERISTIC_BUTTON_UUID "4e923fa6-ba0c-408a-b1d3-167ec9f1d80d"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      BLEDevice::startAdvertising();
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyLedCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        SetLed(value[0] == '1');
      }
    }
};

void SetLed(bool light) {
  if (light) {
    pLedCharacter->setValue("1");
    digitalWrite(LED_PIN, HIGH);
  } else {
    pLedCharacter->setValue("0");
    digitalWrite(LED_PIN, LOW);
  }
}

void InitBLE() {
  // Create the BLE Device
  BLEDevice::init("ESP32");
  Serial.print("My MAC:");
  Serial.println(BLEDevice::getAddress().toString().c_str());

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pDataCharacter = pService->createCharacteristic(
                      CHARACTERISTIC_DATA_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pDataCharacter->addDescriptor(new BLE2902());

  pLedCharacter = pService->createCharacteristic(
                      CHARACTERISTIC_LED_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_WRITE
                    );
  pLedCharacter->setCallbacks(new MyLedCallbacks());

  pButtonCharacter = pService->createCharacteristic(
                      CHARACTERISTIC_BUTTON_UUID,
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );
  pButtonCharacter->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
  digitalWrite(LED_PIN, HIGH);
}

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000); //Take some time to open up the Serial Monitor
  print_wakeup_reason();

  Debouncer.attach(BUTTON_PIN,INPUT_PULLUP); // Attach the debouncer to a pin with INPUT_PULLUP mode
  Debouncer.interval(25); // Use a debounce interval of 25 milliseconds
  pinMode(LED_PIN, OUTPUT);
  InitScale();
  InitBLE();
  sleepTime = millis();
}

void loop() {
  if (false) {
    scale.get_value();
    Serial.print("get value:");
    Serial.print(millis());
    for (int i = 0; i < HX711_COUNT; i++) {
      Serial.print("\t");
      Serial.print(scale.mValue[i]);
    }
    Serial.println();
    return;
  }

  Debouncer.update(); // Update the Bounce instance
  if (Debouncer.fell()) {  // Call code if button transitions from HIGH to LOW
    pButtonCharacter->notify();
  }

  int16_t bat1 = analogRead(BATTERY_PIN1);
  int16_t bat2 = analogRead(BATTERY_PIN2);
  int16_t bat = (bat1 * 2 - bat2);
  sendBuf[0] = (static_cast<unsigned long>(sendCnt++) << 24
      | (static_cast<unsigned long>(bat) & 0x0000FFFF) );
  
  // notify changed value
  scale.read();
  for (int i = 0; i < HX711_COUNT; i++) {
    sendBuf[i+1] = scale.mValueRaw[i];
  }
  if (deviceConnected) {
    pDataCharacter->setValue((uint8_t*)&sendBuf, (HX711_COUNT+1) * sizeof(int32_t));
    pDataCharacter->notify();
    delay(1); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
  }
  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
    digitalWrite(LED_PIN, HIGH);
    sleepTime = millis();
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    Serial.println("deviceConnected");
    oldDeviceConnected = deviceConnected;
    digitalWrite(LED_PIN, LOW);
    sleepTime = 0;
  }
  if (sleepTime != 0 && millis() - sleepTime > 60 * 1000) {
    Serial.println("esp_deep_sleep_start");
    scale.power_down();
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0); //1 = High, 0 = Low
    esp_deep_sleep_start();
  }
}

沒有留言:

張貼留言