diff options
| author | Scott Shawcroft <scott.shawcroft@gmail.com> | 2018-05-29 18:21:19 -0700 |
|---|---|---|
| committer | Scott Shawcroft <scott.shawcroft@gmail.com> | 2018-06-01 15:08:48 -0700 |
| commit | d0fb6e7a2fd6b60ecc0fc8a332a9de5ed32f88af (patch) | |
| tree | 0004029e575afaac3af1e898eda534628acaad9c /ports/atmel-samd/peripherals | |
| parent | fd71e56891749837a823a4b555b84150b6fbc1e8 (diff) | |
atmel-samd: Add rotary encoder support.
Fixes #283
Diffstat (limited to 'ports/atmel-samd/peripherals')
| -rw-r--r-- | ports/atmel-samd/peripherals/clocks.c | 51 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/clocks.h | 1 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/events.c | 3 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/external_interrupts.c | 105 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/external_interrupts.h | 54 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/samd21/clocks.c | 14 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/samd21/external_interrupts.c | 86 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/samd51/external_interrupts.c | 132 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/samd51/sercom.c | 2 | ||||
| -rw-r--r-- | ports/atmel-samd/peripherals/samd51/timers.c | 2 |
10 files changed, 403 insertions, 47 deletions
diff --git a/ports/atmel-samd/peripherals/clocks.c b/ports/atmel-samd/peripherals/clocks.c index a890210bf..bfcca4547 100644 --- a/ports/atmel-samd/peripherals/clocks.c +++ b/ports/atmel-samd/peripherals/clocks.c @@ -56,41 +56,20 @@ uint8_t find_free_gclk(uint16_t divisor) { return 0xff; } +static uint8_t last_static_clock = 0; + +void init_dynamic_clocks(void) { + // Find the last statically initialized clock and save it. Everything after will be reset with + // the VM via reset_gclks. + for (uint8_t i = 0; i < GCLK_GEN_NUM; i++) { + if (gclk_enabled(i)) { + last_static_clock = i; + } + } +} + void reset_gclks(void) { - // Never reset GCLK0 because its used for the core - #if CONF_GCLK_GEN_1_GENEN == 0 - disable_gclk(1); - #endif - #if CONF_GCLK_GEN_2_GENEN == 0 - disable_gclk(2); - #endif - #if CONF_GCLK_GEN_3_GENEN == 0 - disable_gclk(3); - #endif - #if CONF_GCLK_GEN_4_GENEN == 0 - disable_gclk(4); - #endif - #if CONF_GCLK_GEN_5_GENEN == 0 - disable_gclk(5); - #endif - #if CONF_GCLK_GEN_6_GENEN == 0 - disable_gclk(6); - #endif - #if CONF_GCLK_GEN_7_GENEN == 0 - disable_gclk(7); - #endif - #ifdef SAMD51 - #if CONF_GCLK_GEN_8_GENEN == 0 - disable_gclk(8); - #endif - #if CONF_GCLK_GEN_9_GENEN == 0 - disable_gclk(9); - #endif - #if CONF_GCLK_GEN_10_GENEN == 0 - disable_gclk(10); - #endif - #if CONF_GCLK_GEN_11_GENEN == 0 - disable_gclk(11); - #endif - #endif + for (uint8_t i = last_static_clock + 1; i < GCLK_GEN_NUM; i++) { + disable_gclk(i); + } } diff --git a/ports/atmel-samd/peripherals/clocks.h b/ports/atmel-samd/peripherals/clocks.h index 6b7cfedce..78b2e2e3d 100644 --- a/ports/atmel-samd/peripherals/clocks.h +++ b/ports/atmel-samd/peripherals/clocks.h @@ -63,6 +63,7 @@ static inline bool board_has_crystal(void) { } void clock_init(void); +void init_dynamic_clocks(void); bool clock_get_enabled(uint8_t type, uint8_t index); bool clock_get_parent(uint8_t type, uint8_t index, uint8_t *p_type, uint8_t *p_index); diff --git a/ports/atmel-samd/peripherals/events.c b/ports/atmel-samd/peripherals/events.c index 9a4ab3b0c..2f6aea716 100644 --- a/ports/atmel-samd/peripherals/events.c +++ b/ports/atmel-samd/peripherals/events.c @@ -27,9 +27,6 @@ #include <stdint.h> #include "peripherals/events.h" -// -// #include "clocks.h" -// #include "py/runtime.h" uint8_t find_async_event_channel(void) { diff --git a/ports/atmel-samd/peripherals/external_interrupts.c b/ports/atmel-samd/peripherals/external_interrupts.c new file mode 100644 index 000000000..dfec6c998 --- /dev/null +++ b/ports/atmel-samd/peripherals/external_interrupts.c @@ -0,0 +1,105 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "common-hal/pulseio/PulseIn.h" +#include "common-hal/rotaryio/IncrementalEncoder.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "peripherals/external_interrupts.h" + +#include "sam.h" + +// This structure is used to share per-channel storage amongst all users of external interrupts. +// Without this there would be multiple arrays even though they are disjoint because each channel +// has one user. +static void *channel_data[EIC_EXTINT_NUM]; +static uint8_t channel_handler[EIC_EXTINT_NUM]; + +void external_interrupt_handler(uint8_t channel) { + uint8_t handler = channel_handler[channel]; + if (handler == EIC_HANDLER_PULSEIN) { + pulsein_interrupt_handler(channel); + } else if (handler == EIC_HANDLER_INCREMENTAL_ENCODER) { + incrementalencoder_interrupt_handler(channel); + } + EIC->INTFLAG.reg = (1 << channel) << EIC_INTFLAG_EXTINT_Pos; +} + +void configure_eic_channel(uint8_t eic_channel, uint32_t sense_setting) { + uint8_t config_index = eic_channel / 8; + uint8_t position = (eic_channel % 8) * 4; + #ifdef SAMD51 + eic_set_enable(false); + #endif + common_hal_mcu_disable_interrupts(); + uint32_t masked_value = EIC->CONFIG[config_index].reg & ~(0xf << position); + EIC->CONFIG[config_index].reg = masked_value | (sense_setting << position); + common_hal_mcu_enable_interrupts(); + #ifdef SAMD51 + eic_set_enable(true); + #endif +} + +void turn_on_eic_channel(uint8_t eic_channel, uint32_t sense_setting, + uint8_t channel_interrupt_handler) { + // We do very light filtering using majority voting. + sense_setting |= EIC_CONFIG_FILTEN0; + configure_eic_channel(eic_channel, sense_setting); + uint32_t mask = 1 << eic_channel; + EIC->INTENSET.reg = mask << EIC_INTENSET_EXTINT_Pos; + if (channel_interrupt_handler != EIC_HANDLER_NO_INTERRUPT) { + channel_handler[eic_channel] = channel_interrupt_handler; + turn_on_cpu_interrupt(eic_channel); + } +} + +void turn_off_eic_channel(uint8_t eic_channel) { + uint32_t mask = 1 << eic_channel; + EIC->INTENCLR.reg = mask << EIC_INTENSET_EXTINT_Pos; + #ifdef SAMD51 + NVIC_DisableIRQ(EIC_0_IRQn + eic_channel); + NVIC_ClearPendingIRQ(EIC_0_IRQn + eic_channel); + #endif + channel_data[eic_channel] = NULL; + + #ifdef SAMD21 + if (EIC->INTENSET.reg == 0) { + NVIC_DisableIRQ(EIC_IRQn); + NVIC_ClearPendingIRQ(EIC_IRQn); + } + #endif + // Test if all channels are null and deinit everything if they are. + if (EIC->EVCTRL.reg == 0 && EIC->INTENSET.reg == 0) { + turn_off_external_interrupt_controller(); + } +} + +void* get_eic_channel_data(uint8_t eic_channel) { + return channel_data[eic_channel]; +} + +void set_eic_channel_data(uint8_t eic_channel, void* data) { + channel_data[eic_channel] = data; +} diff --git a/ports/atmel-samd/peripherals/external_interrupts.h b/ports/atmel-samd/peripherals/external_interrupts.h new file mode 100644 index 000000000..12d820677 --- /dev/null +++ b/ports/atmel-samd/peripherals/external_interrupts.h @@ -0,0 +1,54 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_EXTERNAL_INTERRUPTS_H +#define MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_EXTERNAL_INTERRUPTS_H + +#include <stdbool.h> +#include <stdint.h> + +#define EIC_HANDLER_NO_INTERRUPT 0x0 +#define EIC_HANDLER_PULSEIN 0x1 +#define EIC_HANDLER_INCREMENTAL_ENCODER 0x2 + +void turn_on_external_interrupt_controller(void); +void turn_off_external_interrupt_controller(void); +void turn_on_cpu_interrupt(uint8_t eic_channel); +void turn_on_eic_channel(uint8_t eic_channel, uint32_t sense_setting, + uint8_t channel_interrupt_handler); +void configure_eic_channel(uint8_t eic_channel, uint32_t sense_setting); +void turn_off_eic_channel(uint8_t eic_channel); +bool eic_channel_free(uint8_t eic_channel); +bool eic_get_enable(void); +void eic_set_enable(bool value); +void eic_reset(void); + +void* get_eic_channel_data(uint8_t eic_channel); +void set_eic_channel_data(uint8_t eic_channel, void* data); + +void external_interrupt_handler(uint8_t channel); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_PERIPHERALS_EXTERNAL_INTERRUPTS_H diff --git a/ports/atmel-samd/peripherals/samd21/clocks.c b/ports/atmel-samd/peripherals/samd21/clocks.c index 94b515814..aad251a84 100644 --- a/ports/atmel-samd/peripherals/samd21/clocks.c +++ b/ports/atmel-samd/peripherals/samd21/clocks.c @@ -124,17 +124,23 @@ static void init_clock_source_dfll48m(void) { void clock_init(void) { init_clock_source_osc8m(); - if (board_has_crystal()) + if (board_has_crystal()) { init_clock_source_xosc32k(); - else + } else { init_clock_source_osc32k(); + } + enable_clock_generator(0, GCLK_GENCTRL_SRC_DFLL48M_Val, 1); enable_clock_generator(1, GCLK_GENCTRL_SRC_DFLL48M_Val, 150); init_clock_source_dfll48m(); - if (board_has_crystal()) + if (board_has_crystal()) { enable_clock_generator(2, GCLK_GENCTRL_SRC_XOSC32K_Val, 32); - else + } else { enable_clock_generator(2, GCLK_GENCTRL_SRC_OSC32K_Val, 32); + } + + // Do this after all static clock init so that they aren't used dynamically. + init_dynamic_clocks(); } static bool clk_enabled(uint8_t clk) { diff --git a/ports/atmel-samd/peripherals/samd21/external_interrupts.c b/ports/atmel-samd/peripherals/samd21/external_interrupts.c new file mode 100644 index 000000000..209c439c0 --- /dev/null +++ b/ports/atmel-samd/peripherals/samd21/external_interrupts.c @@ -0,0 +1,86 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "peripherals/external_interrupts.h" + +#include "hpl/gclk/hpl_gclk_base.h" +#include "peripherals/clocks.h" +#include "sam.h" + +void turn_on_external_interrupt_controller(void) { + PM->APBAMASK.bit.EIC_ = true; + _gclk_enable_channel(EIC_GCLK_ID, GCLK_CLKCTRL_GEN_GCLK0_Val); + eic_set_enable(true); +} + +void turn_off_external_interrupt_controller(void) { + eic_set_enable(false); + PM->APBAMASK.bit.EIC_ = false; + hri_gclk_write_CLKCTRL_reg(GCLK, GCLK_CLKCTRL_ID(EIC_GCLK_ID)); +} + +void turn_on_cpu_interrupt(uint8_t eic_channel) { + // Ignore the channel since the CPU interrupt line is shared. + (void) eic_channel; + + NVIC_DisableIRQ(EIC_IRQn); + NVIC_ClearPendingIRQ(EIC_IRQn); + NVIC_EnableIRQ(EIC_IRQn); +} + +bool eic_get_enable(void) { + return EIC->CTRL.bit.ENABLE; +} + +void eic_set_enable(bool value) { + EIC->CTRL.bit.ENABLE = value; + while (EIC->STATUS.bit.SYNCBUSY != 0) {} +} + +void eic_reset(void) { + EIC->CTRL.bit.SWRST = true; + while (EIC->STATUS.bit.SYNCBUSY != 0) {} + for (int i = 0; i < EIC_EXTINT_NUM; i++) { + set_eic_channel_data(i, NULL); + } + NVIC_DisableIRQ(EIC_IRQn); + NVIC_ClearPendingIRQ(EIC_IRQn); +} + +bool eic_channel_free(uint8_t eic_channel) { + uint32_t mask = 1 << eic_channel; + return get_eic_channel_data(eic_channel) == NULL && + (EIC->INTENSET.vec.EXTINT & mask) == 0 && + (EIC->EVCTRL.vec.EXTINTEO & mask) == 0; +} + +void EIC_Handler(void) { + for (uint8_t i = 0; i < 16; i++) { + if ((EIC->INTFLAG.vec.EXTINT & (1 << i)) != 0) { + external_interrupt_handler(i); + } + } +} diff --git a/ports/atmel-samd/peripherals/samd51/external_interrupts.c b/ports/atmel-samd/peripherals/samd51/external_interrupts.c new file mode 100644 index 000000000..6006d480e --- /dev/null +++ b/ports/atmel-samd/peripherals/samd51/external_interrupts.c @@ -0,0 +1,132 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "peripherals/external_interrupts.h" + +#include "peripherals/clocks.h" +#include "sam.h" + +void turn_on_external_interrupt_controller(void) { + MCLK->APBAMASK.bit.EIC_ = true; + + // We use the 48mhz clock to lightly filter the incoming pulse to reduce spurious interrupts. + connect_gclk_to_peripheral(GCLK_PCHCTRL_GEN_GCLK1_Val, EIC_GCLK_ID); + eic_set_enable(true); +} + +void turn_off_external_interrupt_controller(void) { + eic_set_enable(false); + MCLK->APBAMASK.bit.EIC_ = false; + disconnect_gclk_from_peripheral(GCLK_PCHCTRL_GEN_GCLK1_Val, EIC_GCLK_ID); +} + +void turn_on_cpu_interrupt(uint8_t eic_channel) { + // Ignore the channel since the CPU interrupt line is shared. + (void) eic_channel; + + NVIC_DisableIRQ(EIC_0_IRQn + eic_channel); + NVIC_ClearPendingIRQ(EIC_0_IRQn + eic_channel); + NVIC_EnableIRQ(EIC_0_IRQn + eic_channel); +} + +bool eic_get_enable(void) { + return EIC->CTRLA.bit.ENABLE; +} + +void eic_set_enable(bool value) { + EIC->CTRLA.bit.ENABLE = value; + while (EIC->SYNCBUSY.bit.ENABLE != 0) {} + // This won't actually block long enough in Rev A of SAMD51 and will miss edges in the first + // three cycles of the peripheral clock. See the errata for details. It shouldn't impact us. +} + +void eic_reset(void) { + EIC->CTRLA.bit.SWRST = true; + while (EIC->SYNCBUSY.bit.SWRST != 0) {} + // This won't actually block long enough in Rev A of SAMD51 and will miss edges in the first + // three cycles of the peripheral clock. See the errata for details. It shouldn't impact us. + for (int i = 0; i < EIC_EXTINT_NUM; i++) { + set_eic_channel_data(i, NULL); + NVIC_DisableIRQ(EIC_0_IRQn + i); + NVIC_ClearPendingIRQ(EIC_0_IRQn + i); + } +} + +bool eic_channel_free(uint8_t eic_channel) { + uint32_t mask = 1 << eic_channel; + return get_eic_channel_data(eic_channel) == NULL && + (EIC->INTENSET.bit.EXTINT & mask) == 0 && + (EIC->EVCTRL.bit.EXTINTEO & mask) == 0; +} + +void EIC_0_Handler(void) { + external_interrupt_handler(0); +} +void EIC_1_Handler(void) { + external_interrupt_handler(1); +} +void EIC_2_Handler(void) { + external_interrupt_handler(2); +} +void EIC_3_Handler(void) { + external_interrupt_handler(3); +} +void EIC_4_Handler(void) { + external_interrupt_handler(4); +} +void EIC_5_Handler(void) { + external_interrupt_handler(5); +} +void EIC_6_Handler(void) { + external_interrupt_handler(6); +} +void EIC_7_Handler(void) { + external_interrupt_handler(7); +} +void EIC_8_Handler(void) { + external_interrupt_handler(8); +} +void EIC_9_Handler(void) { + external_interrupt_handler(9); +} +void EIC_10_Handler(void) { + external_interrupt_handler(10); +} +void EIC_11_Handler(void) { + external_interrupt_handler(11); +} +void EIC_12_Handler(void) { + external_interrupt_handler(12); +} +void EIC_13_Handler(void) { + external_interrupt_handler(13); +} +void EIC_14_Handler(void) { + external_interrupt_handler(14); +} +void EIC_15_Handler(void) { + external_interrupt_handler(15); +} diff --git a/ports/atmel-samd/peripherals/samd51/sercom.c b/ports/atmel-samd/peripherals/samd51/sercom.c index fc4421422..6d0a02fbd 100644 --- a/ports/atmel-samd/peripherals/samd51/sercom.c +++ b/ports/atmel-samd/peripherals/samd51/sercom.c @@ -28,8 +28,6 @@ #include "hpl/gclk/hpl_gclk_base.h" #include "hri/hri_mclk_d51.h" -// FIXME(tannewt): Should this be called sercom.c? - // The clock initializer values are rather random, so we need to put them in // tables for lookup. We can't compute them. diff --git a/ports/atmel-samd/peripherals/samd51/timers.c b/ports/atmel-samd/peripherals/samd51/timers.c index 305acce38..f25dbc0e4 100644 --- a/ports/atmel-samd/peripherals/samd51/timers.c +++ b/ports/atmel-samd/peripherals/samd51/timers.c @@ -29,8 +29,6 @@ #include "peripherals/timers.h" -//#include "common-hal/pulseio/PulseOut.h" - #include "hri/hri_gclk_d51.h" const uint8_t tcc_cc_num[5] = {6, 4, 3, 2, 2}; |
