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();
}
}
沒有留言:
張貼留言