ESPHome 2025.5.0
Loading...
Searching...
No Matches
pca9685_output.cpp
Go to the documentation of this file.
1#include "pca9685_output.h"
2#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
5
6namespace esphome {
7namespace pca9685 {
8
9static const char *const TAG = "pca9685";
10
11const uint8_t PCA9685_MODE_INVERTED = 0x10;
12const uint8_t PCA9685_MODE_OUTPUT_ONACK = 0x08;
13const uint8_t PCA9685_MODE_OUTPUT_TOTEM_POLE = 0x04;
14const uint8_t PCA9685_MODE_OUTNE_HIGHZ = 0x02;
15const uint8_t PCA9685_MODE_OUTNE_LOW = 0x01;
16
17static const uint8_t PCA9685_REGISTER_SOFTWARE_RESET = 0x06;
18static const uint8_t PCA9685_REGISTER_MODE1 = 0x00;
19static const uint8_t PCA9685_REGISTER_MODE2 = 0x01;
20static const uint8_t PCA9685_REGISTER_LED0 = 0x06;
21static const uint8_t PCA9685_REGISTER_PRE_SCALE = 0xFE;
22
23static const uint8_t PCA9685_MODE1_RESTART = 0b10000000;
24static const uint8_t PCA9685_MODE1_EXTCLK = 0b01000000;
25static const uint8_t PCA9685_MODE1_AUTOINC = 0b00100000;
26static const uint8_t PCA9685_MODE1_SLEEP = 0b00010000;
27
29 ESP_LOGCONFIG(TAG, "Setting up PCA9685OutputComponent...");
30
31 ESP_LOGV(TAG, " Resetting devices...");
32 if (!this->write_bytes(PCA9685_REGISTER_SOFTWARE_RESET, nullptr, 0)) {
33 this->mark_failed();
34 return;
35 }
36
37 if (!this->write_byte(PCA9685_REGISTER_MODE1, PCA9685_MODE1_RESTART | PCA9685_MODE1_AUTOINC)) {
38 this->mark_failed();
39 return;
40 }
41 if (!this->write_byte(PCA9685_REGISTER_MODE2, this->mode_)) {
42 this->mark_failed();
43 return;
44 }
45
46 uint8_t mode1;
47 if (!this->read_byte(PCA9685_REGISTER_MODE1, &mode1)) {
48 this->mark_failed();
49 return;
50 }
51 mode1 = (mode1 & ~PCA9685_MODE1_RESTART) | PCA9685_MODE1_SLEEP;
52 if (!this->write_byte(PCA9685_REGISTER_MODE1, mode1)) {
53 this->mark_failed();
54 return;
55 }
56
57 int pre_scaler = 3;
58 if (this->extclk_) {
59 mode1 = mode1 | PCA9685_MODE1_EXTCLK;
60 if (!this->write_byte(PCA9685_REGISTER_MODE1, mode1)) {
61 this->mark_failed();
62 return;
63 }
64 } else {
65 pre_scaler = static_cast<int>((25000000 / (4096 * this->frequency_)) - 1);
66 pre_scaler = clamp(pre_scaler, 3, 255);
67
68 ESP_LOGV(TAG, " -> Prescaler: %d", pre_scaler);
69 }
70 if (!this->write_byte(PCA9685_REGISTER_PRE_SCALE, pre_scaler)) {
71 this->mark_failed();
72 return;
73 }
74
75 mode1 = (mode1 & ~PCA9685_MODE1_SLEEP) | PCA9685_MODE1_RESTART;
76 if (!this->write_byte(PCA9685_REGISTER_MODE1, mode1)) {
77 this->mark_failed();
78 return;
79 }
81
82 this->loop();
83}
84
86 ESP_LOGCONFIG(TAG, "PCA9685:");
87 ESP_LOGCONFIG(TAG, " Mode: 0x%02X", this->mode_);
88 if (this->extclk_) {
89 ESP_LOGCONFIG(TAG, " EXTCLK: enabled");
90 } else {
91 ESP_LOGCONFIG(TAG, " EXTCLK: disabled");
92 ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_);
93 }
94 if (this->is_failed()) {
95 ESP_LOGE(TAG, "Setting up PCA9685 failed!");
96 }
97}
98
100 if (this->min_channel_ == 0xFF || !this->update_)
101 return;
102
103 const uint16_t num_channels = this->max_channel_ - this->min_channel_ + 1;
104 const uint16_t phase_delta_begin = 4096 / num_channels;
105 for (uint8_t channel = this->min_channel_; channel <= this->max_channel_; channel++) {
106 uint16_t phase_begin = (channel - this->min_channel_) * phase_delta_begin;
107 uint16_t phase_end;
108 uint16_t amount = this->pwm_amounts_[channel];
109 if (amount == 0) {
110 phase_end = 4096;
111 } else if (amount >= 4096) {
112 phase_begin = 4096;
113 phase_end = 0;
114 } else {
115 phase_end = phase_begin + amount;
116 if (phase_end >= 4096)
117 phase_end -= 4096;
118 }
119
120 ESP_LOGVV(TAG, "Channel %02u: amount=%04u phase_begin=%04u phase_end=%04u", channel, amount, phase_begin,
121 phase_end);
122
123 uint8_t data[4];
124 data[0] = phase_begin & 0xFF;
125 data[1] = (phase_begin >> 8) & 0xFF;
126 data[2] = phase_end & 0xFF;
127 data[3] = (phase_end >> 8) & 0xFF;
128
129 uint8_t reg = PCA9685_REGISTER_LED0 + 4 * channel;
130 if (!this->write_bytes(reg, data, 4)) {
131 this->status_set_warning();
132 return;
133 }
134 }
135
136 this->status_clear_warning();
137 this->update_ = false;
138}
139
141 auto c = channel->channel_;
142 this->min_channel_ = std::min(this->min_channel_, c);
143 this->max_channel_ = std::max(this->max_channel_, c);
144 channel->set_parent(this);
145}
146
148 const uint16_t max_duty = 4096;
149 const float duty_rounded = roundf(state * max_duty);
150 auto duty = static_cast<uint16_t>(duty_rounded);
151 this->parent_->set_channel_value_(this->channel_, duty);
152}
153
154} // namespace pca9685
155} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message="unspecified")
void status_clear_warning()
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop=true)
Definition i2c.h:252
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition i2c.h:266
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:153
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition i2c.h:239
void write_state(float state) override
void set_parent(PCA9685Output *parent)
void set_channel_value_(uint8_t channel, uint16_t value)
void register_channel(PCA9685Channel *channel)
bool state
Definition fan.h:0
const uint8_t PCA9685_MODE_OUTPUT_ONACK
Channel update happens upon ACK (post-set) rather than on STOP (endTransmission)
const uint8_t PCA9685_MODE_OUTPUT_TOTEM_POLE
Use a totem-pole (push-pull) style output rather than an open-drain structure.
const uint8_t PCA9685_MODE_OUTNE_HIGHZ
For active low output enable, sets channel output to high-impedance state.
const uint8_t PCA9685_MODE_OUTNE_LOW
Similarly, sets channel output to high if in totem-pole mode, otherwise.
const uint8_t PCA9685_MODE_INVERTED
Inverts polarity of channel output signal.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:30
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:101