drivers/analog/hx711.c: Add driver for hx711 adc
Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
This commit is contained in:
parent
b634798bd6
commit
84a2cab886
220
Documentation/components/drivers/character/hx711.rst
Normal file
220
Documentation/components/drivers/character/hx711.rst
Normal file
@ -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
|
contactless.rst
|
||||||
crypto/index.rst
|
crypto/index.rst
|
||||||
efuse.rst
|
efuse.rst
|
||||||
|
hx711.rst
|
||||||
i2s.rst
|
i2s.rst
|
||||||
input/index.rst
|
input/index.rst
|
||||||
ipcc.rst
|
ipcc.rst
|
||||||
|
@ -91,6 +91,10 @@ if(CONFIG_ADC)
|
|||||||
if(CONFIG_ADC_LTC1867L)
|
if(CONFIG_ADC_LTC1867L)
|
||||||
list(APPEND SRCS ltc1867l.c)
|
list(APPEND SRCS ltc1867l.c)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_ADC_HX711)
|
||||||
|
list(APPEND SRCS hx711.c)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CONFIG_LMP92001)
|
if(CONFIG_LMP92001)
|
||||||
|
@ -192,6 +192,43 @@ endchoice
|
|||||||
|
|
||||||
endif # ADC_MAX1161X
|
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
|
endif # ADC
|
||||||
|
|
||||||
config COMP
|
config COMP
|
||||||
|
@ -103,6 +103,10 @@ endif
|
|||||||
ifeq ($(CONFIG_ADC_LTC1867L),y)
|
ifeq ($(CONFIG_ADC_LTC1867L),y)
|
||||||
CSRCS += ltc1867l.c
|
CSRCS += ltc1867l.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(CONFIG_ADC_HX711),y)
|
||||||
|
CSRCS += hx711.c
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(CONFIG_LMP92001),y)
|
ifeq ($(CONFIG_LMP92001),y)
|
||||||
|
961
drivers/analog/hx711.c
Normal file
961
drivers/analog/hx711.c
Normal file
@ -0,0 +1,961 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* drivers/analog/hx711.c
|
||||||
|
*
|
||||||
|
* 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/kmalloc.h>
|
||||||
|
#include <nuttx/irq.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <nuttx/analog/hx711.h>
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Pre-processor Definitions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#define DEVNAME_FMT "/dev/hx711_%d"
|
||||||
|
#define DEVNAME_FMTLEN (11 + 3 + 1)
|
||||||
|
|
||||||
|
/* hx711 is a 24 bit ADC, but in case they decide to do like a
|
||||||
|
* hx771s(uperb) with 32 bit resolution, here is easy to change def
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define HX711_BITS_PER_READ 24
|
||||||
|
|
||||||
|
#define HX711_TARE_MAX_LOOP 64
|
||||||
|
#define HX711_TARE_NSAMPLES 5
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Types
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
struct hx711_dev_s
|
||||||
|
{
|
||||||
|
FAR struct hx711_lower_s *lower;
|
||||||
|
mutex_t excl;
|
||||||
|
sem_t hx711_ready;
|
||||||
|
int crefs;
|
||||||
|
int unlinked;
|
||||||
|
unsigned char minor;
|
||||||
|
|
||||||
|
int val_per_unit;
|
||||||
|
long tare;
|
||||||
|
unsigned char average;
|
||||||
|
unsigned char gain;
|
||||||
|
char channel;
|
||||||
|
signed char sign;
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Function Prototypes
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_open(FAR struct file *filep);
|
||||||
|
static int hx711_close(FAR struct file *filep);
|
||||||
|
static int hx711_unlink(FAR struct inode *inode);
|
||||||
|
static int hx711_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
||||||
|
static ssize_t hx711_read(FAR struct file *filep,
|
||||||
|
FAR char *buf, size_t buflen);
|
||||||
|
static int32_t hx711_single_read(FAR struct hx711_dev_s *dev);
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Data
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static const struct file_operations g_hx711_fops =
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
||||||
|
.unlink = hx711_unlink,
|
||||||
|
#endif /* CONFIG_DISABLE_PSEUDOFS_OPERATIONS */
|
||||||
|
.open = hx711_open,
|
||||||
|
.close = hx711_close,
|
||||||
|
.read = hx711_read,
|
||||||
|
.ioctl = hx711_ioctl
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Public Data
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Private Functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_tare
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Tares the scale. Function will read some number of samples and will
|
||||||
|
* check if these readings are stable (more or less the same within
|
||||||
|
* specified precision). If operation is a success, next call to read()
|
||||||
|
* will return value close to 0 (if no force is applied to tensometer).
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* dev - hx711 instance to tare
|
||||||
|
* precision - precision with which to tare the scale. If set to 100
|
||||||
|
* function will set new tare if min-max values read are
|
||||||
|
* less than 100
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* OK - on success
|
||||||
|
* -EIO - no communication with the hx711
|
||||||
|
* -ETIME - scale was not stable for HX711_TARE_NSAMPLES loops
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_tare(FAR struct hx711_dev_s *dev, float precision)
|
||||||
|
{
|
||||||
|
int32_t samples[HX711_TARE_NSAMPLES];
|
||||||
|
int i;
|
||||||
|
int j;
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
long tare;
|
||||||
|
int prec;
|
||||||
|
long taresave;
|
||||||
|
|
||||||
|
/* If value per unit is defined, we assume precision is specified
|
||||||
|
* in units, calculate raw value for precision
|
||||||
|
*/
|
||||||
|
|
||||||
|
prec = dev->val_per_unit > 0 ? precision * dev->val_per_unit : precision;
|
||||||
|
|
||||||
|
/* Save old tare value, which we will restore when we have an error */
|
||||||
|
|
||||||
|
taresave = dev->tare;
|
||||||
|
|
||||||
|
/* Reset tare value during taring */
|
||||||
|
|
||||||
|
dev->tare = 0;
|
||||||
|
|
||||||
|
for (i = 0; i != HX711_TARE_NSAMPLES; i++)
|
||||||
|
{
|
||||||
|
samples[i] = hx711_single_read(dev);
|
||||||
|
if (samples[i] == INT32_MIN)
|
||||||
|
{
|
||||||
|
dev->tare = taresave;
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i != HX711_TARE_MAX_LOOP; i++)
|
||||||
|
{
|
||||||
|
/* Check if scale reading is stable */
|
||||||
|
|
||||||
|
min = INT_MAX;
|
||||||
|
max = INT_MIN;
|
||||||
|
for (j = 0; j != HX711_TARE_NSAMPLES; j++)
|
||||||
|
{
|
||||||
|
min = samples[j] < min ? samples[j] : min;
|
||||||
|
max = samples[j] > max ? samples[j] : max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max - min <= prec)
|
||||||
|
{
|
||||||
|
/* Scale readings are stable within specified precision.
|
||||||
|
* Use average of these readings to set new tare value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (tare = j = 0; j != HX711_TARE_NSAMPLES; j++)
|
||||||
|
{
|
||||||
|
tare += samples[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
tare /= HX711_TARE_NSAMPLES;
|
||||||
|
dev->tare = tare;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reading is not yet stable, perform next read and check
|
||||||
|
* stability again
|
||||||
|
*/
|
||||||
|
|
||||||
|
samples[i % HX711_TARE_NSAMPLES] = hx711_single_read(dev);
|
||||||
|
if (samples[i % HX711_TARE_NSAMPLES] == INT32_MIN)
|
||||||
|
{
|
||||||
|
dev->tare = taresave;
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we get here, we couldn't get stable readings within specified
|
||||||
|
* limit
|
||||||
|
*/
|
||||||
|
|
||||||
|
dev->tare = taresave;
|
||||||
|
return -ETIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_ioctl
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Perform device specific operations.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* filep - file on vfs associated with the driver.
|
||||||
|
* cmd - command to perform
|
||||||
|
* arg - argument for the cmd
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* Returns OK on success or negated errno on failure.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = filep->f_inode->i_private;
|
||||||
|
|
||||||
|
/* Get exclusive access to the hx711 driver state */
|
||||||
|
|
||||||
|
ret = nxmutex_lock(&dev->excl);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = OK;
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case HX711_SET_AVERAGE:
|
||||||
|
if (arg < 1 || arg > HX711_MAX_AVG_SAMPLES)
|
||||||
|
{
|
||||||
|
/* Averaging more than HX711_MAX_AVG_SAMPLES samples could
|
||||||
|
* overflow averaging variable leading to invalid reading.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->average = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HX711_SET_CHANNEL:
|
||||||
|
if (arg != 'a' || arg != 'b')
|
||||||
|
{
|
||||||
|
/* Only channel a or b are available */
|
||||||
|
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->channel = arg;
|
||||||
|
|
||||||
|
if (dev->channel == 'b')
|
||||||
|
{
|
||||||
|
/* Only valid gain for channel b is 32, adjust */
|
||||||
|
|
||||||
|
dev->gain = 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->channel == 'a')
|
||||||
|
{
|
||||||
|
/* If we are switching from channel 'b', gain will be 32,
|
||||||
|
* which is invalid value for channel 'a'. If current gain
|
||||||
|
* is not valid for channel 'a', set default value of 128
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (dev->gain != 128 && dev->gain != 64)
|
||||||
|
{
|
||||||
|
dev->gain = 128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Channel setting will be applied after next read from hx711,
|
||||||
|
* we have to do one dummy read, so that user can immediately
|
||||||
|
* read from new channel
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (hx711_single_read(dev) == INT32_MIN)
|
||||||
|
{
|
||||||
|
ret = -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HX711_SET_GAIN:
|
||||||
|
if (dev->channel == 'a' && (arg != 128 || arg != 64))
|
||||||
|
{
|
||||||
|
/* For channel 'a' only gain of value 128 and 64 are valid */
|
||||||
|
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (dev->channel == 'b' && arg != 32)
|
||||||
|
{
|
||||||
|
/* For channel 'b' only gain of 32 is valid */
|
||||||
|
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->gain = arg;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HX711_SET_VAL_PER_UNIT:
|
||||||
|
dev->val_per_unit = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HX711_GET_AVERAGE:
|
||||||
|
{
|
||||||
|
unsigned *ptr = (unsigned *)((uintptr_t)arg);
|
||||||
|
if (ptr == NULL)
|
||||||
|
{
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = dev->average;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HX711_GET_CHANNEL:
|
||||||
|
{
|
||||||
|
char *ptr = (char *)((uintptr_t)arg);
|
||||||
|
if (ptr == NULL)
|
||||||
|
{
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = dev->channel;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HX711_GET_GAIN:
|
||||||
|
{
|
||||||
|
unsigned char *ptr = (unsigned char *)((uintptr_t)arg);
|
||||||
|
if (ptr == NULL)
|
||||||
|
{
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = dev->gain;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HX711_GET_VAL_PER_UNIT:
|
||||||
|
{
|
||||||
|
unsigned *ptr = (unsigned *)((uintptr_t)arg);
|
||||||
|
if (ptr == NULL)
|
||||||
|
{
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ptr = dev->val_per_unit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HX711_TARE:
|
||||||
|
{
|
||||||
|
float *precision = (float *)((uintptr_t)arg);
|
||||||
|
if (precision == NULL)
|
||||||
|
{
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = hx711_tare(dev, *precision);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HX711_SET_SIGN:
|
||||||
|
{
|
||||||
|
int *sign = (int *)((uintptr_t)arg);
|
||||||
|
if (sign == NULL || (*sign != 1 && *sign != -1))
|
||||||
|
{
|
||||||
|
ret = EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->sign = *sign;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ret = EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_data_interrupt
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Function is called when we are waiting for hx711 to be ready and once
|
||||||
|
* data line goes from HIGH to LOW state.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* arg - hx711 device instance
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_data_interrupt(int irq, FAR void *context, FAR void *arg)
|
||||||
|
{
|
||||||
|
UNUSED(irq);
|
||||||
|
UNUSED(context);
|
||||||
|
FAR struct hx711_dev_s *dev = arg;
|
||||||
|
|
||||||
|
nxsem_post(&dev->hx711_ready);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_wait_ready
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Waits for conversion to be ready to read.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* dev - hx711 device instance
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* Function returns OK when chip is ready for reading, or -EIO, which
|
||||||
|
* means there is problem communicating with the device.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_wait_ready(FAR struct hx711_dev_s *dev)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct timespec tp;
|
||||||
|
|
||||||
|
/* It is possible that there was no read() call for long enough
|
||||||
|
* that hx711 is already ready, if that is the case just quickly return
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (dev->lower->data_read(dev->minor) == 0)
|
||||||
|
{
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Install data line interrupt, so we know when hx711 is ready.
|
||||||
|
* This can even be 100ms between sampling, and up to 500ms when
|
||||||
|
* hx711 goes out of low power mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((ret = dev->lower->data_irq(dev->minor, hx711_data_interrupt, dev)))
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* During waiting for ready signal, clock should be low */
|
||||||
|
|
||||||
|
dev->lower->clock_set(dev->minor, 0);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||||
|
tp.tv_sec += 1;
|
||||||
|
|
||||||
|
if ((ret = nxsem_timedwait(&dev->hx711_ready, &tp)))
|
||||||
|
{
|
||||||
|
/* Chip not ready for long time. This probably mean that the
|
||||||
|
* hx711 chip is not properly (if at all) connected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dev->lower->data_irq(dev->minor, NULL, NULL);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hx711 is ready */
|
||||||
|
|
||||||
|
dev->lower->data_irq(dev->minor, NULL, NULL);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_delay
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* hx711 datasheet specifies that time between clock changes should be
|
||||||
|
* between 0.2us and 50us, with typical value of 1us. On slow MCUs this
|
||||||
|
* is not a problem, as all operations between clocking take longer than
|
||||||
|
* that time, but on fast CHIP, clocking without delay will cause data
|
||||||
|
* lose.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void hx711_delay(void)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ADC_HX711_ADD_DELAY
|
||||||
|
up_delay(1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_single_read
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Reads single, 24bit adc data from hx711. Function will perform
|
||||||
|
* conversion form 24bit 2's complement to 32bit 2's complement.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* dev - hx711 instance to perform read from.
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* Read value from hx711. Returned value is stored on 24 bits of
|
||||||
|
* int32_t type. If there was error during read, function will
|
||||||
|
* return INT32_MIN.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int32_t hx711_single_read(FAR struct hx711_dev_s *dev)
|
||||||
|
{
|
||||||
|
int32_t value;
|
||||||
|
int i;
|
||||||
|
int pulses;
|
||||||
|
int flags;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Wait for conversion to be finished */
|
||||||
|
|
||||||
|
if ((ret = hx711_wait_ready(dev)))
|
||||||
|
{
|
||||||
|
/* Timeout while waiting for chip, assuming chip is not connected */
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return INT32_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Even though we are clocking the hx711, we must perform whole readout
|
||||||
|
* without interruption. This is because, if we set clock pin to HIGH,
|
||||||
|
* hx711 will go into low power mode in 60us unless we set clock to LOW
|
||||||
|
* within that time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
flags = enter_critical_section();
|
||||||
|
|
||||||
|
for (value = i = 0; i != HX711_BITS_PER_READ; i++)
|
||||||
|
{
|
||||||
|
dev->lower->clock_set(dev->minor, 1);
|
||||||
|
hx711_delay();
|
||||||
|
|
||||||
|
/* Data is sent MSB first */
|
||||||
|
|
||||||
|
value |= dev->lower->data_read(dev->minor);
|
||||||
|
value <<= 1;
|
||||||
|
dev->lower->clock_set(dev->minor, 0);
|
||||||
|
hx711_delay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next few clock pulses will determine type of next conversion
|
||||||
|
* hx711 will perform. We gotta do this in the same critical
|
||||||
|
* section block as read.
|
||||||
|
*
|
||||||
|
* 1 pulse - Channel A, Gain 128
|
||||||
|
* 2 pulses - Channel B, Gain 32
|
||||||
|
* 3 pulses - Channel A, Gain 64
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (dev->channel == 'b')
|
||||||
|
{
|
||||||
|
/* Channel B has static gain of 32 */
|
||||||
|
|
||||||
|
pulses = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* channel A has 2 possible gains, either 128 or 64. */
|
||||||
|
|
||||||
|
pulses = dev->gain == 128 ? 1 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i != pulses; i++)
|
||||||
|
{
|
||||||
|
dev->lower->clock_set(dev->minor, 1);
|
||||||
|
hx711_delay();
|
||||||
|
dev->lower->clock_set(dev->minor, 0);
|
||||||
|
hx711_delay();
|
||||||
|
}
|
||||||
|
|
||||||
|
leave_critical_section(flags);
|
||||||
|
|
||||||
|
/* Data is sent in standard 2's complement, but we just stored
|
||||||
|
* 24bit integer in a 32bit integer. For positives reading this
|
||||||
|
* makes no difference, but if we have just returned 24bit negative
|
||||||
|
* number in 32bit integer, we would end up with positive (and false)
|
||||||
|
* reading.
|
||||||
|
*
|
||||||
|
* If number is negative, convert it to 32bit negative.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (value & 0x800000)
|
||||||
|
{
|
||||||
|
value |= 0xff000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply tare value and sign at the end */
|
||||||
|
|
||||||
|
return dev->sign * (value + dev->tare);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_read
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Performs read from the hx711 device. Only a single value can be read
|
||||||
|
* with single call, but when averaging is enabled, driver will read
|
||||||
|
* configured number of points and will return single, average value
|
||||||
|
* of them all.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* filep - file on vfs associated with the driver.
|
||||||
|
* buf - pointer to 32bit integer where value will be stored.
|
||||||
|
* buflen - size of buf, must be equal to 4 (sizeof(int32_t))
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* On success 4 is returned (sizeof(int32_t)), as in number of bytes
|
||||||
|
* copied to userspace. On failure, negated errno is returned.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static ssize_t hx711_read(FAR struct file *filep,
|
||||||
|
FAR char *buf, size_t buflen)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
int ret;
|
||||||
|
int32_t value; /* 24bit value from hx711 will be stored here */
|
||||||
|
int32_t average;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
value = 0;
|
||||||
|
dev = filep->f_inode->i_private;
|
||||||
|
|
||||||
|
if (buflen == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buflen < sizeof(int32_t))
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get exclusive access to the hx711 driver state */
|
||||||
|
|
||||||
|
ret = nxmutex_lock(&dev->excl);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 1; i <= dev->average; i++)
|
||||||
|
{
|
||||||
|
value = hx711_single_read(dev);
|
||||||
|
if (value == INT32_MIN)
|
||||||
|
{
|
||||||
|
/* There was error while reading sample. */
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
average = (average * (i - 1) + value) / i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are done with the device, so free mutex for next possible client */
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
|
||||||
|
/* If user specified value per unit, we convert raw data into units */
|
||||||
|
|
||||||
|
if (dev->val_per_unit > 0)
|
||||||
|
{
|
||||||
|
average /= dev->val_per_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy data back to userspace and exit */
|
||||||
|
|
||||||
|
if (buflen == sizeof(int32_t))
|
||||||
|
{
|
||||||
|
/* int32 was passed, assuming binary operation from C code */
|
||||||
|
|
||||||
|
memcpy(buf, &average, sizeof(average));
|
||||||
|
return sizeof(int32_t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Something else passed, assuming it's shell operation. If it's
|
||||||
|
* called from C, it's assumed user wants c-string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ret = snprintf(buf, buflen, "%"PRIi32"\n", average);
|
||||||
|
|
||||||
|
/* snprintf returns number of bytes written (or that would have
|
||||||
|
* been written) without null byte, but we return number of bytes
|
||||||
|
* written including that byte, hence +1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ret += 1;
|
||||||
|
|
||||||
|
/* If buflen is not big enough, snprintf() will return number
|
||||||
|
* of bytes that would have been written to buf if enough space
|
||||||
|
* had been available and not number of bytes actually written.
|
||||||
|
* We must return number of bytes actually written, so we take
|
||||||
|
* smaller value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return MIN(ret, (int)buflen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_cleanup
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Called when last user closed hx711 dsevice and that device is (or was)
|
||||||
|
* unlinked.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* dev - hx711 device instance.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static void hx711_cleanup(FAR struct hx711_dev_s *dev)
|
||||||
|
{
|
||||||
|
/* Put chip into sleep state by setting clock to HIGH */
|
||||||
|
|
||||||
|
dev->lower->clock_set(dev->minor, 1);
|
||||||
|
|
||||||
|
if (dev->lower->cleanup)
|
||||||
|
{
|
||||||
|
dev->lower->cleanup(dev->minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
nxmutex_destroy(&dev->excl);
|
||||||
|
nxsem_destroy(&dev->hx711_ready);
|
||||||
|
kmm_free(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_open
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Open driver for use by userspace application.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* filep - pointer to a file structure to open
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* OK on success, or negated errno on failure
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_open(FAR struct file *filep)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = filep->f_inode->i_private;
|
||||||
|
|
||||||
|
/* Get exclusive access to the hx711 driver state */
|
||||||
|
|
||||||
|
ret = nxmutex_lock(&dev->excl);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increment the count of open references on the driver */
|
||||||
|
|
||||||
|
dev->crefs++;
|
||||||
|
DEBUGASSERT(dev->crefs > 0);
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_close
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Closes the driver device. If this is last reference and file has been
|
||||||
|
* unlinked, we will also free resources allocated by ipcc_register()
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* filep - pointer to a file structure to close.
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* OK on success, or negated errno on failure.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_close(FAR struct file *filep)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = filep->f_inode->i_private;
|
||||||
|
|
||||||
|
/* Get exclusive access to the hx711 driver state */
|
||||||
|
|
||||||
|
ret = nxmutex_lock(&dev->excl);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decrement the count of open references on the driver */
|
||||||
|
|
||||||
|
DEBUGASSERT(dev->crefs > 0);
|
||||||
|
dev->crefs--;
|
||||||
|
|
||||||
|
if (dev->crefs <= 0 && dev->unlinked)
|
||||||
|
{
|
||||||
|
/* If count ref is zero and file has been unlinked, it
|
||||||
|
* means nobody uses the driver and seems like nobody
|
||||||
|
* wants to use it anymore, so free up resources. This
|
||||||
|
* also means we are last holders of excl mutex, which
|
||||||
|
* will be destroyed in cleanup function, so we don't
|
||||||
|
* have to unlock it here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hx711_cleanup(dev);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Name: hx711_unlink
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Action to take upon file unlinking. Function will free resources if
|
||||||
|
* noone is using the driver when unlinking occured. If driver is still
|
||||||
|
* in use, it will be marked as unlinked and resource freeing will take
|
||||||
|
* place in hx711_close() function instead, once last reference is closed.
|
||||||
|
*
|
||||||
|
* Input Parameters:
|
||||||
|
* inode - driver inode that is being unlinked.
|
||||||
|
*
|
||||||
|
* Returned Value:
|
||||||
|
* OK on successfull close, or negated errno on failure.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
static int hx711_unlink(FAR struct inode *inode)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = inode->i_private;
|
||||||
|
|
||||||
|
/* Get exclusive access to the hx711 driver state */
|
||||||
|
|
||||||
|
ret = nxmutex_lock(&dev->excl);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is anyone still using the driver? */
|
||||||
|
|
||||||
|
if (dev->crefs <= 0)
|
||||||
|
{
|
||||||
|
/* No, we are free to free resources */
|
||||||
|
|
||||||
|
hx711_cleanup(dev);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Yes, someone is still using the driver, just mark file
|
||||||
|
* as unlinked and free resources in hx711_close() once last
|
||||||
|
* reference is closed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dev->unlinked = true;
|
||||||
|
nxmutex_unlock(&dev->excl);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Public Functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
FAR struct hx711_dev_s *dev;
|
||||||
|
char devname[DEVNAME_FMTLEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev = kmm_zalloc(sizeof(*dev));
|
||||||
|
if (dev == NULL)
|
||||||
|
{
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(devname, DEVNAME_FMTLEN, DEVNAME_FMT, minor);
|
||||||
|
ret = register_driver(devname, &g_hx711_fops, 0666, dev);
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
kmm_free(dev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->channel = 'a';
|
||||||
|
dev->gain = 128;
|
||||||
|
dev->lower = lower;
|
||||||
|
dev->average = 1;
|
||||||
|
dev->sign = 1;
|
||||||
|
nxmutex_init(&dev->excl);
|
||||||
|
nxsem_init(&dev->hx711_ready, 0, 0);
|
||||||
|
|
||||||
|
/* Put chip into working state by setting clock to LOW */
|
||||||
|
|
||||||
|
dev->lower->clock_set(dev->minor, 0);
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
212
include/nuttx/analog/hx711.h
Normal file
212
include/nuttx/analog/hx711.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user