samd/machine_pwm: Serialize fast update of PWM settings.
Any update of freq or duty_cycle requires the previous PWM cycle to be finished. Otherwise the new settings are not accepted. Other changes in this commit: - Report the set duty cycles even when the PWM is not yet started. - pwm.freq(0) stops the pwm device, instead of raising an expception. - Clear the duty cycle value cache on soft reset.
This commit is contained in:
parent
ac1e31267b
commit
474233c250
|
@ -25,6 +25,7 @@
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
#include "py/mphal.h"
|
#include "py/mphal.h"
|
||||||
#include "modmachine.h"
|
#include "modmachine.h"
|
||||||
|
@ -46,6 +47,8 @@ typedef struct _machine_pwm_obj_t {
|
||||||
uint8_t output;
|
uint8_t output;
|
||||||
uint16_t prescaler;
|
uint16_t prescaler;
|
||||||
uint32_t period; // full period count ticks
|
uint32_t period; // full period count ticks
|
||||||
|
uint32_t duty_ns; // just for reporting
|
||||||
|
uint16_t duty_u16; // just for reporting
|
||||||
} machine_pwm_obj_t;
|
} machine_pwm_obj_t;
|
||||||
|
|
||||||
#define PWM_NOT_INIT (0)
|
#define PWM_NOT_INIT (0)
|
||||||
|
@ -53,6 +56,7 @@ typedef struct _machine_pwm_obj_t {
|
||||||
#define PWM_TCC_ENABLED (2)
|
#define PWM_TCC_ENABLED (2)
|
||||||
#define PWM_MASTER_CLK (get_peripheral_freq())
|
#define PWM_MASTER_CLK (get_peripheral_freq())
|
||||||
#define PWM_FULL_SCALE (65536)
|
#define PWM_FULL_SCALE (65536)
|
||||||
|
#define PWM_UPDATE_TIMEOUT (2000)
|
||||||
|
|
||||||
static Tcc *tcc_instance[] = TCC_INSTS;
|
static Tcc *tcc_instance[] = TCC_INSTS;
|
||||||
|
|
||||||
|
@ -148,6 +152,7 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args
|
||||||
self->output = config.device_channel & 0x0f;
|
self->output = config.device_channel & 0x0f;
|
||||||
self->prescaler = 1;
|
self->prescaler = 1;
|
||||||
self->period = 1; // Use an invalid but safe value
|
self->period = 1; // Use an invalid but safe value
|
||||||
|
self->duty_u16 = self->duty_ns = 0;
|
||||||
put_duty_value(self->device, self->channel, 0);
|
put_duty_value(self->device, self->channel, 0);
|
||||||
|
|
||||||
Tcc *tcc = self->instance;
|
Tcc *tcc = self->instance;
|
||||||
|
@ -240,6 +245,7 @@ void pwm_deinit_all(void) {
|
||||||
device_status[i] = PWM_NOT_INIT;
|
device_status[i] = PWM_NOT_INIT;
|
||||||
duty_type_flags[i] = 0;
|
duty_type_flags[i] = 0;
|
||||||
output_active[i] = 0;
|
output_active[i] = 0;
|
||||||
|
memset(pwm_duty_values, 0, sizeof(pwm_duty_values));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,8 +262,25 @@ STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC void wait_for_register_update(Tcc *tcc) {
|
||||||
|
// Wait for a period's end (may be long) to have the change settled
|
||||||
|
// Each loop cycle takes at least 1 ms, giving an implicit timeout.
|
||||||
|
for (int i = 0; i < PWM_UPDATE_TIMEOUT; i++) {
|
||||||
|
if (tcc->INTFLAG.reg & TCC_INTFLAG_OVF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MICROPY_EVENT_POLL_HOOK
|
||||||
|
}
|
||||||
|
// Clear the flag, telling that a cycle has been handled.
|
||||||
|
tcc->INTFLAG.reg = TCC_INTFLAG_OVF;
|
||||||
|
}
|
||||||
|
|
||||||
STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
|
STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) {
|
||||||
return MP_OBJ_NEW_SMALL_INT(PWM_MASTER_CLK / self->prescaler / self->period);
|
if (self->instance->CTRLA.reg & TCC_CTRLA_ENABLE) {
|
||||||
|
return MP_OBJ_NEW_SMALL_INT(PWM_MASTER_CLK / self->prescaler / self->period);
|
||||||
|
} else {
|
||||||
|
return MP_OBJ_NEW_SMALL_INT(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
|
STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
|
||||||
|
@ -267,7 +290,8 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
|
||||||
|
|
||||||
Tcc *tcc = self->instance;
|
Tcc *tcc = self->instance;
|
||||||
if (freq < 1) {
|
if (freq < 1) {
|
||||||
mp_raise_ValueError(MP_ERROR_TEXT("invalid freq"));
|
pwm_stop_device(self->device);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the actual settings of prescaler & period from the unit
|
// Get the actual settings of prescaler & period from the unit
|
||||||
|
@ -288,6 +312,11 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
|
||||||
if (period < 2) {
|
if (period < 2) {
|
||||||
mp_raise_ValueError(MP_ERROR_TEXT("freq too large"));
|
mp_raise_ValueError(MP_ERROR_TEXT("freq too large"));
|
||||||
}
|
}
|
||||||
|
// If the PWM is running, ensure that a cycle has passed since the
|
||||||
|
// previous setting before setting a new frequency/duty value
|
||||||
|
if (tcc->CTRLA.reg & TCC_CTRLA_ENABLE) {
|
||||||
|
wait_for_register_update(tcc);
|
||||||
|
}
|
||||||
// Check, if the prescaler has to be changed and stop the device if so.
|
// Check, if the prescaler has to be changed and stop the device if so.
|
||||||
if (index != tcc->CTRLA.bit.PRESCALER) {
|
if (index != tcc->CTRLA.bit.PRESCALER) {
|
||||||
// stop the device
|
// stop the device
|
||||||
|
@ -339,25 +368,37 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) {
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) {
|
STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) {
|
||||||
return MP_OBJ_NEW_SMALL_INT(self->instance->CC[self->channel].reg * PWM_FULL_SCALE / (self->instance->PER.reg + 1));
|
return MP_OBJ_NEW_SMALL_INT(self->duty_u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) {
|
STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) {
|
||||||
|
// Remember the values for update & reporting
|
||||||
put_duty_value(self->device, self->channel, duty_u16);
|
put_duty_value(self->device, self->channel, duty_u16);
|
||||||
|
self->duty_u16 = duty_u16;
|
||||||
|
self->duty_ns = 0;
|
||||||
// If the device is enabled, than the period is set and we get a reasonable value for
|
// If the device is enabled, than the period is set and we get a reasonable value for
|
||||||
// the duty cycle, set to the CCBUF register. Otherwise, PWM does not start.
|
// the duty cycle, set to the CCBUF register. Otherwise, PWM does not start.
|
||||||
if (self->instance->CTRLA.reg & TCC_CTRLA_ENABLE) {
|
if (self->instance->CTRLA.reg & TCC_CTRLA_ENABLE) {
|
||||||
self->instance->CCBUF[self->channel].reg = (uint64_t)duty_u16 * (self->instance->PER.reg + 1) / PWM_FULL_SCALE;
|
// Ensure that a cycle has passed updating the registers
|
||||||
|
// since the previous setting before setting a new duty value
|
||||||
|
wait_for_register_update(self->instance);
|
||||||
|
self->instance->CCBUF[self->channel].reg = (uint64_t)duty_u16 * (self->period) / PWM_FULL_SCALE;
|
||||||
}
|
}
|
||||||
duty_type_flags[self->device] |= 1 << self->channel;
|
duty_type_flags[self->device] |= 1 << self->channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) {
|
STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) {
|
||||||
return MP_OBJ_NEW_SMALL_INT(1000000000ULL * self->instance->CC[self->channel].reg * self->prescaler / PWM_MASTER_CLK);
|
return MP_OBJ_NEW_SMALL_INT(self->duty_ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) {
|
STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) {
|
||||||
|
// Remember the values for update & reporting
|
||||||
put_duty_value(self->device, self->channel, duty_ns);
|
put_duty_value(self->device, self->channel, duty_ns);
|
||||||
|
self->duty_ns = duty_ns;
|
||||||
|
self->duty_u16 = 0;
|
||||||
|
// Ensure that a cycle has passed updating the registers
|
||||||
|
// since the previous setting before setting a new duty value
|
||||||
|
wait_for_register_update(self->instance);
|
||||||
self->instance->CCBUF[self->channel].reg = (uint64_t)duty_ns * PWM_MASTER_CLK / self->prescaler / 1000000000ULL;
|
self->instance->CCBUF[self->channel].reg = (uint64_t)duty_ns * PWM_MASTER_CLK / self->prescaler / 1000000000ULL;
|
||||||
duty_type_flags[self->device] &= ~(1 << self->channel);
|
duty_type_flags[self->device] &= ~(1 << self->channel);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue