-
Notifications
You must be signed in to change notification settings - Fork 580
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add binding for generic HX711 (load cell amplifier and 24-Bit ADC Mod…
…ule) driver (#1994) * Add Hx711 device - Add Hx711 options - Add `SetCalibration` method - Fix weight reading process --------- Co-authored-by: Matteo Tosi <matteo.tosi@ericsoft.com> Co-authored-by: Matteo Tosi <Matteo.Tosi@zucchetti.it> Co-authored-by: Matteo Tosi <matteo.tosi@euris.it>
- Loading branch information
1 parent
2291697
commit 489759a
Showing
12 changed files
with
938 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Iot.Device.Hx711 | ||
{ | ||
/// <summary> | ||
/// Byte order ("endianness") in an architecture | ||
/// </summary> | ||
internal enum ByteFormat | ||
{ | ||
/// <summary> | ||
/// Less Significant Bit (aka Little-endian) byte format sequence | ||
/// </summary> | ||
Lsb = 0, | ||
|
||
/// <summary> | ||
/// Most Significant Bit (aka Big-endian) byte format sequence | ||
/// </summary> | ||
Msb = 1, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Device; | ||
using System.Device.Gpio; | ||
using System.Linq; | ||
using UnitsNet; | ||
|
||
namespace Iot.Device.Hx711 | ||
{ | ||
/// <summary> | ||
/// Hx711 - Weight scale Module | ||
/// </summary> | ||
public sealed class Hx711 : IDisposable | ||
{ | ||
private readonly GpioController _gpioController; | ||
private readonly bool _shouldDispose; | ||
|
||
private readonly int _pinDout; | ||
private readonly int _pinPdSck; | ||
|
||
private readonly Hx711Options _options; | ||
private readonly object _readLock; | ||
private readonly Hx711Reader _reader; | ||
|
||
private readonly List<double> _conversionRatioList = new(); | ||
|
||
private bool _isInitialize; | ||
private bool _isCalibrated; | ||
private int _tareValue; | ||
|
||
/// <summary> | ||
/// Offset value from 0 at startup | ||
/// </summary> | ||
private int _offsetFormZero; | ||
|
||
/// <summary> | ||
/// Conversion ratio between Hx711 units and grams | ||
/// </summary> | ||
public double ConversionRatio { get; private set; } | ||
|
||
/// <summary> | ||
/// Weight set as tare | ||
/// </summary> | ||
public Mass TareValue | ||
{ | ||
get | ||
{ | ||
return ConversionRatio == 0 ? Mass.FromGrams(_tareValue) : Mass.FromGrams(_tareValue / ConversionRatio); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new instance of the Hx711 module. | ||
/// </summary> | ||
/// <param name="pinDout">Trigger pulse output. (Digital OUTput)</param> | ||
/// <param name="pinPdSck">Trigger pulse input. (Power Down control and Serial Clock input)</param> | ||
/// <param name="options">How to use the Hx711 module.</param> | ||
/// <param name="gpioController">GPIO controller related with the pins.</param> | ||
/// <param name="pinNumberingScheme">Scheme and numeration used by controller.</param> | ||
/// <param name="shouldDispose">True to dispose the Gpio Controller.</param> | ||
public Hx711(int pinDout, int pinPdSck, Hx711Options? options = null, GpioController? gpioController = null, | ||
PinNumberingScheme pinNumberingScheme = PinNumberingScheme.Logical, bool shouldDispose = true) | ||
{ | ||
_pinDout = pinDout; | ||
_pinPdSck = pinPdSck; | ||
|
||
_shouldDispose = shouldDispose || gpioController is null; | ||
_gpioController = gpioController ?? new(pinNumberingScheme); | ||
|
||
// Mutex for reading from the Hx711, in case multiple threads in client | ||
// software try to access get values from the class at the same time. | ||
_readLock = new object(); | ||
|
||
_gpioController.OpenPin(_pinPdSck, PinMode.Output); | ||
_gpioController.OpenPin(_pinDout, PinMode.Input); | ||
|
||
// Needed initialize | ||
_isInitialize = false; | ||
|
||
// Needed calibration | ||
_isCalibrated = false; | ||
|
||
_offsetFormZero = 0; | ||
_tareValue = 0; | ||
ConversionRatio = 0; | ||
|
||
_options = options ?? new Hx711Options(); | ||
|
||
_reader = new Hx711Reader(_gpioController, _options, pinDout, pinPdSck, _readLock); | ||
} | ||
|
||
/// <summary> | ||
/// Load cells always return different values also based on their range and sensitivity. | ||
/// For this reason, a first calibration step with a known weight is required. | ||
/// You can repeat it several times to get a more precise value. | ||
/// </summary> | ||
/// <param name="knowWeight">Known weight currently on load cell and detected by the Hx711.</param> | ||
/// <param name="numberOfReads">Number of readings to take from which to average, to get a more accurate value.</param> | ||
/// <exception cref="ArgumentOutOfRangeException">Throw if know weight have invalid value</exception> | ||
public void SetCalibration(Mass knowWeight, int numberOfReads = 15) | ||
{ | ||
if (knowWeight.Grams == 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(paramName: nameof(knowWeight), message: "Param value must be greater than zero!"); | ||
} | ||
|
||
var readValue = _reader.Read(numberOfReads, _offsetFormZero); | ||
|
||
lock (_readLock) | ||
{ | ||
var referenceValue = readValue / knowWeight.Grams; | ||
|
||
// If we do several calibrations, the most accurate value is the average. | ||
_conversionRatioList.Add(referenceValue); | ||
ConversionRatio = _conversionRatioList.Average(); | ||
|
||
_isCalibrated = true; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// If you already know the reference unit between the Hx711 value and grams, you can set it and skip the calibration. | ||
/// </summary> | ||
/// <param name="conversionRatio">Conversion ratio between Hx711 units and grams</param> | ||
/// <exception cref="ArgumentOutOfRangeException">Throw if know weight have invalid value</exception> | ||
public void SetConversionRatio(double conversionRatio) | ||
{ | ||
if (conversionRatio == 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(paramName: nameof(conversionRatio), message: "Param value must be greater than zero!"); | ||
} | ||
|
||
lock (_readLock) | ||
{ | ||
ConversionRatio = conversionRatio; | ||
|
||
// Calibration no longer required | ||
_isCalibrated = true; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Read the weight from the Hx711 through channel A to which the load cell is connected, | ||
/// range and precision depend on load cell connected. | ||
/// </summary> | ||
/// <param name="numberOfReads">Number of readings to take from which to average, to get a more accurate value.</param> | ||
/// <returns>Return a weigh read from Hx711</returns> | ||
public Mass GetWeight(int numberOfReads = 3) | ||
{ | ||
if (!_isCalibrated) | ||
{ | ||
throw new Hx711CalibrationNotDoneException(); | ||
} | ||
|
||
// Lock is internal in fisical read | ||
var value = GetNetWeight(numberOfReads); | ||
|
||
return Mass.FromGrams(Math.Round(value / ConversionRatio, digits: 0)); | ||
} | ||
|
||
/// <summary> | ||
/// Sets tare for channel A for compatibility purposes | ||
/// </summary> | ||
/// <param name="numberOfReads">Number of readings to take from which to average, to get a more accurate value.</param> | ||
public void Tare(int numberOfReads = 15) | ||
{ | ||
lock (_readLock) | ||
{ | ||
if (!_isCalibrated) | ||
{ | ||
throw new Hx711CalibrationNotDoneException(); | ||
} | ||
|
||
_tareValue = GetNetWeight(numberOfReads); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Power up Hx711 and set it ready to work | ||
/// </summary> | ||
public void PowerUp() | ||
{ | ||
// Wait for and get the Read Lock, incase another thread is already | ||
// driving the Hx711 serial interface. | ||
lock (_readLock) | ||
{ | ||
// Lower the Hx711 Digital Serial Clock (PD_SCK) line. | ||
// Docs says "When PD_SCK Input is low, chip is in normal working mode." | ||
// page 5 | ||
// https://html.alldatasheet.com/html-pdf/1132222/AVIA/Hx711/573/5/Hx711.html | ||
_gpioController.Write(_pinPdSck, PinValue.Low); | ||
|
||
// Wait 100µs for the Hx711 to power back up. | ||
DelayHelper.DelayMicroseconds(microseconds: 100, allowThreadYield: true); | ||
|
||
// Release the Read Lock, now that we've finished driving the Hx711 | ||
// serial interface. | ||
} | ||
|
||
// Hx711 will now be defaulted to Channel A with gain of 128. If this | ||
// isn't what client software has requested from us, take a sample and | ||
// throw it away, so that next sample from the Hx711 will be from the | ||
// correct channel/gain. | ||
if (_options.Mode != Hx711Mode.ChannelAGain128 || !_isInitialize) | ||
{ | ||
_ = _reader.Read(numberOfReads: 15, offsetFromZero: 0); | ||
_isInitialize = true; | ||
} | ||
|
||
// Read offset from 0 | ||
var value = _reader.Read(numberOfReads: 15, offsetFromZero: 0); | ||
_offsetFormZero = value; | ||
} | ||
|
||
/// <summary> | ||
/// Power down Hx711 | ||
/// </summary> | ||
public void PowerDown() | ||
{ | ||
// Wait for and get the Read Lock, incase another thread is already | ||
// driving the Hx711 serial interface. | ||
lock (_readLock) | ||
{ | ||
// Cause a rising edge on Hx711 Digital Serial Clock (PD_SCK). We then | ||
// leave it held up and wait 100µs. After 60µs the Hx711 should be | ||
// powered down. | ||
// Docs says "When PD_SCK pin changes from low to high | ||
// and stays at high for longer than 60µs, Hx711 | ||
// enters power down mode", page 5 https://html.alldatasheet.com/html-pdf/1132222/AVIA/Hx711/573/5/Hx711.html | ||
_gpioController.Write(_pinPdSck, PinValue.Low); | ||
_gpioController.Write(_pinPdSck, PinValue.High); | ||
|
||
DelayHelper.DelayMicroseconds(microseconds: 65, allowThreadYield: true); | ||
|
||
// Release the Read Lock, now that we've finished driving the Hx711 | ||
// serial interface. | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// PowerDown and restart component | ||
/// </summary> | ||
public void Reset() | ||
{ | ||
PowerDown(); | ||
PowerUp(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Dispose() | ||
{ | ||
if (_shouldDispose) | ||
{ | ||
_gpioController?.Dispose(); | ||
} | ||
else | ||
{ | ||
_gpioController?.ClosePin(_pinPdSck); | ||
_gpioController?.ClosePin(_pinDout); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Read weight from Hx711 | ||
/// </summary> | ||
/// <param name="numberOfReads">Number of readings to take from which to average, to get a more accurate value.</param> | ||
/// <returns>Return total weight - tare weight</returns> | ||
private int GetNetWeight(int numberOfReads) => _reader.Read(numberOfReads, _offsetFormZero) - _tareValue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks> | ||
<EnableDefaultItems>false</EnableDefaultItems> | ||
<LangVersion>10</LangVersion> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="*.cs" /> | ||
<None Include="README.md" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
namespace Iot.Device.Hx711 | ||
{ | ||
/// <summary> | ||
/// Exception thorw if Hx711 miss calibration process | ||
/// </summary> | ||
public class Hx711CalibrationNotDoneException : Exception | ||
{ | ||
private new const string Message = "Hx711 component need a calibration process first."; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="Hx711CalibrationNotDoneException"/> class. | ||
/// </summary> | ||
public Hx711CalibrationNotDoneException() | ||
: base(message: Message) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="Hx711CalibrationNotDoneException"/> class. | ||
/// </summary> | ||
/// <param name="inner">The exception that is the cause of the current exception.</param> | ||
public Hx711CalibrationNotDoneException(Exception inner) | ||
: base(message: Message, inner) | ||
{ | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Iot.Device.Hx711 | ||
{ | ||
/// <summary> | ||
/// Hx711 options for all manufacturers | ||
/// </summary> | ||
public sealed class Hx711Options | ||
{ | ||
/// <summary> | ||
/// Hx711 has 3 modes of operation, choose the one based on the fisical connection with load cell. | ||
/// Default value: <code>Mode = Hx711Mode.ChannelAGain128</code> | ||
/// </summary> | ||
public Hx711Mode Mode { get; private set; } | ||
|
||
/// <summary> | ||
/// If <code>true</code> bytes read from Hx711 made by Lsb format. | ||
/// Some Hx711 manufacturers return bytes in Lsb, but most in Msb. | ||
/// Default value: <code>UseByteLittleEndian = false</code> | ||
/// </summary> | ||
public bool UseByteLittleEndian { get; private set; } | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="Hx711Options"/> class with default values. | ||
/// </summary> | ||
public Hx711Options() | ||
{ | ||
Mode = Hx711Mode.ChannelAGain128; | ||
UseByteLittleEndian = false; | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="Hx711Options"/> class. | ||
/// </summary> | ||
/// <param name="mode">Hx711 has 3 modes of operation, choose the one based on the fisical connection with load cell.</param> | ||
/// <param name="useByteLittleEndian">If <code>true</code> bytes read from Hx711 made by Lsb format.</param> | ||
public Hx711Options(Hx711Mode mode, bool useByteLittleEndian) | ||
{ | ||
Mode = mode; | ||
UseByteLittleEndian = useByteLittleEndian; | ||
} | ||
} | ||
} |
Oops, something went wrong.