1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-03-14 18:13:09 +01:00

Optimise HAL drivers for TOF sensor and Analogue Inputs

Increased use of async I2C in HAL drivers to reduce overall loop time overhead.
This commit is contained in:
Neil McKechnie 2021-10-05 12:48:45 +01:00
parent 7aed7de6cd
commit 6dde811279
2 changed files with 154 additions and 102 deletions

View File

@ -63,7 +63,9 @@ public:
_firstVpin = firstVpin; _firstVpin = firstVpin;
_nPins = min(nPins,4); _nPins = min(nPins,4);
_i2cAddress = i2cAddress; _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); addDevice(this);
} }
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) { static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
@ -73,6 +75,7 @@ private:
void _begin() { void _begin() {
// Initialise ADS device // Initialise ADS device
if (I2CManager.exists(_i2cAddress)) { if (I2CManager.exists(_i2cAddress)) {
_nextState = STATE_STARTSCAN;
#ifdef DIAG_IO #ifdef DIAG_IO
_display(); _display();
#endif #endif
@ -84,36 +87,48 @@ private:
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
// Check that previous non-blocking write has completed, if not then wait // 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 (status == I2C_STATUS_OK) {
// If _currentPin is in the valid range, continue reading the pin values switch (_nextState) {
if (_currentPin < _nPins) { case STATE_STARTSCAN:
_outBuffer[0] = 0x00; // Conversion register address // Configure ADC and multiplexer for next scan. See ADS111x datasheet for details
uint8_t status = I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1); // Read register // of configuration register settings.
if (status == I2C_STATUS_OK) { _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]; _value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
#ifdef IO_ANALOGUE_SLOW #ifdef IO_ANALOGUE_SLOW
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]); DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
#endif #endif
}
// Move to next pin
if (++_currentPin >= _nPins) _currentPin = 0;
_nextState = STATE_STARTSCAN;
break;
default:
break;
} }
} } else { // error status
if (status != I2C_STATUS_OK) {
DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED; _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 { int _readAnalogue(VPIN vpin) override {
@ -133,12 +148,18 @@ private:
#else #else
const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds. const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds.
#endif #endif
enum : uint8_t {
STATE_STARTSCAN,
STATE_STARTREAD,
STATE_GETVALUE,
};
uint16_t _value[4]; uint16_t _value[4];
uint8_t _i2cAddress; uint8_t _i2cAddress;
uint8_t _outBuffer[3]; uint8_t _outBuffer[3];
uint8_t _inBuffer[2]; uint8_t _inBuffer[2];
uint8_t _currentPin; // ADC pin currently being scanned uint8_t _currentPin; // ADC pin currently being scanned
I2CRB _i2crb; I2CRB _i2crb;
uint8_t _nextState;
}; };
#endif // io_analogueinputs_h #endif // io_analogueinputs_h

View File

@ -28,11 +28,12 @@
* The operation shown here doesn't include any calibration, so is probably not as accurate * 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. * 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) * 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), * 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 * 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, * 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 * you can use this address. However, the address can be modified by software. If
@ -100,9 +101,22 @@ private:
uint16_t _offThreshold; uint16_t _offThreshold;
VPIN _xshutPin; VPIN _xshutPin;
bool _value; bool _value;
bool _initialising = true; uint8_t _nextState = 0;
uint8_t _entryCount = 0; I2CRB _rb;
bool _scanInProgress = false; 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 // Register addresses
enum : uint8_t { enum : uint8_t {
VL53L0X_REG_SYSRANGE_START=0x00, VL53L0X_REG_SYSRANGE_START=0x00,
@ -130,89 +144,107 @@ public:
protected: protected:
void _begin() override { void _begin() override {
_initialising = true; if (_xshutPin == VPIN_NONE) {
// Check if device is already responding on the nominated address. // Check if device is already responding on the nominated address.
if (I2CManager.exists(_i2cAddress)) { if (I2CManager.exists(_i2cAddress)) {
// Yes, it's already on this address, so skip the address initialisation. // Yes, it's already on this address, so skip the address initialisation.
_entryCount = 3; _nextState = STATE_CONFIGUREDEVICE;
} else { } else {
_entryCount = 0; _nextState = STATE_INIT;
}
} }
} }
void _loop(unsigned long currentMicros) override { void _loop(unsigned long currentMicros) override {
if (_initialising) { uint8_t status;
switch (_entryCount++) { switch (_nextState) {
case 0: case STATE_INIT:
// On first entry to loop, reset this module by pulling XSHUT low. All modules // On first entry to loop, reset this module by pulling XSHUT low. All modules
// will be reset in turn. // will be reset in turn.
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0); if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
break; _nextState = STATE_CONFIGUREADDRESS;
case 1: break;
// On second entry, set XSHUT pin high to allow the module to restart. case STATE_CONFIGUREADDRESS:
// On the module, there is a diode in series with the XSHUT pin to // On second entry, set XSHUT pin high to allow the module to restart.
// protect the low-voltage pin against +5V. // On the module, there is a diode in series with the XSHUT pin to
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1); // protect the low-voltage pin against +5V.
// Allow the module time to restart if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1);
delay(10); // Allow the module time to restart
// Then write the desired I2C address to the device, while this is the only delay(10);
// module responding to the default address. // Then write the desired I2C address to the device, while this is the only
I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress); // module responding to the default address.
break; I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress);
case 3: _nextState = STATE_SKIP;
// After two more loops, check if device has been configured. break;
if (I2CManager.exists(_i2cAddress)) { case STATE_SKIP:
#ifdef DIAG_IO // Do nothing on the third entry.
_display(); _nextState = STATE_CONFIGUREDEVICE;
#endif break;
// Set 2.8V mode case STATE_CONFIGUREDEVICE:
write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV, // On next entry, check if device address has been set.
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); if (I2CManager.exists(_i2cAddress)) {
} else { #ifdef DIAG_IO
DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress); _display();
_deviceState = DEVSTATE_FAILED; #endif
} // Set 2.8V mode
_initialising = false; write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
_entryCount = 0; read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
break; } else {
default: DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress);
break; _deviceState = DEVSTATE_FAILED;
} }
} else { _nextState = STATE_INITIATESCAN;
break;
if (!_scanInProgress) { case STATE_INITIATESCAN:
// Not scanning, so initiate a scan // 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) { if (status != I2C_STATUS_OK) {
DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status)); DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED; _deviceState = DEVSTATE_FAILED;
_value = false; _value = false;
} else } else
_scanInProgress = true; _nextState = 2;
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
} else { _nextState = STATE_GETRESULTS;
// Scan in progress, so check for completion. break;
uint8_t status = read_reg(VL53L0X_REG_RESULT_RANGE_STATUS); case STATE_GETRESULTS:
if (status & 1) { // Ranging completed. Request results
// Completed. Retrieve data _outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
uint8_t inBuffer[12]; I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
read_registers(VL53L0X_REG_RESULT_RANGE_STATUS, inBuffer, 12); _nextState = 3;
uint8_t deviceRangeStatus = ((inBuffer[0] & 0x78) >> 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) { if (deviceRangeStatus == 0x0b) {
// Range status OK, so use data // Range status OK, so use data
_ambient = makeuint16(inBuffer[7], inBuffer[6]); _ambient = makeuint16(_inBuffer[7], _inBuffer[6]);
_signal = makeuint16(inBuffer[9], inBuffer[8]); _signal = makeuint16(_inBuffer[9], _inBuffer[8]);
_distance = makeuint16(inBuffer[11], inBuffer[10]); _distance = makeuint16(_inBuffer[11], _inBuffer[10]);
if (_distance <= _onThreshold) if (_distance <= _onThreshold)
_value = true; _value = true;
else if (_distance > _offThreshold) else if (_distance > _offThreshold)
_value = false; _value = false;
} }
_scanInProgress = false;
} }
} // Completed. Restart scan on next loop entry.
// Next entry in 10 milliseconds. _nextState = STATE_INITIATESCAN;
delayUntil(currentMicros + 10000UL); break;
default:
break;
} }
} }
@ -231,9 +263,12 @@ protected:
} }
} }
// For digital read, return the same value for all pins. // For digital read, return zero for all but first pin.
int _read(VPIN) override { int _read(VPIN vpin) override {
return _value; if (vpin == _firstVpin)
return _value;
else
return 0;
} }
void _display() override { void _display() override {
@ -255,13 +290,9 @@ private:
return I2CManager.write(_i2cAddress, outBuffer, 2); return I2CManager.write(_i2cAddress, outBuffer, 2);
} }
uint8_t read_reg(uint8_t reg) { uint8_t read_reg(uint8_t reg) {
// read byte from register register // read byte from register and return value
uint8_t inBuffer[1]; I2CManager.read(_i2cAddress, _inBuffer, 1, &reg, 1);
I2CManager.read(_i2cAddress, inBuffer, 1, &reg, 1); return _inBuffer[0];
return inBuffer[0];
}
void read_registers(uint8_t reg, uint8_t buffer[], uint8_t size) {
I2CManager.read(_i2cAddress, buffer, size, &reg, 1);
} }
}; };