I2C PWM expander added from different Project
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES freeRtosApi
|
||||
esp_common
|
||||
esp_driver_gpio
|
||||
esp_driver_i2c
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file i2cPort.hpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for I2C-interface
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "portBase.hpp"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief I2C-Master Class
|
||||
* @details More recent implementation of IDF-based I2C master
|
||||
*/
|
||||
class I2cMaster : public PortBase {
|
||||
public:
|
||||
class Slave {
|
||||
public:
|
||||
enum ClockRate {
|
||||
_100k = 100000,
|
||||
_400k = 400000,
|
||||
};
|
||||
|
||||
Slave(I2cMaster &interface, uint8_t addr, ClockRate rate = _100k);
|
||||
~Slave();
|
||||
bool writeTo(const std::vector<uint8_t> &data);
|
||||
std::vector<uint8_t> readFrom(const std::vector<uint8_t> &address, unsigned size);
|
||||
|
||||
protected:
|
||||
const uint8_t Addr;
|
||||
I2cMaster &Interface;
|
||||
i2c_master_dev_handle_t _devHandle;
|
||||
};
|
||||
|
||||
I2cMaster(i2c_port_t port, gpio_num_t sda, gpio_num_t scl);
|
||||
~I2cMaster() = default;
|
||||
|
||||
bool probe(uint8_t addr);
|
||||
|
||||
const i2c_port_t Port;
|
||||
|
||||
private:
|
||||
friend class Slave;
|
||||
i2c_master_bus_handle_t _busHandle;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @file portBase.hpp
|
||||
* @author Gustice
|
||||
* @brief Base-class for all hardware ports
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PortBase {
|
||||
public:
|
||||
PortBase(/* args */) {};
|
||||
|
||||
// Forbid Copy and Move assignments
|
||||
PortBase(const PortBase &other) = delete;
|
||||
PortBase &operator=(const PortBase &other) = delete;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @file pwmExpanderPca9685.hpp
|
||||
* @author Gustice
|
||||
* @brief PWM-Expander PCA9685
|
||||
* @details https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "i2cPort.hpp"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* @brief I2C-Connected LED-Driver with 16 ports
|
||||
* @note This module can also be used to generate Servo-Motor signals
|
||||
*/
|
||||
class PmwExpanderPca9685 {
|
||||
public:
|
||||
static constexpr int PwmChannels = 16;
|
||||
using GrayValues = std::array<uint16_t, PwmChannels>;
|
||||
PmwExpanderPca9685(I2cMaster &port, bool inverted = false, uint8_t address = 0x40); // 0x40 default Address
|
||||
|
||||
void sendImage(GrayValues &grayValues);
|
||||
void reset();
|
||||
void setPwmFreq(float frequency, bool inverted);
|
||||
|
||||
private:
|
||||
I2cMaster::Slave _port;
|
||||
const uint8_t _address;
|
||||
const size_t _count = 16;
|
||||
// 16 LED-Channels with 4 bytes each + 4 byte overhead
|
||||
std::vector<uint8_t> _data;
|
||||
|
||||
bool write(uint8_t reg, uint8_t data);
|
||||
std::optional<uint8_t> read(uint8_t reg);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @file i2cPort.cpp
|
||||
* @author Gustice
|
||||
* @brief C++ Wrapper for I2C-interface
|
||||
*
|
||||
* @copyright Copyright (c) 2020
|
||||
*/
|
||||
|
||||
#include "i2cPort.hpp"
|
||||
#include "esp_log.h"
|
||||
|
||||
const int TxBufDisabled = 0; /*!< I2C master doesn't need buffer */
|
||||
const int RxBufDisabled = 0; /*!< I2C master doesn't need buffer */
|
||||
constexpr int I2C_TOOL_TIMEOUT_VALUE_MS = 50;
|
||||
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
|
||||
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
|
||||
|
||||
I2cMaster::I2cMaster(i2c_port_t port, gpio_num_t sda, gpio_num_t scl) : Port(port) {
|
||||
i2c_master_bus_config_t i2c_bus_config{
|
||||
.i2c_port = port,
|
||||
.sda_io_num = sda,
|
||||
.scl_io_num = scl,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
};
|
||||
i2c_bus_config.flags.enable_internal_pullup = 1;
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &_busHandle));
|
||||
}
|
||||
|
||||
bool I2cMaster::probe(uint8_t addr) {
|
||||
esp_err_t ret = i2c_master_probe(_busHandle, addr, I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
return ret == ESP_OK;
|
||||
}
|
||||
|
||||
I2cMaster::Slave::Slave(I2cMaster &interface, uint8_t addr, ClockRate rate) : Addr(addr), Interface(interface) {
|
||||
i2c_device_config_t i2c_dev_conf{
|
||||
.device_address = addr,
|
||||
.scl_speed_hz = rate,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(interface._busHandle, &i2c_dev_conf, &_devHandle));
|
||||
}
|
||||
|
||||
I2cMaster::Slave::~Slave() {
|
||||
i2c_master_bus_rm_device(_devHandle);
|
||||
}
|
||||
|
||||
bool I2cMaster::Slave::writeTo(const std::vector<uint8_t> &data) {
|
||||
esp_err_t ret = i2c_master_transmit(_devHandle, data.data(), data.size(), I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW("I2C", "Bus is busy");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::vector<uint8_t> I2cMaster::Slave::readFrom(const std::vector<uint8_t> &address, unsigned size) {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
std::vector<uint8_t> buffer(size);
|
||||
esp_err_t ret = i2c_master_transmit_receive(_devHandle, address.data(), address.size(), buffer.data(), buffer.size(), I2C_TOOL_TIMEOUT_VALUE_MS);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW("I2C", "Bus is busy");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
constexpr int DisableBuffer = 0; /*!< I2C master doesn't need buffers */
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file pwmExpanderPca9685.cpp
|
||||
* @author Gustice
|
||||
* @brief PWM-Expander PCA9685
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*/
|
||||
|
||||
#include "pwmExpanderPca9685.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cmath>
|
||||
#include <unistd.h> // for usleepCommand
|
||||
|
||||
enum Pca9685Codes {
|
||||
PCA9685_MODE1 = 0x00,
|
||||
PCA9685_MODE2 = 0x01,
|
||||
Led0_ON_L = 0x06,
|
||||
Led0_ON_H = 0x07,
|
||||
Led0_OFF_L = 0x08,
|
||||
Led0_OFF_H = 0x09,
|
||||
// Led1_...Led15
|
||||
AllLedON_L = 0xFA,
|
||||
AllLedON_H = 0xFB,
|
||||
AllLedOFF_L = 0xFC,
|
||||
AllLedOFF_H = 0xFD,
|
||||
PCA9685_PRESCALE = 0xFE,
|
||||
};
|
||||
|
||||
enum Pca9685_Mode1Bits {
|
||||
Mode1_Sleep = 0x10,
|
||||
Mode1_AutoIncrement = 0x20,
|
||||
Mode1_Restart = 0x80,
|
||||
};
|
||||
|
||||
enum Pca9685_Mode2Bits {
|
||||
Mode2_Invert = 0x10,
|
||||
Mode2_OutDriver = 0x04,
|
||||
};
|
||||
|
||||
constexpr int LedPortRegisterWidth = 2 * 2;
|
||||
constexpr int BitResolution = 12;
|
||||
constexpr int PwmMaxValue = 4096 - 1;
|
||||
|
||||
PmwExpanderPca9685::PmwExpanderPca9685(I2cMaster &port, bool inverted, uint8_t address)
|
||||
: _port(port, address, I2cMaster::Slave::_400k), _address(address & 0x7F) {
|
||||
_data.resize(PwmChannels * LedPortRegisterWidth + 1);
|
||||
reset();
|
||||
setPwmFreq(400, inverted);
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::reset(void) {
|
||||
uint8_t mode = Mode1_Restart | Mode1_AutoIncrement;
|
||||
write(PCA9685_MODE1, mode);
|
||||
usleep(10000); // 10 ms
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::setPwmFreq(float frequency, bool inverted) {
|
||||
frequency *= 0.9f; // Correct for overshoot in the frequency setting (see issue #11).
|
||||
float prescaleLevel = 25000000;
|
||||
prescaleLevel /= PwmMaxValue;
|
||||
prescaleLevel /= frequency;
|
||||
prescaleLevel -= 1;
|
||||
|
||||
uint8_t prescale = (uint8_t)std::floor(prescaleLevel + 0.5f);
|
||||
uint8_t oldMode = read(PCA9685_MODE1).value_or(0);
|
||||
uint8_t newMode = (oldMode & 0x7F) | Mode1_Sleep; // sleep + Autoincrement
|
||||
write(PCA9685_MODE1, newMode); // go to sleep
|
||||
write(PCA9685_PRESCALE, prescale); // set the prescaler
|
||||
write(PCA9685_MODE1, oldMode);
|
||||
if (inverted) {
|
||||
write(PCA9685_MODE2, Mode2_OutDriver | Mode2_Invert);
|
||||
} else {
|
||||
write(PCA9685_MODE2, Mode2_OutDriver);
|
||||
}
|
||||
|
||||
usleep(5000); // 5 ms
|
||||
// This sets the MODE1 register to turn on auto increment.
|
||||
write(PCA9685_MODE1, oldMode | Mode1_Restart | Mode1_AutoIncrement);
|
||||
}
|
||||
|
||||
void PmwExpanderPca9685::sendImage(GrayValues &grayValues) {
|
||||
// Try to distribute load peaks over full duty cycle
|
||||
// OUT1 ..###______###____###______###____###__..
|
||||
// OUT2 ..__###______###____###______###____###..
|
||||
// OUT3 ..____###______###______###____###_____..
|
||||
// ...
|
||||
int idx = 0;
|
||||
int step = UINT16_MAX / 4 / PwmChannels; // max 90 deg phase shift
|
||||
int curStep = step;
|
||||
_data[idx++] = Led0_ON_L;
|
||||
for (size_t i = 0; i < _count; i++) {
|
||||
curStep = curStep + step;
|
||||
uint16_t dur = (grayValues[i] + curStep) >> 4;
|
||||
uint on = curStep >> 4;
|
||||
// On-Cnt
|
||||
_data[idx++] = (uint8_t)on;
|
||||
_data[idx++] = (uint8_t)(on >> 8);
|
||||
// Off-Cnt
|
||||
_data[idx++] = (uint8_t)dur;
|
||||
_data[idx++] = (uint8_t)(dur >> 8);
|
||||
}
|
||||
_port.writeTo(_data);
|
||||
}
|
||||
|
||||
bool PmwExpanderPca9685::write(uint8_t reg, uint8_t word) {
|
||||
std::vector<uint8_t> data{reg, word};
|
||||
return _port.writeTo(data);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> PmwExpanderPca9685::read(uint8_t reg) {
|
||||
auto value = _port.readFrom({reg}, 1);
|
||||
if (value.size() == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {value.front()};
|
||||
}
|
||||
Reference in New Issue
Block a user