/// <summary>
        /// Writes the "on" and "off" values of a channel together, and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
        /// <param name="value"><see cref="NxpPca9685ChannelValue"/> to write.</param>
        /// <returns>
        /// Updated channel value or null when all channels were updated.
        /// </returns>
        public NxpPca9685Channel WriteChannel(int index, NxpPca9685ChannelValue value)
        {
            // Validate
            if (index <0 | index> ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            // Validate
            if (value.On > NxpPca9685ChannelValue.Maximum + 1 ||
                value.Off > NxpPca9685ChannelValue.Maximum + 1)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            // Calculate register address
            var register = GetChannelAddress(index);

            // Convert and write value
            var bytes = value.ToByteArray();

            Hardware.WriteJoinBytes(register, bytes);

            // Read and return result
            if (index < ChannelCount)
            {
                return(ReadChannel(index));
            }
            ReadAllChannels();
            return(null);
        }
        /// <summary>
        /// Calculates the "on" and "off" values of a channel from milliseconds (and optional delay),
        /// then writes them together, and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
        /// <param name="length">
        /// Pulse length in milliseconds. Cannot be greater than one clock interval (1000 / frequency).
        /// </param>
        /// <param name="delay">Optional delay in milliseconds. Cannot be greater than one clock interval (1000 / frequency).</param>
        /// <returns>
        /// Updated channel value or null when all channels were updated.
        /// </returns>
        public NxpPca9685Channel WriteChannelMilliseconds(int index, float length, float delay = 0)
        {
            // Read current frequency
            var frequency = ReadFrequency();

            // Call overloaded method
            return(WriteChannel(index, NxpPca9685ChannelValue.FromMilliseconds(length, frequency, delay)));
        }
        /// <summary>
        /// Reads the pre-scale register and calculates the <see cref="Frequency"/> (and related properties)
        /// based on <see cref="ClockSpeed"/>.
        /// </summary>
        /// <returns>
        /// Frequency in Hz. Related properties are also udpated.
        /// </returns>
        public float ReadFrequency()
        {
            // Read pre-scale register
            var preScale = Hardware.WriteReadByte((byte)NxpPca9685Register.PreScale);

            // Calculate frequency
            var frequency = CalculateFrequency(preScale, ClockSpeed);

            // Update related properties
            Frequency    = frequency;
            PwmMsMinimum = NxpPca9685ChannelValue.CalculateMilliseconds(Frequency, 0);
            PwmMsMaximum = NxpPca9685ChannelValue.CalculateMilliseconds(Frequency, NxpPca9685ChannelValue.Maximum);

            // Return result
            return(frequency);
        }
        /// <summary>
        /// Sets RGB values of the high intensity LED together (in one operation).
        /// </summary>
        /// <remarks>
        /// Automatically inverts the value, providing a natural behaviour where a higher number
        /// produces a higher LED intensity. Normally, when written as raw PWM values, the output
        /// is inverted due to common anode.
        /// </remarks>
        /// <param name="red">Red value in the range 0-<see cref="NxpPca9685ChannelValue.Maximum"/>.</param>
        /// <param name="green">Green value in the range 0-<see cref="NxpPca9685ChannelValue.Maximum"/>.</param>
        /// <param name="blue">Blue value in the range 0-<see cref="NxpPca9685ChannelValue.Maximum"/>.</param>
        public void SetLed(int red, int green, int blue)
        {
            // Validate
            if (red < 0 || red > NxpPca9685ChannelValue.Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(red));
            }
            if (green < 0 || green > NxpPca9685ChannelValue.Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(green));
            }
            if (blue < 0 || blue > NxpPca9685ChannelValue.Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(blue));
            }

            // Invert values
            var inverseRed   = ~red & 0x0fff;
            var inverseGreen = ~green & 0x0fff;
            var inverseBlue  = ~blue & 0x0fff;

            // Build block of data which sets all three register high and low values
            var redBytes   = NxpPca9685ChannelValue.FromLength(inverseRed).ToByteArray();
            var greenBytes = NxpPca9685ChannelValue.FromLength(inverseGreen).ToByteArray();
            var blueBytes  = NxpPca9685ChannelValue.FromLength(inverseBlue).ToByteArray();
            var data       = new byte[1 + ChannelSize * 3];

            data[0] = ChannelStartAddress;
            Array.ConstrainedCopy(blueBytes, 0, data, 1, ChannelSize);
            Array.ConstrainedCopy(greenBytes, 0, data, 1 + ChannelSize, ChannelSize);
            Array.ConstrainedCopy(redBytes, 0, data, 1 + ChannelSize * 2, ChannelSize);

            // Set new value
            Hardware.Write(data);

            // Update properties
            ReadLed();
        }
        /// <summary>
        /// Writes the "on" and "off" values of a channel together, and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
        /// <param name="value"><see cref="NxpPca9685ChannelValue"/> to write.</param>
        /// <returns>
        /// Updated channel value or null when all channels were updated.
        /// </returns>
        public NxpPca9685Channel WriteChannel(int index, NxpPca9685ChannelValue value)
        {
            // Validate
            if (index < 0 | index > ChannelCount) throw new ArgumentOutOfRangeException(nameof(index));

            // Validate
            if (value.On > NxpPca9685ChannelValue.Maximum + 1 ||
            value.Off > NxpPca9685ChannelValue.Maximum + 1)
                throw new ArgumentOutOfRangeException(nameof(value));

            // Calculate register address
            var register = GetChannelAddress(index);

            // Convert and write value
            var bytes = value.ToByteArray();
            Hardware.WriteJoinBytes(register, bytes);

            // Read and return result
            if (index < ChannelCount)
                return ReadChannel(index);
            ReadAllChannels();
            return null;
        }
 /// <summary>
 /// Calculates the "on" and "off" values of a channel from length (and optional delay),
 /// then writes them together, and updates it in <see cref="Channels"/>.
 /// </summary>
 /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
 /// <param name="length">Pulse length in clock ticks.</param>
 /// <param name="delay">Optional delay in clock ticks.</param>
 /// <returns>
 /// Updated channel value or null when all channels were updated.
 /// </returns>
 public NxpPca9685Channel WriteChannelLength(int index, int length, int delay = 0)
 {
     // Call overloaded method
     return(WriteChannel(index, NxpPca9685ChannelValue.FromLength(length, delay)));
 }
 /// <summary>
 /// Creates an instance with the specified values.
 /// </summary>
 public NxpPca9685Channel(int index, NxpPca9685ChannelValue value)
 {
     Index = index;
     Value = value;
     Value.Changed += OnValueChanged;
 }
 /// <summary>
 /// Creates an instance with the specified values.
 /// </summary>
 public NxpPca9685Channel(int index, NxpPca9685ChannelValue value)
 {
     Index          = index;
     Value          = value;
     Value.Changed += OnValueChanged;
 }