2024-02-25 09:15:36 +01:00
|
|
|
/****************************************************************************
|
|
|
|
* 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;
|
2024-03-02 17:27:37 +01:00
|
|
|
signed char signsave;
|
2024-02-25 09:15:36 +01:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2024-03-02 17:27:37 +01:00
|
|
|
/* Save old tare value and sign, which we will restore when we
|
|
|
|
* have an error
|
|
|
|
*/
|
2024-02-25 09:15:36 +01:00
|
|
|
|
|
|
|
taresave = dev->tare;
|
2024-03-02 17:27:37 +01:00
|
|
|
signsave = dev->sign;
|
2024-02-25 09:15:36 +01:00
|
|
|
|
2024-03-02 17:27:37 +01:00
|
|
|
/* Reset tare value and sign during taring */
|
2024-02-25 09:15:36 +01:00
|
|
|
|
|
|
|
dev->tare = 0;
|
2024-03-02 17:27:37 +01:00
|
|
|
dev->sign = 1;
|
2024-02-25 09:15:36 +01:00
|
|
|
|
|
|
|
for (i = 0; i != HX711_TARE_NSAMPLES; i++)
|
|
|
|
{
|
|
|
|
samples[i] = hx711_single_read(dev);
|
|
|
|
if (samples[i] == INT32_MIN)
|
|
|
|
{
|
|
|
|
dev->tare = taresave;
|
2024-03-02 17:27:37 +01:00
|
|
|
dev->sign = signsave;
|
2024-02-25 09:15:36 +01:00
|
|
|
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;
|
2024-03-02 17:27:37 +01:00
|
|
|
dev->sign = signsave;
|
2024-02-25 09:15:36 +01:00
|
|
|
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;
|
2024-03-02 17:27:37 +01:00
|
|
|
dev->sign = signsave;
|
2024-02-25 09:15:36 +01:00
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we get here, we couldn't get stable readings within specified
|
|
|
|
* limit
|
|
|
|
*/
|
|
|
|
|
|
|
|
dev->tare = taresave;
|
2024-03-02 17:27:37 +01:00
|
|
|
dev->sign = signsave;
|
2024-02-25 09:15:36 +01:00
|
|
|
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:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR unsigned *ptr = (FAR unsigned *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
if (ptr == NULL)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = dev->average;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case HX711_GET_CHANNEL:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR char *ptr = (FAR char *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
if (ptr == NULL)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = dev->channel;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case HX711_GET_GAIN:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR unsigned char *ptr = (FAR unsigned char *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
if (ptr == NULL)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = dev->gain;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case HX711_GET_VAL_PER_UNIT:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR unsigned *ptr = (FAR unsigned *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
if (ptr == NULL)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = dev->val_per_unit;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case HX711_TARE:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR float *precision = (FAR float *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
if (precision == NULL)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = hx711_tare(dev, *precision);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case HX711_SET_SIGN:
|
|
|
|
{
|
2024-08-25 01:21:12 +02:00
|
|
|
FAR int *sign = (FAR int *)((uintptr_t)arg);
|
2024-02-25 09:15:36 +01:00
|
|
|
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 */
|
|
|
|
|
2024-03-02 17:27:37 +01:00
|
|
|
return dev->sign * (value - dev->tare);
|
2024-02-25 09:15:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* 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;
|
2024-03-02 17:23:33 +01:00
|
|
|
int i;
|
2024-02-25 09:15:36 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:33 +01:00
|
|
|
for (i = 1; i <= (int)dev->average; i++)
|
2024-02-25 09:15:36 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-08-24 18:54:12 +02:00
|
|
|
snprintf(devname, sizeof(devname), DEVNAME_FMT, minor);
|
2024-02-25 09:15:36 +01:00
|
|
|
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;
|
|
|
|
}
|