227 lines
7.5 KiB
C++
227 lines
7.5 KiB
C++
/**
|
|
* Marlin 3D Printer Firmware
|
|
*
|
|
* Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|
* SAMD51 HAL developed by Giuliano Zaro (AKA GMagician)
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* This comes from Arduino library which at the moment is buggy and uncompilable
|
|
*/
|
|
|
|
#ifdef __SAMD51__
|
|
|
|
#include "../../inc/MarlinConfig.h"
|
|
|
|
#if HAS_SERVOS
|
|
|
|
#include "../shared/Marduino.h"
|
|
#include "../shared/servo.h"
|
|
#include "../shared/servo_private.h"
|
|
#include "SAMD51.h"
|
|
#include "timers.h"
|
|
|
|
#define __TC_GCLK_ID(t) TC##t##_GCLK_ID
|
|
#define _TC_GCLK_ID(t) __TC_GCLK_ID(t)
|
|
#define TC_GCLK_ID _TC_GCLK_ID(SERVO_TC)
|
|
|
|
#define _TC_PRESCALER(d) TC_CTRLA_PRESCALER_DIV##d##_Val
|
|
#define TC_PRESCALER(d) _TC_PRESCALER(d)
|
|
|
|
#define __SERVO_IRQn(t) TC##t##_IRQn
|
|
#define _SERVO_IRQn(t) __SERVO_IRQn(t)
|
|
#define SERVO_IRQn _SERVO_IRQn(SERVO_TC)
|
|
|
|
#define HAL_SERVO_TIMER_ISR() TC_HANDLER(SERVO_TC)
|
|
|
|
#define TIMER_TCCHANNEL(t) ((t) & 1)
|
|
#define TC_COUNTER_START_VAL 0xFFFF
|
|
|
|
|
|
static volatile int8_t currentServoIndex[_Nbr_16timers]; // index for the servo being pulsed for each timer (or -1 if refresh interval)
|
|
|
|
FORCE_INLINE static uint16_t getTimerCount() {
|
|
Tc * const tc = TimerConfig[SERVO_TC].pTimer;
|
|
|
|
tc->COUNT16.CTRLBSET.reg = TC_CTRLBCLR_CMD_READSYNC;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CTRLB || tc->COUNT16.SYNCBUSY.bit.COUNT);
|
|
|
|
return tc->COUNT16.COUNT.reg;
|
|
}
|
|
|
|
// ----------------------------
|
|
// Interrupt handler for the TC
|
|
// ----------------------------
|
|
HAL_SERVO_TIMER_ISR() {
|
|
Tc * const tc = TimerConfig[SERVO_TC].pTimer;
|
|
const timer16_Sequence_t timer =
|
|
#ifndef _useTimer1
|
|
_timer2
|
|
#elif !defined(_useTimer2)
|
|
_timer1
|
|
#else
|
|
(tc->COUNT16.INTFLAG.reg & tc->COUNT16.INTENSET.reg & TC_INTFLAG_MC0) ? _timer1 : _timer2
|
|
#endif
|
|
;
|
|
const uint8_t tcChannel = TIMER_TCCHANNEL(timer);
|
|
|
|
if (currentServoIndex[timer] < 0) {
|
|
#if defined(_useTimer1) && defined(_useTimer2)
|
|
if (currentServoIndex[timer ^ 1] >= 0) {
|
|
// Wait for both channels
|
|
// Clear the interrupt
|
|
tc->COUNT16.INTFLAG.reg = (tcChannel == 0) ? TC_INTFLAG_MC0 : TC_INTFLAG_MC1;
|
|
return;
|
|
}
|
|
#endif
|
|
tc->COUNT16.COUNT.reg = TC_COUNTER_START_VAL;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.COUNT);
|
|
}
|
|
else if (SERVO_INDEX(timer, currentServoIndex[timer]) < ServoCount && SERVO(timer, currentServoIndex[timer]).Pin.isActive)
|
|
digitalWrite(SERVO(timer, currentServoIndex[timer]).Pin.nbr, LOW); // pulse this channel low if activated
|
|
|
|
// Select the next servo controlled by this timer
|
|
currentServoIndex[timer]++;
|
|
|
|
if (SERVO_INDEX(timer, currentServoIndex[timer]) < ServoCount && currentServoIndex[timer] < SERVOS_PER_TIMER) {
|
|
if (SERVO(timer, currentServoIndex[timer]).Pin.isActive) // check if activated
|
|
digitalWrite(SERVO(timer, currentServoIndex[timer]).Pin.nbr, HIGH); // it's an active channel so pulse it high
|
|
|
|
tc->COUNT16.CC[tcChannel].reg = getTimerCount() - (uint16_t)SERVO(timer, currentServoIndex[timer]).ticks;
|
|
}
|
|
else {
|
|
// finished all channels so wait for the refresh period to expire before starting over
|
|
currentServoIndex[timer] = -1; // this will get incremented at the end of the refresh period to start again at the first channel
|
|
|
|
const uint16_t tcCounterValue = getTimerCount();
|
|
|
|
if ((TC_COUNTER_START_VAL - tcCounterValue) + 4UL < usToTicks(REFRESH_INTERVAL)) // allow a few ticks to ensure the next OCR1A not missed
|
|
tc->COUNT16.CC[tcChannel].reg = TC_COUNTER_START_VAL - (uint16_t)usToTicks(REFRESH_INTERVAL);
|
|
else
|
|
tc->COUNT16.CC[tcChannel].reg = (uint16_t)(tcCounterValue - 4UL); // at least REFRESH_INTERVAL has elapsed
|
|
}
|
|
if (tcChannel == 0) {
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CC0);
|
|
// Clear the interrupt
|
|
tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC0;
|
|
}
|
|
else {
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CC1);
|
|
// Clear the interrupt
|
|
tc->COUNT16.INTFLAG.reg = TC_INTFLAG_MC1;
|
|
}
|
|
}
|
|
|
|
void initISR(timer16_Sequence_t timer) {
|
|
Tc * const tc = TimerConfig[SERVO_TC].pTimer;
|
|
const uint8_t tcChannel = TIMER_TCCHANNEL(timer);
|
|
|
|
static bool initialized = false; // Servo TC has been initialized
|
|
if (!initialized) {
|
|
NVIC_DisableIRQ(SERVO_IRQn);
|
|
|
|
// Disable the timer
|
|
tc->COUNT16.CTRLA.bit.ENABLE = false;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.ENABLE);
|
|
|
|
// Select GCLK0 as timer/counter input clock source
|
|
GCLK->PCHCTRL[TC_GCLK_ID].bit.CHEN = false;
|
|
SYNC(GCLK->PCHCTRL[TC_GCLK_ID].bit.CHEN);
|
|
GCLK->PCHCTRL[TC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN; // 120MHz startup code programmed
|
|
SYNC(!GCLK->PCHCTRL[TC_GCLK_ID].bit.CHEN);
|
|
|
|
// Reset the timer
|
|
tc->COUNT16.CTRLA.bit.SWRST = true;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.SWRST);
|
|
SYNC(tc->COUNT16.CTRLA.bit.SWRST);
|
|
|
|
// Set timer counter mode to 16 bits
|
|
tc->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16;
|
|
|
|
// Set timer counter mode as normal PWM
|
|
tc->COUNT16.WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val;
|
|
|
|
// Set the prescaler factor
|
|
tc->COUNT16.CTRLA.bit.PRESCALER = TC_PRESCALER(SERVO_TIMER_PRESCALER);
|
|
|
|
// Count down
|
|
tc->COUNT16.CTRLBSET.reg = TC_CTRLBCLR_DIR;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CTRLB);
|
|
|
|
// Reset all servo indexes
|
|
memset((void *)currentServoIndex, 0xFF, sizeof(currentServoIndex));
|
|
|
|
// Configure interrupt request
|
|
NVIC_ClearPendingIRQ(SERVO_IRQn);
|
|
NVIC_SetPriority(SERVO_IRQn, 5);
|
|
NVIC_EnableIRQ(SERVO_IRQn);
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
if (!tc->COUNT16.CTRLA.bit.ENABLE) {
|
|
// Reset the timer counter
|
|
tc->COUNT16.COUNT.reg = TC_COUNTER_START_VAL;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.COUNT);
|
|
|
|
// Enable the timer and start it
|
|
tc->COUNT16.CTRLA.bit.ENABLE = true;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.ENABLE);
|
|
}
|
|
// First interrupt request after 1 ms
|
|
tc->COUNT16.CC[tcChannel].reg = getTimerCount() - (uint16_t)usToTicks(1000UL);
|
|
|
|
if (tcChannel == 0 ) {
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CC0);
|
|
|
|
// Clear pending match interrupt
|
|
tc->COUNT16.INTFLAG.reg = TC_INTENSET_MC0;
|
|
// Enable the match channel interrupt request
|
|
tc->COUNT16.INTENSET.reg = TC_INTENSET_MC0;
|
|
}
|
|
else {
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.CC1);
|
|
|
|
// Clear pending match interrupt
|
|
tc->COUNT16.INTFLAG.reg = TC_INTENSET_MC1;
|
|
// Enable the match channel interrupt request
|
|
tc->COUNT16.INTENSET.reg = TC_INTENSET_MC1;
|
|
}
|
|
}
|
|
|
|
void finISR(timer16_Sequence_t timer) {
|
|
Tc * const tc = TimerConfig[SERVO_TC].pTimer;
|
|
const uint8_t tcChannel = TIMER_TCCHANNEL(timer);
|
|
|
|
// Disable the match channel interrupt request
|
|
tc->COUNT16.INTENCLR.reg = (tcChannel == 0) ? TC_INTENCLR_MC0 : TC_INTENCLR_MC1;
|
|
|
|
if (true
|
|
#if defined(_useTimer1) && defined(_useTimer2)
|
|
&& (tc->COUNT16.INTENCLR.reg & (TC_INTENCLR_MC0|TC_INTENCLR_MC1)) == 0
|
|
#endif
|
|
) {
|
|
// Disable the timer if not used
|
|
tc->COUNT16.CTRLA.bit.ENABLE = false;
|
|
SYNC(tc->COUNT16.SYNCBUSY.bit.ENABLE);
|
|
}
|
|
}
|
|
|
|
#endif // HAS_SERVOS
|
|
|
|
#endif // __SAMD51__
|