You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
analogWrite() has some problems with regard to using a timer for pulse width modulation:
It uses a fixed value of 0xFFFF for the waveform period with a TCC timer. The user might very well want to change the period on the fly.
On the first call to analogWrite() for a given timer, the timer is initialized and the CC register is written, but not the CCB register. On subsequent calls the timer is not initialized and the CCB register is written. Initialization should be separate from setting/changing the on-time and period, and the user should be able to re-initialize a timer whenever he wants.
A minor issue is that the variable name 'value' is sort of misleading when it comes to PWM, as both the on-time and period are "values" that determine the PWM waveform. I suggest renaming 'value' to 'on_time' in the new functions I suggest below (which are PWM-specific).
Some TCC timers are 16-bits and some are 24-bits and it varies depending on SAMD flavor. The user should have access to the size programmatically so he can make decisions regarding setting of resolution. Furthermore, the PWM period must never be larger than the largest number available with the user-set PWM resolution (2^resolution-1). Also if that resolution is larger than the number of TCC bits, the period and on-time should be mapped from the user resolution to TCC timer resolution, and that is the ONLY time it should be mapped. (Unlike with ADC, where a DIFFERENCE between the resolutions implies mapping; with PWM, the resolution may be set to the largest period used and the user is probably not concerned that a potentially LARGER resolution could be used with that TCC).
I suggest these fixes:
Move this code section outside of analogWrite() to the file level so it can be used by additional functions below:
Add function analogGetResolution_TCC_SAMD_TT() for getting the TCC resolution (bits):
/**************************************************************************/
/*!
@brief Return the resolution of a TCC.
@param pin Arduino pin number assigned to TCC, an index into table
g_APinDescription[] in SAMD core file variant.cpp.
@returns 16 or 24 if TCC is a 16-bit or 24-bit timer, 0 if 'pin' is non-PWM
or is a TC-type timer.
@note This resets the TCC timer, it must be reinitialized to use.
@note A portion of TCC timer initialization is done here, which is relied
upon by analogStartPWM_TCC_SAMD_TT().
*/
/**************************************************************************/
int analogGetResolution_TCC_SAMD_TT(pin_size_t pin) {
PinDescription pinDesc = g_APinDescription[pin];
// Return 0 if non-PWM pin specified.
uint32_t attr = pinDesc.ulPinAttribute;
if ((attr & PIN_ATTR_PWM) != PIN_ATTR_PWM)
return(0);
// Return 0 if TC-type timer rather than TCC timer.
uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
if (tcNum >= TCC_INST_NUM)
return(0);
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
// Configure the pin.
if (attr & PIN_ATTR_TIMER) {
#if !(ARDUINO_SAMD_VARIANT_COMPLIANCE >= 10603)
// Compatibility for cores based on SAMD core <=1.6.2
if (pinDesc.ulPinType == PIO_TIMER_ALT) {
pinPeripheral(pin, PIO_TIMER_ALT);
} else
#endif
{
pinPeripheral(pin, PIO_TIMER);
}
// We suppose that attr has PIN_ATTR_TIMER_ALT bit set...
pinPeripheral(pin, PIO_TIMER_ALT);
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_IDs[tcNum]);
while (GCLK->STATUS.bit.SYNCBUSY == 1);
// Get the TCC pointer.
Tcc* TCCx = (Tcc*) GetTC(pinDesc.ulPWMChannel);
// Disable TCC.
TCCx->CTRLA.bit.ENABLE = 0;
syncTCC(TCCx);
// Set TCC as normal PWM
TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM;
syncTCC(TCCx);
// Test if TCC is 24-bit. If not, it is 16-bit.
TCCx->PER.reg = 0xFFFFFFUL;
if (TCCx->PER.reg == 0xFFFFFFUL)
return(24);
return(16);
}
Add function analogStartPWM_TCC() for starting the PWM:
/**************************************************************************/
/*!
@brief Initialize the PWM and set its initial period and duty cycle.
@param pin Arduino pin number to write, an index into table
g_APinDescription[] in SAMD core file variant.cpp.
@returns TCC timer resolution in bits, 16 or 24 if TCC is a 16-bit or 24-bit
timer, or 0 if 'pin' is non-PWM or is a TC-type timer.
@note This supports PWM only on TCC timers.
@note Single-slope (NPWM) mode is used.
@note Dithering is not enabled.
@note The generic clock used depends on the TCC which depends on the pin,
see g_APinDescription[]. It will be either GCM_TCC0_TCC1 or
GCM_TCC2_TC3 for the SAMD21G.
@note This initializes the pulse on-time to 0 and pulse width to an
arbitrary setting, so the PWM output remains off until you call
analogSetPWM_TCC_SAMD_TT() to set a pulse on-time and period.
*/
/**************************************************************************/
int analogStartPWM_TCC_SAMD_TT(pin_size_t pin)
{
int res = analogGetResolution_TCC_SAMD_TT(pin);
if (res == 0)
return(0);
PinDescription pinDesc = g_APinDescription[pin];
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
Tcc* TCCx = (Tcc*) GetTC(pinDesc.ulPWMChannel);
// Set the initial CC and CCB register values to 0.
TCCx->CC[tcChannel].reg = 0;
syncTCC(TCCx);
TCCx->CCB[tcChannel].reg = 0;
syncTCC(TCCx);
// Set both PER and PERB to an arbitrary non-zero value. Since the minimum TCC
// size is 16 bits, 0xFFFF will work fine.
TCCx->PER.reg = 0xFFFF;
syncTCC(TCCx);
TCCx->PERB.reg = 0xFFFF;
syncTCC(TCCx);
// Enable TCCx
TCCx->CTRLA.bit.ENABLE = 1;
syncTCC(TCCx);
return(res);
}
Add function analogSetPWM_TCC() for setting/changing the PWM on-time and period values:
/**************************************************************************/
/*!
@brief Update the PWM on-time and period. It must already be initialized.
@param pin Arduino pin number to write, an index into table
g_APinDescription[] in SAMD core file variant.cpp.
@param res The TCC timer resolution in bits, as returned by
analogStartPWM_TCC_SAMD_TT().
@param on_time The pulse on-time value, 0 <= on_time <= period. This is
mapped linearly FROM the resolution requested by calling
one of the functions analogWriteResolution_PWM_SAMD_TT()
or analogWriteResolution_SAMD_TT() TO the ACTUAL
resolution of the TCC timer implied by the 'pin'
argument ONLY IF THAT ACTUAL RESOLUTION IS LOWER THAN
THE RESOLUTION THAT WAS REQUESTED. For example, if
requested resolution is 16 bits and the TCC is 24 bits,
no mapping is done, but if the requested resolution is
24 bits and the TCC is 16 bits, mapping is done.
@param period The initial PWM period, 1 <= period <= 2^resolution-1 of
resolution set with analogWriteResolution_PWM_SAMD_TT().
This is also mapped linearly in the same way as'on_time.
100% * (on_time/period).
@returns true if successful, false if 'pin' is non-PWM or is a TC-type timer
or on_time > period or period > 2^res-1.
*/
/**************************************************************************/
bool analogSetPWM_TCC_SAMD_TT(pin_size_t pin, int res, uint32_t on_time,
uint32_t period)
{
if (res == 0 || on_time > period || period > ((1UL << res)-1))
return(false);
PinDescription pinDesc = g_APinDescription[pin];
uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);
// Get the TCC pointer.
Tcc* TCCx = (Tcc*) GetTC(pinDesc.ulPWMChannel);
// Update the TCC.
TCCx->CTRLBSET.bit.LUPD = 1;
syncTCC(TCCx);
if (res < _writeResolution_PWM)
on_time = mapResolution(on_time, _writeResolution_PWM, res);
TCCx->CCB[tcChannel].reg = on_time;
syncTCC(TCCx);
if (res < _writeResolution_PWM)
period = mapResolution(period, _writeResolution_PWM, res);
TCCx->PERB.reg = period;
syncTCC(TCCx);
TCCx->CTRLBCLR.bit.LUPD = 1;
syncTCC(TCCx);
return(true);
}
The text was updated successfully, but these errors were encountered:
analogWrite() has some problems with regard to using a timer for pulse width modulation:
It uses a fixed value of 0xFFFF for the waveform period with a TCC timer. The user might very well want to change the period on the fly.
On the first call to analogWrite() for a given timer, the timer is initialized and the CC register is written, but not the CCB register. On subsequent calls the timer is not initialized and the CCB register is written. Initialization should be separate from setting/changing the on-time and period, and the user should be able to re-initialize a timer whenever he wants.
A minor issue is that the variable name 'value' is sort of misleading when it comes to PWM, as both the on-time and period are "values" that determine the PWM waveform. I suggest renaming 'value' to 'on_time' in the new functions I suggest below (which are PWM-specific).
Some TCC timers are 16-bits and some are 24-bits and it varies depending on SAMD flavor. The user should have access to the size programmatically so he can make decisions regarding setting of resolution. Furthermore, the PWM period must never be larger than the largest number available with the user-set PWM resolution (2^resolution-1). Also if that resolution is larger than the number of TCC bits, the period and on-time should be mapped from the user resolution to TCC timer resolution, and that is the ONLY time it should be mapped. (Unlike with ADC, where a DIFFERENCE between the resolutions implies mapping; with PWM, the resolution may be set to the largest period used and the user is probably not concerned that a potentially LARGER resolution could be used with that TCC).
I suggest these fixes:
The text was updated successfully, but these errors were encountered: