]> git.earman.xyz Git - sensor-watch.git/commitdiff
big PWM refactor: drive both LEDs and buzzer from TCC
authorJoey Castillo <jose.castillo@gmail.com>
Sun, 29 Aug 2021 21:40:44 +0000 (17:40 -0400)
committerJoey Castillo <jose.castillo@gmail.com>
Sun, 29 Aug 2021 21:40:44 +0000 (17:40 -0400)
watch-library/hpl/tcc/hpl_tcc.c
watch-library/hw/driver_init.c
watch-library/watch/watch_buzzer.c
watch-library/watch/watch_buzzer.h
watch-library/watch/watch_led.c
watch-library/watch/watch_led.h
watch-library/watch/watch_private.c
watch-library/watch/watch_private.h

index 52fc6f3c751c16cd8a62d3819eda45eacd69d0b4..af44aec103f90d01dbca3868219f9a666f90aa2c 100644 (file)
@@ -331,13 +331,6 @@ static void tcc_pwm_interrupt_handler(struct _pwm_device *device)
        }
 }
 
-/**
- * \brief TCC interrupt handler
- */
-void TCC0_Handler(void)
-{
-       tcc_pwm_interrupt_handler(_tcc0_dev);
-}
 
 static struct tcc_cfg *_get_tcc_cfg(void *hw)
 {
index 6d910d22510b81e7b724f1e5f443d7161fc040ef..daf3901d5556c26d435e1e42447bfcdd1fb33c84 100644 (file)
@@ -83,37 +83,6 @@ void delay_driver_init(void) {
        delay_init(SysTick);
 }
 
-void PWM_0_PORT_init(void) {
-       gpio_set_pin_function(RED, PINMUX_PA20E_TC3_WO0);
-       gpio_set_pin_function(GREEN, PINMUX_PA21E_TC3_WO1);
-}
-
-void PWM_0_CLOCK_init(void) {
-       hri_mclk_set_APBCMASK_TC3_bit(MCLK);
-       hri_gclk_write_PCHCTRL_reg(GCLK, TC3_GCLK_ID, CONF_GCLK_TC3_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos));
-}
-
-void PWM_0_init(void) {
-       PWM_0_CLOCK_init();
-       PWM_0_PORT_init();
-       pwm_init(&PWM_0, TC3, _tc_get_pwm());
-}
-
-void PWM_1_PORT_init(void) {
-       gpio_set_pin_function(BUZZER, PINMUX_PA27F_TCC0_WO5);
-}
-
-void PWM_1_CLOCK_init(void) {
-       hri_mclk_set_APBCMASK_TCC0_bit(MCLK);
-       hri_gclk_write_PCHCTRL_reg(GCLK, TCC0_GCLK_ID, CONF_GCLK_TCC0_SRC | (1 << GCLK_PCHCTRL_CHEN_Pos));
-}
-
-void PWM_1_init(void) {
-       PWM_1_CLOCK_init();
-       PWM_1_PORT_init();
-       pwm_init(&PWM_1, TCC0, _tcc_get_pwm());
-}
-
 void SEGMENT_LCD_0_PORT_init(void) {
        gpio_set_pin_function(COM0, PINMUX_PB06B_SLCD_LP0);
        gpio_set_pin_function(COM1, PINMUX_PB07B_SLCD_LP1);
index 72771508d023c940ee7081d0366b5df275efb741..c6c89a0e7216b1ffeadb7d67f871f8a8427d44f4 100644 (file)
  */
 
  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);
index 38b40f6ea312fd2d24168a2dc43149c8c3a52a0c..995e059ab28b70597a6777ba75f1312b0807f68d 100644 (file)
 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.
index 01f59fc49284a6c7123dd5ac1a52966eb9385e40..4f9898d884b13a828590a18aace41c94cc5bba69 100644 (file)
  * 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);
index 04b6e5faa44f9c3ab0cc4f8a27495968749dc915..5dedadf3479565b7ea16fac7da18cec92e8893fc 100644 (file)
@@ -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).
index ab988d109c234f7cb6a340e944300397b002735a..e820bf44674912414d2bd453728813b907dbaff0 100644 (file)
@@ -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);
index 51c78ae1aedf10bacefeb6d926235256280c8d73..abee085aa956fc3d95854e02b6bc1b5eddf0395c 100644 (file)
 /// 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();