mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-13 15:33:40 +00:00
machine: refactor PWM support
This commit refactors PWM support in the machine package to be more flexible. The new API can be used to produce tones at a specific frequency and control servos in a portable way, by abstracting over counter widths and prescalers.
This commit is contained in:
committed by
Ron Evans
parent
f880950c3e
commit
72acda22b0
+340
-168
@@ -31,8 +31,8 @@ const (
|
||||
PinInput PinMode = 9
|
||||
PinInputPullup PinMode = 10
|
||||
PinOutput PinMode = 11
|
||||
PinPWM PinMode = PinTimer
|
||||
PinPWMAlt PinMode = PinTimerAlt
|
||||
PinTCC PinMode = PinTimer
|
||||
PinTCCAlt PinMode = PinTimerAlt
|
||||
PinInputPulldown PinMode = 12
|
||||
)
|
||||
|
||||
@@ -1421,201 +1421,373 @@ func (spi SPI) txrx24mhz(tx, rx []byte) {
|
||||
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
|
||||
}
|
||||
|
||||
// PWM
|
||||
const period = 0xFFFF
|
||||
// TCC is one timer/counter peripheral, which consists of a counter and multiple
|
||||
// output channels (that can be connected to actual pins). You can set the
|
||||
// frequency using SetPeriod, but only for all the channels in this TCC
|
||||
// peripheral at once.
|
||||
type TCC sam.TCC_Type
|
||||
|
||||
// InitPWM initializes the PWM interface.
|
||||
func InitPWM() {
|
||||
// turn on timer clocks used for PWM
|
||||
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_ | sam.PM_APBCMASK_TCC1_ | sam.PM_APBCMASK_TCC2_)
|
||||
// The SAM D21 has three TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
TCC1 = (*TCC)(sam.TCC1)
|
||||
TCC2 = (*TCC)(sam.TCC2)
|
||||
)
|
||||
|
||||
// Use GCLK0 for TCC0/TCC1
|
||||
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
|
||||
}
|
||||
|
||||
// Use GCLK0 for TCC2/TC3
|
||||
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
|
||||
}
|
||||
//go:inline
|
||||
func (tcc *TCC) timer() *sam.TCC_Type {
|
||||
return (*sam.TCC_Type)(tcc)
|
||||
}
|
||||
|
||||
// Configure configures a PWM pin for output.
|
||||
func (pwm PWM) Configure() error {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
if timer == nil {
|
||||
return ErrInvalidOutputPin
|
||||
// Configure enables and configures this TCC.
|
||||
func (tcc *TCC) Configure(config PWMConfig) error {
|
||||
// Enable the clock source for this timer.
|
||||
switch tcc.timer() {
|
||||
case sam.TCC0:
|
||||
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_)
|
||||
// Use GCLK0 for TCC0/TCC1
|
||||
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
|
||||
}
|
||||
case sam.TCC1:
|
||||
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC1_)
|
||||
// Use GCLK0 for TCC0/TCC1
|
||||
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
|
||||
}
|
||||
case sam.TCC2:
|
||||
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC2_)
|
||||
// Use GCLK0 for TCC2/TC3
|
||||
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) |
|
||||
(sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
|
||||
sam.GCLK_CLKCTRL_CLKEN)
|
||||
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
|
||||
}
|
||||
}
|
||||
|
||||
// disable timer
|
||||
timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
|
||||
// Wait for synchronization
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
// Disable timer (if it was enabled). This is necessary because
|
||||
// tcc.setPeriod may want to change the prescaler bits in CTRLA, which is
|
||||
// only allowed when the TCC is disabled.
|
||||
tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
|
||||
|
||||
// Use "Normal PWM" (single-slope PWM)
|
||||
timer.WAVE.SetBits(sam.TCC_WAVE_WAVEGEN_NPWM)
|
||||
// Wait for synchronization
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_WAVE) {
|
||||
tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM)
|
||||
|
||||
// Wait for synchronization of all changed registers.
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
|
||||
// Set the period (the number to count to (TOP) before resetting timer)
|
||||
//TCC0->PER.reg = period;
|
||||
timer.PER.Set(period)
|
||||
// Wait for synchronization
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_PER) {
|
||||
// Set the period and prescaler.
|
||||
err := tcc.setPeriod(config.Period, true)
|
||||
|
||||
// Enable the timer.
|
||||
tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
|
||||
|
||||
// Wait for synchronization of all changed registers.
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
|
||||
// Set pin as output
|
||||
sam.PORT.DIRSET0.Set(1 << uint8(pwm.Pin))
|
||||
// Set pin to low
|
||||
sam.PORT.OUTCLR0.Set(1 << uint8(pwm.Pin))
|
||||
// Return any error that might have occured in the tcc.setPeriod call.
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable the port multiplexer for pin
|
||||
pwm.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
|
||||
|
||||
// Connect TCCX timer to pin.
|
||||
// we normally use the F channel aka ALT
|
||||
pwmConfig := PinPWMAlt
|
||||
|
||||
// in the case of PA6 or PA7 we have to use E channel
|
||||
if pwm.Pin == 6 || pwm.Pin == 7 {
|
||||
pwmConfig = PinPWM
|
||||
// SetPeriod updates the period of this TCC peripheral.
|
||||
// To set a particular frequency, use the following formula:
|
||||
//
|
||||
// period = 1e9 / frequency
|
||||
//
|
||||
// If you use a period of 0, a period that works well for LEDs will be picked.
|
||||
//
|
||||
// SetPeriod will not change the prescaler, but also won't change the current
|
||||
// value in any of the channels. This means that you may need to update the
|
||||
// value for the particular channel.
|
||||
//
|
||||
// Note that you cannot pick any arbitrary period after the TCC peripheral has
|
||||
// been configured. If you want to switch between frequencies, pick the lowest
|
||||
// frequency (longest period) once when calling Configure and adjust the
|
||||
// frequency here as needed.
|
||||
func (tcc *TCC) SetPeriod(period uint64) error {
|
||||
err := tcc.setPeriod(period, false)
|
||||
if err == nil {
|
||||
if tcc.Counter() >= tcc.Top() {
|
||||
// When setting the timer to a shorter period, there is a chance
|
||||
// that it passes the counter value and thus goes all the way to MAX
|
||||
// before wrapping back to zero.
|
||||
// To avoid this, reset the counter back to 0.
|
||||
tcc.timer().COUNT.Set(0)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if pwm.Pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_PMUX0_PMUXO_Pos))
|
||||
// setPeriod sets the period of this TCC, possibly updating the prescaler as
|
||||
// well. The prescaler can only modified when the TCC is disabled, that is, in
|
||||
// the Configure function.
|
||||
func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error {
|
||||
var top uint64
|
||||
if period == 0 {
|
||||
// Make sure the TOP value is at 0xffff (enough for a 16-bit timer).
|
||||
top = 0xffff
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := pwm.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
pwm.setPMux(val | uint8(pwmConfig<<sam.PORT_PMUX0_PMUXE_Pos))
|
||||
// The formula below calculates the following formula, optimized:
|
||||
// period * (48e6 / 1e9)
|
||||
// This assumes that the chip is running at the (default) 48MHz speed.
|
||||
top = period * 6 / 125
|
||||
}
|
||||
|
||||
maxTop := uint64(0xffffff)
|
||||
if tcc.timer() == sam.TCC2 {
|
||||
// TCC2 is a 16-bit timer, not a 24-bit timer.
|
||||
maxTop = 0xffff
|
||||
}
|
||||
|
||||
if updatePrescaler {
|
||||
// This function was called during Configure(), with the timer disabled.
|
||||
// Note that updating the prescaler can only happen while the peripheral
|
||||
// is disabled.
|
||||
var prescaler uint32
|
||||
switch {
|
||||
case top <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1
|
||||
case top/2 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV2
|
||||
top = top / 2
|
||||
case top/4 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV4
|
||||
top = top / 4
|
||||
case top/8 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV8
|
||||
top = top / 8
|
||||
case top/16 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV16
|
||||
top = top / 16
|
||||
case top/64 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV64
|
||||
top = top / 64
|
||||
case top/256 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV256
|
||||
top = top / 256
|
||||
case top/1024 <= maxTop:
|
||||
prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024
|
||||
top = top / 1024
|
||||
default:
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos))
|
||||
} else {
|
||||
// Do not update the prescaler, but use the already-configured
|
||||
// prescaler. This is the normal SetPeriod case, where the prescaler
|
||||
// must not be changed.
|
||||
prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos
|
||||
switch prescaler {
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV1:
|
||||
top /= 1 // no-op
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV2:
|
||||
top /= 2
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV4:
|
||||
top /= 4
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV8:
|
||||
top /= 8
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV16:
|
||||
top /= 16
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV64:
|
||||
top /= 64
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV256:
|
||||
top /= 256
|
||||
case sam.TCC_CTRLA_PRESCALER_DIV1024:
|
||||
top /= 1024
|
||||
default:
|
||||
// unreachable
|
||||
}
|
||||
if top > maxTop {
|
||||
return ErrPWMPeriodTooLong
|
||||
}
|
||||
}
|
||||
|
||||
// Set the period (the counter top).
|
||||
tcc.timer().PER.Set(uint32(top) - 1)
|
||||
|
||||
// Wait for synchronization of CTRLA.PRESCALER and PER registers.
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set turns on the duty cycle for a PWM pin using the provided value.
|
||||
func (pwm PWM) Set(value uint16) {
|
||||
// figure out which TCCX timer for this pin
|
||||
timer := pwm.getTimer()
|
||||
if timer == nil {
|
||||
// The Configure call above cannot have succeeded, so simply ignore this
|
||||
// error.
|
||||
return
|
||||
// Top returns the current counter top, for use in duty cycle calculation. It
|
||||
// will only change with a call to Configure or SetPeriod, otherwise it is
|
||||
// constant.
|
||||
//
|
||||
// The value returned here is hardware dependent. In general, it's best to treat
|
||||
// it as an opaque value that can be divided by some number and passed to Set
|
||||
// (see Set documentation for more information).
|
||||
func (tcc *TCC) Top() uint32 {
|
||||
return tcc.timer().PER.Get() + 1
|
||||
}
|
||||
|
||||
// Counter returns the current counter value of the timer in this TCC
|
||||
// peripheral. It may be useful for debugging.
|
||||
func (tcc *TCC) Counter() uint32 {
|
||||
tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos)
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
return tcc.timer().COUNT.Get()
|
||||
}
|
||||
|
||||
// Some constans to make pinTimerMapping below easier to read.
|
||||
const (
|
||||
pinTCC0 = 1
|
||||
pinTCC1 = 2
|
||||
pinTCC2 = 3
|
||||
pinTimerCh0 = 0 << 3
|
||||
pinTimerCh2 = 1 << 3
|
||||
pinTCC0Ch0 = pinTCC0 | pinTimerCh0
|
||||
pinTCC0Ch2 = pinTCC0 | pinTimerCh2
|
||||
pinTCC1Ch0 = pinTCC1 | pinTimerCh0
|
||||
pinTCC1Ch2 = pinTCC1 | pinTimerCh2
|
||||
pinTCC2Ch0 = pinTCC2 | pinTimerCh0
|
||||
)
|
||||
|
||||
// Mapping from pin number to TCC peripheral and channel using a special
|
||||
// encoding. Note that only TCC0-TCC2 are included, not TC3 and up.
|
||||
// Every byte is split in two nibbles where the low nibble describes PinTCC and
|
||||
// the high nibble describes PinTCCAlt. Within a nibble, there is one bit that
|
||||
// indicates Ch0/Ch1 or Ch2/Ch3, and three other bits that contain the TCC
|
||||
// peripheral number plus one (to distinguish between TCC0Ch0 and 0).
|
||||
//
|
||||
// The encoding can be so compact because all pins are configured in pairs, so
|
||||
// if you know PA00 you can infer the configuration of PA01. And only channel 0
|
||||
// or 2 need to be included (taking up just one bit), because channel 0 and 2
|
||||
// are only ever used on odd pins and channel 1 and 3 on even pins, again using
|
||||
// the pin pair pattern to reduce the amount of information needed to be stored.
|
||||
//
|
||||
// Datasheet: https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf
|
||||
var pinTimerMapping = [...]uint8{
|
||||
// page 21
|
||||
PA00 / 2: pinTCC2Ch0 | 0,
|
||||
PA04 / 2: pinTCC0Ch0 | 0,
|
||||
PA06 / 2: pinTCC1Ch0 | 0,
|
||||
PA08 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4,
|
||||
PA10 / 2: pinTCC1Ch0 | pinTCC0Ch2<<4,
|
||||
// page 22
|
||||
PB10 / 2: 0 | pinTCC0Ch0<<4,
|
||||
PB12 / 2: 0 | pinTCC0Ch2<<4,
|
||||
PA12 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4,
|
||||
PA14 / 2: 0 | pinTCC0Ch0<<4,
|
||||
PA16 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4,
|
||||
PA18 / 2: 0 | pinTCC0Ch2<<4,
|
||||
PB16 / 2: 0 | pinTCC0Ch0<<4,
|
||||
PA20 / 2: 0 | pinTCC0Ch2<<4,
|
||||
PA22 / 2: 0 | pinTCC0Ch0<<4,
|
||||
PA24 / 2: 0 | pinTCC1Ch2<<4,
|
||||
// page 23
|
||||
PA30 / 2: 0 | pinTCC1Ch0<<4,
|
||||
PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4,
|
||||
}
|
||||
|
||||
// findPinPadMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel
|
||||
// number for a given timer and pin. A zero PinMode is returned if no mapping
|
||||
// could be found.
|
||||
func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) {
|
||||
mapping := pinTimerMapping[pin/2]
|
||||
// evenChannel below indicates the channel 0 or 2, for the even part of the
|
||||
// pin pair. The next pin will also have the next channel (1 or 3).
|
||||
if mapping&0x07 == timer+1 {
|
||||
// PWM output is on peripheral function E.
|
||||
evenChannel := ((mapping >> 3) & 1) * 2
|
||||
return PinTCC, evenChannel + uint8(pin&1)
|
||||
}
|
||||
if (mapping&0x70)>>4 == timer+1 {
|
||||
// PWM output is on peripheral function F.
|
||||
evenChannel := ((mapping >> 7) & 1) * 2
|
||||
return PinTCCAlt, evenChannel + uint8(pin&1)
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Channel returns a PWM channel for the given pin. Note that one channel may be
|
||||
// shared between multiple pins, and so will have the same duty cycle. If this
|
||||
// is not desirable, look for a different TCC peripheral or consider using a
|
||||
// different pin.
|
||||
func (tcc *TCC) Channel(pin Pin) (uint8, error) {
|
||||
var pinMode PinMode
|
||||
var channel uint8
|
||||
switch tcc.timer() {
|
||||
case sam.TCC0:
|
||||
pinMode, channel = findPinTimerMapping(0, pin)
|
||||
case sam.TCC1:
|
||||
pinMode, channel = findPinTimerMapping(1, pin)
|
||||
case sam.TCC2:
|
||||
pinMode, channel = findPinTimerMapping(2, pin)
|
||||
}
|
||||
|
||||
// disable output
|
||||
timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE)
|
||||
|
||||
// Wait for synchronization
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
|
||||
if pinMode == 0 {
|
||||
// No pin could be found.
|
||||
return 0, ErrInvalidOutputPin
|
||||
}
|
||||
|
||||
// Enable the port multiplexer for pin
|
||||
pin.setPinCfg(sam.PORT_PINCFG0_PMUXEN)
|
||||
|
||||
if pin&1 > 0 {
|
||||
// odd pin, so save the even pins
|
||||
val := pin.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
|
||||
pin.setPMux(val | uint8(pinMode<<sam.PORT_PMUX0_PMUXO_Pos))
|
||||
} else {
|
||||
// even pin, so save the odd pins
|
||||
val := pin.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
|
||||
pin.setPMux(val | uint8(pinMode<<sam.PORT_PMUX0_PMUXE_Pos))
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
// SetInverting sets whether to invert the output of this channel.
|
||||
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
|
||||
// the time and low for the rest. Inverting flips the output as if a NOT gate
|
||||
// was placed at the output, meaning that the output would be 25% low and 75%
|
||||
// high with a duty cycle of 25%.
|
||||
func (tcc *TCC) SetInverting(channel uint8, inverting bool) {
|
||||
if inverting {
|
||||
tcc.timer().WAVE.SetBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
|
||||
} else {
|
||||
tcc.timer().WAVE.ClearBits(1 << (sam.TCC_WAVE_POL0_Pos + channel))
|
||||
}
|
||||
|
||||
// Wait for synchronization of the WAVE register.
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Set updates the channel value. This is used to control the channel duty
|
||||
// cycle, in other words the fraction of time the channel output is high (or low
|
||||
// when inverted). For example, to set it to a 25% duty cycle, use:
|
||||
//
|
||||
// tcc.Set(channel, tcc.Top() / 4)
|
||||
//
|
||||
// tcc.Set(channel, 0) will set the output to low and tcc.Set(channel,
|
||||
// tcc.Top()) will set the output to high, assuming the output isn't inverted.
|
||||
func (tcc *TCC) Set(channel uint8, value uint32) {
|
||||
// Set PWM signal to output duty cycle
|
||||
pwm.setChannel(timer, uint32(value))
|
||||
|
||||
// Wait for synchronization on all channels
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0 |
|
||||
sam.TCC_SYNCBUSY_CC1 |
|
||||
sam.TCC_SYNCBUSY_CC2 |
|
||||
sam.TCC_SYNCBUSY_CC3) {
|
||||
}
|
||||
|
||||
// enable
|
||||
timer.CTRLA.SetBits(sam.TCC_CTRLA_ENABLE)
|
||||
// Wait for synchronization
|
||||
for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) {
|
||||
}
|
||||
}
|
||||
|
||||
// getPMux returns the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) getPMux() uint8 {
|
||||
return pwm.Pin.getPMux()
|
||||
}
|
||||
|
||||
// setPMux sets the value for the correct PMUX register for this pin.
|
||||
func (pwm PWM) setPMux(val uint8) {
|
||||
pwm.Pin.setPMux(val)
|
||||
}
|
||||
|
||||
// getPinCfg returns the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) getPinCfg() uint8 {
|
||||
return pwm.Pin.getPinCfg()
|
||||
}
|
||||
|
||||
// setPinCfg sets the value for the correct PINCFG register for this pin.
|
||||
func (pwm PWM) setPinCfg(val uint8) {
|
||||
pwm.Pin.setPinCfg(val)
|
||||
}
|
||||
|
||||
// getTimer returns the timer to be used for PWM on this pin
|
||||
func (pwm PWM) getTimer() *sam.TCC_Type {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
return sam.TCC1
|
||||
case 7:
|
||||
return sam.TCC1
|
||||
case 8:
|
||||
return sam.TCC1
|
||||
case 9:
|
||||
return sam.TCC1
|
||||
case 14:
|
||||
return sam.TCC0
|
||||
case 15:
|
||||
return sam.TCC0
|
||||
case 16:
|
||||
return sam.TCC0
|
||||
case 17:
|
||||
return sam.TCC0
|
||||
case 18:
|
||||
return sam.TCC0
|
||||
case 19:
|
||||
return sam.TCC0
|
||||
case 20:
|
||||
return sam.TCC0
|
||||
case 21:
|
||||
return sam.TCC0
|
||||
switch channel {
|
||||
case 0:
|
||||
tcc.timer().CC0.Set(value)
|
||||
case 1:
|
||||
tcc.timer().CC1.Set(value)
|
||||
case 2:
|
||||
tcc.timer().CC2.Set(value)
|
||||
case 3:
|
||||
tcc.timer().CC3.Set(value)
|
||||
default:
|
||||
return nil // not supported on this pin
|
||||
// invalid PWM channel, ignore.
|
||||
}
|
||||
}
|
||||
|
||||
// setChannel sets the value for the correct channel for PWM on this pin
|
||||
func (pwm PWM) setChannel(timer *sam.TCC_Type, val uint32) {
|
||||
switch pwm.Pin {
|
||||
case 6:
|
||||
timer.CC0.Set(val)
|
||||
case 7:
|
||||
timer.CC1.Set(val)
|
||||
case 8:
|
||||
timer.CC0.Set(val)
|
||||
case 9:
|
||||
timer.CC1.Set(val)
|
||||
case 14:
|
||||
timer.CC0.Set(val)
|
||||
case 15:
|
||||
timer.CC1.Set(val)
|
||||
case 16:
|
||||
timer.CC2.Set(val)
|
||||
case 17:
|
||||
timer.CC3.Set(val)
|
||||
case 18:
|
||||
timer.CC2.Set(val)
|
||||
case 19:
|
||||
timer.CC3.Set(val)
|
||||
case 20:
|
||||
timer.CC2.Set(val)
|
||||
case 21:
|
||||
timer.CC3.Set(val)
|
||||
default:
|
||||
return // not supported on this pin
|
||||
// Wait for synchronization on all channels (or anything in this peripheral,
|
||||
// really).
|
||||
for tcc.timer().SYNCBUSY.Get() != 0 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user