/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2020-2021 Damien P. George * Copyright (c) 2021 Robert Hammelrath * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ // This file is never compiled standalone, it's included directly from // extmod/machine_pwm.c via MICROPY_PY_MACHINE_PWM_INCLUDEFILE. #include "py/mphal.h" #include "pin.h" #include "fsl_clock.h" #include "fsl_iomuxc.h" #include "hal/pwm_backport.h" #define PWM_MIDDLE (0) #define PWM_BEGIN (1) #define PWM_END (2) #define PWM_CHANNEL1 (1) #define PWM_CHANNEL2 (2) #define VALUE_NOT_SET (-1) typedef struct _machine_pwm_obj_t { mp_obj_base_t base; PWM_Type *instance; bool is_flexpwm; uint8_t complementary; uint8_t module; uint8_t submodule; uint8_t channel1; uint8_t channel2; uint8_t invert; bool sync; int32_t freq; int16_t prescale; int32_t duty_u16; int32_t duty_ns; uint16_t center; uint32_t deadtime; bool output_enable_1; bool output_enable_2; uint8_t xor; bool is_init; } machine_pwm_obj_t; static char channel_char[] = {'B', 'A', 'X' }; static char *ERRMSG_FREQ = "PWM frequency too low"; static char *ERRMSG_INIT = "PWM set-up failed"; static char *ERRMSG_VALUE = "value larger than period"; STATIC void mp_machine_pwm_start(machine_pwm_obj_t *self); STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->is_flexpwm) { mp_printf(print, "module, self->submodule); if (self->complementary) { mp_printf(print, "channel=%c/%c", channel_char[self->channel1], channel_char[self->channel2]); } else { mp_printf(print, "channel=%c", channel_char[self->channel1]); } if (self->duty_ns != VALUE_NOT_SET) { mp_printf(print, " duty_ns=%d", self->duty_ns); } else { mp_printf(print, " duty_u16=%d", self->duty_u16); } mp_printf(print, " freq=%d center=%u, deadtime=%u, sync=%u>", self->freq, self->center, self->deadtime, self->sync); #ifdef FSL_FEATURE_SOC_TMR_COUNT } else { mp_printf(print, "module, self->channel1, self->freq); if (self->duty_ns != VALUE_NOT_SET) { mp_printf(print, "duty_ns=%d>", self->duty_ns); } else { mp_printf(print, "duty_u16=%d>", self->duty_u16); } #endif } } // Utility functions for decoding and converting // STATIC uint32_t duty_ns_to_duty_u16(uint32_t freq, uint32_t duty_ns) { uint64_t duty = (uint64_t)duty_ns * freq * PWM_FULL_SCALE / 1000000000ULL; if (duty > PWM_FULL_SCALE) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_VALUE)); } return (uint32_t)duty; } STATIC uint8_t module_decode(char channel) { switch (channel) { case '0': return kPWM_Module_0; case '1': return kPWM_Module_1; case '2': return kPWM_Module_2; case '3': return kPWM_Module_3; default: return kPWM_Module_1; } } STATIC uint8_t channel_decode(char channel) { switch (channel) { case 'A': return kPWM_PwmA; case 'B': return kPWM_PwmB; case 'X': return kPWM_PwmX; default: return kPWM_PwmA; } } // decode the AF objects module and Port number. Returns NULL if it is not a FLEXPWM object STATIC const machine_pin_af_obj_t *af_name_decode_flexpwm(const machine_pin_af_obj_t *af_obj, uint8_t *module, uint8_t *submodule, uint8_t *channel) { const char *str; size_t len; str = (char *)qstr_data(af_obj->name, &len); // test for the name starting with FLEXPWM if (len < 15 || strncmp(str, "FLEXPWM", 7) != 0) { return NULL; } // Get module, submodule and channel from the name, e.g. FLEXPWM1_PWM0_A *module = str[7] - '0'; *submodule = module_decode(str[12]); *channel = channel_decode(str[14]); return af_obj; } #ifdef FSL_FEATURE_SOC_TMR_COUNT STATIC uint8_t qtmr_decode(char channel) { switch (channel) { case '0': return kQTMR_Channel_0; case '1': return kQTMR_Channel_1; case '2': return kQTMR_Channel_2; case '3': return kQTMR_Channel_3; default: return kPWM_Module_1; } } // decode the AF objects module and Port number. Returns NULL if it is not a QTMR object STATIC const machine_pin_af_obj_t *af_name_decode_qtmr(const machine_pin_af_obj_t *af_obj, uint8_t *module, uint8_t *channel) { const char *str; size_t len; str = (char *)qstr_data(af_obj->name, &len); // test for the name starting with TMR if (len < 11 || strncmp(str, "TMR", 3) != 0) { return NULL; } // Get module, submodule and channel from the name, e.g. FLEXPWM1_PWM0_A *module = str[3] - '0'; *channel = qtmr_decode(str[10]); return af_obj; } #endif STATIC bool is_board_pin(const machine_pin_obj_t *pin) { const mp_map_t *board_map = &machine_pin_board_pins_locals_dict.map; for (uint i = 0; i < board_map->alloc; i++) { if (pin == MP_OBJ_TO_PTR(board_map->table[i].value)) { return true; } } return false; } // Functions for configuring the PWM Device // STATIC int calc_prescaler(uint32_t clock, uint32_t freq) { float temp = (float)clock / (float)PWM_FULL_SCALE / (float)freq; for (int prescale = 0; prescale < 8; prescale++, temp /= 2) { if (temp <= 1) { return prescale; } } // Frequency too low, cannot scale down. return -1; } STATIC void configure_flexpwm(machine_pwm_obj_t *self) { pwm_signal_param_u16_t pwmSignal; // Initialize PWM module. #if defined(MIMXRT117x_SERIES) uint32_t pwmSourceClockInHz = CLOCK_GetRootClockFreq(kCLOCK_Root_Bus); #else uint32_t pwmSourceClockInHz = CLOCK_GetFreq(kCLOCK_IpgClk); #endif int prescale = calc_prescaler(pwmSourceClockInHz, self->freq); if (prescale < 0) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_FREQ)); } if (self->prescale != prescale || self->is_init == false) { pwm_config_t pwmConfig; PWM_GetDefaultConfig(&pwmConfig); self->prescale = prescale; pwmConfig.prescale = prescale; pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle; if (self->complementary) { pwmConfig.pairOperation = self->channel1 == kPWM_PwmA ? kPWM_ComplementaryPwmA : kPWM_ComplementaryPwmB; } else { pwmConfig.pairOperation = kPWM_Independent; } pwmConfig.clockSource = kPWM_BusClock; pwmConfig.enableWait = false; pwmConfig.initializationControl = self->sync ? kPWM_Initialize_MasterSync : kPWM_Initialize_LocalSync; if (PWM_Init(self->instance, self->submodule, &pwmConfig) == kStatus_Fail) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_INIT)); } } // Disable the fault detect function to avoid using the xbara PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel1, kPWM_faultchannel_0, 0); PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel1, kPWM_faultchannel_1, 0); if (self->complementary) { PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel2, kPWM_faultchannel_0, 0); PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel2, kPWM_faultchannel_1, 0); } if (self->channel1 != kPWM_PwmX) { // Only for A/B channels // Initialize the channel parameters pwmSignal.pwmChannel = self->channel1; pwmSignal.level = (self->invert & PWM_CHANNEL1) ? kPWM_LowTrue : kPWM_HighTrue; pwmSignal.dutyCycle_u16 = self->duty_u16; pwmSignal.Center_u16 = self->center; pwmSignal.deadtimeValue = ((uint64_t)pwmSourceClockInHz * self->deadtime) / 1000000000ULL; PWM_SetupPwm_u16(self->instance, self->submodule, &pwmSignal, self->freq, pwmSourceClockInHz, self->output_enable_1); if (self->complementary) { // Initialize the second channel of the pair. pwmSignal.pwmChannel = self->channel2; pwmSignal.level = (self->invert & PWM_CHANNEL2) ? kPWM_LowTrue : kPWM_HighTrue; PWM_SetupPwm_u16(self->instance, self->submodule, &pwmSignal, self->freq, pwmSourceClockInHz, self->output_enable_2); } if (self->xor == 1) { // Set the DBLEN bit for A, B = A ^ B self->instance->SM[self->submodule].CTRL &= ~PWM_CTRL_SPLIT_MASK; self->instance->SM[self->submodule].CTRL |= PWM_CTRL_DBLEN_MASK; } else if (self->xor == 2) { // Set the DBLEN and SPLIT bits for A, B = A ^ B self->instance->SM[self->submodule].CTRL |= PWM_CTRL_DBLEN_MASK | PWM_CTRL_SPLIT_MASK; } else { self->instance->SM[self->submodule].CTRL &= ~(PWM_CTRL_DBLEN_MASK | PWM_CTRL_SPLIT_MASK); } } else { PWM_SetupPwmx_u16(self->instance, self->submodule, self->freq, self->duty_u16, self->invert, pwmSourceClockInHz); if (self->xor) { // Set the DBLX bit for X = A ^ B self->instance->SM[self->submodule].CTRL |= PWM_CTRL_DBLX_MASK; } else { self->instance->SM[self->submodule].CTRL &= ~PWM_CTRL_DBLX_MASK; } } // Set the load okay bit for the submodules PWM_SetPwmLdok(self->instance, 1 << self->submodule, true); // Start the PWM generation from the Submodules PWM_StartTimer(self->instance, 1 << self->submodule); } #ifdef FSL_FEATURE_SOC_TMR_COUNT STATIC void configure_qtmr(machine_pwm_obj_t *self) { qtmr_config_t qtmrConfig; int prescale; #if defined(MIMXRT117x_SERIES) uint32_t pwmSourceClockInHz = CLOCK_GetRootClockFreq(kCLOCK_Root_Bus); #else uint32_t pwmSourceClockInHz = CLOCK_GetFreq(kCLOCK_IpgClk); #endif TMR_Type *instance = (TMR_Type *)self->instance; prescale = calc_prescaler(pwmSourceClockInHz, self->freq); if (prescale < 0) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_FREQ)); } if (prescale != self->prescale) { QTMR_GetDefaultConfig(&qtmrConfig); qtmrConfig.primarySource = prescale + kQTMR_ClockDivide_1; QTMR_Init(instance, self->channel1, &qtmrConfig); self->prescale = prescale; } // Set up the PWM channel if (QTMR_SetupPwm_u16(instance, self->channel1, self->freq, self->duty_u16, self->invert, pwmSourceClockInHz / (1 << prescale), self->is_init) == kStatus_Fail) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_INIT)); } // Start the output QTMR_StartTimer(instance, self->channel1, kQTMR_PriSrcRiseEdge); } #endif // FSL_FEATURE_SOC_TMR_COUNT STATIC void configure_pwm(machine_pwm_obj_t *self) { // Set the clock frequencies // Freq range is 15Hz to ~ 3 MHz. static bool set_frequency = true; // set the frequency only once if (set_frequency) { #if !defined(MIMXRT117x_SERIES) CLOCK_SetDiv(kCLOCK_IpgDiv, 0x3); // Set IPG PODF to 3, divide by 4 #endif set_frequency = false; } // Enable the PWM only if both freq and duty value are set if (self->freq != VALUE_NOT_SET && (self->duty_u16 != VALUE_NOT_SET || self->duty_ns != VALUE_NOT_SET)) { if (self->duty_ns != VALUE_NOT_SET) { self->duty_u16 = duty_ns_to_duty_u16(self->freq, self->duty_ns); } if (self->is_flexpwm) { configure_flexpwm(self); #ifdef FSL_FEATURE_SOC_TMR_COUNT } else { configure_qtmr(self); #endif } } } // Micropython API functions // STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_freq, ARG_duty_u16, ARG_duty_ns, ARG_center, ARG_align, ARG_invert, ARG_sync, ARG_xor, ARG_deadtime }; static const mp_arg_t allowed_args[] = { { MP_QSTR_freq, MP_ARG_INT, {.u_int = VALUE_NOT_SET} }, { MP_QSTR_duty_u16, MP_ARG_INT, {.u_int = VALUE_NOT_SET} }, { MP_QSTR_duty_ns, MP_ARG_INT, {.u_int = VALUE_NOT_SET} }, { MP_QSTR_center, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_align, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, { MP_QSTR_sync, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, { MP_QSTR_xor, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, { MP_QSTR_deadtime, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1}}, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); if ((n_args + kw_args->used) > 0 || self->is_init == false) { // Maybe change PWM timer if (args[ARG_freq].u_int > 0) { self->freq = args[ARG_freq].u_int; } // Set duty_u16 cycle? int32_t duty = args[ARG_duty_u16].u_int; if (duty >= 0) { if (duty > PWM_FULL_SCALE) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_VALUE)); } self->duty_u16 = duty; self->duty_ns = VALUE_NOT_SET; } // Set duty_ns value? duty = args[ARG_duty_ns].u_int; if (duty >= 0) { self->duty_ns = duty; self->duty_u16 = VALUE_NOT_SET; } // Set center value? int32_t center = args[ARG_center].u_int; if (center >= 0) { if (center > PWM_FULL_SCALE) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_VALUE)); } self->center = center; } else { // Use alignment setting shortcut if (args[ARG_align].u_int >= 0) { uint8_t align = args[ARG_align].u_int & 3; // limit to 0..3 if (align == PWM_BEGIN) { self->center = self->duty_u16 / 2; } else if (align == PWM_END) { self->center = PWM_FULL_SCALE - self->duty_u16 / 2; } else { self->center = 32768; // Default value: mid. } } } if (args[ARG_invert].u_int >= 0) { self->invert = args[ARG_invert].u_int & (PWM_CHANNEL1 | PWM_CHANNEL2); } if (args[ARG_sync].u_int >= 0) { self->sync = args[ARG_sync].u_int != false && self->submodule != 0; } if (args[ARG_xor].u_int >= 0) { self->xor = args[ARG_xor].u_int & 0x03; } if (args[ARG_deadtime].u_int >= 0) { self->deadtime = args[ARG_deadtime].u_int; } configure_pwm(self); self->is_init = true; } else { mp_machine_pwm_start(self); } } // PWM(pin | pin-tuple, freq, [args]) STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // Check number of arguments mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); mp_obj_t *pins; const machine_pin_obj_t *pin1; const machine_pin_obj_t *pin2; // Get referred Pin object(s) if (mp_obj_is_type(args[0], &mp_type_tuple)) { mp_obj_get_array_fixed_n(args[0], 2, &pins); pin1 = pin_find(pins[0]); pin2 = pin_find(pins[1]); } else { pin1 = pin_find(args[0]); pin2 = NULL; } // Check whether it supports PWM and decode submodule & channel const machine_pin_af_obj_t *af_obj1 = NULL; uint8_t submodule1; uint8_t channel1; const machine_pin_af_obj_t *af_obj2 = NULL; uint8_t submodule2; uint8_t channel2; uint8_t module; bool is_flexpwm = false; for (int i = 0; i < pin1->af_list_len; ++i) { af_obj1 = af_name_decode_flexpwm(&(pin1->af_list[i]), &module, &submodule1, &channel1); if (af_obj1 != NULL) { break; } } if (pin2 != NULL) { for (int i = 0; i < pin1->af_list_len; ++i) { af_obj2 = af_name_decode_flexpwm(&(pin2->af_list[i]), &module, &submodule2, &channel2); if (af_obj2 != NULL) { break; } } if (af_obj2 == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("the second Pin doesn't support PWM")); } } if (af_obj1 == NULL) { submodule1 = 0; #ifdef FSL_FEATURE_SOC_TMR_COUNT // Check for QTimer support if (is_board_pin(pin1)) { for (int i = 0; i < pin1->af_list_len; ++i) { af_obj1 = af_name_decode_qtmr(&(pin1->af_list[i]), &module, &channel1); if (af_obj1 != NULL) { break; } } } #endif if (af_obj1 == NULL) { mp_raise_ValueError(MP_ERROR_TEXT("the first Pin doesn't support PWM")); } } else { // is flexpwm, check for instance match is_flexpwm = true; if (pin2 != NULL && (af_obj1->instance != af_obj2->instance || submodule1 != submodule2)) { mp_raise_ValueError(MP_ERROR_TEXT("the pins must be a A/B pair of a submodule")); } } // Create and populate the PWM object. machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); self->is_flexpwm = is_flexpwm; self->instance = af_obj1->instance; self->module = module; self->submodule = submodule1; self->channel1 = channel1; self->invert = 0; self->freq = VALUE_NOT_SET; self->prescale = -1; self->duty_u16 = VALUE_NOT_SET; self->duty_ns = VALUE_NOT_SET; self->center = 32768; self->output_enable_1 = is_board_pin(pin1); self->sync = false; self->deadtime = 0; self->xor = 0; self->is_init = false; // Initialize the Pin(s). CLOCK_EnableClock(kCLOCK_Iomuxc); // just in case it was not set yet IOMUXC_SetPinMux(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, pin1->configRegister, 0U); IOMUXC_SetPinConfig(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, pin1->configRegister, pin_generate_config(PIN_PULL_DISABLED, PIN_MODE_OUT, PIN_DRIVE_5, pin1->configRegister)); // Settings for the second pin, if given. if (pin2 != NULL && pin2 != pin1) { self->complementary = 1; self->channel2 = channel2; self->output_enable_2 = is_board_pin(pin2); // Initialize the Pin(s) IOMUXC_SetPinMux(pin2->muxRegister, af_obj2->af_mode, af_obj2->input_register, af_obj2->input_daisy, pin2->configRegister, 0U); IOMUXC_SetPinConfig(pin2->muxRegister, af_obj2->af_mode, af_obj2->input_register, af_obj2->input_daisy, pin2->configRegister, pin_generate_config(PIN_PULL_DISABLED, PIN_MODE_OUT, PIN_DRIVE_5, pin2->configRegister)); } else { self->complementary = 0; } // Process the remaining parameters. mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); return MP_OBJ_FROM_PTR(self); } // Disable all PWM devices. Called on soft reset void machine_pwm_deinit_all(void) { static PWM_Type *const pwm_bases[] = PWM_BASE_PTRS; for (int i = 1; i < ARRAY_SIZE(pwm_bases); i++) { PWM_StopTimer(pwm_bases[i], 0x0f); // Stop all submodules pwm_bases[i]->OUTEN = 0; // Disable output on all submodules, all channels } #ifdef FSL_FEATURE_SOC_TMR_COUNT static TMR_Type *const tmr_bases[] = TMR_BASE_PTRS; for (int i = 1; i < ARRAY_SIZE(tmr_bases); i++) { for (int j = 0; j < 4; j++) { QTMR_StopTimer(tmr_bases[i], j); // Stop all timers } } #endif } STATIC void mp_machine_pwm_start(machine_pwm_obj_t *self) { if (self->is_flexpwm) { PWM_StartTimer(self->instance, 1 << self->submodule); #ifdef FSL_FEATURE_SOC_TMR_COUNT } else { QTMR_StartTimer((TMR_Type *)self->instance, self->channel1, kQTMR_PriSrcRiseEdge); #endif } } STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { if (self->is_flexpwm) { PWM_StopTimer(self->instance, 1 << self->submodule); #ifdef FSL_FEATURE_SOC_TMR_COUNT } else { QTMR_StopTimer((TMR_Type *)self->instance, self->channel1); #endif } } mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { return MP_OBJ_NEW_SMALL_INT(self->freq); } void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { self->freq = freq; configure_pwm(self); } mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { return MP_OBJ_NEW_SMALL_INT(self->duty_u16); } void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty) { if (duty >= 0) { if (duty > PWM_FULL_SCALE) { mp_raise_ValueError(MP_ERROR_TEXT(ERRMSG_VALUE)); } self->duty_u16 = duty; self->duty_ns = VALUE_NOT_SET; configure_pwm(self); } } mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { return MP_OBJ_NEW_SMALL_INT(self->duty_ns); } void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty) { if (duty >= 0) { self->duty_ns = duty; self->duty_u16 = VALUE_NOT_SET; configure_pwm(self); } }