Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

Committer:
arnoz
Date:
Fri Oct 01 08:19:46 2021 +0000
Revision:
116:7a67265d7c19
Parent:
109:310ac82cbbee
- Correct information regarding your last merge

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 77:0b96f6867312 1 // New PWM
mjr 77:0b96f6867312 2 //
mjr 77:0b96f6867312 3 // This is a replacement for the mbed PwmOut class. It's both stripped
mjr 77:0b96f6867312 4 // down and beefed up. It's stripped down to just the functionality we
mjr 77:0b96f6867312 5 // need in the Pinscape code, and to a purely KL25Z implementation, which
mjr 77:0b96f6867312 6 // allows for a smaller memory footprint per instance. It's beefed up to
mjr 77:0b96f6867312 7 // correct a number of problems in the mbed implementation.
mjr 77:0b96f6867312 8 //
mjr 77:0b96f6867312 9 // Note that this class isn't quite API-compatible with the mbed version.
mjr 77:0b96f6867312 10 // We make the channel/TPM unit structure explicit, and we put the period()
mjr 77:0b96f6867312 11 // method (to change the PWM cycle time) on the unit object rather than the
mjr 77:0b96f6867312 12 // channel. We do this to emphasize in the API that the period is a property
mjr 77:0b96f6867312 13 // of the unit (which contains multiple channels) rather than the channel.
mjr 77:0b96f6867312 14 // The mbed library is misleading when it pretends that the period is a
mjr 77:0b96f6867312 15 // property of the channel, since this confusingly suggests that a channel's
mjr 77:0b96f6867312 16 // period can be set independently. It can't; the period can only be set for
mjr 77:0b96f6867312 17 // the whole group of channels controlled by a unit.
mjr 77:0b96f6867312 18 //
mjr 77:0b96f6867312 19 // Improvements over the mbed version:
mjr 77:0b96f6867312 20 //
mjr 77:0b96f6867312 21 // 1. We provide an alternative, non-glitching version of write(). The mbed
mjr 77:0b96f6867312 22 // version of write(), and our default version with the same name, causes a
mjr 77:0b96f6867312 23 // glitch on every write by resetting the TPM counter, which cuts the cycle
mjr 77:0b96f6867312 24 // short and causes a momentary drop in brightness (from the short cycle)
mjr 77:0b96f6867312 25 // that's visible if an LED is connected. This is particularly noticeable
mjr 77:0b96f6867312 26 // when doing a series of rapid writes, such as when fading a light on or off.
mjr 77:0b96f6867312 27 //
mjr 77:0b96f6867312 28 // We offer a version of write() that doesn't reset the counter, avoiding the
mjr 77:0b96f6867312 29 // glitch. This version skips the counter reset that the default version does.
mjr 77:0b96f6867312 30 //
mjr 77:0b96f6867312 31 // But this must be used with caution, because there's a whole separate
mjr 77:0b96f6867312 32 // problem if you don't reset the counter, which is why the mbed library
mjr 77:0b96f6867312 33 // does this by default. The KL25Z hardware only allows the value register
mjr 77:0b96f6867312 34 // to be written once per PWM cycle; if it's written more than once, the
mjr 77:0b96f6867312 35 // second and subsequent writes are simply ignored, so those updates will
mjr 77:0b96f6867312 36 // be forever lost. The counter reset, in addition to casuing the glitch,
mjr 77:0b96f6867312 37 // resets the cycle and thus avoids the one-write-per-cycle limitation.
mjr 77:0b96f6867312 38 // Callers using the non-glitchy version must take care to time writes so
mjr 77:0b96f6867312 39 // that there's only one per PWM period. Or, alternatively, they can just
mjr 77:0b96f6867312 40 // be sure to repeat updates periodically to ensure that the last update is
mjr 77:0b96f6867312 41 // eventually applied.
mjr 77:0b96f6867312 42 //
mjr 77:0b96f6867312 43 // 2. We optimize the TPM clock pre-scaler to maximize the precision of the
mjr 77:0b96f6867312 44 // output period, to get as close as possible to the requested period. The
mjr 77:0b96f6867312 45 // base mbed code uses a fixed pre-scaler setting with a fixed 750kHz update
mjr 77:0b96f6867312 46 // frequency, which means the period can be set in 1.333us increments. The
mjr 77:0b96f6867312 47 // hardware is capable of increments as small as .02us. The tradeoff is that
mjr 77:0b96f6867312 48 // the higher precision modes with smaller increments only allow for limited
mjr 77:0b96f6867312 49 // total period lengths, since the cycle counter is 16 bits: the maximum
mjr 77:0b96f6867312 50 // period at a given clock increment is 65535 times the increment. So the
mjr 77:0b96f6867312 51 // mbed default of 1.333us increments allows for periods of up to 87ms with
mjr 77:0b96f6867312 52 // 1.333us precision, whereas the maximum precision of .02us increments only
mjr 77:0b96f6867312 53 // allows for a maximum period of 1.36ms.
mjr 77:0b96f6867312 54 //
mjr 77:0b96f6867312 55 // To deal with this tradeoff, we choose the scaling factor each time the
mjr 77:0b96f6867312 56 // period is changed, using the highest precision (smallest time increment,
mjr 77:0b96f6867312 57 // or lowest pre-scaling clock divider) available for the requested period.
mjr 77:0b96f6867312 58 //
mjr 77:0b96f6867312 59 // Similar variable pre-scaling functionality is available with the FastPWM
mjr 77:0b96f6867312 60 // class.
mjr 77:0b96f6867312 61 //
mjr 77:0b96f6867312 62 // 3. We properly handle the shared clock in the TPM units. The mbed library
mjr 77:0b96f6867312 63 // doesn't, nor does FastPWM.
mjr 77:0b96f6867312 64 //
mjr 77:0b96f6867312 65 // The period/frequency of a PWM channel on the KL25Z is a function of the
mjr 77:0b96f6867312 66 // TPM unit containing the channel, NOT of the channel itself. A channel's
mjr 77:0b96f6867312 67 // frequency CANNOT be set independently; it can only set for the entire
mjr 77:0b96f6867312 68 // group of channels controlled through the same TPM unit as the target
mjr 77:0b96f6867312 69 // channel.
mjr 77:0b96f6867312 70 //
mjr 77:0b96f6867312 71 // The mbed library and FastPWM library pretend that the period can be set
mjr 77:0b96f6867312 72 // per channel. This is misleading and bug-prone, since an application that
mjr 77:0b96f6867312 73 // takes the API at its word and sets a channel's frequency on the fly won't
mjr 77:0b96f6867312 74 // necessarily realize that it just changed the frequency for all of the other
mjr 77:0b96f6867312 75 // channels on the same TPM. What's more, the change in TPM period will
mjr 77:0b96f6867312 76 // effectively change the duty cycle for all channels attached to the PWM,
mjr 77:0b96f6867312 77 // since it'll update the counter modulus, so all channels on the same TPM
mjr 77:0b96f6867312 78 // have to have their duty cycles reset after any frequency change.
mjr 77:0b96f6867312 79 //
mjr 77:0b96f6867312 80 // This implementation changes the API design to better reflect reality. We
mjr 77:0b96f6867312 81 // expose a separate object representing the TPM unit for a channel, and we
mjr 77:0b96f6867312 82 // put the period update function on the TPM unit object rather than on the
mjr 77:0b96f6867312 83 // channel. We also automatically update the duty cycle variable for all
mjr 77:0b96f6867312 84 // channels on a TPM when updating the frequency, to maintain the original
mjr 77:0b96f6867312 85 // duty cycle (or as close as possible, after rounding error).
mjr 77:0b96f6867312 86 //
mjr 77:0b96f6867312 87 // Applications that need to control the duty cycle on more than one channel
mjr 77:0b96f6867312 88 // must take care to ensure that the separately controlled channels are on
mjr 77:0b96f6867312 89 // separate TPM units. The KL25Z offers three physical TPM units, so there
mjr 77:0b96f6867312 90 // can be up to three independently controlled periods. The KL25Z has 10
mjr 77:0b96f6867312 91 // channels in total (6 on unit 0, 2 on unit 1, 2 on unit 2), so the remaining
mjr 77:0b96f6867312 92 // 7 channels have to share their periods with their TPM unit-mates.
mjr 100:1ff35c07217c 93 //
mjr 77:0b96f6867312 94
mjr 77:0b96f6867312 95
mjr 77:0b96f6867312 96 #ifndef _NEWPWMOUT_H_
mjr 77:0b96f6867312 97 #define _NEWPWMOUT_H_
mjr 77:0b96f6867312 98
mjr 77:0b96f6867312 99 #include <mbed.h>
mjr 77:0b96f6867312 100 #include <pinmap.h>
mjr 77:0b96f6867312 101 #include <PeripheralPins.h>
mjr 77:0b96f6867312 102 #include <clk_freqs.h>
mjr 77:0b96f6867312 103
mjr 77:0b96f6867312 104 // TPM Unit. This corresponds to one TPM unit in the hardware. Each
mjr 77:0b96f6867312 105 // unit controls 6 channels; a channel corresponds to one output pin.
mjr 77:0b96f6867312 106 // A unit contains the clock input, pre-scaler, counter, and counter
mjr 77:0b96f6867312 107 // modulus; these are shared among all 6 channels in the unit, and
mjr 77:0b96f6867312 108 // together determine the cycle time (period) of all channels in the
mjr 77:0b96f6867312 109 // unit. The period of a single channel can't be set independently;
mjr 77:0b96f6867312 110 // a channel takes its period from its unit.
mjr 77:0b96f6867312 111 //
mjr 77:0b96f6867312 112 // Since the KL25Z hardware has a fixed set of 3 TPM units, we have
mjr 77:0b96f6867312 113 // a fixed array of 3 of these objects.
mjr 94:0476b3e2b996 114 //
mjr 77:0b96f6867312 115 class NewPwmUnit
mjr 77:0b96f6867312 116 {
mjr 77:0b96f6867312 117 public:
mjr 77:0b96f6867312 118 NewPwmUnit()
mjr 77:0b96f6867312 119 {
mjr 77:0b96f6867312 120 // figure our unit number from the singleton array position
mjr 77:0b96f6867312 121 int tpm_n = this - unit;
mjr 77:0b96f6867312 122
mjr 77:0b96f6867312 123 // start with all channels disabled
mjr 77:0b96f6867312 124 activeChannels = 0;
mjr 77:0b96f6867312 125
mjr 77:0b96f6867312 126 // get our TPM unit hardware register base
mjr 77:0b96f6867312 127 tpm = (TPM_Type *)(TPM0_BASE + 0x1000*tpm_n);
mjr 77:0b96f6867312 128
mjr 77:0b96f6867312 129 // Determine which clock input we're using. Save the clock
mjr 77:0b96f6867312 130 // frequency for later use when setting the PWM period, and
mjr 77:0b96f6867312 131 // set up the SIM control register for the appropriate clock
mjr 77:0b96f6867312 132 // input. This setting is global, so we really only need to
mjr 77:0b96f6867312 133 // do it once for all three units, but it'll be the same every
mjr 77:0b96f6867312 134 // time so it won't hurt (except for a little redundancy) to
mjr 77:0b96f6867312 135 // do it again on each unit constructor.
mjr 77:0b96f6867312 136 if (mcgpllfll_frequency()) {
mjr 77:0b96f6867312 137 SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK
mjr 77:0b96f6867312 138 sysClock = mcgpllfll_frequency();
mjr 77:0b96f6867312 139 } else {
mjr 77:0b96f6867312 140 SIM->SOPT2 |= SIM_SOPT2_TPMSRC(2); // Clock source: ExtOsc
mjr 77:0b96f6867312 141 sysClock = extosc_frequency();
mjr 77:0b96f6867312 142 }
mjr 77:0b96f6867312 143 }
mjr 77:0b96f6867312 144
mjr 94:0476b3e2b996 145 // Default PWM period, in seconds
mjr 94:0476b3e2b996 146 static float defaultPeriod;
mjr 94:0476b3e2b996 147
mjr 77:0b96f6867312 148 // enable a channel
mjr 77:0b96f6867312 149 void enableChannel(int ch)
mjr 77:0b96f6867312 150 {
mjr 77:0b96f6867312 151 // if this is the first channel we're enabling, enable the
mjr 77:0b96f6867312 152 // unit clock gate
mjr 77:0b96f6867312 153 if (activeChannels == 0)
mjr 77:0b96f6867312 154 {
mjr 77:0b96f6867312 155 // enable the clock gate on the TPM unit
mjr 77:0b96f6867312 156 int tpm_n = this - unit;
mjr 77:0b96f6867312 157 SIM->SCGC6 |= 1 << (SIM_SCGC6_TPM0_SHIFT + tpm_n);
mjr 77:0b96f6867312 158
mjr 94:0476b3e2b996 159 // set the default PWM frequency (period)
mjr 94:0476b3e2b996 160 period(defaultPeriod);
mjr 77:0b96f6867312 161 }
mjr 77:0b96f6867312 162
mjr 77:0b96f6867312 163 // add the channel bit to our collection
mjr 77:0b96f6867312 164 activeChannels |= (1 << ch);
mjr 77:0b96f6867312 165 }
mjr 77:0b96f6867312 166
mjr 77:0b96f6867312 167 // Set the period for the unit. This updates all channels associated
mjr 77:0b96f6867312 168 // with the unit so that their duty cycle is scaled properly to the
mjr 77:0b96f6867312 169 // period counter.
mjr 77:0b96f6867312 170 void period(float seconds)
mjr 77:0b96f6867312 171 {
mjr 77:0b96f6867312 172 // First check to see if we actually need to change anything. If
mjr 77:0b96f6867312 173 // the requested period already matches the current period, there's
mjr 77:0b96f6867312 174 // nothing to do. This will avoid unnecessarily resetting any
mjr 77:0b96f6867312 175 // running cycles, which could cause visible flicker.
mjr 77:0b96f6867312 176 uint32_t freq = sysClock >> (tpm->SC & TPM_SC_PS_MASK);
mjr 77:0b96f6867312 177 uint32_t oldMod = tpm->MOD;
mjr 77:0b96f6867312 178 uint32_t newMod = uint32_t(seconds*freq) - 1;
mjr 77:0b96f6867312 179 if (newMod == oldMod && (tpm->SC & TPM_SC_CMOD_MASK) == TPM_SC_CMOD(1))
mjr 77:0b96f6867312 180 return;
mjr 77:0b96f6867312 181
mjr 77:0b96f6867312 182 // Figure the minimum pre-scaler needed to allow this period. The
mjr 77:0b96f6867312 183 // unit counter is 16 bits, so the maximum cycle length is 65535
mjr 77:0b96f6867312 184 // ticks. One tick is the system clock tick time multiplied by
mjr 77:0b96f6867312 185 // the pre-scaler. The scaler comes in powers of two from 1 to 128.
mjr 77:0b96f6867312 186
mjr 77:0b96f6867312 187 // start at scaler=0 -> divide by 1
mjr 77:0b96f6867312 188 int ps = 0;
mjr 77:0b96f6867312 189 freq = sysClock;
mjr 77:0b96f6867312 190
mjr 77:0b96f6867312 191 // at this rate, the maximum period is 65535 ticks of the system clock
mjr 77:0b96f6867312 192 float pmax = 65535.0f/sysClock;
mjr 77:0b96f6867312 193
mjr 77:0b96f6867312 194 // Now figure how much we have to divide the system clock: each
mjr 77:0b96f6867312 195 // scaler step divides by another factor of 2, which doubles the
mjr 77:0b96f6867312 196 // maximum period. Keep going while the maximum period is below
mjr 77:0b96f6867312 197 // the desired period, but stop if we reach the maximum per-scale
mjr 77:0b96f6867312 198 // value of divide-by-128.
mjr 77:0b96f6867312 199 while (ps < 7 && pmax < seconds)
mjr 77:0b96f6867312 200 {
mjr 77:0b96f6867312 201 ++ps;
mjr 77:0b96f6867312 202 pmax *= 2.0f;
mjr 77:0b96f6867312 203 freq /= 2;
mjr 77:0b96f6867312 204 }
mjr 77:0b96f6867312 205
mjr 77:0b96f6867312 206 // Before writing the prescaler bits, we have to disable the
mjr 77:0b96f6867312 207 // clock (CMOD) bits in the status & control register. These
mjr 77:0b96f6867312 208 // bits might take a while to update, so spin until they clear.
mjr 77:0b96f6867312 209 while ((tpm->SC & 0x1F) != 0)
mjr 77:0b96f6867312 210 tpm->SC &= ~0x1F;
mjr 77:0b96f6867312 211
mjr 77:0b96f6867312 212 // Reset the CnV (trigger value) for all active channels to
mjr 77:0b96f6867312 213 // maintain each channel's current duty cycle.
mjr 77:0b96f6867312 214 for (int i = 0 ; i < 6 ; ++i)
mjr 77:0b96f6867312 215 {
mjr 77:0b96f6867312 216 // if this channel is active, reset it
mjr 77:0b96f6867312 217 if ((activeChannels & (1 << i)) != 0)
mjr 77:0b96f6867312 218 {
mjr 77:0b96f6867312 219 // figure the old duty cycle, based on the current
mjr 77:0b96f6867312 220 // channel value and the old modulus
mjr 77:0b96f6867312 221 uint32_t oldCnV = tpm->CONTROLS[i].CnV;
mjr 77:0b96f6867312 222 float dc = float(oldCnV)/float(oldMod + 1);
mjr 77:0b96f6867312 223 if (dc > 1.0f) dc = 1.0f;
mjr 77:0b96f6867312 224
mjr 77:0b96f6867312 225 // figure the new value that maintains the same duty
mjr 77:0b96f6867312 226 // cycle with the new modulus
mjr 77:0b96f6867312 227 uint32_t newCnV = uint32_t(dc*(newMod + 1));
mjr 77:0b96f6867312 228
mjr 77:0b96f6867312 229 // if it changed, write the new value
mjr 77:0b96f6867312 230 if (newCnV != oldCnV)
mjr 77:0b96f6867312 231 tpm->CONTROLS[i].CnV = newCnV;
mjr 77:0b96f6867312 232 }
mjr 77:0b96f6867312 233 }
mjr 77:0b96f6867312 234
mjr 77:0b96f6867312 235 // reset the unit counter register
mjr 77:0b96f6867312 236 tpm->CNT = 0;
mjr 77:0b96f6867312 237
mjr 77:0b96f6867312 238 // set the new clock period
mjr 77:0b96f6867312 239 tpm->MOD = newMod = uint32_t(seconds*freq) - 1;
mjr 77:0b96f6867312 240
mjr 77:0b96f6867312 241 // set the new pre-scaler bits and set clock mode 01 (enabled,
mjr 77:0b96f6867312 242 // increments on every LPTPM clock)
mjr 77:0b96f6867312 243 tpm->SC = TPM_SC_CMOD(1) | TPM_SC_PS(ps);
mjr 77:0b96f6867312 244 }
mjr 77:0b96f6867312 245
mjr 79:682ae3171a08 246 // wait for the end of the current cycle
mjr 109:310ac82cbbee 247 inline void waitEndCycle()
mjr 79:682ae3171a08 248 {
mjr 104:6e06e0f4b476 249 // clear the overflow flag (note the usual KL25Z convention for
mjr 104:6e06e0f4b476 250 // hardware status registers like this: writing '1' clears the bit)
mjr 79:682ae3171a08 251 tpm->SC |= TPM_SC_TOF_MASK;
mjr 79:682ae3171a08 252
mjr 79:682ae3171a08 253 // The flag will be set at the next overflow
mjr 79:682ae3171a08 254 while (!(tpm->SC & TPM_SC_TOF_MASK)) ;
mjr 79:682ae3171a08 255 }
mjr 79:682ae3171a08 256
mjr 77:0b96f6867312 257 // hardware register base
mjr 77:0b96f6867312 258 TPM_Type *tpm;
mjr 77:0b96f6867312 259
mjr 77:0b96f6867312 260 // Channels that are active in this unit, as a bit mask:
mjr 77:0b96f6867312 261 // 1<<n is our channel n.
mjr 77:0b96f6867312 262 uint8_t activeChannels;
mjr 77:0b96f6867312 263
mjr 77:0b96f6867312 264 // fixed array of unit singletons
mjr 77:0b96f6867312 265 static NewPwmUnit unit[3];
mjr 77:0b96f6867312 266
mjr 77:0b96f6867312 267 // system clock frequency
mjr 77:0b96f6867312 268 static uint32_t sysClock;
mjr 77:0b96f6867312 269 };
mjr 77:0b96f6867312 270
mjr 77:0b96f6867312 271
mjr 77:0b96f6867312 272 class NewPwmOut
mjr 77:0b96f6867312 273 {
mjr 77:0b96f6867312 274 public:
mjr 104:6e06e0f4b476 275 // Set up the output pin.
mjr 104:6e06e0f4b476 276 //
mjr 104:6e06e0f4b476 277 // 'invertedCycle' means that the output is OFF during the first phase
mjr 104:6e06e0f4b476 278 // of each PWM period (the part between the start of the period and the
mjr 104:6e06e0f4b476 279 // duty cycle percentage) and ON during the second phase. This makes
mjr 104:6e06e0f4b476 280 // the duty cycle setting in the write() calls the OFF duty cycle. For
mjr 104:6e06e0f4b476 281 // example, with an inverted cycle, write(.1) means that the output will
mjr 104:6e06e0f4b476 282 // be OFF 10% of the time and ON 90% of the time. This is primarily
mjr 104:6e06e0f4b476 283 // for complex timing situations where the caller has to be able to
mjr 104:6e06e0f4b476 284 // coordinate the alignment of up/down transitions on the output; in
mjr 109:310ac82cbbee 285 // particular, it allows the caller to use the waitEndCycle() to sync
mjr 109:310ac82cbbee 286 // with the falling edge on the output.
mjr 100:1ff35c07217c 287 NewPwmOut(PinName pin, bool invertedCycle = false)
mjr 77:0b96f6867312 288 {
mjr 77:0b96f6867312 289 // determine the TPM unit number and channel
mjr 77:0b96f6867312 290 PWMName pwm = (PWMName)pinmap_peripheral(pin, PinMap_PWM);
mjr 77:0b96f6867312 291 MBED_ASSERT(pwm != (PWMName)NC);
mjr 77:0b96f6867312 292 unsigned int port = (unsigned int)pin >> PORT_SHIFT;
mjr 77:0b96f6867312 293
mjr 77:0b96f6867312 294 // decode the port ID into the TPM unit and channel number
mjr 77:0b96f6867312 295 tpm_n = (pwm >> TPM_SHIFT);
mjr 77:0b96f6867312 296 ch_n = (pwm & 0xFF);
mjr 77:0b96f6867312 297
mjr 77:0b96f6867312 298 // enable the clock gate on the port (PTx)
mjr 77:0b96f6867312 299 SIM->SCGC5 |= 1 << (SIM_SCGC5_PORTA_SHIFT + port);
mjr 77:0b96f6867312 300
mjr 77:0b96f6867312 301 // enable the channel on the TPM unit
mjr 77:0b96f6867312 302 NewPwmUnit::unit[tpm_n].enableChannel(ch_n);
mjr 100:1ff35c07217c 303
mjr 100:1ff35c07217c 304 // Figure the ELSB:ELSA mode according to whether we want the normal
mjr 100:1ff35c07217c 305 // "high-true" cycle (high after reset, low after match) or the
mjr 100:1ff35c07217c 306 // inverted "low-true" cycle (low after reset, high after match)
mjr 100:1ff35c07217c 307 uint32_t els_bits = invertedCycle ? TPM_CnSC_ELSA_MASK : TPM_CnSC_ELSB_MASK;
mjr 77:0b96f6867312 308
mjr 77:0b96f6867312 309 // set the channel control register:
mjr 77:0b96f6867312 310 // CHIE = 0 = interrupts disabled
mjr 100:1ff35c07217c 311 // MSB:MBA:ELSB:ELSA = 10cc = edge-aligned PWM (cc = 10 high-true, 01 low-true = inverted cycle)
mjr 77:0b96f6867312 312 // DMA = 0 = DMA off
mjr 77:0b96f6867312 313 TPM_Type *tpm = getUnit()->tpm;
mjr 100:1ff35c07217c 314 tpm->CONTROLS[ch_n].CnSC = (TPM_CnSC_MSB_MASK | els_bits);
mjr 77:0b96f6867312 315
mjr 77:0b96f6867312 316 // wire the pinout
mjr 77:0b96f6867312 317 pinmap_pinout(pin, PinMap_PWM);
mjr 77:0b96f6867312 318 }
mjr 77:0b96f6867312 319
mjr 77:0b96f6867312 320 float read()
mjr 77:0b96f6867312 321 {
mjr 77:0b96f6867312 322 TPM_Type *tpm = getUnit()->tpm;
mjr 77:0b96f6867312 323 float v = float(tpm->CONTROLS[ch_n].CnV)/float(tpm->MOD + 1);
mjr 77:0b96f6867312 324 return v > 1.0f ? 1.0f : v;
mjr 77:0b96f6867312 325 }
mjr 77:0b96f6867312 326
mjr 77:0b96f6867312 327 void write(float val)
mjr 77:0b96f6867312 328 {
mjr 77:0b96f6867312 329 // do the glitch-free write
mjr 77:0b96f6867312 330 glitchFreeWrite(val);
mjr 77:0b96f6867312 331
mjr 77:0b96f6867312 332 // Reset the counter. This is a workaround for a hardware problem
mjr 77:0b96f6867312 333 // on the KL25Z, namely that the CnV register can only be written
mjr 77:0b96f6867312 334 // once per PWM cycle. Any subsequent attempt to write it in the
mjr 77:0b96f6867312 335 // same cycle will be lost. Resetting the counter forces the end
mjr 77:0b96f6867312 336 // of the cycle and makes the register writable again. This isn't
mjr 77:0b96f6867312 337 // an ideal workaround because it causes visible brightness glitching
mjr 77:0b96f6867312 338 // if the caller writes new values repeatedly, such as when fading
mjr 77:0b96f6867312 339 // lights in or out.
mjr 77:0b96f6867312 340 TPM_Type *tpm = getUnit()->tpm;
mjr 77:0b96f6867312 341 tpm->CNT = 0;
mjr 77:0b96f6867312 342 }
mjr 77:0b96f6867312 343
mjr 77:0b96f6867312 344 // Write a new value without forcing the current PWM cycle to end.
mjr 77:0b96f6867312 345 // This results in glitch-free writing during fades or other series
mjr 77:0b96f6867312 346 // of rapid writes, BUT with the giant caveat that the caller MUST NOT
mjr 77:0b96f6867312 347 // write another value before the current PWM cycle ends. Doing so
mjr 77:0b96f6867312 348 // will cause the later write to be lost. Callers using this must
mjr 77:0b96f6867312 349 // take care, using mechanisms of their own, to limit writes to once
mjr 77:0b96f6867312 350 // per PWM cycle.
mjr 77:0b96f6867312 351 void glitchFreeWrite(float val)
mjr 77:0b96f6867312 352 {
mjr 77:0b96f6867312 353 // limit to 0..1 range
mjr 77:0b96f6867312 354 val = (val < 0.0f ? 0.0f : val > 1.0f ? 1.0f : val);
mjr 104:6e06e0f4b476 355
mjr 77:0b96f6867312 356 // Write the duty cycle register. The argument value is a duty
mjr 77:0b96f6867312 357 // cycle on a normalized 0..1 scale; for the hardware, we need to
mjr 77:0b96f6867312 358 // renormalize to the 0..MOD scale, where MOD is the cycle length
mjr 77:0b96f6867312 359 // in clock counts.
mjr 77:0b96f6867312 360 TPM_Type *tpm = getUnit()->tpm;
mjr 77:0b96f6867312 361 tpm->CONTROLS[ch_n].CnV = (uint32_t)((float)(tpm->MOD + 1) * val);
mjr 77:0b96f6867312 362 }
mjr 77:0b96f6867312 363
mjr 79:682ae3171a08 364 // Wait for the end of a cycle
mjr 79:682ae3171a08 365 void waitEndCycle() { getUnit()->waitEndCycle(); }
mjr 79:682ae3171a08 366
mjr 77:0b96f6867312 367 // Get my TPM unit object. This can be used to change the period.
mjr 100:1ff35c07217c 368 //
mjr 100:1ff35c07217c 369 // (Note that it's intentional that we make you ask for the unit to
mjr 100:1ff35c07217c 370 // modify the period. It might seem attractive to provide a convenience
mjr 100:1ff35c07217c 371 // method here that sets the period in the unit on the caller's behalf,
mjr 100:1ff35c07217c 372 // but we omit that *on purpose*, to make it explicit to people calling
mjr 100:1ff35c07217c 373 // this code that the period is an attribute of the unit, not of the
mjr 100:1ff35c07217c 374 // channel, and to make it self-documenting in all calling code that
mjr 100:1ff35c07217c 375 // this is the case. The original mbed interface makes it look like the
mjr 100:1ff35c07217c 376 // period is abstractly an attribute of the channel, allowing a naive
mjr 100:1ff35c07217c 377 // developer to believe that a channel's period can be changed in
mjr 100:1ff35c07217c 378 // isolation. In fact, changing a channel's period in the mbed API
mjr 100:1ff35c07217c 379 // has global side effects, in that it also changes the period for
mjr 100:1ff35c07217c 380 // all other channels on the same unit. Global side effects like that
mjr 100:1ff35c07217c 381 // violate the principle of encapsulation. This reflects a defect in the
mjr 100:1ff35c07217c 382 // mbed API's design, not its implementation, in that the hardware forces
mjr 100:1ff35c07217c 383 // this implementation. For this reason, we deliberately require callers
mjr 100:1ff35c07217c 384 // to spell out in the code that they're operating on the unit when
mjr 100:1ff35c07217c 385 // changing attributes that belong in fact to the unit.)
mjr 77:0b96f6867312 386 inline NewPwmUnit *getUnit() { return &NewPwmUnit::unit[tpm_n]; }
mjr 77:0b96f6867312 387
mjr 100:1ff35c07217c 388 // Get my TPM unit number and channel number
mjr 100:1ff35c07217c 389 inline int getUnitNum() const { return tpm_n; }
mjr 100:1ff35c07217c 390 inline int getChannelNum() const { return ch_n; }
mjr 100:1ff35c07217c 391
mjr 77:0b96f6867312 392 protected:
mjr 77:0b96f6867312 393 // TPM unit number and channel number
mjr 77:0b96f6867312 394 uint8_t tpm_n;
mjr 77:0b96f6867312 395 uint8_t ch_n;
mjr 77:0b96f6867312 396 };
mjr 77:0b96f6867312 397
mjr 77:0b96f6867312 398 #endif