mirror of https://github.com/arendst/Tasmota.git
Merge pull request #14590 from s-hadinger/pwm_autophase
PWM auto-phasing for lights by default (new behavior) unless ``SetOption134 1``
This commit is contained in:
commit
7adbb99423
|
@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Command ``SspmLog<relay> [x]`` to retrieve relay power state change and cause logging
|
||||
- Command ``SspmScan`` to rescan Sonoff SPM modbus
|
||||
- Support for MQ analog sensor for air quality by Francesco Adriani (#14581)
|
||||
- PWM auto-phasing for lights by default (new behavior) unless ``SetOption134 1``
|
||||
|
||||
### Changed
|
||||
- BME68x-Sensor-API library from v3.5.9 to v4.4.7
|
||||
|
|
|
@ -37,3 +37,139 @@
|
|||
#include "esp8266toEsp32.h"
|
||||
|
||||
// ESP Stuff
|
||||
|
||||
/*********************************************************************************************\
|
||||
* ESP32 analogWrite emulation support
|
||||
\*********************************************************************************************/
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99 };
|
||||
uint32_t _pwm_frequency = 977; // Default 977Hz
|
||||
uint8_t _pwm_bit_num = 10; // Default 1023
|
||||
#else // other ESP32
|
||||
uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99, 99, 99 };
|
||||
uint32_t _pwm_frequency = 977; // Default 977Hz
|
||||
uint8_t _pwm_bit_num = 10; // Default 1023
|
||||
#endif // CONFIG_IDF_TARGET_ESP32C3 vs ESP32
|
||||
|
||||
uint32_t _analog_pin2chan(uint32_t pin) {
|
||||
for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if ((_pwm_channel[channel] < 99) && (_pwm_channel[channel] == pin)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _analogWriteFreqRange(void) {
|
||||
for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (_pwm_channel[channel] < 99) {
|
||||
ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// input range is in full range, ledc needs bits
|
||||
uint32_t _analogGetResolution(uint32_t x) {
|
||||
uint32_t bits = 0;
|
||||
while (x) {
|
||||
bits++;
|
||||
x >>= 1;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
void analogWriteRange(uint32_t range) {
|
||||
_pwm_bit_num = _analogGetResolution(range);
|
||||
_analogWriteFreqRange();
|
||||
}
|
||||
|
||||
void analogWriteFreq(uint32_t freq) {
|
||||
_pwm_frequency = freq;
|
||||
_analogWriteFreqRange();
|
||||
}
|
||||
|
||||
bool analogAttach(uint32_t pin) {
|
||||
// Find if pin is already attached
|
||||
uint32_t channel;
|
||||
for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (_pwm_channel[channel] == pin) {
|
||||
// Already attached
|
||||
// Serial.printf("PWM: Already attached pin %d to channel %d\n", pin, channel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Find an empty channel
|
||||
for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (99 == _pwm_channel[channel]) {
|
||||
_pwm_channel[channel] = pin;
|
||||
ledcAttachPin(pin, channel + PWM_CHANNEL_OFFSET);
|
||||
ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num);
|
||||
// Serial.printf("PWM: New attach pin %d to channel %d\n", pin, channel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// No more channels available
|
||||
return false;
|
||||
}
|
||||
|
||||
void analogWrite(uint8_t pin, int val)
|
||||
{
|
||||
uint32_t channel = _analog_pin2chan(pin);
|
||||
if ( val >> (_pwm_bit_num-1) ) ++val;
|
||||
ledcWrite(channel + PWM_CHANNEL_OFFSET, val);
|
||||
// Serial.printf("write %d - %d\n",channel,val);
|
||||
}
|
||||
|
||||
/*
|
||||
The primary goal of this library is to add phase control to PWM ledc
|
||||
functions.
|
||||
|
||||
Phase control allows to stress less the power supply of LED lights.
|
||||
By default all phases are starting at the same moment. This means
|
||||
the the power supply always takes a power hit at the start of each
|
||||
new cycle, even if the average power is low.
|
||||
|
||||
Phase control is also of major importance for H-bridge where
|
||||
both PWM lines should NEVER be active at the same time.
|
||||
|
||||
Unfortunately Arduino Core does not allow any customization nor
|
||||
extendibility for the ledc/analogWrite functions. We have therefore
|
||||
no other choice than duplicating part of Arduino code.
|
||||
|
||||
WARNING: this means it can easily break if ever Arduino internal
|
||||
implementation changes.
|
||||
*/
|
||||
|
||||
#include "driver/ledc.h"
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||
#endif
|
||||
|
||||
// exported from Arduno Core
|
||||
extern uint8_t channels_resolution[LEDC_CHANNELS];
|
||||
|
||||
void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase)
|
||||
{
|
||||
uint32_t chan = _analog_pin2chan(pin) + PWM_CHANNEL_OFFSET;
|
||||
if (duty >> (_pwm_bit_num-1) ) ++duty;
|
||||
|
||||
if(chan >= LEDC_CHANNELS){
|
||||
return;
|
||||
}
|
||||
uint8_t group=(chan/8), channel=(chan%8);
|
||||
|
||||
//Fixing if all bits in resolution is set = LEDC FULL ON
|
||||
uint32_t max_duty = (1 << channels_resolution[chan]) - 1;
|
||||
phase = phase % max_duty;
|
||||
|
||||
if(duty == max_duty){ // no sure whether this is needed anymore TODO
|
||||
duty = max_duty + 1;
|
||||
}
|
||||
|
||||
ledc_set_duty_with_hpoint((ledc_mode_t)group, (ledc_channel_t)channel, duty, phase);
|
||||
ledc_update_duty((ledc_mode_t)group, (ledc_channel_t)channel);
|
||||
}
|
||||
|
|
|
@ -28,105 +28,29 @@
|
|||
|
||||
#include <Esp.h>
|
||||
|
||||
/*********************************************************************************************\
|
||||
* ESP32 analogWrite emulation support
|
||||
\*********************************************************************************************/
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
#define PWM_SUPPORTED_CHANNELS 6
|
||||
#define PWM_CHANNEL_OFFSET 1 // Webcam uses channel 0, so we offset standard PWM
|
||||
|
||||
uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99 };
|
||||
uint32_t _pwm_frequency = 977; // Default 977Hz
|
||||
uint8_t _pwm_bit_num = 10; // Default 1023
|
||||
#else // other ESP32
|
||||
#define PWM_SUPPORTED_CHANNELS 8
|
||||
#define PWM_CHANNEL_OFFSET 2 // Webcam uses channel 0, so we offset standard PWM
|
||||
|
||||
uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS] = { 99, 99, 99, 99, 99, 99, 99, 99 };
|
||||
uint32_t _pwm_frequency = 977; // Default 977Hz
|
||||
uint8_t _pwm_bit_num = 10; // Default 1023
|
||||
#endif // CONFIG_IDF_TARGET_ESP32C3 vs ESP32
|
||||
|
||||
inline uint32_t _analog_pin2chan(uint32_t pin) {
|
||||
for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if ((_pwm_channel[channel] < 99) && (_pwm_channel[channel] == pin)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void _analogWriteFreqRange(void) {
|
||||
for (uint32_t channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (_pwm_channel[channel] < 99) {
|
||||
// uint32_t duty = ledcRead(channel + PWM_CHANNEL_OFFSET);
|
||||
ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num);
|
||||
// ledcWrite(channel + PWM_CHANNEL_OFFSET, duty);
|
||||
}
|
||||
}
|
||||
// Serial.printf("freq - range %d - %d\n",freq,range);
|
||||
}
|
||||
extern uint8_t _pwm_channel[PWM_SUPPORTED_CHANNELS];
|
||||
extern uint32_t _pwm_frequency;
|
||||
extern uint8_t _pwm_bit_num;
|
||||
|
||||
void _analogWriteFreqRange(void);
|
||||
// input range is in full range, ledc needs bits
|
||||
inline uint32_t _analogGetResolution(uint32_t x) {
|
||||
uint32_t bits = 0;
|
||||
while (x) {
|
||||
bits++;
|
||||
x >>= 1;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
uint32_t _analogGetResolution(uint32_t x);
|
||||
void analogWriteRange(uint32_t range);
|
||||
void analogWriteFreq(uint32_t freq);
|
||||
bool analogAttach(uint32_t pin);
|
||||
void analogWrite(uint8_t pin, int val);
|
||||
|
||||
inline void analogWriteRange(uint32_t range) {
|
||||
_pwm_bit_num = _analogGetResolution(range);
|
||||
_analogWriteFreqRange();
|
||||
}
|
||||
// Extended version that also allows to change phase
|
||||
extern void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase = 0);
|
||||
|
||||
inline void analogWriteFreq(uint32_t freq) {
|
||||
_pwm_frequency = freq;
|
||||
_analogWriteFreqRange();
|
||||
}
|
||||
|
||||
/*
|
||||
inline void analogAttach(uint32_t pin, uint32_t channel) {
|
||||
_pwm_channel[channel &7] = pin;
|
||||
ledcAttachPin(pin, channel + PWM_CHANNEL_OFFSET);
|
||||
ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num);
|
||||
// Serial.printf("attach %d - %d\n", channel, pin);
|
||||
}
|
||||
*/
|
||||
inline bool analogAttach(uint32_t pin) {
|
||||
// Find if pin is already attached
|
||||
uint32_t channel;
|
||||
for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (_pwm_channel[channel] == pin) {
|
||||
// Already attached
|
||||
// Serial.printf("PWM: Already attached pin %d to channel %d\n", pin, channel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Find an empty channel
|
||||
for (channel = 0; channel < PWM_SUPPORTED_CHANNELS; channel++) {
|
||||
if (99 == _pwm_channel[channel]) {
|
||||
_pwm_channel[channel] = pin;
|
||||
ledcAttachPin(pin, channel + PWM_CHANNEL_OFFSET);
|
||||
ledcSetup(channel + PWM_CHANNEL_OFFSET, _pwm_frequency, _pwm_bit_num);
|
||||
// Serial.printf("PWM: New attach pin %d to channel %d\n", pin, channel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// No more channels available
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void analogWrite(uint8_t pin, int val)
|
||||
{
|
||||
uint32_t channel = _analog_pin2chan(pin);
|
||||
if ( val >> (_pwm_bit_num-1) ) ++val;
|
||||
ledcWrite(channel + PWM_CHANNEL_OFFSET, val);
|
||||
// Serial.printf("write %d - %d\n",channel,val);
|
||||
}
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
|
|
|
@ -662,6 +662,7 @@ extern const bcstring be_const_str_set_matrix_pixel_color;
|
|||
extern const bcstring be_const_str_set_percentage;
|
||||
extern const bcstring be_const_str_set_pixel_color;
|
||||
extern const bcstring be_const_str_set_power;
|
||||
extern const bcstring be_const_str_set_pwm;
|
||||
extern const bcstring be_const_str_set_rate;
|
||||
extern const bcstring be_const_str_set_style_bg_color;
|
||||
extern const bcstring be_const_str_set_style_line_color;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,18 +1,19 @@
|
|||
#include "be_constobj.h"
|
||||
|
||||
static be_define_const_map_slots(m_libgpio_map) {
|
||||
{ be_const_key(pin, -1), be_const_func(gp_pin) },
|
||||
{ be_const_key(member, -1), be_const_func(gp_member) },
|
||||
{ be_const_key(digital_read, -1), be_const_func(gp_digital_read) },
|
||||
{ be_const_key(digital_write, -1), be_const_func(gp_digital_write) },
|
||||
{ be_const_key(pin_mode, 1), be_const_func(gp_pin_mode) },
|
||||
{ be_const_key(pin_used, -1), be_const_func(gp_pin_used) },
|
||||
{ be_const_key(dac_voltage, -1), be_const_func(gp_dac_voltage) },
|
||||
{ be_const_key(digital_read, 3), be_const_func(gp_digital_read) },
|
||||
{ be_const_key(member, 1), be_const_func(gp_member) },
|
||||
{ be_const_key(set_pwm, 5), be_const_ctype_func(gp_set_duty) },
|
||||
{ be_const_key(pin, 7), be_const_func(gp_pin) },
|
||||
{ be_const_key(pin_mode, 2), be_const_func(gp_pin_mode) },
|
||||
{ be_const_key(pin_used, -1), be_const_func(gp_pin_used) },
|
||||
};
|
||||
|
||||
static be_define_const_map(
|
||||
m_libgpio_map,
|
||||
7
|
||||
8
|
||||
);
|
||||
|
||||
static be_define_const_module(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* read power values
|
||||
*******************************************************************/
|
||||
#include "be_constobj.h"
|
||||
#include "be_mapping.h"
|
||||
|
||||
// Tasmota specific
|
||||
|
||||
|
@ -18,6 +19,10 @@ extern int gp_dac_voltage(bvm *vm);
|
|||
extern int gp_pin_used(bvm *vm);
|
||||
extern int gp_pin(bvm *vm);
|
||||
|
||||
// esp_err_tledc_set_duty_and_update(ledc_mode_tspeed_mode, ledc_channel_tchannel, uint32_t duty, uint32_t hpoint)
|
||||
extern void gp_set_duty(int32_t pin, int32_t duty, int32_t hpoint); BE_FUNC_CTYPE_DECLARE(gp_set_duty, "", "ii[i]");
|
||||
|
||||
|
||||
/* @const_object_info_begin
|
||||
module gpio (scope: global) {
|
||||
member, func(gp_member)
|
||||
|
@ -29,6 +34,8 @@ module gpio (scope: global) {
|
|||
|
||||
pin_used, func(gp_pin_used)
|
||||
pin, func(gp_pin)
|
||||
|
||||
set_pwm, ctype_func(gp_set_duty)
|
||||
}
|
||||
@const_object_info_end */
|
||||
#include "be_fixed_gpio.h"
|
||||
|
|
|
@ -163,7 +163,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
|||
uint32_t tuya_allow_dimmer_0 : 1; // bit 17 (v10.0.0.3) - SetOption131 - (Tuya) Allow save dimmer = 0 receved by MCU
|
||||
uint32_t tls_use_fingerprint : 1; // bit 18 (v10.0.0.4) - SetOption132 - (TLS) Use fingerprint validation instead of CA based
|
||||
uint32_t shift595_invert_outputs : 1; // bit 19 (v10.0.0.4) - SetOption133 - (Shift595) Invert outputs of 74x595 shift registers
|
||||
uint32_t spare20 : 1; // bit 20
|
||||
uint32_t pwm_force_same_phase : 1; // bit 20 (2022.01.3) - SetOption134 - (PWM) force PWM lights to start at same phase, default is to spread phases to minimze overlap (also needed for H-bridge)
|
||||
uint32_t spare21 : 1; // bit 21
|
||||
uint32_t spare22 : 1; // bit 22
|
||||
uint32_t spare23 : 1; // bit 23
|
||||
|
|
|
@ -2087,6 +2087,11 @@ void LightApplyPower(uint8_t new_color[LST_MAX], power_t power) {
|
|||
void LightSetOutputs(const uint16_t *cur_col_10) {
|
||||
// now apply the actual PWM values, adjusted and remapped 10-bits range
|
||||
if (TasmotaGlobal.light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix...
|
||||
#ifdef ESP32
|
||||
uint32_t pwm_phase = 0; // dephase each PWM channel with the value of the previous
|
||||
uint32_t pwm_modulus = (1 << _pwm_bit_num) - 1; // 1023
|
||||
#endif // ESP32
|
||||
|
||||
#ifdef USE_PWM_DIMMER
|
||||
uint16_t max_col = 0;
|
||||
#ifdef USE_I2C
|
||||
|
@ -2111,7 +2116,14 @@ void LightSetOutputs(const uint16_t *cur_col_10) {
|
|||
cur_col = cur_col > 0 ? changeUIntScale(cur_col, 0, Settings->pwm_range, Light.pwm_min, Light.pwm_max) : 0; // shrink to the range of pwm_min..pwm_max
|
||||
}
|
||||
if (!Settings->flag4.zerocross_dimmer) {
|
||||
analogWrite(Pin(GPIO_PWM1, i), bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col);
|
||||
uint32_t pwm_val = bitRead(TasmotaGlobal.pwm_inverted, i) ? Settings->pwm_range - cur_col : cur_col;
|
||||
#ifdef ESP32
|
||||
uint32_t pwm_phase_invert = bitRead(TasmotaGlobal.pwm_inverted, i) ? cur_col : 0; // move phase if inverted
|
||||
analogWritePhase(Pin(GPIO_PWM1, i), pwm_val, Settings->flag5.pwm_force_same_phase ? 0 : (pwm_phase + pwm_phase_invert) & pwm_modulus);
|
||||
pwm_phase = (pwm_phase + cur_col) & pwm_modulus;
|
||||
#else // ESP32
|
||||
analogWrite(Pin(GPIO_PWM1, i), pwm_val);
|
||||
#endif // ESP32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#ifdef USE_BERRY
|
||||
|
||||
#include <berry.h>
|
||||
#include "esp8266toEsp32.h"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#include <driver/dac.h>
|
||||
|
@ -201,8 +202,10 @@ extern "C" {
|
|||
be_raise(vm, kTypeError, nullptr);
|
||||
}
|
||||
|
||||
void gp_set_duty(int32_t pin, int32_t duty, int32_t hpoint) {
|
||||
analogWritePhase(pin, duty, hpoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // USE_BERRY
|
||||
|
|
Loading…
Reference in New Issue