459b4434b1
ptype of ((int32_t)0 * (uint32_t)0) is (unsigned). "i" counter was declared unsigned to not make comparision with "unsigned char" in for loop. This resulted in calculation in average to be implicitly casted to unsigned, and when negative number was added to it, it turned into huge value. Change type of "i" to signed, and just cast (unsigned char) to (int) to fix this. Signed-off-by: Michał Łyszczek <michal.lyszczek@bofc.pl>
962 lines
24 KiB
C
962 lines
24 KiB
C
/****************************************************************************
|
|
* 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;
|
|
int 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 <= (int)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;
|
|
}
|