From 6353d25d46979efed36c50ec186c8006f266d78a Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Sun, 29 Aug 2021 17:40:44 -0400 Subject: big PWM refactor: drive both LEDs and buzzer from TCC --- watch-library/watch/watch_buzzer.c | 22 +++++++++---- watch-library/watch/watch_buzzer.h | 10 ++++-- watch-library/watch/watch_led.c | 46 ++++++++++++-------------- watch-library/watch/watch_led.h | 9 ++--- watch-library/watch/watch_private.c | 65 +++++++++++++++++++++++++++++++++++++ watch-library/watch/watch_private.h | 6 ++++ 6 files changed, 120 insertions(+), 38 deletions(-) (limited to 'watch-library/watch') diff --git a/watch-library/watch/watch_buzzer.c b/watch-library/watch/watch_buzzer.c index 72771508..c6c89a0e 100644 --- a/watch-library/watch/watch_buzzer.c +++ b/watch-library/watch/watch_buzzer.c @@ -23,28 +23,38 @@ */ inline void watch_enable_buzzer() { - PWM_1_init(); + if (!hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + _watch_enable_tcc(); + } } - inline void watch_set_buzzer_period(uint32_t period) { - pwm_set_parameters(&PWM_1, period, period / 2); + hri_tcc_write_PERBUF_reg(TCC0, period); +} + +void watch_disable_buzzer() { + _watch_disable_tcc(); } inline void watch_set_buzzer_on() { - pwm_enable(&PWM_1); + gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OUT); + gpio_set_pin_function(BUZZER, PINMUX_PA27F_TCC0_WO5); } inline void watch_set_buzzer_off() { - pwm_disable(&PWM_1); + gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OFF); + gpio_set_pin_function(BUZZER, GPIO_PIN_FUNCTION_OFF); } +// note: the buzzer uses a 1 MHz clock. these values were determined by dividing 1,000,000 by the target frequency. +// i.e. for a 440 Hz tone (A4 on the piano), 1MHz/440Hz = 2273 const uint16_t NotePeriods[108] = {18182,17161,16197,15288,14430,13620,12857,12134,11453,10811,10204,9631,9091,8581,8099,7645,7216,6811,6428,6068,5727,5405,5102,4816,4545,4290,4050,3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025,1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012,956,902,851,804,758,716,676,638,602,568,536,506,478,451,426,402,379,358,338,319,301,284,268,253,239,225,213,201,190,179,169,159,150,142,134,127}; void watch_buzzer_play_note(BuzzerNote note, uint16_t duration_ms) { if (note == BUZZER_NOTE_REST) { watch_set_buzzer_off(); } else { - pwm_set_parameters(&PWM_1, NotePeriods[note], NotePeriods[note] / 2); + hri_tcc_write_PERBUF_reg(TCC0, NotePeriods[note]); + hri_tcc_write_CCBUF_reg(TCC0, 1, NotePeriods[note] / 2); watch_set_buzzer_on(); } delay_ms(duration_ms); diff --git a/watch-library/watch/watch_buzzer.h b/watch-library/watch/watch_buzzer.h index 38b40f6e..995e059a 100644 --- a/watch-library/watch/watch_buzzer.h +++ b/watch-library/watch/watch_buzzer.h @@ -32,11 +32,17 @@ void watch_enable_buzzer(); /** @brief Sets the period of the buzzer. - * @param period The period of a single cycle for the PWM peripheral. You can use the following formula to - * convert a desired frequency to a period for this function: period = 513751 * (freq^−1.0043) + * @param period The period of a single cycle for the TCC peripheral. You can determine the period for + * a desired frequency with the following formula: period = 1000000 / freq */ void watch_set_buzzer_period(uint32_t period); +/** @brief Disables the TCC peripheral that drives the buzzer. + * @note If you are using PWM to set custom LED colors, this method will also disable the LED PWM driver, + * since the buzzer and LED both make use of the same peripheral to drive their PWM behavior. + */ +void watch_disable_buzzer(); + /** @brief Turns the buzzer output on. It will emit a continuous sound at the given frequency. * @note The TCC peripheral that drives the buzzer does not run in standby mode; if you wish for buzzer * output to continue, you should prevent your app from going to sleep. diff --git a/watch-library/watch/watch_led.c b/watch-library/watch/watch_led.c index 01f59fc4..4f9898d8 100644 --- a/watch-library/watch/watch_led.c +++ b/watch-library/watch/watch_led.c @@ -22,45 +22,39 @@ * SOFTWARE. */ - bool PWM_0_enabled = false; void watch_enable_led(bool pwm) { if (pwm) { - if (PWM_0_enabled) return; - - PWM_0_init(); - pwm_set_parameters(&PWM_0, 10000, 0); - pwm_enable(&PWM_0); - - PWM_0_enabled = true; + if (!hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + _watch_enable_tcc(); + } } else { watch_enable_digital_output(RED); watch_enable_digital_output(GREEN); + watch_set_led_off(); } - watch_set_led_off(); } void watch_disable_led(bool pwm) { if (pwm) { - if (!PWM_0_enabled) return; - pwm_disable(&PWM_0); - PWM_0_enabled = false; + _watch_disable_tcc(); + } else { + watch_disable_digital_output(RED); + watch_disable_digital_output(GREEN); } - - watch_disable_digital_output(RED); - watch_disable_digital_output(GREEN); } -void watch_set_led_color(uint16_t red, uint16_t green) { - if (PWM_0_enabled) { - TC3->COUNT16.CC[0].reg = red; - TC3->COUNT16.CC[1].reg = green; +void watch_set_led_color(uint8_t red, uint8_t green) { + if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + uint32_t period = hri_tcc_get_PER_reg(TCC0, TCC_PER_MASK); + hri_tcc_write_CCBUF_reg(TCC0, 2, ((period * red * 1000ull) / 255000ull)); + hri_tcc_write_CCBUF_reg(TCC0, 3, ((period * green * 1000ull) / 255000ull)); } } void watch_set_led_red() { - if (PWM_0_enabled) { - watch_set_led_color(65535, 0); + if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + watch_set_led_color(255, 0); } else { watch_set_pin_level(RED, true); watch_set_pin_level(GREEN, false); @@ -68,8 +62,8 @@ void watch_set_led_red() { } void watch_set_led_green() { - if (PWM_0_enabled) { - watch_set_led_color(65535, 0); + if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + watch_set_led_color(0, 255); } else { watch_set_pin_level(RED, false); watch_set_pin_level(GREEN, true); @@ -77,8 +71,8 @@ void watch_set_led_green() { } void watch_set_led_yellow() { - if (PWM_0_enabled) { - watch_set_led_color(65535, 65535); + if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { + watch_set_led_color(255, 255); } else { watch_set_pin_level(RED, true); watch_set_pin_level(GREEN, true); @@ -86,7 +80,7 @@ void watch_set_led_yellow() { } void watch_set_led_off() { - if (PWM_0_enabled) { + if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) { watch_set_led_color(0, 0); } else { watch_set_pin_level(RED, false); diff --git a/watch-library/watch/watch_led.h b/watch-library/watch/watch_led.h index 04b6e5fa..5dedadf3 100644 --- a/watch-library/watch/watch_led.h +++ b/watch-library/watch/watch_led.h @@ -43,15 +43,16 @@ void watch_enable_led(bool pwm); /** @brief Disables the LEDs. * @param pwm if true, disables the PWM output. If false, disables the digital outputs. + * @note If pwm is true, this method will also disable the buzzer, since the buzzer and LED both make use of + * the same peripheral to drive their PWM behavior. */ void watch_disable_led(bool pwm); /** @brief Sets the LED to a custom color by modulating each output's duty cycle. - * @param red The red value. - * @param green The green value. - * @note still working on this, 0-65535 works now but these values may change. + * @param red The red value from 0-255. + * @param green The green value from 0-255. */ -void watch_set_led_color(uint16_t red, uint16_t green); +void watch_set_led_color(uint8_t red, uint8_t green); /** @brief Sets the red LED to full brightness, and turns the green LED off. * @note Of the two LED's in the RG bi-color LED, the red LED is the less power-efficient one (~4.5 mA). diff --git a/watch-library/watch/watch_private.c b/watch-library/watch/watch_private.c index ab988d10..e820bf44 100644 --- a/watch-library/watch/watch_private.c +++ b/watch-library/watch/watch_private.c @@ -45,6 +45,71 @@ void _watch_init() { a4_callback = NULL; } +void _watch_enable_tcc() { + // clock TCC0 with the main clock (4 or 16 MHz) and enable the peripheral clock. + hri_gclk_write_PCHCTRL_reg(GCLK, TCC0_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK0_Val | GCLK_PCHCTRL_CHEN); + hri_mclk_set_APBCMASK_TCC0_bit(MCLK); + // disable and reset TCC0. + hri_tcc_clear_CTRLA_ENABLE_bit(TCC0); + hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_ENABLE); + hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_SWRST); + hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_SWRST); + // have prescaler divide it down to 1 MHz. we need to know the actual CPU speed to do this. + uint32_t freq = watch_get_cpu_speed(); + switch (freq) { + case 4000000: + hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV4); + break; + case 8000000: + hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV8); + break; + case 12000000: + // NOTE: this case is here for completeness but the watch library never runs the hardware at 12 MHz. + // If you do, buzzer tones will be out of tune, as we can't evenly divide a 12 MHz clock into 1 MHz. + hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV16); + break; + case 16000000: + hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV16); + break; + } + // We're going to use normal PWM mode, which means period is controlled by PER, and duty cycle is controlled by + // each compare channel's value: + // * Buzzer tones are set by setting PER to the desired period for a given frequency, and CC[1] to half of that + // period (i.e. a square wave with a 50% duty cycle). + // * LEDs on CC[2] and CC[3] can be set to any value from 0 (off) to PER (fully on). + hri_tcc_write_WAVE_reg(TCC0, TCC_WAVE_WAVEGEN_NPWM); + // The buzzer will set the period depending on the tone it wants to play, but we have to set some period here to + // get the LED working. Almost any period will do, tho it should be below 20000 (i.e. 50 Hz) to avoid flickering. + hri_tcc_write_PER_reg(TCC0, 4096); + // Set the duty cycle of all pins to 0: LED's off, buzzer not buzzing. + hri_tcc_write_CC_reg(TCC0, 1, 0); + hri_tcc_write_CC_reg(TCC0, 2, 0); + hri_tcc_write_CC_reg(TCC0, 3, 0); + // Enable the TCC + hri_tcc_set_CTRLA_ENABLE_bit(TCC0); + hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_ENABLE); + + // enable LED PWM pins (the LED driver assumes if the TCC is on, the pins are enabled) + gpio_set_pin_direction(RED, GPIO_DIRECTION_OUT); + gpio_set_pin_function(RED, PINMUX_PA20F_TCC0_WO6); + gpio_set_pin_direction(GREEN, GPIO_DIRECTION_OUT); + gpio_set_pin_function(GREEN, PINMUX_PA21F_TCC0_WO7); +} + +void _watch_disable_tcc() { + // disable all PWM pins + gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OFF); + gpio_set_pin_function(BUZZER, GPIO_PIN_FUNCTION_OFF); + gpio_set_pin_direction(RED, GPIO_DIRECTION_OFF); + gpio_set_pin_function(RED, GPIO_PIN_FUNCTION_OFF); + gpio_set_pin_direction(GREEN, GPIO_DIRECTION_OFF); + gpio_set_pin_function(GREEN, GPIO_PIN_FUNCTION_OFF); + + // disable the TCC + hri_tcc_clear_CTRLA_ENABLE_bit(TCC0); + hri_mclk_clear_APBCMASK_TCC0_bit(MCLK); +} + void _watch_enable_usb() { // disable USB, just in case. hri_usb_clear_CTRLA_ENABLE_bit(USB); diff --git a/watch-library/watch/watch_private.h b/watch-library/watch/watch_private.h index 51c78ae1..abee085a 100644 --- a/watch-library/watch/watch_private.h +++ b/watch-library/watch/watch_private.h @@ -25,5 +25,11 @@ /// Called by main.c while setting up the app. You should not call this from your app. void _watch_init(); +/// Called by buzzer and LED setup functions. You should not call this from your app. +void _watch_enable_tcc(); + +/// Called by buzzer and LED teardown functions. You should not call this from your app. +void _watch_disable_tcc(); + /// Called by main.c if plugged in to USB. You should not call this from your app. void _watch_enable_usb(); -- cgit v1.2.3