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:
Theo Arends 2022-01-25 09:00:38 +01:00 committed by GitHub
commit 7adbb99423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1168 additions and 1081 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}
/*********************************************************************************************/

View File

@ -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

View File

@ -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(

View File

@ -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"

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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