diff --git a/Documentation/components/drivers/character/hx711.rst b/Documentation/components/drivers/character/hx711.rst new file mode 100644 index 00000000000..198e2713d15 --- /dev/null +++ b/Documentation/components/drivers/character/hx711.rst @@ -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 to hx711 device, default: /dev/hx711_0 + -t 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 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 set how many samples should be averaged before returning value, + values [1..225] are valid + -c set channel to read (either 'a' or 'b' is valid) + -g set adc gain, for channel 'a' 64 and 128 are valid, + for channel 'b', only 64 is valid + -r 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 diff --git a/Documentation/components/drivers/character/index.rst b/Documentation/components/drivers/character/index.rst index 555b92bd0dc..20ae9082263 100644 --- a/Documentation/components/drivers/character/index.rst +++ b/Documentation/components/drivers/character/index.rst @@ -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 diff --git a/drivers/analog/CMakeLists.txt b/drivers/analog/CMakeLists.txt index 3631ef0b73c..2a5e8ebb929 100644 --- a/drivers/analog/CMakeLists.txt +++ b/drivers/analog/CMakeLists.txt @@ -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) diff --git a/drivers/analog/Kconfig b/drivers/analog/Kconfig index b6536a49bb3..a95be948d15 100644 --- a/drivers/analog/Kconfig +++ b/drivers/analog/Kconfig @@ -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 diff --git a/drivers/analog/Make.defs b/drivers/analog/Make.defs index c649266018b..45ee37f371d 100644 --- a/drivers/analog/Make.defs +++ b/drivers/analog/Make.defs @@ -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) diff --git a/drivers/analog/hx711.c b/drivers/analog/hx711.c new file mode 100644 index 00000000000..90f04178be5 --- /dev/null +++ b/drivers/analog/hx711.c @@ -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 +#include +#include +#include +#include + +#include +#include + +#include + +/**************************************************************************** + * 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; +} diff --git a/include/nuttx/analog/hx711.h b/include/nuttx/analog/hx711.h new file mode 100644 index 00000000000..b8aa4daa1f1 --- /dev/null +++ b/include/nuttx/analog/hx711.h @@ -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 +#include +#include + +/**************************************************************************** + * 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);