/**************************************************************************** * 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; signed char signsave; /* 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 and sign, which we will restore when we * have an error */ taresave = dev->tare; signsave = dev->sign; /* Reset tare value and sign during taring */ dev->tare = 0; dev->sign = 1; for (i = 0; i != HX711_TARE_NSAMPLES; i++) { samples[i] = hx711_single_read(dev); if (samples[i] == INT32_MIN) { dev->tare = taresave; dev->sign = signsave; 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; dev->sign = signsave; 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; dev->sign = signsave; return -EIO; } } /* If we get here, we couldn't get stable readings within specified * limit */ dev->tare = taresave; dev->sign = signsave; 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: { FAR unsigned *ptr = (FAR unsigned *)((uintptr_t)arg); if (ptr == NULL) { ret = -EINVAL; break; } *ptr = dev->average; break; } case HX711_GET_CHANNEL: { FAR char *ptr = (FAR char *)((uintptr_t)arg); if (ptr == NULL) { ret = -EINVAL; break; } *ptr = dev->channel; break; } case HX711_GET_GAIN: { FAR unsigned char *ptr = (FAR unsigned char *)((uintptr_t)arg); if (ptr == NULL) { ret = -EINVAL; break; } *ptr = dev->gain; break; } case HX711_GET_VAL_PER_UNIT: { FAR unsigned *ptr = (FAR unsigned *)((uintptr_t)arg); if (ptr == NULL) { ret = -EINVAL; break; } *ptr = dev->val_per_unit; break; } case HX711_TARE: { FAR float *precision = (FAR float *)((uintptr_t)arg); if (precision == NULL) { ret = -EINVAL; break; } ret = hx711_tare(dev, *precision); break; } case HX711_SET_SIGN: { FAR int *sign = (FAR 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, sizeof(devname), 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; }