Understanding SAMD(E) SIMPLEFOC_SAMD_PWM_RESOLUTION

Helo

Just wanna share this with future generations. At first glance, it can be quite hard to understand. Correct me if Im wrong…

The SIMPLEFOC_SAMD_PWM_RESOLUTION definition is important for understanding how the PWM pins are set.

This function in the BLDCDriver3PWM outputs a value between 0.0 and 1.0, while making sure the phase voltage does not exceed the voltage_limit.

// Set voltage to the pwm pin
void BLDCDriver3PWM::setPwm(float Ua, float Ub, float Uc) {
  
  // limit the voltage in driver
  Ua = _constrain(Ua, 0.0, voltage_limit);
  Ub = _constrain(Ub, 0.0, voltage_limit);
  Uc = _constrain(Uc, 0.0, voltage_limit);    
  // calculate duty cycle
  // limited in [0,1]
  dc_a = _constrain(Ua / voltage_power_supply, 0.0 , 1.0 );
  dc_b = _constrain(Ub / voltage_power_supply, 0.0 , 1.0 );
  dc_c = _constrain(Uc / voltage_power_supply, 0.0 , 1.0 );

  // hardware specific writing
  // hardware specific function - depending on driver and mcu
  _writeDutyCycle3PWM(dc_a, dc_b, dc_c, pwmA, pwmB, pwmC);

In Samd51_mcu.cpp there is a (half done) function which multiplies the before mentioned value with the resolution and set´s the actual PWM duty cycle (Caµnt)

void writeSAMDDutyCycle(int chaninfo, float dc) {
	uint8_t tccn = GetTCNumber(chaninfo);
	uint8_t chan = GetTCChannelNumber(chaninfo);
	if (tccn<TCC_INST_NUM) {
		Tcc* tcc = (Tcc*)GetTC(chaninfo);
		// set via CCBUF
		while ( (tcc->SYNCBUSY.vec.CC & (0x1<<chan)) > 0 );
		tcc->CCBUF[chan].reg = (uint32_t)((SIMPLEFOC_SAMD_PWM_RESOLUTION-1) * dc); // TODO pwm frequency!
//		tcc->STATUS.vec.CCBUFV |= (0x1<<chan);
//		while ( tcc->SYNCBUSY.bit.STATUS > 0 );
//		tcc->CTRLBSET.reg |= TCC_CTRLBSET_CMD(TCC_CTRLBSET_CMD_UPDATE_Val);
//		while ( tcc->SYNCBUSY.bit.CTRLB > 0 );
	}
	else {
		// Tc* tc = (Tc*)GetTC(chaninfo);
		// //tc->COUNT16.CC[chan].reg = (uint32_t)((SIMPLEFOC_SAMD_PWM_RESOLUTION-1) * dc);
		// tc->COUNT8.CC[chan].reg = (uint8_t)((SIMPLEFOC_SAMD_PWM_TC_RESOLUTION-1) * dc);
		// while ( tc->COUNT8.STATUS.bit.SYNCBUSY == 1 );
	}
}

I am curious to know, how many values are between 0.0 and 1.0? How many decimals?

Lets find out:

float target = 0;
//float target_voltage = 0.5;
void serialLoop() {
static String received_chars;

while (Serial.available()) {
  char inChar = (char) Serial.read();
  received_chars += inChar;
  if (inChar == '\n') {
    target = received_chars.toFloat();
    Serial.print("Target ="); Serial.println(target);
    received_chars = "";
  }
}
}

void loop() {

float Ua;
  float dc_a;
  Ua = _constrain(Ua, target, 1);
  dc_a = _constrain(Ua / 24, 0.0 , 1.0 );
  Serial.print("Value ="); Serial.println(dc_a);

  delay(1000);
  //motor.monitor();

}

From what I can see, the value passed on to the PWM setting function can only be 100 values.

[Edit] See below…

It doesn´t make sense to set a resolution, and then divide that resolution into 100 chunks.

Aha!

The serial monitor is hiding my digid´s

-Serial.println(1.23456, 0) gives "1" 
-Serial.println(1.23456, 2) gives "1.23" 
-Serial.println(1.23456, 4) gives "1.2346" 

secret digids

This is a more tricky question that it may first appear… it all has to do with “precision” - simplefoc uses 32 bit floats to represent its numbers and do its calculations.

32 bit single precision floats have the following structure (img borrowed from wikipedia):

So there is 1 sign bit, 8 exponent bits and 23 mantissa or fractional bits. There is an implicit 1-bit at the start of the mantissa, for 24 effective mantissa bits.

Zero is of course represented precisely, with the exponent set to 127.
The smallest number larger than 0 that can be represented is very small indeed: about 1.4x10^-45 (as a subnormal number with exponent set to 0x00).

However, the closest number lower than but not equal to 1.0 that can be represented is (if I got this correctly) 0xFFFFFF for the mantissa, with an exponent of -1, i.e. 0.5 + 0.25 + 0.125 + … + 2^-24, or 1.0 - 0.000000059604645 = 0.999999940395355 …

How many numbers are there between 0.0 and 1.0 that can be represented? It will be the values
0x00 - 0x7E for the exponent, and the values 0x000000 to 0xFFFFFF for the mantissa (but high bit is always on). So 126 x 8388607 = 1,056,964,482 discrete values, plus the 1.0 value, makes for 1,056,964,483 different representable numbers between 0 and 1…
I’m probably off by a few since there are some special representations like NaN, and zero can be represented in multiple ways.

Sorry for literally answering your question, but it piqued my interest. Its actually a good question for a CS exam :smiley:

The limit in terms of different PWM outputs is much lower, by the way. It is hardware dependent.
First off, it depends on the counter limits of the counters being used to control the PWM. Typical is 16 bit counters. But depending on the MCU speed (more specifically the clock driving the counter) you may not be able to use the full scale of the timer and still have reasonable PWM frequencies.
E.g. a 32MHz clock driving a timer can count to 16000 in 500us, for a 2KHz PWM rate - probably too slow. So you would limit the timer range to, for example, 1600 - for a 20KHz PWM rate…

So now you have effectively 1600 different possible levels in the PWM signal.

If you further use the driver.voltage_limit to set the limit below the PSU voltage, then you are limiting the PWM to less than the full scale - so if you set voltage_limit to 0.1V on a 10V power-supply, you’re effectively using only 1% of the possible range, and in the example above you would now have only 16 different PWM levels that get output…