From f5cdf6aa67454481290278320931f697a118c860 Mon Sep 17 00:00:00 2001 From: Jeff McBride Date: Thu, 21 Oct 2021 15:41:49 -0700 Subject: [PATCH] [sam] Add driver for SAMG timer channels --- README.md | 2 +- examples/samg55_xplained_pro/timer/main.cpp | 86 +++++ .../samg55_xplained_pro/timer/openocd.cfg | 6 + .../samg55_xplained_pro/timer/project.xml | 14 + src/modm/board/samg55_xplained_pro/board.hpp | 11 +- src/modm/platform/timer/samg/module.lb | 67 ++++ .../platform/timer/samg/timer_channel.hpp.in | 313 ++++++++++++++++++ .../timer/samg/timer_channel_base.hpp | 103 ++++++ 8 files changed, 600 insertions(+), 2 deletions(-) create mode 100644 examples/samg55_xplained_pro/timer/main.cpp create mode 100644 examples/samg55_xplained_pro/timer/openocd.cfg create mode 100644 examples/samg55_xplained_pro/timer/project.xml create mode 100644 src/modm/platform/timer/samg/module.lb create mode 100644 src/modm/platform/timer/samg/timer_channel.hpp.in create mode 100644 src/modm/platform/timer/samg/timer_channel_base.hpp diff --git a/README.md b/README.md index 4170220f31..b7a9debaa5 100644 --- a/README.md +++ b/README.md @@ -449,7 +449,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ○ -○ +✅ ○ ○ ○ diff --git a/examples/samg55_xplained_pro/timer/main.cpp b/examples/samg55_xplained_pro/timer/main.cpp new file mode 100644 index 0000000000..75423792c0 --- /dev/null +++ b/examples/samg55_xplained_pro/timer/main.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021, Jeff McBride + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +using namespace modm::platform; +using namespace modm::literals; + +MODM_ISR(TC3) +{ + // Clear pending interrupts by reading them + (void)TimerChannel3::getInterruptFlags(); + + static bool flag = false; + if (flag) + { + TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Clear, + TimerChannel0::TioEffect::Set); + } else + { + TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Set, + TimerChannel0::TioEffect::Clear); + } + flag = !flag; +} + +int +main() +{ + /* + * This example uses channel 0 to generate two output waveforms, and channel3 + * to create a periodic IRQ which swaps the polarity on the TIOB output. + * + * Note: + * On the SAMG55, the waveform outputs on the second module (TC1, TimerChannel[3-5]) + * are not connected to external pins. + */ + Board::initialize(); + + TimerChannel0::initialize(); + TimerChannel0::connect(); + + TimerChannel0::setClockSource(TimerChannel0::ClockSource::MckDiv2); + TimerChannel0::setWaveformMode(true); + // Setup timer to count up, and trigger reset on Rc match + TimerChannel0::setWaveformSelection(TimerChannel0::WavSel::Up_Rc); + + // Setup TioA to set on RA match, and clear on RC match + TimerChannel0::setTioaEffects(TimerChannel0::TioEffect::Set, TimerChannel0::TioEffect::Clear); + // Setup TioB to clear on RB match, and set on RC match + TimerChannel0::setTiobEffects(TimerChannel0::TioEffect::Clear, TimerChannel0::TioEffect::Set); + + // Change external event source, so that TIOB can be used as an output + TimerChannel0::setExtEventSource(TimerChannel0::ExtEventSource::Xc0); + + TimerChannel0::setRegA(10000); + TimerChannel0::setRegB(5000); + TimerChannel0::setRegC(15000); + + TimerChannel0::enable(); + TimerChannel0::start(); + + // Setup TC3 irq to swap TIOB polarity periodically + // Period = 128 * 10000 / 120MHz = ~10.6ms + TimerChannel3::initialize(); + TimerChannel3::setClockSource(TimerChannel0::ClockSource::MckDiv128); + TimerChannel3::setRegC(10000); + TimerChannel3::setWaveformMode(true); + TimerChannel3::setWaveformSelection(TimerChannel0::WavSel::Up_Rc); + TimerChannel3::enableInterruptVector(true); + TimerChannel3::enableInterrupt(TimerChannel3::Interrupt::RcCompare); + TimerChannel3::enable(); + TimerChannel3::start(); + + while (true) + ; +} \ No newline at end of file diff --git a/examples/samg55_xplained_pro/timer/openocd.cfg b/examples/samg55_xplained_pro/timer/openocd.cfg new file mode 100644 index 0000000000..233bc3ba94 --- /dev/null +++ b/examples/samg55_xplained_pro/timer/openocd.cfg @@ -0,0 +1,6 @@ + +# Configure for ATSAMG55-XPRO dev board +source [find interface/cmsis-dap.cfg] + +set CHIPNAME ATSAMG55J19 +source [find target/at91samg5x.cfg] diff --git a/examples/samg55_xplained_pro/timer/project.xml b/examples/samg55_xplained_pro/timer/project.xml new file mode 100644 index 0000000000..70dc0c60f9 --- /dev/null +++ b/examples/samg55_xplained_pro/timer/project.xml @@ -0,0 +1,14 @@ + + modm:samg55-xplained-pro + + + + + + modm:build:scons + modm:platform:timer:0 + modm:platform:timer:1 + modm:platform:timer:2 + modm:platform:timer:3 + + \ No newline at end of file diff --git a/src/modm/board/samg55_xplained_pro/board.hpp b/src/modm/board/samg55_xplained_pro/board.hpp index 078289d939..249ff052a4 100644 --- a/src/modm/board/samg55_xplained_pro/board.hpp +++ b/src/modm/board/samg55_xplained_pro/board.hpp @@ -27,7 +27,16 @@ struct SystemClock static constexpr uint32_t Frequency = PllAMult * SlowClkFreqHz; // CPU core frequency static constexpr uint32_t Usb = PllBMult * SlowClkFreqHz; - static constexpr uint32_t Mck = Frequency; // Master clock, used by most peripherals + static constexpr uint32_t Mck = Frequency; // Master clock: default used by most peripherals + // Programmable clocks: optionally used by certain peripherals + static constexpr uint32_t Pck0 = Mck; + static constexpr uint32_t Pck1 = Mck; + static constexpr uint32_t Pck2 = Mck; + static constexpr uint32_t Pck3 = Mck; + static constexpr uint32_t Pck4 = Mck; + static constexpr uint32_t Pck5 = Mck; + static constexpr uint32_t Pck6 = Mck; + static constexpr uint32_t Pck7 = Mck; static bool inline enable() diff --git a/src/modm/platform/timer/samg/module.lb b/src/modm/platform/timer/samg/module.lb new file mode 100644 index 0000000000..b6283d9272 --- /dev/null +++ b/src/modm/platform/timer/samg/module.lb @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2021, Jeff McBride +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +props = {} +class Instance(Module): + def __init__(self, driver, instance, channel): + self.driver = driver + self.instance = int(instance) + self.channel = channel + self.vectors = None + + def init(self, module): + module.name = str(self.instance) + module.description = "Instance {}".format(self.instance) + + def prepare(self, module, options): + return True + + def build(self, env): + global props + props["id"] = self.instance + props["module"] = int(self.instance / 3) + props["channel"] = self.channel + + env.substitutions = props + env.outbasepath = "modm/src/modm/platform/timer" + env.template("timer_channel.hpp.in", "timer_channel_{}.hpp".format(self.instance)) + +def init(module): + module.name = ":platform:timer" + module.description = "Timers (TC)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("tc:samg*"): + return False + + module.depends( + ":cmsis:device", + ":platform:gpio") + + timers = device.get_all_drivers("tc:samg*") + for driver in timers: + for instance in driver["instance"]: + instance = int(instance) + # Each TC instance has 3 channels, and we create an independent driver for each + for channel in range(3): + module.add_submodule(Instance(driver, instance*3+channel, channel)) + + global props + device = options[":target"] + props["target"] = device.identifier + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/timer" + env.copy("timer_channel_base.hpp") \ No newline at end of file diff --git a/src/modm/platform/timer/samg/timer_channel.hpp.in b/src/modm/platform/timer/samg/timer_channel.hpp.in new file mode 100644 index 0000000000..4e0e6f45bc --- /dev/null +++ b/src/modm/platform/timer/samg/timer_channel.hpp.in @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2021, Jeff McBride + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once +#include +#include "timer_channel_base.hpp" + +#include + +namespace modm::platform +{ + +/** + * @brief Timer Channel {{ id }} + * + * Driver for TC{{ module }} Channel {{ channel }} + * + * Each TC module provides three independent 16-bit timer channels, and each + * channel can generate two waveform outputs (TIOA/TIOB). In the datasheet + * and headers, TCx is sometimes used to refer to a module, and sometimes + * to a specific channel (for example, TC3_IRQn is the IRQ for TC1 channel 0). + * + * This driver does not cover all the functionality of the timer. For example, + * it does not (yet) support input capture or the block control that allows + * synchronization of the three timers within a module. + * + * To define an interrupt handler for this timer: + * \code + * MODM_ISR(TC{{ id }}) + * { + * // Reading the interrupt flags clears them + * TimerChannel{{ id }}::Interrupt_t flags = TimerChannel{{ id }}::getInterruptFlags(); + * } + * \endcode + * + * @author Jeff McBride + * @ingroup modm_platform_timer + */ +class TimerChannel{{ id }} : public TimerChannelBase +{ +public: + template< class... Pins > + static void + connect() + { + using TioaPin = GetPin_t; + using TiobPin = GetPin_t; + using TclkPin = GetPin_t; + using Tc = Peripherals::Tc<{{ module | int }}>; + + if constexpr (!std::is_void::value) { + using TioaConnector = typename TioaPin::template Connector>; + TioaConnector::connect(); + } + if constexpr (!std::is_void::value) { + using TiobConnector = typename TiobPin::template Connector>; + TiobConnector::connect(); + } + if constexpr (!std::is_void::value) { + using TclkConnector = typename TclkPin::template Connector>; + TclkConnector::connect(); + } + } + + /** Enable the peripheral clock + * + * This method must be called before any timer channel configuration can + * be done. + */ + static void + initialize() + { + ClockGen::enable(); + } + + /** Select which clock source will be used for counting + */ + static inline void + setClockSource(ClockSource src) + { + uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR & ~(TC_CMR_TCCLKS_Msk); + tmp |= (uint32_t)src << TC_CMR_TCCLKS_Pos; + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp; + } + + /** Returns the currently selected clock source + */ + static inline ClockSource + getClockSource() + { + uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR; + return (ClockSource)((cmr & TC_CMR_TCCLKS_Msk) >> TC_CMR_TCCLKS_Pos); + } + + static inline void + setWaveformSelection(WavSel ws) + { + uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR; + cmr &= ~TC_CMR_WAVSEL_Msk; + cmr |= (uint32_t)ws << TC_CMR_WAVSEL_Pos; + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = cmr; + } + + static inline void + enable() + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_CLKEN; + } + + static inline void + disable() + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_CLKDIS; + } + + static inline void + start() + { + softwareTrigger(); + } + + static inline uint16_t + getValue() + { + return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CV; + } + + static inline void + setValue(uint16_t value) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CV = value; + } + + static inline uint16_t + getRegA() + { + return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RA; + } + + static inline void + setRegA(uint16_t value) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RA = value; + } + + static inline uint16_t + getRegB() + { + return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RB; + } + + static inline void + setRegB(uint16_t value) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RB = value; + } + + static inline uint16_t + getRegC() + { + return TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RC; + } + + static inline void + setRegC(uint16_t value) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_RC = value; + } + + /** Return the frequency of the counter based on current configuration + * + * This method will return 0 if the counter is clocked from an external + * input. + */ + template + static inline uint32_t + getTickFrequency() + { + switch(getClockSource()) + { + case ClockSource::MckDiv2: + return SystemClock::Mck / 2; + case ClockSource::MckDiv8: + return SystemClock::Mck / 8; + case ClockSource::MckDiv32: + return SystemClock::Mck / 32; + case ClockSource::MckDiv128: + return SystemClock::Mck / 128; + case ClockSource::Pck3: + return SystemClock::Pck3; + default: + return 0; // Clocked externally, so we can't know the frequency + } + } + + /** Enable the NVIC IRQ for this timer channel + */ + static inline void enableInterruptVector(bool enable, uint32_t priority=5) + { + if(enable) + { + NVIC_SetPriority(TC{{ id }}_IRQn, priority); + NVIC_EnableIRQ(TC{{ id }}_IRQn); + } else { + NVIC_DisableIRQ(TC{{ id }}_IRQn); + } + } + + /** Enable interrupt in peripheral interrupt mask register + */ + static inline void enableInterrupt(Interrupt_t interrupts) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_IER = interrupts.value; + } + + /** Disable interrupt in peripheral interrupt mask register + */ + static inline void disableInterrupt(Interrupt_t interrupts) + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_IDR = interrupts.value; + } + + /** Reads the currently pending interrupt flags + * + * Flags are automatically cleared in hardware when they are read + */ + static inline Interrupt_t getInterruptFlags() + { + return (Interrupt_t)(TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_SR & 0x3f); + } + + /** Generate a software trigger event */ + static inline void softwareTrigger() + { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CCR = TC_CCR_SWTRG; + } + + /** Enable or disable waveform mode + * + * When not in waveform mode, timer operates in capture mode. + */ + static inline void setWaveformMode(bool enabled) + { + if(enabled) { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR |= TC_CMR_WAVE; + } else { + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR &= ~TC_CMR_WAVE; + } + } + + /** Selects which input signal is used as an external event + * + * Note that by default, TIOB is selected, and this prevents TIOB from + * being used as a waveform output. To use it as an output, select one + * of the other input options + */ + static inline void setExtEventSource(ExtEventSource src) + { + uint32_t cmr = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR; + cmr &= ~TC_CMR_EEVT_Msk; + cmr |= (uint32_t)src << TC_CMR_EEVT_Pos; + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = cmr; + } + + /** Set the effect of events on the TIOA output in waveform mode + */ + static inline void setTioaEffects( + TioEffect raCompare, + TioEffect rcCompare, + TioEffect extEvent = TioEffect::None, + TioEffect swTrig = TioEffect::None) + { + uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR; + tmp &= ~(TC_CMR_ACPA_Msk | TC_CMR_ACPC_Msk | TC_CMR_AEEVT_Msk | TC_CMR_ASWTRG_Msk); + tmp |= ((uint32_t)raCompare << TC_CMR_ACPA_Pos) | + ((uint32_t)rcCompare << TC_CMR_ACPC_Pos) | + ((uint32_t)extEvent << TC_CMR_AEEVT_Pos) | + ((uint32_t)swTrig << TC_CMR_ASWTRG_Pos); + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp; + } + + /** Set the effect of events on the TIOB output in waveform mode + * + * Note that by default TIOB is configured as an input for external events. + * To use it as a waveform output, you just select a different external + * event input with setExtEventSource. + */ + static inline void setTiobEffects( + TioEffect rbCompare, + TioEffect rcCompare, + TioEffect extEvent = TioEffect::None, + TioEffect swTrig = TioEffect::None) + { + uint32_t tmp = TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR; + tmp &= ~(TC_CMR_BCPB_Msk | TC_CMR_BCPC_Msk | TC_CMR_BEEVT_Msk | TC_CMR_BSWTRG_Msk); + tmp |= ((uint32_t)rbCompare << TC_CMR_BCPB_Pos) | + ((uint32_t)rcCompare << TC_CMR_BCPC_Pos) | + ((uint32_t)extEvent << TC_CMR_BEEVT_Pos) | + ((uint32_t)swTrig << TC_CMR_BSWTRG_Pos); + TC{{ module }}->TC_CHANNEL[{{ channel }}].TC_CMR = tmp; + } + +}; + +}; // namespace modm::platform diff --git a/src/modm/platform/timer/samg/timer_channel_base.hpp b/src/modm/platform/timer/samg/timer_channel_base.hpp new file mode 100644 index 0000000000..8657f7fb50 --- /dev/null +++ b/src/modm/platform/timer/samg/timer_channel_base.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, Jeff McBride + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- +#pragma once + +#include "../device.hpp" + +namespace modm::platform +{ + +class TimerChannelBase +{ +public: + /// Effect of various events on the TioX output pins + enum class TioEffect : uint32_t + { + None = 0, + Set = 1, + Clear = 2, + Toggle = 3 + }; + + /// External Event Selection values + enum class ExtEventSource : uint32_t + { + Tiob = 0, + Xc0 = 1, + Xc1 = 2, + Xc2 = 3, + }; + + /// Waveform mode selection values + enum class WavSel : uint32_t + { + Up = 0, // UP mode without automatic trigger on RC compare + UpDown = 1, // UPDOWN mode without automatic trigger on RC compare + Up_Rc = 2, // UP mode with automatic trigger on RC compare + UpDown_Rc = 3 // UPDOWN mode with automatic trigger on RC compare + }; + + /// Input clock selection values + enum class ClockSource : uint32_t + { + MckDiv2 = 0, + MckDiv8 = 1, + MckDiv32 = 2, + MckDiv128 = 3, + Pck3 = 4, + Xc0 = 5, + Xc1 = 6, + Xc2 = 7 + }; + + /// Interrupt flags + enum class Interrupt : uint32_t + { + CounterOverflow = TC_IER_COVFS, + LoadOverrun = TC_IER_LOVRS, + RaCompare = TC_IER_CPAS, + RbCompare = TC_IER_CPBS, + RcCompare = TC_IER_CPCS, + RaLoading = TC_IER_LDRAS, + RbLoading = TC_IER_LDRBS, + ExternalTrigger = TC_IER_ETRGS, + EndOfRxTransfer = TC_IER_ENDRX, + RxBufferFull = TC_IER_RXBUFF, + }; + MODM_FLAGS32(Interrupt); + + // The CMR register has dual definitions: one for capture mode and one for + // waveform mode. However it seems that only the capture mode bit definitions + // made it into the CMSIS device header. Here, the missing waveform mode + // definitions are defined as needed. + static const uint32_t TC_CMR_EEVT_Pos = 10; + static const uint32_t TC_CMR_EEVT_Msk = (0x3) << TC_CMR_EEVT_Pos; + static const uint32_t TC_CMR_WAVSEL_Pos = 13; + static const uint32_t TC_CMR_WAVSEL_Msk = (0x3) << TC_CMR_WAVSEL_Pos; + static const uint32_t TC_CMR_ACPA_Pos = 16; + static const uint32_t TC_CMR_ACPA_Msk = (0x3) << TC_CMR_ACPA_Pos; + static const uint32_t TC_CMR_ACPC_Pos = 18; + static const uint32_t TC_CMR_ACPC_Msk = (0x3) << TC_CMR_ACPC_Pos; + static const uint32_t TC_CMR_AEEVT_Pos = 20; + static const uint32_t TC_CMR_AEEVT_Msk = (0x3) << TC_CMR_AEEVT_Pos; + static const uint32_t TC_CMR_ASWTRG_Pos = 22; + static const uint32_t TC_CMR_ASWTRG_Msk = (0x3) << TC_CMR_ASWTRG_Pos; + static const uint32_t TC_CMR_BCPB_Pos = 24; + static const uint32_t TC_CMR_BCPB_Msk = (0x3) << TC_CMR_BCPB_Pos; + static const uint32_t TC_CMR_BCPC_Pos = 26; + static const uint32_t TC_CMR_BCPC_Msk = (0x3) << TC_CMR_BCPC_Pos; + static const uint32_t TC_CMR_BEEVT_Pos = 28; + static const uint32_t TC_CMR_BEEVT_Msk = (0x3) << TC_CMR_BEEVT_Pos; + static const uint32_t TC_CMR_BSWTRG_Pos = 30; + static const uint32_t TC_CMR_BSWTRG_Msk = (0x3) << TC_CMR_BSWTRG_Pos; +}; + +} // namespace modm::platform \ No newline at end of file