diff --git a/IO_AnalogueInputs.h b/IO_AnalogueInputs.h index 725f300..85c224a 100644 --- a/IO_AnalogueInputs.h +++ b/IO_AnalogueInputs.h @@ -63,7 +63,9 @@ public: _firstVpin = firstVpin; _nPins = min(nPins,4); _i2cAddress = i2cAddress; - _currentPin = _nPins; // Suppress read on first loop entry. + _currentPin = 0; + for (int8_t i=0; i<_nPins; i++) + _value[i] = -1; addDevice(this); } static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { @@ -73,6 +75,7 @@ private: void _begin() { // Initialise ADS device if (I2CManager.exists(_i2cAddress)) { + _nextState = STATE_STARTSCAN; #ifdef DIAG_IO _display(); #endif @@ -84,36 +87,48 @@ private: void _loop(unsigned long currentMicros) override { // Check that previous non-blocking write has completed, if not then wait - uint8_t status = _i2crb.wait(); + uint8_t status = _i2crb.status; + if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything. if (status == I2C_STATUS_OK) { - // If _currentPin is in the valid range, continue reading the pin values - if (_currentPin < _nPins) { - _outBuffer[0] = 0x00; // Conversion register address - uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register - if (status == I2C_STATUS_OK) { + switch (_nextState) { + case STATE_STARTSCAN: + // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details + // of configuration register settings. + _outBuffer[0] = 0x01; // Config register address + _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n + _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off + // Write command, without waiting for completion. + I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); + + delayUntil(currentMicros + scanInterval); + _nextState = STATE_STARTREAD; + break; + + case STATE_STARTREAD: + // Reading the pin value + _outBuffer[0] = 0x00; // Conversion register address + I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register + _nextState = STATE_GETVALUE; + break; + + case STATE_GETVALUE: _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1]; #ifdef IO_ANALOGUE_SLOW DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); #endif - } + + // Move to next pin + if (++_currentPin >= _nPins) _currentPin = 0; + _nextState = STATE_STARTSCAN; + break; + + default: + break; } - } - if (status != I2C_STATUS_OK) { + } else { // error status DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; } - // Move to next pin - if (++_currentPin >= _nPins) _currentPin = 0; - - // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details - // of configuration register settings. - _outBuffer[0] = 0x01; // Config register address - _outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n - _outBuffer[2] = 0xA3; // 250 samples/sec, comparator off - // Write command, without waiting for completion. - I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb); - - delayUntil(currentMicros + scanInterval); } int _readAnalogue(VPIN vpin) override { @@ -133,12 +148,18 @@ private: #else const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds. #endif + enum : uint8_t { + STATE_STARTSCAN, + STATE_STARTREAD, + STATE_GETVALUE, + }; uint16_t _value[4]; uint8_t _i2cAddress; uint8_t _outBuffer[3]; uint8_t _inBuffer[2]; uint8_t _currentPin; // ADC pin currently being scanned I2CRB _i2crb; + uint8_t _nextState; }; #endif // io_analogueinputs_h \ No newline at end of file diff --git a/IO_VL53L0X.h b/IO_VL53L0X.h index 33c6a16..bcdbc49 100644 --- a/IO_VL53L0X.h +++ b/IO_VL53L0X.h @@ -28,11 +28,12 @@ * The operation shown here doesn't include any calibration, so is probably not as accurate * as using the full driver, but it's probably accurate enough for the purpose. * - * The device driver allocates up to 3 vpins to the device. A digital read on any of the pins + * The device driver allocates up to 3 vpins to the device. A digital read on the first pin * will return a value that indicates whether the object is within the threshold range (1) * or not (0). An analogue read on the first pin returns the last measured distance (in mm), * the second pin returns the signal strength, and the third pin returns detected - * ambient light level. + * ambient light level. By default the device takes around 60ms to complete a ranging + * operation, so we do a 100ms cycle (10 samples per second). * * The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module, * you can use this address. However, the address can be modified by software. If @@ -100,9 +101,22 @@ private: uint16_t _offThreshold; VPIN _xshutPin; bool _value; - bool _initialising = true; - uint8_t _entryCount = 0; - bool _scanInProgress = false; + uint8_t _nextState = 0; + I2CRB _rb; + uint8_t _inBuffer[12]; + uint8_t _outBuffer[2]; + // State machine states. + enum : uint8_t { + STATE_INIT = 0, + STATE_CONFIGUREADDRESS = 1, + STATE_SKIP = 2, + STATE_CONFIGUREDEVICE = 3, + STATE_INITIATESCAN = 4, + STATE_CHECKSTATUS = 5, + STATE_GETRESULTS = 6, + STATE_DECODERESULTS = 7, + }; + // Register addresses enum : uint8_t { VL53L0X_REG_SYSRANGE_START=0x00, @@ -130,89 +144,107 @@ public: protected: void _begin() override { - _initialising = true; - // Check if device is already responding on the nominated address. - if (I2CManager.exists(_i2cAddress)) { - // Yes, it's already on this address, so skip the address initialisation. - _entryCount = 3; - } else { - _entryCount = 0; + if (_xshutPin == VPIN_NONE) { + // Check if device is already responding on the nominated address. + if (I2CManager.exists(_i2cAddress)) { + // Yes, it's already on this address, so skip the address initialisation. + _nextState = STATE_CONFIGUREDEVICE; + } else { + _nextState = STATE_INIT; + } } } void _loop(unsigned long currentMicros) override { - if (_initialising) { - switch (_entryCount++) { - case 0: - // On first entry to loop, reset this module by pulling XSHUT low. All modules - // will be reset in turn. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); - break; - case 1: - // On second entry, set XSHUT pin high to allow the module to restart. - // On the module, there is a diode in series with the XSHUT pin to - // protect the low-voltage pin against +5V. - if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); - // Allow the module time to restart - delay(10); - // Then write the desired I2C address to the device, while this is the only - // module responding to the default address. - I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); - break; - case 3: - // After two more loops, check if device has been configured. - if (I2CManager.exists(_i2cAddress)) { - #ifdef DIAG_IO - _display(); - #endif - // Set 2.8V mode - write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, - read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); - } else { - DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); - _deviceState = DEVSTATE_FAILED; - } - _initialising = false; - _entryCount = 0; - break; - default: - break; - } - } else { - - if (!_scanInProgress) { + uint8_t status; + switch (_nextState) { + case STATE_INIT: + // On first entry to loop, reset this module by pulling XSHUT low. All modules + // will be reset in turn. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); + _nextState = STATE_CONFIGUREADDRESS; + break; + case STATE_CONFIGUREADDRESS: + // On second entry, set XSHUT pin high to allow the module to restart. + // On the module, there is a diode in series with the XSHUT pin to + // protect the low-voltage pin against +5V. + if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); + // Allow the module time to restart + delay(10); + // Then write the desired I2C address to the device, while this is the only + // module responding to the default address. + I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); + _nextState = STATE_SKIP; + break; + case STATE_SKIP: + // Do nothing on the third entry. + _nextState = STATE_CONFIGUREDEVICE; + break; + case STATE_CONFIGUREDEVICE: + // On next entry, check if device address has been set. + if (I2CManager.exists(_i2cAddress)) { + #ifdef DIAG_IO + _display(); + #endif + // Set 2.8V mode + write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); + } else { + DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); + _deviceState = DEVSTATE_FAILED; + } + _nextState = STATE_INITIATESCAN; + break; + case STATE_INITIATESCAN: // Not scanning, so initiate a scan - uint8_t status = write_reg(VL53L0X_REG_SYSRANGE_START, 0x01); + _outBuffer[0] = VL53L0X_REG_SYSRANGE_START; + _outBuffer[1] = 0x01; + I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb); + _nextState = STATE_CHECKSTATUS; + break; + case STATE_CHECKSTATUS: + status = _rb.status; + if (status == I2C_STATUS_PENDING) return; // try next time if (status != I2C_STATUS_OK) { DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); _deviceState = DEVSTATE_FAILED; _value = false; } else - _scanInProgress = true; - - } else { - // Scan in progress, so check for completion. - uint8_t status = read_reg(VL53L0X_REG_RESULT_RANGE_STATUS); - if (status & 1) { - // Completed. Retrieve data - uint8_t inBuffer[12]; - read_registers(VL53L0X_REG_RESULT_RANGE_STATUS, inBuffer, 12); - uint8_t deviceRangeStatus = ((inBuffer[0] & 0x78) >> 3); + _nextState = 2; + delayUntil(currentMicros + 95000); // wait for 95 ms before checking. + _nextState = STATE_GETRESULTS; + break; + case STATE_GETRESULTS: + // Ranging completed. Request results + _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS; + I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb); + _nextState = 3; + delayUntil(currentMicros + 5000); // Allow 5ms to get data + _nextState = STATE_DECODERESULTS; + break; + case STATE_DECODERESULTS: + // If I2C write still busy, return. + status = _rb.status; + if (status == I2C_STATUS_PENDING) return; // try again next time + if (status == I2C_STATUS_OK) { + if (!(_inBuffer[0] & 1)) return; // device still busy + uint8_t deviceRangeStatus = ((_inBuffer[0] & 0x78) >> 3); if (deviceRangeStatus == 0x0b) { // Range status OK, so use data - _ambient = makeuint16(inBuffer[7], inBuffer[6]); - _signal = makeuint16(inBuffer[9], inBuffer[8]); - _distance = makeuint16(inBuffer[11], inBuffer[10]); + _ambient = makeuint16(_inBuffer[7], _inBuffer[6]); + _signal = makeuint16(_inBuffer[9], _inBuffer[8]); + _distance = makeuint16(_inBuffer[11], _inBuffer[10]); if (_distance <= _onThreshold) _value = true; else if (_distance > _offThreshold) _value = false; } - _scanInProgress = false; } - } - // Next entry in 10 milliseconds. - delayUntil(currentMicros + 10000UL); + // Completed. Restart scan on next loop entry. + _nextState = STATE_INITIATESCAN; + break; + default: + break; } } @@ -231,9 +263,12 @@ protected: } } - // For digital read, return the same value for all pins. - int _read(VPIN) override { - return _value; + // For digital read, return zero for all but first pin. + int _read(VPIN vpin) override { + if (vpin == _firstVpin) + return _value; + else + return 0; } void _display() override { @@ -255,13 +290,9 @@ private: return I2CManager.write(_i2cAddress, outBuffer, 2); } uint8_t read_reg(uint8_t reg) { - // read byte from register register - uint8_t inBuffer[1]; - I2CManager.read(_i2cAddress, inBuffer, 1, ®, 1); - return inBuffer[0]; - } - void read_registers(uint8_t reg, uint8_t buffer[], uint8_t size) { - I2CManager.read(_i2cAddress, buffer, size, ®, 1); + // read byte from register and return value + I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1); + return _inBuffer[0]; } };