/// <summary>
        /// Creates an instance and reads current values.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The required mode bits are set, but the device state and PWM values unchanged (supporting recovery).
        /// Initializing without restart allows the caller decide whether to recover or reset the device.
        /// Before starting any output be sure to check the <see cref="INavioPwmDevice.Frequency"/>.
        /// </para>
        /// <para>
        /// To start with new settings, call <see cref="Reset"/> then set <see cref="Enabled"/>.
        /// </para>
        /// </remarks>
        public Navio1LedPwmDevice()
        {
            try
            {
                // Initialize members
                _pwmChannels         = new PwmPulse[PwmChannelCount];
                _pwmChannelsReadOnly = new ReadOnlyCollection <PwmPulse>(_pwmChannels);

                // Connect to hardware
                _device    = new Pca9685Device(I2cControllerIndex, ChipNumber, ExternalClockSpeed);
                _enablePin = GpioExtensions.Connect(GpioControllerIndex, OutputEnableGpioPin,
                                                    GpioPinDriveMode.Output, GpioSharingMode.Exclusive);

                // Read properties
                Read();
            }
            catch
            {
                // Close devices in case partially initialized
                _device?.Dispose();
                _enablePin?.Dispose();

                // Continue error
                throw;
            }
        }
        /// <summary>
        /// Sets a single channel value.
        /// </summary>
        void INavioPwmDevice.SetChannel(int number, PwmPulse value)
        {
            // Thread-safe lock
            lock (_lock)
            {
                // Validate
                if (number < 1 || number > PwmChannelCount)
                {
                    throw new ArgumentOutOfRangeException(nameof(number));
                }
                var index = number - 1;

                // Check frequency matches (no per-channel frequency on this device)
                if (value.Frequency != _device.Frequency)
                {
                    throw new ArgumentOutOfRangeException(nameof(value.Frequency));
                }

                // Do nothing when unchanged
                var oldValue = _pwmChannels[index];
                if (value == oldValue)
                {
                    return;
                }

                // Write new value
                _device.WriteChannelMs(PwmChannelIndex + index, value.Width);

                // Update property
                _pwmChannels[index] = value;
            }
        }
        /// <summary>
        /// Reads the PWM channels from the device then updates the related properties.
        /// </summary>
        void INavioPwmDevice.Read()
        {
            // Thread-safe lock
            lock (_lock)
            {
                // Read channels together (BGR ordered)
                var channels = _device.ReadChannels(PwmChannelIndex, PwmChannelCount);

                // Update channel properties
                var frequency = _device.Frequency;
                for (int index = 0; index < PwmChannelCount; index++)
                {
                    var widthTicks = channels[index].Width;
                    var widthMs    = Pca9685ChannelValue.CalculateWidthMs(frequency, widthTicks);
                    _pwmChannels[index] = PwmPulse.FromWidth(frequency, widthMs);
                }
            }
        }
        /// <summary>
        /// Reads the LED &amp; PWM channels from the device then updates the related properties.
        /// </summary>
        public void Read()
        {
            // Thread-safe lock
            lock (_lock)
            {
                // Read all PWM and LED channels
                _device.ReadAll();

                // Update LED properties
                _ledRed   = ~_device.Channels[LedRedChannelIndex].Width & 0xfff;
                _ledGreen = ~_device.Channels[LedGreenChannelIndex].Width & 0xfff;
                _ledBlue  = ~_device.Channels[LedGreenChannelIndex].Width & 0xfff;

                // Update PWM properties
                var frequency = _device.Frequency;
                for (int index = 0, pwmIndex = PwmChannelIndex; index < PwmChannelCount; index++, pwmIndex++)
                {
                    var widthTicks = _device.Channels[pwmIndex].Width;
                    var widthMs    = Pca9685ChannelValue.CalculateWidthMs(frequency, widthTicks);
                    _pwmChannels[index] = PwmPulse.FromWidth(frequency, widthMs);
                }
            }
        }