drivers/analog/hx711.c: Add driver for hx711 adc

Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
This commit is contained in:
Michał Łyszczek
2024-02-25 09:15:36 +01:00
committed by Alan Carvalho de Assis
parent b634798bd6
commit 84a2cab886
7 changed files with 1439 additions and 0 deletions
@@ -0,0 +1,220 @@
=============================
HX711 ADC DESIGNED FOR SCALES
=============================
Driver contributed by Michał Łyszczek.
HX711 is a 24bit ADC (Analog Digital Converter) designed for weight scales.
This chip can be very slow. With internal oscillator and RATE pin pulled
down, it outputs only 10 samples per second. To not hog down CPU, driver
uses interrupt to detect when chip is ready. This will make read(2) blocking,
but system can do whatever it needs before chip is ready. Because of that
driver does not fully follow ADC API, but rather standard character device
(read only).
Values from tensometer can be easily read from shell with ``cat`` command
.. code-block::
cat /dev/hxx711_0
Altough it may be better to dump values with example ``hx711`` program,
since ``cat`` will just read until the end of time, and if ctrl+c is
not working, it will steal shell forever.
-------
reading
-------
Reading is done by calling standard, posix, read(2) function. Only one value
can be returned with single call to read(2). But an averaging function can
be enabled, so that driver will read N samples, average them, and then will
return single averaged value.
This function accepts two types of buffer.
If buffer is of size ``sizeof(int32_t)`` a int32 value will be stored in
a buffer. If buffer size of bigger than ``sizeof(int32_t)`` function will
store string representation of values in passed buffer.
Simple code to read and print value may look like this
.. code-block:: C
int fd;
fd = open("/dev/hx711_0", O_RDONLY);
for (; ; )
{
int32_t value;
value = read(fd, &value, sizeof(value));
printf("Read: %"PRIi32"\n", value);
}
-----
ioctl
-----
Since this chip (and driver) is designed for weight scale, kernel driver
can provide some processing to make life easier for userspace code. These
functions are implemented via ioctl(2) commands. In practice, non of these
can be used, but if you just open driver and read it, you will get raw
values from hx711 chip, which you will have to process yourself. If your
needs are more standard, it's better to use kernel processing.
HX711_SET_AVERAGE
-----------------
.. code-block:: C
unsigned average = 5;
ioctl(fd, HX711_SET_AVERAGE, average);
Driver will read this number of samples from hx711 and will return average
value of them all. To avoid corrupted data due to integer overflow, max
average value that can be set is 225. If you need to average more values
you will need to write your own code for that.
HX711_SET_CHANNEL
-----------------
.. code-block:: C
char channel = 'a';
ioctl(fd, HX711_SET_CHANNEL, channel);
HX711 has 2 channels, A and B, which can be swapped as necessary. Driver
automatically performs dummy read, so that next call to read(2) will return
value from new channel. When you switch to channel 'B', driver automatically
changes gain to 32 (the only possible value). Going back to 'A' will set
gain to 128.
HX711_SET_GAIN
--------------
.. code-block:: C
unsigned char gain = 128;
ioctl(fd, HX711_SET_GAIN, gain);
Set gain. Channel 'A' supports gain "128" and "64". Channel 'B' has only
one gain option - 32.
HX711_SET_VAL_PER_UNIT
----------------------
.. code-block:: C
int val_per_unit = 813;
ioctl(fd, HX711_SET_VAL_PER_UNIT, val_per_unit);
Driver can perform calculations so that you can read physical values like
grams, ounce or pounds, or your own artificial unit. You just need to specify
what value from tensometer coresponds to one unit.
Say you have tensometer that has max value of 1'000'000. Value 100'000 means
1kg and sensor is fully linear. If you want to get readings in kg, you would
set ``val_per_unit`` to 100'000. If you wanted output in grams, it would be
value of 100. To have tenths of grams precision, you would set it to 10.
Driver does not care about unit, you just pick one and stick to it.
Note that driver can only return integers, so if you set it to return unit
of kg, you will only get 1, 2, 3kg... and you won't be able to sense 0.5kg
or 1.5kg. For that you would have to set value to 10'000, and driver would
return you values of 15 (for 1.5kg) or 0.5 (for 0.5kg).
HX711_TARE
----------
.. code-block:: C
float precision = 0.1;
ioctl(fd, HX711_TARE, &precision);
Every scale needs a tare function. Driver polls hx711 for some time, and if
it detects that scale is stable state, ioctl(2) will return with success,
and next read(2) call will take new tare value into consideration when
returning readings. Scale is assumed to be stable when several consecutive
readings are (min-max values) are within specified precition.
If ``HX711_SET_VAL_PER_UNIT`` was set prior to this, you can pass value
in your unit. If you configured driver to work with grams, you can set
this value to 0.1 (gram) or 5 (gram).
If driver cannot get stable reading within some time, it will return with
ETIME errno set.
Important note, make sure you have set correct sign before taring, or
else you will double your tare value instead of zeroing it!
HX711_SIGN
----------
.. code-block:: C
int sign = -1;
ioctl(fd, HX711_SIGN, &sign);
If values from drivers go lower when mass on scale goes higher you can swap
the sign. This may be necessary when tensometer was installed upside down.
---------------------
hx711 example program
---------------------
There is also companion program in Application Configuration ---> Examples
called ``HX711 driver example``. Main purpose of this is to show how to
use the driver, but it also is a very good tool for quickly debuging chip
from the shell, as it can dump readings and set all options.
.. code-block::
-h print this help message
-d<path> path to hx711 device, default: /dev/hx711_0
-t<prec> tares the scale with specified precision, might take few seconds to complete.
If you set value per unit, precision is in units, otherwise it's raw values.
If units are used, float can be passed like 0.1
-v<val> value read that coresponds to one unit. This value has to be
calibrated first before it's known
-s reverse sign, if values decreses when mass increases, pass this
-D dumps current device settings (like, average, channel, gain etc.)
-a<avg> set how many samples should be averaged before returning value,
values [1..225] are valid
-c<chan> set channel to read (either 'a' or 'b' is valid)
-g<gain> set adc gain, for channel 'a' 64 and 128 are valid,
for channel 'b', only 64 is valid
-r<num> read this number of samples before exiting, samples will be printed
on stdout as string, one sample per line
Set values are persistant, as in once set they are stored in driver and
will be applied during execution of this program.
If you specify only <-a|-c|-g|-v|-t> without -r, program will set new parameters
and exit. You can later call program again only with -r option to read
samples with previously set values. You can also pass all of them in one call
To test if you require CONFIG_ADC_HX711_ADD_DELAY option set, run as:
hx711 -a225 -r128
This will load hx711 chip long enough to show any possible errors due to
lack of added delay.
Program executes in order: set options, tare, dump, run, so if you specify all
options, new settings will be applied, then new settings will be printed
and at the end program will tare the scale and print samples
Examples:
Set hx711 settings for first chip and exit:
hx711 -a32 -ca -g64
Dump chip settings from different chip
hx711 -d/dev/hx711_2 -D
Read 10 samples with previously set hx711 settings
hx711 -r10
Change channel and read 32 samples (average setting won't change):
hx711 -cb -r32
Set value per unit, to get output in grams, and then tare with 10g precision
hx711 -v 813 -t 10
@@ -61,6 +61,7 @@ Character device drivers have these properties:
contactless.rst
crypto/index.rst
efuse.rst
hx711.rst
i2s.rst
input/index.rst
ipcc.rst
+4
View File
@@ -91,6 +91,10 @@ if(CONFIG_ADC)
if(CONFIG_ADC_LTC1867L)
list(APPEND SRCS ltc1867l.c)
endif()
if(CONFIG_ADC_HX711)
list(APPEND SRCS hx711.c)
endif()
endif()
if(CONFIG_LMP92001)
+37
View File
@@ -192,6 +192,43 @@ endchoice
endif # ADC_MAX1161X
config ADC_HX711
bool "Avia Semiconductor HX711 support"
default n
---help---
Enable driver to support Avia Semiconductor HX711 ADC
designed for weight scales.
Driver supports both 'a' and 'b' channels with 32, 64
and 128 gain.
Driver does not support continuous read and is not buffered.
Driver uses interrupts to not hog the CPU while waiting
for hx711 to be ready.
if ADC_HX711
config ADC_HA711_ADD_DELAY
bool "Add 1us delay between clock pulses"
default y if BOARD_LOOPSPERMSEC >= 15000
---help---
HX711 requires about 1us between clock pulses to work.
This is not an issue on slower chips, but faster chips
will most likely try to clock HX711 too fast, which
will result in data lose.
If this is enabled, code will insert 1us of delay to each
clock change. Enable this only if you get data lose, or
else you will just introduce unnecessary delay to your
program.
Best way to know if you need this, is to compile
HX711 demo program and run it. If there are no errors
reported during runtime, you can turn this of. If you
see communication errors, then you should enable this.
endif # ADC_HX711
endif # ADC
config COMP
+4
View File
@@ -103,6 +103,10 @@ endif
ifeq ($(CONFIG_ADC_LTC1867L),y)
CSRCS += ltc1867l.c
endif
ifeq ($(CONFIG_ADC_HX711),y)
CSRCS += hx711.c
endif
endif
ifeq ($(CONFIG_LMP92001),y)
File diff suppressed because it is too large Load Diff
+212
View File
@@ -0,0 +1,212 @@
/****************************************************************************
* include/nuttx/analog/hx711.h
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/compiler.h>
#include <nuttx/irq.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define HX711_MAX_AVG_SAMPLES 225
/* ioctl requests ***********************************************************/
/* Set how many samples to read from hx711 to get a single averaged value.
* Minimum value is 1. To prevent possible integer overflow, maximum value
* is HX711_MAX_AVG_SAMPLES.
*/
#define HX711_SET_AVERAGE 0
/* Set channel to use for next read() operation. Channels 'a' and 'b'
* are available. Specify channel as 'a' character (0x61 hex)
*/
#define HX711_SET_CHANNEL 1
/* Set gain to use for next read() operation. Channel 'b' only supports
* gain of 32, and channel 'a' supports gain 128 and 64
*/
#define HX711_SET_GAIN 2
/* Set what value coresponds to 1 unit. Takes integer.
* If set to 0 (default) driver will return raw readings from
* hx711 instead of calculated units.
*/
#define HX711_SET_VAL_PER_UNIT 3
/* Depending on tensometer position, value will go higher or lower
* (into negative values) when mass increases. If your sign does
* not match, it can be changed by calling this.
* 1 - no sign change (default)
* -1 - sign will be changed
*/
#define HX711_SET_SIGN 4
/* ioctl get functions */
/* Get current average, pass pointer to unsigned int type */
#define HX711_GET_AVERAGE 100
/* Get current channel, pass pointer to single char */
#define HX711_GET_CHANNEL 101
/* Get current gain, pass pointer to single unsignedchar */
#define HX711_GET_GAIN 102
/* Get current value per unit */
#define HX711_GET_VAL_PER_UNIT 103
/* Tare the scale. Accepts int value with desired precision.
* If HX711_VAL_PER_UNIT was set earlier, you should pass value
* in units, otherwise you need to pass raw value as read from hx711.
* Takes pointer to a float value.
*/
#define HX711_TARE 200
/****************************************************************************
* Public Types
****************************************************************************/
/* hx711 exposes 2 pins for communication. One is for data reading, and
* second one is clock signal. This is similar to i2c but hx711 uses custom
* protocol that is not compatible with i2c in any way.
*
* Platform code should provide these functions to manipulate these GPIOs
*/
struct hx711_lower_s
{
/**************************************************************************
* Name: clock_set
*
* Description:
* Sets underlying GPIO pin according to val.
*
* Input Parameters:
* val - set GPIO pin high (1) or low (0)
* minor - hx711 device being manipulated
*
* Returned Value:
* OK on success, or negated errno on failure
*
**************************************************************************/
CODE int (*clock_set)(unsigned char minor, int val);
/**************************************************************************
* Name: data_read
*
* Description:
* Reads current value of data GPIO pin.
*
* Input Parameters:
* minor - hx711 device being manipulated
*
* Returned Value:
* For success, return 0 when GPIO is low, 1 when GPIO is high
* or negated errno on failure.
*
**************************************************************************/
CODE int (*data_read)(unsigned char minor);
/**************************************************************************
* Name: cleanup
*
* Description:
* This function is called when last instance of minor is closed and
* unlinked from fs so that hx711 minor instance is no longer available.
* Platform should free all resources it allocated to register the
* device.
*
* This function does not have to be set, if there is nothing to clean.
*
* Input Parameters:
* minor - hx711 instance being destroyed
*
**************************************************************************/
CODE void (*cleanup)(unsigned char minor);
/**************************************************************************
* Name: data_irq
*
* Description:
* Setup (or tear down when handler is NULL) interrupt when data line
* goes from HIGH to LOW state (falling edge).
*
* hx711 is slow, on internal oscillator and RATE=0 it takes 100ms to
* sample a single reading. To avoid hogging CPU polling for data to
* go down, driver will install interrupt handler before reading.
* Once interrupt is received, driver will disable the handler.
*
* Input Parameters:
* minor - hx711 device being manipulated
* handler - function interrupt should call
* arg - private data for handler, should be passed to handler
*
* Returned Value:
* On successfull interrupt initialization 0 should be returned,
* when there was failure initializing interrupt -1 shall be returned.
*
**************************************************************************/
CODE int (*data_irq)(unsigned char minor, xcpt_t handler, void *arg);
};
/****************************************************************************
* Public Functions Prototypes
****************************************************************************/
/****************************************************************************
* Name: hx711_register
*
* Description:
* Register new hx711 device in /dev/hx711_%d. Multiple hx711 can be
* supported by providing different minor number. When driver calls
* platform specific function, minor number is passed back, so platform
* can know which hx711 is manipulated.
*
* Input Parameters:
* minor - unique number identifying hx711 chip.
* lower - provided by platform code to manipulate hx711 with platform
* dependant functions>
*
* Returned Value:
* OK on success, or negated errno on failure
*
****************************************************************************/
int hx711_register(unsigned char minor, FAR struct hx711_lower_s *lower);