Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding frequency output mode for buzzer #8994

Merged
merged 2 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tasmota/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t teleinfo_rawdata : 1; // bit 26 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t alexa_gen_1 : 1; // bit 27 (v8.4.0.3) - SetOption109 - Alexa gen1 mode - if you only have Echo Dot 2nd gen devices
uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - disable Zigbee auto-config when pairing new devices
uint32_t spare29 : 1; // bit 29
uint32_t buzzer_freq_mode : 1; // bit 29 (v8.5.0.1) - SetOption111 - Use frequency output for buzzer pin instead of on/off signal
uint32_t spare30 : 1; // bit 30
uint32_t spare31 : 1; // bit 31
};
Expand Down
42 changes: 35 additions & 7 deletions tasmota/xdrv_24_buzzer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,36 @@ struct BUZZER {
uint8_t inverted = 0; // Buzzer inverted flag (1 = (0 = On, 1 = Off))
uint8_t count = 0; // Number of buzzes
uint8_t mode = 0; // Buzzer mode (0 = regular, 1 = infinite, 2 = follow LED)
uint8_t freq_mode = 0; // Output mode (0 = regular, 1 = using frequency output)
uint8_t set[2];
uint8_t duration;
uint8_t state = 0;
} Buzzer;

/*********************************************************************************************/

void BuzzerOff(void)
void BuzzerSet(uint8_t state)
{
DigitalWrite(GPIO_BUZZER, 0, Buzzer.inverted); // Buzzer Off
if (Buzzer.inverted) {
state = !state;
}

if (Buzzer.freq_mode == 1) {
static uint8_t last_state = 0;
if (last_state != state) {
if (state) {
Copy link
Collaborator

@s-hadinger s-hadinger Aug 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you are messing with the lights structure for something that is not a light at all. Doing this way you're just make the Red channel unusable.

Copy link
Contributor Author

@hello-world-dot-c hello-world-dot-c Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing. Let me answer this one by one.

  1. Messing with light control?
    No, see xdrv_24_buzzer.ino line 95:
  // If we use multi-channel PWM and have a PWM1 output, we can use PWM mode for buzzer output if enabled.
  if ((Settings.flag4.buzzer_pwm_mode) &&     // SetOption102 - Enable PWM mode for buzzer, uses PWM1 if enabled
      (Settings.flag3.pwm_multi_channels) &&  // SetOption68 - Enable multi-channels PWM instead of Color PWM
      PinUsed(GPIO_PWM1)) {
      Buzzer.pwm_mode = 1;
  }
  else {
    Buzzer.pwm_mode = 0;
  }

The PWM buzzer mode depends on Buzzer.pwm_mode which can only be enabled if multi-channels PWM with SetOption68 is also enabled. IOW, you can't use light control and PWM buzzer at the same time.

I asked in the TasmotaUsers group how to best control on/off an individual PWM channel in multi-channel PWM mode directly, and using "light control" is the most high-level and recommended way to do it. analogWrite() did not work and was pointed out to potentially break a lot of things:

https://groups.google.com/g/sonoffusers/c/GxOUfRmHLSg/m/jR-2MawzAgAJ

If you enable multi-channel PWM, the web UI offers to turn on/off the individual channels interactively, the same way I do it here programmatically, so I wouldn't expect this to break anything and be quite safe, actually.

  1. Why PWM?
    Piezoelectric (cheap) buzzers need an oscillating input. See
    https://en.wikipedia.org/wiki/Buzzer#Piezoelectric_2

  2. Can't control the PWMFrequency?
    Not true - one can use PwmFrequency command: https://tasmota.github.io/docs/Commands/
    The duty cycle setting for PWM1 should be left the default 50% but I found that changing it gives slightly different sound, so one can adjust it to make the buzzer sound best.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for you answer. I don't understand why you decided that Buzzer and Lights should not be used at the same time. If we are to make this feature available to anyone, we shouldn't have this constraint.

Please just use analogWrite() on GPIO_BUZZER. This would make it much easier to understand for users, not needing to change SO68 and not conflicting with PWM lights. This way also you make sure you that Gamma correction will not interfere.

When you say analogWrite() did not work, I assume it is because you did it on GPIO_PWM1 which was in direct conflict of the light management code.

As mentioned, the only caveat is that the PWMFrequency should be the same for lights and buzzer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I did not realize that one could use use analogWrite() on GPIO_BUZZER and the platform would then apply PWM to it, I thought that a PWM output could only work on a configured GPIO_PWMx pin. Of course if that works it is the much better option. I will try this and get back to you.

Copy link
Contributor Author

@hello-world-dot-c hello-world-dot-c Sep 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now changed and works well. Again, thanks for the suggestion.

Measuring the output, it looks like this:

image

The slightly inaccurate timing has nothing to do with this change, I filed it as #9265.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PWM frequency and generation itself is rock-solid, I agree. The timing issue lies solely in the buzzer implementation. As you can see in the bug report I filed, the exact same issue is present in the current firmware when using the Buzzer command, not using PWM/frequency output at all but static on/off output.

It seems more related to how often or when the buzzer module is called or what timing reference is used to call it. As far as I observed, the 'on' phase can become shorter and the 'off' phase can become longer, but never the other way around, at least with the non-inverted buzzer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, my buzzer does not need PWM. Just 5V and if beeps at his own frequency. What do we see in the diagram? Is it 1second and 10 times on/off or should it be the frequency of the buzzer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is the first I agree. There is no guarantee in TASMOTA to get every50ms control over something. Sometimes the step is just missed if there is too much load. But in this case also a PWM does not help. To shift the on/off into a RTC timer is a bit overengineered but would solve the missing 50ms loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the Buzzer -1 command, the buzzer GPIO pin is turning on/off every 100 ms indefinitely, this is what I used. In your case using the current implementation, the output would look like this:

image

Every high and low phase is approx. 100 ms here. This is turning an active buzzer such as yours on and off. When using a passive piezoelectric buzzer, this won't do anything, though, because the buzzer doesn't generate the sound itself but is more like a loudspeaker so you need to feed it the frequency it should use to generate a beeping sound. That's what the new option does. In this diagram:

image

the solid bars are again 100 ms wide but in this case, during the 'on' phase the 50% PWM frequency signal is generated to generate the buzzer sound. You can't see the PWM signal because of the scaling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PWM here is not meant to solve the timing issue, maybe a misunderstanding here. This pull request is only to add the support for passive buzzers.

analogWrite(Pin(GPIO_BUZZER, 0), Settings.pwm_range / 2); // set 50% duty cycle for frequency output
}
else {
analogWrite(Pin(GPIO_BUZZER, 0), 0); // set 0% (or 100% for inverted PWM) duty cycle which turns off frequency output either way
}
last_state = state;
}
}
else {
DigitalWrite(GPIO_BUZZER, 0, state); // Buzzer On/Off
}

}

//void BuzzerBeep(uint32_t count = 1, uint32_t on = 1, uint32_t off = 1, uint32_t tune = 0, uint32_t mode = 0);
Expand Down Expand Up @@ -69,19 +89,27 @@ void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32
}
Buzzer.count = count * 2; // Start buzzer

AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BUZ: %d(%d),%d,%d,0x%08X(0x%08X)"), count, Buzzer.count, on, off, tune, Buzzer.tune);
// We can use PWM mode for buzzer output if enabled.
if (Settings.flag4.buzzer_freq_mode) { // SetOption111 - Enable frequency output mode for buzzer
Buzzer.freq_mode = 1;
}
else {
Buzzer.freq_mode = 0;
}

AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BUZ: %d(%d),%d,%d,0x%08X(0x%08X),%d"), count, Buzzer.count, on, off, tune, Buzzer.tune, Buzzer.freq_mode);

Buzzer.enable = (Buzzer.count > 0);
if (!Buzzer.enable) {
BuzzerOff();
BuzzerSet(0);
}
}

void BuzzerSetStateToLed(uint32_t state)
{
if (Buzzer.enable && (2 == Buzzer.mode)) {
Buzzer.state = (state != 0);
DigitalWrite(GPIO_BUZZER, 0, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state);
BuzzerSet(Buzzer.state);
}
}

Expand Down Expand Up @@ -113,7 +141,7 @@ void BuzzerInit(void)
{
if (PinUsed(GPIO_BUZZER)) {
pinMode(Pin(GPIO_BUZZER), OUTPUT);
BuzzerOff();
BuzzerSet(0);
} else {
Buzzer.active = false;
}
Expand All @@ -140,7 +168,7 @@ void BuzzerEvery100mSec(void)
Buzzer.duration = Buzzer.set[Buzzer.state];
}
}
DigitalWrite(GPIO_BUZZER, 0, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state);
BuzzerSet(Buzzer.state);
} else {
Buzzer.enable = false;
}
Expand Down