Ejemplo n.º 1
0
        /// <summary>
        /// Reads the PWM "off" (falling) value of a channel, and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15).</param>
        /// <returns>Channel value.</returns>
        public int ReadChannelOff(int index)
        {
            // Validate
            if (index < 0 | index >= ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            // Calculate register address of second word value
            var register = (byte)(GetChannelAddress(index) + sizeof(ushort));

            // Read and convert value
            var bytes = I2cExtensions.WriteReadBytes(_hardware, register, sizeof(ushort));
            var value = BitConverter.ToUInt16(bytes, 0);

            // Update channel when changed
            var oldValue = _channels[index];

            if (oldValue.Off != value)
            {
                _channels[index] = new Pca9685ChannelValue(oldValue.On, value);
            }

            // Return result
            return(value);
        }
Ejemplo n.º 2
0
        /// <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="Pca9685ChannelValue"/> to write.</param>
        /// <returns>
        /// Updated channel value or null when all channels were updated.
        /// </returns>
        public Pca9685ChannelValue?WriteChannel(int index, Pca9685ChannelValue value)
        {
            // Validate
            if (index <0 | index> ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

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

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

            I2cExtensions.WriteJoinBytes(_hardware, register, bytes);

            // Read and return result when single channel
            if (index < ChannelCount)
            {
                return(ReadChannel(index));
            }

            // Read all channels when "all call".
            ReadAllChannels();
            return(null);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Calculates the prescale value from the frequency (according to <see cref="ClockSpeed"/>)
        /// then writes that register, then calls <see cref="ReadFrequency"/> to update properties.
        /// Note the actual frequency may differ to the requested frequency due to clock scale (rounding).
        /// </summary>
        /// <remarks>
        /// The prescale can only be set during sleep mode. This method enters <see cref="Sleep"/> if necessary,
        /// then only if the device was awake before, calls <see cref="Wake"/> afterwards. It's important not to
        /// start output unexpectedly to avoid damage, i.e. if the device was sleeping before, the frequency is
        /// changed without starting the oscillator.
        /// </remarks>
        /// <param name="frequency">Desired frequency to set in Hz.</param>
        /// <returns>
        /// Effective frequency in Hz, read-back and recalculated after setting the desired frequency.
        /// Frequency in Hz. Related properties are also updated.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown when <paramref name="frequency"/> is less than <see cref="FrequencyMinimum"/> or greater than
        /// <see cref="FrequencyMaximum"/>.
        /// </exception>
        public int WriteFrequency(int frequency)
        {
            // Validate
            if (frequency < FrequencyMinimum || frequency > FrequencyMaximum)
            {
                throw new ArgumentOutOfRangeException(nameof(frequency));
            }

            // Calculate prescale
            var prescale = CalculatePrescale(frequency, ClockSpeed);

            // Enter sleep mode and record wake status
            var wasAwake = Sleep();

            // Write prescale
            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.Prescale, prescale);

            // Read result
            var actual = ReadFrequency();

            // Wake-up if previously running
            if (wasAwake)
            {
                Wake();
            }

            // Update related properties
            PwmMsMinimum = Pca9685ChannelValue.CalculateWidthMs(frequency, 0);
            PwmMsMaximum = Pca9685ChannelValue.CalculateWidthMs(frequency, Pca9685ChannelValue.Maximum);

            // Return actual frequency
            return(actual);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Reads a single byte at the "current address" (next byte after the last operation).
        /// </summary>
        public byte[] ReadPage(int length)
        {
            // Validate
            if (length < 0 || length > Size)
            {
                throw new ArgumentOutOfRangeException(nameof(length));
            }

            // Get correct I2C device for memory address and flags
            var device = GetI2cDeviceForMemoryAddress(0);

            // Read data with chunking when transfer size exceeds limit
            var resultBuffer = new byte[length];
            var remaining = length; var offset = 0;

            do
            {
                // Check transfer size and reduce when necessary
                var transferSize = remaining;
                if (transferSize > I2cExtensions.MaximumTransferSize)
                {
                    transferSize = I2cExtensions.MaximumTransferSize;
                }

                // Read data
                var buffer = I2cExtensions.ReadBytes(device, transferSize);
                Array.ConstrainedCopy(buffer, 0, resultBuffer, offset, transferSize);

                // Next transfer when necessary...
                remaining -= transferSize;
                offset    += transferSize;
            }while (remaining > 0);
            return(resultBuffer);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Reads a single byte at the "current address" (next byte after the last operation).
        /// </summary>
        public byte ReadByte()
        {
            // Get correct I2C device for memory address and flags
            var device = GetI2cDeviceForMemoryAddress(0);

            // Read and return data
            return(I2cExtensions.ReadByte(device));
        }
Ejemplo n.º 6
0
        public Mb85rc256vDevice(int busNumber, byte chipNumber,
                                I2cBusSpeed speed = I2cBusSpeed.FastMode, I2cSharingMode sharingMode = I2cSharingMode.Exclusive)
            : base(chipNumber, MemorySize)
        {
            // Get address
            var address = GetDataI2cAddress(chipNumber);

            // Connect to hardware
            Hardware = I2cExtensions.Connect(busNumber, address, speed, sharingMode);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Reads all channels and updates <see cref="Channels"/>.
        /// </summary>
        public void ReadAllChannels()
        {
            // Read all channels as one block of data
            var data = I2cExtensions.WriteReadBytes(_hardware, ChannelStartAddress, ChannelSize * ChannelCount);

            // Update properties
            for (var index = 0; index < ChannelCount; index++)
            {
                _channels[index] = Pca9685ChannelValue.FromByteArray(data, ChannelSize * index);
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Reads the current value of the <see cref="Pca9685Register.Mode2"/> register.
        /// </summary>
        /// <returns>Bit flags corresponding to the actual mode byte.</returns>
        public Pca9685Mode2Bits ReadMode2()
        {
            // Read register
            var value = (Pca9685Mode2Bits)I2cExtensions.WriteReadByte(_hardware, (byte)Pca9685Register.Mode2);

            // Update property
            Mode2Register = value;

            // Return result
            return(value);
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Reads a coefficient value from the PROM.
        /// </summary>
        /// <param name="index">Coefficient index (0-7).</param>
        /// <param name="buffer">Target buffer.</param>
        /// <param name="offset">Target offset.</param>
        /// <remarks>
        /// Reads <see cref="Ms5611PromData.CoefficientSize"/> bytes into the target buffer at the specified offset.
        /// </remarks>
        private void ReadPromCoefficient(int index, byte[] buffer, int offset)
        {
            // Calculate address
            var coefficientOffset = (byte)(index * Ms5611PromData.CoefficientSize);
            var address           = (byte)(Ms5611Command.PromRead + coefficientOffset);

            // Read from hardware return value
            var coefficient = I2cExtensions.WriteReadBytes(_hardware, address, Ms5611PromData.CoefficientSize);

            Array.ConstrainedCopy(coefficient, 0, buffer, offset, Ms5611PromData.CoefficientSize);
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Executes the <see cref="Ms5611Command.ConvertD2Temperature"/> command to measure
        /// pressure at the specified OSR, waits then returns the result.
        /// </summary>
        public int ConvertTemperature(Ms5611Osr rate)
        {
            // Send command to hardware
            I2cExtensions.WriteJoinByte(_hardware, (byte)(Ms5611Command.ConvertD2Temperature + (byte)rate), 0);

            // Wait for completion
            WaitForConversion(rate);

            // Return result
            var result = I2cExtensions.WriteReadBytes(_hardware, (byte)Ms5611Command.AdcRead, 3);

            return(result[0] << 16 | result[1] << 8 | result[2]);
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Clears all channels cleanly, then updates all <see cref="Channels"/>.
        /// </summary>
        /// <remarks>
        /// To "cleanly" clear the channels, it is necessary to first ensure they are not disabled,
        /// set them to zero, then disable them. Otherwise the ON value and the low OFF value
        /// remain because writes are ignored when the OFF channel bit 12 is already set.
        /// </remarks>
        public void Clear()
        {
            // Enable all channels
            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.AllChannelsOffHigh, 0x00);

            // Zero all channels
            I2cExtensions.WriteJoinBytes(_hardware, (byte)Pca9685Register.AllChannelsOnLow, new byte[] { 0x00, 0x00, 0x00, 0x00 });

            // Disable all channels
            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.AllChannelsOffHigh, 0x10);

            // Update channels
            ReadAllChannels();
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Resets the device, updates PROM data and clears current measurements.
        /// </summary>
        public void Reset()
        {
            // Send reset command
            I2cExtensions.WriteJoinByte(_hardware, (byte)Ms5611Command.Reset, 0);

            // Wait for completion
            Task.Delay(TimeSpanExtensions.FromMicroseconds(ResetTime)).Wait();

            // Update PROM values
            ReadProm();

            // Clear measurements
            Pressure    = 0;
            Temperature = 0;
        }
Ejemplo n.º 13
0
        /// <summary>
        /// Reads a single byte "randomly" at the specified address.
        /// </summary>
        public byte ReadByte(int address)
        {
            // Validate
            if (address < 0 || address > Size - 1)
            {
                throw new ArgumentOutOfRangeException(nameof(address));
            }

            // Get correct I2C device and data for memory address and flags
            var device       = GetI2cDeviceForMemoryAddress(address);
            var addressBytes = GetMemoryAddressBytes(address);

            // Read and return data
            return(I2cExtensions.WriteReadByte(device, addressBytes));
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Writes a single byte at the specified address.
        /// </summary>
        public void WriteByte(int address, byte data)
        {
            // Validate
            if (address < 0 || address > Size - 1)
            {
                throw new ArgumentOutOfRangeException(nameof(address));
            }

            // Get correct I2C device and data for memory address and flags
            var device       = GetI2cDeviceForMemoryAddress(address);
            var addressBytes = GetMemoryAddressBytes(address);

            // Write data
            I2cExtensions.WriteJoinByte(device, addressBytes, data);
        }
Ejemplo n.º 15
0
        /// <summary>
        /// Writes the PWM "on" (rising) value of a channel.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
        /// <param name="value">12-bit channel value in the range 0-<see cref="Pca9685ChannelValue.Maximum"/>.</param>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="value"/> is greater than <see cref="Pca9685ChannelValue.Maximum"/>.</exception>
        public void WriteChannelOn(int index, int value)
        {
            // Validate
            if (value > Pca9685ChannelValue.Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

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

            // Convert and write value
            var data = BitConverter.GetBytes(value);

            I2cExtensions.WriteJoinBytes(_hardware, register, data);
        }
Ejemplo n.º 16
0
        /// <summary>
        /// Reads the prescale register and calculates the <see cref="Frequency"/> (and related properties)
        /// based on <see cref="ClockSpeed"/>.
        /// </summary>
        /// <returns>
        /// Frequency in Hz. Related properties are also updated.
        /// </returns>
        public int ReadFrequency()
        {
            // Read prescale register
            var prescale = I2cExtensions.WriteReadByte(_hardware, (byte)Pca9685Register.Prescale);

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

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

            // Return result
            return(frequency);
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Writes the PWM "off" (falling) value of a channel.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15) or 16 for the "all call" channel.</param>
        /// <param name="value">12-bit channel value in the range 0-<see cref="Pca9685ChannelValue.Maximum"/>.</param>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="value"/> is greater then <see cref="Pca9685ChannelValue.Maximum"/>.</exception>
        public void WriteChannelOff(int index, int value)
        {
            // Validate
            if (value > Pca9685ChannelValue.Maximum)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            // Calculate register address of second word value
            var register = (byte)(GetChannelAddress(index) + sizeof(ushort));

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

            I2cExtensions.WriteJoinBytes(_hardware, register, bytes);
        }
Ejemplo n.º 18
0
        public Ms5611Device(int busNumber, bool csb, Ms5611Osr rate,
                            I2cBusSpeed speed = I2cBusSpeed.FastMode, I2cSharingMode sharingMode = I2cSharingMode.Exclusive)
        {
            // Get address
            ChipSelectBit = csb;
            Address       = GetI2cAddress(csb);

            // Connect to hardware
            _hardware = I2cExtensions.Connect(busNumber, Address, speed, sharingMode);

            // Initialize members
            Prom = new Ms5611PromData();
            Osr  = rate;

            // Read current calibration data (potentially not stable until next reset)
            // We don't reset automatically so that it is possible for any ongoing tasks to complete
            ReadProm();
        }
Ejemplo n.º 19
0
        /// <summary>
        /// Reads a whole channel value (on and off), and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15).</param>
        /// <returns>Channel value</returns>
        public Pca9685ChannelValue ReadChannel(int index)
        {
            // Validate
            if (index < 0 | index >= ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

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

            // Read value
            var bytes = I2cExtensions.WriteReadBytes(_hardware, register, sizeof(ushort) * 2);

            // Update channel property and return result
            var value = Pca9685ChannelValue.FromByteArray(bytes);

            return(_channels[index] = value);
        }
Ejemplo n.º 20
0
        /// <summary>
        /// Writes multiple channels together (both "on" and "off" values), 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="values">Collection of <see cref="Pca9685ChannelValue"/>s to write.</param>
        public void WriteChannels(int index, IList <Pca9685ChannelValue> values)
        {
            // Validate
            if (index <0 | index> ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }
            if (values == null || values.Count == 0)
            {
                throw new ArgumentNullException(nameof(values));
            }
            var count = values.Count;

            if (index + count > ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(values));
            }

            // Build I2C packet
            var data = new byte[1 + ChannelSize * count];

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

            data[0] = register;

            // Write channels and update properties
            for (int dataIndex = 0, dataOffset = 1; dataIndex < count; dataIndex++, dataOffset += ChannelSize)
            {
                // Get channel data
                var channelData = values[dataIndex].ToByteArray();

                // Copy to buffer
                Array.ConstrainedCopy(channelData, 0, data, dataOffset, ChannelSize);

                // Update property
                _channels[index + dataIndex] = Pca9685ChannelValue.FromByteArray(channelData);
            }

            // Send packet
            I2cExtensions.WriteBytes(_hardware, data);
        }
Ejemplo n.º 21
0
        public static Mb85rcvDeviceId?GetDeviceId(int busNumber, byte idAddress, byte dataAddress,
                                                  I2cBusSpeed speed = I2cBusSpeed.FastMode, I2cSharingMode sharingMode = I2cSharingMode.Exclusive)
        {
            // Connect to ID device
            using (var framIdRegister = I2cExtensions.Connect(busNumber, idAddress, speed, sharingMode))
            {
                // Send ID command sequence, returning device ID bytes
                var dataAddress8bit = (byte)(dataAddress << 1);
                var data            = new byte[Mb85rcvDeviceId.Size];
                var transfer        = framIdRegister.WriteReadPartial(new[] { dataAddress8bit }, data);
                if (transfer.Status == I2cTransferStatus.SlaveAddressNotAcknowledged)       // Any other error must throw
                {
                    // Return null when not found
                    return(null);
                }

                // Decode and return ID when found
                return(new Mb85rcvDeviceId(data));
            }
        }
Ejemplo n.º 22
0
        /// <summary>
        /// Restarts the device with additional options specified, then updates all properties.
        /// </summary>
        /// <param name="options">
        /// Optional mode 1 parameters to add to the final restart sequence. A logical OR is applied to this value and
        /// the standard <see cref="Pca9685Mode1Bits.Restart"/>, <see cref="Pca9685Mode1Bits.ExternalClock"/> and
        /// <see cref="Pca9685Mode1Bits.AutoIncrement"/> bits.
        /// </param>
        public void Restart(Pca9685Mode1Bits options)
        {
            // Configure according to external clock presence
            var externalClock = ClockIsExternal;
            var delay         = TimeSpan.FromTicks(Convert.ToInt64(Math.Round(TimeSpan.TicksPerMillisecond * 0.5)));

            // Send I2C restart sequence...

            // Write first sleep
            var sleep = (byte)Pca9685Mode1Bits.Sleep;

            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.Mode1, sleep);

            // Write sleep again with external clock option (when present)
            if (externalClock)
            {
                sleep |= (byte)(Pca9685Mode1Bits.ExternalClock);
                I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.Mode1, sleep);
            }
            else
            {
                // At least 500 nanoseconds sleep required using internal clock
                Task.Delay(delay).Wait();
            }

            // Write reset with external clock option and any additional options
            var restart = (byte)(Pca9685Mode1Bits.Restart | Pca9685Mode1Bits.AutoIncrement);

            if (externalClock)
            {
                restart |= (byte)Pca9685Mode1Bits.ExternalClock;
            }
            restart |= (byte)options;
            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.Mode1, restart);

            // At least 500 nanoseconds delay to allow oscillator to start
            Task.Delay(delay).Wait();

            // Update all properties
            ReadAll();
        }
Ejemplo n.º 23
0
        public Pca9685Device(int busNumber, byte chipNumber, int?clockSpeed,
                             I2cBusSpeed speed = I2cBusSpeed.FastMode, I2cSharingMode sharingMode = I2cSharingMode.Exclusive)
        {
            // Validate
            if (clockSpeed.HasValue && (clockSpeed == 0 || clockSpeed.Value > ClockSpeedMaximum))
            {
                throw new ArgumentOutOfRangeException(nameof(clockSpeed));
            }

            // Get address
            var address = GetI2cAddress(chipNumber);

            // Connect to hardware
            _hardware = I2cExtensions.Connect(busNumber, address, speed, sharingMode);

            // Initialize configuration
            ClockIsExternal  = clockSpeed.HasValue;
            ClockSpeed       = clockSpeed ?? InternalClockSpeed;
            FrequencyDefault = CalculateFrequency(PrescaleDefault, ClockSpeed);
            FrequencyMinimum = CalculateFrequency(PrescaleMaximum, ClockSpeed); // Inverse relationship (max = min)
            FrequencyMaximum = CalculateFrequency(PrescaleMinimum, ClockSpeed); // Inverse relationship (min = max)

            // Build channels
            _channels = new Collection <Pca9685ChannelValue>();
            Channels  = new ReadOnlyCollection <Pca9685ChannelValue>(_channels);
            for (var index = 0; index < ChannelCount; index++)
            {
                _channels.Add(new Pca9685ChannelValue(index));
            }

            // Set "all call" address
            I2cExtensions.WriteJoinByte(_hardware, (byte)Pca9685Register.AllCall, I2cAllCallAddress);

            // Enable auto-increment and "all call"
            I2cExtensions.WriteReadWriteBit(_hardware, (byte)Pca9685Register.Mode1,
                                            (byte)(Pca9685Mode1Bits.AutoIncrement | Pca9685Mode1Bits.AllCall), true);

            // Read current values and update properties
            ReadAll();
        }
Ejemplo n.º 24
0
        /// <summary>
        /// Leaves sleep mode.
        /// </summary>
        /// <remarks>
        /// Clears the <see cref="Pca9685Register.Mode1"/> register <see cref="Pca9685Mode1Bits.Sleep"/> bit
        /// then waits for <see cref="ModeSwitchDelay"/> to allow the oscillator to start.
        /// </remarks>
        /// <returns>
        /// True when mode was changed, false when not sleeping.
        /// </returns>
        public bool Wake()
        {
            // Read sleep bit (do nothing when already sleeping)
            var sleeping = I2cExtensions.WriteReadBit(_hardware, (byte)Pca9685Register.Mode1, (byte)Pca9685Mode1Bits.Sleep);

            if (!sleeping)
            {
                return(false);
            }

            // Clear sleep bit
            I2cExtensions.WriteReadWriteBit(_hardware, (byte)Pca9685Register.Mode1, (byte)Pca9685Mode1Bits.Sleep, false);

            // Wait for completion
            Task.Delay(ModeSwitchDelay).Wait();

            // Update related properties
            ReadMode1();

            // Return changed
            return(true);
        }
Ejemplo n.º 25
0
        public Mb85rc04vDevice(int busNumber, byte chipNumber,
                               I2cBusSpeed speed = I2cBusSpeed.FastMode, I2cSharingMode sharingMode = I2cSharingMode.Exclusive)
            : base(chipNumber, MemorySize)
        {
            // Check device addresses
            var lowerAddress = GetDataI2cAddress(chipNumber, false);
            var upperAddress = GetDataI2cAddress(chipNumber, true);

            // Connect to devices
            try
            {
                Hardware      = I2cExtensions.Connect(busNumber, lowerAddress, speed, sharingMode);
                HardwareUpper = I2cExtensions.Connect(busNumber, upperAddress, speed, sharingMode);
            }
            catch
            {
                // Free resources on initialization error
                Hardware?.Dispose();
                HardwareUpper?.Dispose();

                // Continue error
                throw;
            }
        }
Ejemplo n.º 26
0
        /// <summary>
        /// Reads multiple channel values (both on and off for each), and updates it in <see cref="Channels"/>.
        /// </summary>
        /// <param name="index">Zero based channel number (0-15).</param>
        /// <param name="count">Number of channels to read.</param>
        /// <returns>Channel values</returns>
        public Collection <Pca9685ChannelValue> ReadChannels(int index, int count)
        {
            // Validate
            if (index < 0 | index >= ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }
            if (count < 1 || index + count > ChannelCount)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }

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

            // Send I2C command to read channels in one operation
            var data = I2cExtensions.WriteReadBytes(_hardware, register, ChannelSize * count);

            // Update channel properties and add to results
            var results = new Collection <Pca9685ChannelValue>();

            for (int channelIndex = index, offset = 0; count > 0; count--, channelIndex++, offset += ChannelSize)
            {
                // Calculate value
                var value = Pca9685ChannelValue.FromByteArray(data, offset);

                // Update property
                _channels[channelIndex] = value;

                // Add to results
                results.Add(value);
            }

            // Return results
            return(results);
        }