/** Set the width of the positive pulses on one of the PWM channels. \param[in] i2cAdr The I2C address of the PWM chip. \param[in] channel The channel on the PWM chip for which to set the pulse width. \param[in] dutyCycle The desired duty-cycle of the positive pulses (0-0xFFFFFFFF for 0-100%). \return HRESULT success or error code. */ HRESULT PCA9685Device::SetPwmDutyCycle(ULONG i2cAdr, ULONG channel, ULONG dutyCycle) { HRESULT hr = S_OK; I2cTransactionClass transaction; ULONGLONG tmpPulsetime = 0; UCHAR bitRegsAdr = 0; // Address of start of registers for bit in question UCHAR pulseData[REGS_PER_LED] = { 0x00, 0x00, 0x00, 0x00 }; // Registers data to set pulse time if (channel >= LED_COUNT) { hr = DMAP_E_INVALID_PORT_BIT_FOR_DEVICE; } if (SUCCEEDED(hr)) { // Make sure the PWM chip is initialized. hr = _InitializeChip(i2cAdr); } if (SUCCEEDED(hr)) { // Set the I2C address of the PWM chip. hr = transaction.setAddress(i2cAdr); } if (SUCCEEDED(hr)) { // Indicate this chip supports high speed I2C transfers. transaction.useHighSpeed(); // Calculate the address of the first register for the port in question. bitRegsAdr = (UCHAR)(LEDS_BASE_ADR + (channel * REGS_PER_LED)); // Queue sending the base address of the port registers to the chip. hr = transaction.queueWrite(&bitRegsAdr, 1); } if (SUCCEEDED(hr)) { // Get the pulse high time in PWM chip terms. tmpPulsetime = ((((ULONGLONG)dutyCycle) * (1LL << PWM_BITS)) + 0x80000000LL) / 0x100000000LL; tmpPulsetime = tmpPulsetime & ((1LL << PWM_BITS) - 1LL); pulseData[2] = (UCHAR)(tmpPulsetime & 0xFF); pulseData[3] = (UCHAR)((tmpPulsetime >> 8) & 0xFF); // Queue sending the registers contents for the desired pulse width. hr = transaction.queueWrite(pulseData, REGS_PER_LED); } if (SUCCEEDED(hr)) { // Actually perform the I2C transfers specified above. hr = transaction.execute(g_i2c.getController()); } return hr; }
/** This method takes the actions needed to set a port bit of the PWM chip to the desired state. \param[in] i2cAdr The I2C address of the PWM chip. \param[in] portBit The number of the port bit to modify. \param[in] state The state to set the port bit to: HIGH or LOW. \return HRESULT success or error code. */ HRESULT PCA9685Device::SetBitState(ULONG i2cAdr, ULONG portBit, ULONG state) { HRESULT hr = S_OK; I2cTransactionClass transaction; UCHAR bitRegsAdr = 0; // Address of start of registers for bit in question UCHAR lowBitData[REGS_PER_LED] = { 0x00, 0x00, 0x00, 0x10 }; // Registers data to set bit low UCHAR highBitData[REGS_PER_LED] = { 0x00, 0x10, 0x00, 0x00 }; // Registers data to set bit high if (portBit >= LED_COUNT) { hr = DMAP_E_INVALID_PORT_BIT_FOR_DEVICE; } if (SUCCEEDED(hr) && (state != HIGH) && (state != LOW)) { hr = DMAP_E_INVALID_PIN_STATE_SPECIFIED; } if (SUCCEEDED(hr)) { // Make sure the PWM chip is initialized. hr = _InitializeChip(i2cAdr); } if (SUCCEEDED(hr)) { // Set the I2C address of the PWM chip. hr = transaction.setAddress(i2cAdr); } if (SUCCEEDED(hr)) { // Indicate this chip supports high speed I2C transfers. transaction.useHighSpeed(); // Calculate the address of the first register for the port in question. bitRegsAdr = (UCHAR)(LEDS_BASE_ADR + (portBit * REGS_PER_LED)); // Queue sending the base address of the port registers to the chip. hr = transaction.queueWrite(&bitRegsAdr, 1); } if (SUCCEEDED(hr)) { // Queue sending the registers contents to set the specified bit state. if (state == LOW) { hr = transaction.queueWrite(lowBitData, REGS_PER_LED); } else { hr = transaction.queueWrite(highBitData, REGS_PER_LED); } } if (SUCCEEDED(hr)) { // Actually perform the I2C transfers specified above. hr = transaction.execute(g_i2c.getController()); } return hr; }
/** This expects the port bit to be configured to be constantly on or off. \param[in] i2cAdr The I2C address of the PWM chip. \param[in] portBit The number of the port bit to read. \param[out] state The state of the port bit: HIGH or LOW. \return HRESULT success or error code. */ HRESULT PCA9685Device::GetBitState(ULONG i2cAdr, ULONG portBit, ULONG & state) { HRESULT hr = S_OK; I2cTransactionClass transaction; UCHAR bitRegsAdr = 0; // Address of start of registers for bit in question UCHAR bitData[REGS_PER_LED] = { 0 }; // Buffer for bit register contents if (portBit >= LED_COUNT) { hr = DMAP_E_INVALID_PORT_BIT_FOR_DEVICE; } if (SUCCEEDED(hr)) { // Make sure the PWM chip is initialized. hr = _InitializeChip(i2cAdr); } if (SUCCEEDED(hr)) { // Set the I2C address of the PWM chip. hr = transaction.setAddress(i2cAdr); } if (SUCCEEDED(hr)) { // Indicate this chip supports high speed I2C transfers. transaction.useHighSpeed(); // Calculate the address of the first register for the port in question. bitRegsAdr = (UCHAR)(LEDS_BASE_ADR + (portBit * REGS_PER_LED)); // Queue sending the base address of the port registers to the chip. hr = transaction.queueWrite(&bitRegsAdr, 1); } if (SUCCEEDED(hr)) { // Queue reading the registers for the bit in question. hr = transaction.queueRead(bitData, REGS_PER_LED); } if (SUCCEEDED(hr)) { // Actually perform the I2C transfers specified above. hr = transaction.execute(g_i2c.getController()); } if (SUCCEEDED(hr)) { // If constant OFF bit is 1, the port bit is LOW, regardless of constant ON bit. if ((bitData[3] & 0x10) != 0) { state = LOW; } // If constant OFF bit is 0, and constant ON bit is 1, the port bit is HIGH. else if ((bitData[1] & 0x10) != 0) { state = HIGH; } // If both constant state bits are zero, the port bit is not in a constant state. { hr = DMAP_E_GPIO_PIN_IS_SET_TO_PWM; } } return hr; }
/** Set the pulse repetition rate for the PWM channels on the specified chip. \param[in] i2cAdr The I2C address of the PWM chip. \param[in] frequency The desired PWM pulse repetition rate in pulses per second. \return HRESULT success or error code. */ HRESULT PCA9685Device::SetPwmFrequency(ULONG i2cAdr, ULONG frequency) { HRESULT hr = S_OK; I2cTransactionClass transaction; ULONG preScale; UCHAR readBuf[1] = { 0 }; // Buffer for reading data from chip UCHAR mode1RegAdr[1] = { MODE1_ADR }; // Buffer for MODE1 register address UCHAR preScaleAdr[1] = { PRE_SCALE_ADR }; // Buffer for PRE_SCALE register address MODE1 mode1Sleep = { 0, 0, 0, 0, 1, 1, 0, 0 }; // Sleep, auto-increment, internal clock MODE1 mode1Run = { 0, 0, 0, 0, 0, 1, 0, 0 }; // No sleep, auto-increment, internal clock // Make sure the PWM chip is initialized. hr = _InitializeChip(i2cAdr); if (SUCCEEDED(hr)) { // Set the I2C address of the PWM chip. hr = transaction.setAddress(i2cAdr); } if (SUCCEEDED(hr)) { // Indicate this chip supports high speed I2C transfers. transaction.useHighSpeed(); // Calculate the nearest prescale value for the requested pulse rate. if (frequency < 24) { preScale = 0xFF; } else { // From PCA9685 datasheet: prescale = round(25,000,000 / (4096 * pulse_rate)) - 1 preScale = (((25000000 + ((4096 * frequency) / 2))) / (4096 * frequency)) - 1; } preScale = preScale & 0xFF; } // If we need to set a new prescale value. if (SUCCEEDED(hr) && (m_freqPreScale != preScale)) { // Queue a write to set the Sleep bit (so we can change the PWM frequency). hr = transaction.queueWrite(mode1RegAdr, sizeof(mode1RegAdr)); if (SUCCEEDED(hr)) { hr = transaction.queueWrite((PUCHAR)&mode1Sleep, 1); } // Queue a write to set the frequency prescale value. if (SUCCEEDED(hr)) { hr = transaction.queueWrite(preScaleAdr, sizeof(preScaleAdr), TRUE); } if (SUCCEEDED(hr)) { hr = transaction.queueWrite((PUCHAR)&preScale, 1); } // Queue a write to clear the Sleep bit. if (SUCCEEDED(hr)) { hr = transaction.queueWrite(mode1RegAdr, sizeof(mode1RegAdr), TRUE); } if (SUCCEEDED(hr)) { hr = transaction.queueWrite((PUCHAR)&mode1Run, 1); } // Delay for 500 microseconds for clock to start. for (int i = 0; SUCCEEDED(hr) && (i < 5); i++) { hr = transaction.queueWrite(mode1RegAdr, sizeof(mode1RegAdr), TRUE); if (SUCCEEDED(hr)) { hr = transaction.queueRead(readBuf, sizeof(readBuf)); } } if (SUCCEEDED(hr)) { // Actually perform the I2C transfers specified above. hr = transaction.execute(g_i2c.getController()); } // Record the prescale value just set. if (SUCCEEDED(hr)) { m_freqPreScale = (UCHAR)preScale; } } return hr; }